From 5ffa24ef718e02878b642454810170789d460fbe Mon Sep 17 00:00:00 2001 From: Packit Service Date: Feb 09 2021 06:15:39 +0000 Subject: NetworkManager-1.30.0 base --- diff --git a/Makefile.am b/Makefile.am index 99d8b21..7ae9648 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,7 +123,7 @@ shared/nm-udev-aux/.dirstamp: config-extra.h shared/systemd/.dirstamp: config-extra.h shared/systemd/src/basic/.dirstamp: config-extra.h shared/systemd/src/shared/.dirstamp: config-extra.h -src/dhcp/.dirstamp: config-extra.h +src/core/dhcp/.dirstamp: config-extra.h ############################################################################### @@ -135,8 +135,8 @@ set_sanitizer_env = \ fi check_so_symbols = \ - $(call set_sanitizer_env,$(1),$(builddir)/src/NetworkManager); \ - LD_BIND_NOW=1 LD_PRELOAD=$${LD_PRELOAD}:$(1) $(builddir)/src/NetworkManager --version >/dev/null + $(call set_sanitizer_env,$(1),$(builddir)/src/core/NetworkManager); \ + LD_BIND_NOW=1 LD_PRELOAD=$${LD_PRELOAD}:$(1) $(builddir)/src/core/NetworkManager --version >/dev/null ############################################################################### @@ -1026,7 +1026,7 @@ dbusinterfaces_DATA = \ CLEANFILES += $(introspection_sources) CLEANFILES += $(DBUS_INTERFACE_DOCS) -$(libnm_liblibnm_la_OBJECTS): $(introspection_sources) +$(libnm_libnm_static_la_OBJECTS): $(introspection_sources) $(libnm_libnm_la_OBJECTS): $(introspection_sources) EXTRA_DIST += \ @@ -1232,13 +1232,13 @@ $(dispatcher_nm_dispatcher_OBJECTS): $(li $(libnm_core_libnm_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_libnm_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_tests_libnm_vpn_plugin_utils_test_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_team_libnm_device_plugin_team_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_team_libnm_device_plugin_team_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) libnm_core_libnm_core_la_CPPFLAGS = \ $(dflt_cppflags_libnm_core) \ @@ -1595,23 +1595,23 @@ nodist_libnminclude_HEADERS += \ ############################################################################### -noinst_LTLIBRARIES += libnm/liblibnm.la +noinst_LTLIBRARIES += libnm/libnm_static.la -libnm_liblibnm_la_CPPFLAGS = \ +libnm_libnm_static_la_CPPFLAGS = \ $(INTROSPECTION_CFLAGS) \ $(libnm_lib_cppflags) \ $(NULL) -libnm_liblibnm_la_SOURCES = \ +libnm_libnm_static_la_SOURCES = \ $(libnm_lib_c_real) \ $(NULL) -nodist_libnm_liblibnm_la_SOURCES = \ +nodist_libnm_libnm_static_la_SOURCES = \ $(libnm_lib_h_pub_mkenums) \ $(libnm_lib_c_mkenums) \ $(NULL) -libnm_liblibnm_la_LIBADD = \ +libnm_libnm_static_la_LIBADD = \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -1631,8 +1631,8 @@ libnm_liblibnm_la_LIBADD = \ $(LIBUDEV_LIBS) \ $(NULL) -$(libnm_liblibnm_la_OBJECTS) : $(libnm_lib_h_pub_mkenums) -$(libnm_liblibnm_la_OBJECTS) : $(libnm_core_lib_h_pub_mkenums) +$(libnm_libnm_static_la_OBJECTS) : $(libnm_lib_h_pub_mkenums) +$(libnm_libnm_static_la_OBJECTS) : $(libnm_core_lib_h_pub_mkenums) ############################################################################### @@ -1667,7 +1667,7 @@ EXTRA_libnm_libnm_la_DEPENDENCIES = \ libnm/libnm.ver libnm_libnm_la_LIBADD = \ - libnm/liblibnm.la \ + libnm/libnm_static.la \ $(NULL) libnm_libnm_la_LDFLAGS = \ @@ -1806,7 +1806,7 @@ libnm_tests_cppflags = \ $(NULL) libnm_tests_ldadd = \ - libnm/liblibnm.la \ + libnm/libnm_static.la \ $(GLIB_LIBS) \ $(NULL) @@ -1892,26 +1892,26 @@ EXTRA_DIST += \ libnm/tests/meson.build ############################################################################### -# src/ +# src/core/ ############################################################################### -src_cppflags_base = \ - -I$(srcdir)/src \ +src_core_cppflags_base = \ + -I$(srcdir)/src/core \ $(dflt_cppflags_libnm_core) \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON \ $(SANITIZER_EXEC_CFLAGS) \ $(NULL) -src_cppflags_base_test = \ - $(src_cppflags_base) \ +src_core_cppflags_base_test = \ + $(src_core_cppflags_base) \ -DNETWORKMANAGER_COMPILATION_TEST \ $(NULL) -src_cppflags_device_plugin = \ - $(src_cppflags_base) +src_core_cppflags_device_plugin = \ + $(src_core_cppflags_base) -src_cppflags = \ - $(src_cppflags_base) \ +src_core_cppflags = \ + $(src_core_cppflags_base) \ \ $(LIBNDP_CFLAGS) \ $(LIBPSL_CFLAGS) \ @@ -1923,27 +1923,27 @@ src_cppflags = \ \ $(NULL) -src_cppflags_test = \ - $(src_cppflags) \ +src_core_cppflags_test = \ + $(src_core_cppflags) \ -DNETWORKMANAGER_COMPILATION_TEST \ $(NULL) if REQUIRE_ROOT_TESTS -src_cppflags_test += -DREQUIRE_ROOT_TESTS=1 +src_core_cppflags_test += -DREQUIRE_ROOT_TESTS=1 endif -src_ldflags = $(CODE_COVERAGE_LDFLAGS) +src_core_ldflags = $(CODE_COVERAGE_LDFLAGS) sbin_PROGRAMS += \ - src/NetworkManager + src/core/NetworkManager libexec_PROGRAMS += \ - src/nm-iface-helper + src/core/nm-iface-helper noinst_LTLIBRARIES += \ - src/libNetworkManagerBase.la \ - src/libNetworkManager.la \ - src/libnm-systemd-core.la \ + src/core/libNetworkManagerBase.la \ + src/core/libNetworkManager.la \ + src/core/libnm-systemd-core.la \ $(NULL) check-config-options: @@ -2128,373 +2128,373 @@ shared_systemd_libnm_systemd_shared_la_LIBADD = \ ############################################################################### -src_libnm_systemd_core_la_cppflags = \ +src_core_libnm_systemd_core_la_cppflags = \ $(libsystemd_cppflags) \ -I$(srcdir)/libnm-core \ -I$(builddir)/libnm-core \ - -I$(srcdir)/src \ - -I$(srcdir)/src/systemd/sd-adapt-core \ - -I$(srcdir)/src/systemd/src/systemd \ - -I$(srcdir)/src/systemd/src/libsystemd-network \ - -I$(srcdir)/src/systemd/src/libsystemd/sd-event \ + -I$(srcdir)/src/core \ + -I$(srcdir)/src/core/systemd/sd-adapt-core \ + -I$(srcdir)/src/core/systemd/src/systemd \ + -I$(srcdir)/src/core/systemd/src/libsystemd-network \ + -I$(srcdir)/src/core/systemd/src/libsystemd/sd-event \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD \ $(NULL) -src_libnm_systemd_core_la_libadd = \ +src_core_libnm_systemd_core_la_libadd = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) -src_libnm_systemd_core_la_SOURCES = \ - src/systemd/nm-sd-utils-core.c \ - src/systemd/nm-sd-utils-core.h \ - src/systemd/nm-sd.c \ - src/systemd/nm-sd.h \ - src/systemd/nm-sd-utils-dhcp.h \ - src/systemd/nm-sd-utils-dhcp.c \ - src/systemd/sd-adapt-core/condition.h \ - src/systemd/sd-adapt-core/conf-parser.h \ - src/systemd/sd-adapt-core/device-util.h \ - src/systemd/sd-adapt-core/khash.h \ - src/systemd/sd-adapt-core/network-util.h \ - src/systemd/sd-adapt-core/nm-sd-adapt-core.c \ - src/systemd/sd-adapt-core/nm-sd-adapt-core.h \ - src/systemd/sd-adapt-core/sd-daemon.h \ - src/systemd/sd-adapt-core/sd-device.h \ - src/systemd/sd-adapt-core/udev-util.h \ - src/systemd/src/libsystemd-network/arp-util.c \ - src/systemd/src/libsystemd-network/arp-util.h \ - src/systemd/src/libsystemd-network/dhcp-identifier.c \ - src/systemd/src/libsystemd-network/dhcp-identifier.h \ - src/systemd/src/libsystemd-network/dhcp-internal.h \ - src/systemd/src/libsystemd-network/dhcp-lease-internal.h \ - src/systemd/src/libsystemd-network/dhcp-network.c \ - src/systemd/src/libsystemd-network/dhcp-option.c \ - src/systemd/src/libsystemd-network/dhcp-packet.c \ - src/systemd/src/libsystemd-network/dhcp-protocol.h \ - src/systemd/src/libsystemd-network/dhcp6-internal.h \ - src/systemd/src/libsystemd-network/dhcp6-lease-internal.h \ - src/systemd/src/libsystemd-network/dhcp6-network.c \ - src/systemd/src/libsystemd-network/dhcp6-option.c \ - src/systemd/src/libsystemd-network/dhcp6-protocol.h \ - src/systemd/src/libsystemd-network/lldp-internal.h \ - src/systemd/src/libsystemd-network/lldp-neighbor.c \ - src/systemd/src/libsystemd-network/lldp-neighbor.h \ - src/systemd/src/libsystemd-network/lldp-network.c \ - src/systemd/src/libsystemd-network/lldp-network.h \ - src/systemd/src/libsystemd-network/network-internal.c \ - src/systemd/src/libsystemd-network/network-internal.h \ - src/systemd/src/libsystemd-network/sd-dhcp-client.c \ - src/systemd/src/libsystemd-network/sd-dhcp-lease.c \ - src/systemd/src/libsystemd-network/sd-dhcp6-client.c \ - src/systemd/src/libsystemd-network/sd-dhcp6-lease.c \ - src/systemd/src/libsystemd-network/sd-ipv4acd.c \ - src/systemd/src/libsystemd-network/sd-ipv4ll.c \ - src/systemd/src/libsystemd-network/sd-lldp.c \ - src/systemd/src/libsystemd/sd-event/event-source.h \ - src/systemd/src/libsystemd/sd-event/event-util.c \ - src/systemd/src/libsystemd/sd-event/event-util.h \ - src/systemd/src/libsystemd/sd-event/sd-event.c \ - src/systemd/src/libsystemd/sd-id128/id128-util.c \ - src/systemd/src/libsystemd/sd-id128/id128-util.h \ - src/systemd/src/libsystemd/sd-id128/sd-id128.c \ - src/systemd/src/systemd/_sd-common.h \ - src/systemd/src/systemd/sd-dhcp-client.h \ - src/systemd/src/systemd/sd-dhcp-lease.h \ - src/systemd/src/systemd/sd-dhcp-option.h \ - src/systemd/src/systemd/sd-dhcp6-client.h \ - src/systemd/src/systemd/sd-dhcp6-lease.h \ - src/systemd/src/systemd/sd-dhcp6-option.h \ - src/systemd/src/systemd/sd-event.h \ - src/systemd/src/systemd/sd-id128.h \ - src/systemd/src/systemd/sd-ipv4acd.h \ - src/systemd/src/systemd/sd-ipv4ll.h \ - src/systemd/src/systemd/sd-lldp.h \ - src/systemd/src/systemd/sd-ndisc.h \ - $(NULL) - -src_libnm_systemd_core_la_CPPFLAGS = $(src_libnm_systemd_core_la_cppflags) -src_libnm_systemd_core_la_LIBADD = \ +src_core_libnm_systemd_core_la_SOURCES = \ + src/core/systemd/nm-sd-utils-core.c \ + src/core/systemd/nm-sd-utils-core.h \ + src/core/systemd/nm-sd.c \ + src/core/systemd/nm-sd.h \ + src/core/systemd/nm-sd-utils-dhcp.h \ + src/core/systemd/nm-sd-utils-dhcp.c \ + src/core/systemd/sd-adapt-core/condition.h \ + src/core/systemd/sd-adapt-core/conf-parser.h \ + src/core/systemd/sd-adapt-core/device-util.h \ + src/core/systemd/sd-adapt-core/khash.h \ + src/core/systemd/sd-adapt-core/network-util.h \ + src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c \ + src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h \ + src/core/systemd/sd-adapt-core/sd-daemon.h \ + src/core/systemd/sd-adapt-core/sd-device.h \ + src/core/systemd/sd-adapt-core/udev-util.h \ + src/core/systemd/src/libsystemd-network/arp-util.c \ + src/core/systemd/src/libsystemd-network/arp-util.h \ + src/core/systemd/src/libsystemd-network/dhcp-identifier.c \ + src/core/systemd/src/libsystemd-network/dhcp-identifier.h \ + src/core/systemd/src/libsystemd-network/dhcp-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp-network.c \ + src/core/systemd/src/libsystemd-network/dhcp-option.c \ + src/core/systemd/src/libsystemd-network/dhcp-packet.c \ + src/core/systemd/src/libsystemd-network/dhcp-protocol.h \ + src/core/systemd/src/libsystemd-network/dhcp6-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp6-network.c \ + src/core/systemd/src/libsystemd-network/dhcp6-option.c \ + src/core/systemd/src/libsystemd-network/dhcp6-protocol.h \ + src/core/systemd/src/libsystemd-network/lldp-internal.h \ + src/core/systemd/src/libsystemd-network/lldp-neighbor.c \ + src/core/systemd/src/libsystemd-network/lldp-neighbor.h \ + src/core/systemd/src/libsystemd-network/lldp-network.c \ + src/core/systemd/src/libsystemd-network/lldp-network.h \ + src/core/systemd/src/libsystemd-network/network-internal.c \ + src/core/systemd/src/libsystemd-network/network-internal.h \ + src/core/systemd/src/libsystemd-network/sd-dhcp-client.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c \ + src/core/systemd/src/libsystemd-network/sd-ipv4acd.c \ + src/core/systemd/src/libsystemd-network/sd-ipv4ll.c \ + src/core/systemd/src/libsystemd-network/sd-lldp.c \ + src/core/systemd/src/libsystemd/sd-event/event-source.h \ + src/core/systemd/src/libsystemd/sd-event/event-util.c \ + src/core/systemd/src/libsystemd/sd-event/event-util.h \ + src/core/systemd/src/libsystemd/sd-event/sd-event.c \ + src/core/systemd/src/libsystemd/sd-id128/id128-util.c \ + src/core/systemd/src/libsystemd/sd-id128/id128-util.h \ + src/core/systemd/src/libsystemd/sd-id128/sd-id128.c \ + src/core/systemd/src/systemd/_sd-common.h \ + src/core/systemd/src/systemd/sd-dhcp-client.h \ + src/core/systemd/src/systemd/sd-dhcp-lease.h \ + src/core/systemd/src/systemd/sd-dhcp-option.h \ + src/core/systemd/src/systemd/sd-dhcp6-client.h \ + src/core/systemd/src/systemd/sd-dhcp6-lease.h \ + src/core/systemd/src/systemd/sd-dhcp6-option.h \ + src/core/systemd/src/systemd/sd-event.h \ + src/core/systemd/src/systemd/sd-id128.h \ + src/core/systemd/src/systemd/sd-ipv4acd.h \ + src/core/systemd/src/systemd/sd-ipv4ll.h \ + src/core/systemd/src/systemd/sd-lldp.h \ + src/core/systemd/src/systemd/sd-ndisc.h \ + $(NULL) + +src_core_libnm_systemd_core_la_CPPFLAGS = $(src_core_libnm_systemd_core_la_cppflags) +src_core_libnm_systemd_core_la_LIBADD = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) -$(src_libnm_systemd_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libnm_systemd_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/systemd/meson.build + src/core/systemd/meson.build ############################################################################### -src_libNetworkManagerBase_la_CPPFLAGS = \ +src_core_libNetworkManagerBase_la_CPPFLAGS = \ $(libsystemd_cppflags) \ - $(src_cppflags) + $(src_core_cppflags) -src_libNetworkManagerBase_la_SOURCES = \ +src_core_libNetworkManagerBase_la_SOURCES = \ \ - src/nm-core-utils.c \ - src/nm-core-utils.h \ + src/core/nm-core-utils.c \ + src/core/nm-core-utils.h \ \ - src/NetworkManagerUtils.c \ - src/NetworkManagerUtils.h \ + src/core/NetworkManagerUtils.c \ + src/core/NetworkManagerUtils.h \ \ - src/platform/nmp-object.c \ - src/platform/nmp-object.h \ - src/platform/nm-platform.c \ - src/platform/nm-platform.h \ - src/platform/nm-platform-private.h \ - src/platform/nm-linux-platform.c \ - src/platform/nm-linux-platform.h \ - src/platform/nmp-rules-manager.c \ - src/platform/nmp-rules-manager.h \ - src/platform/wifi/nm-wifi-utils-nl80211.c \ - src/platform/wifi/nm-wifi-utils-nl80211.h \ - src/platform/wifi/nm-wifi-utils-private.h \ - src/platform/wifi/nm-wifi-utils.c \ - src/platform/wifi/nm-wifi-utils.h \ - src/platform/wpan/nm-wpan-utils.c \ - src/platform/wpan/nm-wpan-utils.h \ + src/core/platform/nmp-object.c \ + src/core/platform/nmp-object.h \ + src/core/platform/nm-platform.c \ + src/core/platform/nm-platform.h \ + src/core/platform/nm-platform-private.h \ + src/core/platform/nm-linux-platform.c \ + src/core/platform/nm-linux-platform.h \ + src/core/platform/nmp-rules-manager.c \ + src/core/platform/nmp-rules-manager.h \ + src/core/platform/wifi/nm-wifi-utils-nl80211.c \ + src/core/platform/wifi/nm-wifi-utils-nl80211.h \ + src/core/platform/wifi/nm-wifi-utils-private.h \ + src/core/platform/wifi/nm-wifi-utils.c \ + src/core/platform/wifi/nm-wifi-utils.h \ + src/core/platform/wpan/nm-wpan-utils.c \ + src/core/platform/wpan/nm-wpan-utils.h \ \ - src/ndisc/nm-lndp-ndisc.c \ - src/ndisc/nm-lndp-ndisc.h \ - src/ndisc/nm-ndisc.c \ - src/ndisc/nm-ndisc.h \ - src/ndisc/nm-ndisc-private.h \ + src/core/ndisc/nm-lndp-ndisc.c \ + src/core/ndisc/nm-lndp-ndisc.h \ + src/core/ndisc/nm-ndisc.c \ + src/core/ndisc/nm-ndisc.h \ + src/core/ndisc/nm-ndisc-private.h \ \ - src/nm-dbus-utils.c \ - src/nm-dbus-utils.h \ - src/nm-dbus-object.c \ - src/nm-dbus-object.h \ - src/nm-netns.c \ - src/nm-netns.h \ - src/nm-l3-config-data.c \ - src/nm-l3-config-data.h \ - src/nm-l3-ipv4ll.c \ - src/nm-l3-ipv4ll.h \ - src/nm-l3cfg.c \ - src/nm-l3cfg.h \ - src/nm-ip-config.c \ - src/nm-ip-config.h \ - src/nm-ip4-config.c \ - src/nm-ip4-config.h \ - src/nm-ip6-config.c \ - src/nm-ip6-config.h \ + src/core/nm-dbus-utils.c \ + src/core/nm-dbus-utils.h \ + src/core/nm-dbus-object.c \ + src/core/nm-dbus-object.h \ + src/core/nm-netns.c \ + src/core/nm-netns.h \ + src/core/nm-l3-config-data.c \ + src/core/nm-l3-config-data.h \ + src/core/nm-l3-ipv4ll.c \ + src/core/nm-l3-ipv4ll.h \ + src/core/nm-l3cfg.c \ + src/core/nm-l3cfg.h \ + src/core/nm-ip-config.c \ + src/core/nm-ip-config.h \ + src/core/nm-ip4-config.c \ + src/core/nm-ip4-config.h \ + src/core/nm-ip6-config.c \ + src/core/nm-ip6-config.h \ \ - src/dhcp/nm-dhcp-client.c \ - src/dhcp/nm-dhcp-client.h \ - src/dhcp/nm-dhcp-client-logging.h \ - src/dhcp/nm-dhcp-nettools.c \ - src/dhcp/nm-dhcp-utils.c \ - src/dhcp/nm-dhcp-utils.h \ - src/dhcp/nm-dhcp-options.c \ - src/dhcp/nm-dhcp-options.h \ - src/dhcp/nm-dhcp-systemd.c \ - src/dhcp/nm-dhcp-manager.c \ - src/dhcp/nm-dhcp-manager.h \ + src/core/dhcp/nm-dhcp-client.c \ + src/core/dhcp/nm-dhcp-client.h \ + src/core/dhcp/nm-dhcp-client-logging.h \ + src/core/dhcp/nm-dhcp-nettools.c \ + src/core/dhcp/nm-dhcp-utils.c \ + src/core/dhcp/nm-dhcp-utils.h \ + src/core/dhcp/nm-dhcp-options.c \ + src/core/dhcp/nm-dhcp-options.h \ + src/core/dhcp/nm-dhcp-systemd.c \ + src/core/dhcp/nm-dhcp-manager.c \ + src/core/dhcp/nm-dhcp-manager.h \ \ - src/main-utils.c \ - src/main-utils.h \ + src/core/main-utils.c \ + src/core/main-utils.h \ \ $(NULL) if WITH_WEXT -src_libNetworkManagerBase_la_SOURCES += \ - src/platform/wifi/nm-wifi-utils-wext.c \ - src/platform/wifi/nm-wifi-utils-wext.h +src_core_libNetworkManagerBase_la_SOURCES += \ + src/core/platform/wifi/nm-wifi-utils-wext.c \ + src/core/platform/wifi/nm-wifi-utils-wext.h endif -src_libNetworkManagerBase_la_LIBADD = \ +src_core_libNetworkManagerBase_la_LIBADD = \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) \ $(NULL) -$(src_libNetworkManagerBase_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManagerBase_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/platform/linux/nl802154.h + src/core/platform/linux/nl802154.h ############################################################################### -src_libNetworkManager_la_CPPFLAGS = $(src_cppflags) +src_core_libNetworkManager_la_CPPFLAGS = $(src_core_cppflags) -src_libNetworkManager_la_SOURCES = \ +src_core_libNetworkManager_la_SOURCES = \ \ - src/nm-checkpoint.c \ - src/nm-checkpoint.h \ - src/nm-checkpoint-manager.c \ - src/nm-checkpoint-manager.h \ + src/core/nm-checkpoint.c \ + src/core/nm-checkpoint.h \ + src/core/nm-checkpoint-manager.c \ + src/core/nm-checkpoint-manager.h \ \ - src/devices/nm-acd-manager.c \ - src/devices/nm-acd-manager.h \ - src/devices/nm-lldp-listener.c \ - src/devices/nm-lldp-listener.h \ - src/devices/nm-device.c \ - src/devices/nm-device.h \ - src/devices/nm-device-ethernet-utils.c \ - src/devices/nm-device-ethernet-utils.h \ - src/devices/nm-device-factory.c \ - src/devices/nm-device-factory.h \ - src/devices/nm-device-generic.c \ - src/devices/nm-device-generic.h \ - src/devices/nm-device-logging.h \ - src/devices/nm-device-private.h \ + src/core/devices/nm-acd-manager.c \ + src/core/devices/nm-acd-manager.h \ + src/core/devices/nm-lldp-listener.c \ + src/core/devices/nm-lldp-listener.h \ + src/core/devices/nm-device.c \ + src/core/devices/nm-device.h \ + src/core/devices/nm-device-ethernet-utils.c \ + src/core/devices/nm-device-ethernet-utils.h \ + src/core/devices/nm-device-factory.c \ + src/core/devices/nm-device-factory.h \ + src/core/devices/nm-device-generic.c \ + src/core/devices/nm-device-generic.h \ + src/core/devices/nm-device-logging.h \ + src/core/devices/nm-device-private.h \ \ - src/devices/nm-device-6lowpan.c \ - src/devices/nm-device-6lowpan.h \ - src/devices/nm-device-bond.c \ - src/devices/nm-device-bond.h \ - src/devices/nm-device-bridge.c \ - src/devices/nm-device-bridge.h \ - src/devices/nm-device-dummy.c \ - src/devices/nm-device-dummy.h \ - src/devices/nm-device-ethernet.c \ - src/devices/nm-device-ethernet.h \ - src/devices/nm-device-infiniband.c \ - src/devices/nm-device-infiniband.h \ - src/devices/nm-device-ip-tunnel.c \ - src/devices/nm-device-ip-tunnel.h \ - src/devices/nm-device-macsec.c \ - src/devices/nm-device-macsec.h \ - src/devices/nm-device-macvlan.c \ - src/devices/nm-device-macvlan.h \ - src/devices/nm-device-ppp.c \ - src/devices/nm-device-ppp.h \ - src/devices/nm-device-tun.c \ - src/devices/nm-device-tun.h \ - src/devices/nm-device-veth.c \ - src/devices/nm-device-veth.h \ - src/devices/nm-device-vlan.c \ - src/devices/nm-device-vlan.h \ - src/devices/nm-device-vrf.c \ - src/devices/nm-device-vrf.h \ - src/devices/nm-device-vxlan.c \ - src/devices/nm-device-vxlan.h \ - src/devices/nm-device-wireguard.c \ - src/devices/nm-device-wireguard.h \ - src/devices/nm-device-wpan.c \ - src/devices/nm-device-wpan.h \ + src/core/devices/nm-device-6lowpan.c \ + src/core/devices/nm-device-6lowpan.h \ + src/core/devices/nm-device-bond.c \ + src/core/devices/nm-device-bond.h \ + src/core/devices/nm-device-bridge.c \ + src/core/devices/nm-device-bridge.h \ + src/core/devices/nm-device-dummy.c \ + src/core/devices/nm-device-dummy.h \ + src/core/devices/nm-device-ethernet.c \ + src/core/devices/nm-device-ethernet.h \ + src/core/devices/nm-device-infiniband.c \ + src/core/devices/nm-device-infiniband.h \ + src/core/devices/nm-device-ip-tunnel.c \ + src/core/devices/nm-device-ip-tunnel.h \ + src/core/devices/nm-device-macsec.c \ + src/core/devices/nm-device-macsec.h \ + src/core/devices/nm-device-macvlan.c \ + src/core/devices/nm-device-macvlan.h \ + src/core/devices/nm-device-ppp.c \ + src/core/devices/nm-device-ppp.h \ + src/core/devices/nm-device-tun.c \ + src/core/devices/nm-device-tun.h \ + src/core/devices/nm-device-veth.c \ + src/core/devices/nm-device-veth.h \ + src/core/devices/nm-device-vlan.c \ + src/core/devices/nm-device-vlan.h \ + src/core/devices/nm-device-vrf.c \ + src/core/devices/nm-device-vrf.h \ + src/core/devices/nm-device-vxlan.c \ + src/core/devices/nm-device-vxlan.h \ + src/core/devices/nm-device-wireguard.c \ + src/core/devices/nm-device-wireguard.h \ + src/core/devices/nm-device-wpan.c \ + src/core/devices/nm-device-wpan.h \ \ - src/dhcp/nm-dhcp-dhcpcanon.c \ - src/dhcp/nm-dhcp-dhclient.c \ - src/dhcp/nm-dhcp-dhcpcd.c \ - src/dhcp/nm-dhcp-helper-api.h \ - src/dhcp/nm-dhcp-listener.c \ - src/dhcp/nm-dhcp-listener.h \ - src/dhcp/nm-dhcp-dhclient-utils.c \ - src/dhcp/nm-dhcp-dhclient-utils.h \ + src/core/dhcp/nm-dhcp-dhcpcanon.c \ + src/core/dhcp/nm-dhcp-dhclient.c \ + src/core/dhcp/nm-dhcp-dhcpcd.c \ + src/core/dhcp/nm-dhcp-helper-api.h \ + src/core/dhcp/nm-dhcp-listener.c \ + src/core/dhcp/nm-dhcp-listener.h \ + src/core/dhcp/nm-dhcp-dhclient-utils.c \ + src/core/dhcp/nm-dhcp-dhclient-utils.h \ \ - src/dns/nm-dns-manager.c \ - src/dns/nm-dns-manager.h \ - src/dns/nm-dns-plugin.c \ - src/dns/nm-dns-plugin.h \ - src/dns/nm-dns-dnsmasq.c \ - src/dns/nm-dns-dnsmasq.h \ - src/dns/nm-dns-systemd-resolved.c \ - src/dns/nm-dns-systemd-resolved.h \ - src/dns/nm-dns-unbound.c \ - src/dns/nm-dns-unbound.h \ + src/core/dns/nm-dns-manager.c \ + src/core/dns/nm-dns-manager.h \ + src/core/dns/nm-dns-plugin.c \ + src/core/dns/nm-dns-plugin.h \ + src/core/dns/nm-dns-dnsmasq.c \ + src/core/dns/nm-dns-dnsmasq.h \ + src/core/dns/nm-dns-systemd-resolved.c \ + src/core/dns/nm-dns-systemd-resolved.h \ + src/core/dns/nm-dns-unbound.c \ + src/core/dns/nm-dns-unbound.h \ \ - src/dnsmasq/nm-dnsmasq-manager.c \ - src/dnsmasq/nm-dnsmasq-manager.h \ - src/dnsmasq/nm-dnsmasq-utils.c \ - src/dnsmasq/nm-dnsmasq-utils.h \ + src/core/dnsmasq/nm-dnsmasq-manager.c \ + src/core/dnsmasq/nm-dnsmasq-manager.h \ + src/core/dnsmasq/nm-dnsmasq-utils.c \ + src/core/dnsmasq/nm-dnsmasq-utils.h \ \ - src/ppp/nm-ppp-manager-call.c \ - src/ppp/nm-ppp-manager-call.h \ - src/ppp/nm-ppp-manager.h \ - src/ppp/nm-ppp-status.h \ + src/core/ppp/nm-ppp-manager-call.c \ + src/core/ppp/nm-ppp-manager-call.h \ + src/core/ppp/nm-ppp-manager.h \ + src/core/ppp/nm-ppp-status.h \ \ - src/nm-hostname-manager.c \ - src/nm-hostname-manager.h \ + src/core/nm-hostname-manager.c \ + src/core/nm-hostname-manager.h \ \ - src/settings/nm-agent-manager.c \ - src/settings/nm-agent-manager.h \ - src/settings/nm-secret-agent.c \ - src/settings/nm-secret-agent.h \ - src/settings/nm-settings-connection.c \ - src/settings/nm-settings-connection.h \ - src/settings/nm-settings-storage.c \ - src/settings/nm-settings-storage.h \ - src/settings/nm-settings-plugin.c \ - src/settings/nm-settings-plugin.h \ - src/settings/nm-settings.c \ - src/settings/nm-settings.h \ - src/settings/nm-settings-utils.c \ - src/settings/nm-settings-utils.h \ + src/core/settings/nm-agent-manager.c \ + src/core/settings/nm-agent-manager.h \ + src/core/settings/nm-secret-agent.c \ + src/core/settings/nm-secret-agent.h \ + src/core/settings/nm-settings-connection.c \ + src/core/settings/nm-settings-connection.h \ + src/core/settings/nm-settings-storage.c \ + src/core/settings/nm-settings-storage.h \ + src/core/settings/nm-settings-plugin.c \ + src/core/settings/nm-settings-plugin.h \ + src/core/settings/nm-settings.c \ + src/core/settings/nm-settings.h \ + src/core/settings/nm-settings-utils.c \ + src/core/settings/nm-settings-utils.h \ \ - src/settings/plugins/keyfile/nms-keyfile-storage.c \ - src/settings/plugins/keyfile/nms-keyfile-storage.h \ - src/settings/plugins/keyfile/nms-keyfile-plugin.c \ - src/settings/plugins/keyfile/nms-keyfile-plugin.h \ - src/settings/plugins/keyfile/nms-keyfile-reader.c \ - src/settings/plugins/keyfile/nms-keyfile-reader.h \ - src/settings/plugins/keyfile/nms-keyfile-utils.c \ - src/settings/plugins/keyfile/nms-keyfile-utils.h \ - src/settings/plugins/keyfile/nms-keyfile-writer.c \ - src/settings/plugins/keyfile/nms-keyfile-writer.h \ + src/core/settings/plugins/keyfile/nms-keyfile-storage.c \ + src/core/settings/plugins/keyfile/nms-keyfile-storage.h \ + src/core/settings/plugins/keyfile/nms-keyfile-plugin.c \ + src/core/settings/plugins/keyfile/nms-keyfile-plugin.h \ + src/core/settings/plugins/keyfile/nms-keyfile-reader.c \ + src/core/settings/plugins/keyfile/nms-keyfile-reader.h \ + src/core/settings/plugins/keyfile/nms-keyfile-utils.c \ + src/core/settings/plugins/keyfile/nms-keyfile-utils.h \ + src/core/settings/plugins/keyfile/nms-keyfile-writer.c \ + src/core/settings/plugins/keyfile/nms-keyfile-writer.h \ \ - src/supplicant/nm-supplicant-config.c \ - src/supplicant/nm-supplicant-config.h \ - src/supplicant/nm-supplicant-interface.c \ - src/supplicant/nm-supplicant-interface.h \ - src/supplicant/nm-supplicant-manager.c \ - src/supplicant/nm-supplicant-manager.h \ - src/supplicant/nm-supplicant-settings-verify.c \ - src/supplicant/nm-supplicant-settings-verify.h \ - src/supplicant/nm-supplicant-types.h \ + src/core/supplicant/nm-supplicant-config.c \ + src/core/supplicant/nm-supplicant-config.h \ + src/core/supplicant/nm-supplicant-interface.c \ + src/core/supplicant/nm-supplicant-interface.h \ + src/core/supplicant/nm-supplicant-manager.c \ + src/core/supplicant/nm-supplicant-manager.h \ + src/core/supplicant/nm-supplicant-settings-verify.c \ + src/core/supplicant/nm-supplicant-settings-verify.h \ + src/core/supplicant/nm-supplicant-types.h \ \ - src/vpn/nm-vpn-connection.c \ - src/vpn/nm-vpn-connection.h \ - src/vpn/nm-vpn-manager.c \ - src/vpn/nm-vpn-manager.h \ + src/core/vpn/nm-vpn-connection.c \ + src/core/vpn/nm-vpn-connection.h \ + src/core/vpn/nm-vpn-manager.c \ + src/core/vpn/nm-vpn-manager.h \ \ - src/nm-act-request.c \ - src/nm-act-request.h \ - src/nm-active-connection.c \ - src/nm-active-connection.h \ - src/nm-audit-manager.c \ - src/nm-audit-manager.h \ - src/nm-dbus-manager.c \ - src/nm-dbus-manager.h \ - src/nm-config.c \ - src/nm-config.h \ - src/nm-config-data.c \ - src/nm-config-data.h \ - src/nm-connectivity.c \ - src/nm-connectivity.h \ - src/nm-dcb.c \ - src/nm-dcb.h \ - src/nm-dhcp-config.c \ - src/nm-dhcp-config.h \ - src/nm-dispatcher.c \ - src/nm-dispatcher.h \ - src/nm-firewall-manager.c \ - src/nm-firewall-manager.h \ - src/nm-proxy-config.c \ - src/nm-proxy-config.h \ - src/nm-auth-manager.c \ - src/nm-auth-manager.h \ - src/nm-auth-utils.c \ - src/nm-auth-utils.h \ - src/nm-manager.c \ - src/nm-manager.h \ - src/nm-pacrunner-manager.c \ - src/nm-pacrunner-manager.h \ - src/nm-policy.c \ - src/nm-policy.h \ - src/nm-rfkill-manager.c \ - src/nm-rfkill-manager.h \ - src/nm-session-monitor.h \ - src/nm-session-monitor.c \ - src/nm-keep-alive.c \ - src/nm-keep-alive.h \ - src/nm-sleep-monitor.c \ - src/nm-sleep-monitor.h \ - src/nm-types.h \ + src/core/nm-act-request.c \ + src/core/nm-act-request.h \ + src/core/nm-active-connection.c \ + src/core/nm-active-connection.h \ + src/core/nm-audit-manager.c \ + src/core/nm-audit-manager.h \ + src/core/nm-dbus-manager.c \ + src/core/nm-dbus-manager.h \ + src/core/nm-config.c \ + src/core/nm-config.h \ + src/core/nm-config-data.c \ + src/core/nm-config-data.h \ + src/core/nm-connectivity.c \ + src/core/nm-connectivity.h \ + src/core/nm-dcb.c \ + src/core/nm-dcb.h \ + src/core/nm-dhcp-config.c \ + src/core/nm-dhcp-config.h \ + src/core/nm-dispatcher.c \ + src/core/nm-dispatcher.h \ + src/core/nm-firewall-manager.c \ + src/core/nm-firewall-manager.h \ + src/core/nm-proxy-config.c \ + src/core/nm-proxy-config.h \ + src/core/nm-auth-manager.c \ + src/core/nm-auth-manager.h \ + src/core/nm-auth-utils.c \ + src/core/nm-auth-utils.h \ + src/core/nm-manager.c \ + src/core/nm-manager.h \ + src/core/nm-pacrunner-manager.c \ + src/core/nm-pacrunner-manager.h \ + src/core/nm-policy.c \ + src/core/nm-policy.h \ + src/core/nm-rfkill-manager.c \ + src/core/nm-rfkill-manager.h \ + src/core/nm-session-monitor.h \ + src/core/nm-session-monitor.c \ + src/core/nm-keep-alive.c \ + src/core/nm-keep-alive.h \ + src/core/nm-sleep-monitor.c \ + src/core/nm-sleep-monitor.h \ + src/core/nm-types.h \ \ $(NULL) -src_libNetworkManager_la_LIBADD = \ - src/libNetworkManagerBase.la \ +src_core_libNetworkManager_la_LIBADD = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -2506,7 +2506,7 @@ src_libNetworkManager_la_LIBADD = \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ - src/libnm-systemd-core.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libnacd.la \ shared/libndhcp4.la \ @@ -2524,91 +2524,91 @@ src_libNetworkManager_la_LIBADD = \ $(LIBCURL_LIBS) \ $(NULL) -$(src_libNetworkManager_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManager_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -check_ltlibraries += src/libNetworkManagerTest.la +check_ltlibraries += src/core/libNetworkManagerTest.la -src_tests_cppflags_fake = $(src_cppflags_test) -DSETUP=nm_fake_platform_setup -src_tests_cppflags_linux = $(src_cppflags_test) -DSETUP=nm_linux_platform_setup +src_core_tests_cppflags_fake = $(src_core_cppflags_test) -DSETUP=nm_fake_platform_setup +src_core_tests_cppflags_linux = $(src_core_cppflags_test) -DSETUP=nm_linux_platform_setup -src_libNetworkManagerTest_la_CPPFLAGS = $(src_cppflags_test) +src_core_libNetworkManagerTest_la_CPPFLAGS = $(src_core_cppflags_test) -src_libNetworkManagerTest_la_SOURCES = \ - src/ndisc/nm-fake-ndisc.c \ - src/ndisc/nm-fake-ndisc.h \ - src/platform/nm-fake-platform.c \ - src/platform/nm-fake-platform.h \ - src/platform/tests/test-common.c \ - src/platform/tests/test-common.h \ +src_core_libNetworkManagerTest_la_SOURCES = \ + src/core/ndisc/nm-fake-ndisc.c \ + src/core/ndisc/nm-fake-ndisc.h \ + src/core/platform/nm-fake-platform.c \ + src/core/platform/nm-fake-platform.h \ + src/core/platform/tests/test-common.c \ + src/core/platform/tests/test-common.h \ $(NULL) -src_libNetworkManagerTest_la_LIBADD = \ - src/libNetworkManager.la \ +src_core_libNetworkManagerTest_la_LIBADD = \ + src/core/libNetworkManager.la \ $(CODE_COVERAGE_LDFLAGS) \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) \ $(NULL) -$(src_libNetworkManagerTest_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManagerTest_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -noinst_PROGRAMS += src/NetworkManager-all-sym +noinst_PROGRAMS += src/core/NetworkManager-all-sym -src_NetworkManager_all_sym_CPPFLAGS = $(src_cppflags) +src_core_NetworkManager_all_sym_CPPFLAGS = $(src_core_cppflags) -src_NetworkManager_all_sym_SOURCES = \ - src/main.c +src_core_NetworkManager_all_sym_SOURCES = \ + src/core/main.c -src_NetworkManager_all_sym_LDADD = \ - src/libNetworkManager.la \ +src_core_NetworkManager_all_sym_LDADD = \ + src/core/libNetworkManager.la \ $(GLIB_LIBS) \ $(NULL) -src_NetworkManager_all_sym_LDFLAGS = \ +src_core_NetworkManager_all_sym_LDFLAGS = \ -rdynamic \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -$(src_NetworkManager_all_sym_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_all_sym_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -src/NetworkManager.ver: src/NetworkManager-all-sym $(core_plugins) +src/core/NetworkManager.ver: src/core/NetworkManager-all-sym $(core_plugins) $(AM_V_GEN) NM="$(NM)" "$(srcdir)/tools/create-exports-NetworkManager.sh" --called-from-build "$(srcdir)" -CLEANFILES += src/NetworkManager.ver +CLEANFILES += src/core/NetworkManager.ver -EXTRA_src_NetworkManager_DEPENDENCIES = \ - src/NetworkManager.ver +EXTRA_src_core_NetworkManager_DEPENDENCIES = \ + src/core/NetworkManager.ver -src_NetworkManager_CPPFLAGS = $(src_cppflags) +src_core_NetworkManager_CPPFLAGS = $(src_core_cppflags) -src_NetworkManager_SOURCES = \ - src/main.c +src_core_NetworkManager_SOURCES = \ + src/core/main.c -src_NetworkManager_LDADD = \ - src/libNetworkManager.la \ +src_core_NetworkManager_LDADD = \ + src/core/libNetworkManager.la \ $(GLIB_LIBS) \ $(NULL) -src_NetworkManager_LDFLAGS = \ +src_core_NetworkManager_LDFLAGS = \ -rdynamic \ - -Wl,--version-script="src/NetworkManager.ver" \ + -Wl,--version-script="src/core/NetworkManager.ver" \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -$(src_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -src_nm_iface_helper_CPPFLAGS = $(src_cppflags) +src_core_nm_iface_helper_CPPFLAGS = $(src_core_cppflags) -src_nm_iface_helper_SOURCES = \ - src/nm-iface-helper.c +src_core_nm_iface_helper_SOURCES = \ + src/core/nm-iface-helper.c -src_nm_iface_helper_LDADD = \ - src/libNetworkManagerBase.la \ +src_core_nm_iface_helper_LDADD = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -2620,7 +2620,7 @@ src_nm_iface_helper_LDADD = \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ - src/libnm-systemd-core.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libnacd.la \ shared/libndhcp4.la \ @@ -2633,37 +2633,37 @@ src_nm_iface_helper_LDADD = \ $(DL_LIBS) \ $(NULL) -src_nm_iface_helper_LDFLAGS = \ +src_core_nm_iface_helper_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" \ $(SANITIZER_EXEC_LDFLAGS) -$(src_nm_iface_helper_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_nm_iface_helper_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -noinst_LTLIBRARIES += src/initrd/libnmi-core.la +noinst_LTLIBRARIES += src/core/initrd/libnmi-core.la -src_initrd_libnmi_core_la_CPPFLAGS = \ - $(src_cppflags) +src_core_initrd_libnmi_core_la_CPPFLAGS = \ + $(src_core_cppflags) -src_initrd_libnmi_core_la_SOURCES = \ - src/initrd/nm-initrd-generator.h \ - src/initrd/nmi-cmdline-reader.c \ - src/initrd/nmi-dt-reader.c \ - src/initrd/nmi-ibft-reader.c \ +src_core_initrd_libnmi_core_la_SOURCES = \ + src/core/initrd/nm-initrd-generator.h \ + src/core/initrd/nmi-cmdline-reader.c \ + src/core/initrd/nmi-dt-reader.c \ + src/core/initrd/nmi-ibft-reader.c \ $(NULL) -libexec_PROGRAMS += src/initrd/nm-initrd-generator +libexec_PROGRAMS += src/core/initrd/nm-initrd-generator -src_initrd_nm_initrd_generator_CPPFLAGS = \ - $(src_cppflags) +src_core_initrd_nm_initrd_generator_CPPFLAGS = \ + $(src_core_cppflags) -src_initrd_nm_initrd_generator_SOURCES = \ - src/initrd/nm-initrd-generator.c +src_core_initrd_nm_initrd_generator_SOURCES = \ + src/core/initrd/nm-initrd-generator.c -src_initrd_nm_initrd_generator_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerBase.la \ +src_core_initrd_nm_initrd_generator_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -2684,44 +2684,44 @@ src_initrd_nm_initrd_generator_LDADD = \ $(GLIB_LIBS) \ $(NULL) -src_initrd_nm_initrd_generator_LDFLAGS = \ +src_core_initrd_nm_initrd_generator_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" \ $(SANITIZER_EXEC_LDFLAGS) -check_programs += src/initrd/tests/test-dt-reader +check_programs += src/core/initrd/tests/test-dt-reader -src_initrd_tests_test_dt_reader_CPPFLAGS = \ +src_core_initrd_tests_test_dt_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_dt_reader_LDFLAGS = \ +src_core_initrd_tests_test_dt_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_dt_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_dt_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(NULL) -check_programs += src/initrd/tests/test-ibft-reader +check_programs += src/core/initrd/tests/test-ibft-reader -src_initrd_tests_test_ibft_reader_CPPFLAGS = \ +src_core_initrd_tests_test_ibft_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_ibft_reader_LDFLAGS = \ +src_core_initrd_tests_test_ibft_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_ibft_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_ibft_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ @@ -2729,261 +2729,261 @@ src_initrd_tests_test_ibft_reader_LDADD = \ $(NULL) EXTRA_DIST += \ - src/initrd/meson.build \ - src/initrd/tests/meson.build \ - src/initrd/tests/sysfs/class/net/eth0/address \ - src/initrd/tests/sysfs/class/net/eth2/address \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/flags \ - src/initrd/tests/sysfs/firmware/ibft/initiator/isns-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/slp-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/index \ - src/initrd/tests/sysfs/firmware/ibft/initiator/flags \ - src/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc \ - src/initrd/tests/sysfs/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/target0/chap-type \ - src/initrd/tests/sysfs/firmware/ibft/target0/index \ - src/initrd/tests/sysfs/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs/firmware/ibft/target0/flags \ - src/initrd/tests/sysfs/firmware/ibft/target0/port \ - src/initrd/tests/sysfs/firmware/ibft/target2/target-name \ - src/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc \ - src/initrd/tests/sysfs/firmware/ibft/target2/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/target2/chap-type \ - src/initrd/tests/sysfs/firmware/ibft/target2/index \ - src/initrd/tests/sysfs/firmware/ibft/target2/lun \ - src/initrd/tests/sysfs/firmware/ibft/target2/flags \ - src/initrd/tests/sysfs/firmware/ibft/target2/port \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/signature \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/mac \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/origin \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/index \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/flags \ - src/initrd/tests/sysfs-bad-dns1/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-dns2/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-gateway/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-dhcp/class/net/eth0/address \ - src/initrd/tests/sysfs-dhcp/class/net/eth1/address \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name \ - src/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath \ - src/initrd/tests/sysfs-static/class/net/eth0/address \ - src/initrd/tests/sysfs-static/class/net/eth1/address \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/target-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/lun \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/port \ - src/initrd/tests/sysfs-vlan/class/net/eth0/address \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/port \ - $(NULL) - -check_programs += src/initrd/tests/test-cmdline-reader - -src_initrd_tests_test_cmdline_reader_CPPFLAGS = \ + src/core/initrd/meson.build \ + src/core/initrd/tests/meson.build \ + src/core/initrd/tests/sysfs/class/net/eth0/address \ + src/core/initrd/tests/sysfs/class/net/eth2/address \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/index \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/index \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/lun \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/port \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags \ + src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address \ + src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name \ + src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath \ + src/core/initrd/tests/sysfs-static/class/net/eth0/address \ + src/core/initrd/tests/sysfs-static/class/net/eth1/address \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port \ + src/core/initrd/tests/sysfs-vlan/class/net/eth0/address \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port \ + $(NULL) + +check_programs += src/core/initrd/tests/test-cmdline-reader + +src_core_initrd_tests_test_cmdline_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_cmdline_reader_LDFLAGS = \ +src_core_initrd_tests_test_cmdline_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_cmdline_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_cmdline_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(NULL) -$(src_initrd_libnmi_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_nm_initrd_generator_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_cmdline_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_ibft_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_dt_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_libnmi_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_nm_initrd_generator_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_cmdline_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_ibft_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_dt_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### EXTRA_DIST += \ - src/org.freedesktop.NetworkManager.conf \ - src/nm-test-utils-core.h \ - src/meson.build + src/core/org.freedesktop.NetworkManager.conf \ + src/core/nm-test-utils-core.h \ + src/core/meson.build ############################################################################### -# src/dhcp +# src/core/dhcp ############################################################################### -libexec_PROGRAMS += src/dhcp/nm-dhcp-helper +libexec_PROGRAMS += src/core/dhcp/nm-dhcp-helper -src_dhcp_nm_dhcp_helper_CPPFLAGS = \ +src_core_dhcp_nm_dhcp_helper_CPPFLAGS = \ $(dflt_cppflags) \ -I$(srcdir)/shared \ -I$(builddir)/shared \ @@ -2992,60 +2992,60 @@ src_dhcp_nm_dhcp_helper_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(NULL) -src_dhcp_nm_dhcp_helper_SOURCES = \ - src/dhcp/nm-dhcp-helper.c \ - src/dhcp/nm-dhcp-helper-api.h \ +src_core_dhcp_nm_dhcp_helper_SOURCES = \ + src/core/dhcp/nm-dhcp-helper.c \ + src/core/dhcp/nm-dhcp-helper-api.h \ $(NULL) -src_dhcp_nm_dhcp_helper_LDFLAGS = \ +src_core_dhcp_nm_dhcp_helper_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" -src_dhcp_nm_dhcp_helper_LDADD = $(GLIB_LIBS) +src_core_dhcp_nm_dhcp_helper_LDADD = $(GLIB_LIBS) EXTRA_DIST += \ - src/dhcp/meson.build + src/core/dhcp/meson.build ############################################################################### -# src/dhcp/tests +# src/core/dhcp/tests ############################################################################### -src_dhcp_tests_cppflags = $(src_cppflags_test) +src_core_dhcp_tests_cppflags = $(src_core_cppflags_test) -src_dhcp_tests_ldadd = \ - src/libNetworkManagerTest.la +src_core_dhcp_tests_ldadd = \ + src/core/libNetworkManagerTest.la check_programs += \ - src/dhcp/tests/test-dhcp-dhclient \ - src/dhcp/tests/test-dhcp-utils + src/core/dhcp/tests/test-dhcp-dhclient \ + src/core/dhcp/tests/test-dhcp-utils -src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS = $(src_dhcp_tests_cppflags) -src_dhcp_tests_test_dhcp_utils_CPPFLAGS = $(src_dhcp_tests_cppflags) +src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS = $(src_core_dhcp_tests_cppflags) +src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS = $(src_core_dhcp_tests_cppflags) -src_dhcp_tests_test_dhcp_dhclient_LDADD = $(src_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_utils_LDADD = $(src_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_dhclient_LDADD = $(src_core_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_utils_LDADD = $(src_core_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_dhclient_LDFLAGS = $(src_tests_ldflags) -src_dhcp_tests_test_dhcp_utils_LDFLAGS = $(src_tests_ldflags) +src_core_dhcp_tests_test_dhcp_dhclient_LDFLAGS = $(src_core_tests_ldflags) +src_core_dhcp_tests_test_dhcp_utils_LDFLAGS = $(src_core_tests_ldflags) -$(src_dhcp_tests_test_dhcp_dhclient_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_dhcp_tests_test_dhcp_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dhcp_tests_test_dhcp_dhclient_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dhcp_tests_test_dhcp_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/dhcp/tests/test-dhclient-duid.leases \ - src/dhcp/tests/test-dhclient-commented-duid.leases \ - src/dhcp/tests/meson.build + src/core/dhcp/tests/test-dhclient-duid.leases \ + src/core/dhcp/tests/test-dhclient-commented-duid.leases \ + src/core/dhcp/tests/meson.build ############################################################################### -# src/ppp +# src/core/ppp ############################################################################### if WITH_PPP -core_plugins += src/ppp/libnm-ppp-plugin.la +core_plugins += src/core/ppp/libnm-ppp-plugin.la -pppd_plugin_LTLIBRARIES += src/ppp/nm-pppd-plugin.la +pppd_plugin_LTLIBRARIES += src/core/ppp/nm-pppd-plugin.la -src_ppp_nm_pppd_plugin_la_CPPFLAGS = \ +src_core_ppp_nm_pppd_plugin_la_CPPFLAGS = \ $(dflt_cppflags) \ -I$(srcdir)/shared \ -I$(builddir)/shared \ @@ -3054,217 +3054,217 @@ src_ppp_nm_pppd_plugin_la_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB \ $(GLIB_CFLAGS) -src_ppp_nm_pppd_plugin_la_SOURCES = \ - src/ppp/nm-pppd-plugin.c \ - src/ppp/nm-pppd-plugin.h \ - src/ppp/nm-ppp-status.h +src_core_ppp_nm_pppd_plugin_la_SOURCES = \ + src/core/ppp/nm-pppd-plugin.c \ + src/core/ppp/nm-pppd-plugin.h \ + src/core/ppp/nm-ppp-status.h -src_ppp_nm_pppd_plugin_la_LDFLAGS = \ +src_core_ppp_nm_pppd_plugin_la_LDFLAGS = \ -module -avoid-version -src_ppp_nm_pppd_plugin_la_LIBADD = \ +src_core_ppp_nm_pppd_plugin_la_LIBADD = \ $(GLIB_LIBS) \ $(DL_LIBS) \ $(NULL) -$(src_ppp_nm_pppd_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ppp_nm_pppd_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -src_ppp_libnm_ppp_plugin_la_SOURCES = \ - src/ppp/nm-pppd-plugin.h \ - src/ppp/nm-ppp-manager.c \ - src/ppp/nm-ppp-manager.h \ - src/ppp/nm-ppp-plugin-api.h \ - src/ppp/nm-ppp-status.h \ +src_core_ppp_libnm_ppp_plugin_la_SOURCES = \ + src/core/ppp/nm-pppd-plugin.h \ + src/core/ppp/nm-ppp-manager.c \ + src/core/ppp/nm-ppp-manager.h \ + src/core/ppp/nm-ppp-plugin-api.h \ + src/core/ppp/nm-ppp-status.h \ $(NULL) -EXTRA_src_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ - src/ppp/nm-ppp-plugin.ver \ +EXTRA_src_core_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ + src/core/ppp/nm-ppp-plugin.ver \ $(NULL) -src_ppp_libnm_ppp_plugin_la_CPPFLAGS = $(src_cppflags_base) +src_core_ppp_libnm_ppp_plugin_la_CPPFLAGS = $(src_core_cppflags_base) -src_ppp_libnm_ppp_plugin_la_LDFLAGS = \ +src_core_ppp_libnm_ppp_plugin_la_LDFLAGS = \ -module -avoid-version \ - -Wl,--version-script="$(srcdir)/src/ppp/nm-ppp-plugin.ver" \ + -Wl,--version-script="$(srcdir)/src/core/ppp/nm-ppp-plugin.ver" \ $(NULL) -src_ppp_libnm_ppp_plugin_la_LIBADD = \ +src_core_ppp_libnm_ppp_plugin_la_LIBADD = \ $(GLIB_LIBS) \ $(NULL) -$(src_ppp_libnm_ppp_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ppp_libnm_ppp_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) endif EXTRA_DIST += \ - src/ppp/meson.build + src/core/ppp/meson.build ############################################################################### -# src/settings/plugins/keyfile/tests +# src/core/settings/plugins/keyfile/tests ############################################################################### -check_programs += src/settings/plugins/keyfile/tests/test-keyfile-settings +check_programs += src/core/settings/plugins/keyfile/tests/test-keyfile-settings -src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS = $(src_cppflags_test) +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS = $(src_core_cppflags_test) -src_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS = \ +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD = \ - src/libNetworkManagerTest.la +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD = \ + src/core/libNetworkManagerTest.la -$(src_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case \ - src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format \ - src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 \ - src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT \ - src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain \ - src/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing \ - src/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component \ - src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name \ - src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property \ - src/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 \ + src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT \ + src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config \ \ - src/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem \ - src/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem \ + src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem \ + src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem \ \ - src/settings/plugins/keyfile/tests/meson.build + src/core/settings/plugins/keyfile/tests/meson.build ############################################################################### -# src/settings/plugins/ifcfg-rh +# src/core/settings/plugins/ifcfg-rh ############################################################################### if CONFIG_PLUGIN_IFCFG_RH -core_plugins += src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la +core_plugins += src/core/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la noinst_LTLIBRARIES += \ - src/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la \ - src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la + src/core/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la \ + src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la ############################################################################### -nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES = \ - src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c \ - src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES = \ + src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c \ + src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h -src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS = \ - $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(src_cppflags_base)) \ +src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS = \ + $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(src_core_cppflags_base)) \ $(INTROSPECTION_EXTRA_CFLAGS) \ $(NULL) -CLEANFILES += $(nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) +CLEANFILES += $(nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) -src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h: src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml - @$(MKDIR_P) src/settings/plugins/ifcfg-rh/ +src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h: src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml + @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh/ $(AM_V_GEN) gdbus-codegen \ --generate-c-code $(basename $@) \ --c-namespace NMDBus \ --interface-prefix com.redhat \ $< -src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c: src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c: src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h @true ############################################################################### -src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES = \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ - src/settings/plugins/ifcfg-rh/shvar.c \ - src/settings/plugins/ifcfg-rh/shvar.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h \ +src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES = \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ + src/core/settings/plugins/ifcfg-rh/shvar.c \ + src/core/settings/plugins/ifcfg-rh/shvar.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h \ $(NULL) -src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS = $(src_cppflags_base) +src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS = $(src_core_cppflags_base) -$(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES = \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h \ +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES = \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h \ $(NULL) -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS = $(src_cppflags_base) +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS = $(src_core_cppflags_base) -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS = \ +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-settings.ver" \ $(SANITIZER_EXEC_LDFLAGS) -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD = \ - src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD = \ + src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la -$(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h -$(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +$(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -check-local-symbols-settings-ifcfg-rh: src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la - $(call check_so_symbols,$(builddir)/src/settings/plugins/ifcfg-rh/.libs/libnm-settings-plugin-ifcfg-rh.so) +check-local-symbols-settings-ifcfg-rh: src/core/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la + $(call check_so_symbols,$(builddir)/src/core/settings/plugins/ifcfg-rh/.libs/libnm-settings-plugin-ifcfg-rh.so) check_local += check-local-symbols-settings-ifcfg-rh ############################################################################### -# src/settings/plugins/ifcfg-rh/tests +# src/core/settings/plugins/ifcfg-rh/tests ############################################################################### -check_programs += src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh +check_programs += src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES = \ - src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES = \ + src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS = $(src_cppflags_base_test) +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS = $(src_core_cppflags_base_test) -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS = \ +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD = \ - src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ - src/libNetworkManagerTest.la +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD = \ + src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ + src/core/libNetworkManagerTest.la -$(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS): $(libnm_core_lib_h_pub_mkenums) dist_libexec_SCRIPTS += \ - src/settings/plugins/ifcfg-rh/nm-ifup \ - src/settings/plugins/ifcfg-rh/nm-ifdown + src/core/settings/plugins/ifcfg-rh/nm-ifup \ + src/core/settings/plugins/ifcfg-rh/nm-ifdown install-data-hook-ifcfg-rh: $(mkinstalldirs) -m 0755 $(DESTDIR)$(sysconfdir)/sysconfig/network-scripts @@ -3274,229 +3274,229 @@ install_data_hook += install-data-hook-ifcfg-rh endif EXTRA_DIST += \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 \ $(NULL) # make target dependencies can't have colons in their names, which ends up # meaning that we can't add the alias files to EXTRA_DIST. They are instead # dist'ed via dist-hook-settings-ifcfg-rh below. -src_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files = \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 +src_core_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files = \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 dist-hook-settings-ifcfg-rh-alias-files: - @for f in $(src_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files); do \ - cp $(abs_srcdir)/$$f $(distdir)/src/settings/plugins/ifcfg-rh/tests/network-scripts/; \ + @for f in $(src_core_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files); do \ + cp $(abs_srcdir)/$$f $(distdir)/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/; \ done dist_hook += dist-hook-settings-ifcfg-rh-alias-files @@ -3504,473 +3504,473 @@ dist_hook += dist-hook-settings-ifcfg-rh-alias-files ############################################################################### if CONFIG_PLUGIN_IFCFG_RH -dbusservice_DATA += src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf +dbusservice_DATA += src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf endif EXTRA_DIST += \ - src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf \ - src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml \ - src/settings/plugins/ifcfg-rh/meson.build \ - src/settings/plugins/ifcfg-rh/tests/meson.build + src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf \ + src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml \ + src/core/settings/plugins/ifcfg-rh/meson.build \ + src/core/settings/plugins/ifcfg-rh/tests/meson.build ############################################################################### -# src/settings/plugins/ifupdown +# src/core/settings/plugins/ifupdown ############################################################################### if CONFIG_PLUGIN_IFUPDOWN -core_plugins += src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la +core_plugins += src/core/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la -noinst_LTLIBRARIES += src/settings/plugins/ifupdown/libnms-ifupdown-core.la +noinst_LTLIBRARIES += src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la -src_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES = \ - src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ - src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ - src/settings/plugins/ifupdown/nms-ifupdown-parser.c \ - src/settings/plugins/ifupdown/nms-ifupdown-parser.h \ +src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES = \ + src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ + src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h \ $(NULL) -src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS = $(src_cppflags_base) +src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS = $(src_core_cppflags_base) -src_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD = \ +src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD = \ $(LIBUDEV_LIBS) \ $(NULL) -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES = \ - src/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ - src/settings/plugins/ifupdown/nms-ifupdown-plugin.h \ +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES = \ + src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h \ $(NULL) -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS = $(src_cppflags_base) +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS = $(src_core_cppflags_base) -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS = \ +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-settings.ver" \ $(NULL) -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD = \ - src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD = \ + src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ $(LIBUDEV_LIBS) \ $(NULL) -$(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -check-local-symbols-settings-ifupdown: src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la - $(call check_so_symbols,$(builddir)/src/settings/plugins/ifupdown/.libs/libnm-settings-plugin-ifupdown.so) +check-local-symbols-settings-ifupdown: src/core/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la + $(call check_so_symbols,$(builddir)/src/core/settings/plugins/ifupdown/.libs/libnm-settings-plugin-ifupdown.so) check_local += check-local-symbols-settings-ifupdown ############################################################################### -check_programs += src/settings/plugins/ifupdown/tests/test-ifupdown +check_programs += src/core/settings/plugins/ifupdown/tests/test-ifupdown -src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS = $(src_cppflags_base_test) +src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS = $(src_core_cppflags_base_test) -src_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS = \ +src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_settings_plugins_ifupdown_tests_test_ifupdown_LDADD = \ - src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ - src/libNetworkManagerTest.la \ +src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDADD = \ + src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) \ $(NULL) -$(src_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS): $(libnm_core_lib_h_pub_mkenums) endif EXTRA_DIST += \ - src/settings/plugins/ifupdown/tests/test1 \ - src/settings/plugins/ifupdown/tests/test2 \ - src/settings/plugins/ifupdown/tests/test3 \ - src/settings/plugins/ifupdown/tests/test4 \ - src/settings/plugins/ifupdown/tests/test5 \ - src/settings/plugins/ifupdown/tests/test6 \ - src/settings/plugins/ifupdown/tests/test7 \ - src/settings/plugins/ifupdown/tests/test8 \ - src/settings/plugins/ifupdown/tests/test9 \ - src/settings/plugins/ifupdown/tests/test11 \ - src/settings/plugins/ifupdown/tests/test12 \ - src/settings/plugins/ifupdown/tests/test13 \ - src/settings/plugins/ifupdown/tests/test14 \ - src/settings/plugins/ifupdown/tests/test15 \ - src/settings/plugins/ifupdown/tests/test16 \ - src/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 \ - src/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 \ - src/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen \ - src/settings/plugins/ifupdown/tests/test20-source-stanza \ - src/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 \ - src/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 \ - src/settings/plugins/ifupdown/tests/test21-source-dir-stanza \ - src/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d \ - src/settings/plugins/ifupdown/tests/test22-duplicate-stanzas \ - src/settings/plugins/ifupdown/meson.build \ - src/settings/plugins/ifupdown/tests/meson.build - -############################################################################### -# src/devices + src/core/settings/plugins/ifupdown/tests/test1 \ + src/core/settings/plugins/ifupdown/tests/test2 \ + src/core/settings/plugins/ifupdown/tests/test3 \ + src/core/settings/plugins/ifupdown/tests/test4 \ + src/core/settings/plugins/ifupdown/tests/test5 \ + src/core/settings/plugins/ifupdown/tests/test6 \ + src/core/settings/plugins/ifupdown/tests/test7 \ + src/core/settings/plugins/ifupdown/tests/test8 \ + src/core/settings/plugins/ifupdown/tests/test9 \ + src/core/settings/plugins/ifupdown/tests/test11 \ + src/core/settings/plugins/ifupdown/tests/test12 \ + src/core/settings/plugins/ifupdown/tests/test13 \ + src/core/settings/plugins/ifupdown/tests/test14 \ + src/core/settings/plugins/ifupdown/tests/test15 \ + src/core/settings/plugins/ifupdown/tests/test16 \ + src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 \ + src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 \ + src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 \ + src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza \ + src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d \ + src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas \ + src/core/settings/plugins/ifupdown/meson.build \ + src/core/settings/plugins/ifupdown/tests/meson.build + +############################################################################### +# src/core/devices ############################################################################### EXTRA_DIST += \ - src/devices/meson.build + src/core/devices/meson.build ############################################################################### -# src/devices/adsl +# src/core/devices/adsl ############################################################################### -core_plugins += src/devices/adsl/libnm-device-plugin-adsl.la +core_plugins += src/core/devices/adsl/libnm-device-plugin-adsl.la -src_devices_adsl_libnm_device_plugin_adsl_la_SOURCES = \ - src/devices/adsl/nm-atm-manager.c \ - src/devices/adsl/nm-device-adsl.c \ - src/devices/adsl/nm-device-adsl.h +src_core_devices_adsl_libnm_device_plugin_adsl_la_SOURCES = \ + src/core/devices/adsl/nm-atm-manager.c \ + src/core/devices/adsl/nm-device-adsl.c \ + src/core/devices/adsl/nm-device-adsl.h -src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS = $(src_cppflags_device_plugin) +src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS = $(src_core_cppflags_device_plugin) -src_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS = \ +src_core_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -src_devices_adsl_libnm_device_plugin_adsl_la_LIBADD = \ +src_core_devices_adsl_libnm_device_plugin_adsl_la_LIBADD = \ $(LIBUDEV_LIBS) -check-local-devices-adsl: src/devices/adsl/libnm-device-plugin-adsl.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so) +check-local-devices-adsl: src/core/devices/adsl/libnm-device-plugin-adsl.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/adsl/.libs/libnm-device-plugin-adsl.so) check_local += check-local-devices-adsl EXTRA_DIST += \ - src/devices/adsl/meson.build + src/core/devices/adsl/meson.build ############################################################################### -# src/devices/wwan +# src/core/devices/wwan ############################################################################### if WITH_MODEM_MANAGER_1 -src_devices_wwan_cppflags = \ - $(src_cppflags_device_plugin) \ +src_core_devices_wwan_cppflags = \ + $(src_core_cppflags_device_plugin) \ $(MM_GLIB_CFLAGS) \ $(NULL) -core_plugins += src/devices/wwan/libnm-wwan.la +core_plugins += src/core/devices/wwan/libnm-wwan.la -src_devices_wwan_libnm_wwan_la_SOURCES = \ - src/devices/wwan/nm-modem-broadband.c \ - src/devices/wwan/nm-modem-broadband.h \ - src/devices/wwan/nm-modem-manager.c \ - src/devices/wwan/nm-modem-manager.h \ - src/devices/wwan/nm-modem.c \ - src/devices/wwan/nm-modem.h \ - src/devices/wwan/nm-service-providers.c \ - src/devices/wwan/nm-service-providers.h \ +src_core_devices_wwan_libnm_wwan_la_SOURCES = \ + src/core/devices/wwan/nm-modem-broadband.c \ + src/core/devices/wwan/nm-modem-broadband.h \ + src/core/devices/wwan/nm-modem-manager.c \ + src/core/devices/wwan/nm-modem-manager.h \ + src/core/devices/wwan/nm-modem.c \ + src/core/devices/wwan/nm-modem.h \ + src/core/devices/wwan/nm-service-providers.c \ + src/core/devices/wwan/nm-service-providers.h \ $(NULL) if WITH_OFONO -src_devices_wwan_libnm_wwan_la_SOURCES += \ - src/devices/wwan/nm-modem-ofono.c \ - src/devices/wwan/nm-modem-ofono.h +src_core_devices_wwan_libnm_wwan_la_SOURCES += \ + src/core/devices/wwan/nm-modem-ofono.c \ + src/core/devices/wwan/nm-modem-ofono.h endif -src_devices_wwan_libnm_wwan_la_CPPFLAGS = $(src_devices_wwan_cppflags) +src_core_devices_wwan_libnm_wwan_la_CPPFLAGS = $(src_core_devices_wwan_cppflags) -src_devices_wwan_libnm_wwan_la_LDFLAGS = \ +src_core_devices_wwan_libnm_wwan_la_LDFLAGS = \ -avoid-version \ - -Wl,--version-script="$(srcdir)/src/devices/wwan/libnm-wwan.ver" + -Wl,--version-script="$(srcdir)/src/core/devices/wwan/libnm-wwan.ver" -src_devices_wwan_libnm_wwan_la_LIBADD = \ +src_core_devices_wwan_libnm_wwan_la_LIBADD = \ $(GLIB_LIBS) \ $(LIBSYSTEMD_LIBS) \ $(MM_GLIB_LIBS) -$(src_devices_wwan_libnm_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wwan_libnm_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -EXTRA_src_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ - src/devices/wwan/libnm-wwan.ver +EXTRA_src_core_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ + src/core/devices/wwan/libnm-wwan.ver -core_plugins += src/devices/wwan/libnm-device-plugin-wwan.la +core_plugins += src/core/devices/wwan/libnm-device-plugin-wwan.la -src_devices_wwan_libnm_device_plugin_wwan_la_SOURCES = \ - src/devices/wwan/nm-wwan-factory.c \ - src/devices/wwan/nm-device-modem.c \ - src/devices/wwan/nm-device-modem.h +src_core_devices_wwan_libnm_device_plugin_wwan_la_SOURCES = \ + src/core/devices/wwan/nm-wwan-factory.c \ + src/core/devices/wwan/nm-device-modem.c \ + src/core/devices/wwan/nm-device-modem.h -src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS = $(src_devices_wwan_cppflags) +src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS = $(src_core_devices_wwan_cppflags) -src_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS = \ +src_core_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -src_devices_wwan_libnm_device_plugin_wwan_la_LIBADD = \ - src/devices/wwan/libnm-wwan.la \ +src_core_devices_wwan_libnm_device_plugin_wwan_la_LIBADD = \ + src/core/devices/wwan/libnm-wwan.la \ $(GLIB_LIBS) -check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devices/wwan/libnm-wwan.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so) - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/devices/wwan/libnm-wwan.ver" - $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-wwan.so) +check-local-devices-wwan: src/core/devices/wwan/libnm-device-plugin-wwan.la src/core/devices/wwan/libnm-wwan.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/wwan/.libs/libnm-device-plugin-wwan.so) + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/core/devices/wwan/libnm-wwan.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/wwan/.libs/libnm-wwan.so) check_local += check-local-devices-wwan -src_devices_wwan_tests_test_service_providers_SOURCES = \ - src/devices/wwan/tests/test-service-providers.c \ - src/devices/wwan/nm-service-providers.c \ - src/devices/wwan/nm-service-providers.h \ +src_core_devices_wwan_tests_test_service_providers_SOURCES = \ + src/core/devices/wwan/tests/test-service-providers.c \ + src/core/devices/wwan/nm-service-providers.c \ + src/core/devices/wwan/nm-service-providers.h \ $(NULL) -src_devices_wwan_tests_test_service_providers_CPPFLAGS = \ - $(src_cppflags_base_test) \ - -I$(srcdir)/src/devices/wwan \ +src_core_devices_wwan_tests_test_service_providers_CPPFLAGS = \ + $(src_core_cppflags_base_test) \ + -I$(srcdir)/src/core/devices/wwan \ $(NULL) -src_devices_wwan_tests_test_service_providers_LDFLAGS = \ +src_core_devices_wwan_tests_test_service_providers_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -src_devices_wwan_tests_test_service_providers_LDADD = \ - src/libNetworkManagerTest.la \ +src_core_devices_wwan_tests_test_service_providers_LDADD = \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) \ $(NULL) -check_programs += src/devices/wwan/tests/test-service-providers +check_programs += src/core/devices/wwan/tests/test-service-providers -$(src_devices_wwan_tests_test_service_providers_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wwan_tests_test_service_providers_OBJECTS): $(libnm_core_lib_h_pub_mkenums) endif EXTRA_DIST += \ - src/devices/wwan/libnm-wwan.ver \ - src/devices/wwan/meson.build \ - src/devices/wwan/tests/test-service-providers.xml \ + src/core/devices/wwan/libnm-wwan.ver \ + src/core/devices/wwan/meson.build \ + src/core/devices/wwan/tests/test-service-providers.xml \ $(NULL) ############################################################################### -# src/devices/bluetooth +# src/core/devices/bluetooth ############################################################################### if WITH_MODEM_MANAGER_1 -noinst_LTLIBRARIES += src/devices/bluetooth/libnm-bluetooth-utils.la +noinst_LTLIBRARIES += src/core/devices/bluetooth/libnm-bluetooth-utils.la -src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES = \ - src/devices/bluetooth/nm-bluez-common.h \ - src/devices/bluetooth/nm-bt-error.c \ - src/devices/bluetooth/nm-bt-error.h \ +src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES = \ + src/core/devices/bluetooth/nm-bluez-common.h \ + src/core/devices/bluetooth/nm-bt-error.c \ + src/core/devices/bluetooth/nm-bt-error.h \ $(NULL) -src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS = \ - $(src_cppflags_base) \ +src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS = \ + $(src_core_cppflags_base) \ $(NULL) -src_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD = \ +src_core_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD = \ $(GLIB_LIBS) \ $(NULL) if WITH_BLUEZ5_DUN -src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES += \ - src/devices/bluetooth/nm-bluez5-dun.c \ - src/devices/bluetooth/nm-bluez5-dun.h \ +src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES += \ + src/core/devices/bluetooth/nm-bluez5-dun.c \ + src/core/devices/bluetooth/nm-bluez5-dun.h \ $(NULL) -src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS += $(BLUEZ5_CFLAGS) -src_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD += $(BLUEZ5_LIBS) +src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS += $(BLUEZ5_CFLAGS) +src_core_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD += $(BLUEZ5_LIBS) endif -$(src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -core_plugins += src/devices/bluetooth/libnm-device-plugin-bluetooth.la +core_plugins += src/core/devices/bluetooth/libnm-device-plugin-bluetooth.la -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES = \ - src/devices/bluetooth/nm-bluez-manager.c \ - src/devices/bluetooth/nm-bluez-manager.h \ - src/devices/bluetooth/nm-device-bt.c \ - src/devices/bluetooth/nm-device-bt.h \ +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES = \ + src/core/devices/bluetooth/nm-bluez-manager.c \ + src/core/devices/bluetooth/nm-bluez-manager.h \ + src/core/devices/bluetooth/nm-device-bt.c \ + src/core/devices/bluetooth/nm-device-bt.h \ $(NULL) -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS = $(src_cppflags_device_plugin) +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS = $(src_core_cppflags_device_plugin) -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS = \ +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD = \ - src/devices/bluetooth/libnm-bluetooth-utils.la \ - src/devices/wwan/libnm-wwan.la \ +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD = \ + src/core/devices/bluetooth/libnm-bluetooth-utils.la \ + src/core/devices/wwan/libnm-wwan.la \ $(GLIB_LIBS) \ $(NULL) ############################################################################### check_programs_norun += \ - src/devices/bluetooth/tests/nm-bt-test + src/core/devices/bluetooth/tests/nm-bt-test -src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS = \ - $(src_cppflags_test) \ +src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS = \ + $(src_core_cppflags_test) \ $(NULL) -src_devices_bluetooth_tests_nm_bt_test_LDFLAGS = \ +src_core_devices_bluetooth_tests_nm_bt_test_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -src_devices_bluetooth_tests_nm_bt_test_LDADD = \ - src/devices/bluetooth/libnm-bluetooth-utils.la \ - src/libNetworkManager.la \ +src_core_devices_bluetooth_tests_nm_bt_test_LDADD = \ + src/core/devices/bluetooth/libnm-bluetooth-utils.la \ + src/core/libNetworkManager.la \ $(GLIB_LIBS) \ $(NULL) -$(src_devices_bluetooth_tests_nm_bt_test_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_bluetooth_tests_nm_bt_test_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -check-local-devices-bluetooth: src/devices/bluetooth/libnm-device-plugin-bluetooth.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so) +check-local-devices-bluetooth: src/core/devices/bluetooth/libnm-device-plugin-bluetooth.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so) check_local += check-local-devices-bluetooth endif EXTRA_DIST += \ - src/devices/bluetooth/meson.build \ + src/core/devices/bluetooth/meson.build \ $(NULL) ############################################################################### -# src/devices/wifi +# src/core/devices/wifi ############################################################################### if WITH_WIFI -noinst_LTLIBRARIES += src/devices/wifi/libnm-wifi-base.la - -src_devices_wifi_libnm_wifi_base_la_SOURCES = \ - src/devices/wifi/nm-device-olpc-mesh.c \ - src/devices/wifi/nm-device-olpc-mesh.h \ - src/devices/wifi/nm-device-wifi-p2p.c \ - src/devices/wifi/nm-device-wifi-p2p.h \ - src/devices/wifi/nm-device-wifi.c \ - src/devices/wifi/nm-device-wifi.h \ - src/devices/wifi/nm-wifi-ap.c \ - src/devices/wifi/nm-wifi-ap.h \ - src/devices/wifi/nm-wifi-common.c \ - src/devices/wifi/nm-wifi-common.h \ - src/devices/wifi/nm-wifi-p2p-peer.c \ - src/devices/wifi/nm-wifi-p2p-peer.h \ - src/devices/wifi/nm-wifi-utils.c \ - src/devices/wifi/nm-wifi-utils.h \ +noinst_LTLIBRARIES += src/core/devices/wifi/libnm-wifi-base.la + +src_core_devices_wifi_libnm_wifi_base_la_SOURCES = \ + src/core/devices/wifi/nm-device-olpc-mesh.c \ + src/core/devices/wifi/nm-device-olpc-mesh.h \ + src/core/devices/wifi/nm-device-wifi-p2p.c \ + src/core/devices/wifi/nm-device-wifi-p2p.h \ + src/core/devices/wifi/nm-device-wifi.c \ + src/core/devices/wifi/nm-device-wifi.h \ + src/core/devices/wifi/nm-wifi-ap.c \ + src/core/devices/wifi/nm-wifi-ap.h \ + src/core/devices/wifi/nm-wifi-common.c \ + src/core/devices/wifi/nm-wifi-common.h \ + src/core/devices/wifi/nm-wifi-p2p-peer.c \ + src/core/devices/wifi/nm-wifi-p2p-peer.h \ + src/core/devices/wifi/nm-wifi-utils.c \ + src/core/devices/wifi/nm-wifi-utils.h \ $(NULL) if WITH_IWD -src_devices_wifi_libnm_wifi_base_la_SOURCES += \ - src/devices/wifi/nm-device-iwd.c \ - src/devices/wifi/nm-device-iwd.h \ - src/devices/wifi/nm-iwd-manager.c \ - src/devices/wifi/nm-iwd-manager.h \ +src_core_devices_wifi_libnm_wifi_base_la_SOURCES += \ + src/core/devices/wifi/nm-device-iwd.c \ + src/core/devices/wifi/nm-device-iwd.h \ + src/core/devices/wifi/nm-iwd-manager.c \ + src/core/devices/wifi/nm-iwd-manager.h \ $(NULL) endif -src_devices_wifi_libnm_wifi_base_la_CPPFLAGS = $(src_cppflags_device_plugin) +src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS = $(src_core_cppflags_device_plugin) -src_devices_wifi_libnm_wifi_base_la_LIBADD = \ +src_core_devices_wifi_libnm_wifi_base_la_LIBADD = \ $(GLIB_LIBS) -$(src_devices_wifi_libnm_wifi_base_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wifi_libnm_wifi_base_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -core_plugins += src/devices/wifi/libnm-device-plugin-wifi.la +core_plugins += src/core/devices/wifi/libnm-device-plugin-wifi.la -src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \ - src/devices/wifi/nm-wifi-factory.c \ +src_core_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \ + src/core/devices/wifi/nm-wifi-factory.c \ $(NULL) -src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = $(src_cppflags_device_plugin) +src_core_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = $(src_core_cppflags_device_plugin) -src_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS = \ +src_core_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -src_devices_wifi_libnm_device_plugin_wifi_la_LIBADD = \ - src/devices/wifi/libnm-wifi-base.la \ +src_core_devices_wifi_libnm_device_plugin_wifi_la_LIBADD = \ + src/core/devices/wifi/libnm-wifi-base.la \ $(GLIB_LIBS) -check-local-devices-wifi: src/devices/wifi/libnm-device-plugin-wifi.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so) +check-local-devices-wifi: src/core/devices/wifi/libnm-device-plugin-wifi.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/wifi/.libs/libnm-device-plugin-wifi.so) check_local += check-local-devices-wifi -check_programs += src/devices/wifi/tests/test-devices-wifi +check_programs += src/core/devices/wifi/tests/test-devices-wifi -src_devices_wifi_tests_test_devices_wifi_SOURCES = \ - src/devices/wifi/tests/test-devices-wifi.c \ +src_core_devices_wifi_tests_test_devices_wifi_SOURCES = \ + src/core/devices/wifi/tests/test-devices-wifi.c \ $(NULL) -src_devices_wifi_tests_test_devices_wifi_CPPFLAGS = $(src_cppflags_base_test) +src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS = $(src_core_cppflags_base_test) -src_devices_wifi_tests_test_devices_wifi_LDADD = \ - src/devices/wifi/libnm-wifi-base.la \ - src/libNetworkManagerTest.la \ - src/libNetworkManagerBase.la \ +src_core_devices_wifi_tests_test_devices_wifi_LDADD = \ + src/core/devices/wifi/libnm-wifi-base.la \ + src/core/libNetworkManagerTest.la \ + src/core/libNetworkManagerBase.la \ $(NULL) -src_devices_wifi_tests_test_devices_wifi_LDFLAGS = $(SANITIZER_EXEC_LDFLAGS) +src_core_devices_wifi_tests_test_devices_wifi_LDFLAGS = $(SANITIZER_EXEC_LDFLAGS) -$(src_devices_wifi_tests_test_devices_wifi_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wifi_tests_test_devices_wifi_OBJECTS): $(libnm_core_lib_h_pub_mkenums) endif EXTRA_DIST += \ - src/devices/wifi/meson.build \ + src/core/devices/wifi/meson.build \ $(NULL) ############################################################################### -# src/devices/team +# src/core/devices/team ############################################################################### if WITH_TEAMDCTL -core_plugins += src/devices/team/libnm-device-plugin-team.la +core_plugins += src/core/devices/team/libnm-device-plugin-team.la -src_devices_team_libnm_device_plugin_team_la_SOURCES = \ - src/devices/team/nm-team-factory.c \ - src/devices/team/nm-device-team.c \ - src/devices/team/nm-device-team.h \ +src_core_devices_team_libnm_device_plugin_team_la_SOURCES = \ + src/core/devices/team/nm-team-factory.c \ + src/core/devices/team/nm-device-team.c \ + src/core/devices/team/nm-device-team.h \ $(NULL) -src_devices_team_libnm_device_plugin_team_la_CPPFLAGS = \ - $(src_cppflags_device_plugin) \ +src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS = \ + $(src_core_cppflags_device_plugin) \ $(LIBTEAMDCTL_CFLAGS) \ $(JANSSON_CFLAGS) \ $(NULL) -src_devices_team_libnm_device_plugin_team_la_LDFLAGS = \ +src_core_devices_team_libnm_device_plugin_team_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" \ $(NULL) -src_devices_team_libnm_device_plugin_team_la_LIBADD = \ +src_core_devices_team_libnm_device_plugin_team_la_LIBADD = \ $(LIBTEAMDCTL_LIBS) \ $(JANSSON_LIBS) \ $(GLIB_LIBS) \ $(NULL) -check-local-devices-team: src/devices/team/libnm-device-plugin-team.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so) +check-local-devices-team: src/core/devices/team/libnm-device-plugin-team.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/team/.libs/libnm-device-plugin-team.so) check_local += check-local-devices-team endif EXTRA_DIST += \ - src/devices/team/meson.build + src/core/devices/team/meson.build ############################################################################### -# src/devices/ovs +# src/core/devices/ovs ############################################################################### if WITH_OPENVSWITCH @@ -3983,373 +3983,373 @@ systemdnmunit_DATA = \ endif -core_plugins += src/devices/ovs/libnm-device-plugin-ovs.la +core_plugins += src/core/devices/ovs/libnm-device-plugin-ovs.la -src_devices_ovs_libnm_device_plugin_ovs_la_SOURCES = \ - src/devices/ovs/nm-ovsdb.c \ - src/devices/ovs/nm-ovsdb.h \ - src/devices/ovs/nm-ovs-factory.c \ - src/devices/ovs/nm-device-ovs-interface.c \ - src/devices/ovs/nm-device-ovs-interface.h \ - src/devices/ovs/nm-device-ovs-port.c \ - src/devices/ovs/nm-device-ovs-port.h \ - src/devices/ovs/nm-device-ovs-bridge.c \ - src/devices/ovs/nm-device-ovs-bridge.h \ +src_core_devices_ovs_libnm_device_plugin_ovs_la_SOURCES = \ + src/core/devices/ovs/nm-ovsdb.c \ + src/core/devices/ovs/nm-ovsdb.h \ + src/core/devices/ovs/nm-ovs-factory.c \ + src/core/devices/ovs/nm-device-ovs-interface.c \ + src/core/devices/ovs/nm-device-ovs-interface.h \ + src/core/devices/ovs/nm-device-ovs-port.c \ + src/core/devices/ovs/nm-device-ovs-port.h \ + src/core/devices/ovs/nm-device-ovs-bridge.c \ + src/core/devices/ovs/nm-device-ovs-bridge.h \ $(NULL) -src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS = \ - $(src_cppflags_device_plugin) \ +src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS = \ + $(src_core_cppflags_device_plugin) \ $(JANSSON_CFLAGS) \ $(NULL) -src_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS = \ +src_core_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" \ $(NULL) -src_devices_ovs_libnm_device_plugin_ovs_la_LIBADD = \ +src_core_devices_ovs_libnm_device_plugin_ovs_la_LIBADD = \ $(JANSSON_LIBS) \ $(GLIB_LIBS) \ $(NULL) -check-local-devices-ovs: src/devices/ovs/libnm-device-plugin-ovs.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so) +check-local-devices-ovs: src/core/devices/ovs/libnm-device-plugin-ovs.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/ovs/.libs/libnm-device-plugin-ovs.so) endif EXTRA_DIST += \ data/NetworkManager-ovs.conf \ - src/devices/ovs/meson.build + src/core/devices/ovs/meson.build ############################################################################### -# src/dnsmasq/tests +# src/core/dnsmasq/tests ############################################################################### -check_programs += src/dnsmasq/tests/test-dnsmasq-utils +check_programs += src/core/dnsmasq/tests/test-dnsmasq-utils -src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS = $(src_cppflags_test) +src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS = $(src_core_cppflags_test) -src_dnsmasq_tests_test_dnsmasq_utils_LDADD = \ - src/libNetworkManagerTest.la +src_core_dnsmasq_tests_test_dnsmasq_utils_LDADD = \ + src/core/libNetworkManagerTest.la -src_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS = \ +src_core_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) -$(src_dnsmasq_tests_test_dnsmasq_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dnsmasq_tests_test_dnsmasq_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/dnsmasq/tests/meson.build + src/core/dnsmasq/tests/meson.build ############################################################################### -# src/platform/tests +# src/core/platform/tests ############################################################################### -src_platform_tests_ldflags = \ +src_core_platform_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_platform_tests_libadd = \ - src/libNetworkManagerTest.la \ +src_core_platform_tests_libadd = \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) check_programs_norun += \ - src/platform/tests/monitor + src/core/platform/tests/monitor check_programs += \ - src/platform/tests/test-address-fake \ - src/platform/tests/test-address-linux \ - src/platform/tests/test-cleanup-fake \ - src/platform/tests/test-cleanup-linux \ - src/platform/tests/test-link-fake \ - src/platform/tests/test-link-linux \ - src/platform/tests/test-nmp-object \ - src/platform/tests/test-platform-general \ - src/platform/tests/test-route-fake \ - src/platform/tests/test-route-linux \ - src/platform/tests/test-tc-fake \ - src/platform/tests/test-tc-linux \ - $(NULL) - -src_platform_tests_monitor_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_monitor_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_monitor_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_address_fake_SOURCES = src/platform/tests/test-address.c -src_platform_tests_test_address_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_address_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_address_fake_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_address_linux_SOURCES = src/platform/tests/test-address.c -src_platform_tests_test_address_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_address_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_address_linux_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_cleanup_fake_SOURCES = src/platform/tests/test-cleanup.c -src_platform_tests_test_cleanup_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_cleanup_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_cleanup_fake_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_cleanup_linux_SOURCES = src/platform/tests/test-cleanup.c -src_platform_tests_test_cleanup_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_cleanup_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_cleanup_linux_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_link_fake_SOURCES = src/platform/tests/test-link.c -src_platform_tests_test_link_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_link_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_link_fake_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_link_linux_SOURCES = src/platform/tests/test-link.c -src_platform_tests_test_link_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_link_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_link_linux_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_nmp_object_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_test_nmp_object_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_nmp_object_LDADD = src/libNetworkManagerTest.la - -src_platform_tests_test_platform_general_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_test_platform_general_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_platform_general_LDADD = src/libNetworkManagerTest.la - -src_platform_tests_test_route_fake_SOURCES = src/platform/tests/test-route.c -src_platform_tests_test_route_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_route_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_route_fake_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_route_linux_SOURCES = src/platform/tests/test-route.c -src_platform_tests_test_route_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_route_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_route_linux_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_tc_fake_SOURCES = src/platform/tests/test-tc.c -src_platform_tests_test_tc_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_tc_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_tc_fake_LDADD = $(src_platform_tests_libadd) - -src_platform_tests_test_tc_linux_SOURCES = src/platform/tests/test-tc.c -src_platform_tests_test_tc_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_tc_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_tc_linux_LDADD = $(src_platform_tests_libadd) - - -$(src_platform_tests_monitor_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_address_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_address_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_cleanup_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_cleanup_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_link_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_link_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_nmp_object_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_platform_general_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_route_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_route_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_tc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_tc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) + src/core/platform/tests/test-address-fake \ + src/core/platform/tests/test-address-linux \ + src/core/platform/tests/test-cleanup-fake \ + src/core/platform/tests/test-cleanup-linux \ + src/core/platform/tests/test-link-fake \ + src/core/platform/tests/test-link-linux \ + src/core/platform/tests/test-nmp-object \ + src/core/platform/tests/test-platform-general \ + src/core/platform/tests/test-route-fake \ + src/core/platform/tests/test-route-linux \ + src/core/platform/tests/test-tc-fake \ + src/core/platform/tests/test-tc-linux \ + $(NULL) + +src_core_platform_tests_monitor_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_monitor_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_monitor_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_address_fake_SOURCES = src/core/platform/tests/test-address.c +src_core_platform_tests_test_address_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_address_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_address_fake_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_address_linux_SOURCES = src/core/platform/tests/test-address.c +src_core_platform_tests_test_address_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_address_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_address_linux_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_cleanup_fake_SOURCES = src/core/platform/tests/test-cleanup.c +src_core_platform_tests_test_cleanup_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_cleanup_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_cleanup_fake_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_cleanup_linux_SOURCES = src/core/platform/tests/test-cleanup.c +src_core_platform_tests_test_cleanup_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_cleanup_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_cleanup_linux_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_link_fake_SOURCES = src/core/platform/tests/test-link.c +src_core_platform_tests_test_link_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_link_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_link_fake_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_link_linux_SOURCES = src/core/platform/tests/test-link.c +src_core_platform_tests_test_link_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_link_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_link_linux_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_nmp_object_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_test_nmp_object_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_nmp_object_LDADD = src/core/libNetworkManagerTest.la + +src_core_platform_tests_test_platform_general_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_test_platform_general_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_platform_general_LDADD = src/core/libNetworkManagerTest.la + +src_core_platform_tests_test_route_fake_SOURCES = src/core/platform/tests/test-route.c +src_core_platform_tests_test_route_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_route_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_route_fake_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_route_linux_SOURCES = src/core/platform/tests/test-route.c +src_core_platform_tests_test_route_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_route_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_route_linux_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_tc_fake_SOURCES = src/core/platform/tests/test-tc.c +src_core_platform_tests_test_tc_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_tc_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_tc_fake_LDADD = $(src_core_platform_tests_libadd) + +src_core_platform_tests_test_tc_linux_SOURCES = src/core/platform/tests/test-tc.c +src_core_platform_tests_test_tc_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_tc_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_tc_linux_LDADD = $(src_core_platform_tests_libadd) + + +$(src_core_platform_tests_monitor_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_address_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_address_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_cleanup_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_cleanup_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_link_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_link_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_nmp_object_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_platform_general_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_route_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_route_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_tc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_tc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/platform/tests/meson.build \ + src/core/platform/tests/meson.build \ $(NULL) ############################################################################### -# src/devices/tests +# src/core/devices/tests ############################################################################### -src_devices_tests_ldflags = \ +src_core_devices_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) check_programs += \ - src/devices/tests/test-lldp \ - src/devices/tests/test-acd + src/core/devices/tests/test-lldp \ + src/core/devices/tests/test-acd -src_devices_tests_test_lldp_CPPFLAGS = $(src_cppflags_test) -src_devices_tests_test_lldp_LDFLAGS = $(src_devices_tests_ldflags) -src_devices_tests_test_lldp_LDADD = \ - src/libNetworkManagerTest.la +src_core_devices_tests_test_lldp_CPPFLAGS = $(src_core_cppflags_test) +src_core_devices_tests_test_lldp_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_devices_tests_test_lldp_LDADD = \ + src/core/libNetworkManagerTest.la -src_devices_tests_test_acd_CPPFLAGS = $(src_cppflags_test) -src_devices_tests_test_acd_LDFLAGS = $(src_devices_tests_ldflags) -src_devices_tests_test_acd_LDADD = \ - src/libNetworkManagerTest.la +src_core_devices_tests_test_acd_CPPFLAGS = $(src_core_cppflags_test) +src_core_devices_tests_test_acd_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_devices_tests_test_acd_LDADD = \ + src/core/libNetworkManagerTest.la -$(src_devices_tests_test_lldp_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_tests_test_acd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_tests_test_lldp_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_tests_test_acd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/devices/tests/meson.build + src/core/devices/tests/meson.build ############################################################################### -# src/ndisc/tests +# src/core/ndisc/tests ############################################################################### -src_ndisc_tests_ldflags = \ +src_core_ndisc_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_ndisc_tests_ldadd = \ - src/libNetworkManagerTest.la \ +src_core_ndisc_tests_ldadd = \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) -check_programs += src/ndisc/tests/test-ndisc-fake -check_programs_norun += src/ndisc/tests/test-ndisc-linux +check_programs += src/core/ndisc/tests/test-ndisc-fake +check_programs_norun += src/core/ndisc/tests/test-ndisc-linux -src_ndisc_tests_test_ndisc_linux_CPPFLAGS = $(src_cppflags_test) -src_ndisc_tests_test_ndisc_linux_LDFLAGS = $(src_ndisc_tests_ldflags) -src_ndisc_tests_test_ndisc_linux_LDADD = $(src_ndisc_tests_ldadd) +src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS = $(src_core_cppflags_test) +src_core_ndisc_tests_test_ndisc_linux_LDFLAGS = $(src_core_ndisc_tests_ldflags) +src_core_ndisc_tests_test_ndisc_linux_LDADD = $(src_core_ndisc_tests_ldadd) -src_ndisc_tests_test_ndisc_fake_CPPFLAGS = $(src_cppflags_test) -src_ndisc_tests_test_ndisc_fake_LDFLAGS = $(src_ndisc_tests_ldflags) -src_ndisc_tests_test_ndisc_fake_LDADD = $(src_ndisc_tests_ldadd) +src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS = $(src_core_cppflags_test) +src_core_ndisc_tests_test_ndisc_fake_LDFLAGS = $(src_core_ndisc_tests_ldflags) +src_core_ndisc_tests_test_ndisc_fake_LDADD = $(src_core_ndisc_tests_ldadd) -$(src_ndisc_tests_test_ndisc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_ndisc_tests_test_ndisc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ndisc_tests_test_ndisc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ndisc_tests_test_ndisc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/ndisc/tests/meson.build + src/core/ndisc/tests/meson.build ############################################################################### -# src/supplicant/tests +# src/core/supplicant/tests ############################################################################### -check_programs += src/supplicant/tests/test-supplicant-config +check_programs += src/core/supplicant/tests/test-supplicant-config -src_supplicant_tests_test_supplicant_config_CPPFLAGS = $(src_cppflags_test) +src_core_supplicant_tests_test_supplicant_config_CPPFLAGS = $(src_core_cppflags_test) -src_supplicant_tests_test_supplicant_config_LDADD = \ - src/libNetworkManagerTest.la +src_core_supplicant_tests_test_supplicant_config_LDADD = \ + src/core/libNetworkManagerTest.la -src_supplicant_tests_test_supplicant_config_LDFLAGS = \ +src_core_supplicant_tests_test_supplicant_config_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) -$(src_supplicant_tests_test_supplicant_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_supplicant_tests_test_supplicant_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/supplicant/tests/certs/test-ca-cert.pem \ - src/supplicant/tests/certs/test-cert.p12 \ - src/supplicant/tests/meson.build + src/core/supplicant/tests/certs/test-ca-cert.pem \ + src/core/supplicant/tests/certs/test-cert.p12 \ + src/core/supplicant/tests/meson.build ############################################################################### -# src/tests/config +# src/core/tests/config ############################################################################### -check_programs += src/tests/config/test-config +check_programs += src/core/tests/config/test-config -src_tests_config_test_config_SOURCES = \ - src/tests/config/nm-test-device.c \ - src/tests/config/nm-test-device.h \ - src/tests/config/test-config.c +src_core_tests_config_test_config_SOURCES = \ + src/core/tests/config/nm-test-device.c \ + src/core/tests/config/nm-test-device.h \ + src/core/tests/config/test-config.c -src_tests_config_test_config_CPPFLAGS = $(src_cppflags_test) +src_core_tests_config_test_config_CPPFLAGS = $(src_core_cppflags_test) -src_tests_config_test_config_LDADD = \ - src/libNetworkManagerTest.la +src_core_tests_config_test_config_LDADD = \ + src/core/libNetworkManagerTest.la -src_tests_config_test_config_LDFLAGS = \ +src_core_tests_config_test_config_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) -$(src_tests_config_test_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_config_test_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/tests/config/NetworkManager.conf \ - src/tests/config/NetworkManager-warn.conf \ - src/tests/config/NetworkManager.state \ - src/tests/config/bad.conf \ - src/tests/config/global-dns-invalid.conf \ - src/tests/config/conf.d/00-overrides.conf \ - src/tests/config/conf.d/10-more.conf \ - src/tests/config/conf.d/20-config-enable-1.conf \ - src/tests/config/conf.d/90-last.conf \ - src/tests/config/meson.build + src/core/tests/config/NetworkManager.conf \ + src/core/tests/config/NetworkManager-warn.conf \ + src/core/tests/config/NetworkManager.state \ + src/core/tests/config/bad.conf \ + src/core/tests/config/global-dns-invalid.conf \ + src/core/tests/config/conf.d/00-overrides.conf \ + src/core/tests/config/conf.d/10-more.conf \ + src/core/tests/config/conf.d/20-config-enable-1.conf \ + src/core/tests/config/conf.d/90-last.conf \ + src/core/tests/config/meson.build ############################################################################### -# src/tests +# src/core/tests ############################################################################### -src_tests_ldflags = \ +src_core_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_tests_ldadd = \ - src/libNetworkManagerTest.la +src_core_tests_ldadd = \ + src/core/libNetworkManagerTest.la check_programs += \ - src/tests/test-core \ - src/tests/test-core-with-expect \ - src/tests/test-dcb \ - src/tests/test-ip4-config \ - src/tests/test-ip6-config \ - src/tests/test-l3cfg \ - src/tests/test-systemd \ - src/tests/test-utils \ - src/tests/test-wired-defname \ - $(NULL) - -src_tests_test_ip4_config_CPPFLAGS = $(src_cppflags_test) -src_tests_test_ip4_config_LDFLAGS = $(src_tests_ldflags) -src_tests_test_ip4_config_LDADD = $(src_tests_ldadd) - -src_tests_test_ip6_config_CPPFLAGS = $(src_cppflags_test) -src_tests_test_ip6_config_LDFLAGS = $(src_tests_ldflags) -src_tests_test_ip6_config_LDADD = $(src_tests_ldadd) - -src_tests_test_dcb_CPPFLAGS = $(src_cppflags_test) -src_tests_test_dcb_LDFLAGS = $(src_tests_ldflags) -src_tests_test_dcb_LDADD = $(src_tests_ldadd) - -src_tests_test_core_CPPFLAGS = $(src_cppflags_test) -src_tests_test_core_LDFLAGS = $(src_tests_ldflags) -src_tests_test_core_LDADD = $(src_tests_ldadd) - -src_tests_test_core_with_expect_CPPFLAGS = $(src_cppflags_test) -src_tests_test_core_with_expect_LDFLAGS = $(src_tests_ldflags) -src_tests_test_core_with_expect_LDADD = $(src_tests_ldadd) - -src_tests_test_wired_defname_CPPFLAGS = $(src_cppflags_test) -src_tests_test_wired_defname_LDFLAGS = $(src_tests_ldflags) -src_tests_test_wired_defname_LDADD = $(src_tests_ldadd) - -src_tests_test_utils_CPPFLAGS = $(src_cppflags_test) -src_tests_test_utils_LDFLAGS = $(src_tests_ldflags) -src_tests_test_utils_LDADD = $(src_tests_ldadd) - -src_tests_test_l3cfg_CPPFLAGS = $(src_cppflags_test) -src_tests_test_l3cfg_LDFLAGS = $(src_devices_tests_ldflags) -src_tests_test_l3cfg_LDADD = $(src_tests_ldadd) - -$(src_tests_test_core_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_core_with_expect_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_dcb_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_ip4_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_ip6_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_l3cfg_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_wired_defname_OBJECTS): $(libnm_core_lib_h_pub_mkenums) - -src_tests_test_systemd_CPPFLAGS = \ - $(src_libnm_systemd_core_la_cppflags) \ + src/core/tests/test-core \ + src/core/tests/test-core-with-expect \ + src/core/tests/test-dcb \ + src/core/tests/test-ip4-config \ + src/core/tests/test-ip6-config \ + src/core/tests/test-l3cfg \ + src/core/tests/test-systemd \ + src/core/tests/test-utils \ + src/core/tests/test-wired-defname \ + $(NULL) + +src_core_tests_test_ip4_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_ip4_config_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_ip4_config_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_ip6_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_ip6_config_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_ip6_config_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_dcb_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_dcb_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_dcb_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_core_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_core_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_core_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_core_with_expect_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_core_with_expect_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_core_with_expect_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_wired_defname_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_wired_defname_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_wired_defname_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_utils_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_utils_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_utils_LDADD = $(src_core_tests_ldadd) + +src_core_tests_test_l3cfg_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_l3cfg_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_tests_test_l3cfg_LDADD = $(src_core_tests_ldadd) + +$(src_core_tests_test_core_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_core_with_expect_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_dcb_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_ip4_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_ip6_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_l3cfg_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_wired_defname_OBJECTS): $(libnm_core_lib_h_pub_mkenums) + +src_core_tests_test_systemd_CPPFLAGS = \ + $(src_core_libnm_systemd_core_la_cppflags) \ -DNETWORKMANAGER_COMPILATION_TEST \ $(NULL) -src_tests_test_systemd_LDFLAGS = \ +src_core_tests_test_systemd_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -src_tests_test_systemd_LDADD = \ - src/libnm-systemd-core.la \ +src_core_tests_test_systemd_LDADD = \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) -$(src_tests_test_systemd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_systemd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) EXTRA_DIST += \ - src/tests/test-secret-agent.py \ - src/tests/meson.build + src/core/tests/test-secret-agent.py \ + src/core/tests/meson.build ############################################################################### # dispatcher @@ -5394,7 +5394,7 @@ typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) BUILT_SOURCES += $(typelib_DATA) dbusservicedir = $(DBUS_SYS_DIR) -dbusservice_DATA += src/org.freedesktop.NetworkManager.conf +dbusservice_DATA += src/core/org.freedesktop.NetworkManager.conf ############################################################################### @@ -5422,7 +5422,7 @@ EXTRA_DIST += \ linker-script-binary.ver \ linker-script-devices.ver \ linker-script-settings.ver \ - src/ppp/nm-ppp-plugin.ver \ + src/core/ppp/nm-ppp-plugin.ver \ Makefile.glib \ autogen.sh \ lsan.suppressions \ @@ -5436,6 +5436,7 @@ EXTRA_DIST += \ po/meson.build \ \ shared/nm-default.h \ + shared/nm-gassert-patch.h \ shared/nm-test-libnm-utils.h \ shared/nm-test-utils-impl.c \ shared/nm-utils/nm-compat.c \ @@ -5461,7 +5462,7 @@ EXTRA_DIST += \ tools/test-sudo-wrapper.sh \ tools/enums-to-docbook.pl \ \ - src/settings/plugins/meson.build \ + src/core/settings/plugins/meson.build \ \ $(NULL) @@ -5521,6 +5522,10 @@ cscope: ############################################################################### +check-progs: all $(check_PROGRAMS) $(check_LTLIBRARIES) + +############################################################################### + .PRECIOUS: test-suite.log .DELETE_ON_ERROR: -.PHONY: cscope dist-configure-check $(check_local) $(dist_hook) +.PHONY: check-progs cscope dist-configure-check $(check_local) $(dist_hook) diff --git a/Makefile.in b/Makefile.in index 1f25b2f..c076955 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -135,16 +135,16 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -noinst_PROGRAMS = src/NetworkManager-all-sym$(EXEEXT) \ +noinst_PROGRAMS = src/core/NetworkManager-all-sym$(EXEEXT) \ clients/cli/generate-docs-nm-settings-nmcli$(EXEEXT) \ $(am__EXEEXT_19) check_PROGRAMS = $(am__EXEEXT_17) -sbin_PROGRAMS = src/NetworkManager$(EXEEXT) +sbin_PROGRAMS = src/core/NetworkManager$(EXEEXT) bin_PROGRAMS = clients/nm-online$(EXEEXT) $(am__EXEEXT_1) \ $(am__EXEEXT_2) -libexec_PROGRAMS = src/nm-iface-helper$(EXEEXT) \ - src/initrd/nm-initrd-generator$(EXEEXT) \ - src/dhcp/nm-dhcp-helper$(EXEEXT) \ +libexec_PROGRAMS = src/core/nm-iface-helper$(EXEEXT) \ + src/core/initrd/nm-initrd-generator$(EXEEXT) \ + src/core/dhcp/nm-dhcp-helper$(EXEEXT) \ dispatcher/nm-dispatcher$(EXEEXT) $(am__EXEEXT_18) TESTS = $(am__EXEEXT_12) @HAVE_DOCS_TRUE@am__append_1 = \ @@ -171,70 +171,69 @@ TESTS = $(am__EXEEXT_12) @HAVE_INTROSPECTION_FALSE@am__append_16 = $(libnm_tests_programs_req_introspection) @REQUIRE_ROOT_TESTS_TRUE@am__append_17 = -DREQUIRE_ROOT_TESTS=1 @WITH_WEXT_TRUE@am__append_18 = \ -@WITH_WEXT_TRUE@ src/platform/wifi/nm-wifi-utils-wext.c \ -@WITH_WEXT_TRUE@ src/platform/wifi/nm-wifi-utils-wext.h +@WITH_WEXT_TRUE@ src/core/platform/wifi/nm-wifi-utils-wext.c \ +@WITH_WEXT_TRUE@ src/core/platform/wifi/nm-wifi-utils-wext.h ############################################################################### -# src/ppp +# src/core/ppp ############################################################################### -@WITH_PPP_TRUE@am__append_19 = src/ppp/libnm-ppp-plugin.la -@WITH_PPP_TRUE@am__append_20 = src/ppp/nm-pppd-plugin.la +@WITH_PPP_TRUE@am__append_19 = src/core/ppp/libnm-ppp-plugin.la +@WITH_PPP_TRUE@am__append_20 = src/core/ppp/nm-pppd-plugin.la ############################################################################### -# src/settings/plugins/ifcfg-rh +# src/core/settings/plugins/ifcfg-rh ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_21 = src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_21 = src/core/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la @CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_22 = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_23 = $(nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_23 = $(nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) @CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_24 = check-local-symbols-settings-ifcfg-rh ############################################################################### -# src/settings/plugins/ifcfg-rh/tests +# src/core/settings/plugins/ifcfg-rh/tests ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_25 = src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_25 = src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh @CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_26 = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nm-ifup \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nm-ifdown +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nm-ifup \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nm-ifdown @CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_27 = install-data-hook-ifcfg-rh ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_28 = src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__append_28 = src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf ############################################################################### -# src/settings/plugins/ifupdown +# src/core/settings/plugins/ifupdown ############################################################################### -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_29 = src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_30 = src/settings/plugins/ifupdown/libnms-ifupdown-core.la +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_29 = src/core/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_30 = src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la @CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_31 = check-local-symbols-settings-ifupdown ############################################################################### -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_32 = src/settings/plugins/ifupdown/tests/test-ifupdown +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__append_32 = src/core/settings/plugins/ifupdown/tests/test-ifupdown ############################################################################### -@WITH_MODEM_MANAGER_1_TRUE@am__append_33 = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-device-plugin-wwan.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/libnm-device-plugin-bluetooth.la +@WITH_MODEM_MANAGER_1_TRUE@am__append_33 = src/core/devices/wwan/libnm-wwan.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm-device-plugin-wwan.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/libnm-device-plugin-bluetooth.la @WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@am__append_34 = \ -@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@ src/devices/wwan/nm-modem-ofono.c \ -@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@ src/devices/wwan/nm-modem-ofono.h +@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@ src/core/devices/wwan/nm-modem-ofono.c \ +@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@ src/core/devices/wwan/nm-modem-ofono.h @WITH_MODEM_MANAGER_1_TRUE@am__append_35 = check-local-devices-wwan \ @WITH_MODEM_MANAGER_1_TRUE@ check-local-devices-bluetooth -@WITH_MODEM_MANAGER_1_TRUE@am__append_36 = src/devices/wwan/tests/test-service-providers +@WITH_MODEM_MANAGER_1_TRUE@am__append_36 = src/core/devices/wwan/tests/test-service-providers ############################################################################### -# src/devices/bluetooth +# src/core/devices/bluetooth ############################################################################### -@WITH_MODEM_MANAGER_1_TRUE@am__append_37 = src/devices/bluetooth/libnm-bluetooth-utils.la +@WITH_MODEM_MANAGER_1_TRUE@am__append_37 = src/core/devices/bluetooth/libnm-bluetooth-utils.la @WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@am__append_38 = \ -@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bluez5-dun.c \ -@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bluez5-dun.h \ +@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bluez5-dun.c \ +@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bluez5-dun.h \ @WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@ $(NULL) @WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@am__append_39 = $(BLUEZ5_CFLAGS) @@ -242,30 +241,30 @@ TESTS = $(am__EXEEXT_12) ############################################################################### @WITH_MODEM_MANAGER_1_TRUE@am__append_41 = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/tests/nm-bt-test +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/tests/nm-bt-test ############################################################################### -# src/devices/wifi +# src/core/devices/wifi ############################################################################### -@WITH_WIFI_TRUE@am__append_42 = src/devices/wifi/libnm-wifi-base.la +@WITH_WIFI_TRUE@am__append_42 = src/core/devices/wifi/libnm-wifi-base.la @WITH_IWD_TRUE@@WITH_WIFI_TRUE@am__append_43 = \ -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-iwd.c \ -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-iwd.h \ -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/devices/wifi/nm-iwd-manager.c \ -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/devices/wifi/nm-iwd-manager.h \ +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-iwd.c \ +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-iwd.h \ +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-iwd-manager.c \ +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-iwd-manager.h \ @WITH_IWD_TRUE@@WITH_WIFI_TRUE@ $(NULL) -@WITH_WIFI_TRUE@am__append_44 = src/devices/wifi/libnm-device-plugin-wifi.la +@WITH_WIFI_TRUE@am__append_44 = src/core/devices/wifi/libnm-device-plugin-wifi.la @WITH_WIFI_TRUE@am__append_45 = check-local-devices-wifi -@WITH_WIFI_TRUE@am__append_46 = src/devices/wifi/tests/test-devices-wifi +@WITH_WIFI_TRUE@am__append_46 = src/core/devices/wifi/tests/test-devices-wifi ############################################################################### -# src/devices/team +# src/core/devices/team ############################################################################### -@WITH_TEAMDCTL_TRUE@am__append_47 = src/devices/team/libnm-device-plugin-team.la +@WITH_TEAMDCTL_TRUE@am__append_47 = src/core/devices/team/libnm-device-plugin-team.la @WITH_TEAMDCTL_TRUE@am__append_48 = check-local-devices-team -@WITH_OPENVSWITCH_TRUE@am__append_49 = src/devices/ovs/libnm-device-plugin-ovs.la +@WITH_OPENVSWITCH_TRUE@am__append_49 = src/core/devices/ovs/libnm-device-plugin-ovs.la @BUILD_DOCS_TRUE@am__append_50 = $(clients_common_settings_doc_h) @BUILD_DOCS_TRUE@am__append_51 = check-local-settings-docs @HAVE_INTROSPECTION_TRUE@am__append_52 = clients/common/libnmc.la @@ -411,10 +410,10 @@ am__EXEEXT_4 = libnm/tests/test-nm-client$(EXEEXT) \ libnm/tests/test-remote-settings-client$(EXEEXT) \ libnm/tests/test-secret-agent$(EXEEXT) @HAVE_INTROSPECTION_TRUE@am__EXEEXT_5 = $(am__EXEEXT_4) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__EXEEXT_6 = src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__EXEEXT_7 = src/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) -@WITH_MODEM_MANAGER_1_TRUE@am__EXEEXT_8 = src/devices/wwan/tests/test-service-providers$(EXEEXT) -@WITH_WIFI_TRUE@am__EXEEXT_9 = src/devices/wifi/tests/test-devices-wifi$(EXEEXT) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am__EXEEXT_6 = src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am__EXEEXT_7 = src/core/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) +@WITH_MODEM_MANAGER_1_TRUE@am__EXEEXT_8 = src/core/devices/wwan/tests/test-service-providers$(EXEEXT) +@WITH_WIFI_TRUE@am__EXEEXT_9 = src/core/devices/wifi/tests/test-devices-wifi$(EXEEXT) @HAVE_INTROSPECTION_TRUE@am__EXEEXT_10 = clients/common/tests/test-clients-common$(EXEEXT) @BUILD_NMCLI_TRUE@@HAVE_INTROSPECTION_FALSE@am__EXEEXT_11 = clients/common/tests/test-clients-common$(EXEEXT) am__EXEEXT_12 = shared/nm-platform/tests/test-nm-platform$(EXEEXT) \ @@ -427,51 +426,54 @@ am__EXEEXT_12 = shared/nm-platform/tests/test-nm-platform$(EXEEXT) \ libnm-core/tests/test-setting$(EXEEXT) \ libnm-core/tests/test-settings-defaults$(EXEEXT) \ libnm/tests/test-libnm$(EXEEXT) $(am__EXEEXT_5) \ - src/initrd/tests/test-dt-reader$(EXEEXT) \ - src/initrd/tests/test-ibft-reader$(EXEEXT) \ - src/initrd/tests/test-cmdline-reader$(EXEEXT) \ - src/dhcp/tests/test-dhcp-dhclient$(EXEEXT) \ - src/dhcp/tests/test-dhcp-utils$(EXEEXT) \ - src/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) \ + src/core/initrd/tests/test-dt-reader$(EXEEXT) \ + src/core/initrd/tests/test-ibft-reader$(EXEEXT) \ + src/core/initrd/tests/test-cmdline-reader$(EXEEXT) \ + src/core/dhcp/tests/test-dhcp-dhclient$(EXEEXT) \ + src/core/dhcp/tests/test-dhcp-utils$(EXEEXT) \ + src/core/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) \ $(am__EXEEXT_6) $(am__EXEEXT_7) $(am__EXEEXT_8) \ - $(am__EXEEXT_9) src/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) \ - src/platform/tests/test-address-fake$(EXEEXT) \ - src/platform/tests/test-address-linux$(EXEEXT) \ - src/platform/tests/test-cleanup-fake$(EXEEXT) \ - src/platform/tests/test-cleanup-linux$(EXEEXT) \ - src/platform/tests/test-link-fake$(EXEEXT) \ - src/platform/tests/test-link-linux$(EXEEXT) \ - src/platform/tests/test-nmp-object$(EXEEXT) \ - src/platform/tests/test-platform-general$(EXEEXT) \ - src/platform/tests/test-route-fake$(EXEEXT) \ - src/platform/tests/test-route-linux$(EXEEXT) \ - src/platform/tests/test-tc-fake$(EXEEXT) \ - src/platform/tests/test-tc-linux$(EXEEXT) \ - src/devices/tests/test-lldp$(EXEEXT) \ - src/devices/tests/test-acd$(EXEEXT) \ - src/ndisc/tests/test-ndisc-fake$(EXEEXT) \ - src/supplicant/tests/test-supplicant-config$(EXEEXT) \ - src/tests/config/test-config$(EXEEXT) \ - src/tests/test-core$(EXEEXT) \ - src/tests/test-core-with-expect$(EXEEXT) \ - src/tests/test-dcb$(EXEEXT) src/tests/test-ip4-config$(EXEEXT) \ - src/tests/test-ip6-config$(EXEEXT) \ - src/tests/test-l3cfg$(EXEEXT) src/tests/test-systemd$(EXEEXT) \ - src/tests/test-utils$(EXEEXT) \ - src/tests/test-wired-defname$(EXEEXT) \ + $(am__EXEEXT_9) \ + src/core/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) \ + src/core/platform/tests/test-address-fake$(EXEEXT) \ + src/core/platform/tests/test-address-linux$(EXEEXT) \ + src/core/platform/tests/test-cleanup-fake$(EXEEXT) \ + src/core/platform/tests/test-cleanup-linux$(EXEEXT) \ + src/core/platform/tests/test-link-fake$(EXEEXT) \ + src/core/platform/tests/test-link-linux$(EXEEXT) \ + src/core/platform/tests/test-nmp-object$(EXEEXT) \ + src/core/platform/tests/test-platform-general$(EXEEXT) \ + src/core/platform/tests/test-route-fake$(EXEEXT) \ + src/core/platform/tests/test-route-linux$(EXEEXT) \ + src/core/platform/tests/test-tc-fake$(EXEEXT) \ + src/core/platform/tests/test-tc-linux$(EXEEXT) \ + src/core/devices/tests/test-lldp$(EXEEXT) \ + src/core/devices/tests/test-acd$(EXEEXT) \ + src/core/ndisc/tests/test-ndisc-fake$(EXEEXT) \ + src/core/supplicant/tests/test-supplicant-config$(EXEEXT) \ + src/core/tests/config/test-config$(EXEEXT) \ + src/core/tests/test-core$(EXEEXT) \ + src/core/tests/test-core-with-expect$(EXEEXT) \ + src/core/tests/test-dcb$(EXEEXT) \ + src/core/tests/test-ip4-config$(EXEEXT) \ + src/core/tests/test-ip6-config$(EXEEXT) \ + src/core/tests/test-l3cfg$(EXEEXT) \ + src/core/tests/test-systemd$(EXEEXT) \ + src/core/tests/test-utils$(EXEEXT) \ + src/core/tests/test-wired-defname$(EXEEXT) \ dispatcher/tests/test-dispatcher-envp$(EXEEXT) \ $(am__EXEEXT_10) $(am__EXEEXT_11) \ clients/common/tests/test-libnm-core-aux$(EXEEXT) @HAVE_INTROSPECTION_FALSE@am__EXEEXT_13 = $(am__EXEEXT_4) -@WITH_MODEM_MANAGER_1_TRUE@am__EXEEXT_14 = src/devices/bluetooth/tests/nm-bt-test$(EXEEXT) +@WITH_MODEM_MANAGER_1_TRUE@am__EXEEXT_14 = src/core/devices/bluetooth/tests/nm-bt-test$(EXEEXT) @WITH_QT_TRUE@am__EXEEXT_15 = \ @WITH_QT_TRUE@ examples/C/qt/add-connection-wired$(EXEEXT) \ @WITH_QT_TRUE@ examples/C/qt/list-connections$(EXEEXT) \ @WITH_QT_TRUE@ examples/C/qt/change-ipv4-addresses$(EXEEXT) \ @WITH_QT_TRUE@ examples/C/qt/monitor-nm-running$(EXEEXT) am__EXEEXT_16 = $(am__EXEEXT_13) $(am__EXEEXT_14) \ - src/platform/tests/monitor$(EXEEXT) \ - src/ndisc/tests/test-ndisc-linux$(EXEEXT) \ + src/core/platform/tests/monitor$(EXEEXT) \ + src/core/ndisc/tests/test-ndisc-linux$(EXEEXT) \ examples/C/glib/add-connection-gdbus$(EXEEXT) \ examples/C/glib/add-connection-libnm$(EXEEXT) \ examples/C/glib/get-active-connections-gdbus$(EXEEXT) \ @@ -814,7 +816,14 @@ libnm_core_nm_libnm_core_intern_libnm_libnm_core_intern_la_LINK = \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(libnm_core_nm_libnm_core_intern_libnm_libnm_core_intern_la_LDFLAGS) \ $(LDFLAGS) -o $@ -libnm_liblibnm_la_DEPENDENCIES = \ +libnm_libnm_la_DEPENDENCIES = libnm/libnm_static.la +am_libnm_libnm_la_OBJECTS = $(am__objects_2) $(am__objects_2) +libnm_libnm_la_OBJECTS = $(am_libnm_libnm_la_OBJECTS) +libnm_libnm_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnm_libnm_la_LDFLAGS) $(LDFLAGS) -o \ + $@ +libnm_libnm_static_la_DEPENDENCIES = \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la $(libnm_crypto_lib) \ @@ -827,69 +836,64 @@ libnm_liblibnm_la_DEPENDENCIES = \ shared/nm-std-aux/libnm-std-aux.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -am__objects_6 = libnm/liblibnm_la-nm-client.lo \ - libnm/liblibnm_la-nm-object.lo libnm/liblibnm_la-nm-device.lo \ - libnm/liblibnm_la-nm-active-connection.lo \ - libnm/liblibnm_la-nm-access-point.lo \ - libnm/liblibnm_la-nm-checkpoint.lo \ - libnm/liblibnm_la-nm-dbus-helpers.lo \ - libnm/liblibnm_la-nm-device-6lowpan.lo \ - libnm/liblibnm_la-nm-device-adsl.lo \ - libnm/liblibnm_la-nm-device-bond.lo \ - libnm/liblibnm_la-nm-device-bridge.lo \ - libnm/liblibnm_la-nm-device-bt.lo \ - libnm/liblibnm_la-nm-device-dummy.lo \ - libnm/liblibnm_la-nm-device-ethernet.lo \ - libnm/liblibnm_la-nm-device-generic.lo \ - libnm/liblibnm_la-nm-device-infiniband.lo \ - libnm/liblibnm_la-nm-device-ip-tunnel.lo \ - libnm/liblibnm_la-nm-device-macsec.lo \ - libnm/liblibnm_la-nm-device-macvlan.lo \ - libnm/liblibnm_la-nm-device-modem.lo \ - libnm/liblibnm_la-nm-device-olpc-mesh.lo \ - libnm/liblibnm_la-nm-device-ovs-bridge.lo \ - libnm/liblibnm_la-nm-device-ovs-interface.lo \ - libnm/liblibnm_la-nm-device-ovs-port.lo \ - libnm/liblibnm_la-nm-device-ppp.lo \ - libnm/liblibnm_la-nm-device-team.lo \ - libnm/liblibnm_la-nm-device-tun.lo \ - libnm/liblibnm_la-nm-device-veth.lo \ - libnm/liblibnm_la-nm-device-vlan.lo \ - libnm/liblibnm_la-nm-device-vrf.lo \ - libnm/liblibnm_la-nm-device-vxlan.lo \ - libnm/liblibnm_la-nm-device-wifi-p2p.lo \ - libnm/liblibnm_la-nm-device-wifi.lo \ - libnm/liblibnm_la-nm-device-wimax.lo \ - libnm/liblibnm_la-nm-device-wireguard.lo \ - libnm/liblibnm_la-nm-device-wpan.lo \ - libnm/liblibnm_la-nm-dhcp-config.lo \ - libnm/liblibnm_la-nm-dhcp4-config.lo \ - libnm/liblibnm_la-nm-dhcp6-config.lo \ - libnm/liblibnm_la-nm-dns-manager.lo \ - libnm/liblibnm_la-nm-ip-config.lo \ - libnm/liblibnm_la-nm-ip4-config.lo \ - libnm/liblibnm_la-nm-ip6-config.lo \ - libnm/liblibnm_la-nm-libnm-utils.lo \ - libnm/liblibnm_la-nm-remote-connection.lo \ - libnm/liblibnm_la-nm-secret-agent-old.lo \ - libnm/liblibnm_la-nm-vpn-connection.lo \ - libnm/liblibnm_la-nm-vpn-editor.lo \ - libnm/liblibnm_la-nm-vpn-plugin-old.lo \ - libnm/liblibnm_la-nm-vpn-service-plugin.lo \ - libnm/liblibnm_la-nm-wifi-p2p-peer.lo \ - libnm/liblibnm_la-nm-wimax-nsp.lo -am_libnm_liblibnm_la_OBJECTS = $(am__objects_6) -am__objects_7 = libnm/liblibnm_la-nm-enum-types.lo -nodist_libnm_liblibnm_la_OBJECTS = $(am__objects_2) $(am__objects_7) -libnm_liblibnm_la_OBJECTS = $(am_libnm_liblibnm_la_OBJECTS) \ - $(nodist_libnm_liblibnm_la_OBJECTS) -libnm_libnm_la_DEPENDENCIES = libnm/liblibnm.la -am_libnm_libnm_la_OBJECTS = $(am__objects_2) $(am__objects_2) -libnm_libnm_la_OBJECTS = $(am_libnm_libnm_la_OBJECTS) -libnm_libnm_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(libnm_libnm_la_LDFLAGS) $(LDFLAGS) -o \ - $@ +am__objects_6 = libnm/libnm_static_la-nm-client.lo \ + libnm/libnm_static_la-nm-object.lo \ + libnm/libnm_static_la-nm-device.lo \ + libnm/libnm_static_la-nm-active-connection.lo \ + libnm/libnm_static_la-nm-access-point.lo \ + libnm/libnm_static_la-nm-checkpoint.lo \ + libnm/libnm_static_la-nm-dbus-helpers.lo \ + libnm/libnm_static_la-nm-device-6lowpan.lo \ + libnm/libnm_static_la-nm-device-adsl.lo \ + libnm/libnm_static_la-nm-device-bond.lo \ + libnm/libnm_static_la-nm-device-bridge.lo \ + libnm/libnm_static_la-nm-device-bt.lo \ + libnm/libnm_static_la-nm-device-dummy.lo \ + libnm/libnm_static_la-nm-device-ethernet.lo \ + libnm/libnm_static_la-nm-device-generic.lo \ + libnm/libnm_static_la-nm-device-infiniband.lo \ + libnm/libnm_static_la-nm-device-ip-tunnel.lo \ + libnm/libnm_static_la-nm-device-macsec.lo \ + libnm/libnm_static_la-nm-device-macvlan.lo \ + libnm/libnm_static_la-nm-device-modem.lo \ + libnm/libnm_static_la-nm-device-olpc-mesh.lo \ + libnm/libnm_static_la-nm-device-ovs-bridge.lo \ + libnm/libnm_static_la-nm-device-ovs-interface.lo \ + libnm/libnm_static_la-nm-device-ovs-port.lo \ + libnm/libnm_static_la-nm-device-ppp.lo \ + libnm/libnm_static_la-nm-device-team.lo \ + libnm/libnm_static_la-nm-device-tun.lo \ + libnm/libnm_static_la-nm-device-veth.lo \ + libnm/libnm_static_la-nm-device-vlan.lo \ + libnm/libnm_static_la-nm-device-vrf.lo \ + libnm/libnm_static_la-nm-device-vxlan.lo \ + libnm/libnm_static_la-nm-device-wifi-p2p.lo \ + libnm/libnm_static_la-nm-device-wifi.lo \ + libnm/libnm_static_la-nm-device-wimax.lo \ + libnm/libnm_static_la-nm-device-wireguard.lo \ + libnm/libnm_static_la-nm-device-wpan.lo \ + libnm/libnm_static_la-nm-dhcp-config.lo \ + libnm/libnm_static_la-nm-dhcp4-config.lo \ + libnm/libnm_static_la-nm-dhcp6-config.lo \ + libnm/libnm_static_la-nm-dns-manager.lo \ + libnm/libnm_static_la-nm-ip-config.lo \ + libnm/libnm_static_la-nm-ip4-config.lo \ + libnm/libnm_static_la-nm-ip6-config.lo \ + libnm/libnm_static_la-nm-libnm-utils.lo \ + libnm/libnm_static_la-nm-remote-connection.lo \ + libnm/libnm_static_la-nm-secret-agent-old.lo \ + libnm/libnm_static_la-nm-vpn-connection.lo \ + libnm/libnm_static_la-nm-vpn-editor.lo \ + libnm/libnm_static_la-nm-vpn-plugin-old.lo \ + libnm/libnm_static_la-nm-vpn-service-plugin.lo \ + libnm/libnm_static_la-nm-wifi-p2p-peer.lo \ + libnm/libnm_static_la-nm-wimax-nsp.lo +am_libnm_libnm_static_la_OBJECTS = $(am__objects_6) +am__objects_7 = libnm/libnm_static_la-nm-enum-types.lo +nodist_libnm_libnm_static_la_OBJECTS = $(am__objects_2) \ + $(am__objects_7) +libnm_libnm_static_la_OBJECTS = $(am_libnm_libnm_static_la_OBJECTS) \ + $(nodist_libnm_libnm_static_la_OBJECTS) libnm_nm_libnm_aux_libnm_libnm_aux_la_DEPENDENCIES = \ $(am__DEPENDENCIES_1) libnm/libnm.la am_libnm_nm_libnm_aux_libnm_libnm_aux_la_OBJECTS = \ @@ -1089,198 +1093,197 @@ am_shared_systemd_libnm_systemd_shared_la_OBJECTS = \ shared/systemd/src/shared/libnm_systemd_shared_la-web-util.lo shared_systemd_libnm_systemd_shared_la_OBJECTS = \ $(am_shared_systemd_libnm_systemd_shared_la_OBJECTS) -src_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES = \ +src_core_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES = \ $(am__DEPENDENCIES_1) -am_src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS = src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo \ - src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo -src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS = \ - $(am_src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) -src_devices_adsl_libnm_device_plugin_adsl_la_LINK = $(LIBTOOL) \ +am_src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS = src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo \ + src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo +src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS = $(am_src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) +src_core_devices_adsl_libnm_device_plugin_adsl_la_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS) \ + $(src_core_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS) \ $(LDFLAGS) -o $@ @WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES = \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_2) -am__src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES_DIST = \ - src/devices/bluetooth/nm-bluez-common.h \ - src/devices/bluetooth/nm-bt-error.c \ - src/devices/bluetooth/nm-bt-error.h \ - src/devices/bluetooth/nm-bluez5-dun.c \ - src/devices/bluetooth/nm-bluez5-dun.h -@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@am__objects_10 = src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS = src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo \ +am__src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES_DIST = \ + src/core/devices/bluetooth/nm-bluez-common.h \ + src/core/devices/bluetooth/nm-bt-error.c \ + src/core/devices/bluetooth/nm-bt-error.h \ + src/core/devices/bluetooth/nm-bluez5-dun.c \ + src/core/devices/bluetooth/nm-bluez5-dun.h +@WITH_BLUEZ5_DUN_TRUE@@WITH_MODEM_MANAGER_1_TRUE@am__objects_10 = src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS = src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__objects_10) -src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS = \ - $(am_src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_bluetooth_libnm_bluetooth_utils_la_rpath = -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES = src/devices/bluetooth/libnm-bluetooth-utils.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.la \ +src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS = $(am_src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_bluetooth_libnm_bluetooth_utils_la_rpath = +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES = src/core/devices/bluetooth/libnm-bluetooth-utils.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm-wwan.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES_DIST = \ - src/devices/bluetooth/nm-bluez-manager.c \ - src/devices/bluetooth/nm-bluez-manager.h \ - src/devices/bluetooth/nm-device-bt.c \ - src/devices/bluetooth/nm-device-bt.h -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS = src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS = $(am_src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) -src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LINK = \ +am__src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES_DIST = \ + src/core/devices/bluetooth/nm-bluez-manager.c \ + src/core/devices/bluetooth/nm-bluez-manager.h \ + src/core/devices/bluetooth/nm-device-bt.c \ + src/core/devices/bluetooth/nm-device-bt.h +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS = src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS = $(am_src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) +src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LINK = \ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS) \ + $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_bluetooth_libnm_device_plugin_bluetooth_la_rpath = \ +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_rpath = \ @WITH_MODEM_MANAGER_1_TRUE@ -rpath $(plugindir) -@WITH_OPENVSWITCH_TRUE@src_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES = \ +@WITH_OPENVSWITCH_TRUE@src_core_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES = \ @WITH_OPENVSWITCH_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_OPENVSWITCH_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_ovs_libnm_device_plugin_ovs_la_SOURCES_DIST = \ - src/devices/ovs/nm-ovsdb.c src/devices/ovs/nm-ovsdb.h \ - src/devices/ovs/nm-ovs-factory.c \ - src/devices/ovs/nm-device-ovs-interface.c \ - src/devices/ovs/nm-device-ovs-interface.h \ - src/devices/ovs/nm-device-ovs-port.c \ - src/devices/ovs/nm-device-ovs-port.h \ - src/devices/ovs/nm-device-ovs-bridge.c \ - src/devices/ovs/nm-device-ovs-bridge.h -@WITH_OPENVSWITCH_TRUE@am_src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS = src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo -src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS = \ - $(am_src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) -src_devices_ovs_libnm_device_plugin_ovs_la_LINK = $(LIBTOOL) \ +am__src_core_devices_ovs_libnm_device_plugin_ovs_la_SOURCES_DIST = \ + src/core/devices/ovs/nm-ovsdb.c \ + src/core/devices/ovs/nm-ovsdb.h \ + src/core/devices/ovs/nm-ovs-factory.c \ + src/core/devices/ovs/nm-device-ovs-interface.c \ + src/core/devices/ovs/nm-device-ovs-interface.h \ + src/core/devices/ovs/nm-device-ovs-port.c \ + src/core/devices/ovs/nm-device-ovs-port.h \ + src/core/devices/ovs/nm-device-ovs-bridge.c \ + src/core/devices/ovs/nm-device-ovs-bridge.h +@WITH_OPENVSWITCH_TRUE@am_src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS = src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo +src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS = \ + $(am_src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) +src_core_devices_ovs_libnm_device_plugin_ovs_la_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS) \ + $(src_core_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@WITH_OPENVSWITCH_TRUE@am_src_devices_ovs_libnm_device_plugin_ovs_la_rpath = \ +@WITH_OPENVSWITCH_TRUE@am_src_core_devices_ovs_libnm_device_plugin_ovs_la_rpath = \ @WITH_OPENVSWITCH_TRUE@ -rpath $(plugindir) -@WITH_TEAMDCTL_TRUE@src_devices_team_libnm_device_plugin_team_la_DEPENDENCIES = \ +@WITH_TEAMDCTL_TRUE@src_core_devices_team_libnm_device_plugin_team_la_DEPENDENCIES = \ @WITH_TEAMDCTL_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_TEAMDCTL_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_TEAMDCTL_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_team_libnm_device_plugin_team_la_SOURCES_DIST = \ - src/devices/team/nm-team-factory.c \ - src/devices/team/nm-device-team.c \ - src/devices/team/nm-device-team.h -@WITH_TEAMDCTL_TRUE@am_src_devices_team_libnm_device_plugin_team_la_OBJECTS = src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo \ -@WITH_TEAMDCTL_TRUE@ src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo -src_devices_team_libnm_device_plugin_team_la_OBJECTS = \ - $(am_src_devices_team_libnm_device_plugin_team_la_OBJECTS) -src_devices_team_libnm_device_plugin_team_la_LINK = $(LIBTOOL) \ +am__src_core_devices_team_libnm_device_plugin_team_la_SOURCES_DIST = \ + src/core/devices/team/nm-team-factory.c \ + src/core/devices/team/nm-device-team.c \ + src/core/devices/team/nm-device-team.h +@WITH_TEAMDCTL_TRUE@am_src_core_devices_team_libnm_device_plugin_team_la_OBJECTS = src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo \ +@WITH_TEAMDCTL_TRUE@ src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo +src_core_devices_team_libnm_device_plugin_team_la_OBJECTS = $(am_src_core_devices_team_libnm_device_plugin_team_la_OBJECTS) +src_core_devices_team_libnm_device_plugin_team_la_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_team_libnm_device_plugin_team_la_LDFLAGS) \ + $(src_core_devices_team_libnm_device_plugin_team_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@WITH_TEAMDCTL_TRUE@am_src_devices_team_libnm_device_plugin_team_la_rpath = \ +@WITH_TEAMDCTL_TRUE@am_src_core_devices_team_libnm_device_plugin_team_la_rpath = \ @WITH_TEAMDCTL_TRUE@ -rpath $(plugindir) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES = \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm-wifi-base.la \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm-wifi-base.la \ @WITH_WIFI_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES_DIST = \ - src/devices/wifi/nm-wifi-factory.c -@WITH_WIFI_TRUE@am_src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS = src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo -src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS = \ - $(am_src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) -src_devices_wifi_libnm_device_plugin_wifi_la_LINK = $(LIBTOOL) \ +am__src_core_devices_wifi_libnm_device_plugin_wifi_la_SOURCES_DIST = \ + src/core/devices/wifi/nm-wifi-factory.c +@WITH_WIFI_TRUE@am_src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS = src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo +src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS = $(am_src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) +src_core_devices_wifi_libnm_device_plugin_wifi_la_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS) \ + $(src_core_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@WITH_WIFI_TRUE@am_src_devices_wifi_libnm_device_plugin_wifi_la_rpath = \ +@WITH_WIFI_TRUE@am_src_core_devices_wifi_libnm_device_plugin_wifi_la_rpath = \ @WITH_WIFI_TRUE@ -rpath $(plugindir) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_wifi_base_la_DEPENDENCIES = \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_wifi_base_la_DEPENDENCIES = \ @WITH_WIFI_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_wifi_libnm_wifi_base_la_SOURCES_DIST = \ - src/devices/wifi/nm-device-olpc-mesh.c \ - src/devices/wifi/nm-device-olpc-mesh.h \ - src/devices/wifi/nm-device-wifi-p2p.c \ - src/devices/wifi/nm-device-wifi-p2p.h \ - src/devices/wifi/nm-device-wifi.c \ - src/devices/wifi/nm-device-wifi.h \ - src/devices/wifi/nm-wifi-ap.c src/devices/wifi/nm-wifi-ap.h \ - src/devices/wifi/nm-wifi-common.c \ - src/devices/wifi/nm-wifi-common.h \ - src/devices/wifi/nm-wifi-p2p-peer.c \ - src/devices/wifi/nm-wifi-p2p-peer.h \ - src/devices/wifi/nm-wifi-utils.c \ - src/devices/wifi/nm-wifi-utils.h \ - src/devices/wifi/nm-device-iwd.c \ - src/devices/wifi/nm-device-iwd.h \ - src/devices/wifi/nm-iwd-manager.c \ - src/devices/wifi/nm-iwd-manager.h -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@am__objects_11 = src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo \ -@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo -@WITH_WIFI_TRUE@am_src_devices_wifi_libnm_wifi_base_la_OBJECTS = src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo \ +am__src_core_devices_wifi_libnm_wifi_base_la_SOURCES_DIST = \ + src/core/devices/wifi/nm-device-olpc-mesh.c \ + src/core/devices/wifi/nm-device-olpc-mesh.h \ + src/core/devices/wifi/nm-device-wifi-p2p.c \ + src/core/devices/wifi/nm-device-wifi-p2p.h \ + src/core/devices/wifi/nm-device-wifi.c \ + src/core/devices/wifi/nm-device-wifi.h \ + src/core/devices/wifi/nm-wifi-ap.c \ + src/core/devices/wifi/nm-wifi-ap.h \ + src/core/devices/wifi/nm-wifi-common.c \ + src/core/devices/wifi/nm-wifi-common.h \ + src/core/devices/wifi/nm-wifi-p2p-peer.c \ + src/core/devices/wifi/nm-wifi-p2p-peer.h \ + src/core/devices/wifi/nm-wifi-utils.c \ + src/core/devices/wifi/nm-wifi-utils.h \ + src/core/devices/wifi/nm-device-iwd.c \ + src/core/devices/wifi/nm-device-iwd.h \ + src/core/devices/wifi/nm-iwd-manager.c \ + src/core/devices/wifi/nm-iwd-manager.h +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@am__objects_11 = src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo \ +@WITH_IWD_TRUE@@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo +@WITH_WIFI_TRUE@am_src_core_devices_wifi_libnm_wifi_base_la_OBJECTS = src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo \ @WITH_WIFI_TRUE@ $(am__objects_11) -src_devices_wifi_libnm_wifi_base_la_OBJECTS = \ - $(am_src_devices_wifi_libnm_wifi_base_la_OBJECTS) -@WITH_WIFI_TRUE@am_src_devices_wifi_libnm_wifi_base_la_rpath = -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.la \ +src_core_devices_wifi_libnm_wifi_base_la_OBJECTS = \ + $(am_src_core_devices_wifi_libnm_wifi_base_la_OBJECTS) +@WITH_WIFI_TRUE@am_src_core_devices_wifi_libnm_wifi_base_la_rpath = +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES = src/core/devices/wwan/libnm-wwan.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_wwan_libnm_device_plugin_wwan_la_SOURCES_DIST = \ - src/devices/wwan/nm-wwan-factory.c \ - src/devices/wwan/nm-device-modem.c \ - src/devices/wwan/nm-device-modem.h -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS = src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo -src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS = \ - $(am_src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) -src_devices_wwan_libnm_device_plugin_wwan_la_LINK = $(LIBTOOL) \ +am__src_core_devices_wwan_libnm_device_plugin_wwan_la_SOURCES_DIST = \ + src/core/devices/wwan/nm-wwan-factory.c \ + src/core/devices/wwan/nm-device-modem.c \ + src/core/devices/wwan/nm-device-modem.h +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS = src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo +src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS = $(am_src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) +src_core_devices_wwan_libnm_device_plugin_wwan_la_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS) \ + $(src_core_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_wwan_libnm_device_plugin_wwan_la_rpath = \ +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_wwan_libnm_device_plugin_wwan_la_rpath = \ @WITH_MODEM_MANAGER_1_TRUE@ -rpath $(plugindir) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) -am__src_devices_wwan_libnm_wwan_la_SOURCES_DIST = \ - src/devices/wwan/nm-modem-broadband.c \ - src/devices/wwan/nm-modem-broadband.h \ - src/devices/wwan/nm-modem-manager.c \ - src/devices/wwan/nm-modem-manager.h \ - src/devices/wwan/nm-modem.c src/devices/wwan/nm-modem.h \ - src/devices/wwan/nm-service-providers.c \ - src/devices/wwan/nm-service-providers.h \ - src/devices/wwan/nm-modem-ofono.c \ - src/devices/wwan/nm-modem-ofono.h -@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@am__objects_12 = src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_wwan_libnm_wwan_la_OBJECTS = src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm_wwan_la-nm-modem.lo \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm_wwan_la-nm-service-providers.lo \ +am__src_core_devices_wwan_libnm_wwan_la_SOURCES_DIST = \ + src/core/devices/wwan/nm-modem-broadband.c \ + src/core/devices/wwan/nm-modem-broadband.h \ + src/core/devices/wwan/nm-modem-manager.c \ + src/core/devices/wwan/nm-modem-manager.h \ + src/core/devices/wwan/nm-modem.c \ + src/core/devices/wwan/nm-modem.h \ + src/core/devices/wwan/nm-service-providers.c \ + src/core/devices/wwan/nm-service-providers.h \ + src/core/devices/wwan/nm-modem-ofono.c \ + src/core/devices/wwan/nm-modem-ofono.h +@WITH_MODEM_MANAGER_1_TRUE@@WITH_OFONO_TRUE@am__objects_12 = src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_wwan_libnm_wwan_la_OBJECTS = src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm_wwan_la-nm-modem.lo \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__objects_12) -src_devices_wwan_libnm_wwan_la_OBJECTS = \ - $(am_src_devices_wwan_libnm_wwan_la_OBJECTS) -src_devices_wwan_libnm_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_wwan_libnm_wwan_la_LDFLAGS) $(LDFLAGS) -o $@ -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_wwan_libnm_wwan_la_rpath = \ +src_core_devices_wwan_libnm_wwan_la_OBJECTS = \ + $(am_src_core_devices_wwan_libnm_wwan_la_OBJECTS) +src_core_devices_wwan_libnm_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_devices_wwan_libnm_wwan_la_LDFLAGS) $(LDFLAGS) -o \ + $@ +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_wwan_libnm_wwan_la_rpath = \ @WITH_MODEM_MANAGER_1_TRUE@ -rpath $(plugindir) -src_initrd_libnmi_core_la_LIBADD = -am_src_initrd_libnmi_core_la_OBJECTS = \ - src/initrd/libnmi_core_la-nmi-cmdline-reader.lo \ - src/initrd/libnmi_core_la-nmi-dt-reader.lo \ - src/initrd/libnmi_core_la-nmi-ibft-reader.lo -src_initrd_libnmi_core_la_OBJECTS = \ - $(am_src_initrd_libnmi_core_la_OBJECTS) -src_libNetworkManager_la_DEPENDENCIES = src/libNetworkManagerBase.la \ +src_core_initrd_libnmi_core_la_LIBADD = +am_src_core_initrd_libnmi_core_la_OBJECTS = \ + src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo \ + src/core/initrd/libnmi_core_la-nmi-dt-reader.lo \ + src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo +src_core_initrd_libnmi_core_la_OBJECTS = \ + $(am_src_core_initrd_libnmi_core_la_OBJECTS) +src_core_libNetworkManager_la_DEPENDENCIES = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la $(libnm_crypto_lib) \ @@ -1290,7 +1293,8 @@ src_libNetworkManager_la_DEPENDENCIES = src/libNetworkManagerBase.la \ shared/nm-log-core/libnm-log-core.la \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ - shared/nm-std-aux/libnm-std-aux.la src/libnm-systemd-core.la \ + shared/nm-std-aux/libnm-std-aux.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la shared/libnacd.la \ shared/libndhcp4.la shared/libcrbtree.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ @@ -1298,286 +1302,293 @@ src_libNetworkManager_la_DEPENDENCIES = src/libNetworkManagerBase.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -am_src_libNetworkManager_la_OBJECTS = \ - src/libNetworkManager_la-nm-checkpoint.lo \ - src/libNetworkManager_la-nm-checkpoint-manager.lo \ - src/devices/libNetworkManager_la-nm-acd-manager.lo \ - src/devices/libNetworkManager_la-nm-lldp-listener.lo \ - src/devices/libNetworkManager_la-nm-device.lo \ - src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo \ - src/devices/libNetworkManager_la-nm-device-factory.lo \ - src/devices/libNetworkManager_la-nm-device-generic.lo \ - src/devices/libNetworkManager_la-nm-device-6lowpan.lo \ - src/devices/libNetworkManager_la-nm-device-bond.lo \ - src/devices/libNetworkManager_la-nm-device-bridge.lo \ - src/devices/libNetworkManager_la-nm-device-dummy.lo \ - src/devices/libNetworkManager_la-nm-device-ethernet.lo \ - src/devices/libNetworkManager_la-nm-device-infiniband.lo \ - src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo \ - src/devices/libNetworkManager_la-nm-device-macsec.lo \ - src/devices/libNetworkManager_la-nm-device-macvlan.lo \ - src/devices/libNetworkManager_la-nm-device-ppp.lo \ - src/devices/libNetworkManager_la-nm-device-tun.lo \ - src/devices/libNetworkManager_la-nm-device-veth.lo \ - src/devices/libNetworkManager_la-nm-device-vlan.lo \ - src/devices/libNetworkManager_la-nm-device-vrf.lo \ - src/devices/libNetworkManager_la-nm-device-vxlan.lo \ - src/devices/libNetworkManager_la-nm-device-wireguard.lo \ - src/devices/libNetworkManager_la-nm-device-wpan.lo \ - src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo \ - src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo \ - src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo \ - src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo \ - src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo \ - src/dns/libNetworkManager_la-nm-dns-manager.lo \ - src/dns/libNetworkManager_la-nm-dns-plugin.lo \ - src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo \ - src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo \ - src/dns/libNetworkManager_la-nm-dns-unbound.lo \ - src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo \ - src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo \ - src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo \ - src/libNetworkManager_la-nm-hostname-manager.lo \ - src/settings/libNetworkManager_la-nm-agent-manager.lo \ - src/settings/libNetworkManager_la-nm-secret-agent.lo \ - src/settings/libNetworkManager_la-nm-settings-connection.lo \ - src/settings/libNetworkManager_la-nm-settings-storage.lo \ - src/settings/libNetworkManager_la-nm-settings-plugin.lo \ - src/settings/libNetworkManager_la-nm-settings.lo \ - src/settings/libNetworkManager_la-nm-settings-utils.lo \ - src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo \ - src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo \ - src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo \ - src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo \ - src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo \ - src/supplicant/libNetworkManager_la-nm-supplicant-config.lo \ - src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo \ - src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo \ - src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo \ - src/vpn/libNetworkManager_la-nm-vpn-connection.lo \ - src/vpn/libNetworkManager_la-nm-vpn-manager.lo \ - src/libNetworkManager_la-nm-act-request.lo \ - src/libNetworkManager_la-nm-active-connection.lo \ - src/libNetworkManager_la-nm-audit-manager.lo \ - src/libNetworkManager_la-nm-dbus-manager.lo \ - src/libNetworkManager_la-nm-config.lo \ - src/libNetworkManager_la-nm-config-data.lo \ - src/libNetworkManager_la-nm-connectivity.lo \ - src/libNetworkManager_la-nm-dcb.lo \ - src/libNetworkManager_la-nm-dhcp-config.lo \ - src/libNetworkManager_la-nm-dispatcher.lo \ - src/libNetworkManager_la-nm-firewall-manager.lo \ - src/libNetworkManager_la-nm-proxy-config.lo \ - src/libNetworkManager_la-nm-auth-manager.lo \ - src/libNetworkManager_la-nm-auth-utils.lo \ - src/libNetworkManager_la-nm-manager.lo \ - src/libNetworkManager_la-nm-pacrunner-manager.lo \ - src/libNetworkManager_la-nm-policy.lo \ - src/libNetworkManager_la-nm-rfkill-manager.lo \ - src/libNetworkManager_la-nm-session-monitor.lo \ - src/libNetworkManager_la-nm-keep-alive.lo \ - src/libNetworkManager_la-nm-sleep-monitor.lo -src_libNetworkManager_la_OBJECTS = \ - $(am_src_libNetworkManager_la_OBJECTS) -src_libNetworkManagerBase_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) -am__src_libNetworkManagerBase_la_SOURCES_DIST = src/nm-core-utils.c \ - src/nm-core-utils.h src/NetworkManagerUtils.c \ - src/NetworkManagerUtils.h src/platform/nmp-object.c \ - src/platform/nmp-object.h src/platform/nm-platform.c \ - src/platform/nm-platform.h src/platform/nm-platform-private.h \ - src/platform/nm-linux-platform.c \ - src/platform/nm-linux-platform.h \ - src/platform/nmp-rules-manager.c \ - src/platform/nmp-rules-manager.h \ - src/platform/wifi/nm-wifi-utils-nl80211.c \ - src/platform/wifi/nm-wifi-utils-nl80211.h \ - src/platform/wifi/nm-wifi-utils-private.h \ - src/platform/wifi/nm-wifi-utils.c \ - src/platform/wifi/nm-wifi-utils.h \ - src/platform/wpan/nm-wpan-utils.c \ - src/platform/wpan/nm-wpan-utils.h src/ndisc/nm-lndp-ndisc.c \ - src/ndisc/nm-lndp-ndisc.h src/ndisc/nm-ndisc.c \ - src/ndisc/nm-ndisc.h src/ndisc/nm-ndisc-private.h \ - src/nm-dbus-utils.c src/nm-dbus-utils.h src/nm-dbus-object.c \ - src/nm-dbus-object.h src/nm-netns.c src/nm-netns.h \ - src/nm-l3-config-data.c src/nm-l3-config-data.h \ - src/nm-l3-ipv4ll.c src/nm-l3-ipv4ll.h src/nm-l3cfg.c \ - src/nm-l3cfg.h src/nm-ip-config.c src/nm-ip-config.h \ - src/nm-ip4-config.c src/nm-ip4-config.h src/nm-ip6-config.c \ - src/nm-ip6-config.h src/dhcp/nm-dhcp-client.c \ - src/dhcp/nm-dhcp-client.h src/dhcp/nm-dhcp-client-logging.h \ - src/dhcp/nm-dhcp-nettools.c src/dhcp/nm-dhcp-utils.c \ - src/dhcp/nm-dhcp-utils.h src/dhcp/nm-dhcp-options.c \ - src/dhcp/nm-dhcp-options.h src/dhcp/nm-dhcp-systemd.c \ - src/dhcp/nm-dhcp-manager.c src/dhcp/nm-dhcp-manager.h \ - src/main-utils.c src/main-utils.h \ - src/platform/wifi/nm-wifi-utils-wext.c \ - src/platform/wifi/nm-wifi-utils-wext.h -@WITH_WEXT_TRUE@am__objects_13 = src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo -am_src_libNetworkManagerBase_la_OBJECTS = \ - src/libNetworkManagerBase_la-nm-core-utils.lo \ - src/libNetworkManagerBase_la-NetworkManagerUtils.lo \ - src/platform/libNetworkManagerBase_la-nmp-object.lo \ - src/platform/libNetworkManagerBase_la-nm-platform.lo \ - src/platform/libNetworkManagerBase_la-nm-linux-platform.lo \ - src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo \ - src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo \ - src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo \ - src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo \ - src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo \ - src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo \ - src/libNetworkManagerBase_la-nm-dbus-utils.lo \ - src/libNetworkManagerBase_la-nm-dbus-object.lo \ - src/libNetworkManagerBase_la-nm-netns.lo \ - src/libNetworkManagerBase_la-nm-l3-config-data.lo \ - src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo \ - src/libNetworkManagerBase_la-nm-l3cfg.lo \ - src/libNetworkManagerBase_la-nm-ip-config.lo \ - src/libNetworkManagerBase_la-nm-ip4-config.lo \ - src/libNetworkManagerBase_la-nm-ip6-config.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo \ - src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo \ - src/libNetworkManagerBase_la-main-utils.lo $(am__objects_13) -src_libNetworkManagerBase_la_OBJECTS = \ - $(am_src_libNetworkManagerBase_la_OBJECTS) -src_libNetworkManagerTest_la_DEPENDENCIES = src/libNetworkManager.la \ - $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) -am_src_libNetworkManagerTest_la_OBJECTS = \ - src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo \ - src/platform/libNetworkManagerTest_la-nm-fake-platform.lo \ - src/platform/tests/libNetworkManagerTest_la-test-common.lo -src_libNetworkManagerTest_la_OBJECTS = \ - $(am_src_libNetworkManagerTest_la_OBJECTS) -@ENABLE_TESTS_FALSE@am_src_libNetworkManagerTest_la_rpath = -@ENABLE_TESTS_TRUE@am_src_libNetworkManagerTest_la_rpath = -src_libnm_systemd_core_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ +am_src_core_libNetworkManager_la_OBJECTS = \ + src/core/libNetworkManager_la-nm-checkpoint.lo \ + src/core/libNetworkManager_la-nm-checkpoint-manager.lo \ + src/core/devices/libNetworkManager_la-nm-acd-manager.lo \ + src/core/devices/libNetworkManager_la-nm-lldp-listener.lo \ + src/core/devices/libNetworkManager_la-nm-device.lo \ + src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo \ + src/core/devices/libNetworkManager_la-nm-device-factory.lo \ + src/core/devices/libNetworkManager_la-nm-device-generic.lo \ + src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo \ + src/core/devices/libNetworkManager_la-nm-device-bond.lo \ + src/core/devices/libNetworkManager_la-nm-device-bridge.lo \ + src/core/devices/libNetworkManager_la-nm-device-dummy.lo \ + src/core/devices/libNetworkManager_la-nm-device-ethernet.lo \ + src/core/devices/libNetworkManager_la-nm-device-infiniband.lo \ + src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo \ + src/core/devices/libNetworkManager_la-nm-device-macsec.lo \ + src/core/devices/libNetworkManager_la-nm-device-macvlan.lo \ + src/core/devices/libNetworkManager_la-nm-device-ppp.lo \ + src/core/devices/libNetworkManager_la-nm-device-tun.lo \ + src/core/devices/libNetworkManager_la-nm-device-veth.lo \ + src/core/devices/libNetworkManager_la-nm-device-vlan.lo \ + src/core/devices/libNetworkManager_la-nm-device-vrf.lo \ + src/core/devices/libNetworkManager_la-nm-device-vxlan.lo \ + src/core/devices/libNetworkManager_la-nm-device-wireguard.lo \ + src/core/devices/libNetworkManager_la-nm-device-wpan.lo \ + src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo \ + src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo \ + src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo \ + src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo \ + src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo \ + src/core/dns/libNetworkManager_la-nm-dns-manager.lo \ + src/core/dns/libNetworkManager_la-nm-dns-plugin.lo \ + src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo \ + src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo \ + src/core/dns/libNetworkManager_la-nm-dns-unbound.lo \ + src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo \ + src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo \ + src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo \ + src/core/libNetworkManager_la-nm-hostname-manager.lo \ + src/core/settings/libNetworkManager_la-nm-agent-manager.lo \ + src/core/settings/libNetworkManager_la-nm-secret-agent.lo \ + src/core/settings/libNetworkManager_la-nm-settings-connection.lo \ + src/core/settings/libNetworkManager_la-nm-settings-storage.lo \ + src/core/settings/libNetworkManager_la-nm-settings-plugin.lo \ + src/core/settings/libNetworkManager_la-nm-settings.lo \ + src/core/settings/libNetworkManager_la-nm-settings-utils.lo \ + src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo \ + src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo \ + src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo \ + src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo \ + src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo \ + src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo \ + src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo \ + src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo \ + src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo \ + src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo \ + src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo \ + src/core/libNetworkManager_la-nm-act-request.lo \ + src/core/libNetworkManager_la-nm-active-connection.lo \ + src/core/libNetworkManager_la-nm-audit-manager.lo \ + src/core/libNetworkManager_la-nm-dbus-manager.lo \ + src/core/libNetworkManager_la-nm-config.lo \ + src/core/libNetworkManager_la-nm-config-data.lo \ + src/core/libNetworkManager_la-nm-connectivity.lo \ + src/core/libNetworkManager_la-nm-dcb.lo \ + src/core/libNetworkManager_la-nm-dhcp-config.lo \ + src/core/libNetworkManager_la-nm-dispatcher.lo \ + src/core/libNetworkManager_la-nm-firewall-manager.lo \ + src/core/libNetworkManager_la-nm-proxy-config.lo \ + src/core/libNetworkManager_la-nm-auth-manager.lo \ + src/core/libNetworkManager_la-nm-auth-utils.lo \ + src/core/libNetworkManager_la-nm-manager.lo \ + src/core/libNetworkManager_la-nm-pacrunner-manager.lo \ + src/core/libNetworkManager_la-nm-policy.lo \ + src/core/libNetworkManager_la-nm-rfkill-manager.lo \ + src/core/libNetworkManager_la-nm-session-monitor.lo \ + src/core/libNetworkManager_la-nm-keep-alive.lo \ + src/core/libNetworkManager_la-nm-sleep-monitor.lo +src_core_libNetworkManager_la_OBJECTS = \ + $(am_src_core_libNetworkManager_la_OBJECTS) +src_core_libNetworkManagerBase_la_DEPENDENCIES = \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am__src_core_libNetworkManagerBase_la_SOURCES_DIST = \ + src/core/nm-core-utils.c src/core/nm-core-utils.h \ + src/core/NetworkManagerUtils.c src/core/NetworkManagerUtils.h \ + src/core/platform/nmp-object.c src/core/platform/nmp-object.h \ + src/core/platform/nm-platform.c \ + src/core/platform/nm-platform.h \ + src/core/platform/nm-platform-private.h \ + src/core/platform/nm-linux-platform.c \ + src/core/platform/nm-linux-platform.h \ + src/core/platform/nmp-rules-manager.c \ + src/core/platform/nmp-rules-manager.h \ + src/core/platform/wifi/nm-wifi-utils-nl80211.c \ + src/core/platform/wifi/nm-wifi-utils-nl80211.h \ + src/core/platform/wifi/nm-wifi-utils-private.h \ + src/core/platform/wifi/nm-wifi-utils.c \ + src/core/platform/wifi/nm-wifi-utils.h \ + src/core/platform/wpan/nm-wpan-utils.c \ + src/core/platform/wpan/nm-wpan-utils.h \ + src/core/ndisc/nm-lndp-ndisc.c src/core/ndisc/nm-lndp-ndisc.h \ + src/core/ndisc/nm-ndisc.c src/core/ndisc/nm-ndisc.h \ + src/core/ndisc/nm-ndisc-private.h src/core/nm-dbus-utils.c \ + src/core/nm-dbus-utils.h src/core/nm-dbus-object.c \ + src/core/nm-dbus-object.h src/core/nm-netns.c \ + src/core/nm-netns.h src/core/nm-l3-config-data.c \ + src/core/nm-l3-config-data.h src/core/nm-l3-ipv4ll.c \ + src/core/nm-l3-ipv4ll.h src/core/nm-l3cfg.c \ + src/core/nm-l3cfg.h src/core/nm-ip-config.c \ + src/core/nm-ip-config.h src/core/nm-ip4-config.c \ + src/core/nm-ip4-config.h src/core/nm-ip6-config.c \ + src/core/nm-ip6-config.h src/core/dhcp/nm-dhcp-client.c \ + src/core/dhcp/nm-dhcp-client.h \ + src/core/dhcp/nm-dhcp-client-logging.h \ + src/core/dhcp/nm-dhcp-nettools.c src/core/dhcp/nm-dhcp-utils.c \ + src/core/dhcp/nm-dhcp-utils.h src/core/dhcp/nm-dhcp-options.c \ + src/core/dhcp/nm-dhcp-options.h \ + src/core/dhcp/nm-dhcp-systemd.c \ + src/core/dhcp/nm-dhcp-manager.c \ + src/core/dhcp/nm-dhcp-manager.h src/core/main-utils.c \ + src/core/main-utils.h \ + src/core/platform/wifi/nm-wifi-utils-wext.c \ + src/core/platform/wifi/nm-wifi-utils-wext.h +@WITH_WEXT_TRUE@am__objects_13 = src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo +am_src_core_libNetworkManagerBase_la_OBJECTS = \ + src/core/libNetworkManagerBase_la-nm-core-utils.lo \ + src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo \ + src/core/platform/libNetworkManagerBase_la-nmp-object.lo \ + src/core/platform/libNetworkManagerBase_la-nm-platform.lo \ + src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo \ + src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo \ + src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo \ + src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo \ + src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo \ + src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo \ + src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo \ + src/core/libNetworkManagerBase_la-nm-dbus-utils.lo \ + src/core/libNetworkManagerBase_la-nm-dbus-object.lo \ + src/core/libNetworkManagerBase_la-nm-netns.lo \ + src/core/libNetworkManagerBase_la-nm-l3-config-data.lo \ + src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo \ + src/core/libNetworkManagerBase_la-nm-l3cfg.lo \ + src/core/libNetworkManagerBase_la-nm-ip-config.lo \ + src/core/libNetworkManagerBase_la-nm-ip4-config.lo \ + src/core/libNetworkManagerBase_la-nm-ip6-config.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo \ + src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo \ + src/core/libNetworkManagerBase_la-main-utils.lo \ + $(am__objects_13) +src_core_libNetworkManagerBase_la_OBJECTS = \ + $(am_src_core_libNetworkManagerBase_la_OBJECTS) +src_core_libNetworkManagerTest_la_DEPENDENCIES = \ + src/core/libNetworkManager.la $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_src_core_libNetworkManagerTest_la_OBJECTS = \ + src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo \ + src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo \ + src/core/platform/tests/libNetworkManagerTest_la-test-common.lo +src_core_libNetworkManagerTest_la_OBJECTS = \ + $(am_src_core_libNetworkManagerTest_la_OBJECTS) +@ENABLE_TESTS_FALSE@am_src_core_libNetworkManagerTest_la_rpath = +@ENABLE_TESTS_TRUE@am_src_core_libNetworkManagerTest_la_rpath = +src_core_libnm_systemd_core_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) -am_src_libnm_systemd_core_la_OBJECTS = \ - src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo \ - src/systemd/libnm_systemd_core_la-nm-sd.lo \ - src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo \ - src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo \ - src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo \ - src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo \ - src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo \ - src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo \ - src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo -src_libnm_systemd_core_la_OBJECTS = \ - $(am_src_libnm_systemd_core_la_OBJECTS) -@WITH_PPP_TRUE@src_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ +am_src_core_libnm_systemd_core_la_OBJECTS = \ + src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo \ + src/core/systemd/libnm_systemd_core_la-nm-sd.lo \ + src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo \ + src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo \ + src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo \ + src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo \ + src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo \ + src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo \ + src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo +src_core_libnm_systemd_core_la_OBJECTS = \ + $(am_src_core_libnm_systemd_core_la_OBJECTS) +@WITH_PPP_TRUE@src_core_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ @WITH_PPP_TRUE@ $(am__DEPENDENCIES_1) -am__src_ppp_libnm_ppp_plugin_la_SOURCES_DIST = \ - src/ppp/nm-pppd-plugin.h src/ppp/nm-ppp-manager.c \ - src/ppp/nm-ppp-manager.h src/ppp/nm-ppp-plugin-api.h \ - src/ppp/nm-ppp-status.h -@WITH_PPP_TRUE@am_src_ppp_libnm_ppp_plugin_la_OBJECTS = \ -@WITH_PPP_TRUE@ src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo -src_ppp_libnm_ppp_plugin_la_OBJECTS = \ - $(am_src_ppp_libnm_ppp_plugin_la_OBJECTS) -src_ppp_libnm_ppp_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +am__src_core_ppp_libnm_ppp_plugin_la_SOURCES_DIST = \ + src/core/ppp/nm-pppd-plugin.h src/core/ppp/nm-ppp-manager.c \ + src/core/ppp/nm-ppp-manager.h src/core/ppp/nm-ppp-plugin-api.h \ + src/core/ppp/nm-ppp-status.h +@WITH_PPP_TRUE@am_src_core_ppp_libnm_ppp_plugin_la_OBJECTS = src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo +src_core_ppp_libnm_ppp_plugin_la_OBJECTS = \ + $(am_src_core_ppp_libnm_ppp_plugin_la_OBJECTS) +src_core_ppp_libnm_ppp_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_ppp_libnm_ppp_plugin_la_LDFLAGS) \ - $(LDFLAGS) -o $@ -@WITH_PPP_TRUE@am_src_ppp_libnm_ppp_plugin_la_rpath = -rpath \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_ppp_libnm_ppp_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +@WITH_PPP_TRUE@am_src_core_ppp_libnm_ppp_plugin_la_rpath = -rpath \ @WITH_PPP_TRUE@ $(plugindir) -@WITH_PPP_TRUE@src_ppp_nm_pppd_plugin_la_DEPENDENCIES = \ +@WITH_PPP_TRUE@src_core_ppp_nm_pppd_plugin_la_DEPENDENCIES = \ @WITH_PPP_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -am__src_ppp_nm_pppd_plugin_la_SOURCES_DIST = src/ppp/nm-pppd-plugin.c \ - src/ppp/nm-pppd-plugin.h src/ppp/nm-ppp-status.h -@WITH_PPP_TRUE@am_src_ppp_nm_pppd_plugin_la_OBJECTS = \ -@WITH_PPP_TRUE@ src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo -src_ppp_nm_pppd_plugin_la_OBJECTS = \ - $(am_src_ppp_nm_pppd_plugin_la_OBJECTS) -src_ppp_nm_pppd_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +am__src_core_ppp_nm_pppd_plugin_la_SOURCES_DIST = \ + src/core/ppp/nm-pppd-plugin.c src/core/ppp/nm-pppd-plugin.h \ + src/core/ppp/nm-ppp-status.h +@WITH_PPP_TRUE@am_src_core_ppp_nm_pppd_plugin_la_OBJECTS = src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo +src_core_ppp_nm_pppd_plugin_la_OBJECTS = \ + $(am_src_core_ppp_nm_pppd_plugin_la_OBJECTS) +src_core_ppp_nm_pppd_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_ppp_nm_pppd_plugin_la_LDFLAGS) \ - $(LDFLAGS) -o $@ -@WITH_PPP_TRUE@am_src_ppp_nm_pppd_plugin_la_rpath = -rpath \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_ppp_nm_pppd_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +@WITH_PPP_TRUE@am_src_core_ppp_nm_pppd_plugin_la_rpath = -rpath \ @WITH_PPP_TRUE@ $(pppd_plugindir) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES = src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la -am__src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES_DIST = \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS = src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS = $(am_src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) -src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LINK = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES = src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la +am__src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES_DIST = \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS = src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS = $(am_src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) +src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LINK = \ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS) \ + $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_rpath = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_rpath = \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ -rpath $(plugindir) -src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_LIBADD = -@CONFIG_PLUGIN_IFCFG_RH_TRUE@nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS = src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo -src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS = $(nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_rpath = -src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_LIBADD = -am__src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES_DIST = \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ - src/settings/plugins/ifcfg-rh/shvar.c \ - src/settings/plugins/ifcfg-rh/shvar.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ - src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS = src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo -src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS = $(am_src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_rpath = -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES = src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ +src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_LIBADD = +@CONFIG_PLUGIN_IFCFG_RH_TRUE@nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS = src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo +src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS = $(nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_rpath = +src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_LIBADD = +am__src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES_DIST = \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ + src/core/settings/plugins/ifcfg-rh/shvar.c \ + src/core/settings/plugins/ifcfg-rh/shvar.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ + src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS = src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo +src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS = $(am_src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_rpath = +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES = src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(am__DEPENDENCIES_1) -am__src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES_DIST = \ - src/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ - src/settings/plugins/ifupdown/nms-ifupdown-plugin.h -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS = src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS = $(am_src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) -src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LINK = \ +am__src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES_DIST = \ + src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS = src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS = $(am_src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) +src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LINK = \ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS) \ + $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS) \ $(LDFLAGS) -o $@ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_rpath = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_rpath = \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ -rpath $(plugindir) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES = \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(am__DEPENDENCIES_1) -am__src_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES_DIST = \ - src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ - src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ - src/settings/plugins/ifupdown/nms-ifupdown-parser.c \ - src/settings/plugins/ifupdown/nms-ifupdown-parser.h -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS = src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo -src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS = $(am_src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_settings_plugins_ifupdown_libnms_ifupdown_core_la_rpath = +am__src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES_DIST = src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ + src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c \ + src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS = src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo +src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS = $(am_src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@am_src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_rpath = am_clients_cli_generate_docs_nm_settings_nmcli_OBJECTS = clients/cli/generate_docs_nm_settings_nmcli-generate-docs-nm-settings-nmcli.$(OBJEXT) clients_cli_generate_docs_nm_settings_nmcli_OBJECTS = \ $(am_clients_cli_generate_docs_nm_settings_nmcli_OBJECTS) @@ -1993,7 +2004,7 @@ am_libnm_tests_test_libnm_OBJECTS = \ shared/nm-utils/libnm_tests_test_libnm-nm-compat.$(OBJEXT) \ libnm/tests/test_libnm-test-libnm.$(OBJEXT) libnm_tests_test_libnm_OBJECTS = $(am_libnm_tests_test_libnm_OBJECTS) -am__DEPENDENCIES_4 = libnm/liblibnm.la $(am__DEPENDENCIES_1) +am__DEPENDENCIES_4 = libnm/libnm_static.la $(am__DEPENDENCIES_1) libnm_tests_test_libnm_DEPENDENCIES = $(am__DEPENDENCIES_4) libnm_tests_test_libnm_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -2069,127 +2080,132 @@ shared_nm_platform_tests_test_nm_platform_LINK = $(LIBTOOL) $(AM_V_lt) \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(shared_nm_platform_tests_test_nm_platform_LDFLAGS) \ $(LDFLAGS) -o $@ -am_src_NetworkManager_OBJECTS = src/NetworkManager-main.$(OBJEXT) -src_NetworkManager_OBJECTS = $(am_src_NetworkManager_OBJECTS) -src_NetworkManager_DEPENDENCIES = src/libNetworkManager.la \ +am_src_core_NetworkManager_OBJECTS = \ + src/core/NetworkManager-main.$(OBJEXT) +src_core_NetworkManager_OBJECTS = \ + $(am_src_core_NetworkManager_OBJECTS) +src_core_NetworkManager_DEPENDENCIES = src/core/libNetworkManager.la \ $(am__DEPENDENCIES_1) -src_NetworkManager_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_NetworkManager_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_NetworkManager_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_NetworkManager_LDFLAGS) \ $(LDFLAGS) -o $@ -am_src_NetworkManager_all_sym_OBJECTS = \ - src/NetworkManager_all_sym-main.$(OBJEXT) -src_NetworkManager_all_sym_OBJECTS = \ - $(am_src_NetworkManager_all_sym_OBJECTS) -src_NetworkManager_all_sym_DEPENDENCIES = src/libNetworkManager.la \ - $(am__DEPENDENCIES_1) -src_NetworkManager_all_sym_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +am_src_core_NetworkManager_all_sym_OBJECTS = \ + src/core/NetworkManager_all_sym-main.$(OBJEXT) +src_core_NetworkManager_all_sym_OBJECTS = \ + $(am_src_core_NetworkManager_all_sym_OBJECTS) +src_core_NetworkManager_all_sym_DEPENDENCIES = \ + src/core/libNetworkManager.la $(am__DEPENDENCIES_1) +src_core_NetworkManager_all_sym_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_NetworkManager_all_sym_LDFLAGS) \ - $(LDFLAGS) -o $@ -src_devices_bluetooth_tests_nm_bt_test_SOURCES = \ - src/devices/bluetooth/tests/nm-bt-test.c -src_devices_bluetooth_tests_nm_bt_test_OBJECTS = \ - src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.$(OBJEXT) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES = src/devices/bluetooth/libnm-bluetooth-utils.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/libNetworkManager.la \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_NetworkManager_all_sym_LDFLAGS) $(LDFLAGS) -o $@ +src_core_devices_bluetooth_tests_nm_bt_test_SOURCES = \ + src/core/devices/bluetooth/tests/nm-bt-test.c +src_core_devices_bluetooth_tests_nm_bt_test_OBJECTS = src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.$(OBJEXT) +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES = src/core/devices/bluetooth/libnm-bluetooth-utils.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/libNetworkManager.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) -src_devices_bluetooth_tests_nm_bt_test_LINK = $(LIBTOOL) $(AM_V_lt) \ - --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ - $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_bluetooth_tests_nm_bt_test_LDFLAGS) $(LDFLAGS) \ - -o $@ -src_devices_tests_test_acd_SOURCES = src/devices/tests/test-acd.c -src_devices_tests_test_acd_OBJECTS = \ - src/devices/tests/test_acd-test-acd.$(OBJEXT) -src_devices_tests_test_acd_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_devices_tests_test_acd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_devices_tests_test_acd_LDFLAGS) \ +src_core_devices_bluetooth_tests_nm_bt_test_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_devices_bluetooth_tests_nm_bt_test_LDFLAGS) \ $(LDFLAGS) -o $@ -src_devices_tests_test_lldp_SOURCES = src/devices/tests/test-lldp.c -src_devices_tests_test_lldp_OBJECTS = \ - src/devices/tests/test_lldp-test-lldp.$(OBJEXT) -src_devices_tests_test_lldp_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_devices_tests_test_lldp_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_devices_tests_test_acd_SOURCES = \ + src/core/devices/tests/test-acd.c +src_core_devices_tests_test_acd_OBJECTS = \ + src/core/devices/tests/test_acd-test-acd.$(OBJEXT) +src_core_devices_tests_test_acd_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_devices_tests_test_acd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_devices_tests_test_lldp_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_devices_tests_test_acd_LDFLAGS) $(LDFLAGS) -o $@ +src_core_devices_tests_test_lldp_SOURCES = \ + src/core/devices/tests/test-lldp.c +src_core_devices_tests_test_lldp_OBJECTS = \ + src/core/devices/tests/test_lldp-test-lldp.$(OBJEXT) +src_core_devices_tests_test_lldp_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_devices_tests_test_lldp_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_devices_tests_test_lldp_LDFLAGS) $(LDFLAGS) -o $@ +am__src_core_devices_wifi_tests_test_devices_wifi_SOURCES_DIST = \ + src/core/devices/wifi/tests/test-devices-wifi.c +@WITH_WIFI_TRUE@am_src_core_devices_wifi_tests_test_devices_wifi_OBJECTS = src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.$(OBJEXT) +src_core_devices_wifi_tests_test_devices_wifi_OBJECTS = \ + $(am_src_core_devices_wifi_tests_test_devices_wifi_OBJECTS) +@WITH_WIFI_TRUE@src_core_devices_wifi_tests_test_devices_wifi_DEPENDENCIES = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm-wifi-base.la \ +@WITH_WIFI_TRUE@ src/core/libNetworkManagerTest.la \ +@WITH_WIFI_TRUE@ src/core/libNetworkManagerBase.la +src_core_devices_wifi_tests_test_devices_wifi_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_devices_wifi_tests_test_devices_wifi_LDFLAGS) \ $(LDFLAGS) -o $@ -am__src_devices_wifi_tests_test_devices_wifi_SOURCES_DIST = \ - src/devices/wifi/tests/test-devices-wifi.c -@WITH_WIFI_TRUE@am_src_devices_wifi_tests_test_devices_wifi_OBJECTS = src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.$(OBJEXT) -src_devices_wifi_tests_test_devices_wifi_OBJECTS = \ - $(am_src_devices_wifi_tests_test_devices_wifi_OBJECTS) -@WITH_WIFI_TRUE@src_devices_wifi_tests_test_devices_wifi_DEPENDENCIES = \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm-wifi-base.la \ -@WITH_WIFI_TRUE@ src/libNetworkManagerTest.la \ -@WITH_WIFI_TRUE@ src/libNetworkManagerBase.la -src_devices_wifi_tests_test_devices_wifi_LINK = $(LIBTOOL) $(AM_V_lt) \ - --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ - $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_wifi_tests_test_devices_wifi_LDFLAGS) $(LDFLAGS) \ - -o $@ -am__src_devices_wwan_tests_test_service_providers_SOURCES_DIST = \ - src/devices/wwan/tests/test-service-providers.c \ - src/devices/wwan/nm-service-providers.c \ - src/devices/wwan/nm-service-providers.h -@WITH_MODEM_MANAGER_1_TRUE@am_src_devices_wwan_tests_test_service_providers_OBJECTS = src/devices/wwan/tests/test_service_providers-test-service-providers.$(OBJEXT) \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/tests_test_service_providers-nm-service-providers.$(OBJEXT) -src_devices_wwan_tests_test_service_providers_OBJECTS = \ - $(am_src_devices_wwan_tests_test_service_providers_OBJECTS) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_tests_test_service_providers_DEPENDENCIES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/libNetworkManagerTest.la \ +am__src_core_devices_wwan_tests_test_service_providers_SOURCES_DIST = \ + src/core/devices/wwan/tests/test-service-providers.c \ + src/core/devices/wwan/nm-service-providers.c \ + src/core/devices/wwan/nm-service-providers.h +@WITH_MODEM_MANAGER_1_TRUE@am_src_core_devices_wwan_tests_test_service_providers_OBJECTS = src/core/devices/wwan/tests/test_service_providers-test-service-providers.$(OBJEXT) \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/tests_test_service_providers-nm-service-providers.$(OBJEXT) +src_core_devices_wwan_tests_test_service_providers_OBJECTS = $(am_src_core_devices_wwan_tests_test_service_providers_OBJECTS) +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_tests_test_service_providers_DEPENDENCIES = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/libNetworkManagerTest.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__DEPENDENCIES_1) -src_devices_wwan_tests_test_service_providers_LINK = $(LIBTOOL) \ +src_core_devices_wwan_tests_test_service_providers_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_devices_wwan_tests_test_service_providers_LDFLAGS) \ + $(src_core_devices_wwan_tests_test_service_providers_LDFLAGS) \ $(LDFLAGS) -o $@ -am_src_dhcp_nm_dhcp_helper_OBJECTS = \ - src/dhcp/nm_dhcp_helper-nm-dhcp-helper.$(OBJEXT) -src_dhcp_nm_dhcp_helper_OBJECTS = \ - $(am_src_dhcp_nm_dhcp_helper_OBJECTS) -src_dhcp_nm_dhcp_helper_DEPENDENCIES = $(am__DEPENDENCIES_1) -src_dhcp_nm_dhcp_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +am_src_core_dhcp_nm_dhcp_helper_OBJECTS = \ + src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.$(OBJEXT) +src_core_dhcp_nm_dhcp_helper_OBJECTS = \ + $(am_src_core_dhcp_nm_dhcp_helper_OBJECTS) +src_core_dhcp_nm_dhcp_helper_DEPENDENCIES = $(am__DEPENDENCIES_1) +src_core_dhcp_nm_dhcp_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_dhcp_nm_dhcp_helper_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_dhcp_nm_dhcp_helper_LDFLAGS) \ $(LDFLAGS) -o $@ -src_dhcp_tests_test_dhcp_dhclient_SOURCES = \ - src/dhcp/tests/test-dhcp-dhclient.c -src_dhcp_tests_test_dhcp_dhclient_OBJECTS = src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.$(OBJEXT) -src_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES = \ - $(src_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_dhclient_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_dhcp_tests_test_dhcp_dhclient_SOURCES = \ + src/core/dhcp/tests/test-dhcp-dhclient.c +src_core_dhcp_tests_test_dhcp_dhclient_OBJECTS = src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.$(OBJEXT) +src_core_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES = \ + $(src_core_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_dhclient_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_dhcp_tests_test_dhcp_dhclient_LDFLAGS) $(LDFLAGS) -o $@ -src_dhcp_tests_test_dhcp_utils_SOURCES = \ - src/dhcp/tests/test-dhcp-utils.c -src_dhcp_tests_test_dhcp_utils_OBJECTS = \ - src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.$(OBJEXT) -src_dhcp_tests_test_dhcp_utils_DEPENDENCIES = $(src_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_dhcp_tests_test_dhcp_utils_LDFLAGS) $(LDFLAGS) -o $@ -src_dnsmasq_tests_test_dnsmasq_utils_SOURCES = \ - src/dnsmasq/tests/test-dnsmasq-utils.c -src_dnsmasq_tests_test_dnsmasq_utils_OBJECTS = src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.$(OBJEXT) -src_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_dnsmasq_tests_test_dnsmasq_utils_LINK = $(LIBTOOL) $(AM_V_lt) \ + $(src_core_dhcp_tests_test_dhcp_dhclient_LDFLAGS) $(LDFLAGS) \ + -o $@ +src_core_dhcp_tests_test_dhcp_utils_SOURCES = \ + src/core/dhcp/tests/test-dhcp-utils.c +src_core_dhcp_tests_test_dhcp_utils_OBJECTS = \ + src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.$(OBJEXT) +src_core_dhcp_tests_test_dhcp_utils_DEPENDENCIES = \ + $(src_core_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_utils_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS) $(LDFLAGS) -o \ + $(src_core_dhcp_tests_test_dhcp_utils_LDFLAGS) $(LDFLAGS) -o \ $@ -am_src_initrd_nm_initrd_generator_OBJECTS = \ - src/initrd/nm_initrd_generator-nm-initrd-generator.$(OBJEXT) -src_initrd_nm_initrd_generator_OBJECTS = \ - $(am_src_initrd_nm_initrd_generator_OBJECTS) -src_initrd_nm_initrd_generator_DEPENDENCIES = \ - src/initrd/libnmi-core.la src/libNetworkManagerBase.la \ +src_core_dnsmasq_tests_test_dnsmasq_utils_SOURCES = \ + src/core/dnsmasq/tests/test-dnsmasq-utils.c +src_core_dnsmasq_tests_test_dnsmasq_utils_OBJECTS = src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.$(OBJEXT) +src_core_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_dnsmasq_tests_test_dnsmasq_utils_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_initrd_nm_initrd_generator_OBJECTS = src/core/initrd/nm_initrd_generator-nm-initrd-generator.$(OBJEXT) +src_core_initrd_nm_initrd_generator_OBJECTS = \ + $(am_src_core_initrd_nm_initrd_generator_OBJECTS) +src_core_initrd_nm_initrd_generator_DEPENDENCIES = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la $(libnm_crypto_lib) \ @@ -2205,73 +2221,82 @@ src_initrd_nm_initrd_generator_DEPENDENCIES = \ shared/nm-std-aux/libnm-std-aux.la shared/libndhcp4.la \ shared/libcsiphash.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) -src_initrd_nm_initrd_generator_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_initrd_nm_initrd_generator_LDFLAGS) $(LDFLAGS) -o $@ -src_initrd_tests_test_cmdline_reader_SOURCES = \ - src/initrd/tests/test-cmdline-reader.c -src_initrd_tests_test_cmdline_reader_OBJECTS = src/initrd/tests/test_cmdline_reader-test-cmdline-reader.$(OBJEXT) -src_initrd_tests_test_cmdline_reader_DEPENDENCIES = \ - src/initrd/libnmi-core.la src/libNetworkManagerTest.la \ +src_core_initrd_nm_initrd_generator_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_initrd_nm_initrd_generator_LDFLAGS) $(LDFLAGS) -o \ + $@ +src_core_initrd_tests_test_cmdline_reader_SOURCES = \ + src/core/initrd/tests/test-cmdline-reader.c +src_core_initrd_tests_test_cmdline_reader_OBJECTS = src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.$(OBJEXT) +src_core_initrd_tests_test_cmdline_reader_DEPENDENCIES = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) -src_initrd_tests_test_cmdline_reader_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_initrd_tests_test_cmdline_reader_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_initrd_tests_test_cmdline_reader_LDFLAGS) $(LDFLAGS) -o \ - $@ -src_initrd_tests_test_dt_reader_SOURCES = \ - src/initrd/tests/test-dt-reader.c -src_initrd_tests_test_dt_reader_OBJECTS = \ - src/initrd/tests/test_dt_reader-test-dt-reader.$(OBJEXT) -src_initrd_tests_test_dt_reader_DEPENDENCIES = \ - src/initrd/libnmi-core.la src/libNetworkManagerTest.la \ + $(src_core_initrd_tests_test_cmdline_reader_LDFLAGS) \ + $(LDFLAGS) -o $@ +src_core_initrd_tests_test_dt_reader_SOURCES = \ + src/core/initrd/tests/test-dt-reader.c +src_core_initrd_tests_test_dt_reader_OBJECTS = \ + src/core/initrd/tests/test_dt_reader-test-dt-reader.$(OBJEXT) +src_core_initrd_tests_test_dt_reader_DEPENDENCIES = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) -src_initrd_tests_test_dt_reader_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_initrd_tests_test_dt_reader_LDFLAGS) $(LDFLAGS) -o $@ -src_initrd_tests_test_ibft_reader_SOURCES = \ - src/initrd/tests/test-ibft-reader.c -src_initrd_tests_test_ibft_reader_OBJECTS = \ - src/initrd/tests/test_ibft_reader-test-ibft-reader.$(OBJEXT) -src_initrd_tests_test_ibft_reader_DEPENDENCIES = \ - src/initrd/libnmi-core.la src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_dt_reader_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_initrd_tests_test_dt_reader_LDFLAGS) $(LDFLAGS) -o \ + $@ +src_core_initrd_tests_test_ibft_reader_SOURCES = \ + src/core/initrd/tests/test-ibft-reader.c +src_core_initrd_tests_test_ibft_reader_OBJECTS = src/core/initrd/tests/test_ibft_reader-test-ibft-reader.$(OBJEXT) +src_core_initrd_tests_test_ibft_reader_DEPENDENCIES = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) -src_initrd_tests_test_ibft_reader_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_initrd_tests_test_ibft_reader_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_initrd_tests_test_ibft_reader_LDFLAGS) $(LDFLAGS) -o $@ -src_ndisc_tests_test_ndisc_fake_SOURCES = \ - src/ndisc/tests/test-ndisc-fake.c -src_ndisc_tests_test_ndisc_fake_OBJECTS = \ - src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.$(OBJEXT) -am__DEPENDENCIES_5 = src/libNetworkManagerTest.la \ + $(src_core_initrd_tests_test_ibft_reader_LDFLAGS) $(LDFLAGS) \ + -o $@ +src_core_ndisc_tests_test_ndisc_fake_SOURCES = \ + src/core/ndisc/tests/test-ndisc-fake.c +src_core_ndisc_tests_test_ndisc_fake_OBJECTS = src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.$(OBJEXT) +am__DEPENDENCIES_5 = src/core/libNetworkManagerTest.la \ $(am__DEPENDENCIES_1) -src_ndisc_tests_test_ndisc_fake_DEPENDENCIES = $(am__DEPENDENCIES_5) -src_ndisc_tests_test_ndisc_fake_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_ndisc_tests_test_ndisc_fake_LDFLAGS) $(LDFLAGS) -o $@ -src_ndisc_tests_test_ndisc_linux_SOURCES = \ - src/ndisc/tests/test-ndisc-linux.c -src_ndisc_tests_test_ndisc_linux_OBJECTS = \ - src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.$(OBJEXT) -src_ndisc_tests_test_ndisc_linux_DEPENDENCIES = $(am__DEPENDENCIES_5) -src_ndisc_tests_test_ndisc_linux_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_ndisc_tests_test_ndisc_linux_LDFLAGS) $(LDFLAGS) -o $@ -am_src_nm_iface_helper_OBJECTS = \ - src/nm_iface_helper-nm-iface-helper.$(OBJEXT) -src_nm_iface_helper_OBJECTS = $(am_src_nm_iface_helper_OBJECTS) -src_nm_iface_helper_DEPENDENCIES = src/libNetworkManagerBase.la \ +src_core_ndisc_tests_test_ndisc_fake_DEPENDENCIES = \ + $(am__DEPENDENCIES_5) +src_core_ndisc_tests_test_ndisc_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_ndisc_tests_test_ndisc_fake_LDFLAGS) $(LDFLAGS) -o \ + $@ +src_core_ndisc_tests_test_ndisc_linux_SOURCES = \ + src/core/ndisc/tests/test-ndisc-linux.c +src_core_ndisc_tests_test_ndisc_linux_OBJECTS = src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.$(OBJEXT) +src_core_ndisc_tests_test_ndisc_linux_DEPENDENCIES = \ + $(am__DEPENDENCIES_5) +src_core_ndisc_tests_test_ndisc_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_ndisc_tests_test_ndisc_linux_LDFLAGS) $(LDFLAGS) -o \ + $@ +am_src_core_nm_iface_helper_OBJECTS = \ + src/core/nm_iface_helper-nm-iface-helper.$(OBJEXT) +src_core_nm_iface_helper_OBJECTS = \ + $(am_src_core_nm_iface_helper_OBJECTS) +src_core_nm_iface_helper_DEPENDENCIES = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la $(libnm_crypto_lib) \ @@ -2281,276 +2306,289 @@ src_nm_iface_helper_DEPENDENCIES = src/libNetworkManagerBase.la \ shared/nm-log-core/libnm-log-core.la \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ - shared/nm-std-aux/libnm-std-aux.la src/libnm-systemd-core.la \ + shared/nm-std-aux/libnm-std-aux.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la shared/libnacd.la \ shared/libndhcp4.la shared/libcrbtree.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) -src_nm_iface_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_nm_iface_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_nm_iface_helper_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_nm_iface_helper_LDFLAGS) \ $(LDFLAGS) -o $@ -src_platform_tests_monitor_SOURCES = src/platform/tests/monitor.c -src_platform_tests_monitor_OBJECTS = \ - src/platform/tests/monitor-monitor.$(OBJEXT) -am__DEPENDENCIES_6 = src/libNetworkManagerTest.la \ +src_core_platform_tests_monitor_SOURCES = \ + src/core/platform/tests/monitor.c +src_core_platform_tests_monitor_OBJECTS = \ + src/core/platform/tests/monitor-monitor.$(OBJEXT) +am__DEPENDENCIES_6 = src/core/libNetworkManagerTest.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -src_platform_tests_monitor_DEPENDENCIES = $(am__DEPENDENCIES_6) -src_platform_tests_monitor_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_platform_tests_monitor_DEPENDENCIES = $(am__DEPENDENCIES_6) +src_core_platform_tests_monitor_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_platform_tests_monitor_LDFLAGS) \ - $(LDFLAGS) -o $@ -am_src_platform_tests_test_address_fake_OBJECTS = \ - src/platform/tests/test_address_fake-test-address.$(OBJEXT) -src_platform_tests_test_address_fake_OBJECTS = \ - $(am_src_platform_tests_test_address_fake_OBJECTS) -src_platform_tests_test_address_fake_DEPENDENCIES = \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_platform_tests_monitor_LDFLAGS) $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_address_fake_OBJECTS = src/core/platform/tests/test_address_fake-test-address.$(OBJEXT) +src_core_platform_tests_test_address_fake_OBJECTS = \ + $(am_src_core_platform_tests_test_address_fake_OBJECTS) +src_core_platform_tests_test_address_fake_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_address_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_address_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_address_fake_LDFLAGS) $(LDFLAGS) -o \ - $@ -am_src_platform_tests_test_address_linux_OBJECTS = \ - src/platform/tests/test_address_linux-test-address.$(OBJEXT) -src_platform_tests_test_address_linux_OBJECTS = \ - $(am_src_platform_tests_test_address_linux_OBJECTS) -src_platform_tests_test_address_linux_DEPENDENCIES = \ + $(src_core_platform_tests_test_address_fake_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_address_linux_OBJECTS = src/core/platform/tests/test_address_linux-test-address.$(OBJEXT) +src_core_platform_tests_test_address_linux_OBJECTS = \ + $(am_src_core_platform_tests_test_address_linux_OBJECTS) +src_core_platform_tests_test_address_linux_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_address_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ - --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ - $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_address_linux_LDFLAGS) $(LDFLAGS) -o \ - $@ -am_src_platform_tests_test_cleanup_fake_OBJECTS = \ - src/platform/tests/test_cleanup_fake-test-cleanup.$(OBJEXT) -src_platform_tests_test_cleanup_fake_OBJECTS = \ - $(am_src_platform_tests_test_cleanup_fake_OBJECTS) -src_platform_tests_test_cleanup_fake_DEPENDENCIES = \ +src_core_platform_tests_test_address_linux_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_platform_tests_test_address_linux_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_cleanup_fake_OBJECTS = src/core/platform/tests/test_cleanup_fake-test-cleanup.$(OBJEXT) +src_core_platform_tests_test_cleanup_fake_OBJECTS = \ + $(am_src_core_platform_tests_test_cleanup_fake_OBJECTS) +src_core_platform_tests_test_cleanup_fake_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_cleanup_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_cleanup_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_cleanup_fake_LDFLAGS) $(LDFLAGS) -o \ - $@ -am_src_platform_tests_test_cleanup_linux_OBJECTS = \ - src/platform/tests/test_cleanup_linux-test-cleanup.$(OBJEXT) -src_platform_tests_test_cleanup_linux_OBJECTS = \ - $(am_src_platform_tests_test_cleanup_linux_OBJECTS) -src_platform_tests_test_cleanup_linux_DEPENDENCIES = \ + $(src_core_platform_tests_test_cleanup_fake_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_cleanup_linux_OBJECTS = src/core/platform/tests/test_cleanup_linux-test-cleanup.$(OBJEXT) +src_core_platform_tests_test_cleanup_linux_OBJECTS = \ + $(am_src_core_platform_tests_test_cleanup_linux_OBJECTS) +src_core_platform_tests_test_cleanup_linux_DEPENDENCIES = \ + $(am__DEPENDENCIES_6) +src_core_platform_tests_test_cleanup_linux_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_platform_tests_test_cleanup_linux_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_link_fake_OBJECTS = \ + src/core/platform/tests/test_link_fake-test-link.$(OBJEXT) +src_core_platform_tests_test_link_fake_OBJECTS = \ + $(am_src_core_platform_tests_test_link_fake_OBJECTS) +src_core_platform_tests_test_link_fake_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_cleanup_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_link_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_cleanup_linux_LDFLAGS) $(LDFLAGS) -o \ - $@ -am_src_platform_tests_test_link_fake_OBJECTS = \ - src/platform/tests/test_link_fake-test-link.$(OBJEXT) -src_platform_tests_test_link_fake_OBJECTS = \ - $(am_src_platform_tests_test_link_fake_OBJECTS) -src_platform_tests_test_link_fake_DEPENDENCIES = \ + $(src_core_platform_tests_test_link_fake_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_src_core_platform_tests_test_link_linux_OBJECTS = \ + src/core/platform/tests/test_link_linux-test-link.$(OBJEXT) +src_core_platform_tests_test_link_linux_OBJECTS = \ + $(am_src_core_platform_tests_test_link_linux_OBJECTS) +src_core_platform_tests_test_link_linux_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_link_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_link_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_link_fake_LDFLAGS) $(LDFLAGS) -o $@ -am_src_platform_tests_test_link_linux_OBJECTS = \ - src/platform/tests/test_link_linux-test-link.$(OBJEXT) -src_platform_tests_test_link_linux_OBJECTS = \ - $(am_src_platform_tests_test_link_linux_OBJECTS) -src_platform_tests_test_link_linux_DEPENDENCIES = \ - $(am__DEPENDENCIES_6) -src_platform_tests_test_link_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ + $(src_core_platform_tests_test_link_linux_LDFLAGS) $(LDFLAGS) \ + -o $@ +src_core_platform_tests_test_nmp_object_SOURCES = \ + src/core/platform/tests/test-nmp-object.c +src_core_platform_tests_test_nmp_object_OBJECTS = src/core/platform/tests/test_nmp_object-test-nmp-object.$(OBJEXT) +src_core_platform_tests_test_nmp_object_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_platform_tests_test_nmp_object_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_link_linux_LDFLAGS) $(LDFLAGS) -o $@ -src_platform_tests_test_nmp_object_SOURCES = \ - src/platform/tests/test-nmp-object.c -src_platform_tests_test_nmp_object_OBJECTS = \ - src/platform/tests/test_nmp_object-test-nmp-object.$(OBJEXT) -src_platform_tests_test_nmp_object_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_platform_tests_test_nmp_object_LINK = $(LIBTOOL) $(AM_V_lt) \ + $(src_core_platform_tests_test_nmp_object_LDFLAGS) $(LDFLAGS) \ + -o $@ +src_core_platform_tests_test_platform_general_SOURCES = \ + src/core/platform/tests/test-platform-general.c +src_core_platform_tests_test_platform_general_OBJECTS = src/core/platform/tests/test_platform_general-test-platform-general.$(OBJEXT) +src_core_platform_tests_test_platform_general_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_platform_tests_test_platform_general_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_platform_tests_test_platform_general_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_src_core_platform_tests_test_route_fake_OBJECTS = \ + src/core/platform/tests/test_route_fake-test-route.$(OBJEXT) +src_core_platform_tests_test_route_fake_OBJECTS = \ + $(am_src_core_platform_tests_test_route_fake_OBJECTS) +src_core_platform_tests_test_route_fake_DEPENDENCIES = \ + $(am__DEPENDENCIES_6) +src_core_platform_tests_test_route_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_nmp_object_LDFLAGS) $(LDFLAGS) -o $@ -src_platform_tests_test_platform_general_SOURCES = \ - src/platform/tests/test-platform-general.c -src_platform_tests_test_platform_general_OBJECTS = src/platform/tests/test_platform_general-test-platform-general.$(OBJEXT) -src_platform_tests_test_platform_general_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_platform_tests_test_platform_general_LINK = $(LIBTOOL) $(AM_V_lt) \ + $(src_core_platform_tests_test_route_fake_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_src_core_platform_tests_test_route_linux_OBJECTS = \ + src/core/platform/tests/test_route_linux-test-route.$(OBJEXT) +src_core_platform_tests_test_route_linux_OBJECTS = \ + $(am_src_core_platform_tests_test_route_linux_OBJECTS) +src_core_platform_tests_test_route_linux_DEPENDENCIES = \ + $(am__DEPENDENCIES_6) +src_core_platform_tests_test_route_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_platform_general_LDFLAGS) $(LDFLAGS) \ + $(src_core_platform_tests_test_route_linux_LDFLAGS) $(LDFLAGS) \ -o $@ -am_src_platform_tests_test_route_fake_OBJECTS = \ - src/platform/tests/test_route_fake-test-route.$(OBJEXT) -src_platform_tests_test_route_fake_OBJECTS = \ - $(am_src_platform_tests_test_route_fake_OBJECTS) -src_platform_tests_test_route_fake_DEPENDENCIES = \ +am_src_core_platform_tests_test_tc_fake_OBJECTS = \ + src/core/platform/tests/test_tc_fake-test-tc.$(OBJEXT) +src_core_platform_tests_test_tc_fake_OBJECTS = \ + $(am_src_core_platform_tests_test_tc_fake_OBJECTS) +src_core_platform_tests_test_tc_fake_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_route_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_tc_fake_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_route_fake_LDFLAGS) $(LDFLAGS) -o $@ -am_src_platform_tests_test_route_linux_OBJECTS = \ - src/platform/tests/test_route_linux-test-route.$(OBJEXT) -src_platform_tests_test_route_linux_OBJECTS = \ - $(am_src_platform_tests_test_route_linux_OBJECTS) -src_platform_tests_test_route_linux_DEPENDENCIES = \ + $(src_core_platform_tests_test_tc_fake_LDFLAGS) $(LDFLAGS) -o \ + $@ +am_src_core_platform_tests_test_tc_linux_OBJECTS = \ + src/core/platform/tests/test_tc_linux-test-tc.$(OBJEXT) +src_core_platform_tests_test_tc_linux_OBJECTS = \ + $(am_src_core_platform_tests_test_tc_linux_OBJECTS) +src_core_platform_tests_test_tc_linux_DEPENDENCIES = \ $(am__DEPENDENCIES_6) -src_platform_tests_test_route_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ +src_core_platform_tests_test_tc_linux_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_route_linux_LDFLAGS) $(LDFLAGS) -o \ + $(src_core_platform_tests_test_tc_linux_LDFLAGS) $(LDFLAGS) -o \ $@ -am_src_platform_tests_test_tc_fake_OBJECTS = \ - src/platform/tests/test_tc_fake-test-tc.$(OBJEXT) -src_platform_tests_test_tc_fake_OBJECTS = \ - $(am_src_platform_tests_test_tc_fake_OBJECTS) -src_platform_tests_test_tc_fake_DEPENDENCIES = $(am__DEPENDENCIES_6) -src_platform_tests_test_tc_fake_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_tc_fake_LDFLAGS) $(LDFLAGS) -o $@ -am_src_platform_tests_test_tc_linux_OBJECTS = \ - src/platform/tests/test_tc_linux-test-tc.$(OBJEXT) -src_platform_tests_test_tc_linux_OBJECTS = \ - $(am_src_platform_tests_test_tc_linux_OBJECTS) -src_platform_tests_test_tc_linux_DEPENDENCIES = $(am__DEPENDENCIES_6) -src_platform_tests_test_tc_linux_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) \ - $(src_platform_tests_test_tc_linux_LDFLAGS) $(LDFLAGS) -o $@ -am__src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES_DIST = \ - src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS = src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.$(OBJEXT) -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS = $(am_src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES = src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/libNetworkManagerTest.la -src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LINK = $(LIBTOOL) \ - $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ - --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS) \ +am__src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES_DIST = \ + src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@CONFIG_PLUGIN_IFCFG_RH_TRUE@am_src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS = src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.$(OBJEXT) +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS = $(am_src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES = src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/libNetworkManagerTest.la +src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LINK = \ + $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS) \ $(LDFLAGS) -o $@ -src_settings_plugins_ifupdown_tests_test_ifupdown_SOURCES = \ - src/settings/plugins/ifupdown/tests/test-ifupdown.c -src_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS = src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.$(OBJEXT) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES = src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/libNetworkManagerTest.la \ +src_core_settings_plugins_ifupdown_tests_test_ifupdown_SOURCES = \ + src/core/settings/plugins/ifupdown/tests/test-ifupdown.c +src_core_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS = src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.$(OBJEXT) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES = src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/libNetworkManagerTest.la \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(am__DEPENDENCIES_1) -src_settings_plugins_ifupdown_tests_test_ifupdown_LINK = $(LIBTOOL) \ - $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ - --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS) \ +src_core_settings_plugins_ifupdown_tests_test_ifupdown_LINK = \ + $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS) \ $(LDFLAGS) -o $@ -src_settings_plugins_keyfile_tests_test_keyfile_settings_SOURCES = \ - src/settings/plugins/keyfile/tests/test-keyfile-settings.c -src_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS = src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.$(OBJEXT) -src_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_settings_plugins_keyfile_tests_test_keyfile_settings_LINK = \ +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_SOURCES = src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS = src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.$(OBJEXT) +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LINK = \ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS) \ + $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS) \ $(LDFLAGS) -o $@ -src_supplicant_tests_test_supplicant_config_SOURCES = \ - src/supplicant/tests/test-supplicant-config.c -src_supplicant_tests_test_supplicant_config_OBJECTS = src/supplicant/tests/test_supplicant_config-test-supplicant-config.$(OBJEXT) -src_supplicant_tests_test_supplicant_config_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_supplicant_tests_test_supplicant_config_LINK = $(LIBTOOL) \ +src_core_supplicant_tests_test_supplicant_config_SOURCES = \ + src/core/supplicant/tests/test-supplicant-config.c +src_core_supplicant_tests_test_supplicant_config_OBJECTS = src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.$(OBJEXT) +src_core_supplicant_tests_test_supplicant_config_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_supplicant_tests_test_supplicant_config_LINK = $(LIBTOOL) \ $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ - $(src_supplicant_tests_test_supplicant_config_LDFLAGS) \ + $(src_core_supplicant_tests_test_supplicant_config_LDFLAGS) \ $(LDFLAGS) -o $@ -am_src_tests_config_test_config_OBJECTS = \ - src/tests/config/test_config-nm-test-device.$(OBJEXT) \ - src/tests/config/test_config-test-config.$(OBJEXT) -src_tests_config_test_config_OBJECTS = \ - $(am_src_tests_config_test_config_OBJECTS) -src_tests_config_test_config_DEPENDENCIES = \ - src/libNetworkManagerTest.la -src_tests_config_test_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +am_src_core_tests_config_test_config_OBJECTS = \ + src/core/tests/config/test_config-nm-test-device.$(OBJEXT) \ + src/core/tests/config/test_config-test-config.$(OBJEXT) +src_core_tests_config_test_config_OBJECTS = \ + $(am_src_core_tests_config_test_config_OBJECTS) +src_core_tests_config_test_config_DEPENDENCIES = \ + src/core/libNetworkManagerTest.la +src_core_tests_config_test_config_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_tests_config_test_config_LDFLAGS) $(LDFLAGS) -o $@ +src_core_tests_test_core_SOURCES = src/core/tests/test-core.c +src_core_tests_test_core_OBJECTS = \ + src/core/tests/test_core-test-core.$(OBJEXT) +src_core_tests_test_core_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_core_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_config_test_config_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_tests_test_core_LDFLAGS) \ $(LDFLAGS) -o $@ -src_tests_test_core_SOURCES = src/tests/test-core.c -src_tests_test_core_OBJECTS = src/tests/test_core-test-core.$(OBJEXT) -src_tests_test_core_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_core_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_tests_test_core_with_expect_SOURCES = \ + src/core/tests/test-core-with-expect.c +src_core_tests_test_core_with_expect_OBJECTS = src/core/tests/test_core_with_expect-test-core-with-expect.$(OBJEXT) +src_core_tests_test_core_with_expect_DEPENDENCIES = \ + $(src_core_tests_ldadd) +src_core_tests_test_core_with_expect_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_tests_test_core_with_expect_LDFLAGS) $(LDFLAGS) -o \ + $@ +src_core_tests_test_dcb_SOURCES = src/core/tests/test-dcb.c +src_core_tests_test_dcb_OBJECTS = \ + src/core/tests/test_dcb-test-dcb.$(OBJEXT) +src_core_tests_test_dcb_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_dcb_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_core_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_tests_test_dcb_LDFLAGS) \ $(LDFLAGS) -o $@ -src_tests_test_core_with_expect_SOURCES = \ - src/tests/test-core-with-expect.c -src_tests_test_core_with_expect_OBJECTS = src/tests/test_core_with_expect-test-core-with-expect.$(OBJEXT) -src_tests_test_core_with_expect_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_core_with_expect_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_tests_test_ip4_config_SOURCES = \ + src/core/tests/test-ip4-config.c +src_core_tests_test_ip4_config_OBJECTS = \ + src/core/tests/test_ip4_config-test-ip4-config.$(OBJEXT) +src_core_tests_test_ip4_config_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_ip4_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) \ - $(src_tests_test_core_with_expect_LDFLAGS) $(LDFLAGS) -o $@ -src_tests_test_dcb_SOURCES = src/tests/test-dcb.c -src_tests_test_dcb_OBJECTS = src/tests/test_dcb-test-dcb.$(OBJEXT) -src_tests_test_dcb_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_dcb_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_dcb_LDFLAGS) \ - $(LDFLAGS) -o $@ -src_tests_test_ip4_config_SOURCES = src/tests/test-ip4-config.c -src_tests_test_ip4_config_OBJECTS = \ - src/tests/test_ip4_config-test-ip4-config.$(OBJEXT) -src_tests_test_ip4_config_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_ip4_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(src_core_tests_test_ip4_config_LDFLAGS) $(LDFLAGS) -o $@ +src_core_tests_test_ip6_config_SOURCES = \ + src/core/tests/test-ip6-config.c +src_core_tests_test_ip6_config_OBJECTS = \ + src/core/tests/test_ip6_config-test-ip6-config.$(OBJEXT) +src_core_tests_test_ip6_config_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_ip6_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_ip4_config_LDFLAGS) \ - $(LDFLAGS) -o $@ -src_tests_test_ip6_config_SOURCES = src/tests/test-ip6-config.c -src_tests_test_ip6_config_OBJECTS = \ - src/tests/test_ip6_config-test-ip6-config.$(OBJEXT) -src_tests_test_ip6_config_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_ip6_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_ip6_config_LDFLAGS) \ - $(LDFLAGS) -o $@ -src_tests_test_l3cfg_SOURCES = src/tests/test-l3cfg.c -src_tests_test_l3cfg_OBJECTS = \ - src/tests/test_l3cfg-test-l3cfg.$(OBJEXT) -src_tests_test_l3cfg_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_l3cfg_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_tests_test_ip6_config_LDFLAGS) $(LDFLAGS) -o $@ +src_core_tests_test_l3cfg_SOURCES = src/core/tests/test-l3cfg.c +src_core_tests_test_l3cfg_OBJECTS = \ + src/core/tests/test_l3cfg-test-l3cfg.$(OBJEXT) +src_core_tests_test_l3cfg_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_l3cfg_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_l3cfg_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_tests_test_l3cfg_LDFLAGS) \ $(LDFLAGS) -o $@ -src_tests_test_systemd_SOURCES = src/tests/test-systemd.c -src_tests_test_systemd_OBJECTS = \ - src/tests/test_systemd-test-systemd.$(OBJEXT) -src_tests_test_systemd_DEPENDENCIES = src/libnm-systemd-core.la \ +src_core_tests_test_systemd_SOURCES = src/core/tests/test-systemd.c +src_core_tests_test_systemd_OBJECTS = \ + src/core/tests/test_systemd-test-systemd.$(OBJEXT) +src_core_tests_test_systemd_DEPENDENCIES = \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la shared/libcsiphash.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -src_tests_test_systemd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_tests_test_systemd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_systemd_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_tests_test_systemd_LDFLAGS) \ $(LDFLAGS) -o $@ -src_tests_test_utils_SOURCES = src/tests/test-utils.c -src_tests_test_utils_OBJECTS = \ - src/tests/test_utils-test-utils.$(OBJEXT) -src_tests_test_utils_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +src_core_tests_test_utils_SOURCES = src/core/tests/test-utils.c +src_core_tests_test_utils_OBJECTS = \ + src/core/tests/test_utils-test-utils.$(OBJEXT) +src_core_tests_test_utils_DEPENDENCIES = $(src_core_tests_ldadd) +src_core_tests_test_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_utils_LDFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(src_core_tests_test_utils_LDFLAGS) \ $(LDFLAGS) -o $@ -src_tests_test_wired_defname_SOURCES = src/tests/test-wired-defname.c -src_tests_test_wired_defname_OBJECTS = \ - src/tests/test_wired_defname-test-wired-defname.$(OBJEXT) -src_tests_test_wired_defname_DEPENDENCIES = $(src_tests_ldadd) -src_tests_test_wired_defname_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ - $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ - $(AM_CFLAGS) $(CFLAGS) $(src_tests_test_wired_defname_LDFLAGS) \ - $(LDFLAGS) -o $@ -am__dist_libexec_SCRIPTS_DIST = src/settings/plugins/ifcfg-rh/nm-ifup \ - src/settings/plugins/ifcfg-rh/nm-ifdown +src_core_tests_test_wired_defname_SOURCES = \ + src/core/tests/test-wired-defname.c +src_core_tests_test_wired_defname_OBJECTS = src/core/tests/test_wired_defname-test-wired-defname.$(OBJEXT) +src_core_tests_test_wired_defname_DEPENDENCIES = \ + $(src_core_tests_ldadd) +src_core_tests_test_wired_defname_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(src_core_tests_test_wired_defname_LDFLAGS) $(LDFLAGS) -o $@ +am__dist_libexec_SCRIPTS_DIST = \ + src/core/settings/plugins/ifcfg-rh/nm-ifup \ + src/core/settings/plugins/ifcfg-rh/nm-ifdown SCRIPTS = $(dist_libexec_SCRIPTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) @@ -2796,59 +2834,59 @@ am__depfiles_remade = clients/$(DEPDIR)/nm_online-nm-online.Po \ libnm-core/tests/$(DEPDIR)/test_secrets-test-secrets.Po \ libnm-core/tests/$(DEPDIR)/test_setting-test-setting.Po \ libnm-core/tests/$(DEPDIR)/test_settings_defaults-test-settings-defaults.Po \ - libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-client.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-device.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-object.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Plo \ - libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-client.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-device.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-object.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Plo \ + libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Plo \ libnm/nm-libnm-aux/$(DEPDIR)/libnm_libnm_aux_la-nm-libnm-aux.Plo \ libnm/tests/$(DEPDIR)/test_libnm-test-libnm.Po \ libnm/tests/$(DEPDIR)/test_nm_client-test-nm-client.Po \ @@ -2941,232 +2979,232 @@ am__depfiles_remade = clients/$(DEPDIR)/nm_online-nm-online.Po \ shared/systemd/src/basic/$(DEPDIR)/libnm_systemd_shared_la-util.Plo \ shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-dns-domain.Plo \ shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-web-util.Plo \ - src/$(DEPDIR)/NetworkManager-main.Po \ - src/$(DEPDIR)/NetworkManager_all_sym-main.Po \ - src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo \ - src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-config.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo \ - src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo \ - src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo \ - src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo \ - src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo \ - src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo \ - src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo \ - src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo \ - src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo \ - src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo \ - src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po \ - src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo \ - src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo \ - src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo \ - src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo \ - src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo \ - src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo \ - src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo \ - src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po \ - src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po \ - src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo \ - src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo \ - src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po \ - src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo \ - src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo \ - src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po \ - src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo \ - src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo \ - src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po \ - src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po \ - src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po \ - src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo \ - src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo \ - src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo \ - src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo \ - src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo \ - src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo \ - src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo \ - src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po \ - src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo \ - src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo \ - src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo \ - src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po \ - src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po \ - src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po \ - src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po \ - src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo \ - src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo \ - src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo \ - src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po \ - src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po \ - src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo \ - src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo \ - src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo \ - src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo \ - src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo \ - src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo \ - src/platform/tests/$(DEPDIR)/monitor-monitor.Po \ - src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po \ - src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po \ - src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po \ - src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po \ - src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po \ - src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po \ - src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po \ - src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po \ - src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po \ - src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po \ - src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po \ - src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po \ - src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo \ - src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo \ - src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo \ - src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo \ - src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo \ - src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo \ - src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo \ - src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo \ - src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po \ - src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo \ - src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo \ - src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo \ - src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po \ - src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo \ - src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo \ - src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo \ - src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo \ - src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo \ - src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po \ - src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo \ - src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo \ - src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo \ - src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo \ - src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po \ - src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo \ - src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo \ - src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo \ - src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo \ - src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo \ - src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo \ - src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo \ - src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo \ - src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo \ - src/tests/$(DEPDIR)/test_core-test-core.Po \ - src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po \ - src/tests/$(DEPDIR)/test_dcb-test-dcb.Po \ - src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po \ - src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po \ - src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po \ - src/tests/$(DEPDIR)/test_systemd-test-systemd.Po \ - src/tests/$(DEPDIR)/test_utils-test-utils.Po \ - src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po \ - src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po \ - src/tests/config/$(DEPDIR)/test_config-test-config.Po \ - src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo \ - src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo + src/core/$(DEPDIR)/NetworkManager-main.Po \ + src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo \ + src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo \ + src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo \ + src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo \ + src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo \ + src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo \ + src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo \ + src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo \ + src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo \ + src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo \ + src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo \ + src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po \ + src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo \ + src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo \ + src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo \ + src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo \ + src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo \ + src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo \ + src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo \ + src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po \ + src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po \ + src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo \ + src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo \ + src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po \ + src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo \ + src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo \ + src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po \ + src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo \ + src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo \ + src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po \ + src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po \ + src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po \ + src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo \ + src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo \ + src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo \ + src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo \ + src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo \ + src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo \ + src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo \ + src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po \ + src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo \ + src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo \ + src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo \ + src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po \ + src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po \ + src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po \ + src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po \ + src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo \ + src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo \ + src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo \ + src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po \ + src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po \ + src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo \ + src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo \ + src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo \ + src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo \ + src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo \ + src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo \ + src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po \ + src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po \ + src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po \ + src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po \ + src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po \ + src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po \ + src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po \ + src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po \ + src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po \ + src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po \ + src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po \ + src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po \ + src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po \ + src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo \ + src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo \ + src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo \ + src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo \ + src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo \ + src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo \ + src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo \ + src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo \ + src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo \ + src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po \ + src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo \ + src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo \ + src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo \ + src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo \ + src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo \ + src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po \ + src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo \ + src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo \ + src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo \ + src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo \ + src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po \ + src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo \ + src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo \ + src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo \ + src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo \ + src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo \ + src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo \ + src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo \ + src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo \ + src/core/tests/$(DEPDIR)/test_core-test-core.Po \ + src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po \ + src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po \ + src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po \ + src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po \ + src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po \ + src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po \ + src/core/tests/$(DEPDIR)/test_utils-test-utils.Po \ + src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po \ + src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po \ + src/core/tests/config/$(DEPDIR)/test_config-test-config.Po \ + src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo \ + src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -3216,8 +3254,8 @@ SOURCES = $(clients_tui_newt_libnmt_newt_a_SOURCES) \ $(libnm_core_nm_keyfile_libnm_keyfile_la_SOURCES) \ $(libnm_core_nm_libnm_core_aux_libnm_libnm_core_aux_la_SOURCES) \ $(libnm_core_nm_libnm_core_intern_libnm_libnm_core_intern_la_SOURCES) \ - $(libnm_liblibnm_la_SOURCES) \ - $(nodist_libnm_liblibnm_la_SOURCES) $(libnm_libnm_la_SOURCES) \ + $(libnm_libnm_la_SOURCES) $(libnm_libnm_static_la_SOURCES) \ + $(nodist_libnm_libnm_static_la_SOURCES) \ $(libnm_nm_libnm_aux_libnm_libnm_aux_la_SOURCES) \ $(libnm_tests_libnm_vpn_plugin_utils_test_la_SOURCES) \ $(shared_libcrbtree_la_SOURCES) \ @@ -3231,27 +3269,27 @@ SOURCES = $(clients_tui_newt_libnmt_newt_a_SOURCES) \ $(shared_nm_udev_aux_libnm_udev_aux_la_SOURCES) \ $(shared_systemd_libnm_systemd_logging_stub_la_SOURCES) \ $(shared_systemd_libnm_systemd_shared_la_SOURCES) \ - $(src_devices_adsl_libnm_device_plugin_adsl_la_SOURCES) \ - $(src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES) \ - $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES) \ - $(src_devices_ovs_libnm_device_plugin_ovs_la_SOURCES) \ - $(src_devices_team_libnm_device_plugin_team_la_SOURCES) \ - $(src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES) \ - $(src_devices_wifi_libnm_wifi_base_la_SOURCES) \ - $(src_devices_wwan_libnm_device_plugin_wwan_la_SOURCES) \ - $(src_devices_wwan_libnm_wwan_la_SOURCES) \ - $(src_initrd_libnmi_core_la_SOURCES) \ - $(src_libNetworkManager_la_SOURCES) \ - $(src_libNetworkManagerBase_la_SOURCES) \ - $(src_libNetworkManagerTest_la_SOURCES) \ - $(src_libnm_systemd_core_la_SOURCES) \ - $(src_ppp_libnm_ppp_plugin_la_SOURCES) \ - $(src_ppp_nm_pppd_plugin_la_SOURCES) \ - $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES) \ - $(nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) \ - $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES) \ - $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES) \ - $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES) \ + $(src_core_devices_adsl_libnm_device_plugin_adsl_la_SOURCES) \ + $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES) \ + $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES) \ + $(src_core_devices_ovs_libnm_device_plugin_ovs_la_SOURCES) \ + $(src_core_devices_team_libnm_device_plugin_team_la_SOURCES) \ + $(src_core_devices_wifi_libnm_device_plugin_wifi_la_SOURCES) \ + $(src_core_devices_wifi_libnm_wifi_base_la_SOURCES) \ + $(src_core_devices_wwan_libnm_device_plugin_wwan_la_SOURCES) \ + $(src_core_devices_wwan_libnm_wwan_la_SOURCES) \ + $(src_core_initrd_libnmi_core_la_SOURCES) \ + $(src_core_libNetworkManager_la_SOURCES) \ + $(src_core_libNetworkManagerBase_la_SOURCES) \ + $(src_core_libNetworkManagerTest_la_SOURCES) \ + $(src_core_libnm_systemd_core_la_SOURCES) \ + $(src_core_ppp_libnm_ppp_plugin_la_SOURCES) \ + $(src_core_ppp_nm_pppd_plugin_la_SOURCES) \ + $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES) \ + $(nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES) \ + $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES) \ + $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES) \ + $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES) \ $(clients_cli_generate_docs_nm_settings_nmcli_SOURCES) \ $(clients_cli_nmcli_SOURCES) \ $(clients_cloud_setup_nm_cloud_setup_SOURCES) \ @@ -3287,44 +3325,48 @@ SOURCES = $(clients_tui_newt_libnmt_newt_a_SOURCES) \ shared/nm-glib-aux/tests/test-json-aux.c \ shared/nm-glib-aux/tests/test-shared-general.c \ shared/nm-platform/tests/test-nm-platform.c \ - $(src_NetworkManager_SOURCES) \ - $(src_NetworkManager_all_sym_SOURCES) \ - src/devices/bluetooth/tests/nm-bt-test.c \ - src/devices/tests/test-acd.c src/devices/tests/test-lldp.c \ - $(src_devices_wifi_tests_test_devices_wifi_SOURCES) \ - $(src_devices_wwan_tests_test_service_providers_SOURCES) \ - $(src_dhcp_nm_dhcp_helper_SOURCES) \ - src/dhcp/tests/test-dhcp-dhclient.c \ - src/dhcp/tests/test-dhcp-utils.c \ - src/dnsmasq/tests/test-dnsmasq-utils.c \ - $(src_initrd_nm_initrd_generator_SOURCES) \ - src/initrd/tests/test-cmdline-reader.c \ - src/initrd/tests/test-dt-reader.c \ - src/initrd/tests/test-ibft-reader.c \ - src/ndisc/tests/test-ndisc-fake.c \ - src/ndisc/tests/test-ndisc-linux.c \ - $(src_nm_iface_helper_SOURCES) src/platform/tests/monitor.c \ - $(src_platform_tests_test_address_fake_SOURCES) \ - $(src_platform_tests_test_address_linux_SOURCES) \ - $(src_platform_tests_test_cleanup_fake_SOURCES) \ - $(src_platform_tests_test_cleanup_linux_SOURCES) \ - $(src_platform_tests_test_link_fake_SOURCES) \ - $(src_platform_tests_test_link_linux_SOURCES) \ - src/platform/tests/test-nmp-object.c \ - src/platform/tests/test-platform-general.c \ - $(src_platform_tests_test_route_fake_SOURCES) \ - $(src_platform_tests_test_route_linux_SOURCES) \ - $(src_platform_tests_test_tc_fake_SOURCES) \ - $(src_platform_tests_test_tc_linux_SOURCES) \ - $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES) \ - src/settings/plugins/ifupdown/tests/test-ifupdown.c \ - src/settings/plugins/keyfile/tests/test-keyfile-settings.c \ - src/supplicant/tests/test-supplicant-config.c \ - $(src_tests_config_test_config_SOURCES) src/tests/test-core.c \ - src/tests/test-core-with-expect.c src/tests/test-dcb.c \ - src/tests/test-ip4-config.c src/tests/test-ip6-config.c \ - src/tests/test-l3cfg.c src/tests/test-systemd.c \ - src/tests/test-utils.c src/tests/test-wired-defname.c + $(src_core_NetworkManager_SOURCES) \ + $(src_core_NetworkManager_all_sym_SOURCES) \ + src/core/devices/bluetooth/tests/nm-bt-test.c \ + src/core/devices/tests/test-acd.c \ + src/core/devices/tests/test-lldp.c \ + $(src_core_devices_wifi_tests_test_devices_wifi_SOURCES) \ + $(src_core_devices_wwan_tests_test_service_providers_SOURCES) \ + $(src_core_dhcp_nm_dhcp_helper_SOURCES) \ + src/core/dhcp/tests/test-dhcp-dhclient.c \ + src/core/dhcp/tests/test-dhcp-utils.c \ + src/core/dnsmasq/tests/test-dnsmasq-utils.c \ + $(src_core_initrd_nm_initrd_generator_SOURCES) \ + src/core/initrd/tests/test-cmdline-reader.c \ + src/core/initrd/tests/test-dt-reader.c \ + src/core/initrd/tests/test-ibft-reader.c \ + src/core/ndisc/tests/test-ndisc-fake.c \ + src/core/ndisc/tests/test-ndisc-linux.c \ + $(src_core_nm_iface_helper_SOURCES) \ + src/core/platform/tests/monitor.c \ + $(src_core_platform_tests_test_address_fake_SOURCES) \ + $(src_core_platform_tests_test_address_linux_SOURCES) \ + $(src_core_platform_tests_test_cleanup_fake_SOURCES) \ + $(src_core_platform_tests_test_cleanup_linux_SOURCES) \ + $(src_core_platform_tests_test_link_fake_SOURCES) \ + $(src_core_platform_tests_test_link_linux_SOURCES) \ + src/core/platform/tests/test-nmp-object.c \ + src/core/platform/tests/test-platform-general.c \ + $(src_core_platform_tests_test_route_fake_SOURCES) \ + $(src_core_platform_tests_test_route_linux_SOURCES) \ + $(src_core_platform_tests_test_tc_fake_SOURCES) \ + $(src_core_platform_tests_test_tc_linux_SOURCES) \ + $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES) \ + src/core/settings/plugins/ifupdown/tests/test-ifupdown.c \ + src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c \ + src/core/supplicant/tests/test-supplicant-config.c \ + $(src_core_tests_config_test_config_SOURCES) \ + src/core/tests/test-core.c \ + src/core/tests/test-core-with-expect.c \ + src/core/tests/test-dcb.c src/core/tests/test-ip4-config.c \ + src/core/tests/test-ip6-config.c src/core/tests/test-l3cfg.c \ + src/core/tests/test-systemd.c src/core/tests/test-utils.c \ + src/core/tests/test-wired-defname.c DIST_SOURCES = $(am__clients_tui_newt_libnmt_newt_a_SOURCES_DIST) \ $(clients_common_libnmc_base_la_SOURCES) \ $(clients_common_libnmc_la_SOURCES) \ @@ -3335,7 +3377,7 @@ DIST_SOURCES = $(am__clients_tui_newt_libnmt_newt_a_SOURCES_DIST) \ $(libnm_core_nm_keyfile_libnm_keyfile_la_SOURCES) \ $(libnm_core_nm_libnm_core_aux_libnm_libnm_core_aux_la_SOURCES) \ $(libnm_core_nm_libnm_core_intern_libnm_libnm_core_intern_la_SOURCES) \ - $(libnm_liblibnm_la_SOURCES) $(libnm_libnm_la_SOURCES) \ + $(libnm_libnm_la_SOURCES) $(libnm_libnm_static_la_SOURCES) \ $(libnm_nm_libnm_aux_libnm_libnm_aux_la_SOURCES) \ $(libnm_tests_libnm_vpn_plugin_utils_test_la_SOURCES) \ $(shared_libcrbtree_la_SOURCES) \ @@ -3350,26 +3392,26 @@ DIST_SOURCES = $(am__clients_tui_newt_libnmt_newt_a_SOURCES_DIST) \ $(shared_nm_udev_aux_libnm_udev_aux_la_SOURCES) \ $(shared_systemd_libnm_systemd_logging_stub_la_SOURCES) \ $(shared_systemd_libnm_systemd_shared_la_SOURCES) \ - $(src_devices_adsl_libnm_device_plugin_adsl_la_SOURCES) \ - $(am__src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES_DIST) \ - $(am__src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES_DIST) \ - $(am__src_devices_ovs_libnm_device_plugin_ovs_la_SOURCES_DIST) \ - $(am__src_devices_team_libnm_device_plugin_team_la_SOURCES_DIST) \ - $(am__src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES_DIST) \ - $(am__src_devices_wifi_libnm_wifi_base_la_SOURCES_DIST) \ - $(am__src_devices_wwan_libnm_device_plugin_wwan_la_SOURCES_DIST) \ - $(am__src_devices_wwan_libnm_wwan_la_SOURCES_DIST) \ - $(src_initrd_libnmi_core_la_SOURCES) \ - $(src_libNetworkManager_la_SOURCES) \ - $(am__src_libNetworkManagerBase_la_SOURCES_DIST) \ - $(src_libNetworkManagerTest_la_SOURCES) \ - $(src_libnm_systemd_core_la_SOURCES) \ - $(am__src_ppp_libnm_ppp_plugin_la_SOURCES_DIST) \ - $(am__src_ppp_nm_pppd_plugin_la_SOURCES_DIST) \ - $(am__src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES_DIST) \ - $(am__src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES_DIST) \ - $(am__src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES_DIST) \ - $(am__src_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES_DIST) \ + $(src_core_devices_adsl_libnm_device_plugin_adsl_la_SOURCES) \ + $(am__src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES_DIST) \ + $(am__src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES_DIST) \ + $(am__src_core_devices_ovs_libnm_device_plugin_ovs_la_SOURCES_DIST) \ + $(am__src_core_devices_team_libnm_device_plugin_team_la_SOURCES_DIST) \ + $(am__src_core_devices_wifi_libnm_device_plugin_wifi_la_SOURCES_DIST) \ + $(am__src_core_devices_wifi_libnm_wifi_base_la_SOURCES_DIST) \ + $(am__src_core_devices_wwan_libnm_device_plugin_wwan_la_SOURCES_DIST) \ + $(am__src_core_devices_wwan_libnm_wwan_la_SOURCES_DIST) \ + $(src_core_initrd_libnmi_core_la_SOURCES) \ + $(src_core_libNetworkManager_la_SOURCES) \ + $(am__src_core_libNetworkManagerBase_la_SOURCES_DIST) \ + $(src_core_libNetworkManagerTest_la_SOURCES) \ + $(src_core_libnm_systemd_core_la_SOURCES) \ + $(am__src_core_ppp_libnm_ppp_plugin_la_SOURCES_DIST) \ + $(am__src_core_ppp_nm_pppd_plugin_la_SOURCES_DIST) \ + $(am__src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES_DIST) \ + $(am__src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES_DIST) \ + $(am__src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES_DIST) \ + $(am__src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES_DIST) \ $(clients_cli_generate_docs_nm_settings_nmcli_SOURCES) \ $(am__clients_cli_nmcli_SOURCES_DIST) \ $(am__clients_cloud_setup_nm_cloud_setup_SOURCES_DIST) \ @@ -3403,44 +3445,48 @@ DIST_SOURCES = $(am__clients_tui_newt_libnmt_newt_a_SOURCES_DIST) \ shared/nm-glib-aux/tests/test-json-aux.c \ shared/nm-glib-aux/tests/test-shared-general.c \ shared/nm-platform/tests/test-nm-platform.c \ - $(src_NetworkManager_SOURCES) \ - $(src_NetworkManager_all_sym_SOURCES) \ - src/devices/bluetooth/tests/nm-bt-test.c \ - src/devices/tests/test-acd.c src/devices/tests/test-lldp.c \ - $(am__src_devices_wifi_tests_test_devices_wifi_SOURCES_DIST) \ - $(am__src_devices_wwan_tests_test_service_providers_SOURCES_DIST) \ - $(src_dhcp_nm_dhcp_helper_SOURCES) \ - src/dhcp/tests/test-dhcp-dhclient.c \ - src/dhcp/tests/test-dhcp-utils.c \ - src/dnsmasq/tests/test-dnsmasq-utils.c \ - $(src_initrd_nm_initrd_generator_SOURCES) \ - src/initrd/tests/test-cmdline-reader.c \ - src/initrd/tests/test-dt-reader.c \ - src/initrd/tests/test-ibft-reader.c \ - src/ndisc/tests/test-ndisc-fake.c \ - src/ndisc/tests/test-ndisc-linux.c \ - $(src_nm_iface_helper_SOURCES) src/platform/tests/monitor.c \ - $(src_platform_tests_test_address_fake_SOURCES) \ - $(src_platform_tests_test_address_linux_SOURCES) \ - $(src_platform_tests_test_cleanup_fake_SOURCES) \ - $(src_platform_tests_test_cleanup_linux_SOURCES) \ - $(src_platform_tests_test_link_fake_SOURCES) \ - $(src_platform_tests_test_link_linux_SOURCES) \ - src/platform/tests/test-nmp-object.c \ - src/platform/tests/test-platform-general.c \ - $(src_platform_tests_test_route_fake_SOURCES) \ - $(src_platform_tests_test_route_linux_SOURCES) \ - $(src_platform_tests_test_tc_fake_SOURCES) \ - $(src_platform_tests_test_tc_linux_SOURCES) \ - $(am__src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES_DIST) \ - src/settings/plugins/ifupdown/tests/test-ifupdown.c \ - src/settings/plugins/keyfile/tests/test-keyfile-settings.c \ - src/supplicant/tests/test-supplicant-config.c \ - $(src_tests_config_test_config_SOURCES) src/tests/test-core.c \ - src/tests/test-core-with-expect.c src/tests/test-dcb.c \ - src/tests/test-ip4-config.c src/tests/test-ip6-config.c \ - src/tests/test-l3cfg.c src/tests/test-systemd.c \ - src/tests/test-utils.c src/tests/test-wired-defname.c + $(src_core_NetworkManager_SOURCES) \ + $(src_core_NetworkManager_all_sym_SOURCES) \ + src/core/devices/bluetooth/tests/nm-bt-test.c \ + src/core/devices/tests/test-acd.c \ + src/core/devices/tests/test-lldp.c \ + $(am__src_core_devices_wifi_tests_test_devices_wifi_SOURCES_DIST) \ + $(am__src_core_devices_wwan_tests_test_service_providers_SOURCES_DIST) \ + $(src_core_dhcp_nm_dhcp_helper_SOURCES) \ + src/core/dhcp/tests/test-dhcp-dhclient.c \ + src/core/dhcp/tests/test-dhcp-utils.c \ + src/core/dnsmasq/tests/test-dnsmasq-utils.c \ + $(src_core_initrd_nm_initrd_generator_SOURCES) \ + src/core/initrd/tests/test-cmdline-reader.c \ + src/core/initrd/tests/test-dt-reader.c \ + src/core/initrd/tests/test-ibft-reader.c \ + src/core/ndisc/tests/test-ndisc-fake.c \ + src/core/ndisc/tests/test-ndisc-linux.c \ + $(src_core_nm_iface_helper_SOURCES) \ + src/core/platform/tests/monitor.c \ + $(src_core_platform_tests_test_address_fake_SOURCES) \ + $(src_core_platform_tests_test_address_linux_SOURCES) \ + $(src_core_platform_tests_test_cleanup_fake_SOURCES) \ + $(src_core_platform_tests_test_cleanup_linux_SOURCES) \ + $(src_core_platform_tests_test_link_fake_SOURCES) \ + $(src_core_platform_tests_test_link_linux_SOURCES) \ + src/core/platform/tests/test-nmp-object.c \ + src/core/platform/tests/test-platform-general.c \ + $(src_core_platform_tests_test_route_fake_SOURCES) \ + $(src_core_platform_tests_test_route_linux_SOURCES) \ + $(src_core_platform_tests_test_tc_fake_SOURCES) \ + $(src_core_platform_tests_test_tc_linux_SOURCES) \ + $(am__src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES_DIST) \ + src/core/settings/plugins/ifupdown/tests/test-ifupdown.c \ + src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c \ + src/core/supplicant/tests/test-supplicant-config.c \ + $(src_core_tests_config_test_config_SOURCES) \ + src/core/tests/test-core.c \ + src/core/tests/test-core-with-expect.c \ + src/core/tests/test-dcb.c src/core/tests/test-ip4-config.c \ + src/core/tests/test-ip6-config.c src/core/tests/test-l3cfg.c \ + src/core/tests/test-systemd.c src/core/tests/test-utils.c \ + src/core/tests/test-wired-defname.c RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ ctags-recursive dvi-recursive html-recursive info-recursive \ install-data-recursive install-dvi-recursive \ @@ -3476,8 +3522,8 @@ am__recursive_targets = \ AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ cscope check recheck distdir distdir-am dist dist-all \ distcheck -am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \ - $(LISP)config.h.in +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \ + config.h.in # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. @@ -4080,13 +4126,13 @@ noinst_LTLIBRARIES = shared/libcsiphash.la shared/libcrbtree.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm/nm-libnm-aux/libnm-libnm-aux.la \ introspection/libnmdbus.la libnm-core/libnm-core.la \ - $(libnm_crypto_lib) libnm/liblibnm.la \ - src/libNetworkManagerBase.la src/libNetworkManager.la \ - src/libnm-systemd-core.la $(NULL) \ - shared/systemd/libnm-systemd-logging-stub.la \ + $(libnm_crypto_lib) libnm/libnm_static.la \ + src/core/libNetworkManagerBase.la \ + src/core/libNetworkManager.la src/core/libnm-systemd-core.la \ + $(NULL) shared/systemd/libnm-systemd-logging-stub.la \ shared/systemd/libnm-systemd-shared.la \ - src/initrd/libnmi-core.la $(am__append_22) $(am__append_30) \ - $(am__append_37) $(am__append_42) \ + src/core/initrd/libnmi-core.la $(am__append_22) \ + $(am__append_30) $(am__append_37) $(am__append_42) \ dispatcher/libnm-dispatcher-core.la $(am__append_88) check_LTLIBRARIES = $(am__append_90) noinst_LIBRARIES = $(am__append_59) @@ -4098,16 +4144,17 @@ lib_LTLIBRARIES = libnm/libnm.la plugin_LTLIBRARIES = $(core_plugins) ############################################################################### -# src/devices/adsl +# src/core/devices/adsl ############################################################################### core_plugins = $(am__append_19) $(am__append_21) $(am__append_29) \ - src/devices/adsl/libnm-device-plugin-adsl.la $(am__append_33) \ - $(am__append_44) $(am__append_47) $(am__append_49) + src/core/devices/adsl/libnm-device-plugin-adsl.la \ + $(am__append_33) $(am__append_44) $(am__append_47) \ + $(am__append_49) service_DATA = man_MANS = $(am__append_82) examples_DATA = data/server.conf CLEANFILES = $(introspection_sources) $(DBUS_INTERFACE_DOCS) \ - src/NetworkManager.ver $(am__append_23) \ + src/core/NetworkManager.ver $(am__append_23) \ $(dispatcher_nmdbus_dispatcher_sources) \ dispatcher/org.freedesktop.nm_dispatcher.service \ $(am__append_68) clients/tests/test-client.log \ @@ -4140,7 +4187,7 @@ DISTCLEANFILES = config-extra.h intltool-extract intltool-merge \ ############################################################################### ############################################################################### -# src/devices +# src/core/devices ############################################################################### ############################################################################### @@ -4187,519 +4234,524 @@ EXTRA_DIST = shared/c-stdaux/src/c-stdaux.h $(NULL) \ tools/generate-docs-nm-settings-docs-merge.py \ tools/generate-docs-nm-settings-docs-gir.py libnm/meson.build \ libnm/nm-enum-types.c.template libnm/nm-enum-types.h.template \ - $(NULL) libnm/tests/meson.build src/systemd/meson.build \ - src/platform/linux/nl802154.h src/initrd/meson.build \ - src/initrd/tests/meson.build \ - src/initrd/tests/sysfs/class/net/eth0/address \ - src/initrd/tests/sysfs/class/net/eth2/address \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs/firmware/ibft/ethernet0/flags \ - src/initrd/tests/sysfs/firmware/ibft/initiator/isns-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/slp-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server \ - src/initrd/tests/sysfs/firmware/ibft/initiator/index \ - src/initrd/tests/sysfs/firmware/ibft/initiator/flags \ - src/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc \ - src/initrd/tests/sysfs/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/target0/chap-type \ - src/initrd/tests/sysfs/firmware/ibft/target0/index \ - src/initrd/tests/sysfs/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs/firmware/ibft/target0/flags \ - src/initrd/tests/sysfs/firmware/ibft/target0/port \ - src/initrd/tests/sysfs/firmware/ibft/target2/target-name \ - src/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc \ - src/initrd/tests/sysfs/firmware/ibft/target2/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/target2/chap-type \ - src/initrd/tests/sysfs/firmware/ibft/target2/index \ - src/initrd/tests/sysfs/firmware/ibft/target2/lun \ - src/initrd/tests/sysfs/firmware/ibft/target2/flags \ - src/initrd/tests/sysfs/firmware/ibft/target2/port \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id \ - src/initrd/tests/sysfs/firmware/ibft/acpi_header/signature \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/mac \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/origin \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/index \ - src/initrd/tests/sysfs/firmware/ibft/ethernet2/flags \ - src/initrd/tests/sysfs-bad-dns1/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-dns2/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-gateway/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-dhcp/class/net/eth0/address \ - src/initrd/tests/sysfs-dhcp/class/net/eth1/address \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun \ - src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address \ - src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name \ - src/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath \ - src/initrd/tests/sysfs-static/class/net/eth0/address \ - src/initrd/tests/sysfs-static/class/net/eth1/address \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-static/firmware/ibft/target0/port \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns \ - src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/target-name \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/index \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/lun \ - src/initrd/tests/sysfs-static/firmware/ibft/target1/port \ - src/initrd/tests/sysfs-vlan/class/net/eth0/address \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask \ - src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index \ - src/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/index \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun \ - src/initrd/tests/sysfs-vlan/firmware/ibft/target0/port $(NULL) \ - src/org.freedesktop.NetworkManager.conf \ - src/nm-test-utils-core.h src/meson.build src/dhcp/meson.build \ - src/dhcp/tests/test-dhclient-duid.leases \ - src/dhcp/tests/test-dhclient-commented-duid.leases \ - src/dhcp/tests/meson.build src/ppp/meson.build \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case \ - src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format \ - src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 \ - src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT \ - src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain \ - src/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing \ - src/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component \ - src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name \ - src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property \ - src/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property \ - src/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection \ - src/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config \ - src/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem \ - src/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem \ - src/settings/plugins/keyfile/tests/meson.build \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 \ - $(NULL) src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf \ - src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml \ - src/settings/plugins/ifcfg-rh/meson.build \ - src/settings/plugins/ifcfg-rh/tests/meson.build \ - src/settings/plugins/ifupdown/tests/test1 \ - src/settings/plugins/ifupdown/tests/test2 \ - src/settings/plugins/ifupdown/tests/test3 \ - src/settings/plugins/ifupdown/tests/test4 \ - src/settings/plugins/ifupdown/tests/test5 \ - src/settings/plugins/ifupdown/tests/test6 \ - src/settings/plugins/ifupdown/tests/test7 \ - src/settings/plugins/ifupdown/tests/test8 \ - src/settings/plugins/ifupdown/tests/test9 \ - src/settings/plugins/ifupdown/tests/test11 \ - src/settings/plugins/ifupdown/tests/test12 \ - src/settings/plugins/ifupdown/tests/test13 \ - src/settings/plugins/ifupdown/tests/test14 \ - src/settings/plugins/ifupdown/tests/test15 \ - src/settings/plugins/ifupdown/tests/test16 \ - src/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 \ - src/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 \ - src/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen \ - src/settings/plugins/ifupdown/tests/test20-source-stanza \ - src/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 \ - src/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 \ - src/settings/plugins/ifupdown/tests/test21-source-dir-stanza \ - src/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d \ - src/settings/plugins/ifupdown/tests/test22-duplicate-stanzas \ - src/settings/plugins/ifupdown/meson.build \ - src/settings/plugins/ifupdown/tests/meson.build \ - src/devices/meson.build src/devices/adsl/meson.build \ - src/devices/wwan/libnm-wwan.ver src/devices/wwan/meson.build \ - src/devices/wwan/tests/test-service-providers.xml $(NULL) \ - src/devices/bluetooth/meson.build $(NULL) \ - src/devices/wifi/meson.build $(NULL) \ - src/devices/team/meson.build data/NetworkManager-ovs.conf \ - src/devices/ovs/meson.build src/dnsmasq/tests/meson.build \ - src/platform/tests/meson.build $(NULL) \ - src/devices/tests/meson.build src/ndisc/tests/meson.build \ - src/supplicant/tests/certs/test-ca-cert.pem \ - src/supplicant/tests/certs/test-cert.p12 \ - src/supplicant/tests/meson.build \ - src/tests/config/NetworkManager.conf \ - src/tests/config/NetworkManager-warn.conf \ - src/tests/config/NetworkManager.state \ - src/tests/config/bad.conf \ - src/tests/config/global-dns-invalid.conf \ - src/tests/config/conf.d/00-overrides.conf \ - src/tests/config/conf.d/10-more.conf \ - src/tests/config/conf.d/20-config-enable-1.conf \ - src/tests/config/conf.d/90-last.conf \ - src/tests/config/meson.build src/tests/test-secret-agent.py \ - src/tests/meson.build dispatcher/nm-dispatcher.conf \ + $(NULL) libnm/tests/meson.build src/core/systemd/meson.build \ + src/core/platform/linux/nl802154.h src/core/initrd/meson.build \ + src/core/initrd/tests/meson.build \ + src/core/initrd/tests/sysfs/class/net/eth0/address \ + src/core/initrd/tests/sysfs/class/net/eth2/address \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/index \ + src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/index \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/lun \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/flags \ + src/core/initrd/tests/sysfs/firmware/ibft/target2/port \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id \ + src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index \ + src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags \ + src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address \ + src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun \ + src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address \ + src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name \ + src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath \ + src/core/initrd/tests/sysfs-static/class/net/eth0/address \ + src/core/initrd/tests/sysfs-static/class/net/eth1/address \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns \ + src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun \ + src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port \ + src/core/initrd/tests/sysfs-vlan/class/net/eth0/address \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun \ + src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port \ + $(NULL) src/core/org.freedesktop.NetworkManager.conf \ + src/core/nm-test-utils-core.h src/core/meson.build \ + src/core/dhcp/meson.build \ + src/core/dhcp/tests/test-dhclient-duid.leases \ + src/core/dhcp/tests/test-dhclient-commented-duid.leases \ + src/core/dhcp/tests/meson.build src/core/ppp/meson.build \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 \ + src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT \ + src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection \ + src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config \ + src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem \ + src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem \ + src/core/settings/plugins/keyfile/tests/meson.build \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 \ + $(NULL) src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf \ + src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml \ + src/core/settings/plugins/ifcfg-rh/meson.build \ + src/core/settings/plugins/ifcfg-rh/tests/meson.build \ + src/core/settings/plugins/ifupdown/tests/test1 \ + src/core/settings/plugins/ifupdown/tests/test2 \ + src/core/settings/plugins/ifupdown/tests/test3 \ + src/core/settings/plugins/ifupdown/tests/test4 \ + src/core/settings/plugins/ifupdown/tests/test5 \ + src/core/settings/plugins/ifupdown/tests/test6 \ + src/core/settings/plugins/ifupdown/tests/test7 \ + src/core/settings/plugins/ifupdown/tests/test8 \ + src/core/settings/plugins/ifupdown/tests/test9 \ + src/core/settings/plugins/ifupdown/tests/test11 \ + src/core/settings/plugins/ifupdown/tests/test12 \ + src/core/settings/plugins/ifupdown/tests/test13 \ + src/core/settings/plugins/ifupdown/tests/test14 \ + src/core/settings/plugins/ifupdown/tests/test15 \ + src/core/settings/plugins/ifupdown/tests/test16 \ + src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 \ + src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 \ + src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 \ + src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 \ + src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza \ + src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d \ + src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas \ + src/core/settings/plugins/ifupdown/meson.build \ + src/core/settings/plugins/ifupdown/tests/meson.build \ + src/core/devices/meson.build src/core/devices/adsl/meson.build \ + src/core/devices/wwan/libnm-wwan.ver \ + src/core/devices/wwan/meson.build \ + src/core/devices/wwan/tests/test-service-providers.xml $(NULL) \ + src/core/devices/bluetooth/meson.build $(NULL) \ + src/core/devices/wifi/meson.build $(NULL) \ + src/core/devices/team/meson.build data/NetworkManager-ovs.conf \ + src/core/devices/ovs/meson.build \ + src/core/dnsmasq/tests/meson.build \ + src/core/platform/tests/meson.build $(NULL) \ + src/core/devices/tests/meson.build \ + src/core/ndisc/tests/meson.build \ + src/core/supplicant/tests/certs/test-ca-cert.pem \ + src/core/supplicant/tests/certs/test-cert.p12 \ + src/core/supplicant/tests/meson.build \ + src/core/tests/config/NetworkManager.conf \ + src/core/tests/config/NetworkManager-warn.conf \ + src/core/tests/config/NetworkManager.state \ + src/core/tests/config/bad.conf \ + src/core/tests/config/global-dns-invalid.conf \ + src/core/tests/config/conf.d/00-overrides.conf \ + src/core/tests/config/conf.d/10-more.conf \ + src/core/tests/config/conf.d/20-config-enable-1.conf \ + src/core/tests/config/conf.d/90-last.conf \ + src/core/tests/config/meson.build \ + src/core/tests/test-secret-agent.py src/core/tests/meson.build \ + dispatcher/nm-dispatcher.conf \ dispatcher/org.freedesktop.nm_dispatcher.service.in \ dispatcher/nm-dispatcher.xml dispatcher/meson.build \ dispatcher/tests/dispatcher-connectivity-full \ @@ -4742,11 +4794,11 @@ EXTRA_DIST = shared/c-stdaux/src/c-stdaux.h $(NULL) \ CONTRIBUTING COPYING.LGPL COPYING.GFDL NetworkManager.pc.in \ intltool-extract.in intltool-merge.in intltool-update.in \ linker-script-binary.ver linker-script-devices.ver \ - linker-script-settings.ver src/ppp/nm-ppp-plugin.ver \ + linker-script-settings.ver src/core/ppp/nm-ppp-plugin.ver \ Makefile.glib autogen.sh lsan.suppressions \ valgrind.suppressions meson.build meson_options.txt \ config.h.meson config-extra.h.meson docs/meson.build \ - po/meson.build shared/nm-default.h \ + po/meson.build shared/nm-default.h shared/nm-gassert-patch.h \ shared/nm-test-libnm-utils.h shared/nm-test-utils-impl.c \ shared/nm-utils/nm-compat.c shared/nm-utils/nm-compat.h \ shared/nm-utils/nm-test-utils.h \ @@ -4762,7 +4814,7 @@ EXTRA_DIST = shared/c-stdaux/src/c-stdaux.h $(NULL) \ tools/meson-post-install.sh tools/run-nm-test.sh \ tools/test-networkmanager-service.py \ tools/test-sudo-wrapper.sh tools/enums-to-docbook.pl \ - src/settings/plugins/meson.build $(NULL) \ + src/core/settings/plugins/meson.build $(NULL) \ examples/C/glib/meson.build \ examples/C/qt/add-connection-wired.cpp \ examples/C/qt/list-connections.cpp \ @@ -4853,19 +4905,19 @@ man_pages_autogen = man/nm-settings-dbus.5 man/nm-settings-keyfile.5 \ ############################################################################### ############################################################################### -# src/settings/plugins/keyfile/tests +# src/core/settings/plugins/keyfile/tests ############################################################################### ############################################################################### -# src/dnsmasq/tests +# src/core/dnsmasq/tests ############################################################################### ############################################################################### -# src/supplicant/tests +# src/core/supplicant/tests ############################################################################### ############################################################################### -# src/tests/config +# src/core/tests/config ############################################################################### ############################################################################### @@ -4879,38 +4931,40 @@ check_programs = shared/nm-platform/tests/test-nm-platform \ libnm-core/tests/test-general libnm-core/tests/test-keyfile \ libnm-core/tests/test-secrets libnm-core/tests/test-setting \ libnm-core/tests/test-settings-defaults libnm/tests/test-libnm \ - $(am__append_15) src/initrd/tests/test-dt-reader \ - src/initrd/tests/test-ibft-reader \ - src/initrd/tests/test-cmdline-reader \ - src/dhcp/tests/test-dhcp-dhclient \ - src/dhcp/tests/test-dhcp-utils \ - src/settings/plugins/keyfile/tests/test-keyfile-settings \ + $(am__append_15) src/core/initrd/tests/test-dt-reader \ + src/core/initrd/tests/test-ibft-reader \ + src/core/initrd/tests/test-cmdline-reader \ + src/core/dhcp/tests/test-dhcp-dhclient \ + src/core/dhcp/tests/test-dhcp-utils \ + src/core/settings/plugins/keyfile/tests/test-keyfile-settings \ $(am__append_25) $(am__append_32) $(am__append_36) \ - $(am__append_46) src/dnsmasq/tests/test-dnsmasq-utils \ - src/platform/tests/test-address-fake \ - src/platform/tests/test-address-linux \ - src/platform/tests/test-cleanup-fake \ - src/platform/tests/test-cleanup-linux \ - src/platform/tests/test-link-fake \ - src/platform/tests/test-link-linux \ - src/platform/tests/test-nmp-object \ - src/platform/tests/test-platform-general \ - src/platform/tests/test-route-fake \ - src/platform/tests/test-route-linux \ - src/platform/tests/test-tc-fake \ - src/platform/tests/test-tc-linux $(NULL) \ - src/devices/tests/test-lldp src/devices/tests/test-acd \ - src/ndisc/tests/test-ndisc-fake \ - src/supplicant/tests/test-supplicant-config \ - src/tests/config/test-config src/tests/test-core \ - src/tests/test-core-with-expect src/tests/test-dcb \ - src/tests/test-ip4-config src/tests/test-ip6-config \ - src/tests/test-l3cfg src/tests/test-systemd \ - src/tests/test-utils src/tests/test-wired-defname $(NULL) \ - dispatcher/tests/test-dispatcher-envp $(am__append_54) \ + $(am__append_46) src/core/dnsmasq/tests/test-dnsmasq-utils \ + src/core/platform/tests/test-address-fake \ + src/core/platform/tests/test-address-linux \ + src/core/platform/tests/test-cleanup-fake \ + src/core/platform/tests/test-cleanup-linux \ + src/core/platform/tests/test-link-fake \ + src/core/platform/tests/test-link-linux \ + src/core/platform/tests/test-nmp-object \ + src/core/platform/tests/test-platform-general \ + src/core/platform/tests/test-route-fake \ + src/core/platform/tests/test-route-linux \ + src/core/platform/tests/test-tc-fake \ + src/core/platform/tests/test-tc-linux $(NULL) \ + src/core/devices/tests/test-lldp \ + src/core/devices/tests/test-acd \ + src/core/ndisc/tests/test-ndisc-fake \ + src/core/supplicant/tests/test-supplicant-config \ + src/core/tests/config/test-config src/core/tests/test-core \ + src/core/tests/test-core-with-expect src/core/tests/test-dcb \ + src/core/tests/test-ip4-config src/core/tests/test-ip6-config \ + src/core/tests/test-l3cfg src/core/tests/test-systemd \ + src/core/tests/test-utils src/core/tests/test-wired-defname \ + $(NULL) dispatcher/tests/test-dispatcher-envp $(am__append_54) \ $(am__append_55) clients/common/tests/test-libnm-core-aux check_programs_norun = $(am__append_16) $(am__append_41) \ - src/platform/tests/monitor src/ndisc/tests/test-ndisc-linux \ + src/core/platform/tests/monitor \ + src/core/ndisc/tests/test-ndisc-linux \ examples/C/glib/add-connection-gdbus \ examples/C/glib/add-connection-libnm \ examples/C/glib/get-active-connections-gdbus \ @@ -4927,8 +4981,8 @@ check_programs_norun = $(am__append_16) $(am__append_41) \ ############################################################################### check_ltlibraries = $(am__append_6) $(am__append_7) \ libnm/tests/libnm-vpn-plugin-utils-test.la \ - src/libNetworkManagerTest.la clients/common/libnmc-base.la \ - $(am__append_52) + src/core/libNetworkManagerTest.la \ + clients/common/libnmc-base.la $(am__append_52) check_local = check-docs check-local-exports-libnm $(am__append_12) \ check-config-options $(am__append_24) $(am__append_31) \ check-local-devices-adsl $(am__append_35) $(am__append_45) \ @@ -4937,7 +4991,7 @@ check_local = check-docs check-local-exports-libnm $(am__append_12) \ $(am__append_93) check-po-msgfmt VAPIGEN_VAPIS = $(am__append_84) dbusservice_DATA = $(am__append_28) dispatcher/nm-dispatcher.conf \ - src/org.freedesktop.NetworkManager.conf + src/core/org.freedesktop.NetworkManager.conf dbusactivation_DATA = \ dispatcher/org.freedesktop.nm_dispatcher.service systemdsystemunit_DATA = $(am__append_64) $(am__append_69) @@ -4981,8 +5035,8 @@ set_sanitizer_env = \ fi check_so_symbols = \ - $(call set_sanitizer_env,$(1),$(builddir)/src/NetworkManager); \ - LD_BIND_NOW=1 LD_PRELOAD=$${LD_PRELOAD}:$(1) $(builddir)/src/NetworkManager --version >/dev/null + $(call set_sanitizer_env,$(1),$(builddir)/src/core/NetworkManager); \ + LD_BIND_NOW=1 LD_PRELOAD=$${LD_PRELOAD}:$(1) $(builddir)/src/core/NetworkManager --version >/dev/null ############################################################################### @@ -6132,21 +6186,21 @@ libnm_lib_cppflags = \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM \ $(NULL) -libnm_liblibnm_la_CPPFLAGS = \ +libnm_libnm_static_la_CPPFLAGS = \ $(INTROSPECTION_CFLAGS) \ $(libnm_lib_cppflags) \ $(NULL) -libnm_liblibnm_la_SOURCES = \ +libnm_libnm_static_la_SOURCES = \ $(libnm_lib_c_real) \ $(NULL) -nodist_libnm_liblibnm_la_SOURCES = \ +nodist_libnm_libnm_static_la_SOURCES = \ $(libnm_lib_h_pub_mkenums) \ $(libnm_lib_c_mkenums) \ $(NULL) -libnm_liblibnm_la_LIBADD = \ +libnm_libnm_static_la_LIBADD = \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -6187,7 +6241,7 @@ EXTRA_libnm_libnm_la_DEPENDENCIES = \ libnm/libnm.ver libnm_libnm_la_LIBADD = \ - libnm/liblibnm.la \ + libnm/libnm_static.la \ $(NULL) libnm_libnm_la_LDFLAGS = \ @@ -6241,7 +6295,7 @@ libnm_tests_cppflags = \ $(NULL) libnm_tests_ldadd = \ - libnm/liblibnm.la \ + libnm/libnm_static.la \ $(GLIB_LIBS) \ $(NULL) @@ -6305,25 +6359,25 @@ libnm_tests_libnm_vpn_plugin_utils_test_la_LIBADD = \ ############################################################################### -# src/ +# src/core/ ############################################################################### -src_cppflags_base = \ - -I$(srcdir)/src \ +src_core_cppflags_base = \ + -I$(srcdir)/src/core \ $(dflt_cppflags_libnm_core) \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON \ $(SANITIZER_EXEC_CFLAGS) \ $(NULL) -src_cppflags_base_test = \ - $(src_cppflags_base) \ +src_core_cppflags_base_test = \ + $(src_core_cppflags_base) \ -DNETWORKMANAGER_COMPILATION_TEST \ $(NULL) -src_cppflags_device_plugin = \ - $(src_cppflags_base) +src_core_cppflags_device_plugin = \ + $(src_core_cppflags_base) -src_cppflags = \ - $(src_cppflags_base) \ +src_core_cppflags = \ + $(src_core_cppflags_base) \ \ $(LIBNDP_CFLAGS) \ $(LIBPSL_CFLAGS) \ @@ -6335,9 +6389,9 @@ src_cppflags = \ \ $(NULL) -src_cppflags_test = $(src_cppflags) -DNETWORKMANAGER_COMPILATION_TEST \ - $(NULL) $(am__append_17) -src_ldflags = $(CODE_COVERAGE_LDFLAGS) +src_core_cppflags_test = $(src_core_cppflags) \ + -DNETWORKMANAGER_COMPILATION_TEST $(NULL) $(am__append_17) +src_core_ldflags = $(CODE_COVERAGE_LDFLAGS) ############################################################################### libsystemd_cppflags = \ @@ -6511,321 +6565,328 @@ shared_systemd_libnm_systemd_shared_la_LIBADD = \ ############################################################################### -src_libnm_systemd_core_la_cppflags = \ +src_core_libnm_systemd_core_la_cppflags = \ $(libsystemd_cppflags) \ -I$(srcdir)/libnm-core \ -I$(builddir)/libnm-core \ - -I$(srcdir)/src \ - -I$(srcdir)/src/systemd/sd-adapt-core \ - -I$(srcdir)/src/systemd/src/systemd \ - -I$(srcdir)/src/systemd/src/libsystemd-network \ - -I$(srcdir)/src/systemd/src/libsystemd/sd-event \ + -I$(srcdir)/src/core \ + -I$(srcdir)/src/core/systemd/sd-adapt-core \ + -I$(srcdir)/src/core/systemd/src/systemd \ + -I$(srcdir)/src/core/systemd/src/libsystemd-network \ + -I$(srcdir)/src/core/systemd/src/libsystemd/sd-event \ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD \ $(NULL) -src_libnm_systemd_core_la_libadd = \ +src_core_libnm_systemd_core_la_libadd = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) -src_libnm_systemd_core_la_SOURCES = \ - src/systemd/nm-sd-utils-core.c \ - src/systemd/nm-sd-utils-core.h \ - src/systemd/nm-sd.c \ - src/systemd/nm-sd.h \ - src/systemd/nm-sd-utils-dhcp.h \ - src/systemd/nm-sd-utils-dhcp.c \ - src/systemd/sd-adapt-core/condition.h \ - src/systemd/sd-adapt-core/conf-parser.h \ - src/systemd/sd-adapt-core/device-util.h \ - src/systemd/sd-adapt-core/khash.h \ - src/systemd/sd-adapt-core/network-util.h \ - src/systemd/sd-adapt-core/nm-sd-adapt-core.c \ - src/systemd/sd-adapt-core/nm-sd-adapt-core.h \ - src/systemd/sd-adapt-core/sd-daemon.h \ - src/systemd/sd-adapt-core/sd-device.h \ - src/systemd/sd-adapt-core/udev-util.h \ - src/systemd/src/libsystemd-network/arp-util.c \ - src/systemd/src/libsystemd-network/arp-util.h \ - src/systemd/src/libsystemd-network/dhcp-identifier.c \ - src/systemd/src/libsystemd-network/dhcp-identifier.h \ - src/systemd/src/libsystemd-network/dhcp-internal.h \ - src/systemd/src/libsystemd-network/dhcp-lease-internal.h \ - src/systemd/src/libsystemd-network/dhcp-network.c \ - src/systemd/src/libsystemd-network/dhcp-option.c \ - src/systemd/src/libsystemd-network/dhcp-packet.c \ - src/systemd/src/libsystemd-network/dhcp-protocol.h \ - src/systemd/src/libsystemd-network/dhcp6-internal.h \ - src/systemd/src/libsystemd-network/dhcp6-lease-internal.h \ - src/systemd/src/libsystemd-network/dhcp6-network.c \ - src/systemd/src/libsystemd-network/dhcp6-option.c \ - src/systemd/src/libsystemd-network/dhcp6-protocol.h \ - src/systemd/src/libsystemd-network/lldp-internal.h \ - src/systemd/src/libsystemd-network/lldp-neighbor.c \ - src/systemd/src/libsystemd-network/lldp-neighbor.h \ - src/systemd/src/libsystemd-network/lldp-network.c \ - src/systemd/src/libsystemd-network/lldp-network.h \ - src/systemd/src/libsystemd-network/network-internal.c \ - src/systemd/src/libsystemd-network/network-internal.h \ - src/systemd/src/libsystemd-network/sd-dhcp-client.c \ - src/systemd/src/libsystemd-network/sd-dhcp-lease.c \ - src/systemd/src/libsystemd-network/sd-dhcp6-client.c \ - src/systemd/src/libsystemd-network/sd-dhcp6-lease.c \ - src/systemd/src/libsystemd-network/sd-ipv4acd.c \ - src/systemd/src/libsystemd-network/sd-ipv4ll.c \ - src/systemd/src/libsystemd-network/sd-lldp.c \ - src/systemd/src/libsystemd/sd-event/event-source.h \ - src/systemd/src/libsystemd/sd-event/event-util.c \ - src/systemd/src/libsystemd/sd-event/event-util.h \ - src/systemd/src/libsystemd/sd-event/sd-event.c \ - src/systemd/src/libsystemd/sd-id128/id128-util.c \ - src/systemd/src/libsystemd/sd-id128/id128-util.h \ - src/systemd/src/libsystemd/sd-id128/sd-id128.c \ - src/systemd/src/systemd/_sd-common.h \ - src/systemd/src/systemd/sd-dhcp-client.h \ - src/systemd/src/systemd/sd-dhcp-lease.h \ - src/systemd/src/systemd/sd-dhcp-option.h \ - src/systemd/src/systemd/sd-dhcp6-client.h \ - src/systemd/src/systemd/sd-dhcp6-lease.h \ - src/systemd/src/systemd/sd-dhcp6-option.h \ - src/systemd/src/systemd/sd-event.h \ - src/systemd/src/systemd/sd-id128.h \ - src/systemd/src/systemd/sd-ipv4acd.h \ - src/systemd/src/systemd/sd-ipv4ll.h \ - src/systemd/src/systemd/sd-lldp.h \ - src/systemd/src/systemd/sd-ndisc.h \ +src_core_libnm_systemd_core_la_SOURCES = \ + src/core/systemd/nm-sd-utils-core.c \ + src/core/systemd/nm-sd-utils-core.h \ + src/core/systemd/nm-sd.c \ + src/core/systemd/nm-sd.h \ + src/core/systemd/nm-sd-utils-dhcp.h \ + src/core/systemd/nm-sd-utils-dhcp.c \ + src/core/systemd/sd-adapt-core/condition.h \ + src/core/systemd/sd-adapt-core/conf-parser.h \ + src/core/systemd/sd-adapt-core/device-util.h \ + src/core/systemd/sd-adapt-core/khash.h \ + src/core/systemd/sd-adapt-core/network-util.h \ + src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c \ + src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h \ + src/core/systemd/sd-adapt-core/sd-daemon.h \ + src/core/systemd/sd-adapt-core/sd-device.h \ + src/core/systemd/sd-adapt-core/udev-util.h \ + src/core/systemd/src/libsystemd-network/arp-util.c \ + src/core/systemd/src/libsystemd-network/arp-util.h \ + src/core/systemd/src/libsystemd-network/dhcp-identifier.c \ + src/core/systemd/src/libsystemd-network/dhcp-identifier.h \ + src/core/systemd/src/libsystemd-network/dhcp-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp-network.c \ + src/core/systemd/src/libsystemd-network/dhcp-option.c \ + src/core/systemd/src/libsystemd-network/dhcp-packet.c \ + src/core/systemd/src/libsystemd-network/dhcp-protocol.h \ + src/core/systemd/src/libsystemd-network/dhcp6-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h \ + src/core/systemd/src/libsystemd-network/dhcp6-network.c \ + src/core/systemd/src/libsystemd-network/dhcp6-option.c \ + src/core/systemd/src/libsystemd-network/dhcp6-protocol.h \ + src/core/systemd/src/libsystemd-network/lldp-internal.h \ + src/core/systemd/src/libsystemd-network/lldp-neighbor.c \ + src/core/systemd/src/libsystemd-network/lldp-neighbor.h \ + src/core/systemd/src/libsystemd-network/lldp-network.c \ + src/core/systemd/src/libsystemd-network/lldp-network.h \ + src/core/systemd/src/libsystemd-network/network-internal.c \ + src/core/systemd/src/libsystemd-network/network-internal.h \ + src/core/systemd/src/libsystemd-network/sd-dhcp-client.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c \ + src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c \ + src/core/systemd/src/libsystemd-network/sd-ipv4acd.c \ + src/core/systemd/src/libsystemd-network/sd-ipv4ll.c \ + src/core/systemd/src/libsystemd-network/sd-lldp.c \ + src/core/systemd/src/libsystemd/sd-event/event-source.h \ + src/core/systemd/src/libsystemd/sd-event/event-util.c \ + src/core/systemd/src/libsystemd/sd-event/event-util.h \ + src/core/systemd/src/libsystemd/sd-event/sd-event.c \ + src/core/systemd/src/libsystemd/sd-id128/id128-util.c \ + src/core/systemd/src/libsystemd/sd-id128/id128-util.h \ + src/core/systemd/src/libsystemd/sd-id128/sd-id128.c \ + src/core/systemd/src/systemd/_sd-common.h \ + src/core/systemd/src/systemd/sd-dhcp-client.h \ + src/core/systemd/src/systemd/sd-dhcp-lease.h \ + src/core/systemd/src/systemd/sd-dhcp-option.h \ + src/core/systemd/src/systemd/sd-dhcp6-client.h \ + src/core/systemd/src/systemd/sd-dhcp6-lease.h \ + src/core/systemd/src/systemd/sd-dhcp6-option.h \ + src/core/systemd/src/systemd/sd-event.h \ + src/core/systemd/src/systemd/sd-id128.h \ + src/core/systemd/src/systemd/sd-ipv4acd.h \ + src/core/systemd/src/systemd/sd-ipv4ll.h \ + src/core/systemd/src/systemd/sd-lldp.h \ + src/core/systemd/src/systemd/sd-ndisc.h \ $(NULL) -src_libnm_systemd_core_la_CPPFLAGS = $(src_libnm_systemd_core_la_cppflags) -src_libnm_systemd_core_la_LIBADD = \ +src_core_libnm_systemd_core_la_CPPFLAGS = $(src_core_libnm_systemd_core_la_cppflags) +src_core_libnm_systemd_core_la_LIBADD = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) ############################################################################### -src_libNetworkManagerBase_la_CPPFLAGS = \ +src_core_libNetworkManagerBase_la_CPPFLAGS = \ $(libsystemd_cppflags) \ - $(src_cppflags) - -src_libNetworkManagerBase_la_SOURCES = src/nm-core-utils.c \ - src/nm-core-utils.h src/NetworkManagerUtils.c \ - src/NetworkManagerUtils.h src/platform/nmp-object.c \ - src/platform/nmp-object.h src/platform/nm-platform.c \ - src/platform/nm-platform.h src/platform/nm-platform-private.h \ - src/platform/nm-linux-platform.c \ - src/platform/nm-linux-platform.h \ - src/platform/nmp-rules-manager.c \ - src/platform/nmp-rules-manager.h \ - src/platform/wifi/nm-wifi-utils-nl80211.c \ - src/platform/wifi/nm-wifi-utils-nl80211.h \ - src/platform/wifi/nm-wifi-utils-private.h \ - src/platform/wifi/nm-wifi-utils.c \ - src/platform/wifi/nm-wifi-utils.h \ - src/platform/wpan/nm-wpan-utils.c \ - src/platform/wpan/nm-wpan-utils.h src/ndisc/nm-lndp-ndisc.c \ - src/ndisc/nm-lndp-ndisc.h src/ndisc/nm-ndisc.c \ - src/ndisc/nm-ndisc.h src/ndisc/nm-ndisc-private.h \ - src/nm-dbus-utils.c src/nm-dbus-utils.h src/nm-dbus-object.c \ - src/nm-dbus-object.h src/nm-netns.c src/nm-netns.h \ - src/nm-l3-config-data.c src/nm-l3-config-data.h \ - src/nm-l3-ipv4ll.c src/nm-l3-ipv4ll.h src/nm-l3cfg.c \ - src/nm-l3cfg.h src/nm-ip-config.c src/nm-ip-config.h \ - src/nm-ip4-config.c src/nm-ip4-config.h src/nm-ip6-config.c \ - src/nm-ip6-config.h src/dhcp/nm-dhcp-client.c \ - src/dhcp/nm-dhcp-client.h src/dhcp/nm-dhcp-client-logging.h \ - src/dhcp/nm-dhcp-nettools.c src/dhcp/nm-dhcp-utils.c \ - src/dhcp/nm-dhcp-utils.h src/dhcp/nm-dhcp-options.c \ - src/dhcp/nm-dhcp-options.h src/dhcp/nm-dhcp-systemd.c \ - src/dhcp/nm-dhcp-manager.c src/dhcp/nm-dhcp-manager.h \ - src/main-utils.c src/main-utils.h $(NULL) $(am__append_18) -src_libNetworkManagerBase_la_LIBADD = \ + $(src_core_cppflags) + +src_core_libNetworkManagerBase_la_SOURCES = src/core/nm-core-utils.c \ + src/core/nm-core-utils.h src/core/NetworkManagerUtils.c \ + src/core/NetworkManagerUtils.h src/core/platform/nmp-object.c \ + src/core/platform/nmp-object.h src/core/platform/nm-platform.c \ + src/core/platform/nm-platform.h \ + src/core/platform/nm-platform-private.h \ + src/core/platform/nm-linux-platform.c \ + src/core/platform/nm-linux-platform.h \ + src/core/platform/nmp-rules-manager.c \ + src/core/platform/nmp-rules-manager.h \ + src/core/platform/wifi/nm-wifi-utils-nl80211.c \ + src/core/platform/wifi/nm-wifi-utils-nl80211.h \ + src/core/platform/wifi/nm-wifi-utils-private.h \ + src/core/platform/wifi/nm-wifi-utils.c \ + src/core/platform/wifi/nm-wifi-utils.h \ + src/core/platform/wpan/nm-wpan-utils.c \ + src/core/platform/wpan/nm-wpan-utils.h \ + src/core/ndisc/nm-lndp-ndisc.c src/core/ndisc/nm-lndp-ndisc.h \ + src/core/ndisc/nm-ndisc.c src/core/ndisc/nm-ndisc.h \ + src/core/ndisc/nm-ndisc-private.h src/core/nm-dbus-utils.c \ + src/core/nm-dbus-utils.h src/core/nm-dbus-object.c \ + src/core/nm-dbus-object.h src/core/nm-netns.c \ + src/core/nm-netns.h src/core/nm-l3-config-data.c \ + src/core/nm-l3-config-data.h src/core/nm-l3-ipv4ll.c \ + src/core/nm-l3-ipv4ll.h src/core/nm-l3cfg.c \ + src/core/nm-l3cfg.h src/core/nm-ip-config.c \ + src/core/nm-ip-config.h src/core/nm-ip4-config.c \ + src/core/nm-ip4-config.h src/core/nm-ip6-config.c \ + src/core/nm-ip6-config.h src/core/dhcp/nm-dhcp-client.c \ + src/core/dhcp/nm-dhcp-client.h \ + src/core/dhcp/nm-dhcp-client-logging.h \ + src/core/dhcp/nm-dhcp-nettools.c src/core/dhcp/nm-dhcp-utils.c \ + src/core/dhcp/nm-dhcp-utils.h src/core/dhcp/nm-dhcp-options.c \ + src/core/dhcp/nm-dhcp-options.h \ + src/core/dhcp/nm-dhcp-systemd.c \ + src/core/dhcp/nm-dhcp-manager.c \ + src/core/dhcp/nm-dhcp-manager.h src/core/main-utils.c \ + src/core/main-utils.h $(NULL) $(am__append_18) +src_core_libNetworkManagerBase_la_LIBADD = \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) \ $(NULL) ############################################################################### -src_libNetworkManager_la_CPPFLAGS = $(src_cppflags) -src_libNetworkManager_la_SOURCES = \ +src_core_libNetworkManager_la_CPPFLAGS = $(src_core_cppflags) +src_core_libNetworkManager_la_SOURCES = \ \ - src/nm-checkpoint.c \ - src/nm-checkpoint.h \ - src/nm-checkpoint-manager.c \ - src/nm-checkpoint-manager.h \ + src/core/nm-checkpoint.c \ + src/core/nm-checkpoint.h \ + src/core/nm-checkpoint-manager.c \ + src/core/nm-checkpoint-manager.h \ \ - src/devices/nm-acd-manager.c \ - src/devices/nm-acd-manager.h \ - src/devices/nm-lldp-listener.c \ - src/devices/nm-lldp-listener.h \ - src/devices/nm-device.c \ - src/devices/nm-device.h \ - src/devices/nm-device-ethernet-utils.c \ - src/devices/nm-device-ethernet-utils.h \ - src/devices/nm-device-factory.c \ - src/devices/nm-device-factory.h \ - src/devices/nm-device-generic.c \ - src/devices/nm-device-generic.h \ - src/devices/nm-device-logging.h \ - src/devices/nm-device-private.h \ + src/core/devices/nm-acd-manager.c \ + src/core/devices/nm-acd-manager.h \ + src/core/devices/nm-lldp-listener.c \ + src/core/devices/nm-lldp-listener.h \ + src/core/devices/nm-device.c \ + src/core/devices/nm-device.h \ + src/core/devices/nm-device-ethernet-utils.c \ + src/core/devices/nm-device-ethernet-utils.h \ + src/core/devices/nm-device-factory.c \ + src/core/devices/nm-device-factory.h \ + src/core/devices/nm-device-generic.c \ + src/core/devices/nm-device-generic.h \ + src/core/devices/nm-device-logging.h \ + src/core/devices/nm-device-private.h \ \ - src/devices/nm-device-6lowpan.c \ - src/devices/nm-device-6lowpan.h \ - src/devices/nm-device-bond.c \ - src/devices/nm-device-bond.h \ - src/devices/nm-device-bridge.c \ - src/devices/nm-device-bridge.h \ - src/devices/nm-device-dummy.c \ - src/devices/nm-device-dummy.h \ - src/devices/nm-device-ethernet.c \ - src/devices/nm-device-ethernet.h \ - src/devices/nm-device-infiniband.c \ - src/devices/nm-device-infiniband.h \ - src/devices/nm-device-ip-tunnel.c \ - src/devices/nm-device-ip-tunnel.h \ - src/devices/nm-device-macsec.c \ - src/devices/nm-device-macsec.h \ - src/devices/nm-device-macvlan.c \ - src/devices/nm-device-macvlan.h \ - src/devices/nm-device-ppp.c \ - src/devices/nm-device-ppp.h \ - src/devices/nm-device-tun.c \ - src/devices/nm-device-tun.h \ - src/devices/nm-device-veth.c \ - src/devices/nm-device-veth.h \ - src/devices/nm-device-vlan.c \ - src/devices/nm-device-vlan.h \ - src/devices/nm-device-vrf.c \ - src/devices/nm-device-vrf.h \ - src/devices/nm-device-vxlan.c \ - src/devices/nm-device-vxlan.h \ - src/devices/nm-device-wireguard.c \ - src/devices/nm-device-wireguard.h \ - src/devices/nm-device-wpan.c \ - src/devices/nm-device-wpan.h \ + src/core/devices/nm-device-6lowpan.c \ + src/core/devices/nm-device-6lowpan.h \ + src/core/devices/nm-device-bond.c \ + src/core/devices/nm-device-bond.h \ + src/core/devices/nm-device-bridge.c \ + src/core/devices/nm-device-bridge.h \ + src/core/devices/nm-device-dummy.c \ + src/core/devices/nm-device-dummy.h \ + src/core/devices/nm-device-ethernet.c \ + src/core/devices/nm-device-ethernet.h \ + src/core/devices/nm-device-infiniband.c \ + src/core/devices/nm-device-infiniband.h \ + src/core/devices/nm-device-ip-tunnel.c \ + src/core/devices/nm-device-ip-tunnel.h \ + src/core/devices/nm-device-macsec.c \ + src/core/devices/nm-device-macsec.h \ + src/core/devices/nm-device-macvlan.c \ + src/core/devices/nm-device-macvlan.h \ + src/core/devices/nm-device-ppp.c \ + src/core/devices/nm-device-ppp.h \ + src/core/devices/nm-device-tun.c \ + src/core/devices/nm-device-tun.h \ + src/core/devices/nm-device-veth.c \ + src/core/devices/nm-device-veth.h \ + src/core/devices/nm-device-vlan.c \ + src/core/devices/nm-device-vlan.h \ + src/core/devices/nm-device-vrf.c \ + src/core/devices/nm-device-vrf.h \ + src/core/devices/nm-device-vxlan.c \ + src/core/devices/nm-device-vxlan.h \ + src/core/devices/nm-device-wireguard.c \ + src/core/devices/nm-device-wireguard.h \ + src/core/devices/nm-device-wpan.c \ + src/core/devices/nm-device-wpan.h \ \ - src/dhcp/nm-dhcp-dhcpcanon.c \ - src/dhcp/nm-dhcp-dhclient.c \ - src/dhcp/nm-dhcp-dhcpcd.c \ - src/dhcp/nm-dhcp-helper-api.h \ - src/dhcp/nm-dhcp-listener.c \ - src/dhcp/nm-dhcp-listener.h \ - src/dhcp/nm-dhcp-dhclient-utils.c \ - src/dhcp/nm-dhcp-dhclient-utils.h \ + src/core/dhcp/nm-dhcp-dhcpcanon.c \ + src/core/dhcp/nm-dhcp-dhclient.c \ + src/core/dhcp/nm-dhcp-dhcpcd.c \ + src/core/dhcp/nm-dhcp-helper-api.h \ + src/core/dhcp/nm-dhcp-listener.c \ + src/core/dhcp/nm-dhcp-listener.h \ + src/core/dhcp/nm-dhcp-dhclient-utils.c \ + src/core/dhcp/nm-dhcp-dhclient-utils.h \ \ - src/dns/nm-dns-manager.c \ - src/dns/nm-dns-manager.h \ - src/dns/nm-dns-plugin.c \ - src/dns/nm-dns-plugin.h \ - src/dns/nm-dns-dnsmasq.c \ - src/dns/nm-dns-dnsmasq.h \ - src/dns/nm-dns-systemd-resolved.c \ - src/dns/nm-dns-systemd-resolved.h \ - src/dns/nm-dns-unbound.c \ - src/dns/nm-dns-unbound.h \ + src/core/dns/nm-dns-manager.c \ + src/core/dns/nm-dns-manager.h \ + src/core/dns/nm-dns-plugin.c \ + src/core/dns/nm-dns-plugin.h \ + src/core/dns/nm-dns-dnsmasq.c \ + src/core/dns/nm-dns-dnsmasq.h \ + src/core/dns/nm-dns-systemd-resolved.c \ + src/core/dns/nm-dns-systemd-resolved.h \ + src/core/dns/nm-dns-unbound.c \ + src/core/dns/nm-dns-unbound.h \ \ - src/dnsmasq/nm-dnsmasq-manager.c \ - src/dnsmasq/nm-dnsmasq-manager.h \ - src/dnsmasq/nm-dnsmasq-utils.c \ - src/dnsmasq/nm-dnsmasq-utils.h \ + src/core/dnsmasq/nm-dnsmasq-manager.c \ + src/core/dnsmasq/nm-dnsmasq-manager.h \ + src/core/dnsmasq/nm-dnsmasq-utils.c \ + src/core/dnsmasq/nm-dnsmasq-utils.h \ \ - src/ppp/nm-ppp-manager-call.c \ - src/ppp/nm-ppp-manager-call.h \ - src/ppp/nm-ppp-manager.h \ - src/ppp/nm-ppp-status.h \ + src/core/ppp/nm-ppp-manager-call.c \ + src/core/ppp/nm-ppp-manager-call.h \ + src/core/ppp/nm-ppp-manager.h \ + src/core/ppp/nm-ppp-status.h \ \ - src/nm-hostname-manager.c \ - src/nm-hostname-manager.h \ + src/core/nm-hostname-manager.c \ + src/core/nm-hostname-manager.h \ \ - src/settings/nm-agent-manager.c \ - src/settings/nm-agent-manager.h \ - src/settings/nm-secret-agent.c \ - src/settings/nm-secret-agent.h \ - src/settings/nm-settings-connection.c \ - src/settings/nm-settings-connection.h \ - src/settings/nm-settings-storage.c \ - src/settings/nm-settings-storage.h \ - src/settings/nm-settings-plugin.c \ - src/settings/nm-settings-plugin.h \ - src/settings/nm-settings.c \ - src/settings/nm-settings.h \ - src/settings/nm-settings-utils.c \ - src/settings/nm-settings-utils.h \ + src/core/settings/nm-agent-manager.c \ + src/core/settings/nm-agent-manager.h \ + src/core/settings/nm-secret-agent.c \ + src/core/settings/nm-secret-agent.h \ + src/core/settings/nm-settings-connection.c \ + src/core/settings/nm-settings-connection.h \ + src/core/settings/nm-settings-storage.c \ + src/core/settings/nm-settings-storage.h \ + src/core/settings/nm-settings-plugin.c \ + src/core/settings/nm-settings-plugin.h \ + src/core/settings/nm-settings.c \ + src/core/settings/nm-settings.h \ + src/core/settings/nm-settings-utils.c \ + src/core/settings/nm-settings-utils.h \ \ - src/settings/plugins/keyfile/nms-keyfile-storage.c \ - src/settings/plugins/keyfile/nms-keyfile-storage.h \ - src/settings/plugins/keyfile/nms-keyfile-plugin.c \ - src/settings/plugins/keyfile/nms-keyfile-plugin.h \ - src/settings/plugins/keyfile/nms-keyfile-reader.c \ - src/settings/plugins/keyfile/nms-keyfile-reader.h \ - src/settings/plugins/keyfile/nms-keyfile-utils.c \ - src/settings/plugins/keyfile/nms-keyfile-utils.h \ - src/settings/plugins/keyfile/nms-keyfile-writer.c \ - src/settings/plugins/keyfile/nms-keyfile-writer.h \ + src/core/settings/plugins/keyfile/nms-keyfile-storage.c \ + src/core/settings/plugins/keyfile/nms-keyfile-storage.h \ + src/core/settings/plugins/keyfile/nms-keyfile-plugin.c \ + src/core/settings/plugins/keyfile/nms-keyfile-plugin.h \ + src/core/settings/plugins/keyfile/nms-keyfile-reader.c \ + src/core/settings/plugins/keyfile/nms-keyfile-reader.h \ + src/core/settings/plugins/keyfile/nms-keyfile-utils.c \ + src/core/settings/plugins/keyfile/nms-keyfile-utils.h \ + src/core/settings/plugins/keyfile/nms-keyfile-writer.c \ + src/core/settings/plugins/keyfile/nms-keyfile-writer.h \ \ - src/supplicant/nm-supplicant-config.c \ - src/supplicant/nm-supplicant-config.h \ - src/supplicant/nm-supplicant-interface.c \ - src/supplicant/nm-supplicant-interface.h \ - src/supplicant/nm-supplicant-manager.c \ - src/supplicant/nm-supplicant-manager.h \ - src/supplicant/nm-supplicant-settings-verify.c \ - src/supplicant/nm-supplicant-settings-verify.h \ - src/supplicant/nm-supplicant-types.h \ + src/core/supplicant/nm-supplicant-config.c \ + src/core/supplicant/nm-supplicant-config.h \ + src/core/supplicant/nm-supplicant-interface.c \ + src/core/supplicant/nm-supplicant-interface.h \ + src/core/supplicant/nm-supplicant-manager.c \ + src/core/supplicant/nm-supplicant-manager.h \ + src/core/supplicant/nm-supplicant-settings-verify.c \ + src/core/supplicant/nm-supplicant-settings-verify.h \ + src/core/supplicant/nm-supplicant-types.h \ \ - src/vpn/nm-vpn-connection.c \ - src/vpn/nm-vpn-connection.h \ - src/vpn/nm-vpn-manager.c \ - src/vpn/nm-vpn-manager.h \ + src/core/vpn/nm-vpn-connection.c \ + src/core/vpn/nm-vpn-connection.h \ + src/core/vpn/nm-vpn-manager.c \ + src/core/vpn/nm-vpn-manager.h \ \ - src/nm-act-request.c \ - src/nm-act-request.h \ - src/nm-active-connection.c \ - src/nm-active-connection.h \ - src/nm-audit-manager.c \ - src/nm-audit-manager.h \ - src/nm-dbus-manager.c \ - src/nm-dbus-manager.h \ - src/nm-config.c \ - src/nm-config.h \ - src/nm-config-data.c \ - src/nm-config-data.h \ - src/nm-connectivity.c \ - src/nm-connectivity.h \ - src/nm-dcb.c \ - src/nm-dcb.h \ - src/nm-dhcp-config.c \ - src/nm-dhcp-config.h \ - src/nm-dispatcher.c \ - src/nm-dispatcher.h \ - src/nm-firewall-manager.c \ - src/nm-firewall-manager.h \ - src/nm-proxy-config.c \ - src/nm-proxy-config.h \ - src/nm-auth-manager.c \ - src/nm-auth-manager.h \ - src/nm-auth-utils.c \ - src/nm-auth-utils.h \ - src/nm-manager.c \ - src/nm-manager.h \ - src/nm-pacrunner-manager.c \ - src/nm-pacrunner-manager.h \ - src/nm-policy.c \ - src/nm-policy.h \ - src/nm-rfkill-manager.c \ - src/nm-rfkill-manager.h \ - src/nm-session-monitor.h \ - src/nm-session-monitor.c \ - src/nm-keep-alive.c \ - src/nm-keep-alive.h \ - src/nm-sleep-monitor.c \ - src/nm-sleep-monitor.h \ - src/nm-types.h \ + src/core/nm-act-request.c \ + src/core/nm-act-request.h \ + src/core/nm-active-connection.c \ + src/core/nm-active-connection.h \ + src/core/nm-audit-manager.c \ + src/core/nm-audit-manager.h \ + src/core/nm-dbus-manager.c \ + src/core/nm-dbus-manager.h \ + src/core/nm-config.c \ + src/core/nm-config.h \ + src/core/nm-config-data.c \ + src/core/nm-config-data.h \ + src/core/nm-connectivity.c \ + src/core/nm-connectivity.h \ + src/core/nm-dcb.c \ + src/core/nm-dcb.h \ + src/core/nm-dhcp-config.c \ + src/core/nm-dhcp-config.h \ + src/core/nm-dispatcher.c \ + src/core/nm-dispatcher.h \ + src/core/nm-firewall-manager.c \ + src/core/nm-firewall-manager.h \ + src/core/nm-proxy-config.c \ + src/core/nm-proxy-config.h \ + src/core/nm-auth-manager.c \ + src/core/nm-auth-manager.h \ + src/core/nm-auth-utils.c \ + src/core/nm-auth-utils.h \ + src/core/nm-manager.c \ + src/core/nm-manager.h \ + src/core/nm-pacrunner-manager.c \ + src/core/nm-pacrunner-manager.h \ + src/core/nm-policy.c \ + src/core/nm-policy.h \ + src/core/nm-rfkill-manager.c \ + src/core/nm-rfkill-manager.h \ + src/core/nm-session-monitor.h \ + src/core/nm-session-monitor.c \ + src/core/nm-keep-alive.c \ + src/core/nm-keep-alive.h \ + src/core/nm-sleep-monitor.c \ + src/core/nm-sleep-monitor.h \ + src/core/nm-types.h \ \ $(NULL) -src_libNetworkManager_la_LIBADD = \ - src/libNetworkManagerBase.la \ +src_core_libNetworkManager_la_LIBADD = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -6837,7 +6898,7 @@ src_libNetworkManager_la_LIBADD = \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ - src/libnm-systemd-core.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libnacd.la \ shared/libndhcp4.la \ @@ -6855,65 +6916,65 @@ src_libNetworkManager_la_LIBADD = \ $(LIBCURL_LIBS) \ $(NULL) -src_tests_cppflags_fake = $(src_cppflags_test) -DSETUP=nm_fake_platform_setup -src_tests_cppflags_linux = $(src_cppflags_test) -DSETUP=nm_linux_platform_setup -src_libNetworkManagerTest_la_CPPFLAGS = $(src_cppflags_test) -src_libNetworkManagerTest_la_SOURCES = \ - src/ndisc/nm-fake-ndisc.c \ - src/ndisc/nm-fake-ndisc.h \ - src/platform/nm-fake-platform.c \ - src/platform/nm-fake-platform.h \ - src/platform/tests/test-common.c \ - src/platform/tests/test-common.h \ +src_core_tests_cppflags_fake = $(src_core_cppflags_test) -DSETUP=nm_fake_platform_setup +src_core_tests_cppflags_linux = $(src_core_cppflags_test) -DSETUP=nm_linux_platform_setup +src_core_libNetworkManagerTest_la_CPPFLAGS = $(src_core_cppflags_test) +src_core_libNetworkManagerTest_la_SOURCES = \ + src/core/ndisc/nm-fake-ndisc.c \ + src/core/ndisc/nm-fake-ndisc.h \ + src/core/platform/nm-fake-platform.c \ + src/core/platform/nm-fake-platform.h \ + src/core/platform/tests/test-common.c \ + src/core/platform/tests/test-common.h \ $(NULL) -src_libNetworkManagerTest_la_LIBADD = \ - src/libNetworkManager.la \ +src_core_libNetworkManagerTest_la_LIBADD = \ + src/core/libNetworkManager.la \ $(CODE_COVERAGE_LDFLAGS) \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) \ $(NULL) -src_NetworkManager_all_sym_CPPFLAGS = $(src_cppflags) -src_NetworkManager_all_sym_SOURCES = \ - src/main.c +src_core_NetworkManager_all_sym_CPPFLAGS = $(src_core_cppflags) +src_core_NetworkManager_all_sym_SOURCES = \ + src/core/main.c -src_NetworkManager_all_sym_LDADD = \ - src/libNetworkManager.la \ +src_core_NetworkManager_all_sym_LDADD = \ + src/core/libNetworkManager.la \ $(GLIB_LIBS) \ $(NULL) -src_NetworkManager_all_sym_LDFLAGS = \ +src_core_NetworkManager_all_sym_LDFLAGS = \ -rdynamic \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -EXTRA_src_NetworkManager_DEPENDENCIES = \ - src/NetworkManager.ver +EXTRA_src_core_NetworkManager_DEPENDENCIES = \ + src/core/NetworkManager.ver -src_NetworkManager_CPPFLAGS = $(src_cppflags) -src_NetworkManager_SOURCES = \ - src/main.c +src_core_NetworkManager_CPPFLAGS = $(src_core_cppflags) +src_core_NetworkManager_SOURCES = \ + src/core/main.c -src_NetworkManager_LDADD = \ - src/libNetworkManager.la \ +src_core_NetworkManager_LDADD = \ + src/core/libNetworkManager.la \ $(GLIB_LIBS) \ $(NULL) -src_NetworkManager_LDFLAGS = \ +src_core_NetworkManager_LDFLAGS = \ -rdynamic \ - -Wl,--version-script="src/NetworkManager.ver" \ + -Wl,--version-script="src/core/NetworkManager.ver" \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) ############################################################################### -src_nm_iface_helper_CPPFLAGS = $(src_cppflags) -src_nm_iface_helper_SOURCES = \ - src/nm-iface-helper.c +src_core_nm_iface_helper_CPPFLAGS = $(src_core_cppflags) +src_core_nm_iface_helper_SOURCES = \ + src/core/nm-iface-helper.c -src_nm_iface_helper_LDADD = \ - src/libNetworkManagerBase.la \ +src_core_nm_iface_helper_LDADD = \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -6925,7 +6986,7 @@ src_nm_iface_helper_LDADD = \ shared/nm-udev-aux/libnm-udev-aux.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ - src/libnm-systemd-core.la \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libnacd.la \ shared/libndhcp4.la \ @@ -6938,29 +6999,29 @@ src_nm_iface_helper_LDADD = \ $(DL_LIBS) \ $(NULL) -src_nm_iface_helper_LDFLAGS = \ +src_core_nm_iface_helper_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_libnmi_core_la_CPPFLAGS = \ - $(src_cppflags) +src_core_initrd_libnmi_core_la_CPPFLAGS = \ + $(src_core_cppflags) -src_initrd_libnmi_core_la_SOURCES = \ - src/initrd/nm-initrd-generator.h \ - src/initrd/nmi-cmdline-reader.c \ - src/initrd/nmi-dt-reader.c \ - src/initrd/nmi-ibft-reader.c \ +src_core_initrd_libnmi_core_la_SOURCES = \ + src/core/initrd/nm-initrd-generator.h \ + src/core/initrd/nmi-cmdline-reader.c \ + src/core/initrd/nmi-dt-reader.c \ + src/core/initrd/nmi-ibft-reader.c \ $(NULL) -src_initrd_nm_initrd_generator_CPPFLAGS = \ - $(src_cppflags) +src_core_initrd_nm_initrd_generator_CPPFLAGS = \ + $(src_core_cppflags) -src_initrd_nm_initrd_generator_SOURCES = \ - src/initrd/nm-initrd-generator.c +src_core_initrd_nm_initrd_generator_SOURCES = \ + src/core/initrd/nm-initrd-generator.c -src_initrd_nm_initrd_generator_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerBase.la \ +src_core_initrd_nm_initrd_generator_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerBase.la \ libnm-core/nm-libnm-core-aux/libnm-libnm-core-aux.la \ libnm-core/nm-keyfile/libnm-keyfile.la \ libnm-core/libnm-core.la \ @@ -6981,65 +7042,65 @@ src_initrd_nm_initrd_generator_LDADD = \ $(GLIB_LIBS) \ $(NULL) -src_initrd_nm_initrd_generator_LDFLAGS = \ +src_core_initrd_nm_initrd_generator_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_dt_reader_CPPFLAGS = \ +src_core_initrd_tests_test_dt_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_dt_reader_LDFLAGS = \ +src_core_initrd_tests_test_dt_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_dt_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_dt_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(NULL) -src_initrd_tests_test_ibft_reader_CPPFLAGS = \ +src_core_initrd_tests_test_ibft_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_ibft_reader_LDFLAGS = \ +src_core_initrd_tests_test_ibft_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_ibft_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_ibft_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(NULL) -src_initrd_tests_test_cmdline_reader_CPPFLAGS = \ +src_core_initrd_tests_test_cmdline_reader_CPPFLAGS = \ -DNETWORKMANAGER_COMPILATION_TEST \ - -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/initrd/tests\" \ - $(src_cppflags) + -DTEST_INITRD_DIR=\"$(abs_srcdir)/src/core/initrd/tests\" \ + $(src_core_cppflags) -src_initrd_tests_test_cmdline_reader_LDFLAGS = \ +src_core_initrd_tests_test_cmdline_reader_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_initrd_tests_test_cmdline_reader_LDADD = \ - src/initrd/libnmi-core.la \ - src/libNetworkManagerTest.la \ +src_core_initrd_tests_test_cmdline_reader_LDADD = \ + src/core/initrd/libnmi-core.la \ + src/core/libNetworkManagerTest.la \ shared/nm-glib-aux/libnm-glib-aux.la \ shared/nm-std-aux/libnm-std-aux.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(NULL) -src_dhcp_nm_dhcp_helper_CPPFLAGS = \ +src_core_dhcp_nm_dhcp_helper_CPPFLAGS = \ $(dflt_cppflags) \ -I$(srcdir)/shared \ -I$(builddir)/shared \ @@ -7048,30 +7109,30 @@ src_dhcp_nm_dhcp_helper_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(NULL) -src_dhcp_nm_dhcp_helper_SOURCES = \ - src/dhcp/nm-dhcp-helper.c \ - src/dhcp/nm-dhcp-helper-api.h \ +src_core_dhcp_nm_dhcp_helper_SOURCES = \ + src/core/dhcp/nm-dhcp-helper.c \ + src/core/dhcp/nm-dhcp-helper-api.h \ $(NULL) -src_dhcp_nm_dhcp_helper_LDFLAGS = \ +src_core_dhcp_nm_dhcp_helper_LDFLAGS = \ -Wl,--version-script="$(srcdir)/linker-script-binary.ver" -src_dhcp_nm_dhcp_helper_LDADD = $(GLIB_LIBS) +src_core_dhcp_nm_dhcp_helper_LDADD = $(GLIB_LIBS) ############################################################################### -# src/dhcp/tests +# src/core/dhcp/tests ############################################################################### -src_dhcp_tests_cppflags = $(src_cppflags_test) -src_dhcp_tests_ldadd = \ - src/libNetworkManagerTest.la - -src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS = $(src_dhcp_tests_cppflags) -src_dhcp_tests_test_dhcp_utils_CPPFLAGS = $(src_dhcp_tests_cppflags) -src_dhcp_tests_test_dhcp_dhclient_LDADD = $(src_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_utils_LDADD = $(src_dhcp_tests_ldadd) -src_dhcp_tests_test_dhcp_dhclient_LDFLAGS = $(src_tests_ldflags) -src_dhcp_tests_test_dhcp_utils_LDFLAGS = $(src_tests_ldflags) -@WITH_PPP_TRUE@src_ppp_nm_pppd_plugin_la_CPPFLAGS = \ +src_core_dhcp_tests_cppflags = $(src_core_cppflags_test) +src_core_dhcp_tests_ldadd = \ + src/core/libNetworkManagerTest.la + +src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS = $(src_core_dhcp_tests_cppflags) +src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS = $(src_core_dhcp_tests_cppflags) +src_core_dhcp_tests_test_dhcp_dhclient_LDADD = $(src_core_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_utils_LDADD = $(src_core_dhcp_tests_ldadd) +src_core_dhcp_tests_test_dhcp_dhclient_LDFLAGS = $(src_core_tests_ldflags) +src_core_dhcp_tests_test_dhcp_utils_LDFLAGS = $(src_core_tests_ldflags) +@WITH_PPP_TRUE@src_core_ppp_nm_pppd_plugin_la_CPPFLAGS = \ @WITH_PPP_TRUE@ $(dflt_cppflags) \ @WITH_PPP_TRUE@ -I$(srcdir)/shared \ @WITH_PPP_TRUE@ -I$(builddir)/shared \ @@ -7080,345 +7141,345 @@ src_dhcp_tests_test_dhcp_utils_LDFLAGS = $(src_tests_ldflags) @WITH_PPP_TRUE@ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB \ @WITH_PPP_TRUE@ $(GLIB_CFLAGS) -@WITH_PPP_TRUE@src_ppp_nm_pppd_plugin_la_SOURCES = \ -@WITH_PPP_TRUE@ src/ppp/nm-pppd-plugin.c \ -@WITH_PPP_TRUE@ src/ppp/nm-pppd-plugin.h \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-status.h +@WITH_PPP_TRUE@src_core_ppp_nm_pppd_plugin_la_SOURCES = \ +@WITH_PPP_TRUE@ src/core/ppp/nm-pppd-plugin.c \ +@WITH_PPP_TRUE@ src/core/ppp/nm-pppd-plugin.h \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-status.h -@WITH_PPP_TRUE@src_ppp_nm_pppd_plugin_la_LDFLAGS = \ +@WITH_PPP_TRUE@src_core_ppp_nm_pppd_plugin_la_LDFLAGS = \ @WITH_PPP_TRUE@ -module -avoid-version -@WITH_PPP_TRUE@src_ppp_nm_pppd_plugin_la_LIBADD = \ +@WITH_PPP_TRUE@src_core_ppp_nm_pppd_plugin_la_LIBADD = \ @WITH_PPP_TRUE@ $(GLIB_LIBS) \ @WITH_PPP_TRUE@ $(DL_LIBS) \ @WITH_PPP_TRUE@ $(NULL) -@WITH_PPP_TRUE@src_ppp_libnm_ppp_plugin_la_SOURCES = \ -@WITH_PPP_TRUE@ src/ppp/nm-pppd-plugin.h \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-manager.c \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-manager.h \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-plugin-api.h \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-status.h \ +@WITH_PPP_TRUE@src_core_ppp_libnm_ppp_plugin_la_SOURCES = \ +@WITH_PPP_TRUE@ src/core/ppp/nm-pppd-plugin.h \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-manager.c \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-manager.h \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-plugin-api.h \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-status.h \ @WITH_PPP_TRUE@ $(NULL) -@WITH_PPP_TRUE@EXTRA_src_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ -@WITH_PPP_TRUE@ src/ppp/nm-ppp-plugin.ver \ +@WITH_PPP_TRUE@EXTRA_src_core_ppp_libnm_ppp_plugin_la_DEPENDENCIES = \ +@WITH_PPP_TRUE@ src/core/ppp/nm-ppp-plugin.ver \ @WITH_PPP_TRUE@ $(NULL) -@WITH_PPP_TRUE@src_ppp_libnm_ppp_plugin_la_CPPFLAGS = $(src_cppflags_base) -@WITH_PPP_TRUE@src_ppp_libnm_ppp_plugin_la_LDFLAGS = \ +@WITH_PPP_TRUE@src_core_ppp_libnm_ppp_plugin_la_CPPFLAGS = $(src_core_cppflags_base) +@WITH_PPP_TRUE@src_core_ppp_libnm_ppp_plugin_la_LDFLAGS = \ @WITH_PPP_TRUE@ -module -avoid-version \ -@WITH_PPP_TRUE@ -Wl,--version-script="$(srcdir)/src/ppp/nm-ppp-plugin.ver" \ +@WITH_PPP_TRUE@ -Wl,--version-script="$(srcdir)/src/core/ppp/nm-ppp-plugin.ver" \ @WITH_PPP_TRUE@ $(NULL) -@WITH_PPP_TRUE@src_ppp_libnm_ppp_plugin_la_LIBADD = \ +@WITH_PPP_TRUE@src_core_ppp_libnm_ppp_plugin_la_LIBADD = \ @WITH_PPP_TRUE@ $(GLIB_LIBS) \ @WITH_PPP_TRUE@ $(NULL) -src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS = $(src_cppflags_test) -src_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS = \ +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS = $(src_core_cppflags_test) +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDFLAGS = \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD = \ - src/libNetworkManagerTest.la +src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD = \ + src/core/libNetworkManagerTest.la ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@nodist_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +@CONFIG_PLUGIN_IFCFG_RH_TRUE@nodist_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_SOURCES = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(src_cppflags_base)) \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(src_core_cppflags_base)) \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(INTROSPECTION_EXTRA_CFLAGS) \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(NULL) ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/shvar.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/shvar.h \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_SOURCES = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/shvar.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/shvar.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS = $(src_cppflags_base) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS = $(src_core_cppflags_base) ############################################################################### -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_SOURCES = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS = $(src_cppflags_base) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS = $(src_core_cppflags_base) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LDFLAGS = \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ -module -avoid-version \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-settings.ver" \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(SANITIZER_EXEC_LDFLAGS) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_SOURCES = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS = $(src_cppflags_base_test) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS = $(src_core_cppflags_base_test) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDFLAGS = \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(GLIB_LIBS) \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(CODE_COVERAGE_LDFLAGS) \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(SANITIZER_EXEC_LDFLAGS) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD = \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/libNetworkManagerTest.la +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD = \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la \ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ src/core/libNetworkManagerTest.la # make target dependencies can't have colons in their names, which ends up # meaning that we can't add the alias files to EXTRA_DIST. They are instead # dist'ed via dist-hook-settings-ifcfg-rh below. -src_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files = \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 \ - src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 - -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES = \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-parser.c \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-parser.h \ +src_core_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files = \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 \ + src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 + +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_SOURCES = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS = $(src_cppflags_base) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS = $(src_core_cppflags_base) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD = \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(LIBUDEV_LIBS) \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES = \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/nms-ifupdown-plugin.h \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_SOURCES = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS = $(src_cppflags_base) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS = $(src_core_cppflags_base) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LDFLAGS = \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ -module -avoid-version \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-settings.ver" \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD = \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(LIBUDEV_LIBS) \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS = $(src_cppflags_base_test) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS = $(src_core_cppflags_base_test) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDFLAGS = \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(CODE_COVERAGE_LDFLAGS) \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(SANITIZER_EXEC_LDFLAGS) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_settings_plugins_ifupdown_tests_test_ifupdown_LDADD = \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/settings/plugins/ifupdown/libnms-ifupdown-core.la \ -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/libNetworkManagerTest.la \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDADD = \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la \ +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ src/core/libNetworkManagerTest.la \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(GLIB_LIBS) \ @CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(NULL) -src_devices_adsl_libnm_device_plugin_adsl_la_SOURCES = \ - src/devices/adsl/nm-atm-manager.c \ - src/devices/adsl/nm-device-adsl.c \ - src/devices/adsl/nm-device-adsl.h +src_core_devices_adsl_libnm_device_plugin_adsl_la_SOURCES = \ + src/core/devices/adsl/nm-atm-manager.c \ + src/core/devices/adsl/nm-device-adsl.c \ + src/core/devices/adsl/nm-device-adsl.h -src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS = $(src_cppflags_device_plugin) -src_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS = \ +src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS = $(src_core_cppflags_device_plugin) +src_core_devices_adsl_libnm_device_plugin_adsl_la_LDFLAGS = \ -module -avoid-version \ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -src_devices_adsl_libnm_device_plugin_adsl_la_LIBADD = \ +src_core_devices_adsl_libnm_device_plugin_adsl_la_LIBADD = \ $(LIBUDEV_LIBS) ############################################################################### -# src/devices/wwan +# src/core/devices/wwan ############################################################################### -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_cppflags = \ -@WITH_MODEM_MANAGER_1_TRUE@ $(src_cppflags_device_plugin) \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_cppflags = \ +@WITH_MODEM_MANAGER_1_TRUE@ $(src_core_cppflags_device_plugin) \ @WITH_MODEM_MANAGER_1_TRUE@ $(MM_GLIB_CFLAGS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_wwan_la_SOURCES = src/devices/wwan/nm-modem-broadband.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-modem-broadband.h \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-modem-manager.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-modem-manager.h \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-modem.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-modem.h \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-service-providers.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-service-providers.h \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_wwan_la_SOURCES = src/core/devices/wwan/nm-modem-broadband.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-modem-broadband.h \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-modem-manager.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-modem-manager.h \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-modem.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-modem.h \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-service-providers.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-service-providers.h \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) $(am__append_34) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_wwan_la_CPPFLAGS = $(src_devices_wwan_cppflags) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_wwan_la_LDFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_wwan_la_CPPFLAGS = $(src_core_devices_wwan_cppflags) +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_wwan_la_LDFLAGS = \ @WITH_MODEM_MANAGER_1_TRUE@ -avoid-version \ -@WITH_MODEM_MANAGER_1_TRUE@ -Wl,--version-script="$(srcdir)/src/devices/wwan/libnm-wwan.ver" +@WITH_MODEM_MANAGER_1_TRUE@ -Wl,--version-script="$(srcdir)/src/core/devices/wwan/libnm-wwan.ver" -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_wwan_la_LIBADD = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_wwan_la_LIBADD = \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(LIBSYSTEMD_LIBS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(MM_GLIB_LIBS) -@WITH_MODEM_MANAGER_1_TRUE@EXTRA_src_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.ver +@WITH_MODEM_MANAGER_1_TRUE@EXTRA_src_core_devices_wwan_libnm_wwan_la_DEPENDENCIES = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm-wwan.ver -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_device_plugin_wwan_la_SOURCES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-wwan-factory.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-device-modem.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-device-modem.h +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_device_plugin_wwan_la_SOURCES = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-wwan-factory.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-device-modem.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-device-modem.h -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS = $(src_devices_wwan_cppflags) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS = $(src_core_devices_wwan_cppflags) +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_device_plugin_wwan_la_LDFLAGS = \ @WITH_MODEM_MANAGER_1_TRUE@ -module -avoid-version \ @WITH_MODEM_MANAGER_1_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_libnm_device_plugin_wwan_la_LIBADD = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.la \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_libnm_device_plugin_wwan_la_LIBADD = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm-wwan.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_tests_test_service_providers_SOURCES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/tests/test-service-providers.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-service-providers.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/nm-service-providers.h \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_tests_test_service_providers_SOURCES = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/tests/test-service-providers.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-service-providers.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/nm-service-providers.h \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_tests_test_service_providers_CPPFLAGS = \ -@WITH_MODEM_MANAGER_1_TRUE@ $(src_cppflags_base_test) \ -@WITH_MODEM_MANAGER_1_TRUE@ -I$(srcdir)/src/devices/wwan \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_tests_test_service_providers_CPPFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@ $(src_core_cppflags_base_test) \ +@WITH_MODEM_MANAGER_1_TRUE@ -I$(srcdir)/src/core/devices/wwan \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_tests_test_service_providers_LDFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_tests_test_service_providers_LDFLAGS = \ @WITH_MODEM_MANAGER_1_TRUE@ $(SANITIZER_EXEC_LDFLAGS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_wwan_tests_test_service_providers_LDADD = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/libNetworkManagerTest.la \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_wwan_tests_test_service_providers_LDADD = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/libNetworkManagerTest.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES = src/devices/bluetooth/nm-bluez-common.h \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bt-error.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bt-error.h \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_bluetooth_utils_la_SOURCES = src/core/devices/bluetooth/nm-bluez-common.h \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bt-error.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bt-error.h \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) $(am__append_38) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS = \ -@WITH_MODEM_MANAGER_1_TRUE@ $(src_cppflags_base) $(NULL) \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@ $(src_core_cppflags_base) $(NULL) \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__append_39) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD = \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) $(NULL) \ @WITH_MODEM_MANAGER_1_TRUE@ $(am__append_40) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bluez-manager.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-bluez-manager.h \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-device-bt.c \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/nm-device-bt.h \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bluez-manager.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-bluez-manager.h \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-device-bt.c \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/nm-device-bt.h \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS = $(src_cppflags_device_plugin) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS = $(src_core_cppflags_device_plugin) +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LDFLAGS = \ @WITH_MODEM_MANAGER_1_TRUE@ -module -avoid-version \ @WITH_MODEM_MANAGER_1_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/libnm-bluetooth-utils.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/wwan/libnm-wwan.la \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/libnm-bluetooth-utils.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/wwan/libnm-wwan.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS = \ -@WITH_MODEM_MANAGER_1_TRUE@ $(src_cppflags_test) \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@ $(src_core_cppflags_test) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_tests_nm_bt_test_LDFLAGS = \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_tests_nm_bt_test_LDFLAGS = \ @WITH_MODEM_MANAGER_1_TRUE@ $(SANITIZER_EXEC_LDFLAGS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_MODEM_MANAGER_1_TRUE@src_devices_bluetooth_tests_nm_bt_test_LDADD = \ -@WITH_MODEM_MANAGER_1_TRUE@ src/devices/bluetooth/libnm-bluetooth-utils.la \ -@WITH_MODEM_MANAGER_1_TRUE@ src/libNetworkManager.la \ +@WITH_MODEM_MANAGER_1_TRUE@src_core_devices_bluetooth_tests_nm_bt_test_LDADD = \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/devices/bluetooth/libnm-bluetooth-utils.la \ +@WITH_MODEM_MANAGER_1_TRUE@ src/core/libNetworkManager.la \ @WITH_MODEM_MANAGER_1_TRUE@ $(GLIB_LIBS) \ @WITH_MODEM_MANAGER_1_TRUE@ $(NULL) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_wifi_base_la_SOURCES = \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-olpc-mesh.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-olpc-mesh.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-wifi-p2p.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-wifi-p2p.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-wifi.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-device-wifi.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-ap.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-ap.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-common.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-common.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-p2p-peer.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-p2p-peer.h \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-utils.c \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-utils.h $(NULL) \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_wifi_base_la_SOURCES = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-olpc-mesh.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-olpc-mesh.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-wifi-p2p.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-wifi-p2p.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-wifi.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-device-wifi.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-ap.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-ap.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-common.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-common.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-p2p-peer.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-p2p-peer.h \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-utils.c \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-utils.h $(NULL) \ @WITH_WIFI_TRUE@ $(am__append_43) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_wifi_base_la_CPPFLAGS = $(src_cppflags_device_plugin) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_wifi_base_la_LIBADD = \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS = $(src_core_cppflags_device_plugin) +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_wifi_base_la_LIBADD = \ @WITH_WIFI_TRUE@ $(GLIB_LIBS) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \ -@WITH_WIFI_TRUE@ src/devices/wifi/nm-wifi-factory.c \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/nm-wifi-factory.c \ @WITH_WIFI_TRUE@ $(NULL) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = $(src_cppflags_device_plugin) -@WITH_WIFI_TRUE@src_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS = \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = $(src_core_cppflags_device_plugin) +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_device_plugin_wifi_la_LDFLAGS = \ @WITH_WIFI_TRUE@ -module -avoid-version \ @WITH_WIFI_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" -@WITH_WIFI_TRUE@src_devices_wifi_libnm_device_plugin_wifi_la_LIBADD = \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm-wifi-base.la \ +@WITH_WIFI_TRUE@src_core_devices_wifi_libnm_device_plugin_wifi_la_LIBADD = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm-wifi-base.la \ @WITH_WIFI_TRUE@ $(GLIB_LIBS) -@WITH_WIFI_TRUE@src_devices_wifi_tests_test_devices_wifi_SOURCES = \ -@WITH_WIFI_TRUE@ src/devices/wifi/tests/test-devices-wifi.c \ +@WITH_WIFI_TRUE@src_core_devices_wifi_tests_test_devices_wifi_SOURCES = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/tests/test-devices-wifi.c \ @WITH_WIFI_TRUE@ $(NULL) -@WITH_WIFI_TRUE@src_devices_wifi_tests_test_devices_wifi_CPPFLAGS = $(src_cppflags_base_test) -@WITH_WIFI_TRUE@src_devices_wifi_tests_test_devices_wifi_LDADD = \ -@WITH_WIFI_TRUE@ src/devices/wifi/libnm-wifi-base.la \ -@WITH_WIFI_TRUE@ src/libNetworkManagerTest.la \ -@WITH_WIFI_TRUE@ src/libNetworkManagerBase.la \ +@WITH_WIFI_TRUE@src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS = $(src_core_cppflags_base_test) +@WITH_WIFI_TRUE@src_core_devices_wifi_tests_test_devices_wifi_LDADD = \ +@WITH_WIFI_TRUE@ src/core/devices/wifi/libnm-wifi-base.la \ +@WITH_WIFI_TRUE@ src/core/libNetworkManagerTest.la \ +@WITH_WIFI_TRUE@ src/core/libNetworkManagerBase.la \ @WITH_WIFI_TRUE@ $(NULL) -@WITH_WIFI_TRUE@src_devices_wifi_tests_test_devices_wifi_LDFLAGS = $(SANITIZER_EXEC_LDFLAGS) -@WITH_TEAMDCTL_TRUE@src_devices_team_libnm_device_plugin_team_la_SOURCES = \ -@WITH_TEAMDCTL_TRUE@ src/devices/team/nm-team-factory.c \ -@WITH_TEAMDCTL_TRUE@ src/devices/team/nm-device-team.c \ -@WITH_TEAMDCTL_TRUE@ src/devices/team/nm-device-team.h \ +@WITH_WIFI_TRUE@src_core_devices_wifi_tests_test_devices_wifi_LDFLAGS = $(SANITIZER_EXEC_LDFLAGS) +@WITH_TEAMDCTL_TRUE@src_core_devices_team_libnm_device_plugin_team_la_SOURCES = \ +@WITH_TEAMDCTL_TRUE@ src/core/devices/team/nm-team-factory.c \ +@WITH_TEAMDCTL_TRUE@ src/core/devices/team/nm-device-team.c \ +@WITH_TEAMDCTL_TRUE@ src/core/devices/team/nm-device-team.h \ @WITH_TEAMDCTL_TRUE@ $(NULL) -@WITH_TEAMDCTL_TRUE@src_devices_team_libnm_device_plugin_team_la_CPPFLAGS = \ -@WITH_TEAMDCTL_TRUE@ $(src_cppflags_device_plugin) \ +@WITH_TEAMDCTL_TRUE@src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS = \ +@WITH_TEAMDCTL_TRUE@ $(src_core_cppflags_device_plugin) \ @WITH_TEAMDCTL_TRUE@ $(LIBTEAMDCTL_CFLAGS) \ @WITH_TEAMDCTL_TRUE@ $(JANSSON_CFLAGS) \ @WITH_TEAMDCTL_TRUE@ $(NULL) -@WITH_TEAMDCTL_TRUE@src_devices_team_libnm_device_plugin_team_la_LDFLAGS = \ +@WITH_TEAMDCTL_TRUE@src_core_devices_team_libnm_device_plugin_team_la_LDFLAGS = \ @WITH_TEAMDCTL_TRUE@ -module -avoid-version \ @WITH_TEAMDCTL_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" \ @WITH_TEAMDCTL_TRUE@ $(NULL) -@WITH_TEAMDCTL_TRUE@src_devices_team_libnm_device_plugin_team_la_LIBADD = \ +@WITH_TEAMDCTL_TRUE@src_core_devices_team_libnm_device_plugin_team_la_LIBADD = \ @WITH_TEAMDCTL_TRUE@ $(LIBTEAMDCTL_LIBS) \ @WITH_TEAMDCTL_TRUE@ $(JANSSON_LIBS) \ @WITH_TEAMDCTL_TRUE@ $(GLIB_LIBS) \ @@ -7426,209 +7487,209 @@ src_devices_adsl_libnm_device_plugin_adsl_la_LIBADD = \ ############################################################################### -# src/devices/ovs +# src/core/devices/ovs ############################################################################### @HAVE_SYSTEMD_TRUE@@WITH_OPENVSWITCH_TRUE@systemdnmunitdir = $(systemdsystemunitdir)/NetworkManager.service.d @HAVE_SYSTEMD_TRUE@@WITH_OPENVSWITCH_TRUE@systemdnmunit_DATA = \ @HAVE_SYSTEMD_TRUE@@WITH_OPENVSWITCH_TRUE@ data/NetworkManager-ovs.conf -@WITH_OPENVSWITCH_TRUE@src_devices_ovs_libnm_device_plugin_ovs_la_SOURCES = \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-ovsdb.c \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-ovsdb.h \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-ovs-factory.c \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-interface.c \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-interface.h \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-port.c \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-port.h \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-bridge.c \ -@WITH_OPENVSWITCH_TRUE@ src/devices/ovs/nm-device-ovs-bridge.h \ +@WITH_OPENVSWITCH_TRUE@src_core_devices_ovs_libnm_device_plugin_ovs_la_SOURCES = \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-ovsdb.c \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-ovsdb.h \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-ovs-factory.c \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-interface.c \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-interface.h \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-port.c \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-port.h \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-bridge.c \ +@WITH_OPENVSWITCH_TRUE@ src/core/devices/ovs/nm-device-ovs-bridge.h \ @WITH_OPENVSWITCH_TRUE@ $(NULL) -@WITH_OPENVSWITCH_TRUE@src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS = \ -@WITH_OPENVSWITCH_TRUE@ $(src_cppflags_device_plugin) \ +@WITH_OPENVSWITCH_TRUE@src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS = \ +@WITH_OPENVSWITCH_TRUE@ $(src_core_cppflags_device_plugin) \ @WITH_OPENVSWITCH_TRUE@ $(JANSSON_CFLAGS) \ @WITH_OPENVSWITCH_TRUE@ $(NULL) -@WITH_OPENVSWITCH_TRUE@src_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS = \ +@WITH_OPENVSWITCH_TRUE@src_core_devices_ovs_libnm_device_plugin_ovs_la_LDFLAGS = \ @WITH_OPENVSWITCH_TRUE@ -module -avoid-version \ @WITH_OPENVSWITCH_TRUE@ -Wl,--version-script="$(srcdir)/linker-script-devices.ver" \ @WITH_OPENVSWITCH_TRUE@ $(NULL) -@WITH_OPENVSWITCH_TRUE@src_devices_ovs_libnm_device_plugin_ovs_la_LIBADD = \ +@WITH_OPENVSWITCH_TRUE@src_core_devices_ovs_libnm_device_plugin_ovs_la_LIBADD = \ @WITH_OPENVSWITCH_TRUE@ $(JANSSON_LIBS) \ @WITH_OPENVSWITCH_TRUE@ $(GLIB_LIBS) \ @WITH_OPENVSWITCH_TRUE@ $(NULL) -src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS = $(src_cppflags_test) -src_dnsmasq_tests_test_dnsmasq_utils_LDADD = \ - src/libNetworkManagerTest.la +src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS = $(src_core_cppflags_test) +src_core_dnsmasq_tests_test_dnsmasq_utils_LDADD = \ + src/core/libNetworkManagerTest.la -src_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS = \ +src_core_dnsmasq_tests_test_dnsmasq_utils_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) ############################################################################### -# src/platform/tests +# src/core/platform/tests ############################################################################### -src_platform_tests_ldflags = \ +src_core_platform_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_platform_tests_libadd = \ - src/libNetworkManagerTest.la \ +src_core_platform_tests_libadd = \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) -src_platform_tests_monitor_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_monitor_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_monitor_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_address_fake_SOURCES = src/platform/tests/test-address.c -src_platform_tests_test_address_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_address_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_address_fake_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_address_linux_SOURCES = src/platform/tests/test-address.c -src_platform_tests_test_address_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_address_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_address_linux_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_cleanup_fake_SOURCES = src/platform/tests/test-cleanup.c -src_platform_tests_test_cleanup_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_cleanup_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_cleanup_fake_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_cleanup_linux_SOURCES = src/platform/tests/test-cleanup.c -src_platform_tests_test_cleanup_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_cleanup_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_cleanup_linux_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_link_fake_SOURCES = src/platform/tests/test-link.c -src_platform_tests_test_link_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_link_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_link_fake_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_link_linux_SOURCES = src/platform/tests/test-link.c -src_platform_tests_test_link_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_link_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_link_linux_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_nmp_object_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_test_nmp_object_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_nmp_object_LDADD = src/libNetworkManagerTest.la -src_platform_tests_test_platform_general_CPPFLAGS = $(src_cppflags_test) -src_platform_tests_test_platform_general_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_platform_general_LDADD = src/libNetworkManagerTest.la -src_platform_tests_test_route_fake_SOURCES = src/platform/tests/test-route.c -src_platform_tests_test_route_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_route_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_route_fake_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_route_linux_SOURCES = src/platform/tests/test-route.c -src_platform_tests_test_route_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_route_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_route_linux_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_tc_fake_SOURCES = src/platform/tests/test-tc.c -src_platform_tests_test_tc_fake_CPPFLAGS = $(src_tests_cppflags_fake) -src_platform_tests_test_tc_fake_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_tc_fake_LDADD = $(src_platform_tests_libadd) -src_platform_tests_test_tc_linux_SOURCES = src/platform/tests/test-tc.c -src_platform_tests_test_tc_linux_CPPFLAGS = $(src_tests_cppflags_linux) -src_platform_tests_test_tc_linux_LDFLAGS = $(src_platform_tests_ldflags) -src_platform_tests_test_tc_linux_LDADD = $(src_platform_tests_libadd) +src_core_platform_tests_monitor_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_monitor_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_monitor_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_address_fake_SOURCES = src/core/platform/tests/test-address.c +src_core_platform_tests_test_address_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_address_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_address_fake_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_address_linux_SOURCES = src/core/platform/tests/test-address.c +src_core_platform_tests_test_address_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_address_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_address_linux_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_cleanup_fake_SOURCES = src/core/platform/tests/test-cleanup.c +src_core_platform_tests_test_cleanup_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_cleanup_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_cleanup_fake_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_cleanup_linux_SOURCES = src/core/platform/tests/test-cleanup.c +src_core_platform_tests_test_cleanup_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_cleanup_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_cleanup_linux_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_link_fake_SOURCES = src/core/platform/tests/test-link.c +src_core_platform_tests_test_link_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_link_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_link_fake_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_link_linux_SOURCES = src/core/platform/tests/test-link.c +src_core_platform_tests_test_link_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_link_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_link_linux_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_nmp_object_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_test_nmp_object_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_nmp_object_LDADD = src/core/libNetworkManagerTest.la +src_core_platform_tests_test_platform_general_CPPFLAGS = $(src_core_cppflags_test) +src_core_platform_tests_test_platform_general_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_platform_general_LDADD = src/core/libNetworkManagerTest.la +src_core_platform_tests_test_route_fake_SOURCES = src/core/platform/tests/test-route.c +src_core_platform_tests_test_route_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_route_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_route_fake_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_route_linux_SOURCES = src/core/platform/tests/test-route.c +src_core_platform_tests_test_route_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_route_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_route_linux_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_tc_fake_SOURCES = src/core/platform/tests/test-tc.c +src_core_platform_tests_test_tc_fake_CPPFLAGS = $(src_core_tests_cppflags_fake) +src_core_platform_tests_test_tc_fake_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_tc_fake_LDADD = $(src_core_platform_tests_libadd) +src_core_platform_tests_test_tc_linux_SOURCES = src/core/platform/tests/test-tc.c +src_core_platform_tests_test_tc_linux_CPPFLAGS = $(src_core_tests_cppflags_linux) +src_core_platform_tests_test_tc_linux_LDFLAGS = $(src_core_platform_tests_ldflags) +src_core_platform_tests_test_tc_linux_LDADD = $(src_core_platform_tests_libadd) ############################################################################### -# src/devices/tests +# src/core/devices/tests ############################################################################### -src_devices_tests_ldflags = \ +src_core_devices_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_devices_tests_test_lldp_CPPFLAGS = $(src_cppflags_test) -src_devices_tests_test_lldp_LDFLAGS = $(src_devices_tests_ldflags) -src_devices_tests_test_lldp_LDADD = \ - src/libNetworkManagerTest.la +src_core_devices_tests_test_lldp_CPPFLAGS = $(src_core_cppflags_test) +src_core_devices_tests_test_lldp_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_devices_tests_test_lldp_LDADD = \ + src/core/libNetworkManagerTest.la -src_devices_tests_test_acd_CPPFLAGS = $(src_cppflags_test) -src_devices_tests_test_acd_LDFLAGS = $(src_devices_tests_ldflags) -src_devices_tests_test_acd_LDADD = \ - src/libNetworkManagerTest.la +src_core_devices_tests_test_acd_CPPFLAGS = $(src_core_cppflags_test) +src_core_devices_tests_test_acd_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_devices_tests_test_acd_LDADD = \ + src/core/libNetworkManagerTest.la ############################################################################### -# src/ndisc/tests +# src/core/ndisc/tests ############################################################################### -src_ndisc_tests_ldflags = \ +src_core_ndisc_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_ndisc_tests_ldadd = \ - src/libNetworkManagerTest.la \ +src_core_ndisc_tests_ldadd = \ + src/core/libNetworkManagerTest.la \ $(GLIB_LIBS) -src_ndisc_tests_test_ndisc_linux_CPPFLAGS = $(src_cppflags_test) -src_ndisc_tests_test_ndisc_linux_LDFLAGS = $(src_ndisc_tests_ldflags) -src_ndisc_tests_test_ndisc_linux_LDADD = $(src_ndisc_tests_ldadd) -src_ndisc_tests_test_ndisc_fake_CPPFLAGS = $(src_cppflags_test) -src_ndisc_tests_test_ndisc_fake_LDFLAGS = $(src_ndisc_tests_ldflags) -src_ndisc_tests_test_ndisc_fake_LDADD = $(src_ndisc_tests_ldadd) -src_supplicant_tests_test_supplicant_config_CPPFLAGS = $(src_cppflags_test) -src_supplicant_tests_test_supplicant_config_LDADD = \ - src/libNetworkManagerTest.la - -src_supplicant_tests_test_supplicant_config_LDFLAGS = \ +src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS = $(src_core_cppflags_test) +src_core_ndisc_tests_test_ndisc_linux_LDFLAGS = $(src_core_ndisc_tests_ldflags) +src_core_ndisc_tests_test_ndisc_linux_LDADD = $(src_core_ndisc_tests_ldadd) +src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS = $(src_core_cppflags_test) +src_core_ndisc_tests_test_ndisc_fake_LDFLAGS = $(src_core_ndisc_tests_ldflags) +src_core_ndisc_tests_test_ndisc_fake_LDADD = $(src_core_ndisc_tests_ldadd) +src_core_supplicant_tests_test_supplicant_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_supplicant_tests_test_supplicant_config_LDADD = \ + src/core/libNetworkManagerTest.la + +src_core_supplicant_tests_test_supplicant_config_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) -src_tests_config_test_config_SOURCES = \ - src/tests/config/nm-test-device.c \ - src/tests/config/nm-test-device.h \ - src/tests/config/test-config.c +src_core_tests_config_test_config_SOURCES = \ + src/core/tests/config/nm-test-device.c \ + src/core/tests/config/nm-test-device.h \ + src/core/tests/config/test-config.c -src_tests_config_test_config_CPPFLAGS = $(src_cppflags_test) -src_tests_config_test_config_LDADD = \ - src/libNetworkManagerTest.la +src_core_tests_config_test_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_config_test_config_LDADD = \ + src/core/libNetworkManagerTest.la -src_tests_config_test_config_LDFLAGS = \ +src_core_tests_config_test_config_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) ############################################################################### -# src/tests +# src/core/tests ############################################################################### -src_tests_ldflags = \ +src_core_tests_ldflags = \ $(CODE_COVERAGE_LDFLAGS) \ $(SANITIZER_EXEC_LDFLAGS) -src_tests_ldadd = \ - src/libNetworkManagerTest.la - -src_tests_test_ip4_config_CPPFLAGS = $(src_cppflags_test) -src_tests_test_ip4_config_LDFLAGS = $(src_tests_ldflags) -src_tests_test_ip4_config_LDADD = $(src_tests_ldadd) -src_tests_test_ip6_config_CPPFLAGS = $(src_cppflags_test) -src_tests_test_ip6_config_LDFLAGS = $(src_tests_ldflags) -src_tests_test_ip6_config_LDADD = $(src_tests_ldadd) -src_tests_test_dcb_CPPFLAGS = $(src_cppflags_test) -src_tests_test_dcb_LDFLAGS = $(src_tests_ldflags) -src_tests_test_dcb_LDADD = $(src_tests_ldadd) -src_tests_test_core_CPPFLAGS = $(src_cppflags_test) -src_tests_test_core_LDFLAGS = $(src_tests_ldflags) -src_tests_test_core_LDADD = $(src_tests_ldadd) -src_tests_test_core_with_expect_CPPFLAGS = $(src_cppflags_test) -src_tests_test_core_with_expect_LDFLAGS = $(src_tests_ldflags) -src_tests_test_core_with_expect_LDADD = $(src_tests_ldadd) -src_tests_test_wired_defname_CPPFLAGS = $(src_cppflags_test) -src_tests_test_wired_defname_LDFLAGS = $(src_tests_ldflags) -src_tests_test_wired_defname_LDADD = $(src_tests_ldadd) -src_tests_test_utils_CPPFLAGS = $(src_cppflags_test) -src_tests_test_utils_LDFLAGS = $(src_tests_ldflags) -src_tests_test_utils_LDADD = $(src_tests_ldadd) -src_tests_test_l3cfg_CPPFLAGS = $(src_cppflags_test) -src_tests_test_l3cfg_LDFLAGS = $(src_devices_tests_ldflags) -src_tests_test_l3cfg_LDADD = $(src_tests_ldadd) -src_tests_test_systemd_CPPFLAGS = \ - $(src_libnm_systemd_core_la_cppflags) \ +src_core_tests_ldadd = \ + src/core/libNetworkManagerTest.la + +src_core_tests_test_ip4_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_ip4_config_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_ip4_config_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_ip6_config_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_ip6_config_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_ip6_config_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_dcb_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_dcb_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_dcb_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_core_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_core_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_core_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_core_with_expect_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_core_with_expect_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_core_with_expect_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_wired_defname_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_wired_defname_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_wired_defname_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_utils_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_utils_LDFLAGS = $(src_core_tests_ldflags) +src_core_tests_test_utils_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_l3cfg_CPPFLAGS = $(src_core_cppflags_test) +src_core_tests_test_l3cfg_LDFLAGS = $(src_core_devices_tests_ldflags) +src_core_tests_test_l3cfg_LDADD = $(src_core_tests_ldadd) +src_core_tests_test_systemd_CPPFLAGS = \ + $(src_core_libnm_systemd_core_la_cppflags) \ -DNETWORKMANAGER_COMPILATION_TEST \ $(NULL) -src_tests_test_systemd_LDFLAGS = \ +src_core_tests_test_systemd_LDFLAGS = \ $(SANITIZER_EXEC_LDFLAGS) \ $(NULL) -src_tests_test_systemd_LDADD = \ - src/libnm-systemd-core.la \ +src_core_tests_test_systemd_LDADD = \ + src/core/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ @@ -9138,121 +9199,121 @@ libnm-core/nm-libnm-core-intern/libnm-libnm-core-intern.la: $(libnm_core_nm_libn libnm/$(am__dirstamp): @$(MKDIR_P) libnm @: > libnm/$(am__dirstamp) + +libnm/libnm.la: $(libnm_libnm_la_OBJECTS) $(libnm_libnm_la_DEPENDENCIES) $(EXTRA_libnm_libnm_la_DEPENDENCIES) libnm/$(am__dirstamp) + $(AM_V_CCLD)$(libnm_libnm_la_LINK) -rpath $(libdir) $(libnm_libnm_la_OBJECTS) $(libnm_libnm_la_LIBADD) $(LIBS) libnm/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) libnm/$(DEPDIR) @: > libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-client.lo: libnm/$(am__dirstamp) \ - libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-object.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-client.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-object.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-active-connection.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-access-point.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-active-connection.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-checkpoint.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-access-point.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-dbus-helpers.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-checkpoint.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-6lowpan.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-dbus-helpers.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-adsl.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-6lowpan.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-bond.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-adsl.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-bridge.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-bond.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-bt.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-bridge.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-dummy.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-bt.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ethernet.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-dummy.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-generic.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-ethernet.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-infiniband.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-generic.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ip-tunnel.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-infiniband.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-macsec.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-ip-tunnel.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-macvlan.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-macsec.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-modem.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-macvlan.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-olpc-mesh.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-modem.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ovs-bridge.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-olpc-mesh.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ovs-interface.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-ovs-bridge.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ovs-port.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-ovs-interface.lo: \ + libnm/$(am__dirstamp) libnm/$(DEPDIR)/$(am__dirstamp) +libnm/libnm_static_la-nm-device-ovs-port.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-ppp.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-ppp.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-team.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-team.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-tun.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-tun.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-veth.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-veth.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-vlan.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-vlan.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-vrf.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-vrf.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-vxlan.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-vxlan.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-wifi-p2p.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-wifi-p2p.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-wifi.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-wifi.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-wimax.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-wimax.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-wireguard.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-wireguard.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-device-wpan.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-device-wpan.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-dhcp-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-dhcp-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-dhcp4-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-dhcp4-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-dhcp6-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-dhcp6-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-dns-manager.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-dns-manager.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-ip-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-ip-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-ip4-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-ip4-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-ip6-config.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-ip6-config.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-libnm-utils.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-libnm-utils.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-remote-connection.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-remote-connection.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-secret-agent-old.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-secret-agent-old.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-vpn-connection.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-vpn-connection.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-vpn-editor.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-vpn-editor.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-vpn-plugin-old.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-vpn-plugin-old.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-vpn-service-plugin.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-vpn-service-plugin.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-wifi-p2p-peer.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-wifi-p2p-peer.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-wimax-nsp.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-wimax-nsp.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm_la-nm-enum-types.lo: libnm/$(am__dirstamp) \ +libnm/libnm_static_la-nm-enum-types.lo: libnm/$(am__dirstamp) \ libnm/$(DEPDIR)/$(am__dirstamp) -libnm/liblibnm.la: $(libnm_liblibnm_la_OBJECTS) $(libnm_liblibnm_la_DEPENDENCIES) $(EXTRA_libnm_liblibnm_la_DEPENDENCIES) libnm/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(libnm_liblibnm_la_OBJECTS) $(libnm_liblibnm_la_LIBADD) $(LIBS) - -libnm/libnm.la: $(libnm_libnm_la_OBJECTS) $(libnm_libnm_la_DEPENDENCIES) $(EXTRA_libnm_libnm_la_DEPENDENCIES) libnm/$(am__dirstamp) - $(AM_V_CCLD)$(libnm_libnm_la_LINK) -rpath $(libdir) $(libnm_libnm_la_OBJECTS) $(libnm_libnm_la_LIBADD) $(LIBS) +libnm/libnm_static.la: $(libnm_libnm_static_la_OBJECTS) $(libnm_libnm_static_la_DEPENDENCIES) $(EXTRA_libnm_libnm_static_la_DEPENDENCIES) libnm/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(libnm_libnm_static_la_OBJECTS) $(libnm_libnm_static_la_LIBADD) $(LIBS) libnm/nm-libnm-aux/$(am__dirstamp): @$(MKDIR_P) libnm/nm-libnm-aux @: > libnm/nm-libnm-aux/$(am__dirstamp) @@ -9637,720 +9698,744 @@ shared/systemd/src/shared/libnm_systemd_shared_la-web-util.lo: \ shared/systemd/libnm-systemd-shared.la: $(shared_systemd_libnm_systemd_shared_la_OBJECTS) $(shared_systemd_libnm_systemd_shared_la_DEPENDENCIES) $(EXTRA_shared_systemd_libnm_systemd_shared_la_DEPENDENCIES) shared/systemd/$(am__dirstamp) $(AM_V_CCLD)$(LINK) $(shared_systemd_libnm_systemd_shared_la_OBJECTS) $(shared_systemd_libnm_systemd_shared_la_LIBADD) $(LIBS) -src/devices/adsl/$(am__dirstamp): - @$(MKDIR_P) src/devices/adsl - @: > src/devices/adsl/$(am__dirstamp) -src/devices/adsl/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/adsl/$(DEPDIR) - @: > src/devices/adsl/$(DEPDIR)/$(am__dirstamp) -src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo: \ - src/devices/adsl/$(am__dirstamp) \ - src/devices/adsl/$(DEPDIR)/$(am__dirstamp) -src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo: \ - src/devices/adsl/$(am__dirstamp) \ - src/devices/adsl/$(DEPDIR)/$(am__dirstamp) - -src/devices/adsl/libnm-device-plugin-adsl.la: $(src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) $(src_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES) $(EXTRA_src_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES) src/devices/adsl/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_adsl_libnm_device_plugin_adsl_la_LINK) -rpath $(plugindir) $(src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) $(src_devices_adsl_libnm_device_plugin_adsl_la_LIBADD) $(LIBS) -src/devices/bluetooth/$(am__dirstamp): - @$(MKDIR_P) src/devices/bluetooth - @: > src/devices/bluetooth/$(am__dirstamp) -src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/bluetooth/$(DEPDIR) - @: > src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) -src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo: \ - src/devices/bluetooth/$(am__dirstamp) \ - src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) -src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo: \ - src/devices/bluetooth/$(am__dirstamp) \ - src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) - -src/devices/bluetooth/libnm-bluetooth-utils.la: $(src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) $(src_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES) $(EXTRA_src_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES) src/devices/bluetooth/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_devices_bluetooth_libnm_bluetooth_utils_la_rpath) $(src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) $(src_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD) $(LIBS) -src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo: \ - src/devices/bluetooth/$(am__dirstamp) \ - src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) -src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo: \ - src/devices/bluetooth/$(am__dirstamp) \ - src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) - -src/devices/bluetooth/libnm-device-plugin-bluetooth.la: $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES) $(EXTRA_src_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES) src/devices/bluetooth/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LINK) $(am_src_devices_bluetooth_libnm_device_plugin_bluetooth_la_rpath) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD) $(LIBS) -src/devices/ovs/$(am__dirstamp): - @$(MKDIR_P) src/devices/ovs - @: > src/devices/ovs/$(am__dirstamp) -src/devices/ovs/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/ovs/$(DEPDIR) - @: > src/devices/ovs/$(DEPDIR)/$(am__dirstamp) -src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo: \ - src/devices/ovs/$(am__dirstamp) \ - src/devices/ovs/$(DEPDIR)/$(am__dirstamp) -src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo: \ - src/devices/ovs/$(am__dirstamp) \ - src/devices/ovs/$(DEPDIR)/$(am__dirstamp) -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo: \ - src/devices/ovs/$(am__dirstamp) \ - src/devices/ovs/$(DEPDIR)/$(am__dirstamp) -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo: \ - src/devices/ovs/$(am__dirstamp) \ - src/devices/ovs/$(DEPDIR)/$(am__dirstamp) -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo: \ - src/devices/ovs/$(am__dirstamp) \ - src/devices/ovs/$(DEPDIR)/$(am__dirstamp) - -src/devices/ovs/libnm-device-plugin-ovs.la: $(src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) $(src_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES) $(EXTRA_src_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES) src/devices/ovs/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_ovs_libnm_device_plugin_ovs_la_LINK) $(am_src_devices_ovs_libnm_device_plugin_ovs_la_rpath) $(src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) $(src_devices_ovs_libnm_device_plugin_ovs_la_LIBADD) $(LIBS) -src/devices/team/$(am__dirstamp): - @$(MKDIR_P) src/devices/team - @: > src/devices/team/$(am__dirstamp) -src/devices/team/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/team/$(DEPDIR) - @: > src/devices/team/$(DEPDIR)/$(am__dirstamp) -src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo: \ - src/devices/team/$(am__dirstamp) \ - src/devices/team/$(DEPDIR)/$(am__dirstamp) -src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo: \ - src/devices/team/$(am__dirstamp) \ - src/devices/team/$(DEPDIR)/$(am__dirstamp) - -src/devices/team/libnm-device-plugin-team.la: $(src_devices_team_libnm_device_plugin_team_la_OBJECTS) $(src_devices_team_libnm_device_plugin_team_la_DEPENDENCIES) $(EXTRA_src_devices_team_libnm_device_plugin_team_la_DEPENDENCIES) src/devices/team/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_team_libnm_device_plugin_team_la_LINK) $(am_src_devices_team_libnm_device_plugin_team_la_rpath) $(src_devices_team_libnm_device_plugin_team_la_OBJECTS) $(src_devices_team_libnm_device_plugin_team_la_LIBADD) $(LIBS) -src/devices/wifi/$(am__dirstamp): - @$(MKDIR_P) src/devices/wifi - @: > src/devices/wifi/$(am__dirstamp) -src/devices/wifi/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/wifi/$(DEPDIR) - @: > src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) - -src/devices/wifi/libnm-device-plugin-wifi.la: $(src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) $(src_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES) $(EXTRA_src_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES) src/devices/wifi/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_wifi_libnm_device_plugin_wifi_la_LINK) $(am_src_devices_wifi_libnm_device_plugin_wifi_la_rpath) $(src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) $(src_devices_wifi_libnm_device_plugin_wifi_la_LIBADD) $(LIBS) -src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo: \ - src/devices/wifi/$(am__dirstamp) \ - src/devices/wifi/$(DEPDIR)/$(am__dirstamp) - -src/devices/wifi/libnm-wifi-base.la: $(src_devices_wifi_libnm_wifi_base_la_OBJECTS) $(src_devices_wifi_libnm_wifi_base_la_DEPENDENCIES) $(EXTRA_src_devices_wifi_libnm_wifi_base_la_DEPENDENCIES) src/devices/wifi/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_devices_wifi_libnm_wifi_base_la_rpath) $(src_devices_wifi_libnm_wifi_base_la_OBJECTS) $(src_devices_wifi_libnm_wifi_base_la_LIBADD) $(LIBS) -src/devices/wwan/$(am__dirstamp): - @$(MKDIR_P) src/devices/wwan - @: > src/devices/wwan/$(am__dirstamp) -src/devices/wwan/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/wwan/$(DEPDIR) - @: > src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) - -src/devices/wwan/libnm-device-plugin-wwan.la: $(src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) $(src_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES) $(EXTRA_src_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES) src/devices/wwan/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_wwan_libnm_device_plugin_wwan_la_LINK) $(am_src_devices_wwan_libnm_device_plugin_wwan_la_rpath) $(src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) $(src_devices_wwan_libnm_device_plugin_wwan_la_LIBADD) $(LIBS) -src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_wwan_la-nm-modem.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_wwan_la-nm-service-providers.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo: \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) - -src/devices/wwan/libnm-wwan.la: $(src_devices_wwan_libnm_wwan_la_OBJECTS) $(src_devices_wwan_libnm_wwan_la_DEPENDENCIES) $(EXTRA_src_devices_wwan_libnm_wwan_la_DEPENDENCIES) src/devices/wwan/$(am__dirstamp) - $(AM_V_CCLD)$(src_devices_wwan_libnm_wwan_la_LINK) $(am_src_devices_wwan_libnm_wwan_la_rpath) $(src_devices_wwan_libnm_wwan_la_OBJECTS) $(src_devices_wwan_libnm_wwan_la_LIBADD) $(LIBS) -src/initrd/$(am__dirstamp): - @$(MKDIR_P) src/initrd - @: > src/initrd/$(am__dirstamp) -src/initrd/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/initrd/$(DEPDIR) - @: > src/initrd/$(DEPDIR)/$(am__dirstamp) -src/initrd/libnmi_core_la-nmi-cmdline-reader.lo: \ - src/initrd/$(am__dirstamp) \ - src/initrd/$(DEPDIR)/$(am__dirstamp) -src/initrd/libnmi_core_la-nmi-dt-reader.lo: \ - src/initrd/$(am__dirstamp) \ - src/initrd/$(DEPDIR)/$(am__dirstamp) -src/initrd/libnmi_core_la-nmi-ibft-reader.lo: \ - src/initrd/$(am__dirstamp) \ - src/initrd/$(DEPDIR)/$(am__dirstamp) - -src/initrd/libnmi-core.la: $(src_initrd_libnmi_core_la_OBJECTS) $(src_initrd_libnmi_core_la_DEPENDENCIES) $(EXTRA_src_initrd_libnmi_core_la_DEPENDENCIES) src/initrd/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(src_initrd_libnmi_core_la_OBJECTS) $(src_initrd_libnmi_core_la_LIBADD) $(LIBS) -src/$(am__dirstamp): - @$(MKDIR_P) src - @: > src/$(am__dirstamp) -src/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/$(DEPDIR) - @: > src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-checkpoint.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-checkpoint-manager.lo: \ - src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) -src/devices/$(am__dirstamp): - @$(MKDIR_P) src/devices - @: > src/devices/$(am__dirstamp) -src/devices/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/$(DEPDIR) - @: > src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-acd-manager.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-lldp-listener.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-factory.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-generic.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-6lowpan.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-bond.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-bridge.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-dummy.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-ethernet.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-infiniband.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-macsec.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-macvlan.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-ppp.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-tun.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-veth.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-vlan.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-vrf.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-vxlan.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-wireguard.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/devices/libNetworkManager_la-nm-device-wpan.lo: \ - src/devices/$(am__dirstamp) \ - src/devices/$(DEPDIR)/$(am__dirstamp) -src/dhcp/$(am__dirstamp): - @$(MKDIR_P) src/dhcp - @: > src/dhcp/$(am__dirstamp) -src/dhcp/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/dhcp/$(DEPDIR) - @: > src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dns/$(am__dirstamp): - @$(MKDIR_P) src/dns - @: > src/dns/$(am__dirstamp) -src/dns/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/dns/$(DEPDIR) - @: > src/dns/$(DEPDIR)/$(am__dirstamp) -src/dns/libNetworkManager_la-nm-dns-manager.lo: \ - src/dns/$(am__dirstamp) src/dns/$(DEPDIR)/$(am__dirstamp) -src/dns/libNetworkManager_la-nm-dns-plugin.lo: \ - src/dns/$(am__dirstamp) src/dns/$(DEPDIR)/$(am__dirstamp) -src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo: \ - src/dns/$(am__dirstamp) src/dns/$(DEPDIR)/$(am__dirstamp) -src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo: \ - src/dns/$(am__dirstamp) src/dns/$(DEPDIR)/$(am__dirstamp) -src/dns/libNetworkManager_la-nm-dns-unbound.lo: \ - src/dns/$(am__dirstamp) src/dns/$(DEPDIR)/$(am__dirstamp) -src/dnsmasq/$(am__dirstamp): - @$(MKDIR_P) src/dnsmasq - @: > src/dnsmasq/$(am__dirstamp) -src/dnsmasq/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/dnsmasq/$(DEPDIR) - @: > src/dnsmasq/$(DEPDIR)/$(am__dirstamp) -src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo: \ - src/dnsmasq/$(am__dirstamp) \ - src/dnsmasq/$(DEPDIR)/$(am__dirstamp) -src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo: \ - src/dnsmasq/$(am__dirstamp) \ - src/dnsmasq/$(DEPDIR)/$(am__dirstamp) -src/ppp/$(am__dirstamp): - @$(MKDIR_P) src/ppp - @: > src/ppp/$(am__dirstamp) -src/ppp/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/ppp/$(DEPDIR) - @: > src/ppp/$(DEPDIR)/$(am__dirstamp) -src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo: \ - src/ppp/$(am__dirstamp) src/ppp/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-hostname-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/settings/$(am__dirstamp): - @$(MKDIR_P) src/settings - @: > src/settings/$(am__dirstamp) -src/settings/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/$(DEPDIR) - @: > src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-agent-manager.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-secret-agent.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-settings-connection.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-settings-storage.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-settings-plugin.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-settings.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/libNetworkManager_la-nm-settings-utils.lo: \ - src/settings/$(am__dirstamp) \ - src/settings/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/keyfile - @: > src/settings/plugins/keyfile/$(am__dirstamp) -src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/keyfile/$(DEPDIR) - @: > src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo: \ - src/settings/plugins/keyfile/$(am__dirstamp) \ - src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo: \ - src/settings/plugins/keyfile/$(am__dirstamp) \ - src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo: \ - src/settings/plugins/keyfile/$(am__dirstamp) \ - src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo: \ - src/settings/plugins/keyfile/$(am__dirstamp) \ - src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo: \ - src/settings/plugins/keyfile/$(am__dirstamp) \ - src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) -src/supplicant/$(am__dirstamp): - @$(MKDIR_P) src/supplicant - @: > src/supplicant/$(am__dirstamp) -src/supplicant/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/supplicant/$(DEPDIR) - @: > src/supplicant/$(DEPDIR)/$(am__dirstamp) -src/supplicant/libNetworkManager_la-nm-supplicant-config.lo: \ - src/supplicant/$(am__dirstamp) \ - src/supplicant/$(DEPDIR)/$(am__dirstamp) -src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo: \ - src/supplicant/$(am__dirstamp) \ - src/supplicant/$(DEPDIR)/$(am__dirstamp) -src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo: \ - src/supplicant/$(am__dirstamp) \ - src/supplicant/$(DEPDIR)/$(am__dirstamp) -src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo: \ - src/supplicant/$(am__dirstamp) \ - src/supplicant/$(DEPDIR)/$(am__dirstamp) -src/vpn/$(am__dirstamp): - @$(MKDIR_P) src/vpn - @: > src/vpn/$(am__dirstamp) -src/vpn/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/vpn/$(DEPDIR) - @: > src/vpn/$(DEPDIR)/$(am__dirstamp) -src/vpn/libNetworkManager_la-nm-vpn-connection.lo: \ - src/vpn/$(am__dirstamp) src/vpn/$(DEPDIR)/$(am__dirstamp) -src/vpn/libNetworkManager_la-nm-vpn-manager.lo: \ - src/vpn/$(am__dirstamp) src/vpn/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-act-request.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-active-connection.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-audit-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-dbus-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-config-data.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-connectivity.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-dcb.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-dhcp-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-dispatcher.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-firewall-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-proxy-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-auth-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-auth-utils.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-pacrunner-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-policy.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-rfkill-manager.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-session-monitor.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-keep-alive.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManager_la-nm-sleep-monitor.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) - -src/libNetworkManager.la: $(src_libNetworkManager_la_OBJECTS) $(src_libNetworkManager_la_DEPENDENCIES) $(EXTRA_src_libNetworkManager_la_DEPENDENCIES) src/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(src_libNetworkManager_la_OBJECTS) $(src_libNetworkManager_la_LIBADD) $(LIBS) -src/libNetworkManagerBase_la-nm-core-utils.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-NetworkManagerUtils.lo: \ - src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) -src/platform/$(am__dirstamp): - @$(MKDIR_P) src/platform - @: > src/platform/$(am__dirstamp) -src/platform/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/platform/$(DEPDIR) - @: > src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/libNetworkManagerBase_la-nmp-object.lo: \ - src/platform/$(am__dirstamp) \ - src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/libNetworkManagerBase_la-nm-platform.lo: \ - src/platform/$(am__dirstamp) \ - src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/libNetworkManagerBase_la-nm-linux-platform.lo: \ - src/platform/$(am__dirstamp) \ - src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo: \ - src/platform/$(am__dirstamp) \ - src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/wifi/$(am__dirstamp): - @$(MKDIR_P) src/platform/wifi - @: > src/platform/wifi/$(am__dirstamp) -src/platform/wifi/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/platform/wifi/$(DEPDIR) - @: > src/platform/wifi/$(DEPDIR)/$(am__dirstamp) -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo: \ - src/platform/wifi/$(am__dirstamp) \ - src/platform/wifi/$(DEPDIR)/$(am__dirstamp) -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo: \ - src/platform/wifi/$(am__dirstamp) \ - src/platform/wifi/$(DEPDIR)/$(am__dirstamp) -src/platform/wpan/$(am__dirstamp): - @$(MKDIR_P) src/platform/wpan - @: > src/platform/wpan/$(am__dirstamp) -src/platform/wpan/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/platform/wpan/$(DEPDIR) - @: > src/platform/wpan/$(DEPDIR)/$(am__dirstamp) -src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo: \ - src/platform/wpan/$(am__dirstamp) \ - src/platform/wpan/$(DEPDIR)/$(am__dirstamp) -src/ndisc/$(am__dirstamp): - @$(MKDIR_P) src/ndisc - @: > src/ndisc/$(am__dirstamp) -src/ndisc/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/ndisc/$(DEPDIR) - @: > src/ndisc/$(DEPDIR)/$(am__dirstamp) -src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo: \ - src/ndisc/$(am__dirstamp) src/ndisc/$(DEPDIR)/$(am__dirstamp) -src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo: \ - src/ndisc/$(am__dirstamp) src/ndisc/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-dbus-utils.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-dbus-object.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-netns.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-l3-config-data.lo: \ - src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-l3cfg.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-ip-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-ip4-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-nm-ip6-config.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo: \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) -src/libNetworkManagerBase_la-main-utils.lo: src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo: \ - src/platform/wifi/$(am__dirstamp) \ - src/platform/wifi/$(DEPDIR)/$(am__dirstamp) - -src/libNetworkManagerBase.la: $(src_libNetworkManagerBase_la_OBJECTS) $(src_libNetworkManagerBase_la_DEPENDENCIES) $(EXTRA_src_libNetworkManagerBase_la_DEPENDENCIES) src/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(src_libNetworkManagerBase_la_OBJECTS) $(src_libNetworkManagerBase_la_LIBADD) $(LIBS) -src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo: \ - src/ndisc/$(am__dirstamp) src/ndisc/$(DEPDIR)/$(am__dirstamp) -src/platform/libNetworkManagerTest_la-nm-fake-platform.lo: \ - src/platform/$(am__dirstamp) \ - src/platform/$(DEPDIR)/$(am__dirstamp) -src/platform/tests/$(am__dirstamp): - @$(MKDIR_P) src/platform/tests - @: > src/platform/tests/$(am__dirstamp) -src/platform/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/platform/tests/$(DEPDIR) - @: > src/platform/tests/$(DEPDIR)/$(am__dirstamp) -src/platform/tests/libNetworkManagerTest_la-test-common.lo: \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/libNetworkManagerTest.la: $(src_libNetworkManagerTest_la_OBJECTS) $(src_libNetworkManagerTest_la_DEPENDENCIES) $(EXTRA_src_libNetworkManagerTest_la_DEPENDENCIES) src/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_libNetworkManagerTest_la_rpath) $(src_libNetworkManagerTest_la_OBJECTS) $(src_libNetworkManagerTest_la_LIBADD) $(LIBS) -src/systemd/$(am__dirstamp): - @$(MKDIR_P) src/systemd - @: > src/systemd/$(am__dirstamp) -src/systemd/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/systemd/$(DEPDIR) - @: > src/systemd/$(DEPDIR)/$(am__dirstamp) -src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo: \ - src/systemd/$(am__dirstamp) \ - src/systemd/$(DEPDIR)/$(am__dirstamp) -src/systemd/libnm_systemd_core_la-nm-sd.lo: \ - src/systemd/$(am__dirstamp) \ - src/systemd/$(DEPDIR)/$(am__dirstamp) -src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo: \ - src/systemd/$(am__dirstamp) \ - src/systemd/$(DEPDIR)/$(am__dirstamp) -src/systemd/sd-adapt-core/$(am__dirstamp): - @$(MKDIR_P) src/systemd/sd-adapt-core - @: > src/systemd/sd-adapt-core/$(am__dirstamp) -src/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/systemd/sd-adapt-core/$(DEPDIR) - @: > src/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) -src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo: \ - src/systemd/sd-adapt-core/$(am__dirstamp) \ - src/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd-network - @: > src/systemd/src/libsystemd-network/$(am__dirstamp) -src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd-network/$(DEPDIR) - @: > src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo: \ - src/systemd/src/libsystemd-network/$(am__dirstamp) \ - src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-event/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd/sd-event - @: > src/systemd/src/libsystemd/sd-event/$(am__dirstamp) -src/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd/sd-event/$(DEPDIR) - @: > src/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo: \ - src/systemd/src/libsystemd/sd-event/$(am__dirstamp) \ - src/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo: \ - src/systemd/src/libsystemd/sd-event/$(am__dirstamp) \ - src/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-id128/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd/sd-id128 - @: > src/systemd/src/libsystemd/sd-id128/$(am__dirstamp) -src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/systemd/src/libsystemd/sd-id128/$(DEPDIR) - @: > src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo: \ - src/systemd/src/libsystemd/sd-id128/$(am__dirstamp) \ - src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) -src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo: \ - src/systemd/src/libsystemd/sd-id128/$(am__dirstamp) \ - src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) - -src/libnm-systemd-core.la: $(src_libnm_systemd_core_la_OBJECTS) $(src_libnm_systemd_core_la_DEPENDENCIES) $(EXTRA_src_libnm_systemd_core_la_DEPENDENCIES) src/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(src_libnm_systemd_core_la_OBJECTS) $(src_libnm_systemd_core_la_LIBADD) $(LIBS) -src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo: \ - src/ppp/$(am__dirstamp) src/ppp/$(DEPDIR)/$(am__dirstamp) - -src/ppp/libnm-ppp-plugin.la: $(src_ppp_libnm_ppp_plugin_la_OBJECTS) $(src_ppp_libnm_ppp_plugin_la_DEPENDENCIES) $(EXTRA_src_ppp_libnm_ppp_plugin_la_DEPENDENCIES) src/ppp/$(am__dirstamp) - $(AM_V_CCLD)$(src_ppp_libnm_ppp_plugin_la_LINK) $(am_src_ppp_libnm_ppp_plugin_la_rpath) $(src_ppp_libnm_ppp_plugin_la_OBJECTS) $(src_ppp_libnm_ppp_plugin_la_LIBADD) $(LIBS) -src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo: src/ppp/$(am__dirstamp) \ - src/ppp/$(DEPDIR)/$(am__dirstamp) - -src/ppp/nm-pppd-plugin.la: $(src_ppp_nm_pppd_plugin_la_OBJECTS) $(src_ppp_nm_pppd_plugin_la_DEPENDENCIES) $(EXTRA_src_ppp_nm_pppd_plugin_la_DEPENDENCIES) src/ppp/$(am__dirstamp) - $(AM_V_CCLD)$(src_ppp_nm_pppd_plugin_la_LINK) $(am_src_ppp_nm_pppd_plugin_la_rpath) $(src_ppp_nm_pppd_plugin_la_OBJECTS) $(src_ppp_nm_pppd_plugin_la_LIBADD) $(LIBS) -src/settings/plugins/ifcfg-rh/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifcfg-rh - @: > src/settings/plugins/ifcfg-rh/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifcfg-rh/$(DEPDIR) - @: > src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la: $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES) src/settings/plugins/ifcfg-rh/$(am__dirstamp) - $(AM_V_CCLD)$(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LINK) $(am_src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_rpath) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD) $(LIBS) -src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la: $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_DEPENDENCIES) src/settings/plugins/ifcfg-rh/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_rpath) $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_LIBADD) $(LIBS) -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo: \ - src/settings/plugins/ifcfg-rh/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la: $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_DEPENDENCIES) src/settings/plugins/ifcfg-rh/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_rpath) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_LIBADD) $(LIBS) -src/settings/plugins/ifupdown/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifupdown - @: > src/settings/plugins/ifupdown/$(am__dirstamp) -src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifupdown/$(DEPDIR) - @: > src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo: \ - src/settings/plugins/ifupdown/$(am__dirstamp) \ - src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la: $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES) src/settings/plugins/ifupdown/$(am__dirstamp) - $(AM_V_CCLD)$(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LINK) $(am_src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_rpath) $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD) $(LIBS) -src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo: \ - src/settings/plugins/ifupdown/$(am__dirstamp) \ - src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo: \ - src/settings/plugins/ifupdown/$(am__dirstamp) \ - src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifupdown/libnms-ifupdown-core.la: $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES) src/settings/plugins/ifupdown/$(am__dirstamp) - $(AM_V_CCLD)$(LINK) $(am_src_settings_plugins_ifupdown_libnms_ifupdown_core_la_rpath) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD) $(LIBS) +src/core/devices/adsl/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/adsl + @: > src/core/devices/adsl/$(am__dirstamp) +src/core/devices/adsl/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/adsl/$(DEPDIR) + @: > src/core/devices/adsl/$(DEPDIR)/$(am__dirstamp) +src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo: \ + src/core/devices/adsl/$(am__dirstamp) \ + src/core/devices/adsl/$(DEPDIR)/$(am__dirstamp) +src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo: \ + src/core/devices/adsl/$(am__dirstamp) \ + src/core/devices/adsl/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/adsl/libnm-device-plugin-adsl.la: $(src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES) $(EXTRA_src_core_devices_adsl_libnm_device_plugin_adsl_la_DEPENDENCIES) src/core/devices/adsl/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_adsl_libnm_device_plugin_adsl_la_LINK) -rpath $(plugindir) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_LIBADD) $(LIBS) +src/core/devices/bluetooth/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/bluetooth + @: > src/core/devices/bluetooth/$(am__dirstamp) +src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/bluetooth/$(DEPDIR) + @: > src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) +src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo: \ + src/core/devices/bluetooth/$(am__dirstamp) \ + src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) +src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo: \ + src/core/devices/bluetooth/$(am__dirstamp) \ + src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/bluetooth/libnm-bluetooth-utils.la: $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES) $(EXTRA_src_core_devices_bluetooth_libnm_bluetooth_utils_la_DEPENDENCIES) src/core/devices/bluetooth/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_devices_bluetooth_libnm_bluetooth_utils_la_rpath) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_LIBADD) $(LIBS) +src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo: \ + src/core/devices/bluetooth/$(am__dirstamp) \ + src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) +src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo: \ + src/core/devices/bluetooth/$(am__dirstamp) \ + src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/bluetooth/libnm-device-plugin-bluetooth.la: $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES) $(EXTRA_src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_DEPENDENCIES) src/core/devices/bluetooth/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LINK) $(am_src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_rpath) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_LIBADD) $(LIBS) +src/core/devices/ovs/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/ovs + @: > src/core/devices/ovs/$(am__dirstamp) +src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/ovs/$(DEPDIR) + @: > src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo: \ + src/core/devices/ovs/$(am__dirstamp) \ + src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo: \ + src/core/devices/ovs/$(am__dirstamp) \ + src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo: \ + src/core/devices/ovs/$(am__dirstamp) \ + src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo: \ + src/core/devices/ovs/$(am__dirstamp) \ + src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo: \ + src/core/devices/ovs/$(am__dirstamp) \ + src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/ovs/libnm-device-plugin-ovs.la: $(src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES) $(EXTRA_src_core_devices_ovs_libnm_device_plugin_ovs_la_DEPENDENCIES) src/core/devices/ovs/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_ovs_libnm_device_plugin_ovs_la_LINK) $(am_src_core_devices_ovs_libnm_device_plugin_ovs_la_rpath) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_LIBADD) $(LIBS) +src/core/devices/team/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/team + @: > src/core/devices/team/$(am__dirstamp) +src/core/devices/team/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/team/$(DEPDIR) + @: > src/core/devices/team/$(DEPDIR)/$(am__dirstamp) +src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo: \ + src/core/devices/team/$(am__dirstamp) \ + src/core/devices/team/$(DEPDIR)/$(am__dirstamp) +src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo: \ + src/core/devices/team/$(am__dirstamp) \ + src/core/devices/team/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/team/libnm-device-plugin-team.la: $(src_core_devices_team_libnm_device_plugin_team_la_OBJECTS) $(src_core_devices_team_libnm_device_plugin_team_la_DEPENDENCIES) $(EXTRA_src_core_devices_team_libnm_device_plugin_team_la_DEPENDENCIES) src/core/devices/team/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_team_libnm_device_plugin_team_la_LINK) $(am_src_core_devices_team_libnm_device_plugin_team_la_rpath) $(src_core_devices_team_libnm_device_plugin_team_la_OBJECTS) $(src_core_devices_team_libnm_device_plugin_team_la_LIBADD) $(LIBS) +src/core/devices/wifi/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wifi + @: > src/core/devices/wifi/$(am__dirstamp) +src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wifi/$(DEPDIR) + @: > src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wifi/libnm-device-plugin-wifi.la: $(src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) $(src_core_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES) $(EXTRA_src_core_devices_wifi_libnm_device_plugin_wifi_la_DEPENDENCIES) src/core/devices/wifi/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_wifi_libnm_device_plugin_wifi_la_LINK) $(am_src_core_devices_wifi_libnm_device_plugin_wifi_la_rpath) $(src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS) $(src_core_devices_wifi_libnm_device_plugin_wifi_la_LIBADD) $(LIBS) +src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo: \ + src/core/devices/wifi/$(am__dirstamp) \ + src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wifi/libnm-wifi-base.la: $(src_core_devices_wifi_libnm_wifi_base_la_OBJECTS) $(src_core_devices_wifi_libnm_wifi_base_la_DEPENDENCIES) $(EXTRA_src_core_devices_wifi_libnm_wifi_base_la_DEPENDENCIES) src/core/devices/wifi/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_devices_wifi_libnm_wifi_base_la_rpath) $(src_core_devices_wifi_libnm_wifi_base_la_OBJECTS) $(src_core_devices_wifi_libnm_wifi_base_la_LIBADD) $(LIBS) +src/core/devices/wwan/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wwan + @: > src/core/devices/wwan/$(am__dirstamp) +src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wwan/$(DEPDIR) + @: > src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wwan/libnm-device-plugin-wwan.la: $(src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES) $(EXTRA_src_core_devices_wwan_libnm_device_plugin_wwan_la_DEPENDENCIES) src/core/devices/wwan/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_wwan_libnm_device_plugin_wwan_la_LINK) $(am_src_core_devices_wwan_libnm_device_plugin_wwan_la_rpath) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_LIBADD) $(LIBS) +src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_wwan_la-nm-modem.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo: \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wwan/libnm-wwan.la: $(src_core_devices_wwan_libnm_wwan_la_OBJECTS) $(src_core_devices_wwan_libnm_wwan_la_DEPENDENCIES) $(EXTRA_src_core_devices_wwan_libnm_wwan_la_DEPENDENCIES) src/core/devices/wwan/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_devices_wwan_libnm_wwan_la_LINK) $(am_src_core_devices_wwan_libnm_wwan_la_rpath) $(src_core_devices_wwan_libnm_wwan_la_OBJECTS) $(src_core_devices_wwan_libnm_wwan_la_LIBADD) $(LIBS) +src/core/initrd/$(am__dirstamp): + @$(MKDIR_P) src/core/initrd + @: > src/core/initrd/$(am__dirstamp) +src/core/initrd/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/initrd/$(DEPDIR) + @: > src/core/initrd/$(DEPDIR)/$(am__dirstamp) +src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo: \ + src/core/initrd/$(am__dirstamp) \ + src/core/initrd/$(DEPDIR)/$(am__dirstamp) +src/core/initrd/libnmi_core_la-nmi-dt-reader.lo: \ + src/core/initrd/$(am__dirstamp) \ + src/core/initrd/$(DEPDIR)/$(am__dirstamp) +src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo: \ + src/core/initrd/$(am__dirstamp) \ + src/core/initrd/$(DEPDIR)/$(am__dirstamp) + +src/core/initrd/libnmi-core.la: $(src_core_initrd_libnmi_core_la_OBJECTS) $(src_core_initrd_libnmi_core_la_DEPENDENCIES) $(EXTRA_src_core_initrd_libnmi_core_la_DEPENDENCIES) src/core/initrd/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_core_initrd_libnmi_core_la_OBJECTS) $(src_core_initrd_libnmi_core_la_LIBADD) $(LIBS) +src/core/$(am__dirstamp): + @$(MKDIR_P) src/core + @: > src/core/$(am__dirstamp) +src/core/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/$(DEPDIR) + @: > src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-checkpoint.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-checkpoint-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/devices/$(am__dirstamp): + @$(MKDIR_P) src/core/devices + @: > src/core/devices/$(am__dirstamp) +src/core/devices/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/$(DEPDIR) + @: > src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-acd-manager.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-lldp-listener.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-factory.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-generic.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-bond.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-bridge.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-dummy.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-ethernet.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-infiniband.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-macsec.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-macvlan.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-ppp.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-tun.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-veth.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-vlan.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-vrf.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-vxlan.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-wireguard.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/devices/libNetworkManager_la-nm-device-wpan.lo: \ + src/core/devices/$(am__dirstamp) \ + src/core/devices/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/$(am__dirstamp): + @$(MKDIR_P) src/core/dhcp + @: > src/core/dhcp/$(am__dirstamp) +src/core/dhcp/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/dhcp/$(DEPDIR) + @: > src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dns/$(am__dirstamp): + @$(MKDIR_P) src/core/dns + @: > src/core/dns/$(am__dirstamp) +src/core/dns/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/dns/$(DEPDIR) + @: > src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dns/libNetworkManager_la-nm-dns-manager.lo: \ + src/core/dns/$(am__dirstamp) \ + src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dns/libNetworkManager_la-nm-dns-plugin.lo: \ + src/core/dns/$(am__dirstamp) \ + src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo: \ + src/core/dns/$(am__dirstamp) \ + src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo: \ + src/core/dns/$(am__dirstamp) \ + src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dns/libNetworkManager_la-nm-dns-unbound.lo: \ + src/core/dns/$(am__dirstamp) \ + src/core/dns/$(DEPDIR)/$(am__dirstamp) +src/core/dnsmasq/$(am__dirstamp): + @$(MKDIR_P) src/core/dnsmasq + @: > src/core/dnsmasq/$(am__dirstamp) +src/core/dnsmasq/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/dnsmasq/$(DEPDIR) + @: > src/core/dnsmasq/$(DEPDIR)/$(am__dirstamp) +src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo: \ + src/core/dnsmasq/$(am__dirstamp) \ + src/core/dnsmasq/$(DEPDIR)/$(am__dirstamp) +src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo: \ + src/core/dnsmasq/$(am__dirstamp) \ + src/core/dnsmasq/$(DEPDIR)/$(am__dirstamp) +src/core/ppp/$(am__dirstamp): + @$(MKDIR_P) src/core/ppp + @: > src/core/ppp/$(am__dirstamp) +src/core/ppp/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/ppp/$(DEPDIR) + @: > src/core/ppp/$(DEPDIR)/$(am__dirstamp) +src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo: \ + src/core/ppp/$(am__dirstamp) \ + src/core/ppp/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-hostname-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/settings/$(am__dirstamp): + @$(MKDIR_P) src/core/settings + @: > src/core/settings/$(am__dirstamp) +src/core/settings/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/$(DEPDIR) + @: > src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-agent-manager.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-secret-agent.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-settings-connection.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-settings-storage.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-settings-plugin.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-settings.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/libNetworkManager_la-nm-settings-utils.lo: \ + src/core/settings/$(am__dirstamp) \ + src/core/settings/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/keyfile + @: > src/core/settings/plugins/keyfile/$(am__dirstamp) +src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/keyfile/$(DEPDIR) + @: > src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo: \ + src/core/settings/plugins/keyfile/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo: \ + src/core/settings/plugins/keyfile/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo: \ + src/core/settings/plugins/keyfile/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo: \ + src/core/settings/plugins/keyfile/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo: \ + src/core/settings/plugins/keyfile/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/$(am__dirstamp): + @$(MKDIR_P) src/core/supplicant + @: > src/core/supplicant/$(am__dirstamp) +src/core/supplicant/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/supplicant/$(DEPDIR) + @: > src/core/supplicant/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo: \ + src/core/supplicant/$(am__dirstamp) \ + src/core/supplicant/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo: \ + src/core/supplicant/$(am__dirstamp) \ + src/core/supplicant/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo: \ + src/core/supplicant/$(am__dirstamp) \ + src/core/supplicant/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo: \ + src/core/supplicant/$(am__dirstamp) \ + src/core/supplicant/$(DEPDIR)/$(am__dirstamp) +src/core/vpn/$(am__dirstamp): + @$(MKDIR_P) src/core/vpn + @: > src/core/vpn/$(am__dirstamp) +src/core/vpn/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/vpn/$(DEPDIR) + @: > src/core/vpn/$(DEPDIR)/$(am__dirstamp) +src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo: \ + src/core/vpn/$(am__dirstamp) \ + src/core/vpn/$(DEPDIR)/$(am__dirstamp) +src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo: \ + src/core/vpn/$(am__dirstamp) \ + src/core/vpn/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-act-request.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-active-connection.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-audit-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-dbus-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-config.lo: src/core/$(am__dirstamp) \ + src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-config-data.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-connectivity.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-dcb.lo: src/core/$(am__dirstamp) \ + src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-dhcp-config.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-dispatcher.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-firewall-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-proxy-config.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-auth-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-auth-utils.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-manager.lo: src/core/$(am__dirstamp) \ + src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-pacrunner-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-policy.lo: src/core/$(am__dirstamp) \ + src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-rfkill-manager.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-session-monitor.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-keep-alive.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManager_la-nm-sleep-monitor.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) + +src/core/libNetworkManager.la: $(src_core_libNetworkManager_la_OBJECTS) $(src_core_libNetworkManager_la_DEPENDENCIES) $(EXTRA_src_core_libNetworkManager_la_DEPENDENCIES) src/core/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_core_libNetworkManager_la_OBJECTS) $(src_core_libNetworkManager_la_LIBADD) $(LIBS) +src/core/libNetworkManagerBase_la-nm-core-utils.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/platform/$(am__dirstamp): + @$(MKDIR_P) src/core/platform + @: > src/core/platform/$(am__dirstamp) +src/core/platform/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/$(DEPDIR) + @: > src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/libNetworkManagerBase_la-nmp-object.lo: \ + src/core/platform/$(am__dirstamp) \ + src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/libNetworkManagerBase_la-nm-platform.lo: \ + src/core/platform/$(am__dirstamp) \ + src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo: \ + src/core/platform/$(am__dirstamp) \ + src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo: \ + src/core/platform/$(am__dirstamp) \ + src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wifi/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/wifi + @: > src/core/platform/wifi/$(am__dirstamp) +src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/wifi/$(DEPDIR) + @: > src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo: \ + src/core/platform/wifi/$(am__dirstamp) \ + src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo: \ + src/core/platform/wifi/$(am__dirstamp) \ + src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wpan/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/wpan + @: > src/core/platform/wpan/$(am__dirstamp) +src/core/platform/wpan/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/wpan/$(DEPDIR) + @: > src/core/platform/wpan/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo: \ + src/core/platform/wpan/$(am__dirstamp) \ + src/core/platform/wpan/$(DEPDIR)/$(am__dirstamp) +src/core/ndisc/$(am__dirstamp): + @$(MKDIR_P) src/core/ndisc + @: > src/core/ndisc/$(am__dirstamp) +src/core/ndisc/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/ndisc/$(DEPDIR) + @: > src/core/ndisc/$(DEPDIR)/$(am__dirstamp) +src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo: \ + src/core/ndisc/$(am__dirstamp) \ + src/core/ndisc/$(DEPDIR)/$(am__dirstamp) +src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo: \ + src/core/ndisc/$(am__dirstamp) \ + src/core/ndisc/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-dbus-utils.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-dbus-object.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-netns.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-l3-config-data.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-l3cfg.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-ip-config.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-ip4-config.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-nm-ip6-config.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo: \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) +src/core/libNetworkManagerBase_la-main-utils.lo: \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo: \ + src/core/platform/wifi/$(am__dirstamp) \ + src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp) + +src/core/libNetworkManagerBase.la: $(src_core_libNetworkManagerBase_la_OBJECTS) $(src_core_libNetworkManagerBase_la_DEPENDENCIES) $(EXTRA_src_core_libNetworkManagerBase_la_DEPENDENCIES) src/core/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_core_libNetworkManagerBase_la_OBJECTS) $(src_core_libNetworkManagerBase_la_LIBADD) $(LIBS) +src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo: \ + src/core/ndisc/$(am__dirstamp) \ + src/core/ndisc/$(DEPDIR)/$(am__dirstamp) +src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo: \ + src/core/platform/$(am__dirstamp) \ + src/core/platform/$(DEPDIR)/$(am__dirstamp) +src/core/platform/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/tests + @: > src/core/platform/tests/$(am__dirstamp) +src/core/platform/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/platform/tests/$(DEPDIR) + @: > src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) +src/core/platform/tests/libNetworkManagerTest_la-test-common.lo: \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/libNetworkManagerTest.la: $(src_core_libNetworkManagerTest_la_OBJECTS) $(src_core_libNetworkManagerTest_la_DEPENDENCIES) $(EXTRA_src_core_libNetworkManagerTest_la_DEPENDENCIES) src/core/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_libNetworkManagerTest_la_rpath) $(src_core_libNetworkManagerTest_la_OBJECTS) $(src_core_libNetworkManagerTest_la_LIBADD) $(LIBS) +src/core/systemd/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd + @: > src/core/systemd/$(am__dirstamp) +src/core/systemd/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/$(DEPDIR) + @: > src/core/systemd/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo: \ + src/core/systemd/$(am__dirstamp) \ + src/core/systemd/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/libnm_systemd_core_la-nm-sd.lo: \ + src/core/systemd/$(am__dirstamp) \ + src/core/systemd/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo: \ + src/core/systemd/$(am__dirstamp) \ + src/core/systemd/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/sd-adapt-core/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/sd-adapt-core + @: > src/core/systemd/sd-adapt-core/$(am__dirstamp) +src/core/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/sd-adapt-core/$(DEPDIR) + @: > src/core/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo: \ + src/core/systemd/sd-adapt-core/$(am__dirstamp) \ + src/core/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd-network + @: > src/core/systemd/src/libsystemd-network/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd-network/$(DEPDIR) + @: > src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo: \ + src/core/systemd/src/libsystemd-network/$(am__dirstamp) \ + src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-event/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd/sd-event + @: > src/core/systemd/src/libsystemd/sd-event/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd/sd-event/$(DEPDIR) + @: > src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo: \ + src/core/systemd/src/libsystemd/sd-event/$(am__dirstamp) \ + src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo: \ + src/core/systemd/src/libsystemd/sd-event/$(am__dirstamp) \ + src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-id128/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd/sd-id128 + @: > src/core/systemd/src/libsystemd/sd-id128/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR) + @: > src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo: \ + src/core/systemd/src/libsystemd/sd-id128/$(am__dirstamp) \ + src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) +src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo: \ + src/core/systemd/src/libsystemd/sd-id128/$(am__dirstamp) \ + src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) + +src/core/libnm-systemd-core.la: $(src_core_libnm_systemd_core_la_OBJECTS) $(src_core_libnm_systemd_core_la_DEPENDENCIES) $(EXTRA_src_core_libnm_systemd_core_la_DEPENDENCIES) src/core/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_core_libnm_systemd_core_la_OBJECTS) $(src_core_libnm_systemd_core_la_LIBADD) $(LIBS) +src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo: \ + src/core/ppp/$(am__dirstamp) \ + src/core/ppp/$(DEPDIR)/$(am__dirstamp) + +src/core/ppp/libnm-ppp-plugin.la: $(src_core_ppp_libnm_ppp_plugin_la_OBJECTS) $(src_core_ppp_libnm_ppp_plugin_la_DEPENDENCIES) $(EXTRA_src_core_ppp_libnm_ppp_plugin_la_DEPENDENCIES) src/core/ppp/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_ppp_libnm_ppp_plugin_la_LINK) $(am_src_core_ppp_libnm_ppp_plugin_la_rpath) $(src_core_ppp_libnm_ppp_plugin_la_OBJECTS) $(src_core_ppp_libnm_ppp_plugin_la_LIBADD) $(LIBS) +src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo: \ + src/core/ppp/$(am__dirstamp) \ + src/core/ppp/$(DEPDIR)/$(am__dirstamp) + +src/core/ppp/nm-pppd-plugin.la: $(src_core_ppp_nm_pppd_plugin_la_OBJECTS) $(src_core_ppp_nm_pppd_plugin_la_DEPENDENCIES) $(EXTRA_src_core_ppp_nm_pppd_plugin_la_DEPENDENCIES) src/core/ppp/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_ppp_nm_pppd_plugin_la_LINK) $(am_src_core_ppp_nm_pppd_plugin_la_rpath) $(src_core_ppp_nm_pppd_plugin_la_OBJECTS) $(src_core_ppp_nm_pppd_plugin_la_LIBADD) $(LIBS) +src/core/settings/plugins/ifcfg-rh/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh + @: > src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh/$(DEPDIR) + @: > src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la: $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_DEPENDENCIES) src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LINK) $(am_src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_rpath) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_LIBADD) $(LIBS) +src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifcfg-rh/libnmdbus-ifcfg-rh.la: $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_DEPENDENCIES) src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_rpath) $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_LIBADD) $(LIBS) +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo: \ + src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifcfg-rh/libnms-ifcfg-rh-core.la: $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_DEPENDENCIES) src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_rpath) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_LIBADD) $(LIBS) +src/core/settings/plugins/ifupdown/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifupdown + @: > src/core/settings/plugins/ifupdown/$(am__dirstamp) +src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifupdown/$(DEPDIR) + @: > src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo: \ + src/core/settings/plugins/ifupdown/$(am__dirstamp) \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la: $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_DEPENDENCIES) src/core/settings/plugins/ifupdown/$(am__dirstamp) + $(AM_V_CCLD)$(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LINK) $(am_src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_rpath) $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS) $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_LIBADD) $(LIBS) +src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo: \ + src/core/settings/plugins/ifupdown/$(am__dirstamp) \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo: \ + src/core/settings/plugins/ifupdown/$(am__dirstamp) \ + src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifupdown/libnms-ifupdown-core.la: $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_DEPENDENCIES) src/core/settings/plugins/ifupdown/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_rpath) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_LIBADD) $(LIBS) clients/cli/$(am__dirstamp): @$(MKDIR_P) clients/cli @: > clients/cli/$(am__dirstamp) @@ -10820,398 +10905,408 @@ shared/nm-platform/tests/test_nm_platform-test-nm-platform.$(OBJEXT): \ shared/nm-platform/tests/test-nm-platform$(EXEEXT): $(shared_nm_platform_tests_test_nm_platform_OBJECTS) $(shared_nm_platform_tests_test_nm_platform_DEPENDENCIES) $(EXTRA_shared_nm_platform_tests_test_nm_platform_DEPENDENCIES) shared/nm-platform/tests/$(am__dirstamp) @rm -f shared/nm-platform/tests/test-nm-platform$(EXEEXT) $(AM_V_CCLD)$(shared_nm_platform_tests_test_nm_platform_LINK) $(shared_nm_platform_tests_test_nm_platform_OBJECTS) $(shared_nm_platform_tests_test_nm_platform_LDADD) $(LIBS) -src/NetworkManager-main.$(OBJEXT): src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) - -src/NetworkManager$(EXEEXT): $(src_NetworkManager_OBJECTS) $(src_NetworkManager_DEPENDENCIES) $(EXTRA_src_NetworkManager_DEPENDENCIES) src/$(am__dirstamp) - @rm -f src/NetworkManager$(EXEEXT) - $(AM_V_CCLD)$(src_NetworkManager_LINK) $(src_NetworkManager_OBJECTS) $(src_NetworkManager_LDADD) $(LIBS) -src/NetworkManager_all_sym-main.$(OBJEXT): src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) - -src/NetworkManager-all-sym$(EXEEXT): $(src_NetworkManager_all_sym_OBJECTS) $(src_NetworkManager_all_sym_DEPENDENCIES) $(EXTRA_src_NetworkManager_all_sym_DEPENDENCIES) src/$(am__dirstamp) - @rm -f src/NetworkManager-all-sym$(EXEEXT) - $(AM_V_CCLD)$(src_NetworkManager_all_sym_LINK) $(src_NetworkManager_all_sym_OBJECTS) $(src_NetworkManager_all_sym_LDADD) $(LIBS) -src/devices/bluetooth/tests/$(am__dirstamp): - @$(MKDIR_P) src/devices/bluetooth/tests - @: > src/devices/bluetooth/tests/$(am__dirstamp) -src/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/bluetooth/tests/$(DEPDIR) - @: > src/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) -src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.$(OBJEXT): \ - src/devices/bluetooth/tests/$(am__dirstamp) \ - src/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) - -src/devices/bluetooth/tests/nm-bt-test$(EXEEXT): $(src_devices_bluetooth_tests_nm_bt_test_OBJECTS) $(src_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES) $(EXTRA_src_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES) src/devices/bluetooth/tests/$(am__dirstamp) - @rm -f src/devices/bluetooth/tests/nm-bt-test$(EXEEXT) - $(AM_V_CCLD)$(src_devices_bluetooth_tests_nm_bt_test_LINK) $(src_devices_bluetooth_tests_nm_bt_test_OBJECTS) $(src_devices_bluetooth_tests_nm_bt_test_LDADD) $(LIBS) -src/devices/tests/$(am__dirstamp): - @$(MKDIR_P) src/devices/tests - @: > src/devices/tests/$(am__dirstamp) -src/devices/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/tests/$(DEPDIR) - @: > src/devices/tests/$(DEPDIR)/$(am__dirstamp) -src/devices/tests/test_acd-test-acd.$(OBJEXT): \ - src/devices/tests/$(am__dirstamp) \ - src/devices/tests/$(DEPDIR)/$(am__dirstamp) - -src/devices/tests/test-acd$(EXEEXT): $(src_devices_tests_test_acd_OBJECTS) $(src_devices_tests_test_acd_DEPENDENCIES) $(EXTRA_src_devices_tests_test_acd_DEPENDENCIES) src/devices/tests/$(am__dirstamp) - @rm -f src/devices/tests/test-acd$(EXEEXT) - $(AM_V_CCLD)$(src_devices_tests_test_acd_LINK) $(src_devices_tests_test_acd_OBJECTS) $(src_devices_tests_test_acd_LDADD) $(LIBS) -src/devices/tests/test_lldp-test-lldp.$(OBJEXT): \ - src/devices/tests/$(am__dirstamp) \ - src/devices/tests/$(DEPDIR)/$(am__dirstamp) - -src/devices/tests/test-lldp$(EXEEXT): $(src_devices_tests_test_lldp_OBJECTS) $(src_devices_tests_test_lldp_DEPENDENCIES) $(EXTRA_src_devices_tests_test_lldp_DEPENDENCIES) src/devices/tests/$(am__dirstamp) - @rm -f src/devices/tests/test-lldp$(EXEEXT) - $(AM_V_CCLD)$(src_devices_tests_test_lldp_LINK) $(src_devices_tests_test_lldp_OBJECTS) $(src_devices_tests_test_lldp_LDADD) $(LIBS) -src/devices/wifi/tests/$(am__dirstamp): - @$(MKDIR_P) src/devices/wifi/tests - @: > src/devices/wifi/tests/$(am__dirstamp) -src/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/wifi/tests/$(DEPDIR) - @: > src/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) -src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.$(OBJEXT): \ - src/devices/wifi/tests/$(am__dirstamp) \ - src/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) - -src/devices/wifi/tests/test-devices-wifi$(EXEEXT): $(src_devices_wifi_tests_test_devices_wifi_OBJECTS) $(src_devices_wifi_tests_test_devices_wifi_DEPENDENCIES) $(EXTRA_src_devices_wifi_tests_test_devices_wifi_DEPENDENCIES) src/devices/wifi/tests/$(am__dirstamp) - @rm -f src/devices/wifi/tests/test-devices-wifi$(EXEEXT) - $(AM_V_CCLD)$(src_devices_wifi_tests_test_devices_wifi_LINK) $(src_devices_wifi_tests_test_devices_wifi_OBJECTS) $(src_devices_wifi_tests_test_devices_wifi_LDADD) $(LIBS) -src/devices/wwan/tests/$(am__dirstamp): - @$(MKDIR_P) src/devices/wwan/tests - @: > src/devices/wwan/tests/$(am__dirstamp) -src/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/devices/wwan/tests/$(DEPDIR) - @: > src/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/tests/test_service_providers-test-service-providers.$(OBJEXT): \ - src/devices/wwan/tests/$(am__dirstamp) \ - src/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) -src/devices/wwan/tests_test_service_providers-nm-service-providers.$(OBJEXT): \ - src/devices/wwan/$(am__dirstamp) \ - src/devices/wwan/$(DEPDIR)/$(am__dirstamp) - -src/devices/wwan/tests/test-service-providers$(EXEEXT): $(src_devices_wwan_tests_test_service_providers_OBJECTS) $(src_devices_wwan_tests_test_service_providers_DEPENDENCIES) $(EXTRA_src_devices_wwan_tests_test_service_providers_DEPENDENCIES) src/devices/wwan/tests/$(am__dirstamp) - @rm -f src/devices/wwan/tests/test-service-providers$(EXEEXT) - $(AM_V_CCLD)$(src_devices_wwan_tests_test_service_providers_LINK) $(src_devices_wwan_tests_test_service_providers_OBJECTS) $(src_devices_wwan_tests_test_service_providers_LDADD) $(LIBS) -src/dhcp/nm_dhcp_helper-nm-dhcp-helper.$(OBJEXT): \ - src/dhcp/$(am__dirstamp) src/dhcp/$(DEPDIR)/$(am__dirstamp) - -src/dhcp/nm-dhcp-helper$(EXEEXT): $(src_dhcp_nm_dhcp_helper_OBJECTS) $(src_dhcp_nm_dhcp_helper_DEPENDENCIES) $(EXTRA_src_dhcp_nm_dhcp_helper_DEPENDENCIES) src/dhcp/$(am__dirstamp) - @rm -f src/dhcp/nm-dhcp-helper$(EXEEXT) - $(AM_V_CCLD)$(src_dhcp_nm_dhcp_helper_LINK) $(src_dhcp_nm_dhcp_helper_OBJECTS) $(src_dhcp_nm_dhcp_helper_LDADD) $(LIBS) -src/dhcp/tests/$(am__dirstamp): - @$(MKDIR_P) src/dhcp/tests - @: > src/dhcp/tests/$(am__dirstamp) -src/dhcp/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/dhcp/tests/$(DEPDIR) - @: > src/dhcp/tests/$(DEPDIR)/$(am__dirstamp) -src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.$(OBJEXT): \ - src/dhcp/tests/$(am__dirstamp) \ - src/dhcp/tests/$(DEPDIR)/$(am__dirstamp) - -src/dhcp/tests/test-dhcp-dhclient$(EXEEXT): $(src_dhcp_tests_test_dhcp_dhclient_OBJECTS) $(src_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES) $(EXTRA_src_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES) src/dhcp/tests/$(am__dirstamp) - @rm -f src/dhcp/tests/test-dhcp-dhclient$(EXEEXT) - $(AM_V_CCLD)$(src_dhcp_tests_test_dhcp_dhclient_LINK) $(src_dhcp_tests_test_dhcp_dhclient_OBJECTS) $(src_dhcp_tests_test_dhcp_dhclient_LDADD) $(LIBS) -src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.$(OBJEXT): \ - src/dhcp/tests/$(am__dirstamp) \ - src/dhcp/tests/$(DEPDIR)/$(am__dirstamp) - -src/dhcp/tests/test-dhcp-utils$(EXEEXT): $(src_dhcp_tests_test_dhcp_utils_OBJECTS) $(src_dhcp_tests_test_dhcp_utils_DEPENDENCIES) $(EXTRA_src_dhcp_tests_test_dhcp_utils_DEPENDENCIES) src/dhcp/tests/$(am__dirstamp) - @rm -f src/dhcp/tests/test-dhcp-utils$(EXEEXT) - $(AM_V_CCLD)$(src_dhcp_tests_test_dhcp_utils_LINK) $(src_dhcp_tests_test_dhcp_utils_OBJECTS) $(src_dhcp_tests_test_dhcp_utils_LDADD) $(LIBS) -src/dnsmasq/tests/$(am__dirstamp): - @$(MKDIR_P) src/dnsmasq/tests - @: > src/dnsmasq/tests/$(am__dirstamp) -src/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/dnsmasq/tests/$(DEPDIR) - @: > src/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) -src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.$(OBJEXT): \ - src/dnsmasq/tests/$(am__dirstamp) \ - src/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) - -src/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT): $(src_dnsmasq_tests_test_dnsmasq_utils_OBJECTS) $(src_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES) $(EXTRA_src_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES) src/dnsmasq/tests/$(am__dirstamp) - @rm -f src/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) - $(AM_V_CCLD)$(src_dnsmasq_tests_test_dnsmasq_utils_LINK) $(src_dnsmasq_tests_test_dnsmasq_utils_OBJECTS) $(src_dnsmasq_tests_test_dnsmasq_utils_LDADD) $(LIBS) -src/initrd/nm_initrd_generator-nm-initrd-generator.$(OBJEXT): \ - src/initrd/$(am__dirstamp) \ - src/initrd/$(DEPDIR)/$(am__dirstamp) - -src/initrd/nm-initrd-generator$(EXEEXT): $(src_initrd_nm_initrd_generator_OBJECTS) $(src_initrd_nm_initrd_generator_DEPENDENCIES) $(EXTRA_src_initrd_nm_initrd_generator_DEPENDENCIES) src/initrd/$(am__dirstamp) - @rm -f src/initrd/nm-initrd-generator$(EXEEXT) - $(AM_V_CCLD)$(src_initrd_nm_initrd_generator_LINK) $(src_initrd_nm_initrd_generator_OBJECTS) $(src_initrd_nm_initrd_generator_LDADD) $(LIBS) -src/initrd/tests/$(am__dirstamp): - @$(MKDIR_P) src/initrd/tests - @: > src/initrd/tests/$(am__dirstamp) -src/initrd/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/initrd/tests/$(DEPDIR) - @: > src/initrd/tests/$(DEPDIR)/$(am__dirstamp) -src/initrd/tests/test_cmdline_reader-test-cmdline-reader.$(OBJEXT): \ - src/initrd/tests/$(am__dirstamp) \ - src/initrd/tests/$(DEPDIR)/$(am__dirstamp) - -src/initrd/tests/test-cmdline-reader$(EXEEXT): $(src_initrd_tests_test_cmdline_reader_OBJECTS) $(src_initrd_tests_test_cmdline_reader_DEPENDENCIES) $(EXTRA_src_initrd_tests_test_cmdline_reader_DEPENDENCIES) src/initrd/tests/$(am__dirstamp) - @rm -f src/initrd/tests/test-cmdline-reader$(EXEEXT) - $(AM_V_CCLD)$(src_initrd_tests_test_cmdline_reader_LINK) $(src_initrd_tests_test_cmdline_reader_OBJECTS) $(src_initrd_tests_test_cmdline_reader_LDADD) $(LIBS) -src/initrd/tests/test_dt_reader-test-dt-reader.$(OBJEXT): \ - src/initrd/tests/$(am__dirstamp) \ - src/initrd/tests/$(DEPDIR)/$(am__dirstamp) - -src/initrd/tests/test-dt-reader$(EXEEXT): $(src_initrd_tests_test_dt_reader_OBJECTS) $(src_initrd_tests_test_dt_reader_DEPENDENCIES) $(EXTRA_src_initrd_tests_test_dt_reader_DEPENDENCIES) src/initrd/tests/$(am__dirstamp) - @rm -f src/initrd/tests/test-dt-reader$(EXEEXT) - $(AM_V_CCLD)$(src_initrd_tests_test_dt_reader_LINK) $(src_initrd_tests_test_dt_reader_OBJECTS) $(src_initrd_tests_test_dt_reader_LDADD) $(LIBS) -src/initrd/tests/test_ibft_reader-test-ibft-reader.$(OBJEXT): \ - src/initrd/tests/$(am__dirstamp) \ - src/initrd/tests/$(DEPDIR)/$(am__dirstamp) - -src/initrd/tests/test-ibft-reader$(EXEEXT): $(src_initrd_tests_test_ibft_reader_OBJECTS) $(src_initrd_tests_test_ibft_reader_DEPENDENCIES) $(EXTRA_src_initrd_tests_test_ibft_reader_DEPENDENCIES) src/initrd/tests/$(am__dirstamp) - @rm -f src/initrd/tests/test-ibft-reader$(EXEEXT) - $(AM_V_CCLD)$(src_initrd_tests_test_ibft_reader_LINK) $(src_initrd_tests_test_ibft_reader_OBJECTS) $(src_initrd_tests_test_ibft_reader_LDADD) $(LIBS) -src/ndisc/tests/$(am__dirstamp): - @$(MKDIR_P) src/ndisc/tests - @: > src/ndisc/tests/$(am__dirstamp) -src/ndisc/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/ndisc/tests/$(DEPDIR) - @: > src/ndisc/tests/$(DEPDIR)/$(am__dirstamp) -src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.$(OBJEXT): \ - src/ndisc/tests/$(am__dirstamp) \ - src/ndisc/tests/$(DEPDIR)/$(am__dirstamp) - -src/ndisc/tests/test-ndisc-fake$(EXEEXT): $(src_ndisc_tests_test_ndisc_fake_OBJECTS) $(src_ndisc_tests_test_ndisc_fake_DEPENDENCIES) $(EXTRA_src_ndisc_tests_test_ndisc_fake_DEPENDENCIES) src/ndisc/tests/$(am__dirstamp) - @rm -f src/ndisc/tests/test-ndisc-fake$(EXEEXT) - $(AM_V_CCLD)$(src_ndisc_tests_test_ndisc_fake_LINK) $(src_ndisc_tests_test_ndisc_fake_OBJECTS) $(src_ndisc_tests_test_ndisc_fake_LDADD) $(LIBS) -src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.$(OBJEXT): \ - src/ndisc/tests/$(am__dirstamp) \ - src/ndisc/tests/$(DEPDIR)/$(am__dirstamp) - -src/ndisc/tests/test-ndisc-linux$(EXEEXT): $(src_ndisc_tests_test_ndisc_linux_OBJECTS) $(src_ndisc_tests_test_ndisc_linux_DEPENDENCIES) $(EXTRA_src_ndisc_tests_test_ndisc_linux_DEPENDENCIES) src/ndisc/tests/$(am__dirstamp) - @rm -f src/ndisc/tests/test-ndisc-linux$(EXEEXT) - $(AM_V_CCLD)$(src_ndisc_tests_test_ndisc_linux_LINK) $(src_ndisc_tests_test_ndisc_linux_OBJECTS) $(src_ndisc_tests_test_ndisc_linux_LDADD) $(LIBS) -src/nm_iface_helper-nm-iface-helper.$(OBJEXT): src/$(am__dirstamp) \ - src/$(DEPDIR)/$(am__dirstamp) - -src/nm-iface-helper$(EXEEXT): $(src_nm_iface_helper_OBJECTS) $(src_nm_iface_helper_DEPENDENCIES) $(EXTRA_src_nm_iface_helper_DEPENDENCIES) src/$(am__dirstamp) - @rm -f src/nm-iface-helper$(EXEEXT) - $(AM_V_CCLD)$(src_nm_iface_helper_LINK) $(src_nm_iface_helper_OBJECTS) $(src_nm_iface_helper_LDADD) $(LIBS) -src/platform/tests/monitor-monitor.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/monitor$(EXEEXT): $(src_platform_tests_monitor_OBJECTS) $(src_platform_tests_monitor_DEPENDENCIES) $(EXTRA_src_platform_tests_monitor_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/monitor$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_monitor_LINK) $(src_platform_tests_monitor_OBJECTS) $(src_platform_tests_monitor_LDADD) $(LIBS) -src/platform/tests/test_address_fake-test-address.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-address-fake$(EXEEXT): $(src_platform_tests_test_address_fake_OBJECTS) $(src_platform_tests_test_address_fake_DEPENDENCIES) $(EXTRA_src_platform_tests_test_address_fake_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-address-fake$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_address_fake_LINK) $(src_platform_tests_test_address_fake_OBJECTS) $(src_platform_tests_test_address_fake_LDADD) $(LIBS) -src/platform/tests/test_address_linux-test-address.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-address-linux$(EXEEXT): $(src_platform_tests_test_address_linux_OBJECTS) $(src_platform_tests_test_address_linux_DEPENDENCIES) $(EXTRA_src_platform_tests_test_address_linux_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-address-linux$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_address_linux_LINK) $(src_platform_tests_test_address_linux_OBJECTS) $(src_platform_tests_test_address_linux_LDADD) $(LIBS) -src/platform/tests/test_cleanup_fake-test-cleanup.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-cleanup-fake$(EXEEXT): $(src_platform_tests_test_cleanup_fake_OBJECTS) $(src_platform_tests_test_cleanup_fake_DEPENDENCIES) $(EXTRA_src_platform_tests_test_cleanup_fake_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-cleanup-fake$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_cleanup_fake_LINK) $(src_platform_tests_test_cleanup_fake_OBJECTS) $(src_platform_tests_test_cleanup_fake_LDADD) $(LIBS) -src/platform/tests/test_cleanup_linux-test-cleanup.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-cleanup-linux$(EXEEXT): $(src_platform_tests_test_cleanup_linux_OBJECTS) $(src_platform_tests_test_cleanup_linux_DEPENDENCIES) $(EXTRA_src_platform_tests_test_cleanup_linux_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-cleanup-linux$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_cleanup_linux_LINK) $(src_platform_tests_test_cleanup_linux_OBJECTS) $(src_platform_tests_test_cleanup_linux_LDADD) $(LIBS) -src/platform/tests/test_link_fake-test-link.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-link-fake$(EXEEXT): $(src_platform_tests_test_link_fake_OBJECTS) $(src_platform_tests_test_link_fake_DEPENDENCIES) $(EXTRA_src_platform_tests_test_link_fake_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-link-fake$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_link_fake_LINK) $(src_platform_tests_test_link_fake_OBJECTS) $(src_platform_tests_test_link_fake_LDADD) $(LIBS) -src/platform/tests/test_link_linux-test-link.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-link-linux$(EXEEXT): $(src_platform_tests_test_link_linux_OBJECTS) $(src_platform_tests_test_link_linux_DEPENDENCIES) $(EXTRA_src_platform_tests_test_link_linux_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-link-linux$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_link_linux_LINK) $(src_platform_tests_test_link_linux_OBJECTS) $(src_platform_tests_test_link_linux_LDADD) $(LIBS) -src/platform/tests/test_nmp_object-test-nmp-object.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-nmp-object$(EXEEXT): $(src_platform_tests_test_nmp_object_OBJECTS) $(src_platform_tests_test_nmp_object_DEPENDENCIES) $(EXTRA_src_platform_tests_test_nmp_object_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-nmp-object$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_nmp_object_LINK) $(src_platform_tests_test_nmp_object_OBJECTS) $(src_platform_tests_test_nmp_object_LDADD) $(LIBS) -src/platform/tests/test_platform_general-test-platform-general.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-platform-general$(EXEEXT): $(src_platform_tests_test_platform_general_OBJECTS) $(src_platform_tests_test_platform_general_DEPENDENCIES) $(EXTRA_src_platform_tests_test_platform_general_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-platform-general$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_platform_general_LINK) $(src_platform_tests_test_platform_general_OBJECTS) $(src_platform_tests_test_platform_general_LDADD) $(LIBS) -src/platform/tests/test_route_fake-test-route.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-route-fake$(EXEEXT): $(src_platform_tests_test_route_fake_OBJECTS) $(src_platform_tests_test_route_fake_DEPENDENCIES) $(EXTRA_src_platform_tests_test_route_fake_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-route-fake$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_route_fake_LINK) $(src_platform_tests_test_route_fake_OBJECTS) $(src_platform_tests_test_route_fake_LDADD) $(LIBS) -src/platform/tests/test_route_linux-test-route.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-route-linux$(EXEEXT): $(src_platform_tests_test_route_linux_OBJECTS) $(src_platform_tests_test_route_linux_DEPENDENCIES) $(EXTRA_src_platform_tests_test_route_linux_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-route-linux$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_route_linux_LINK) $(src_platform_tests_test_route_linux_OBJECTS) $(src_platform_tests_test_route_linux_LDADD) $(LIBS) -src/platform/tests/test_tc_fake-test-tc.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-tc-fake$(EXEEXT): $(src_platform_tests_test_tc_fake_OBJECTS) $(src_platform_tests_test_tc_fake_DEPENDENCIES) $(EXTRA_src_platform_tests_test_tc_fake_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-tc-fake$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_tc_fake_LINK) $(src_platform_tests_test_tc_fake_OBJECTS) $(src_platform_tests_test_tc_fake_LDADD) $(LIBS) -src/platform/tests/test_tc_linux-test-tc.$(OBJEXT): \ - src/platform/tests/$(am__dirstamp) \ - src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -src/platform/tests/test-tc-linux$(EXEEXT): $(src_platform_tests_test_tc_linux_OBJECTS) $(src_platform_tests_test_tc_linux_DEPENDENCIES) $(EXTRA_src_platform_tests_test_tc_linux_DEPENDENCIES) src/platform/tests/$(am__dirstamp) - @rm -f src/platform/tests/test-tc-linux$(EXEEXT) - $(AM_V_CCLD)$(src_platform_tests_test_tc_linux_LINK) $(src_platform_tests_test_tc_linux_OBJECTS) $(src_platform_tests_test_tc_linux_LDADD) $(LIBS) -src/settings/plugins/ifcfg-rh/tests/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifcfg-rh/tests - @: > src/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifcfg-rh/tests/$(DEPDIR) - @: > src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.$(OBJEXT): \ - src/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) \ - src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT): $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES) src/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) - @rm -f src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) - $(AM_V_CCLD)$(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LINK) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD) $(LIBS) -src/settings/plugins/ifupdown/tests/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifupdown/tests - @: > src/settings/plugins/ifupdown/tests/$(am__dirstamp) -src/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/ifupdown/tests/$(DEPDIR) - @: > src/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.$(OBJEXT): \ - src/settings/plugins/ifupdown/tests/$(am__dirstamp) \ - src/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT): $(src_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS) $(src_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES) $(EXTRA_src_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES) src/settings/plugins/ifupdown/tests/$(am__dirstamp) - @rm -f src/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) - $(AM_V_CCLD)$(src_settings_plugins_ifupdown_tests_test_ifupdown_LINK) $(src_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS) $(src_settings_plugins_ifupdown_tests_test_ifupdown_LDADD) $(LIBS) -src/settings/plugins/keyfile/tests/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/keyfile/tests - @: > src/settings/plugins/keyfile/tests/$(am__dirstamp) -src/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/settings/plugins/keyfile/tests/$(DEPDIR) - @: > src/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) -src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.$(OBJEXT): \ - src/settings/plugins/keyfile/tests/$(am__dirstamp) \ - src/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) - -src/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT): $(src_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES) $(EXTRA_src_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES) src/settings/plugins/keyfile/tests/$(am__dirstamp) - @rm -f src/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) - $(AM_V_CCLD)$(src_settings_plugins_keyfile_tests_test_keyfile_settings_LINK) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD) $(LIBS) -src/supplicant/tests/$(am__dirstamp): - @$(MKDIR_P) src/supplicant/tests - @: > src/supplicant/tests/$(am__dirstamp) -src/supplicant/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/supplicant/tests/$(DEPDIR) - @: > src/supplicant/tests/$(DEPDIR)/$(am__dirstamp) -src/supplicant/tests/test_supplicant_config-test-supplicant-config.$(OBJEXT): \ - src/supplicant/tests/$(am__dirstamp) \ - src/supplicant/tests/$(DEPDIR)/$(am__dirstamp) - -src/supplicant/tests/test-supplicant-config$(EXEEXT): $(src_supplicant_tests_test_supplicant_config_OBJECTS) $(src_supplicant_tests_test_supplicant_config_DEPENDENCIES) $(EXTRA_src_supplicant_tests_test_supplicant_config_DEPENDENCIES) src/supplicant/tests/$(am__dirstamp) - @rm -f src/supplicant/tests/test-supplicant-config$(EXEEXT) - $(AM_V_CCLD)$(src_supplicant_tests_test_supplicant_config_LINK) $(src_supplicant_tests_test_supplicant_config_OBJECTS) $(src_supplicant_tests_test_supplicant_config_LDADD) $(LIBS) -src/tests/config/$(am__dirstamp): - @$(MKDIR_P) src/tests/config - @: > src/tests/config/$(am__dirstamp) -src/tests/config/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/tests/config/$(DEPDIR) - @: > src/tests/config/$(DEPDIR)/$(am__dirstamp) -src/tests/config/test_config-nm-test-device.$(OBJEXT): \ - src/tests/config/$(am__dirstamp) \ - src/tests/config/$(DEPDIR)/$(am__dirstamp) -src/tests/config/test_config-test-config.$(OBJEXT): \ - src/tests/config/$(am__dirstamp) \ - src/tests/config/$(DEPDIR)/$(am__dirstamp) - -src/tests/config/test-config$(EXEEXT): $(src_tests_config_test_config_OBJECTS) $(src_tests_config_test_config_DEPENDENCIES) $(EXTRA_src_tests_config_test_config_DEPENDENCIES) src/tests/config/$(am__dirstamp) - @rm -f src/tests/config/test-config$(EXEEXT) - $(AM_V_CCLD)$(src_tests_config_test_config_LINK) $(src_tests_config_test_config_OBJECTS) $(src_tests_config_test_config_LDADD) $(LIBS) -src/tests/$(am__dirstamp): - @$(MKDIR_P) src/tests - @: > src/tests/$(am__dirstamp) -src/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/tests/$(DEPDIR) - @: > src/tests/$(DEPDIR)/$(am__dirstamp) -src/tests/test_core-test-core.$(OBJEXT): src/tests/$(am__dirstamp) \ - src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-core$(EXEEXT): $(src_tests_test_core_OBJECTS) $(src_tests_test_core_DEPENDENCIES) $(EXTRA_src_tests_test_core_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-core$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_core_LINK) $(src_tests_test_core_OBJECTS) $(src_tests_test_core_LDADD) $(LIBS) -src/tests/test_core_with_expect-test-core-with-expect.$(OBJEXT): \ - src/tests/$(am__dirstamp) src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-core-with-expect$(EXEEXT): $(src_tests_test_core_with_expect_OBJECTS) $(src_tests_test_core_with_expect_DEPENDENCIES) $(EXTRA_src_tests_test_core_with_expect_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-core-with-expect$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_core_with_expect_LINK) $(src_tests_test_core_with_expect_OBJECTS) $(src_tests_test_core_with_expect_LDADD) $(LIBS) -src/tests/test_dcb-test-dcb.$(OBJEXT): src/tests/$(am__dirstamp) \ - src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-dcb$(EXEEXT): $(src_tests_test_dcb_OBJECTS) $(src_tests_test_dcb_DEPENDENCIES) $(EXTRA_src_tests_test_dcb_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-dcb$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_dcb_LINK) $(src_tests_test_dcb_OBJECTS) $(src_tests_test_dcb_LDADD) $(LIBS) -src/tests/test_ip4_config-test-ip4-config.$(OBJEXT): \ - src/tests/$(am__dirstamp) src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-ip4-config$(EXEEXT): $(src_tests_test_ip4_config_OBJECTS) $(src_tests_test_ip4_config_DEPENDENCIES) $(EXTRA_src_tests_test_ip4_config_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-ip4-config$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_ip4_config_LINK) $(src_tests_test_ip4_config_OBJECTS) $(src_tests_test_ip4_config_LDADD) $(LIBS) -src/tests/test_ip6_config-test-ip6-config.$(OBJEXT): \ - src/tests/$(am__dirstamp) src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-ip6-config$(EXEEXT): $(src_tests_test_ip6_config_OBJECTS) $(src_tests_test_ip6_config_DEPENDENCIES) $(EXTRA_src_tests_test_ip6_config_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-ip6-config$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_ip6_config_LINK) $(src_tests_test_ip6_config_OBJECTS) $(src_tests_test_ip6_config_LDADD) $(LIBS) -src/tests/test_l3cfg-test-l3cfg.$(OBJEXT): src/tests/$(am__dirstamp) \ - src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-l3cfg$(EXEEXT): $(src_tests_test_l3cfg_OBJECTS) $(src_tests_test_l3cfg_DEPENDENCIES) $(EXTRA_src_tests_test_l3cfg_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-l3cfg$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_l3cfg_LINK) $(src_tests_test_l3cfg_OBJECTS) $(src_tests_test_l3cfg_LDADD) $(LIBS) -src/tests/test_systemd-test-systemd.$(OBJEXT): \ - src/tests/$(am__dirstamp) src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-systemd$(EXEEXT): $(src_tests_test_systemd_OBJECTS) $(src_tests_test_systemd_DEPENDENCIES) $(EXTRA_src_tests_test_systemd_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-systemd$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_systemd_LINK) $(src_tests_test_systemd_OBJECTS) $(src_tests_test_systemd_LDADD) $(LIBS) -src/tests/test_utils-test-utils.$(OBJEXT): src/tests/$(am__dirstamp) \ - src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-utils$(EXEEXT): $(src_tests_test_utils_OBJECTS) $(src_tests_test_utils_DEPENDENCIES) $(EXTRA_src_tests_test_utils_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-utils$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_utils_LINK) $(src_tests_test_utils_OBJECTS) $(src_tests_test_utils_LDADD) $(LIBS) -src/tests/test_wired_defname-test-wired-defname.$(OBJEXT): \ - src/tests/$(am__dirstamp) src/tests/$(DEPDIR)/$(am__dirstamp) - -src/tests/test-wired-defname$(EXEEXT): $(src_tests_test_wired_defname_OBJECTS) $(src_tests_test_wired_defname_DEPENDENCIES) $(EXTRA_src_tests_test_wired_defname_DEPENDENCIES) src/tests/$(am__dirstamp) - @rm -f src/tests/test-wired-defname$(EXEEXT) - $(AM_V_CCLD)$(src_tests_test_wired_defname_LINK) $(src_tests_test_wired_defname_OBJECTS) $(src_tests_test_wired_defname_LDADD) $(LIBS) +src/core/NetworkManager-main.$(OBJEXT): src/core/$(am__dirstamp) \ + src/core/$(DEPDIR)/$(am__dirstamp) + +src/core/NetworkManager$(EXEEXT): $(src_core_NetworkManager_OBJECTS) $(src_core_NetworkManager_DEPENDENCIES) $(EXTRA_src_core_NetworkManager_DEPENDENCIES) src/core/$(am__dirstamp) + @rm -f src/core/NetworkManager$(EXEEXT) + $(AM_V_CCLD)$(src_core_NetworkManager_LINK) $(src_core_NetworkManager_OBJECTS) $(src_core_NetworkManager_LDADD) $(LIBS) +src/core/NetworkManager_all_sym-main.$(OBJEXT): \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) + +src/core/NetworkManager-all-sym$(EXEEXT): $(src_core_NetworkManager_all_sym_OBJECTS) $(src_core_NetworkManager_all_sym_DEPENDENCIES) $(EXTRA_src_core_NetworkManager_all_sym_DEPENDENCIES) src/core/$(am__dirstamp) + @rm -f src/core/NetworkManager-all-sym$(EXEEXT) + $(AM_V_CCLD)$(src_core_NetworkManager_all_sym_LINK) $(src_core_NetworkManager_all_sym_OBJECTS) $(src_core_NetworkManager_all_sym_LDADD) $(LIBS) +src/core/devices/bluetooth/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/bluetooth/tests + @: > src/core/devices/bluetooth/tests/$(am__dirstamp) +src/core/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/bluetooth/tests/$(DEPDIR) + @: > src/core/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) +src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.$(OBJEXT): \ + src/core/devices/bluetooth/tests/$(am__dirstamp) \ + src/core/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/bluetooth/tests/nm-bt-test$(EXEEXT): $(src_core_devices_bluetooth_tests_nm_bt_test_OBJECTS) $(src_core_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES) $(EXTRA_src_core_devices_bluetooth_tests_nm_bt_test_DEPENDENCIES) src/core/devices/bluetooth/tests/$(am__dirstamp) + @rm -f src/core/devices/bluetooth/tests/nm-bt-test$(EXEEXT) + $(AM_V_CCLD)$(src_core_devices_bluetooth_tests_nm_bt_test_LINK) $(src_core_devices_bluetooth_tests_nm_bt_test_OBJECTS) $(src_core_devices_bluetooth_tests_nm_bt_test_LDADD) $(LIBS) +src/core/devices/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/tests + @: > src/core/devices/tests/$(am__dirstamp) +src/core/devices/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/tests/$(DEPDIR) + @: > src/core/devices/tests/$(DEPDIR)/$(am__dirstamp) +src/core/devices/tests/test_acd-test-acd.$(OBJEXT): \ + src/core/devices/tests/$(am__dirstamp) \ + src/core/devices/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/tests/test-acd$(EXEEXT): $(src_core_devices_tests_test_acd_OBJECTS) $(src_core_devices_tests_test_acd_DEPENDENCIES) $(EXTRA_src_core_devices_tests_test_acd_DEPENDENCIES) src/core/devices/tests/$(am__dirstamp) + @rm -f src/core/devices/tests/test-acd$(EXEEXT) + $(AM_V_CCLD)$(src_core_devices_tests_test_acd_LINK) $(src_core_devices_tests_test_acd_OBJECTS) $(src_core_devices_tests_test_acd_LDADD) $(LIBS) +src/core/devices/tests/test_lldp-test-lldp.$(OBJEXT): \ + src/core/devices/tests/$(am__dirstamp) \ + src/core/devices/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/tests/test-lldp$(EXEEXT): $(src_core_devices_tests_test_lldp_OBJECTS) $(src_core_devices_tests_test_lldp_DEPENDENCIES) $(EXTRA_src_core_devices_tests_test_lldp_DEPENDENCIES) src/core/devices/tests/$(am__dirstamp) + @rm -f src/core/devices/tests/test-lldp$(EXEEXT) + $(AM_V_CCLD)$(src_core_devices_tests_test_lldp_LINK) $(src_core_devices_tests_test_lldp_OBJECTS) $(src_core_devices_tests_test_lldp_LDADD) $(LIBS) +src/core/devices/wifi/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wifi/tests + @: > src/core/devices/wifi/tests/$(am__dirstamp) +src/core/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wifi/tests/$(DEPDIR) + @: > src/core/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.$(OBJEXT): \ + src/core/devices/wifi/tests/$(am__dirstamp) \ + src/core/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wifi/tests/test-devices-wifi$(EXEEXT): $(src_core_devices_wifi_tests_test_devices_wifi_OBJECTS) $(src_core_devices_wifi_tests_test_devices_wifi_DEPENDENCIES) $(EXTRA_src_core_devices_wifi_tests_test_devices_wifi_DEPENDENCIES) src/core/devices/wifi/tests/$(am__dirstamp) + @rm -f src/core/devices/wifi/tests/test-devices-wifi$(EXEEXT) + $(AM_V_CCLD)$(src_core_devices_wifi_tests_test_devices_wifi_LINK) $(src_core_devices_wifi_tests_test_devices_wifi_OBJECTS) $(src_core_devices_wifi_tests_test_devices_wifi_LDADD) $(LIBS) +src/core/devices/wwan/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wwan/tests + @: > src/core/devices/wwan/tests/$(am__dirstamp) +src/core/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/devices/wwan/tests/$(DEPDIR) + @: > src/core/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/tests/test_service_providers-test-service-providers.$(OBJEXT): \ + src/core/devices/wwan/tests/$(am__dirstamp) \ + src/core/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) +src/core/devices/wwan/tests_test_service_providers-nm-service-providers.$(OBJEXT): \ + src/core/devices/wwan/$(am__dirstamp) \ + src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) + +src/core/devices/wwan/tests/test-service-providers$(EXEEXT): $(src_core_devices_wwan_tests_test_service_providers_OBJECTS) $(src_core_devices_wwan_tests_test_service_providers_DEPENDENCIES) $(EXTRA_src_core_devices_wwan_tests_test_service_providers_DEPENDENCIES) src/core/devices/wwan/tests/$(am__dirstamp) + @rm -f src/core/devices/wwan/tests/test-service-providers$(EXEEXT) + $(AM_V_CCLD)$(src_core_devices_wwan_tests_test_service_providers_LINK) $(src_core_devices_wwan_tests_test_service_providers_OBJECTS) $(src_core_devices_wwan_tests_test_service_providers_LDADD) $(LIBS) +src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.$(OBJEXT): \ + src/core/dhcp/$(am__dirstamp) \ + src/core/dhcp/$(DEPDIR)/$(am__dirstamp) + +src/core/dhcp/nm-dhcp-helper$(EXEEXT): $(src_core_dhcp_nm_dhcp_helper_OBJECTS) $(src_core_dhcp_nm_dhcp_helper_DEPENDENCIES) $(EXTRA_src_core_dhcp_nm_dhcp_helper_DEPENDENCIES) src/core/dhcp/$(am__dirstamp) + @rm -f src/core/dhcp/nm-dhcp-helper$(EXEEXT) + $(AM_V_CCLD)$(src_core_dhcp_nm_dhcp_helper_LINK) $(src_core_dhcp_nm_dhcp_helper_OBJECTS) $(src_core_dhcp_nm_dhcp_helper_LDADD) $(LIBS) +src/core/dhcp/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/dhcp/tests + @: > src/core/dhcp/tests/$(am__dirstamp) +src/core/dhcp/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/dhcp/tests/$(DEPDIR) + @: > src/core/dhcp/tests/$(DEPDIR)/$(am__dirstamp) +src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.$(OBJEXT): \ + src/core/dhcp/tests/$(am__dirstamp) \ + src/core/dhcp/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/dhcp/tests/test-dhcp-dhclient$(EXEEXT): $(src_core_dhcp_tests_test_dhcp_dhclient_OBJECTS) $(src_core_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES) $(EXTRA_src_core_dhcp_tests_test_dhcp_dhclient_DEPENDENCIES) src/core/dhcp/tests/$(am__dirstamp) + @rm -f src/core/dhcp/tests/test-dhcp-dhclient$(EXEEXT) + $(AM_V_CCLD)$(src_core_dhcp_tests_test_dhcp_dhclient_LINK) $(src_core_dhcp_tests_test_dhcp_dhclient_OBJECTS) $(src_core_dhcp_tests_test_dhcp_dhclient_LDADD) $(LIBS) +src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.$(OBJEXT): \ + src/core/dhcp/tests/$(am__dirstamp) \ + src/core/dhcp/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/dhcp/tests/test-dhcp-utils$(EXEEXT): $(src_core_dhcp_tests_test_dhcp_utils_OBJECTS) $(src_core_dhcp_tests_test_dhcp_utils_DEPENDENCIES) $(EXTRA_src_core_dhcp_tests_test_dhcp_utils_DEPENDENCIES) src/core/dhcp/tests/$(am__dirstamp) + @rm -f src/core/dhcp/tests/test-dhcp-utils$(EXEEXT) + $(AM_V_CCLD)$(src_core_dhcp_tests_test_dhcp_utils_LINK) $(src_core_dhcp_tests_test_dhcp_utils_OBJECTS) $(src_core_dhcp_tests_test_dhcp_utils_LDADD) $(LIBS) +src/core/dnsmasq/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/dnsmasq/tests + @: > src/core/dnsmasq/tests/$(am__dirstamp) +src/core/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/dnsmasq/tests/$(DEPDIR) + @: > src/core/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) +src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.$(OBJEXT): \ + src/core/dnsmasq/tests/$(am__dirstamp) \ + src/core/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT): $(src_core_dnsmasq_tests_test_dnsmasq_utils_OBJECTS) $(src_core_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES) $(EXTRA_src_core_dnsmasq_tests_test_dnsmasq_utils_DEPENDENCIES) src/core/dnsmasq/tests/$(am__dirstamp) + @rm -f src/core/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) + $(AM_V_CCLD)$(src_core_dnsmasq_tests_test_dnsmasq_utils_LINK) $(src_core_dnsmasq_tests_test_dnsmasq_utils_OBJECTS) $(src_core_dnsmasq_tests_test_dnsmasq_utils_LDADD) $(LIBS) +src/core/initrd/nm_initrd_generator-nm-initrd-generator.$(OBJEXT): \ + src/core/initrd/$(am__dirstamp) \ + src/core/initrd/$(DEPDIR)/$(am__dirstamp) + +src/core/initrd/nm-initrd-generator$(EXEEXT): $(src_core_initrd_nm_initrd_generator_OBJECTS) $(src_core_initrd_nm_initrd_generator_DEPENDENCIES) $(EXTRA_src_core_initrd_nm_initrd_generator_DEPENDENCIES) src/core/initrd/$(am__dirstamp) + @rm -f src/core/initrd/nm-initrd-generator$(EXEEXT) + $(AM_V_CCLD)$(src_core_initrd_nm_initrd_generator_LINK) $(src_core_initrd_nm_initrd_generator_OBJECTS) $(src_core_initrd_nm_initrd_generator_LDADD) $(LIBS) +src/core/initrd/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/initrd/tests + @: > src/core/initrd/tests/$(am__dirstamp) +src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/initrd/tests/$(DEPDIR) + @: > src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp) +src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.$(OBJEXT): \ + src/core/initrd/tests/$(am__dirstamp) \ + src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/initrd/tests/test-cmdline-reader$(EXEEXT): $(src_core_initrd_tests_test_cmdline_reader_OBJECTS) $(src_core_initrd_tests_test_cmdline_reader_DEPENDENCIES) $(EXTRA_src_core_initrd_tests_test_cmdline_reader_DEPENDENCIES) src/core/initrd/tests/$(am__dirstamp) + @rm -f src/core/initrd/tests/test-cmdline-reader$(EXEEXT) + $(AM_V_CCLD)$(src_core_initrd_tests_test_cmdline_reader_LINK) $(src_core_initrd_tests_test_cmdline_reader_OBJECTS) $(src_core_initrd_tests_test_cmdline_reader_LDADD) $(LIBS) +src/core/initrd/tests/test_dt_reader-test-dt-reader.$(OBJEXT): \ + src/core/initrd/tests/$(am__dirstamp) \ + src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/initrd/tests/test-dt-reader$(EXEEXT): $(src_core_initrd_tests_test_dt_reader_OBJECTS) $(src_core_initrd_tests_test_dt_reader_DEPENDENCIES) $(EXTRA_src_core_initrd_tests_test_dt_reader_DEPENDENCIES) src/core/initrd/tests/$(am__dirstamp) + @rm -f src/core/initrd/tests/test-dt-reader$(EXEEXT) + $(AM_V_CCLD)$(src_core_initrd_tests_test_dt_reader_LINK) $(src_core_initrd_tests_test_dt_reader_OBJECTS) $(src_core_initrd_tests_test_dt_reader_LDADD) $(LIBS) +src/core/initrd/tests/test_ibft_reader-test-ibft-reader.$(OBJEXT): \ + src/core/initrd/tests/$(am__dirstamp) \ + src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/initrd/tests/test-ibft-reader$(EXEEXT): $(src_core_initrd_tests_test_ibft_reader_OBJECTS) $(src_core_initrd_tests_test_ibft_reader_DEPENDENCIES) $(EXTRA_src_core_initrd_tests_test_ibft_reader_DEPENDENCIES) src/core/initrd/tests/$(am__dirstamp) + @rm -f src/core/initrd/tests/test-ibft-reader$(EXEEXT) + $(AM_V_CCLD)$(src_core_initrd_tests_test_ibft_reader_LINK) $(src_core_initrd_tests_test_ibft_reader_OBJECTS) $(src_core_initrd_tests_test_ibft_reader_LDADD) $(LIBS) +src/core/ndisc/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/ndisc/tests + @: > src/core/ndisc/tests/$(am__dirstamp) +src/core/ndisc/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/ndisc/tests/$(DEPDIR) + @: > src/core/ndisc/tests/$(DEPDIR)/$(am__dirstamp) +src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.$(OBJEXT): \ + src/core/ndisc/tests/$(am__dirstamp) \ + src/core/ndisc/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/ndisc/tests/test-ndisc-fake$(EXEEXT): $(src_core_ndisc_tests_test_ndisc_fake_OBJECTS) $(src_core_ndisc_tests_test_ndisc_fake_DEPENDENCIES) $(EXTRA_src_core_ndisc_tests_test_ndisc_fake_DEPENDENCIES) src/core/ndisc/tests/$(am__dirstamp) + @rm -f src/core/ndisc/tests/test-ndisc-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_ndisc_tests_test_ndisc_fake_LINK) $(src_core_ndisc_tests_test_ndisc_fake_OBJECTS) $(src_core_ndisc_tests_test_ndisc_fake_LDADD) $(LIBS) +src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.$(OBJEXT): \ + src/core/ndisc/tests/$(am__dirstamp) \ + src/core/ndisc/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/ndisc/tests/test-ndisc-linux$(EXEEXT): $(src_core_ndisc_tests_test_ndisc_linux_OBJECTS) $(src_core_ndisc_tests_test_ndisc_linux_DEPENDENCIES) $(EXTRA_src_core_ndisc_tests_test_ndisc_linux_DEPENDENCIES) src/core/ndisc/tests/$(am__dirstamp) + @rm -f src/core/ndisc/tests/test-ndisc-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_ndisc_tests_test_ndisc_linux_LINK) $(src_core_ndisc_tests_test_ndisc_linux_OBJECTS) $(src_core_ndisc_tests_test_ndisc_linux_LDADD) $(LIBS) +src/core/nm_iface_helper-nm-iface-helper.$(OBJEXT): \ + src/core/$(am__dirstamp) src/core/$(DEPDIR)/$(am__dirstamp) + +src/core/nm-iface-helper$(EXEEXT): $(src_core_nm_iface_helper_OBJECTS) $(src_core_nm_iface_helper_DEPENDENCIES) $(EXTRA_src_core_nm_iface_helper_DEPENDENCIES) src/core/$(am__dirstamp) + @rm -f src/core/nm-iface-helper$(EXEEXT) + $(AM_V_CCLD)$(src_core_nm_iface_helper_LINK) $(src_core_nm_iface_helper_OBJECTS) $(src_core_nm_iface_helper_LDADD) $(LIBS) +src/core/platform/tests/monitor-monitor.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/monitor$(EXEEXT): $(src_core_platform_tests_monitor_OBJECTS) $(src_core_platform_tests_monitor_DEPENDENCIES) $(EXTRA_src_core_platform_tests_monitor_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/monitor$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_monitor_LINK) $(src_core_platform_tests_monitor_OBJECTS) $(src_core_platform_tests_monitor_LDADD) $(LIBS) +src/core/platform/tests/test_address_fake-test-address.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-address-fake$(EXEEXT): $(src_core_platform_tests_test_address_fake_OBJECTS) $(src_core_platform_tests_test_address_fake_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_address_fake_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-address-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_address_fake_LINK) $(src_core_platform_tests_test_address_fake_OBJECTS) $(src_core_platform_tests_test_address_fake_LDADD) $(LIBS) +src/core/platform/tests/test_address_linux-test-address.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-address-linux$(EXEEXT): $(src_core_platform_tests_test_address_linux_OBJECTS) $(src_core_platform_tests_test_address_linux_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_address_linux_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-address-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_address_linux_LINK) $(src_core_platform_tests_test_address_linux_OBJECTS) $(src_core_platform_tests_test_address_linux_LDADD) $(LIBS) +src/core/platform/tests/test_cleanup_fake-test-cleanup.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-cleanup-fake$(EXEEXT): $(src_core_platform_tests_test_cleanup_fake_OBJECTS) $(src_core_platform_tests_test_cleanup_fake_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_cleanup_fake_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-cleanup-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_cleanup_fake_LINK) $(src_core_platform_tests_test_cleanup_fake_OBJECTS) $(src_core_platform_tests_test_cleanup_fake_LDADD) $(LIBS) +src/core/platform/tests/test_cleanup_linux-test-cleanup.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-cleanup-linux$(EXEEXT): $(src_core_platform_tests_test_cleanup_linux_OBJECTS) $(src_core_platform_tests_test_cleanup_linux_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_cleanup_linux_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-cleanup-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_cleanup_linux_LINK) $(src_core_platform_tests_test_cleanup_linux_OBJECTS) $(src_core_platform_tests_test_cleanup_linux_LDADD) $(LIBS) +src/core/platform/tests/test_link_fake-test-link.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-link-fake$(EXEEXT): $(src_core_platform_tests_test_link_fake_OBJECTS) $(src_core_platform_tests_test_link_fake_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_link_fake_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-link-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_link_fake_LINK) $(src_core_platform_tests_test_link_fake_OBJECTS) $(src_core_platform_tests_test_link_fake_LDADD) $(LIBS) +src/core/platform/tests/test_link_linux-test-link.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-link-linux$(EXEEXT): $(src_core_platform_tests_test_link_linux_OBJECTS) $(src_core_platform_tests_test_link_linux_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_link_linux_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-link-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_link_linux_LINK) $(src_core_platform_tests_test_link_linux_OBJECTS) $(src_core_platform_tests_test_link_linux_LDADD) $(LIBS) +src/core/platform/tests/test_nmp_object-test-nmp-object.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-nmp-object$(EXEEXT): $(src_core_platform_tests_test_nmp_object_OBJECTS) $(src_core_platform_tests_test_nmp_object_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_nmp_object_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-nmp-object$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_nmp_object_LINK) $(src_core_platform_tests_test_nmp_object_OBJECTS) $(src_core_platform_tests_test_nmp_object_LDADD) $(LIBS) +src/core/platform/tests/test_platform_general-test-platform-general.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-platform-general$(EXEEXT): $(src_core_platform_tests_test_platform_general_OBJECTS) $(src_core_platform_tests_test_platform_general_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_platform_general_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-platform-general$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_platform_general_LINK) $(src_core_platform_tests_test_platform_general_OBJECTS) $(src_core_platform_tests_test_platform_general_LDADD) $(LIBS) +src/core/platform/tests/test_route_fake-test-route.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-route-fake$(EXEEXT): $(src_core_platform_tests_test_route_fake_OBJECTS) $(src_core_platform_tests_test_route_fake_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_route_fake_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-route-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_route_fake_LINK) $(src_core_platform_tests_test_route_fake_OBJECTS) $(src_core_platform_tests_test_route_fake_LDADD) $(LIBS) +src/core/platform/tests/test_route_linux-test-route.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-route-linux$(EXEEXT): $(src_core_platform_tests_test_route_linux_OBJECTS) $(src_core_platform_tests_test_route_linux_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_route_linux_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-route-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_route_linux_LINK) $(src_core_platform_tests_test_route_linux_OBJECTS) $(src_core_platform_tests_test_route_linux_LDADD) $(LIBS) +src/core/platform/tests/test_tc_fake-test-tc.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-tc-fake$(EXEEXT): $(src_core_platform_tests_test_tc_fake_OBJECTS) $(src_core_platform_tests_test_tc_fake_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_tc_fake_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-tc-fake$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_tc_fake_LINK) $(src_core_platform_tests_test_tc_fake_OBJECTS) $(src_core_platform_tests_test_tc_fake_LDADD) $(LIBS) +src/core/platform/tests/test_tc_linux-test-tc.$(OBJEXT): \ + src/core/platform/tests/$(am__dirstamp) \ + src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/platform/tests/test-tc-linux$(EXEEXT): $(src_core_platform_tests_test_tc_linux_OBJECTS) $(src_core_platform_tests_test_tc_linux_DEPENDENCIES) $(EXTRA_src_core_platform_tests_test_tc_linux_DEPENDENCIES) src/core/platform/tests/$(am__dirstamp) + @rm -f src/core/platform/tests/test-tc-linux$(EXEEXT) + $(AM_V_CCLD)$(src_core_platform_tests_test_tc_linux_LINK) $(src_core_platform_tests_test_tc_linux_OBJECTS) $(src_core_platform_tests_test_tc_linux_LDADD) $(LIBS) +src/core/settings/plugins/ifcfg-rh/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh/tests + @: > src/core/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR) + @: > src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.$(OBJEXT): \ + src/core/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) \ + src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT): $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_DEPENDENCIES) src/core/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) + @rm -f src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) + $(AM_V_CCLD)$(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LINK) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_LDADD) $(LIBS) +src/core/settings/plugins/ifupdown/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifupdown/tests + @: > src/core/settings/plugins/ifupdown/tests/$(am__dirstamp) +src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/ifupdown/tests/$(DEPDIR) + @: > src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.$(OBJEXT): \ + src/core/settings/plugins/ifupdown/tests/$(am__dirstamp) \ + src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT): $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_ifupdown_tests_test_ifupdown_DEPENDENCIES) src/core/settings/plugins/ifupdown/tests/$(am__dirstamp) + @rm -f src/core/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) + $(AM_V_CCLD)$(src_core_settings_plugins_ifupdown_tests_test_ifupdown_LINK) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_LDADD) $(LIBS) +src/core/settings/plugins/keyfile/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/keyfile/tests + @: > src/core/settings/plugins/keyfile/tests/$(am__dirstamp) +src/core/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/settings/plugins/keyfile/tests/$(DEPDIR) + @: > src/core/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) +src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.$(OBJEXT): \ + src/core/settings/plugins/keyfile/tests/$(am__dirstamp) \ + src/core/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT): $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES) $(EXTRA_src_core_settings_plugins_keyfile_tests_test_keyfile_settings_DEPENDENCIES) src/core/settings/plugins/keyfile/tests/$(am__dirstamp) + @rm -f src/core/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) + $(AM_V_CCLD)$(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LINK) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_LDADD) $(LIBS) +src/core/supplicant/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/supplicant/tests + @: > src/core/supplicant/tests/$(am__dirstamp) +src/core/supplicant/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/supplicant/tests/$(DEPDIR) + @: > src/core/supplicant/tests/$(DEPDIR)/$(am__dirstamp) +src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.$(OBJEXT): \ + src/core/supplicant/tests/$(am__dirstamp) \ + src/core/supplicant/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/supplicant/tests/test-supplicant-config$(EXEEXT): $(src_core_supplicant_tests_test_supplicant_config_OBJECTS) $(src_core_supplicant_tests_test_supplicant_config_DEPENDENCIES) $(EXTRA_src_core_supplicant_tests_test_supplicant_config_DEPENDENCIES) src/core/supplicant/tests/$(am__dirstamp) + @rm -f src/core/supplicant/tests/test-supplicant-config$(EXEEXT) + $(AM_V_CCLD)$(src_core_supplicant_tests_test_supplicant_config_LINK) $(src_core_supplicant_tests_test_supplicant_config_OBJECTS) $(src_core_supplicant_tests_test_supplicant_config_LDADD) $(LIBS) +src/core/tests/config/$(am__dirstamp): + @$(MKDIR_P) src/core/tests/config + @: > src/core/tests/config/$(am__dirstamp) +src/core/tests/config/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/tests/config/$(DEPDIR) + @: > src/core/tests/config/$(DEPDIR)/$(am__dirstamp) +src/core/tests/config/test_config-nm-test-device.$(OBJEXT): \ + src/core/tests/config/$(am__dirstamp) \ + src/core/tests/config/$(DEPDIR)/$(am__dirstamp) +src/core/tests/config/test_config-test-config.$(OBJEXT): \ + src/core/tests/config/$(am__dirstamp) \ + src/core/tests/config/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/config/test-config$(EXEEXT): $(src_core_tests_config_test_config_OBJECTS) $(src_core_tests_config_test_config_DEPENDENCIES) $(EXTRA_src_core_tests_config_test_config_DEPENDENCIES) src/core/tests/config/$(am__dirstamp) + @rm -f src/core/tests/config/test-config$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_config_test_config_LINK) $(src_core_tests_config_test_config_OBJECTS) $(src_core_tests_config_test_config_LDADD) $(LIBS) +src/core/tests/$(am__dirstamp): + @$(MKDIR_P) src/core/tests + @: > src/core/tests/$(am__dirstamp) +src/core/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/core/tests/$(DEPDIR) + @: > src/core/tests/$(DEPDIR)/$(am__dirstamp) +src/core/tests/test_core-test-core.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-core$(EXEEXT): $(src_core_tests_test_core_OBJECTS) $(src_core_tests_test_core_DEPENDENCIES) $(EXTRA_src_core_tests_test_core_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-core$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_core_LINK) $(src_core_tests_test_core_OBJECTS) $(src_core_tests_test_core_LDADD) $(LIBS) +src/core/tests/test_core_with_expect-test-core-with-expect.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-core-with-expect$(EXEEXT): $(src_core_tests_test_core_with_expect_OBJECTS) $(src_core_tests_test_core_with_expect_DEPENDENCIES) $(EXTRA_src_core_tests_test_core_with_expect_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-core-with-expect$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_core_with_expect_LINK) $(src_core_tests_test_core_with_expect_OBJECTS) $(src_core_tests_test_core_with_expect_LDADD) $(LIBS) +src/core/tests/test_dcb-test-dcb.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-dcb$(EXEEXT): $(src_core_tests_test_dcb_OBJECTS) $(src_core_tests_test_dcb_DEPENDENCIES) $(EXTRA_src_core_tests_test_dcb_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-dcb$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_dcb_LINK) $(src_core_tests_test_dcb_OBJECTS) $(src_core_tests_test_dcb_LDADD) $(LIBS) +src/core/tests/test_ip4_config-test-ip4-config.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-ip4-config$(EXEEXT): $(src_core_tests_test_ip4_config_OBJECTS) $(src_core_tests_test_ip4_config_DEPENDENCIES) $(EXTRA_src_core_tests_test_ip4_config_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-ip4-config$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_ip4_config_LINK) $(src_core_tests_test_ip4_config_OBJECTS) $(src_core_tests_test_ip4_config_LDADD) $(LIBS) +src/core/tests/test_ip6_config-test-ip6-config.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-ip6-config$(EXEEXT): $(src_core_tests_test_ip6_config_OBJECTS) $(src_core_tests_test_ip6_config_DEPENDENCIES) $(EXTRA_src_core_tests_test_ip6_config_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-ip6-config$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_ip6_config_LINK) $(src_core_tests_test_ip6_config_OBJECTS) $(src_core_tests_test_ip6_config_LDADD) $(LIBS) +src/core/tests/test_l3cfg-test-l3cfg.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-l3cfg$(EXEEXT): $(src_core_tests_test_l3cfg_OBJECTS) $(src_core_tests_test_l3cfg_DEPENDENCIES) $(EXTRA_src_core_tests_test_l3cfg_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-l3cfg$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_l3cfg_LINK) $(src_core_tests_test_l3cfg_OBJECTS) $(src_core_tests_test_l3cfg_LDADD) $(LIBS) +src/core/tests/test_systemd-test-systemd.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-systemd$(EXEEXT): $(src_core_tests_test_systemd_OBJECTS) $(src_core_tests_test_systemd_DEPENDENCIES) $(EXTRA_src_core_tests_test_systemd_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-systemd$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_systemd_LINK) $(src_core_tests_test_systemd_OBJECTS) $(src_core_tests_test_systemd_LDADD) $(LIBS) +src/core/tests/test_utils-test-utils.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-utils$(EXEEXT): $(src_core_tests_test_utils_OBJECTS) $(src_core_tests_test_utils_DEPENDENCIES) $(EXTRA_src_core_tests_test_utils_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-utils$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_utils_LINK) $(src_core_tests_test_utils_OBJECTS) $(src_core_tests_test_utils_LDADD) $(LIBS) +src/core/tests/test_wired_defname-test-wired-defname.$(OBJEXT): \ + src/core/tests/$(am__dirstamp) \ + src/core/tests/$(DEPDIR)/$(am__dirstamp) + +src/core/tests/test-wired-defname$(EXEEXT): $(src_core_tests_test_wired_defname_OBJECTS) $(src_core_tests_test_wired_defname_DEPENDENCIES) $(EXTRA_src_core_tests_test_wired_defname_DEPENDENCIES) src/core/tests/$(am__dirstamp) + @rm -f src/core/tests/test-wired-defname$(EXEEXT) + $(AM_V_CCLD)$(src_core_tests_test_wired_defname_LINK) $(src_core_tests_test_wired_defname_OBJECTS) $(src_core_tests_test_wired_defname_LDADD) $(LIBS) install-dist_libexecSCRIPTS: $(dist_libexec_SCRIPTS) @$(NORMAL_INSTALL) @list='$(dist_libexec_SCRIPTS)'; test -n "$(libexecdir)" || list=; \ @@ -11315,78 +11410,78 @@ mostlyclean-compile: -rm -f shared/systemd/src/basic/*.lo -rm -f shared/systemd/src/shared/*.$(OBJEXT) -rm -f shared/systemd/src/shared/*.lo - -rm -f src/*.$(OBJEXT) - -rm -f src/*.lo - -rm -f src/devices/*.$(OBJEXT) - -rm -f src/devices/*.lo - -rm -f src/devices/adsl/*.$(OBJEXT) - -rm -f src/devices/adsl/*.lo - -rm -f src/devices/bluetooth/*.$(OBJEXT) - -rm -f src/devices/bluetooth/*.lo - -rm -f src/devices/bluetooth/tests/*.$(OBJEXT) - -rm -f src/devices/ovs/*.$(OBJEXT) - -rm -f src/devices/ovs/*.lo - -rm -f src/devices/team/*.$(OBJEXT) - -rm -f src/devices/team/*.lo - -rm -f src/devices/tests/*.$(OBJEXT) - -rm -f src/devices/wifi/*.$(OBJEXT) - -rm -f src/devices/wifi/*.lo - -rm -f src/devices/wifi/tests/*.$(OBJEXT) - -rm -f src/devices/wwan/*.$(OBJEXT) - -rm -f src/devices/wwan/*.lo - -rm -f src/devices/wwan/tests/*.$(OBJEXT) - -rm -f src/dhcp/*.$(OBJEXT) - -rm -f src/dhcp/*.lo - -rm -f src/dhcp/tests/*.$(OBJEXT) - -rm -f src/dns/*.$(OBJEXT) - -rm -f src/dns/*.lo - -rm -f src/dnsmasq/*.$(OBJEXT) - -rm -f src/dnsmasq/*.lo - -rm -f src/dnsmasq/tests/*.$(OBJEXT) - -rm -f src/initrd/*.$(OBJEXT) - -rm -f src/initrd/*.lo - -rm -f src/initrd/tests/*.$(OBJEXT) - -rm -f src/ndisc/*.$(OBJEXT) - -rm -f src/ndisc/*.lo - -rm -f src/ndisc/tests/*.$(OBJEXT) - -rm -f src/platform/*.$(OBJEXT) - -rm -f src/platform/*.lo - -rm -f src/platform/tests/*.$(OBJEXT) - -rm -f src/platform/tests/*.lo - -rm -f src/platform/wifi/*.$(OBJEXT) - -rm -f src/platform/wifi/*.lo - -rm -f src/platform/wpan/*.$(OBJEXT) - -rm -f src/platform/wpan/*.lo - -rm -f src/ppp/*.$(OBJEXT) - -rm -f src/ppp/*.lo - -rm -f src/settings/*.$(OBJEXT) - -rm -f src/settings/*.lo - -rm -f src/settings/plugins/ifcfg-rh/*.$(OBJEXT) - -rm -f src/settings/plugins/ifcfg-rh/*.lo - -rm -f src/settings/plugins/ifcfg-rh/tests/*.$(OBJEXT) - -rm -f src/settings/plugins/ifupdown/*.$(OBJEXT) - -rm -f src/settings/plugins/ifupdown/*.lo - -rm -f src/settings/plugins/ifupdown/tests/*.$(OBJEXT) - -rm -f src/settings/plugins/keyfile/*.$(OBJEXT) - -rm -f src/settings/plugins/keyfile/*.lo - -rm -f src/settings/plugins/keyfile/tests/*.$(OBJEXT) - -rm -f src/supplicant/*.$(OBJEXT) - -rm -f src/supplicant/*.lo - -rm -f src/supplicant/tests/*.$(OBJEXT) - -rm -f src/systemd/*.$(OBJEXT) - -rm -f src/systemd/*.lo - -rm -f src/systemd/sd-adapt-core/*.$(OBJEXT) - -rm -f src/systemd/sd-adapt-core/*.lo - -rm -f src/systemd/src/libsystemd-network/*.$(OBJEXT) - -rm -f src/systemd/src/libsystemd-network/*.lo - -rm -f src/systemd/src/libsystemd/sd-event/*.$(OBJEXT) - -rm -f src/systemd/src/libsystemd/sd-event/*.lo - -rm -f src/systemd/src/libsystemd/sd-id128/*.$(OBJEXT) - -rm -f src/systemd/src/libsystemd/sd-id128/*.lo - -rm -f src/tests/*.$(OBJEXT) - -rm -f src/tests/config/*.$(OBJEXT) - -rm -f src/vpn/*.$(OBJEXT) - -rm -f src/vpn/*.lo + -rm -f src/core/*.$(OBJEXT) + -rm -f src/core/*.lo + -rm -f src/core/devices/*.$(OBJEXT) + -rm -f src/core/devices/*.lo + -rm -f src/core/devices/adsl/*.$(OBJEXT) + -rm -f src/core/devices/adsl/*.lo + -rm -f src/core/devices/bluetooth/*.$(OBJEXT) + -rm -f src/core/devices/bluetooth/*.lo + -rm -f src/core/devices/bluetooth/tests/*.$(OBJEXT) + -rm -f src/core/devices/ovs/*.$(OBJEXT) + -rm -f src/core/devices/ovs/*.lo + -rm -f src/core/devices/team/*.$(OBJEXT) + -rm -f src/core/devices/team/*.lo + -rm -f src/core/devices/tests/*.$(OBJEXT) + -rm -f src/core/devices/wifi/*.$(OBJEXT) + -rm -f src/core/devices/wifi/*.lo + -rm -f src/core/devices/wifi/tests/*.$(OBJEXT) + -rm -f src/core/devices/wwan/*.$(OBJEXT) + -rm -f src/core/devices/wwan/*.lo + -rm -f src/core/devices/wwan/tests/*.$(OBJEXT) + -rm -f src/core/dhcp/*.$(OBJEXT) + -rm -f src/core/dhcp/*.lo + -rm -f src/core/dhcp/tests/*.$(OBJEXT) + -rm -f src/core/dns/*.$(OBJEXT) + -rm -f src/core/dns/*.lo + -rm -f src/core/dnsmasq/*.$(OBJEXT) + -rm -f src/core/dnsmasq/*.lo + -rm -f src/core/dnsmasq/tests/*.$(OBJEXT) + -rm -f src/core/initrd/*.$(OBJEXT) + -rm -f src/core/initrd/*.lo + -rm -f src/core/initrd/tests/*.$(OBJEXT) + -rm -f src/core/ndisc/*.$(OBJEXT) + -rm -f src/core/ndisc/*.lo + -rm -f src/core/ndisc/tests/*.$(OBJEXT) + -rm -f src/core/platform/*.$(OBJEXT) + -rm -f src/core/platform/*.lo + -rm -f src/core/platform/tests/*.$(OBJEXT) + -rm -f src/core/platform/tests/*.lo + -rm -f src/core/platform/wifi/*.$(OBJEXT) + -rm -f src/core/platform/wifi/*.lo + -rm -f src/core/platform/wpan/*.$(OBJEXT) + -rm -f src/core/platform/wpan/*.lo + -rm -f src/core/ppp/*.$(OBJEXT) + -rm -f src/core/ppp/*.lo + -rm -f src/core/settings/*.$(OBJEXT) + -rm -f src/core/settings/*.lo + -rm -f src/core/settings/plugins/ifcfg-rh/*.$(OBJEXT) + -rm -f src/core/settings/plugins/ifcfg-rh/*.lo + -rm -f src/core/settings/plugins/ifcfg-rh/tests/*.$(OBJEXT) + -rm -f src/core/settings/plugins/ifupdown/*.$(OBJEXT) + -rm -f src/core/settings/plugins/ifupdown/*.lo + -rm -f src/core/settings/plugins/ifupdown/tests/*.$(OBJEXT) + -rm -f src/core/settings/plugins/keyfile/*.$(OBJEXT) + -rm -f src/core/settings/plugins/keyfile/*.lo + -rm -f src/core/settings/plugins/keyfile/tests/*.$(OBJEXT) + -rm -f src/core/supplicant/*.$(OBJEXT) + -rm -f src/core/supplicant/*.lo + -rm -f src/core/supplicant/tests/*.$(OBJEXT) + -rm -f src/core/systemd/*.$(OBJEXT) + -rm -f src/core/systemd/*.lo + -rm -f src/core/systemd/sd-adapt-core/*.$(OBJEXT) + -rm -f src/core/systemd/sd-adapt-core/*.lo + -rm -f src/core/systemd/src/libsystemd-network/*.$(OBJEXT) + -rm -f src/core/systemd/src/libsystemd-network/*.lo + -rm -f src/core/systemd/src/libsystemd/sd-event/*.$(OBJEXT) + -rm -f src/core/systemd/src/libsystemd/sd-event/*.lo + -rm -f src/core/systemd/src/libsystemd/sd-id128/*.$(OBJEXT) + -rm -f src/core/systemd/src/libsystemd/sd-id128/*.lo + -rm -f src/core/tests/*.$(OBJEXT) + -rm -f src/core/tests/config/*.$(OBJEXT) + -rm -f src/core/vpn/*.$(OBJEXT) + -rm -f src/core/vpn/*.lo distclean-compile: -rm -f *.tab.c @@ -11620,59 +11715,59 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@libnm-core/tests/$(DEPDIR)/test_secrets-test-secrets.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libnm-core/tests/$(DEPDIR)/test_setting-test-setting.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libnm-core/tests/$(DEPDIR)/test_settings_defaults-test-settings-defaults.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-client.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-device.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-object.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-device.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-object.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libnm/nm-libnm-aux/$(DEPDIR)/libnm_libnm_aux_la-nm-libnm-aux.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libnm/tests/$(DEPDIR)/test_libnm-test-libnm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libnm/tests/$(DEPDIR)/test_nm_client-test-nm-client.Po@am__quote@ # am--include-marker @@ -11765,232 +11860,232 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@shared/systemd/src/basic/$(DEPDIR)/libnm_systemd_shared_la-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-dns-domain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-web-util.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/NetworkManager-main.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/NetworkManager_all_sym-main.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/monitor-monitor.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_core-test-core.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_dcb-test-dcb.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_systemd-test-systemd.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_utils-test-utils.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/tests/config/$(DEPDIR)/test_config-test-config.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/NetworkManager-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_core-test-core.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_utils-test-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/tests/config/$(DEPDIR)/test_config-test-config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @@ -13198,376 +13293,376 @@ libnm-core/nm-libnm-core-intern/libnm_libnm_core_intern_la-nm-libnm-core-utils.l @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_core_nm_libnm_core_intern_libnm_libnm_core_intern_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm-core/nm-libnm-core-intern/libnm_libnm_core_intern_la-nm-libnm-core-utils.lo `test -f 'libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.c' || echo '$(srcdir)/'`libnm-core/nm-libnm-core-intern/nm-libnm-core-utils.c -libnm/liblibnm_la-nm-client.lo: libnm/nm-client.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-client.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-client.Tpo -c -o libnm/liblibnm_la-nm-client.lo `test -f 'libnm/nm-client.c' || echo '$(srcdir)/'`libnm/nm-client.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-client.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-client.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-client.c' object='libnm/liblibnm_la-nm-client.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-client.lo: libnm/nm-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-client.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-client.Tpo -c -o libnm/libnm_static_la-nm-client.lo `test -f 'libnm/nm-client.c' || echo '$(srcdir)/'`libnm/nm-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-client.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-client.c' object='libnm/libnm_static_la-nm-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-client.lo `test -f 'libnm/nm-client.c' || echo '$(srcdir)/'`libnm/nm-client.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-client.lo `test -f 'libnm/nm-client.c' || echo '$(srcdir)/'`libnm/nm-client.c -libnm/liblibnm_la-nm-object.lo: libnm/nm-object.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-object.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-object.Tpo -c -o libnm/liblibnm_la-nm-object.lo `test -f 'libnm/nm-object.c' || echo '$(srcdir)/'`libnm/nm-object.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-object.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-object.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-object.c' object='libnm/liblibnm_la-nm-object.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-object.lo: libnm/nm-object.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-object.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-object.Tpo -c -o libnm/libnm_static_la-nm-object.lo `test -f 'libnm/nm-object.c' || echo '$(srcdir)/'`libnm/nm-object.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-object.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-object.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-object.c' object='libnm/libnm_static_la-nm-object.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-object.lo `test -f 'libnm/nm-object.c' || echo '$(srcdir)/'`libnm/nm-object.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-object.lo `test -f 'libnm/nm-object.c' || echo '$(srcdir)/'`libnm/nm-object.c -libnm/liblibnm_la-nm-device.lo: libnm/nm-device.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device.Tpo -c -o libnm/liblibnm_la-nm-device.lo `test -f 'libnm/nm-device.c' || echo '$(srcdir)/'`libnm/nm-device.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device.c' object='libnm/liblibnm_la-nm-device.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device.lo: libnm/nm-device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device.Tpo -c -o libnm/libnm_static_la-nm-device.lo `test -f 'libnm/nm-device.c' || echo '$(srcdir)/'`libnm/nm-device.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device.c' object='libnm/libnm_static_la-nm-device.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device.lo `test -f 'libnm/nm-device.c' || echo '$(srcdir)/'`libnm/nm-device.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device.lo `test -f 'libnm/nm-device.c' || echo '$(srcdir)/'`libnm/nm-device.c -libnm/liblibnm_la-nm-active-connection.lo: libnm/nm-active-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-active-connection.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Tpo -c -o libnm/liblibnm_la-nm-active-connection.lo `test -f 'libnm/nm-active-connection.c' || echo '$(srcdir)/'`libnm/nm-active-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-active-connection.c' object='libnm/liblibnm_la-nm-active-connection.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-active-connection.lo: libnm/nm-active-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-active-connection.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Tpo -c -o libnm/libnm_static_la-nm-active-connection.lo `test -f 'libnm/nm-active-connection.c' || echo '$(srcdir)/'`libnm/nm-active-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-active-connection.c' object='libnm/libnm_static_la-nm-active-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-active-connection.lo `test -f 'libnm/nm-active-connection.c' || echo '$(srcdir)/'`libnm/nm-active-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-active-connection.lo `test -f 'libnm/nm-active-connection.c' || echo '$(srcdir)/'`libnm/nm-active-connection.c -libnm/liblibnm_la-nm-access-point.lo: libnm/nm-access-point.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-access-point.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Tpo -c -o libnm/liblibnm_la-nm-access-point.lo `test -f 'libnm/nm-access-point.c' || echo '$(srcdir)/'`libnm/nm-access-point.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-access-point.c' object='libnm/liblibnm_la-nm-access-point.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-access-point.lo: libnm/nm-access-point.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-access-point.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Tpo -c -o libnm/libnm_static_la-nm-access-point.lo `test -f 'libnm/nm-access-point.c' || echo '$(srcdir)/'`libnm/nm-access-point.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-access-point.c' object='libnm/libnm_static_la-nm-access-point.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-access-point.lo `test -f 'libnm/nm-access-point.c' || echo '$(srcdir)/'`libnm/nm-access-point.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-access-point.lo `test -f 'libnm/nm-access-point.c' || echo '$(srcdir)/'`libnm/nm-access-point.c -libnm/liblibnm_la-nm-checkpoint.lo: libnm/nm-checkpoint.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-checkpoint.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Tpo -c -o libnm/liblibnm_la-nm-checkpoint.lo `test -f 'libnm/nm-checkpoint.c' || echo '$(srcdir)/'`libnm/nm-checkpoint.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-checkpoint.c' object='libnm/liblibnm_la-nm-checkpoint.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-checkpoint.lo: libnm/nm-checkpoint.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-checkpoint.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Tpo -c -o libnm/libnm_static_la-nm-checkpoint.lo `test -f 'libnm/nm-checkpoint.c' || echo '$(srcdir)/'`libnm/nm-checkpoint.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-checkpoint.c' object='libnm/libnm_static_la-nm-checkpoint.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-checkpoint.lo `test -f 'libnm/nm-checkpoint.c' || echo '$(srcdir)/'`libnm/nm-checkpoint.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-checkpoint.lo `test -f 'libnm/nm-checkpoint.c' || echo '$(srcdir)/'`libnm/nm-checkpoint.c -libnm/liblibnm_la-nm-dbus-helpers.lo: libnm/nm-dbus-helpers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-dbus-helpers.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Tpo -c -o libnm/liblibnm_la-nm-dbus-helpers.lo `test -f 'libnm/nm-dbus-helpers.c' || echo '$(srcdir)/'`libnm/nm-dbus-helpers.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dbus-helpers.c' object='libnm/liblibnm_la-nm-dbus-helpers.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-dbus-helpers.lo: libnm/nm-dbus-helpers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-dbus-helpers.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Tpo -c -o libnm/libnm_static_la-nm-dbus-helpers.lo `test -f 'libnm/nm-dbus-helpers.c' || echo '$(srcdir)/'`libnm/nm-dbus-helpers.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dbus-helpers.c' object='libnm/libnm_static_la-nm-dbus-helpers.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-dbus-helpers.lo `test -f 'libnm/nm-dbus-helpers.c' || echo '$(srcdir)/'`libnm/nm-dbus-helpers.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-dbus-helpers.lo `test -f 'libnm/nm-dbus-helpers.c' || echo '$(srcdir)/'`libnm/nm-dbus-helpers.c -libnm/liblibnm_la-nm-device-6lowpan.lo: libnm/nm-device-6lowpan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-6lowpan.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Tpo -c -o libnm/liblibnm_la-nm-device-6lowpan.lo `test -f 'libnm/nm-device-6lowpan.c' || echo '$(srcdir)/'`libnm/nm-device-6lowpan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-6lowpan.c' object='libnm/liblibnm_la-nm-device-6lowpan.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-6lowpan.lo: libnm/nm-device-6lowpan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-6lowpan.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Tpo -c -o libnm/libnm_static_la-nm-device-6lowpan.lo `test -f 'libnm/nm-device-6lowpan.c' || echo '$(srcdir)/'`libnm/nm-device-6lowpan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-6lowpan.c' object='libnm/libnm_static_la-nm-device-6lowpan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-6lowpan.lo `test -f 'libnm/nm-device-6lowpan.c' || echo '$(srcdir)/'`libnm/nm-device-6lowpan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-6lowpan.lo `test -f 'libnm/nm-device-6lowpan.c' || echo '$(srcdir)/'`libnm/nm-device-6lowpan.c -libnm/liblibnm_la-nm-device-adsl.lo: libnm/nm-device-adsl.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-adsl.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Tpo -c -o libnm/liblibnm_la-nm-device-adsl.lo `test -f 'libnm/nm-device-adsl.c' || echo '$(srcdir)/'`libnm/nm-device-adsl.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-adsl.c' object='libnm/liblibnm_la-nm-device-adsl.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-adsl.lo: libnm/nm-device-adsl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-adsl.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Tpo -c -o libnm/libnm_static_la-nm-device-adsl.lo `test -f 'libnm/nm-device-adsl.c' || echo '$(srcdir)/'`libnm/nm-device-adsl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-adsl.c' object='libnm/libnm_static_la-nm-device-adsl.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-adsl.lo `test -f 'libnm/nm-device-adsl.c' || echo '$(srcdir)/'`libnm/nm-device-adsl.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-adsl.lo `test -f 'libnm/nm-device-adsl.c' || echo '$(srcdir)/'`libnm/nm-device-adsl.c -libnm/liblibnm_la-nm-device-bond.lo: libnm/nm-device-bond.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-bond.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Tpo -c -o libnm/liblibnm_la-nm-device-bond.lo `test -f 'libnm/nm-device-bond.c' || echo '$(srcdir)/'`libnm/nm-device-bond.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bond.c' object='libnm/liblibnm_la-nm-device-bond.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-bond.lo: libnm/nm-device-bond.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-bond.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Tpo -c -o libnm/libnm_static_la-nm-device-bond.lo `test -f 'libnm/nm-device-bond.c' || echo '$(srcdir)/'`libnm/nm-device-bond.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bond.c' object='libnm/libnm_static_la-nm-device-bond.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-bond.lo `test -f 'libnm/nm-device-bond.c' || echo '$(srcdir)/'`libnm/nm-device-bond.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-bond.lo `test -f 'libnm/nm-device-bond.c' || echo '$(srcdir)/'`libnm/nm-device-bond.c -libnm/liblibnm_la-nm-device-bridge.lo: libnm/nm-device-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-bridge.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Tpo -c -o libnm/liblibnm_la-nm-device-bridge.lo `test -f 'libnm/nm-device-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bridge.c' object='libnm/liblibnm_la-nm-device-bridge.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-bridge.lo: libnm/nm-device-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-bridge.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Tpo -c -o libnm/libnm_static_la-nm-device-bridge.lo `test -f 'libnm/nm-device-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bridge.c' object='libnm/libnm_static_la-nm-device-bridge.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-bridge.lo `test -f 'libnm/nm-device-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-bridge.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-bridge.lo `test -f 'libnm/nm-device-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-bridge.c -libnm/liblibnm_la-nm-device-bt.lo: libnm/nm-device-bt.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-bt.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Tpo -c -o libnm/liblibnm_la-nm-device-bt.lo `test -f 'libnm/nm-device-bt.c' || echo '$(srcdir)/'`libnm/nm-device-bt.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bt.c' object='libnm/liblibnm_la-nm-device-bt.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-bt.lo: libnm/nm-device-bt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-bt.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Tpo -c -o libnm/libnm_static_la-nm-device-bt.lo `test -f 'libnm/nm-device-bt.c' || echo '$(srcdir)/'`libnm/nm-device-bt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-bt.c' object='libnm/libnm_static_la-nm-device-bt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-bt.lo `test -f 'libnm/nm-device-bt.c' || echo '$(srcdir)/'`libnm/nm-device-bt.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-bt.lo `test -f 'libnm/nm-device-bt.c' || echo '$(srcdir)/'`libnm/nm-device-bt.c -libnm/liblibnm_la-nm-device-dummy.lo: libnm/nm-device-dummy.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-dummy.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Tpo -c -o libnm/liblibnm_la-nm-device-dummy.lo `test -f 'libnm/nm-device-dummy.c' || echo '$(srcdir)/'`libnm/nm-device-dummy.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-dummy.c' object='libnm/liblibnm_la-nm-device-dummy.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-dummy.lo: libnm/nm-device-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-dummy.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Tpo -c -o libnm/libnm_static_la-nm-device-dummy.lo `test -f 'libnm/nm-device-dummy.c' || echo '$(srcdir)/'`libnm/nm-device-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-dummy.c' object='libnm/libnm_static_la-nm-device-dummy.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-dummy.lo `test -f 'libnm/nm-device-dummy.c' || echo '$(srcdir)/'`libnm/nm-device-dummy.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-dummy.lo `test -f 'libnm/nm-device-dummy.c' || echo '$(srcdir)/'`libnm/nm-device-dummy.c -libnm/liblibnm_la-nm-device-ethernet.lo: libnm/nm-device-ethernet.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ethernet.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Tpo -c -o libnm/liblibnm_la-nm-device-ethernet.lo `test -f 'libnm/nm-device-ethernet.c' || echo '$(srcdir)/'`libnm/nm-device-ethernet.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ethernet.c' object='libnm/liblibnm_la-nm-device-ethernet.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ethernet.lo: libnm/nm-device-ethernet.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ethernet.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Tpo -c -o libnm/libnm_static_la-nm-device-ethernet.lo `test -f 'libnm/nm-device-ethernet.c' || echo '$(srcdir)/'`libnm/nm-device-ethernet.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ethernet.c' object='libnm/libnm_static_la-nm-device-ethernet.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ethernet.lo `test -f 'libnm/nm-device-ethernet.c' || echo '$(srcdir)/'`libnm/nm-device-ethernet.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ethernet.lo `test -f 'libnm/nm-device-ethernet.c' || echo '$(srcdir)/'`libnm/nm-device-ethernet.c -libnm/liblibnm_la-nm-device-generic.lo: libnm/nm-device-generic.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-generic.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Tpo -c -o libnm/liblibnm_la-nm-device-generic.lo `test -f 'libnm/nm-device-generic.c' || echo '$(srcdir)/'`libnm/nm-device-generic.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-generic.c' object='libnm/liblibnm_la-nm-device-generic.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-generic.lo: libnm/nm-device-generic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-generic.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Tpo -c -o libnm/libnm_static_la-nm-device-generic.lo `test -f 'libnm/nm-device-generic.c' || echo '$(srcdir)/'`libnm/nm-device-generic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-generic.c' object='libnm/libnm_static_la-nm-device-generic.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-generic.lo `test -f 'libnm/nm-device-generic.c' || echo '$(srcdir)/'`libnm/nm-device-generic.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-generic.lo `test -f 'libnm/nm-device-generic.c' || echo '$(srcdir)/'`libnm/nm-device-generic.c -libnm/liblibnm_la-nm-device-infiniband.lo: libnm/nm-device-infiniband.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-infiniband.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Tpo -c -o libnm/liblibnm_la-nm-device-infiniband.lo `test -f 'libnm/nm-device-infiniband.c' || echo '$(srcdir)/'`libnm/nm-device-infiniband.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-infiniband.c' object='libnm/liblibnm_la-nm-device-infiniband.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-infiniband.lo: libnm/nm-device-infiniband.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-infiniband.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Tpo -c -o libnm/libnm_static_la-nm-device-infiniband.lo `test -f 'libnm/nm-device-infiniband.c' || echo '$(srcdir)/'`libnm/nm-device-infiniband.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-infiniband.c' object='libnm/libnm_static_la-nm-device-infiniband.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-infiniband.lo `test -f 'libnm/nm-device-infiniband.c' || echo '$(srcdir)/'`libnm/nm-device-infiniband.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-infiniband.lo `test -f 'libnm/nm-device-infiniband.c' || echo '$(srcdir)/'`libnm/nm-device-infiniband.c -libnm/liblibnm_la-nm-device-ip-tunnel.lo: libnm/nm-device-ip-tunnel.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ip-tunnel.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Tpo -c -o libnm/liblibnm_la-nm-device-ip-tunnel.lo `test -f 'libnm/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`libnm/nm-device-ip-tunnel.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ip-tunnel.c' object='libnm/liblibnm_la-nm-device-ip-tunnel.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ip-tunnel.lo: libnm/nm-device-ip-tunnel.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ip-tunnel.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Tpo -c -o libnm/libnm_static_la-nm-device-ip-tunnel.lo `test -f 'libnm/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`libnm/nm-device-ip-tunnel.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ip-tunnel.c' object='libnm/libnm_static_la-nm-device-ip-tunnel.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ip-tunnel.lo `test -f 'libnm/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`libnm/nm-device-ip-tunnel.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ip-tunnel.lo `test -f 'libnm/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`libnm/nm-device-ip-tunnel.c -libnm/liblibnm_la-nm-device-macsec.lo: libnm/nm-device-macsec.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-macsec.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Tpo -c -o libnm/liblibnm_la-nm-device-macsec.lo `test -f 'libnm/nm-device-macsec.c' || echo '$(srcdir)/'`libnm/nm-device-macsec.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-macsec.c' object='libnm/liblibnm_la-nm-device-macsec.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-macsec.lo: libnm/nm-device-macsec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-macsec.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Tpo -c -o libnm/libnm_static_la-nm-device-macsec.lo `test -f 'libnm/nm-device-macsec.c' || echo '$(srcdir)/'`libnm/nm-device-macsec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-macsec.c' object='libnm/libnm_static_la-nm-device-macsec.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-macsec.lo `test -f 'libnm/nm-device-macsec.c' || echo '$(srcdir)/'`libnm/nm-device-macsec.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-macsec.lo `test -f 'libnm/nm-device-macsec.c' || echo '$(srcdir)/'`libnm/nm-device-macsec.c -libnm/liblibnm_la-nm-device-macvlan.lo: libnm/nm-device-macvlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-macvlan.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Tpo -c -o libnm/liblibnm_la-nm-device-macvlan.lo `test -f 'libnm/nm-device-macvlan.c' || echo '$(srcdir)/'`libnm/nm-device-macvlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-macvlan.c' object='libnm/liblibnm_la-nm-device-macvlan.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-macvlan.lo: libnm/nm-device-macvlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-macvlan.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Tpo -c -o libnm/libnm_static_la-nm-device-macvlan.lo `test -f 'libnm/nm-device-macvlan.c' || echo '$(srcdir)/'`libnm/nm-device-macvlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-macvlan.c' object='libnm/libnm_static_la-nm-device-macvlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-macvlan.lo `test -f 'libnm/nm-device-macvlan.c' || echo '$(srcdir)/'`libnm/nm-device-macvlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-macvlan.lo `test -f 'libnm/nm-device-macvlan.c' || echo '$(srcdir)/'`libnm/nm-device-macvlan.c -libnm/liblibnm_la-nm-device-modem.lo: libnm/nm-device-modem.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-modem.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Tpo -c -o libnm/liblibnm_la-nm-device-modem.lo `test -f 'libnm/nm-device-modem.c' || echo '$(srcdir)/'`libnm/nm-device-modem.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-modem.c' object='libnm/liblibnm_la-nm-device-modem.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-modem.lo: libnm/nm-device-modem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-modem.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Tpo -c -o libnm/libnm_static_la-nm-device-modem.lo `test -f 'libnm/nm-device-modem.c' || echo '$(srcdir)/'`libnm/nm-device-modem.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-modem.c' object='libnm/libnm_static_la-nm-device-modem.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-modem.lo `test -f 'libnm/nm-device-modem.c' || echo '$(srcdir)/'`libnm/nm-device-modem.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-modem.lo `test -f 'libnm/nm-device-modem.c' || echo '$(srcdir)/'`libnm/nm-device-modem.c -libnm/liblibnm_la-nm-device-olpc-mesh.lo: libnm/nm-device-olpc-mesh.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-olpc-mesh.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Tpo -c -o libnm/liblibnm_la-nm-device-olpc-mesh.lo `test -f 'libnm/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`libnm/nm-device-olpc-mesh.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-olpc-mesh.c' object='libnm/liblibnm_la-nm-device-olpc-mesh.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-olpc-mesh.lo: libnm/nm-device-olpc-mesh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-olpc-mesh.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Tpo -c -o libnm/libnm_static_la-nm-device-olpc-mesh.lo `test -f 'libnm/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`libnm/nm-device-olpc-mesh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-olpc-mesh.c' object='libnm/libnm_static_la-nm-device-olpc-mesh.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-olpc-mesh.lo `test -f 'libnm/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`libnm/nm-device-olpc-mesh.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-olpc-mesh.lo `test -f 'libnm/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`libnm/nm-device-olpc-mesh.c -libnm/liblibnm_la-nm-device-ovs-bridge.lo: libnm/nm-device-ovs-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ovs-bridge.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Tpo -c -o libnm/liblibnm_la-nm-device-ovs-bridge.lo `test -f 'libnm/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-bridge.c' object='libnm/liblibnm_la-nm-device-ovs-bridge.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ovs-bridge.lo: libnm/nm-device-ovs-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ovs-bridge.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Tpo -c -o libnm/libnm_static_la-nm-device-ovs-bridge.lo `test -f 'libnm/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-bridge.c' object='libnm/libnm_static_la-nm-device-ovs-bridge.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ovs-bridge.lo `test -f 'libnm/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-bridge.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ovs-bridge.lo `test -f 'libnm/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-bridge.c -libnm/liblibnm_la-nm-device-ovs-interface.lo: libnm/nm-device-ovs-interface.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ovs-interface.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Tpo -c -o libnm/liblibnm_la-nm-device-ovs-interface.lo `test -f 'libnm/nm-device-ovs-interface.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-interface.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-interface.c' object='libnm/liblibnm_la-nm-device-ovs-interface.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ovs-interface.lo: libnm/nm-device-ovs-interface.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ovs-interface.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Tpo -c -o libnm/libnm_static_la-nm-device-ovs-interface.lo `test -f 'libnm/nm-device-ovs-interface.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-interface.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-interface.c' object='libnm/libnm_static_la-nm-device-ovs-interface.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ovs-interface.lo `test -f 'libnm/nm-device-ovs-interface.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-interface.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ovs-interface.lo `test -f 'libnm/nm-device-ovs-interface.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-interface.c -libnm/liblibnm_la-nm-device-ovs-port.lo: libnm/nm-device-ovs-port.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ovs-port.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Tpo -c -o libnm/liblibnm_la-nm-device-ovs-port.lo `test -f 'libnm/nm-device-ovs-port.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-port.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-port.c' object='libnm/liblibnm_la-nm-device-ovs-port.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ovs-port.lo: libnm/nm-device-ovs-port.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ovs-port.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Tpo -c -o libnm/libnm_static_la-nm-device-ovs-port.lo `test -f 'libnm/nm-device-ovs-port.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-port.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ovs-port.c' object='libnm/libnm_static_la-nm-device-ovs-port.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ovs-port.lo `test -f 'libnm/nm-device-ovs-port.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-port.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ovs-port.lo `test -f 'libnm/nm-device-ovs-port.c' || echo '$(srcdir)/'`libnm/nm-device-ovs-port.c -libnm/liblibnm_la-nm-device-ppp.lo: libnm/nm-device-ppp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-ppp.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Tpo -c -o libnm/liblibnm_la-nm-device-ppp.lo `test -f 'libnm/nm-device-ppp.c' || echo '$(srcdir)/'`libnm/nm-device-ppp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ppp.c' object='libnm/liblibnm_la-nm-device-ppp.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-ppp.lo: libnm/nm-device-ppp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-ppp.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Tpo -c -o libnm/libnm_static_la-nm-device-ppp.lo `test -f 'libnm/nm-device-ppp.c' || echo '$(srcdir)/'`libnm/nm-device-ppp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-ppp.c' object='libnm/libnm_static_la-nm-device-ppp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-ppp.lo `test -f 'libnm/nm-device-ppp.c' || echo '$(srcdir)/'`libnm/nm-device-ppp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-ppp.lo `test -f 'libnm/nm-device-ppp.c' || echo '$(srcdir)/'`libnm/nm-device-ppp.c -libnm/liblibnm_la-nm-device-team.lo: libnm/nm-device-team.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-team.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Tpo -c -o libnm/liblibnm_la-nm-device-team.lo `test -f 'libnm/nm-device-team.c' || echo '$(srcdir)/'`libnm/nm-device-team.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-team.c' object='libnm/liblibnm_la-nm-device-team.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-team.lo: libnm/nm-device-team.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-team.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Tpo -c -o libnm/libnm_static_la-nm-device-team.lo `test -f 'libnm/nm-device-team.c' || echo '$(srcdir)/'`libnm/nm-device-team.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-team.c' object='libnm/libnm_static_la-nm-device-team.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-team.lo `test -f 'libnm/nm-device-team.c' || echo '$(srcdir)/'`libnm/nm-device-team.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-team.lo `test -f 'libnm/nm-device-team.c' || echo '$(srcdir)/'`libnm/nm-device-team.c -libnm/liblibnm_la-nm-device-tun.lo: libnm/nm-device-tun.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-tun.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Tpo -c -o libnm/liblibnm_la-nm-device-tun.lo `test -f 'libnm/nm-device-tun.c' || echo '$(srcdir)/'`libnm/nm-device-tun.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-tun.c' object='libnm/liblibnm_la-nm-device-tun.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-tun.lo: libnm/nm-device-tun.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-tun.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Tpo -c -o libnm/libnm_static_la-nm-device-tun.lo `test -f 'libnm/nm-device-tun.c' || echo '$(srcdir)/'`libnm/nm-device-tun.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-tun.c' object='libnm/libnm_static_la-nm-device-tun.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-tun.lo `test -f 'libnm/nm-device-tun.c' || echo '$(srcdir)/'`libnm/nm-device-tun.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-tun.lo `test -f 'libnm/nm-device-tun.c' || echo '$(srcdir)/'`libnm/nm-device-tun.c -libnm/liblibnm_la-nm-device-veth.lo: libnm/nm-device-veth.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-veth.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Tpo -c -o libnm/liblibnm_la-nm-device-veth.lo `test -f 'libnm/nm-device-veth.c' || echo '$(srcdir)/'`libnm/nm-device-veth.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-veth.c' object='libnm/liblibnm_la-nm-device-veth.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-veth.lo: libnm/nm-device-veth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-veth.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Tpo -c -o libnm/libnm_static_la-nm-device-veth.lo `test -f 'libnm/nm-device-veth.c' || echo '$(srcdir)/'`libnm/nm-device-veth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-veth.c' object='libnm/libnm_static_la-nm-device-veth.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-veth.lo `test -f 'libnm/nm-device-veth.c' || echo '$(srcdir)/'`libnm/nm-device-veth.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-veth.lo `test -f 'libnm/nm-device-veth.c' || echo '$(srcdir)/'`libnm/nm-device-veth.c -libnm/liblibnm_la-nm-device-vlan.lo: libnm/nm-device-vlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-vlan.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Tpo -c -o libnm/liblibnm_la-nm-device-vlan.lo `test -f 'libnm/nm-device-vlan.c' || echo '$(srcdir)/'`libnm/nm-device-vlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vlan.c' object='libnm/liblibnm_la-nm-device-vlan.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-vlan.lo: libnm/nm-device-vlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-vlan.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Tpo -c -o libnm/libnm_static_la-nm-device-vlan.lo `test -f 'libnm/nm-device-vlan.c' || echo '$(srcdir)/'`libnm/nm-device-vlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vlan.c' object='libnm/libnm_static_la-nm-device-vlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-vlan.lo `test -f 'libnm/nm-device-vlan.c' || echo '$(srcdir)/'`libnm/nm-device-vlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-vlan.lo `test -f 'libnm/nm-device-vlan.c' || echo '$(srcdir)/'`libnm/nm-device-vlan.c -libnm/liblibnm_la-nm-device-vrf.lo: libnm/nm-device-vrf.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-vrf.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Tpo -c -o libnm/liblibnm_la-nm-device-vrf.lo `test -f 'libnm/nm-device-vrf.c' || echo '$(srcdir)/'`libnm/nm-device-vrf.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vrf.c' object='libnm/liblibnm_la-nm-device-vrf.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-vrf.lo: libnm/nm-device-vrf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-vrf.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Tpo -c -o libnm/libnm_static_la-nm-device-vrf.lo `test -f 'libnm/nm-device-vrf.c' || echo '$(srcdir)/'`libnm/nm-device-vrf.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vrf.c' object='libnm/libnm_static_la-nm-device-vrf.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-vrf.lo `test -f 'libnm/nm-device-vrf.c' || echo '$(srcdir)/'`libnm/nm-device-vrf.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-vrf.lo `test -f 'libnm/nm-device-vrf.c' || echo '$(srcdir)/'`libnm/nm-device-vrf.c -libnm/liblibnm_la-nm-device-vxlan.lo: libnm/nm-device-vxlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-vxlan.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Tpo -c -o libnm/liblibnm_la-nm-device-vxlan.lo `test -f 'libnm/nm-device-vxlan.c' || echo '$(srcdir)/'`libnm/nm-device-vxlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vxlan.c' object='libnm/liblibnm_la-nm-device-vxlan.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-vxlan.lo: libnm/nm-device-vxlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-vxlan.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Tpo -c -o libnm/libnm_static_la-nm-device-vxlan.lo `test -f 'libnm/nm-device-vxlan.c' || echo '$(srcdir)/'`libnm/nm-device-vxlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-vxlan.c' object='libnm/libnm_static_la-nm-device-vxlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-vxlan.lo `test -f 'libnm/nm-device-vxlan.c' || echo '$(srcdir)/'`libnm/nm-device-vxlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-vxlan.lo `test -f 'libnm/nm-device-vxlan.c' || echo '$(srcdir)/'`libnm/nm-device-vxlan.c -libnm/liblibnm_la-nm-device-wifi-p2p.lo: libnm/nm-device-wifi-p2p.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-wifi-p2p.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Tpo -c -o libnm/liblibnm_la-nm-device-wifi-p2p.lo `test -f 'libnm/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`libnm/nm-device-wifi-p2p.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wifi-p2p.c' object='libnm/liblibnm_la-nm-device-wifi-p2p.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-wifi-p2p.lo: libnm/nm-device-wifi-p2p.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-wifi-p2p.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Tpo -c -o libnm/libnm_static_la-nm-device-wifi-p2p.lo `test -f 'libnm/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`libnm/nm-device-wifi-p2p.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wifi-p2p.c' object='libnm/libnm_static_la-nm-device-wifi-p2p.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-wifi-p2p.lo `test -f 'libnm/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`libnm/nm-device-wifi-p2p.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-wifi-p2p.lo `test -f 'libnm/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`libnm/nm-device-wifi-p2p.c -libnm/liblibnm_la-nm-device-wifi.lo: libnm/nm-device-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-wifi.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Tpo -c -o libnm/liblibnm_la-nm-device-wifi.lo `test -f 'libnm/nm-device-wifi.c' || echo '$(srcdir)/'`libnm/nm-device-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wifi.c' object='libnm/liblibnm_la-nm-device-wifi.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-wifi.lo: libnm/nm-device-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-wifi.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Tpo -c -o libnm/libnm_static_la-nm-device-wifi.lo `test -f 'libnm/nm-device-wifi.c' || echo '$(srcdir)/'`libnm/nm-device-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wifi.c' object='libnm/libnm_static_la-nm-device-wifi.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-wifi.lo `test -f 'libnm/nm-device-wifi.c' || echo '$(srcdir)/'`libnm/nm-device-wifi.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-wifi.lo `test -f 'libnm/nm-device-wifi.c' || echo '$(srcdir)/'`libnm/nm-device-wifi.c -libnm/liblibnm_la-nm-device-wimax.lo: libnm/nm-device-wimax.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-wimax.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Tpo -c -o libnm/liblibnm_la-nm-device-wimax.lo `test -f 'libnm/nm-device-wimax.c' || echo '$(srcdir)/'`libnm/nm-device-wimax.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wimax.c' object='libnm/liblibnm_la-nm-device-wimax.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-wimax.lo: libnm/nm-device-wimax.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-wimax.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Tpo -c -o libnm/libnm_static_la-nm-device-wimax.lo `test -f 'libnm/nm-device-wimax.c' || echo '$(srcdir)/'`libnm/nm-device-wimax.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wimax.c' object='libnm/libnm_static_la-nm-device-wimax.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-wimax.lo `test -f 'libnm/nm-device-wimax.c' || echo '$(srcdir)/'`libnm/nm-device-wimax.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-wimax.lo `test -f 'libnm/nm-device-wimax.c' || echo '$(srcdir)/'`libnm/nm-device-wimax.c -libnm/liblibnm_la-nm-device-wireguard.lo: libnm/nm-device-wireguard.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-wireguard.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Tpo -c -o libnm/liblibnm_la-nm-device-wireguard.lo `test -f 'libnm/nm-device-wireguard.c' || echo '$(srcdir)/'`libnm/nm-device-wireguard.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wireguard.c' object='libnm/liblibnm_la-nm-device-wireguard.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-wireguard.lo: libnm/nm-device-wireguard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-wireguard.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Tpo -c -o libnm/libnm_static_la-nm-device-wireguard.lo `test -f 'libnm/nm-device-wireguard.c' || echo '$(srcdir)/'`libnm/nm-device-wireguard.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wireguard.c' object='libnm/libnm_static_la-nm-device-wireguard.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-wireguard.lo `test -f 'libnm/nm-device-wireguard.c' || echo '$(srcdir)/'`libnm/nm-device-wireguard.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-wireguard.lo `test -f 'libnm/nm-device-wireguard.c' || echo '$(srcdir)/'`libnm/nm-device-wireguard.c -libnm/liblibnm_la-nm-device-wpan.lo: libnm/nm-device-wpan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-device-wpan.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Tpo -c -o libnm/liblibnm_la-nm-device-wpan.lo `test -f 'libnm/nm-device-wpan.c' || echo '$(srcdir)/'`libnm/nm-device-wpan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wpan.c' object='libnm/liblibnm_la-nm-device-wpan.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-device-wpan.lo: libnm/nm-device-wpan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-device-wpan.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Tpo -c -o libnm/libnm_static_la-nm-device-wpan.lo `test -f 'libnm/nm-device-wpan.c' || echo '$(srcdir)/'`libnm/nm-device-wpan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-device-wpan.c' object='libnm/libnm_static_la-nm-device-wpan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-device-wpan.lo `test -f 'libnm/nm-device-wpan.c' || echo '$(srcdir)/'`libnm/nm-device-wpan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-device-wpan.lo `test -f 'libnm/nm-device-wpan.c' || echo '$(srcdir)/'`libnm/nm-device-wpan.c -libnm/liblibnm_la-nm-dhcp-config.lo: libnm/nm-dhcp-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-dhcp-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Tpo -c -o libnm/liblibnm_la-nm-dhcp-config.lo `test -f 'libnm/nm-dhcp-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp-config.c' object='libnm/liblibnm_la-nm-dhcp-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-dhcp-config.lo: libnm/nm-dhcp-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-dhcp-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Tpo -c -o libnm/libnm_static_la-nm-dhcp-config.lo `test -f 'libnm/nm-dhcp-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp-config.c' object='libnm/libnm_static_la-nm-dhcp-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-dhcp-config.lo `test -f 'libnm/nm-dhcp-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-dhcp-config.lo `test -f 'libnm/nm-dhcp-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp-config.c -libnm/liblibnm_la-nm-dhcp4-config.lo: libnm/nm-dhcp4-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-dhcp4-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Tpo -c -o libnm/liblibnm_la-nm-dhcp4-config.lo `test -f 'libnm/nm-dhcp4-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp4-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp4-config.c' object='libnm/liblibnm_la-nm-dhcp4-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-dhcp4-config.lo: libnm/nm-dhcp4-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-dhcp4-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Tpo -c -o libnm/libnm_static_la-nm-dhcp4-config.lo `test -f 'libnm/nm-dhcp4-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp4-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp4-config.c' object='libnm/libnm_static_la-nm-dhcp4-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-dhcp4-config.lo `test -f 'libnm/nm-dhcp4-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp4-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-dhcp4-config.lo `test -f 'libnm/nm-dhcp4-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp4-config.c -libnm/liblibnm_la-nm-dhcp6-config.lo: libnm/nm-dhcp6-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-dhcp6-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Tpo -c -o libnm/liblibnm_la-nm-dhcp6-config.lo `test -f 'libnm/nm-dhcp6-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp6-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp6-config.c' object='libnm/liblibnm_la-nm-dhcp6-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-dhcp6-config.lo: libnm/nm-dhcp6-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-dhcp6-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Tpo -c -o libnm/libnm_static_la-nm-dhcp6-config.lo `test -f 'libnm/nm-dhcp6-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp6-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dhcp6-config.c' object='libnm/libnm_static_la-nm-dhcp6-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-dhcp6-config.lo `test -f 'libnm/nm-dhcp6-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp6-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-dhcp6-config.lo `test -f 'libnm/nm-dhcp6-config.c' || echo '$(srcdir)/'`libnm/nm-dhcp6-config.c -libnm/liblibnm_la-nm-dns-manager.lo: libnm/nm-dns-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-dns-manager.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Tpo -c -o libnm/liblibnm_la-nm-dns-manager.lo `test -f 'libnm/nm-dns-manager.c' || echo '$(srcdir)/'`libnm/nm-dns-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dns-manager.c' object='libnm/liblibnm_la-nm-dns-manager.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-dns-manager.lo: libnm/nm-dns-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-dns-manager.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Tpo -c -o libnm/libnm_static_la-nm-dns-manager.lo `test -f 'libnm/nm-dns-manager.c' || echo '$(srcdir)/'`libnm/nm-dns-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-dns-manager.c' object='libnm/libnm_static_la-nm-dns-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-dns-manager.lo `test -f 'libnm/nm-dns-manager.c' || echo '$(srcdir)/'`libnm/nm-dns-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-dns-manager.lo `test -f 'libnm/nm-dns-manager.c' || echo '$(srcdir)/'`libnm/nm-dns-manager.c -libnm/liblibnm_la-nm-ip-config.lo: libnm/nm-ip-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-ip-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Tpo -c -o libnm/liblibnm_la-nm-ip-config.lo `test -f 'libnm/nm-ip-config.c' || echo '$(srcdir)/'`libnm/nm-ip-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip-config.c' object='libnm/liblibnm_la-nm-ip-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-ip-config.lo: libnm/nm-ip-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-ip-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Tpo -c -o libnm/libnm_static_la-nm-ip-config.lo `test -f 'libnm/nm-ip-config.c' || echo '$(srcdir)/'`libnm/nm-ip-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip-config.c' object='libnm/libnm_static_la-nm-ip-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-ip-config.lo `test -f 'libnm/nm-ip-config.c' || echo '$(srcdir)/'`libnm/nm-ip-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-ip-config.lo `test -f 'libnm/nm-ip-config.c' || echo '$(srcdir)/'`libnm/nm-ip-config.c -libnm/liblibnm_la-nm-ip4-config.lo: libnm/nm-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-ip4-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Tpo -c -o libnm/liblibnm_la-nm-ip4-config.lo `test -f 'libnm/nm-ip4-config.c' || echo '$(srcdir)/'`libnm/nm-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip4-config.c' object='libnm/liblibnm_la-nm-ip4-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-ip4-config.lo: libnm/nm-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-ip4-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Tpo -c -o libnm/libnm_static_la-nm-ip4-config.lo `test -f 'libnm/nm-ip4-config.c' || echo '$(srcdir)/'`libnm/nm-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip4-config.c' object='libnm/libnm_static_la-nm-ip4-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-ip4-config.lo `test -f 'libnm/nm-ip4-config.c' || echo '$(srcdir)/'`libnm/nm-ip4-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-ip4-config.lo `test -f 'libnm/nm-ip4-config.c' || echo '$(srcdir)/'`libnm/nm-ip4-config.c -libnm/liblibnm_la-nm-ip6-config.lo: libnm/nm-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-ip6-config.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Tpo -c -o libnm/liblibnm_la-nm-ip6-config.lo `test -f 'libnm/nm-ip6-config.c' || echo '$(srcdir)/'`libnm/nm-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip6-config.c' object='libnm/liblibnm_la-nm-ip6-config.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-ip6-config.lo: libnm/nm-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-ip6-config.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Tpo -c -o libnm/libnm_static_la-nm-ip6-config.lo `test -f 'libnm/nm-ip6-config.c' || echo '$(srcdir)/'`libnm/nm-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-ip6-config.c' object='libnm/libnm_static_la-nm-ip6-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-ip6-config.lo `test -f 'libnm/nm-ip6-config.c' || echo '$(srcdir)/'`libnm/nm-ip6-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-ip6-config.lo `test -f 'libnm/nm-ip6-config.c' || echo '$(srcdir)/'`libnm/nm-ip6-config.c -libnm/liblibnm_la-nm-libnm-utils.lo: libnm/nm-libnm-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-libnm-utils.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Tpo -c -o libnm/liblibnm_la-nm-libnm-utils.lo `test -f 'libnm/nm-libnm-utils.c' || echo '$(srcdir)/'`libnm/nm-libnm-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-libnm-utils.c' object='libnm/liblibnm_la-nm-libnm-utils.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-libnm-utils.lo: libnm/nm-libnm-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-libnm-utils.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Tpo -c -o libnm/libnm_static_la-nm-libnm-utils.lo `test -f 'libnm/nm-libnm-utils.c' || echo '$(srcdir)/'`libnm/nm-libnm-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-libnm-utils.c' object='libnm/libnm_static_la-nm-libnm-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-libnm-utils.lo `test -f 'libnm/nm-libnm-utils.c' || echo '$(srcdir)/'`libnm/nm-libnm-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-libnm-utils.lo `test -f 'libnm/nm-libnm-utils.c' || echo '$(srcdir)/'`libnm/nm-libnm-utils.c -libnm/liblibnm_la-nm-remote-connection.lo: libnm/nm-remote-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-remote-connection.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Tpo -c -o libnm/liblibnm_la-nm-remote-connection.lo `test -f 'libnm/nm-remote-connection.c' || echo '$(srcdir)/'`libnm/nm-remote-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-remote-connection.c' object='libnm/liblibnm_la-nm-remote-connection.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-remote-connection.lo: libnm/nm-remote-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-remote-connection.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Tpo -c -o libnm/libnm_static_la-nm-remote-connection.lo `test -f 'libnm/nm-remote-connection.c' || echo '$(srcdir)/'`libnm/nm-remote-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-remote-connection.c' object='libnm/libnm_static_la-nm-remote-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-remote-connection.lo `test -f 'libnm/nm-remote-connection.c' || echo '$(srcdir)/'`libnm/nm-remote-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-remote-connection.lo `test -f 'libnm/nm-remote-connection.c' || echo '$(srcdir)/'`libnm/nm-remote-connection.c -libnm/liblibnm_la-nm-secret-agent-old.lo: libnm/nm-secret-agent-old.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-secret-agent-old.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Tpo -c -o libnm/liblibnm_la-nm-secret-agent-old.lo `test -f 'libnm/nm-secret-agent-old.c' || echo '$(srcdir)/'`libnm/nm-secret-agent-old.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-secret-agent-old.c' object='libnm/liblibnm_la-nm-secret-agent-old.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-secret-agent-old.lo: libnm/nm-secret-agent-old.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-secret-agent-old.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Tpo -c -o libnm/libnm_static_la-nm-secret-agent-old.lo `test -f 'libnm/nm-secret-agent-old.c' || echo '$(srcdir)/'`libnm/nm-secret-agent-old.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-secret-agent-old.c' object='libnm/libnm_static_la-nm-secret-agent-old.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-secret-agent-old.lo `test -f 'libnm/nm-secret-agent-old.c' || echo '$(srcdir)/'`libnm/nm-secret-agent-old.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-secret-agent-old.lo `test -f 'libnm/nm-secret-agent-old.c' || echo '$(srcdir)/'`libnm/nm-secret-agent-old.c -libnm/liblibnm_la-nm-vpn-connection.lo: libnm/nm-vpn-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-vpn-connection.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Tpo -c -o libnm/liblibnm_la-nm-vpn-connection.lo `test -f 'libnm/nm-vpn-connection.c' || echo '$(srcdir)/'`libnm/nm-vpn-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-connection.c' object='libnm/liblibnm_la-nm-vpn-connection.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-vpn-connection.lo: libnm/nm-vpn-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-vpn-connection.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Tpo -c -o libnm/libnm_static_la-nm-vpn-connection.lo `test -f 'libnm/nm-vpn-connection.c' || echo '$(srcdir)/'`libnm/nm-vpn-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-connection.c' object='libnm/libnm_static_la-nm-vpn-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-vpn-connection.lo `test -f 'libnm/nm-vpn-connection.c' || echo '$(srcdir)/'`libnm/nm-vpn-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-vpn-connection.lo `test -f 'libnm/nm-vpn-connection.c' || echo '$(srcdir)/'`libnm/nm-vpn-connection.c -libnm/liblibnm_la-nm-vpn-editor.lo: libnm/nm-vpn-editor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-vpn-editor.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Tpo -c -o libnm/liblibnm_la-nm-vpn-editor.lo `test -f 'libnm/nm-vpn-editor.c' || echo '$(srcdir)/'`libnm/nm-vpn-editor.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-editor.c' object='libnm/liblibnm_la-nm-vpn-editor.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-vpn-editor.lo: libnm/nm-vpn-editor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-vpn-editor.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Tpo -c -o libnm/libnm_static_la-nm-vpn-editor.lo `test -f 'libnm/nm-vpn-editor.c' || echo '$(srcdir)/'`libnm/nm-vpn-editor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-editor.c' object='libnm/libnm_static_la-nm-vpn-editor.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-vpn-editor.lo `test -f 'libnm/nm-vpn-editor.c' || echo '$(srcdir)/'`libnm/nm-vpn-editor.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-vpn-editor.lo `test -f 'libnm/nm-vpn-editor.c' || echo '$(srcdir)/'`libnm/nm-vpn-editor.c -libnm/liblibnm_la-nm-vpn-plugin-old.lo: libnm/nm-vpn-plugin-old.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-vpn-plugin-old.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Tpo -c -o libnm/liblibnm_la-nm-vpn-plugin-old.lo `test -f 'libnm/nm-vpn-plugin-old.c' || echo '$(srcdir)/'`libnm/nm-vpn-plugin-old.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-plugin-old.c' object='libnm/liblibnm_la-nm-vpn-plugin-old.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-vpn-plugin-old.lo: libnm/nm-vpn-plugin-old.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-vpn-plugin-old.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Tpo -c -o libnm/libnm_static_la-nm-vpn-plugin-old.lo `test -f 'libnm/nm-vpn-plugin-old.c' || echo '$(srcdir)/'`libnm/nm-vpn-plugin-old.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-plugin-old.c' object='libnm/libnm_static_la-nm-vpn-plugin-old.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-vpn-plugin-old.lo `test -f 'libnm/nm-vpn-plugin-old.c' || echo '$(srcdir)/'`libnm/nm-vpn-plugin-old.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-vpn-plugin-old.lo `test -f 'libnm/nm-vpn-plugin-old.c' || echo '$(srcdir)/'`libnm/nm-vpn-plugin-old.c -libnm/liblibnm_la-nm-vpn-service-plugin.lo: libnm/nm-vpn-service-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-vpn-service-plugin.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Tpo -c -o libnm/liblibnm_la-nm-vpn-service-plugin.lo `test -f 'libnm/nm-vpn-service-plugin.c' || echo '$(srcdir)/'`libnm/nm-vpn-service-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-service-plugin.c' object='libnm/liblibnm_la-nm-vpn-service-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-vpn-service-plugin.lo: libnm/nm-vpn-service-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-vpn-service-plugin.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Tpo -c -o libnm/libnm_static_la-nm-vpn-service-plugin.lo `test -f 'libnm/nm-vpn-service-plugin.c' || echo '$(srcdir)/'`libnm/nm-vpn-service-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-vpn-service-plugin.c' object='libnm/libnm_static_la-nm-vpn-service-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-vpn-service-plugin.lo `test -f 'libnm/nm-vpn-service-plugin.c' || echo '$(srcdir)/'`libnm/nm-vpn-service-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-vpn-service-plugin.lo `test -f 'libnm/nm-vpn-service-plugin.c' || echo '$(srcdir)/'`libnm/nm-vpn-service-plugin.c -libnm/liblibnm_la-nm-wifi-p2p-peer.lo: libnm/nm-wifi-p2p-peer.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-wifi-p2p-peer.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Tpo -c -o libnm/liblibnm_la-nm-wifi-p2p-peer.lo `test -f 'libnm/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`libnm/nm-wifi-p2p-peer.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-wifi-p2p-peer.c' object='libnm/liblibnm_la-nm-wifi-p2p-peer.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-wifi-p2p-peer.lo: libnm/nm-wifi-p2p-peer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-wifi-p2p-peer.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Tpo -c -o libnm/libnm_static_la-nm-wifi-p2p-peer.lo `test -f 'libnm/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`libnm/nm-wifi-p2p-peer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-wifi-p2p-peer.c' object='libnm/libnm_static_la-nm-wifi-p2p-peer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-wifi-p2p-peer.lo `test -f 'libnm/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`libnm/nm-wifi-p2p-peer.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-wifi-p2p-peer.lo `test -f 'libnm/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`libnm/nm-wifi-p2p-peer.c -libnm/liblibnm_la-nm-wimax-nsp.lo: libnm/nm-wimax-nsp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-wimax-nsp.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Tpo -c -o libnm/liblibnm_la-nm-wimax-nsp.lo `test -f 'libnm/nm-wimax-nsp.c' || echo '$(srcdir)/'`libnm/nm-wimax-nsp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-wimax-nsp.c' object='libnm/liblibnm_la-nm-wimax-nsp.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-wimax-nsp.lo: libnm/nm-wimax-nsp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-wimax-nsp.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Tpo -c -o libnm/libnm_static_la-nm-wimax-nsp.lo `test -f 'libnm/nm-wimax-nsp.c' || echo '$(srcdir)/'`libnm/nm-wimax-nsp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-wimax-nsp.c' object='libnm/libnm_static_la-nm-wimax-nsp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-wimax-nsp.lo `test -f 'libnm/nm-wimax-nsp.c' || echo '$(srcdir)/'`libnm/nm-wimax-nsp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-wimax-nsp.lo `test -f 'libnm/nm-wimax-nsp.c' || echo '$(srcdir)/'`libnm/nm-wimax-nsp.c -libnm/liblibnm_la-nm-enum-types.lo: libnm/nm-enum-types.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/liblibnm_la-nm-enum-types.lo -MD -MP -MF libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Tpo -c -o libnm/liblibnm_la-nm-enum-types.lo `test -f 'libnm/nm-enum-types.c' || echo '$(srcdir)/'`libnm/nm-enum-types.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Tpo libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-enum-types.c' object='libnm/liblibnm_la-nm-enum-types.lo' libtool=yes @AMDEPBACKSLASH@ +libnm/libnm_static_la-nm-enum-types.lo: libnm/nm-enum-types.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/libnm_static_la-nm-enum-types.lo -MD -MP -MF libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Tpo -c -o libnm/libnm_static_la-nm-enum-types.lo `test -f 'libnm/nm-enum-types.c' || echo '$(srcdir)/'`libnm/nm-enum-types.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Tpo libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libnm/nm-enum-types.c' object='libnm/libnm_static_la-nm-enum-types.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_liblibnm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/liblibnm_la-nm-enum-types.lo `test -f 'libnm/nm-enum-types.c' || echo '$(srcdir)/'`libnm/nm-enum-types.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_libnm_static_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libnm/libnm_static_la-nm-enum-types.lo `test -f 'libnm/nm-enum-types.c' || echo '$(srcdir)/'`libnm/nm-enum-types.c libnm/nm-libnm-aux/libnm_libnm_aux_la-nm-libnm-aux.lo: libnm/nm-libnm-aux/nm-libnm-aux.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnm_nm_libnm_aux_libnm_libnm_aux_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libnm/nm-libnm-aux/libnm_libnm_aux_la-nm-libnm-aux.lo -MD -MP -MF libnm/nm-libnm-aux/$(DEPDIR)/libnm_libnm_aux_la-nm-libnm-aux.Tpo -c -o libnm/nm-libnm-aux/libnm_libnm_aux_la-nm-libnm-aux.lo `test -f 'libnm/nm-libnm-aux/nm-libnm-aux.c' || echo '$(srcdir)/'`libnm/nm-libnm-aux/nm-libnm-aux.c @@ -14122,1258 +14217,1258 @@ shared/systemd/src/shared/libnm_systemd_shared_la-web-util.lo: shared/systemd/sr @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(shared_systemd_libnm_systemd_shared_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o shared/systemd/src/shared/libnm_systemd_shared_la-web-util.lo `test -f 'shared/systemd/src/shared/web-util.c' || echo '$(srcdir)/'`shared/systemd/src/shared/web-util.c -src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo: src/devices/adsl/nm-atm-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo -MD -MP -MF src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Tpo -c -o src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo `test -f 'src/devices/adsl/nm-atm-manager.c' || echo '$(srcdir)/'`src/devices/adsl/nm-atm-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Tpo src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/adsl/nm-atm-manager.c' object='src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo: src/core/devices/adsl/nm-atm-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo -MD -MP -MF src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Tpo -c -o src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo `test -f 'src/core/devices/adsl/nm-atm-manager.c' || echo '$(srcdir)/'`src/core/devices/adsl/nm-atm-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Tpo src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/adsl/nm-atm-manager.c' object='src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo `test -f 'src/devices/adsl/nm-atm-manager.c' || echo '$(srcdir)/'`src/devices/adsl/nm-atm-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-atm-manager.lo `test -f 'src/core/devices/adsl/nm-atm-manager.c' || echo '$(srcdir)/'`src/core/devices/adsl/nm-atm-manager.c -src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo: src/devices/adsl/nm-device-adsl.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo -MD -MP -MF src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Tpo -c -o src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo `test -f 'src/devices/adsl/nm-device-adsl.c' || echo '$(srcdir)/'`src/devices/adsl/nm-device-adsl.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Tpo src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/adsl/nm-device-adsl.c' object='src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo: src/core/devices/adsl/nm-device-adsl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo -MD -MP -MF src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Tpo -c -o src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo `test -f 'src/core/devices/adsl/nm-device-adsl.c' || echo '$(srcdir)/'`src/core/devices/adsl/nm-device-adsl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Tpo src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/adsl/nm-device-adsl.c' object='src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo `test -f 'src/devices/adsl/nm-device-adsl.c' || echo '$(srcdir)/'`src/devices/adsl/nm-device-adsl.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_adsl_libnm_device_plugin_adsl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/adsl/libnm_device_plugin_adsl_la-nm-device-adsl.lo `test -f 'src/core/devices/adsl/nm-device-adsl.c' || echo '$(srcdir)/'`src/core/devices/adsl/nm-device-adsl.c -src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo: src/devices/bluetooth/nm-bt-error.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo -MD -MP -MF src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Tpo -c -o src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo `test -f 'src/devices/bluetooth/nm-bt-error.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bt-error.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Tpo src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/nm-bt-error.c' object='src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo: src/core/devices/bluetooth/nm-bt-error.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo -MD -MP -MF src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Tpo -c -o src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo `test -f 'src/core/devices/bluetooth/nm-bt-error.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bt-error.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Tpo src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/nm-bt-error.c' object='src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo `test -f 'src/devices/bluetooth/nm-bt-error.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bt-error.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bt-error.lo `test -f 'src/core/devices/bluetooth/nm-bt-error.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bt-error.c -src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo: src/devices/bluetooth/nm-bluez5-dun.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo -MD -MP -MF src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Tpo -c -o src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo `test -f 'src/devices/bluetooth/nm-bluez5-dun.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bluez5-dun.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Tpo src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/nm-bluez5-dun.c' object='src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo: src/core/devices/bluetooth/nm-bluez5-dun.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo -MD -MP -MF src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Tpo -c -o src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo `test -f 'src/core/devices/bluetooth/nm-bluez5-dun.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bluez5-dun.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Tpo src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/nm-bluez5-dun.c' object='src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo `test -f 'src/devices/bluetooth/nm-bluez5-dun.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bluez5-dun.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_bluetooth_utils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/libnm_bluetooth_utils_la-nm-bluez5-dun.lo `test -f 'src/core/devices/bluetooth/nm-bluez5-dun.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bluez5-dun.c -src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo: src/devices/bluetooth/nm-bluez-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo -MD -MP -MF src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Tpo -c -o src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo `test -f 'src/devices/bluetooth/nm-bluez-manager.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bluez-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Tpo src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/nm-bluez-manager.c' object='src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo: src/core/devices/bluetooth/nm-bluez-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo -MD -MP -MF src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Tpo -c -o src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo `test -f 'src/core/devices/bluetooth/nm-bluez-manager.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bluez-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Tpo src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/nm-bluez-manager.c' object='src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo `test -f 'src/devices/bluetooth/nm-bluez-manager.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-bluez-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-bluez-manager.lo `test -f 'src/core/devices/bluetooth/nm-bluez-manager.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-bluez-manager.c -src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo: src/devices/bluetooth/nm-device-bt.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo -MD -MP -MF src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Tpo -c -o src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo `test -f 'src/devices/bluetooth/nm-device-bt.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-device-bt.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Tpo src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/nm-device-bt.c' object='src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo: src/core/devices/bluetooth/nm-device-bt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo -MD -MP -MF src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Tpo -c -o src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo `test -f 'src/core/devices/bluetooth/nm-device-bt.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-device-bt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Tpo src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/nm-device-bt.c' object='src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo `test -f 'src/devices/bluetooth/nm-device-bt.c' || echo '$(srcdir)/'`src/devices/bluetooth/nm-device-bt.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/libnm_device_plugin_bluetooth_la-nm-device-bt.lo `test -f 'src/core/devices/bluetooth/nm-device-bt.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/nm-device-bt.c -src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo: src/devices/ovs/nm-ovsdb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo -MD -MP -MF src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Tpo -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo `test -f 'src/devices/ovs/nm-ovsdb.c' || echo '$(srcdir)/'`src/devices/ovs/nm-ovsdb.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Tpo src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/ovs/nm-ovsdb.c' object='src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo: src/core/devices/ovs/nm-ovsdb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo -MD -MP -MF src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Tpo -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo `test -f 'src/core/devices/ovs/nm-ovsdb.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-ovsdb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Tpo src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/ovs/nm-ovsdb.c' object='src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo `test -f 'src/devices/ovs/nm-ovsdb.c' || echo '$(srcdir)/'`src/devices/ovs/nm-ovsdb.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovsdb.lo `test -f 'src/core/devices/ovs/nm-ovsdb.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-ovsdb.c -src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo: src/devices/ovs/nm-ovs-factory.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo -MD -MP -MF src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Tpo -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo `test -f 'src/devices/ovs/nm-ovs-factory.c' || echo '$(srcdir)/'`src/devices/ovs/nm-ovs-factory.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Tpo src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/ovs/nm-ovs-factory.c' object='src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo: src/core/devices/ovs/nm-ovs-factory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo -MD -MP -MF src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Tpo -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo `test -f 'src/core/devices/ovs/nm-ovs-factory.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-ovs-factory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Tpo src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/ovs/nm-ovs-factory.c' object='src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo `test -f 'src/devices/ovs/nm-ovs-factory.c' || echo '$(srcdir)/'`src/devices/ovs/nm-ovs-factory.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-ovs-factory.lo `test -f 'src/core/devices/ovs/nm-ovs-factory.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-ovs-factory.c -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo: src/devices/ovs/nm-device-ovs-interface.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo -MD -MP -MF src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Tpo -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo `test -f 'src/devices/ovs/nm-device-ovs-interface.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-interface.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Tpo src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/ovs/nm-device-ovs-interface.c' object='src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo: src/core/devices/ovs/nm-device-ovs-interface.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo -MD -MP -MF src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Tpo -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo `test -f 'src/core/devices/ovs/nm-device-ovs-interface.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-interface.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Tpo src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/ovs/nm-device-ovs-interface.c' object='src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo `test -f 'src/devices/ovs/nm-device-ovs-interface.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-interface.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-interface.lo `test -f 'src/core/devices/ovs/nm-device-ovs-interface.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-interface.c -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo: src/devices/ovs/nm-device-ovs-port.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo -MD -MP -MF src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Tpo -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo `test -f 'src/devices/ovs/nm-device-ovs-port.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-port.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Tpo src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/ovs/nm-device-ovs-port.c' object='src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo: src/core/devices/ovs/nm-device-ovs-port.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo -MD -MP -MF src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Tpo -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo `test -f 'src/core/devices/ovs/nm-device-ovs-port.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-port.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Tpo src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/ovs/nm-device-ovs-port.c' object='src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo `test -f 'src/devices/ovs/nm-device-ovs-port.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-port.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-port.lo `test -f 'src/core/devices/ovs/nm-device-ovs-port.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-port.c -src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo: src/devices/ovs/nm-device-ovs-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo -MD -MP -MF src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Tpo -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo `test -f 'src/devices/ovs/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Tpo src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/ovs/nm-device-ovs-bridge.c' object='src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo: src/core/devices/ovs/nm-device-ovs-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo -MD -MP -MF src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Tpo -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo `test -f 'src/core/devices/ovs/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Tpo src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/ovs/nm-device-ovs-bridge.c' object='src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo `test -f 'src/devices/ovs/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`src/devices/ovs/nm-device-ovs-bridge.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_ovs_libnm_device_plugin_ovs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/ovs/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.lo `test -f 'src/core/devices/ovs/nm-device-ovs-bridge.c' || echo '$(srcdir)/'`src/core/devices/ovs/nm-device-ovs-bridge.c -src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo: src/devices/team/nm-team-factory.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo -MD -MP -MF src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Tpo -c -o src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo `test -f 'src/devices/team/nm-team-factory.c' || echo '$(srcdir)/'`src/devices/team/nm-team-factory.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Tpo src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/team/nm-team-factory.c' object='src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo: src/core/devices/team/nm-team-factory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo -MD -MP -MF src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Tpo -c -o src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo `test -f 'src/core/devices/team/nm-team-factory.c' || echo '$(srcdir)/'`src/core/devices/team/nm-team-factory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Tpo src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/team/nm-team-factory.c' object='src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo `test -f 'src/devices/team/nm-team-factory.c' || echo '$(srcdir)/'`src/devices/team/nm-team-factory.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/team/libnm_device_plugin_team_la-nm-team-factory.lo `test -f 'src/core/devices/team/nm-team-factory.c' || echo '$(srcdir)/'`src/core/devices/team/nm-team-factory.c -src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo: src/devices/team/nm-device-team.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo -MD -MP -MF src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Tpo -c -o src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo `test -f 'src/devices/team/nm-device-team.c' || echo '$(srcdir)/'`src/devices/team/nm-device-team.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Tpo src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/team/nm-device-team.c' object='src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo: src/core/devices/team/nm-device-team.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo -MD -MP -MF src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Tpo -c -o src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo `test -f 'src/core/devices/team/nm-device-team.c' || echo '$(srcdir)/'`src/core/devices/team/nm-device-team.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Tpo src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/team/nm-device-team.c' object='src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/team/libnm_device_plugin_team_la-nm-device-team.lo `test -f 'src/devices/team/nm-device-team.c' || echo '$(srcdir)/'`src/devices/team/nm-device-team.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_team_libnm_device_plugin_team_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/team/libnm_device_plugin_team_la-nm-device-team.lo `test -f 'src/core/devices/team/nm-device-team.c' || echo '$(srcdir)/'`src/core/devices/team/nm-device-team.c -src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo: src/devices/wifi/nm-wifi-factory.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Tpo -c -o src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo `test -f 'src/devices/wifi/nm-wifi-factory.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-factory.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Tpo src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-wifi-factory.c' object='src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo: src/core/devices/wifi/nm-wifi-factory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Tpo -c -o src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo `test -f 'src/core/devices/wifi/nm-wifi-factory.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-factory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-wifi-factory.c' object='src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo `test -f 'src/devices/wifi/nm-wifi-factory.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-factory.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_device_plugin_wifi_la-nm-wifi-factory.lo `test -f 'src/core/devices/wifi/nm-wifi-factory.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-factory.c -src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo: src/devices/wifi/nm-device-olpc-mesh.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo `test -f 'src/devices/wifi/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-olpc-mesh.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-device-olpc-mesh.c' object='src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo: src/core/devices/wifi/nm-device-olpc-mesh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo `test -f 'src/core/devices/wifi/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-olpc-mesh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-device-olpc-mesh.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo `test -f 'src/devices/wifi/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-olpc-mesh.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-olpc-mesh.lo `test -f 'src/core/devices/wifi/nm-device-olpc-mesh.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-olpc-mesh.c -src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo: src/devices/wifi/nm-device-wifi-p2p.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo `test -f 'src/devices/wifi/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-wifi-p2p.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-device-wifi-p2p.c' object='src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo: src/core/devices/wifi/nm-device-wifi-p2p.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo `test -f 'src/core/devices/wifi/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-wifi-p2p.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-device-wifi-p2p.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo `test -f 'src/devices/wifi/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-wifi-p2p.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi-p2p.lo `test -f 'src/core/devices/wifi/nm-device-wifi-p2p.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-wifi-p2p.c -src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo: src/devices/wifi/nm-device-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo `test -f 'src/devices/wifi/nm-device-wifi.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-device-wifi.c' object='src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo: src/core/devices/wifi/nm-device-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo `test -f 'src/core/devices/wifi/nm-device-wifi.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-device-wifi.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo `test -f 'src/devices/wifi/nm-device-wifi.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-wifi.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-wifi.lo `test -f 'src/core/devices/wifi/nm-device-wifi.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-wifi.c -src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo: src/devices/wifi/nm-wifi-ap.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo `test -f 'src/devices/wifi/nm-wifi-ap.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-ap.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-wifi-ap.c' object='src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo: src/core/devices/wifi/nm-wifi-ap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo `test -f 'src/core/devices/wifi/nm-wifi-ap.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-ap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-wifi-ap.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo `test -f 'src/devices/wifi/nm-wifi-ap.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-ap.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-ap.lo `test -f 'src/core/devices/wifi/nm-wifi-ap.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-ap.c -src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo: src/devices/wifi/nm-wifi-common.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo `test -f 'src/devices/wifi/nm-wifi-common.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-common.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-wifi-common.c' object='src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo: src/core/devices/wifi/nm-wifi-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo `test -f 'src/core/devices/wifi/nm-wifi-common.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-wifi-common.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo `test -f 'src/devices/wifi/nm-wifi-common.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-common.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-common.lo `test -f 'src/core/devices/wifi/nm-wifi-common.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-common.c -src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo: src/devices/wifi/nm-wifi-p2p-peer.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo `test -f 'src/devices/wifi/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-p2p-peer.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-wifi-p2p-peer.c' object='src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo: src/core/devices/wifi/nm-wifi-p2p-peer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo `test -f 'src/core/devices/wifi/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-p2p-peer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-wifi-p2p-peer.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo `test -f 'src/devices/wifi/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-p2p-peer.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-p2p-peer.lo `test -f 'src/core/devices/wifi/nm-wifi-p2p-peer.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-p2p-peer.c -src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo: src/devices/wifi/nm-wifi-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo `test -f 'src/devices/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-wifi-utils.c' object='src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo: src/core/devices/wifi/nm-wifi-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo `test -f 'src/core/devices/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-wifi-utils.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo `test -f 'src/devices/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/devices/wifi/nm-wifi-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-wifi-utils.lo `test -f 'src/core/devices/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-wifi-utils.c -src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo: src/devices/wifi/nm-device-iwd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo `test -f 'src/devices/wifi/nm-device-iwd.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-iwd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-device-iwd.c' object='src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo: src/core/devices/wifi/nm-device-iwd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo `test -f 'src/core/devices/wifi/nm-device-iwd.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-iwd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-device-iwd.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo `test -f 'src/devices/wifi/nm-device-iwd.c' || echo '$(srcdir)/'`src/devices/wifi/nm-device-iwd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-device-iwd.lo `test -f 'src/core/devices/wifi/nm-device-iwd.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-device-iwd.c -src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo: src/devices/wifi/nm-iwd-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo -MD -MP -MF src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Tpo -c -o src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo `test -f 'src/devices/wifi/nm-iwd-manager.c' || echo '$(srcdir)/'`src/devices/wifi/nm-iwd-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Tpo src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/nm-iwd-manager.c' object='src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo: src/core/devices/wifi/nm-iwd-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo -MD -MP -MF src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Tpo -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo `test -f 'src/core/devices/wifi/nm-iwd-manager.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-iwd-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Tpo src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/nm-iwd-manager.c' object='src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo `test -f 'src/devices/wifi/nm-iwd-manager.c' || echo '$(srcdir)/'`src/devices/wifi/nm-iwd-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_libnm_wifi_base_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/libnm_wifi_base_la-nm-iwd-manager.lo `test -f 'src/core/devices/wifi/nm-iwd-manager.c' || echo '$(srcdir)/'`src/core/devices/wifi/nm-iwd-manager.c -src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo: src/devices/wwan/nm-wwan-factory.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Tpo -c -o src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo `test -f 'src/devices/wwan/nm-wwan-factory.c' || echo '$(srcdir)/'`src/devices/wwan/nm-wwan-factory.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Tpo src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-wwan-factory.c' object='src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo: src/core/devices/wwan/nm-wwan-factory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Tpo -c -o src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo `test -f 'src/core/devices/wwan/nm-wwan-factory.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-wwan-factory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-wwan-factory.c' object='src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo `test -f 'src/devices/wwan/nm-wwan-factory.c' || echo '$(srcdir)/'`src/devices/wwan/nm-wwan-factory.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-wwan-factory.lo `test -f 'src/core/devices/wwan/nm-wwan-factory.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-wwan-factory.c -src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo: src/devices/wwan/nm-device-modem.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Tpo -c -o src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo `test -f 'src/devices/wwan/nm-device-modem.c' || echo '$(srcdir)/'`src/devices/wwan/nm-device-modem.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Tpo src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-device-modem.c' object='src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo: src/core/devices/wwan/nm-device-modem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Tpo -c -o src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo `test -f 'src/core/devices/wwan/nm-device-modem.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-device-modem.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-device-modem.c' object='src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo `test -f 'src/devices/wwan/nm-device-modem.c' || echo '$(srcdir)/'`src/devices/wwan/nm-device-modem.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_device_plugin_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_device_plugin_wwan_la-nm-device-modem.lo `test -f 'src/core/devices/wwan/nm-device-modem.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-device-modem.c -src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo: src/devices/wwan/nm-modem-broadband.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Tpo -c -o src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo `test -f 'src/devices/wwan/nm-modem-broadband.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-broadband.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Tpo src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-modem-broadband.c' object='src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo: src/core/devices/wwan/nm-modem-broadband.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Tpo -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo `test -f 'src/core/devices/wwan/nm-modem-broadband.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-broadband.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-modem-broadband.c' object='src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo `test -f 'src/devices/wwan/nm-modem-broadband.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-broadband.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-broadband.lo `test -f 'src/core/devices/wwan/nm-modem-broadband.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-broadband.c -src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo: src/devices/wwan/nm-modem-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Tpo -c -o src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo `test -f 'src/devices/wwan/nm-modem-manager.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Tpo src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-modem-manager.c' object='src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo: src/core/devices/wwan/nm-modem-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Tpo -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo `test -f 'src/core/devices/wwan/nm-modem-manager.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-modem-manager.c' object='src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_wwan_la-nm-modem-manager.lo `test -f 'src/devices/wwan/nm-modem-manager.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-manager.lo `test -f 'src/core/devices/wwan/nm-modem-manager.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-manager.c -src/devices/wwan/libnm_wwan_la-nm-modem.lo: src/devices/wwan/nm-modem.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_wwan_la-nm-modem.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Tpo -c -o src/devices/wwan/libnm_wwan_la-nm-modem.lo `test -f 'src/devices/wwan/nm-modem.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Tpo src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-modem.c' object='src/devices/wwan/libnm_wwan_la-nm-modem.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_wwan_la-nm-modem.lo: src/core/devices/wwan/nm-modem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_wwan_la-nm-modem.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Tpo -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem.lo `test -f 'src/core/devices/wwan/nm-modem.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-modem.c' object='src/core/devices/wwan/libnm_wwan_la-nm-modem.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_wwan_la-nm-modem.lo `test -f 'src/devices/wwan/nm-modem.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem.lo `test -f 'src/core/devices/wwan/nm-modem.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem.c -src/devices/wwan/libnm_wwan_la-nm-service-providers.lo: src/devices/wwan/nm-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_wwan_la-nm-service-providers.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Tpo -c -o src/devices/wwan/libnm_wwan_la-nm-service-providers.lo `test -f 'src/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/nm-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Tpo src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-service-providers.c' object='src/devices/wwan/libnm_wwan_la-nm-service-providers.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo: src/core/devices/wwan/nm-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Tpo -c -o src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo `test -f 'src/core/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-service-providers.c' object='src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_wwan_la-nm-service-providers.lo `test -f 'src/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/nm-service-providers.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_wwan_la-nm-service-providers.lo `test -f 'src/core/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-service-providers.c -src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo: src/devices/wwan/nm-modem-ofono.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo -MD -MP -MF src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Tpo -c -o src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo `test -f 'src/devices/wwan/nm-modem-ofono.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-ofono.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Tpo src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-modem-ofono.c' object='src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo: src/core/devices/wwan/nm-modem-ofono.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Tpo -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo `test -f 'src/core/devices/wwan/nm-modem-ofono.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-ofono.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Tpo src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-modem-ofono.c' object='src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo `test -f 'src/devices/wwan/nm-modem-ofono.c' || echo '$(srcdir)/'`src/devices/wwan/nm-modem-ofono.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_libnm_wwan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/libnm_wwan_la-nm-modem-ofono.lo `test -f 'src/core/devices/wwan/nm-modem-ofono.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-modem-ofono.c -src/initrd/libnmi_core_la-nmi-cmdline-reader.lo: src/initrd/nmi-cmdline-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/libnmi_core_la-nmi-cmdline-reader.lo -MD -MP -MF src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Tpo -c -o src/initrd/libnmi_core_la-nmi-cmdline-reader.lo `test -f 'src/initrd/nmi-cmdline-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-cmdline-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Tpo src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/nmi-cmdline-reader.c' object='src/initrd/libnmi_core_la-nmi-cmdline-reader.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo: src/core/initrd/nmi-cmdline-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo -MD -MP -MF src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Tpo -c -o src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo `test -f 'src/core/initrd/nmi-cmdline-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-cmdline-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Tpo src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/nmi-cmdline-reader.c' object='src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/libnmi_core_la-nmi-cmdline-reader.lo `test -f 'src/initrd/nmi-cmdline-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-cmdline-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/libnmi_core_la-nmi-cmdline-reader.lo `test -f 'src/core/initrd/nmi-cmdline-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-cmdline-reader.c -src/initrd/libnmi_core_la-nmi-dt-reader.lo: src/initrd/nmi-dt-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/libnmi_core_la-nmi-dt-reader.lo -MD -MP -MF src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Tpo -c -o src/initrd/libnmi_core_la-nmi-dt-reader.lo `test -f 'src/initrd/nmi-dt-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-dt-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Tpo src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/nmi-dt-reader.c' object='src/initrd/libnmi_core_la-nmi-dt-reader.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/initrd/libnmi_core_la-nmi-dt-reader.lo: src/core/initrd/nmi-dt-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/libnmi_core_la-nmi-dt-reader.lo -MD -MP -MF src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Tpo -c -o src/core/initrd/libnmi_core_la-nmi-dt-reader.lo `test -f 'src/core/initrd/nmi-dt-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-dt-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Tpo src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/nmi-dt-reader.c' object='src/core/initrd/libnmi_core_la-nmi-dt-reader.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/libnmi_core_la-nmi-dt-reader.lo `test -f 'src/initrd/nmi-dt-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-dt-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/libnmi_core_la-nmi-dt-reader.lo `test -f 'src/core/initrd/nmi-dt-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-dt-reader.c -src/initrd/libnmi_core_la-nmi-ibft-reader.lo: src/initrd/nmi-ibft-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/libnmi_core_la-nmi-ibft-reader.lo -MD -MP -MF src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Tpo -c -o src/initrd/libnmi_core_la-nmi-ibft-reader.lo `test -f 'src/initrd/nmi-ibft-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-ibft-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Tpo src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/nmi-ibft-reader.c' object='src/initrd/libnmi_core_la-nmi-ibft-reader.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo: src/core/initrd/nmi-ibft-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo -MD -MP -MF src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Tpo -c -o src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo `test -f 'src/core/initrd/nmi-ibft-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-ibft-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Tpo src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/nmi-ibft-reader.c' object='src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/libnmi_core_la-nmi-ibft-reader.lo `test -f 'src/initrd/nmi-ibft-reader.c' || echo '$(srcdir)/'`src/initrd/nmi-ibft-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_libnmi_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/libnmi_core_la-nmi-ibft-reader.lo `test -f 'src/core/initrd/nmi-ibft-reader.c' || echo '$(srcdir)/'`src/core/initrd/nmi-ibft-reader.c -src/libNetworkManager_la-nm-checkpoint.lo: src/nm-checkpoint.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-checkpoint.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Tpo -c -o src/libNetworkManager_la-nm-checkpoint.lo `test -f 'src/nm-checkpoint.c' || echo '$(srcdir)/'`src/nm-checkpoint.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-checkpoint.c' object='src/libNetworkManager_la-nm-checkpoint.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-checkpoint.lo: src/core/nm-checkpoint.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-checkpoint.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Tpo -c -o src/core/libNetworkManager_la-nm-checkpoint.lo `test -f 'src/core/nm-checkpoint.c' || echo '$(srcdir)/'`src/core/nm-checkpoint.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-checkpoint.c' object='src/core/libNetworkManager_la-nm-checkpoint.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-checkpoint.lo `test -f 'src/nm-checkpoint.c' || echo '$(srcdir)/'`src/nm-checkpoint.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-checkpoint.lo `test -f 'src/core/nm-checkpoint.c' || echo '$(srcdir)/'`src/core/nm-checkpoint.c -src/libNetworkManager_la-nm-checkpoint-manager.lo: src/nm-checkpoint-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-checkpoint-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Tpo -c -o src/libNetworkManager_la-nm-checkpoint-manager.lo `test -f 'src/nm-checkpoint-manager.c' || echo '$(srcdir)/'`src/nm-checkpoint-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-checkpoint-manager.c' object='src/libNetworkManager_la-nm-checkpoint-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-checkpoint-manager.lo: src/core/nm-checkpoint-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-checkpoint-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Tpo -c -o src/core/libNetworkManager_la-nm-checkpoint-manager.lo `test -f 'src/core/nm-checkpoint-manager.c' || echo '$(srcdir)/'`src/core/nm-checkpoint-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-checkpoint-manager.c' object='src/core/libNetworkManager_la-nm-checkpoint-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-checkpoint-manager.lo `test -f 'src/nm-checkpoint-manager.c' || echo '$(srcdir)/'`src/nm-checkpoint-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-checkpoint-manager.lo `test -f 'src/core/nm-checkpoint-manager.c' || echo '$(srcdir)/'`src/core/nm-checkpoint-manager.c -src/devices/libNetworkManager_la-nm-acd-manager.lo: src/devices/nm-acd-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-acd-manager.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Tpo -c -o src/devices/libNetworkManager_la-nm-acd-manager.lo `test -f 'src/devices/nm-acd-manager.c' || echo '$(srcdir)/'`src/devices/nm-acd-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-acd-manager.c' object='src/devices/libNetworkManager_la-nm-acd-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-acd-manager.lo: src/core/devices/nm-acd-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-acd-manager.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Tpo -c -o src/core/devices/libNetworkManager_la-nm-acd-manager.lo `test -f 'src/core/devices/nm-acd-manager.c' || echo '$(srcdir)/'`src/core/devices/nm-acd-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-acd-manager.c' object='src/core/devices/libNetworkManager_la-nm-acd-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-acd-manager.lo `test -f 'src/devices/nm-acd-manager.c' || echo '$(srcdir)/'`src/devices/nm-acd-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-acd-manager.lo `test -f 'src/core/devices/nm-acd-manager.c' || echo '$(srcdir)/'`src/core/devices/nm-acd-manager.c -src/devices/libNetworkManager_la-nm-lldp-listener.lo: src/devices/nm-lldp-listener.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-lldp-listener.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Tpo -c -o src/devices/libNetworkManager_la-nm-lldp-listener.lo `test -f 'src/devices/nm-lldp-listener.c' || echo '$(srcdir)/'`src/devices/nm-lldp-listener.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-lldp-listener.c' object='src/devices/libNetworkManager_la-nm-lldp-listener.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-lldp-listener.lo: src/core/devices/nm-lldp-listener.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-lldp-listener.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Tpo -c -o src/core/devices/libNetworkManager_la-nm-lldp-listener.lo `test -f 'src/core/devices/nm-lldp-listener.c' || echo '$(srcdir)/'`src/core/devices/nm-lldp-listener.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-lldp-listener.c' object='src/core/devices/libNetworkManager_la-nm-lldp-listener.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-lldp-listener.lo `test -f 'src/devices/nm-lldp-listener.c' || echo '$(srcdir)/'`src/devices/nm-lldp-listener.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-lldp-listener.lo `test -f 'src/core/devices/nm-lldp-listener.c' || echo '$(srcdir)/'`src/core/devices/nm-lldp-listener.c -src/devices/libNetworkManager_la-nm-device.lo: src/devices/nm-device.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Tpo -c -o src/devices/libNetworkManager_la-nm-device.lo `test -f 'src/devices/nm-device.c' || echo '$(srcdir)/'`src/devices/nm-device.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device.c' object='src/devices/libNetworkManager_la-nm-device.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device.lo: src/core/devices/nm-device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device.lo `test -f 'src/core/devices/nm-device.c' || echo '$(srcdir)/'`src/core/devices/nm-device.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device.c' object='src/core/devices/libNetworkManager_la-nm-device.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device.lo `test -f 'src/devices/nm-device.c' || echo '$(srcdir)/'`src/devices/nm-device.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device.lo `test -f 'src/core/devices/nm-device.c' || echo '$(srcdir)/'`src/core/devices/nm-device.c -src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo: src/devices/nm-device-ethernet-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Tpo -c -o src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo `test -f 'src/devices/nm-device-ethernet-utils.c' || echo '$(srcdir)/'`src/devices/nm-device-ethernet-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-ethernet-utils.c' object='src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo: src/core/devices/nm-device-ethernet-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo `test -f 'src/core/devices/nm-device-ethernet-utils.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ethernet-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-ethernet-utils.c' object='src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-ethernet-utils.lo `test -f 'src/devices/nm-device-ethernet-utils.c' || echo '$(srcdir)/'`src/devices/nm-device-ethernet-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-ethernet-utils.lo `test -f 'src/core/devices/nm-device-ethernet-utils.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ethernet-utils.c -src/devices/libNetworkManager_la-nm-device-factory.lo: src/devices/nm-device-factory.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-factory.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Tpo -c -o src/devices/libNetworkManager_la-nm-device-factory.lo `test -f 'src/devices/nm-device-factory.c' || echo '$(srcdir)/'`src/devices/nm-device-factory.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-factory.c' object='src/devices/libNetworkManager_la-nm-device-factory.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-factory.lo: src/core/devices/nm-device-factory.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-factory.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-factory.lo `test -f 'src/core/devices/nm-device-factory.c' || echo '$(srcdir)/'`src/core/devices/nm-device-factory.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-factory.c' object='src/core/devices/libNetworkManager_la-nm-device-factory.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-factory.lo `test -f 'src/devices/nm-device-factory.c' || echo '$(srcdir)/'`src/devices/nm-device-factory.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-factory.lo `test -f 'src/core/devices/nm-device-factory.c' || echo '$(srcdir)/'`src/core/devices/nm-device-factory.c -src/devices/libNetworkManager_la-nm-device-generic.lo: src/devices/nm-device-generic.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-generic.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Tpo -c -o src/devices/libNetworkManager_la-nm-device-generic.lo `test -f 'src/devices/nm-device-generic.c' || echo '$(srcdir)/'`src/devices/nm-device-generic.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-generic.c' object='src/devices/libNetworkManager_la-nm-device-generic.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-generic.lo: src/core/devices/nm-device-generic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-generic.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-generic.lo `test -f 'src/core/devices/nm-device-generic.c' || echo '$(srcdir)/'`src/core/devices/nm-device-generic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-generic.c' object='src/core/devices/libNetworkManager_la-nm-device-generic.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-generic.lo `test -f 'src/devices/nm-device-generic.c' || echo '$(srcdir)/'`src/devices/nm-device-generic.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-generic.lo `test -f 'src/core/devices/nm-device-generic.c' || echo '$(srcdir)/'`src/core/devices/nm-device-generic.c -src/devices/libNetworkManager_la-nm-device-6lowpan.lo: src/devices/nm-device-6lowpan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-6lowpan.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Tpo -c -o src/devices/libNetworkManager_la-nm-device-6lowpan.lo `test -f 'src/devices/nm-device-6lowpan.c' || echo '$(srcdir)/'`src/devices/nm-device-6lowpan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-6lowpan.c' object='src/devices/libNetworkManager_la-nm-device-6lowpan.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo: src/core/devices/nm-device-6lowpan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo `test -f 'src/core/devices/nm-device-6lowpan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-6lowpan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-6lowpan.c' object='src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-6lowpan.lo `test -f 'src/devices/nm-device-6lowpan.c' || echo '$(srcdir)/'`src/devices/nm-device-6lowpan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-6lowpan.lo `test -f 'src/core/devices/nm-device-6lowpan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-6lowpan.c -src/devices/libNetworkManager_la-nm-device-bond.lo: src/devices/nm-device-bond.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-bond.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Tpo -c -o src/devices/libNetworkManager_la-nm-device-bond.lo `test -f 'src/devices/nm-device-bond.c' || echo '$(srcdir)/'`src/devices/nm-device-bond.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-bond.c' object='src/devices/libNetworkManager_la-nm-device-bond.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-bond.lo: src/core/devices/nm-device-bond.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-bond.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-bond.lo `test -f 'src/core/devices/nm-device-bond.c' || echo '$(srcdir)/'`src/core/devices/nm-device-bond.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-bond.c' object='src/core/devices/libNetworkManager_la-nm-device-bond.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-bond.lo `test -f 'src/devices/nm-device-bond.c' || echo '$(srcdir)/'`src/devices/nm-device-bond.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-bond.lo `test -f 'src/core/devices/nm-device-bond.c' || echo '$(srcdir)/'`src/core/devices/nm-device-bond.c -src/devices/libNetworkManager_la-nm-device-bridge.lo: src/devices/nm-device-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-bridge.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Tpo -c -o src/devices/libNetworkManager_la-nm-device-bridge.lo `test -f 'src/devices/nm-device-bridge.c' || echo '$(srcdir)/'`src/devices/nm-device-bridge.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-bridge.c' object='src/devices/libNetworkManager_la-nm-device-bridge.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-bridge.lo: src/core/devices/nm-device-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-bridge.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-bridge.lo `test -f 'src/core/devices/nm-device-bridge.c' || echo '$(srcdir)/'`src/core/devices/nm-device-bridge.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-bridge.c' object='src/core/devices/libNetworkManager_la-nm-device-bridge.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-bridge.lo `test -f 'src/devices/nm-device-bridge.c' || echo '$(srcdir)/'`src/devices/nm-device-bridge.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-bridge.lo `test -f 'src/core/devices/nm-device-bridge.c' || echo '$(srcdir)/'`src/core/devices/nm-device-bridge.c -src/devices/libNetworkManager_la-nm-device-dummy.lo: src/devices/nm-device-dummy.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-dummy.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Tpo -c -o src/devices/libNetworkManager_la-nm-device-dummy.lo `test -f 'src/devices/nm-device-dummy.c' || echo '$(srcdir)/'`src/devices/nm-device-dummy.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-dummy.c' object='src/devices/libNetworkManager_la-nm-device-dummy.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-dummy.lo: src/core/devices/nm-device-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-dummy.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-dummy.lo `test -f 'src/core/devices/nm-device-dummy.c' || echo '$(srcdir)/'`src/core/devices/nm-device-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-dummy.c' object='src/core/devices/libNetworkManager_la-nm-device-dummy.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-dummy.lo `test -f 'src/devices/nm-device-dummy.c' || echo '$(srcdir)/'`src/devices/nm-device-dummy.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-dummy.lo `test -f 'src/core/devices/nm-device-dummy.c' || echo '$(srcdir)/'`src/core/devices/nm-device-dummy.c -src/devices/libNetworkManager_la-nm-device-ethernet.lo: src/devices/nm-device-ethernet.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-ethernet.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Tpo -c -o src/devices/libNetworkManager_la-nm-device-ethernet.lo `test -f 'src/devices/nm-device-ethernet.c' || echo '$(srcdir)/'`src/devices/nm-device-ethernet.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-ethernet.c' object='src/devices/libNetworkManager_la-nm-device-ethernet.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-ethernet.lo: src/core/devices/nm-device-ethernet.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-ethernet.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-ethernet.lo `test -f 'src/core/devices/nm-device-ethernet.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ethernet.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-ethernet.c' object='src/core/devices/libNetworkManager_la-nm-device-ethernet.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-ethernet.lo `test -f 'src/devices/nm-device-ethernet.c' || echo '$(srcdir)/'`src/devices/nm-device-ethernet.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-ethernet.lo `test -f 'src/core/devices/nm-device-ethernet.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ethernet.c -src/devices/libNetworkManager_la-nm-device-infiniband.lo: src/devices/nm-device-infiniband.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-infiniband.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Tpo -c -o src/devices/libNetworkManager_la-nm-device-infiniband.lo `test -f 'src/devices/nm-device-infiniband.c' || echo '$(srcdir)/'`src/devices/nm-device-infiniband.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-infiniband.c' object='src/devices/libNetworkManager_la-nm-device-infiniband.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-infiniband.lo: src/core/devices/nm-device-infiniband.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-infiniband.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-infiniband.lo `test -f 'src/core/devices/nm-device-infiniband.c' || echo '$(srcdir)/'`src/core/devices/nm-device-infiniband.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-infiniband.c' object='src/core/devices/libNetworkManager_la-nm-device-infiniband.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-infiniband.lo `test -f 'src/devices/nm-device-infiniband.c' || echo '$(srcdir)/'`src/devices/nm-device-infiniband.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-infiniband.lo `test -f 'src/core/devices/nm-device-infiniband.c' || echo '$(srcdir)/'`src/core/devices/nm-device-infiniband.c -src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo: src/devices/nm-device-ip-tunnel.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Tpo -c -o src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo `test -f 'src/devices/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`src/devices/nm-device-ip-tunnel.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-ip-tunnel.c' object='src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo: src/core/devices/nm-device-ip-tunnel.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo `test -f 'src/core/devices/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ip-tunnel.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-ip-tunnel.c' object='src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-ip-tunnel.lo `test -f 'src/devices/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`src/devices/nm-device-ip-tunnel.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-ip-tunnel.lo `test -f 'src/core/devices/nm-device-ip-tunnel.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ip-tunnel.c -src/devices/libNetworkManager_la-nm-device-macsec.lo: src/devices/nm-device-macsec.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-macsec.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Tpo -c -o src/devices/libNetworkManager_la-nm-device-macsec.lo `test -f 'src/devices/nm-device-macsec.c' || echo '$(srcdir)/'`src/devices/nm-device-macsec.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-macsec.c' object='src/devices/libNetworkManager_la-nm-device-macsec.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-macsec.lo: src/core/devices/nm-device-macsec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-macsec.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-macsec.lo `test -f 'src/core/devices/nm-device-macsec.c' || echo '$(srcdir)/'`src/core/devices/nm-device-macsec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-macsec.c' object='src/core/devices/libNetworkManager_la-nm-device-macsec.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-macsec.lo `test -f 'src/devices/nm-device-macsec.c' || echo '$(srcdir)/'`src/devices/nm-device-macsec.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-macsec.lo `test -f 'src/core/devices/nm-device-macsec.c' || echo '$(srcdir)/'`src/core/devices/nm-device-macsec.c -src/devices/libNetworkManager_la-nm-device-macvlan.lo: src/devices/nm-device-macvlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-macvlan.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Tpo -c -o src/devices/libNetworkManager_la-nm-device-macvlan.lo `test -f 'src/devices/nm-device-macvlan.c' || echo '$(srcdir)/'`src/devices/nm-device-macvlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-macvlan.c' object='src/devices/libNetworkManager_la-nm-device-macvlan.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-macvlan.lo: src/core/devices/nm-device-macvlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-macvlan.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-macvlan.lo `test -f 'src/core/devices/nm-device-macvlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-macvlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-macvlan.c' object='src/core/devices/libNetworkManager_la-nm-device-macvlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-macvlan.lo `test -f 'src/devices/nm-device-macvlan.c' || echo '$(srcdir)/'`src/devices/nm-device-macvlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-macvlan.lo `test -f 'src/core/devices/nm-device-macvlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-macvlan.c -src/devices/libNetworkManager_la-nm-device-ppp.lo: src/devices/nm-device-ppp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-ppp.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Tpo -c -o src/devices/libNetworkManager_la-nm-device-ppp.lo `test -f 'src/devices/nm-device-ppp.c' || echo '$(srcdir)/'`src/devices/nm-device-ppp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-ppp.c' object='src/devices/libNetworkManager_la-nm-device-ppp.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-ppp.lo: src/core/devices/nm-device-ppp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-ppp.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-ppp.lo `test -f 'src/core/devices/nm-device-ppp.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ppp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-ppp.c' object='src/core/devices/libNetworkManager_la-nm-device-ppp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-ppp.lo `test -f 'src/devices/nm-device-ppp.c' || echo '$(srcdir)/'`src/devices/nm-device-ppp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-ppp.lo `test -f 'src/core/devices/nm-device-ppp.c' || echo '$(srcdir)/'`src/core/devices/nm-device-ppp.c -src/devices/libNetworkManager_la-nm-device-tun.lo: src/devices/nm-device-tun.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-tun.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Tpo -c -o src/devices/libNetworkManager_la-nm-device-tun.lo `test -f 'src/devices/nm-device-tun.c' || echo '$(srcdir)/'`src/devices/nm-device-tun.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-tun.c' object='src/devices/libNetworkManager_la-nm-device-tun.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-tun.lo: src/core/devices/nm-device-tun.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-tun.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-tun.lo `test -f 'src/core/devices/nm-device-tun.c' || echo '$(srcdir)/'`src/core/devices/nm-device-tun.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-tun.c' object='src/core/devices/libNetworkManager_la-nm-device-tun.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-tun.lo `test -f 'src/devices/nm-device-tun.c' || echo '$(srcdir)/'`src/devices/nm-device-tun.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-tun.lo `test -f 'src/core/devices/nm-device-tun.c' || echo '$(srcdir)/'`src/core/devices/nm-device-tun.c -src/devices/libNetworkManager_la-nm-device-veth.lo: src/devices/nm-device-veth.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-veth.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Tpo -c -o src/devices/libNetworkManager_la-nm-device-veth.lo `test -f 'src/devices/nm-device-veth.c' || echo '$(srcdir)/'`src/devices/nm-device-veth.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-veth.c' object='src/devices/libNetworkManager_la-nm-device-veth.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-veth.lo: src/core/devices/nm-device-veth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-veth.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-veth.lo `test -f 'src/core/devices/nm-device-veth.c' || echo '$(srcdir)/'`src/core/devices/nm-device-veth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-veth.c' object='src/core/devices/libNetworkManager_la-nm-device-veth.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-veth.lo `test -f 'src/devices/nm-device-veth.c' || echo '$(srcdir)/'`src/devices/nm-device-veth.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-veth.lo `test -f 'src/core/devices/nm-device-veth.c' || echo '$(srcdir)/'`src/core/devices/nm-device-veth.c -src/devices/libNetworkManager_la-nm-device-vlan.lo: src/devices/nm-device-vlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-vlan.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Tpo -c -o src/devices/libNetworkManager_la-nm-device-vlan.lo `test -f 'src/devices/nm-device-vlan.c' || echo '$(srcdir)/'`src/devices/nm-device-vlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-vlan.c' object='src/devices/libNetworkManager_la-nm-device-vlan.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-vlan.lo: src/core/devices/nm-device-vlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-vlan.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-vlan.lo `test -f 'src/core/devices/nm-device-vlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-vlan.c' object='src/core/devices/libNetworkManager_la-nm-device-vlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-vlan.lo `test -f 'src/devices/nm-device-vlan.c' || echo '$(srcdir)/'`src/devices/nm-device-vlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-vlan.lo `test -f 'src/core/devices/nm-device-vlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vlan.c -src/devices/libNetworkManager_la-nm-device-vrf.lo: src/devices/nm-device-vrf.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-vrf.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Tpo -c -o src/devices/libNetworkManager_la-nm-device-vrf.lo `test -f 'src/devices/nm-device-vrf.c' || echo '$(srcdir)/'`src/devices/nm-device-vrf.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-vrf.c' object='src/devices/libNetworkManager_la-nm-device-vrf.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-vrf.lo: src/core/devices/nm-device-vrf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-vrf.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-vrf.lo `test -f 'src/core/devices/nm-device-vrf.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vrf.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-vrf.c' object='src/core/devices/libNetworkManager_la-nm-device-vrf.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-vrf.lo `test -f 'src/devices/nm-device-vrf.c' || echo '$(srcdir)/'`src/devices/nm-device-vrf.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-vrf.lo `test -f 'src/core/devices/nm-device-vrf.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vrf.c -src/devices/libNetworkManager_la-nm-device-vxlan.lo: src/devices/nm-device-vxlan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-vxlan.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Tpo -c -o src/devices/libNetworkManager_la-nm-device-vxlan.lo `test -f 'src/devices/nm-device-vxlan.c' || echo '$(srcdir)/'`src/devices/nm-device-vxlan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-vxlan.c' object='src/devices/libNetworkManager_la-nm-device-vxlan.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-vxlan.lo: src/core/devices/nm-device-vxlan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-vxlan.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-vxlan.lo `test -f 'src/core/devices/nm-device-vxlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vxlan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-vxlan.c' object='src/core/devices/libNetworkManager_la-nm-device-vxlan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-vxlan.lo `test -f 'src/devices/nm-device-vxlan.c' || echo '$(srcdir)/'`src/devices/nm-device-vxlan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-vxlan.lo `test -f 'src/core/devices/nm-device-vxlan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-vxlan.c -src/devices/libNetworkManager_la-nm-device-wireguard.lo: src/devices/nm-device-wireguard.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-wireguard.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Tpo -c -o src/devices/libNetworkManager_la-nm-device-wireguard.lo `test -f 'src/devices/nm-device-wireguard.c' || echo '$(srcdir)/'`src/devices/nm-device-wireguard.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-wireguard.c' object='src/devices/libNetworkManager_la-nm-device-wireguard.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-wireguard.lo: src/core/devices/nm-device-wireguard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-wireguard.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-wireguard.lo `test -f 'src/core/devices/nm-device-wireguard.c' || echo '$(srcdir)/'`src/core/devices/nm-device-wireguard.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-wireguard.c' object='src/core/devices/libNetworkManager_la-nm-device-wireguard.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-wireguard.lo `test -f 'src/devices/nm-device-wireguard.c' || echo '$(srcdir)/'`src/devices/nm-device-wireguard.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-wireguard.lo `test -f 'src/core/devices/nm-device-wireguard.c' || echo '$(srcdir)/'`src/core/devices/nm-device-wireguard.c -src/devices/libNetworkManager_la-nm-device-wpan.lo: src/devices/nm-device-wpan.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/libNetworkManager_la-nm-device-wpan.lo -MD -MP -MF src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Tpo -c -o src/devices/libNetworkManager_la-nm-device-wpan.lo `test -f 'src/devices/nm-device-wpan.c' || echo '$(srcdir)/'`src/devices/nm-device-wpan.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Tpo src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/nm-device-wpan.c' object='src/devices/libNetworkManager_la-nm-device-wpan.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/devices/libNetworkManager_la-nm-device-wpan.lo: src/core/devices/nm-device-wpan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/libNetworkManager_la-nm-device-wpan.lo -MD -MP -MF src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Tpo -c -o src/core/devices/libNetworkManager_la-nm-device-wpan.lo `test -f 'src/core/devices/nm-device-wpan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-wpan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Tpo src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/nm-device-wpan.c' object='src/core/devices/libNetworkManager_la-nm-device-wpan.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/libNetworkManager_la-nm-device-wpan.lo `test -f 'src/devices/nm-device-wpan.c' || echo '$(srcdir)/'`src/devices/nm-device-wpan.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/libNetworkManager_la-nm-device-wpan.lo `test -f 'src/core/devices/nm-device-wpan.c' || echo '$(srcdir)/'`src/core/devices/nm-device-wpan.c -src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo: src/dhcp/nm-dhcp-dhcpcanon.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Tpo -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo `test -f 'src/dhcp/nm-dhcp-dhcpcanon.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhcpcanon.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Tpo src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-dhcpcanon.c' object='src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo: src/core/dhcp/nm-dhcp-dhcpcanon.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Tpo -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo `test -f 'src/core/dhcp/nm-dhcp-dhcpcanon.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhcpcanon.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-dhcpcanon.c' object='src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo `test -f 'src/dhcp/nm-dhcp-dhcpcanon.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhcpcanon.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcanon.lo `test -f 'src/core/dhcp/nm-dhcp-dhcpcanon.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhcpcanon.c -src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo: src/dhcp/nm-dhcp-dhclient.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Tpo -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo `test -f 'src/dhcp/nm-dhcp-dhclient.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhclient.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Tpo src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-dhclient.c' object='src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo: src/core/dhcp/nm-dhcp-dhclient.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Tpo -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo `test -f 'src/core/dhcp/nm-dhcp-dhclient.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhclient.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-dhclient.c' object='src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo `test -f 'src/dhcp/nm-dhcp-dhclient.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhclient.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient.lo `test -f 'src/core/dhcp/nm-dhcp-dhclient.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhclient.c -src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo: src/dhcp/nm-dhcp-dhcpcd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Tpo -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo `test -f 'src/dhcp/nm-dhcp-dhcpcd.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhcpcd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Tpo src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-dhcpcd.c' object='src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo: src/core/dhcp/nm-dhcp-dhcpcd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Tpo -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo `test -f 'src/core/dhcp/nm-dhcp-dhcpcd.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhcpcd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-dhcpcd.c' object='src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo `test -f 'src/dhcp/nm-dhcp-dhcpcd.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhcpcd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhcpcd.lo `test -f 'src/core/dhcp/nm-dhcp-dhcpcd.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhcpcd.c -src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo: src/dhcp/nm-dhcp-listener.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Tpo -c -o src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo `test -f 'src/dhcp/nm-dhcp-listener.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-listener.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Tpo src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-listener.c' object='src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo: src/core/dhcp/nm-dhcp-listener.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Tpo -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo `test -f 'src/core/dhcp/nm-dhcp-listener.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-listener.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-listener.c' object='src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManager_la-nm-dhcp-listener.lo `test -f 'src/dhcp/nm-dhcp-listener.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-listener.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-listener.lo `test -f 'src/core/dhcp/nm-dhcp-listener.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-listener.c -src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo: src/dhcp/nm-dhcp-dhclient-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Tpo -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo `test -f 'src/dhcp/nm-dhcp-dhclient-utils.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhclient-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Tpo src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-dhclient-utils.c' object='src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo: src/core/dhcp/nm-dhcp-dhclient-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Tpo -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo `test -f 'src/core/dhcp/nm-dhcp-dhclient-utils.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhclient-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-dhclient-utils.c' object='src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo `test -f 'src/dhcp/nm-dhcp-dhclient-utils.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-dhclient-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManager_la-nm-dhcp-dhclient-utils.lo `test -f 'src/core/dhcp/nm-dhcp-dhclient-utils.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-dhclient-utils.c -src/dns/libNetworkManager_la-nm-dns-manager.lo: src/dns/nm-dns-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dns/libNetworkManager_la-nm-dns-manager.lo -MD -MP -MF src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Tpo -c -o src/dns/libNetworkManager_la-nm-dns-manager.lo `test -f 'src/dns/nm-dns-manager.c' || echo '$(srcdir)/'`src/dns/nm-dns-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Tpo src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dns/nm-dns-manager.c' object='src/dns/libNetworkManager_la-nm-dns-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dns/libNetworkManager_la-nm-dns-manager.lo: src/core/dns/nm-dns-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dns/libNetworkManager_la-nm-dns-manager.lo -MD -MP -MF src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Tpo -c -o src/core/dns/libNetworkManager_la-nm-dns-manager.lo `test -f 'src/core/dns/nm-dns-manager.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Tpo src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dns/nm-dns-manager.c' object='src/core/dns/libNetworkManager_la-nm-dns-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dns/libNetworkManager_la-nm-dns-manager.lo `test -f 'src/dns/nm-dns-manager.c' || echo '$(srcdir)/'`src/dns/nm-dns-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dns/libNetworkManager_la-nm-dns-manager.lo `test -f 'src/core/dns/nm-dns-manager.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-manager.c -src/dns/libNetworkManager_la-nm-dns-plugin.lo: src/dns/nm-dns-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dns/libNetworkManager_la-nm-dns-plugin.lo -MD -MP -MF src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Tpo -c -o src/dns/libNetworkManager_la-nm-dns-plugin.lo `test -f 'src/dns/nm-dns-plugin.c' || echo '$(srcdir)/'`src/dns/nm-dns-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Tpo src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dns/nm-dns-plugin.c' object='src/dns/libNetworkManager_la-nm-dns-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dns/libNetworkManager_la-nm-dns-plugin.lo: src/core/dns/nm-dns-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dns/libNetworkManager_la-nm-dns-plugin.lo -MD -MP -MF src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Tpo -c -o src/core/dns/libNetworkManager_la-nm-dns-plugin.lo `test -f 'src/core/dns/nm-dns-plugin.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Tpo src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dns/nm-dns-plugin.c' object='src/core/dns/libNetworkManager_la-nm-dns-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dns/libNetworkManager_la-nm-dns-plugin.lo `test -f 'src/dns/nm-dns-plugin.c' || echo '$(srcdir)/'`src/dns/nm-dns-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dns/libNetworkManager_la-nm-dns-plugin.lo `test -f 'src/core/dns/nm-dns-plugin.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-plugin.c -src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo: src/dns/nm-dns-dnsmasq.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo -MD -MP -MF src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Tpo -c -o src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo `test -f 'src/dns/nm-dns-dnsmasq.c' || echo '$(srcdir)/'`src/dns/nm-dns-dnsmasq.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Tpo src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dns/nm-dns-dnsmasq.c' object='src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo: src/core/dns/nm-dns-dnsmasq.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo -MD -MP -MF src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Tpo -c -o src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo `test -f 'src/core/dns/nm-dns-dnsmasq.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-dnsmasq.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Tpo src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dns/nm-dns-dnsmasq.c' object='src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dns/libNetworkManager_la-nm-dns-dnsmasq.lo `test -f 'src/dns/nm-dns-dnsmasq.c' || echo '$(srcdir)/'`src/dns/nm-dns-dnsmasq.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dns/libNetworkManager_la-nm-dns-dnsmasq.lo `test -f 'src/core/dns/nm-dns-dnsmasq.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-dnsmasq.c -src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo: src/dns/nm-dns-systemd-resolved.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo -MD -MP -MF src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Tpo -c -o src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo `test -f 'src/dns/nm-dns-systemd-resolved.c' || echo '$(srcdir)/'`src/dns/nm-dns-systemd-resolved.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Tpo src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dns/nm-dns-systemd-resolved.c' object='src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo: src/core/dns/nm-dns-systemd-resolved.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo -MD -MP -MF src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Tpo -c -o src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo `test -f 'src/core/dns/nm-dns-systemd-resolved.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-systemd-resolved.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Tpo src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dns/nm-dns-systemd-resolved.c' object='src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo `test -f 'src/dns/nm-dns-systemd-resolved.c' || echo '$(srcdir)/'`src/dns/nm-dns-systemd-resolved.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dns/libNetworkManager_la-nm-dns-systemd-resolved.lo `test -f 'src/core/dns/nm-dns-systemd-resolved.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-systemd-resolved.c -src/dns/libNetworkManager_la-nm-dns-unbound.lo: src/dns/nm-dns-unbound.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dns/libNetworkManager_la-nm-dns-unbound.lo -MD -MP -MF src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Tpo -c -o src/dns/libNetworkManager_la-nm-dns-unbound.lo `test -f 'src/dns/nm-dns-unbound.c' || echo '$(srcdir)/'`src/dns/nm-dns-unbound.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Tpo src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dns/nm-dns-unbound.c' object='src/dns/libNetworkManager_la-nm-dns-unbound.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dns/libNetworkManager_la-nm-dns-unbound.lo: src/core/dns/nm-dns-unbound.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dns/libNetworkManager_la-nm-dns-unbound.lo -MD -MP -MF src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Tpo -c -o src/core/dns/libNetworkManager_la-nm-dns-unbound.lo `test -f 'src/core/dns/nm-dns-unbound.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-unbound.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Tpo src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dns/nm-dns-unbound.c' object='src/core/dns/libNetworkManager_la-nm-dns-unbound.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dns/libNetworkManager_la-nm-dns-unbound.lo `test -f 'src/dns/nm-dns-unbound.c' || echo '$(srcdir)/'`src/dns/nm-dns-unbound.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dns/libNetworkManager_la-nm-dns-unbound.lo `test -f 'src/core/dns/nm-dns-unbound.c' || echo '$(srcdir)/'`src/core/dns/nm-dns-unbound.c -src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo: src/dnsmasq/nm-dnsmasq-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo -MD -MP -MF src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Tpo -c -o src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo `test -f 'src/dnsmasq/nm-dnsmasq-manager.c' || echo '$(srcdir)/'`src/dnsmasq/nm-dnsmasq-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Tpo src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dnsmasq/nm-dnsmasq-manager.c' object='src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo: src/core/dnsmasq/nm-dnsmasq-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo -MD -MP -MF src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Tpo -c -o src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo `test -f 'src/core/dnsmasq/nm-dnsmasq-manager.c' || echo '$(srcdir)/'`src/core/dnsmasq/nm-dnsmasq-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Tpo src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dnsmasq/nm-dnsmasq-manager.c' object='src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo `test -f 'src/dnsmasq/nm-dnsmasq-manager.c' || echo '$(srcdir)/'`src/dnsmasq/nm-dnsmasq-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-manager.lo `test -f 'src/core/dnsmasq/nm-dnsmasq-manager.c' || echo '$(srcdir)/'`src/core/dnsmasq/nm-dnsmasq-manager.c -src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo: src/dnsmasq/nm-dnsmasq-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo -MD -MP -MF src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Tpo -c -o src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo `test -f 'src/dnsmasq/nm-dnsmasq-utils.c' || echo '$(srcdir)/'`src/dnsmasq/nm-dnsmasq-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Tpo src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dnsmasq/nm-dnsmasq-utils.c' object='src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo: src/core/dnsmasq/nm-dnsmasq-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo -MD -MP -MF src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Tpo -c -o src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo `test -f 'src/core/dnsmasq/nm-dnsmasq-utils.c' || echo '$(srcdir)/'`src/core/dnsmasq/nm-dnsmasq-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Tpo src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dnsmasq/nm-dnsmasq-utils.c' object='src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo `test -f 'src/dnsmasq/nm-dnsmasq-utils.c' || echo '$(srcdir)/'`src/dnsmasq/nm-dnsmasq-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dnsmasq/libNetworkManager_la-nm-dnsmasq-utils.lo `test -f 'src/core/dnsmasq/nm-dnsmasq-utils.c' || echo '$(srcdir)/'`src/core/dnsmasq/nm-dnsmasq-utils.c -src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo: src/ppp/nm-ppp-manager-call.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo -MD -MP -MF src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Tpo -c -o src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo `test -f 'src/ppp/nm-ppp-manager-call.c' || echo '$(srcdir)/'`src/ppp/nm-ppp-manager-call.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Tpo src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ppp/nm-ppp-manager-call.c' object='src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo: src/core/ppp/nm-ppp-manager-call.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo -MD -MP -MF src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Tpo -c -o src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo `test -f 'src/core/ppp/nm-ppp-manager-call.c' || echo '$(srcdir)/'`src/core/ppp/nm-ppp-manager-call.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Tpo src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ppp/nm-ppp-manager-call.c' object='src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ppp/libNetworkManager_la-nm-ppp-manager-call.lo `test -f 'src/ppp/nm-ppp-manager-call.c' || echo '$(srcdir)/'`src/ppp/nm-ppp-manager-call.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ppp/libNetworkManager_la-nm-ppp-manager-call.lo `test -f 'src/core/ppp/nm-ppp-manager-call.c' || echo '$(srcdir)/'`src/core/ppp/nm-ppp-manager-call.c -src/libNetworkManager_la-nm-hostname-manager.lo: src/nm-hostname-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-hostname-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Tpo -c -o src/libNetworkManager_la-nm-hostname-manager.lo `test -f 'src/nm-hostname-manager.c' || echo '$(srcdir)/'`src/nm-hostname-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-hostname-manager.c' object='src/libNetworkManager_la-nm-hostname-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-hostname-manager.lo: src/core/nm-hostname-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-hostname-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Tpo -c -o src/core/libNetworkManager_la-nm-hostname-manager.lo `test -f 'src/core/nm-hostname-manager.c' || echo '$(srcdir)/'`src/core/nm-hostname-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-hostname-manager.c' object='src/core/libNetworkManager_la-nm-hostname-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-hostname-manager.lo `test -f 'src/nm-hostname-manager.c' || echo '$(srcdir)/'`src/nm-hostname-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-hostname-manager.lo `test -f 'src/core/nm-hostname-manager.c' || echo '$(srcdir)/'`src/core/nm-hostname-manager.c -src/settings/libNetworkManager_la-nm-agent-manager.lo: src/settings/nm-agent-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-agent-manager.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Tpo -c -o src/settings/libNetworkManager_la-nm-agent-manager.lo `test -f 'src/settings/nm-agent-manager.c' || echo '$(srcdir)/'`src/settings/nm-agent-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-agent-manager.c' object='src/settings/libNetworkManager_la-nm-agent-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-agent-manager.lo: src/core/settings/nm-agent-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-agent-manager.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Tpo -c -o src/core/settings/libNetworkManager_la-nm-agent-manager.lo `test -f 'src/core/settings/nm-agent-manager.c' || echo '$(srcdir)/'`src/core/settings/nm-agent-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-agent-manager.c' object='src/core/settings/libNetworkManager_la-nm-agent-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-agent-manager.lo `test -f 'src/settings/nm-agent-manager.c' || echo '$(srcdir)/'`src/settings/nm-agent-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-agent-manager.lo `test -f 'src/core/settings/nm-agent-manager.c' || echo '$(srcdir)/'`src/core/settings/nm-agent-manager.c -src/settings/libNetworkManager_la-nm-secret-agent.lo: src/settings/nm-secret-agent.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-secret-agent.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Tpo -c -o src/settings/libNetworkManager_la-nm-secret-agent.lo `test -f 'src/settings/nm-secret-agent.c' || echo '$(srcdir)/'`src/settings/nm-secret-agent.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-secret-agent.c' object='src/settings/libNetworkManager_la-nm-secret-agent.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-secret-agent.lo: src/core/settings/nm-secret-agent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-secret-agent.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Tpo -c -o src/core/settings/libNetworkManager_la-nm-secret-agent.lo `test -f 'src/core/settings/nm-secret-agent.c' || echo '$(srcdir)/'`src/core/settings/nm-secret-agent.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-secret-agent.c' object='src/core/settings/libNetworkManager_la-nm-secret-agent.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-secret-agent.lo `test -f 'src/settings/nm-secret-agent.c' || echo '$(srcdir)/'`src/settings/nm-secret-agent.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-secret-agent.lo `test -f 'src/core/settings/nm-secret-agent.c' || echo '$(srcdir)/'`src/core/settings/nm-secret-agent.c -src/settings/libNetworkManager_la-nm-settings-connection.lo: src/settings/nm-settings-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-settings-connection.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Tpo -c -o src/settings/libNetworkManager_la-nm-settings-connection.lo `test -f 'src/settings/nm-settings-connection.c' || echo '$(srcdir)/'`src/settings/nm-settings-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-settings-connection.c' object='src/settings/libNetworkManager_la-nm-settings-connection.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-settings-connection.lo: src/core/settings/nm-settings-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-settings-connection.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Tpo -c -o src/core/settings/libNetworkManager_la-nm-settings-connection.lo `test -f 'src/core/settings/nm-settings-connection.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-settings-connection.c' object='src/core/settings/libNetworkManager_la-nm-settings-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-settings-connection.lo `test -f 'src/settings/nm-settings-connection.c' || echo '$(srcdir)/'`src/settings/nm-settings-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-settings-connection.lo `test -f 'src/core/settings/nm-settings-connection.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-connection.c -src/settings/libNetworkManager_la-nm-settings-storage.lo: src/settings/nm-settings-storage.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-settings-storage.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Tpo -c -o src/settings/libNetworkManager_la-nm-settings-storage.lo `test -f 'src/settings/nm-settings-storage.c' || echo '$(srcdir)/'`src/settings/nm-settings-storage.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-settings-storage.c' object='src/settings/libNetworkManager_la-nm-settings-storage.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-settings-storage.lo: src/core/settings/nm-settings-storage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-settings-storage.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Tpo -c -o src/core/settings/libNetworkManager_la-nm-settings-storage.lo `test -f 'src/core/settings/nm-settings-storage.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-storage.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-settings-storage.c' object='src/core/settings/libNetworkManager_la-nm-settings-storage.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-settings-storage.lo `test -f 'src/settings/nm-settings-storage.c' || echo '$(srcdir)/'`src/settings/nm-settings-storage.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-settings-storage.lo `test -f 'src/core/settings/nm-settings-storage.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-storage.c -src/settings/libNetworkManager_la-nm-settings-plugin.lo: src/settings/nm-settings-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-settings-plugin.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Tpo -c -o src/settings/libNetworkManager_la-nm-settings-plugin.lo `test -f 'src/settings/nm-settings-plugin.c' || echo '$(srcdir)/'`src/settings/nm-settings-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-settings-plugin.c' object='src/settings/libNetworkManager_la-nm-settings-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-settings-plugin.lo: src/core/settings/nm-settings-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-settings-plugin.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Tpo -c -o src/core/settings/libNetworkManager_la-nm-settings-plugin.lo `test -f 'src/core/settings/nm-settings-plugin.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-settings-plugin.c' object='src/core/settings/libNetworkManager_la-nm-settings-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-settings-plugin.lo `test -f 'src/settings/nm-settings-plugin.c' || echo '$(srcdir)/'`src/settings/nm-settings-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-settings-plugin.lo `test -f 'src/core/settings/nm-settings-plugin.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-plugin.c -src/settings/libNetworkManager_la-nm-settings.lo: src/settings/nm-settings.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-settings.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Tpo -c -o src/settings/libNetworkManager_la-nm-settings.lo `test -f 'src/settings/nm-settings.c' || echo '$(srcdir)/'`src/settings/nm-settings.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-settings.c' object='src/settings/libNetworkManager_la-nm-settings.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-settings.lo: src/core/settings/nm-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-settings.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Tpo -c -o src/core/settings/libNetworkManager_la-nm-settings.lo `test -f 'src/core/settings/nm-settings.c' || echo '$(srcdir)/'`src/core/settings/nm-settings.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-settings.c' object='src/core/settings/libNetworkManager_la-nm-settings.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-settings.lo `test -f 'src/settings/nm-settings.c' || echo '$(srcdir)/'`src/settings/nm-settings.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-settings.lo `test -f 'src/core/settings/nm-settings.c' || echo '$(srcdir)/'`src/core/settings/nm-settings.c -src/settings/libNetworkManager_la-nm-settings-utils.lo: src/settings/nm-settings-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/libNetworkManager_la-nm-settings-utils.lo -MD -MP -MF src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Tpo -c -o src/settings/libNetworkManager_la-nm-settings-utils.lo `test -f 'src/settings/nm-settings-utils.c' || echo '$(srcdir)/'`src/settings/nm-settings-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Tpo src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/nm-settings-utils.c' object='src/settings/libNetworkManager_la-nm-settings-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/libNetworkManager_la-nm-settings-utils.lo: src/core/settings/nm-settings-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/libNetworkManager_la-nm-settings-utils.lo -MD -MP -MF src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Tpo -c -o src/core/settings/libNetworkManager_la-nm-settings-utils.lo `test -f 'src/core/settings/nm-settings-utils.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Tpo src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/nm-settings-utils.c' object='src/core/settings/libNetworkManager_la-nm-settings-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/libNetworkManager_la-nm-settings-utils.lo `test -f 'src/settings/nm-settings-utils.c' || echo '$(srcdir)/'`src/settings/nm-settings-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/libNetworkManager_la-nm-settings-utils.lo `test -f 'src/core/settings/nm-settings-utils.c' || echo '$(srcdir)/'`src/core/settings/nm-settings-utils.c -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo: src/settings/plugins/keyfile/nms-keyfile-storage.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo -MD -MP -MF src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Tpo -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-storage.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-storage.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Tpo src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/nms-keyfile-storage.c' object='src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo: src/core/settings/plugins/keyfile/nms-keyfile-storage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo -MD -MP -MF src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Tpo -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-storage.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-storage.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Tpo src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/nms-keyfile-storage.c' object='src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-storage.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-storage.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-storage.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-storage.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-storage.c -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo: src/settings/plugins/keyfile/nms-keyfile-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo -MD -MP -MF src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Tpo -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Tpo src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/nms-keyfile-plugin.c' object='src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo: src/core/settings/plugins/keyfile/nms-keyfile-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo -MD -MP -MF src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Tpo -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Tpo src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/nms-keyfile-plugin.c' object='src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-plugin.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-plugin.c -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo: src/settings/plugins/keyfile/nms-keyfile-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo -MD -MP -MF src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Tpo -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-reader.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Tpo src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/nms-keyfile-reader.c' object='src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo: src/core/settings/plugins/keyfile/nms-keyfile-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo -MD -MP -MF src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Tpo -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-reader.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Tpo src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/nms-keyfile-reader.c' object='src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-reader.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-reader.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-reader.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-reader.c -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo: src/settings/plugins/keyfile/nms-keyfile-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo -MD -MP -MF src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Tpo -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-utils.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Tpo src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/nms-keyfile-utils.c' object='src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo: src/core/settings/plugins/keyfile/nms-keyfile-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo -MD -MP -MF src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Tpo -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-utils.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Tpo src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/nms-keyfile-utils.c' object='src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-utils.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-utils.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-utils.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-utils.c -src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo: src/settings/plugins/keyfile/nms-keyfile-writer.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo -MD -MP -MF src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Tpo -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-writer.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-writer.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Tpo src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/nms-keyfile-writer.c' object='src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo: src/core/settings/plugins/keyfile/nms-keyfile-writer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo -MD -MP -MF src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Tpo -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-writer.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-writer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Tpo src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/nms-keyfile-writer.c' object='src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo `test -f 'src/settings/plugins/keyfile/nms-keyfile-writer.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/nms-keyfile-writer.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/libNetworkManager_la-nms-keyfile-writer.lo `test -f 'src/core/settings/plugins/keyfile/nms-keyfile-writer.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/nms-keyfile-writer.c -src/supplicant/libNetworkManager_la-nm-supplicant-config.lo: src/supplicant/nm-supplicant-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/libNetworkManager_la-nm-supplicant-config.lo -MD -MP -MF src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Tpo -c -o src/supplicant/libNetworkManager_la-nm-supplicant-config.lo `test -f 'src/supplicant/nm-supplicant-config.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Tpo src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/nm-supplicant-config.c' object='src/supplicant/libNetworkManager_la-nm-supplicant-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo: src/core/supplicant/nm-supplicant-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo -MD -MP -MF src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Tpo -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo `test -f 'src/core/supplicant/nm-supplicant-config.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Tpo src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/nm-supplicant-config.c' object='src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/libNetworkManager_la-nm-supplicant-config.lo `test -f 'src/supplicant/nm-supplicant-config.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-config.lo `test -f 'src/core/supplicant/nm-supplicant-config.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-config.c -src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo: src/supplicant/nm-supplicant-interface.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo -MD -MP -MF src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Tpo -c -o src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo `test -f 'src/supplicant/nm-supplicant-interface.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-interface.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Tpo src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/nm-supplicant-interface.c' object='src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo: src/core/supplicant/nm-supplicant-interface.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo -MD -MP -MF src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Tpo -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo `test -f 'src/core/supplicant/nm-supplicant-interface.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-interface.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Tpo src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/nm-supplicant-interface.c' object='src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/libNetworkManager_la-nm-supplicant-interface.lo `test -f 'src/supplicant/nm-supplicant-interface.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-interface.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-interface.lo `test -f 'src/core/supplicant/nm-supplicant-interface.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-interface.c -src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo: src/supplicant/nm-supplicant-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo -MD -MP -MF src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Tpo -c -o src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo `test -f 'src/supplicant/nm-supplicant-manager.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Tpo src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/nm-supplicant-manager.c' object='src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo: src/core/supplicant/nm-supplicant-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo -MD -MP -MF src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Tpo -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo `test -f 'src/core/supplicant/nm-supplicant-manager.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Tpo src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/nm-supplicant-manager.c' object='src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/libNetworkManager_la-nm-supplicant-manager.lo `test -f 'src/supplicant/nm-supplicant-manager.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-manager.lo `test -f 'src/core/supplicant/nm-supplicant-manager.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-manager.c -src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo: src/supplicant/nm-supplicant-settings-verify.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo -MD -MP -MF src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Tpo -c -o src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo `test -f 'src/supplicant/nm-supplicant-settings-verify.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-settings-verify.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Tpo src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/nm-supplicant-settings-verify.c' object='src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo: src/core/supplicant/nm-supplicant-settings-verify.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo -MD -MP -MF src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Tpo -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo `test -f 'src/core/supplicant/nm-supplicant-settings-verify.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-settings-verify.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Tpo src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/nm-supplicant-settings-verify.c' object='src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo `test -f 'src/supplicant/nm-supplicant-settings-verify.c' || echo '$(srcdir)/'`src/supplicant/nm-supplicant-settings-verify.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/libNetworkManager_la-nm-supplicant-settings-verify.lo `test -f 'src/core/supplicant/nm-supplicant-settings-verify.c' || echo '$(srcdir)/'`src/core/supplicant/nm-supplicant-settings-verify.c -src/vpn/libNetworkManager_la-nm-vpn-connection.lo: src/vpn/nm-vpn-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/vpn/libNetworkManager_la-nm-vpn-connection.lo -MD -MP -MF src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Tpo -c -o src/vpn/libNetworkManager_la-nm-vpn-connection.lo `test -f 'src/vpn/nm-vpn-connection.c' || echo '$(srcdir)/'`src/vpn/nm-vpn-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Tpo src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/vpn/nm-vpn-connection.c' object='src/vpn/libNetworkManager_la-nm-vpn-connection.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo: src/core/vpn/nm-vpn-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo -MD -MP -MF src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Tpo -c -o src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo `test -f 'src/core/vpn/nm-vpn-connection.c' || echo '$(srcdir)/'`src/core/vpn/nm-vpn-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Tpo src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/vpn/nm-vpn-connection.c' object='src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/vpn/libNetworkManager_la-nm-vpn-connection.lo `test -f 'src/vpn/nm-vpn-connection.c' || echo '$(srcdir)/'`src/vpn/nm-vpn-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/vpn/libNetworkManager_la-nm-vpn-connection.lo `test -f 'src/core/vpn/nm-vpn-connection.c' || echo '$(srcdir)/'`src/core/vpn/nm-vpn-connection.c -src/vpn/libNetworkManager_la-nm-vpn-manager.lo: src/vpn/nm-vpn-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/vpn/libNetworkManager_la-nm-vpn-manager.lo -MD -MP -MF src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Tpo -c -o src/vpn/libNetworkManager_la-nm-vpn-manager.lo `test -f 'src/vpn/nm-vpn-manager.c' || echo '$(srcdir)/'`src/vpn/nm-vpn-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Tpo src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/vpn/nm-vpn-manager.c' object='src/vpn/libNetworkManager_la-nm-vpn-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo: src/core/vpn/nm-vpn-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo -MD -MP -MF src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Tpo -c -o src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo `test -f 'src/core/vpn/nm-vpn-manager.c' || echo '$(srcdir)/'`src/core/vpn/nm-vpn-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Tpo src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/vpn/nm-vpn-manager.c' object='src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/vpn/libNetworkManager_la-nm-vpn-manager.lo `test -f 'src/vpn/nm-vpn-manager.c' || echo '$(srcdir)/'`src/vpn/nm-vpn-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/vpn/libNetworkManager_la-nm-vpn-manager.lo `test -f 'src/core/vpn/nm-vpn-manager.c' || echo '$(srcdir)/'`src/core/vpn/nm-vpn-manager.c -src/libNetworkManager_la-nm-act-request.lo: src/nm-act-request.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-act-request.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Tpo -c -o src/libNetworkManager_la-nm-act-request.lo `test -f 'src/nm-act-request.c' || echo '$(srcdir)/'`src/nm-act-request.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-act-request.c' object='src/libNetworkManager_la-nm-act-request.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-act-request.lo: src/core/nm-act-request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-act-request.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Tpo -c -o src/core/libNetworkManager_la-nm-act-request.lo `test -f 'src/core/nm-act-request.c' || echo '$(srcdir)/'`src/core/nm-act-request.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-act-request.c' object='src/core/libNetworkManager_la-nm-act-request.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-act-request.lo `test -f 'src/nm-act-request.c' || echo '$(srcdir)/'`src/nm-act-request.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-act-request.lo `test -f 'src/core/nm-act-request.c' || echo '$(srcdir)/'`src/core/nm-act-request.c -src/libNetworkManager_la-nm-active-connection.lo: src/nm-active-connection.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-active-connection.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Tpo -c -o src/libNetworkManager_la-nm-active-connection.lo `test -f 'src/nm-active-connection.c' || echo '$(srcdir)/'`src/nm-active-connection.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-active-connection.c' object='src/libNetworkManager_la-nm-active-connection.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-active-connection.lo: src/core/nm-active-connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-active-connection.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Tpo -c -o src/core/libNetworkManager_la-nm-active-connection.lo `test -f 'src/core/nm-active-connection.c' || echo '$(srcdir)/'`src/core/nm-active-connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-active-connection.c' object='src/core/libNetworkManager_la-nm-active-connection.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-active-connection.lo `test -f 'src/nm-active-connection.c' || echo '$(srcdir)/'`src/nm-active-connection.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-active-connection.lo `test -f 'src/core/nm-active-connection.c' || echo '$(srcdir)/'`src/core/nm-active-connection.c -src/libNetworkManager_la-nm-audit-manager.lo: src/nm-audit-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-audit-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Tpo -c -o src/libNetworkManager_la-nm-audit-manager.lo `test -f 'src/nm-audit-manager.c' || echo '$(srcdir)/'`src/nm-audit-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-audit-manager.c' object='src/libNetworkManager_la-nm-audit-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-audit-manager.lo: src/core/nm-audit-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-audit-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Tpo -c -o src/core/libNetworkManager_la-nm-audit-manager.lo `test -f 'src/core/nm-audit-manager.c' || echo '$(srcdir)/'`src/core/nm-audit-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-audit-manager.c' object='src/core/libNetworkManager_la-nm-audit-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-audit-manager.lo `test -f 'src/nm-audit-manager.c' || echo '$(srcdir)/'`src/nm-audit-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-audit-manager.lo `test -f 'src/core/nm-audit-manager.c' || echo '$(srcdir)/'`src/core/nm-audit-manager.c -src/libNetworkManager_la-nm-dbus-manager.lo: src/nm-dbus-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-dbus-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Tpo -c -o src/libNetworkManager_la-nm-dbus-manager.lo `test -f 'src/nm-dbus-manager.c' || echo '$(srcdir)/'`src/nm-dbus-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dbus-manager.c' object='src/libNetworkManager_la-nm-dbus-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-dbus-manager.lo: src/core/nm-dbus-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-dbus-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Tpo -c -o src/core/libNetworkManager_la-nm-dbus-manager.lo `test -f 'src/core/nm-dbus-manager.c' || echo '$(srcdir)/'`src/core/nm-dbus-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dbus-manager.c' object='src/core/libNetworkManager_la-nm-dbus-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-dbus-manager.lo `test -f 'src/nm-dbus-manager.c' || echo '$(srcdir)/'`src/nm-dbus-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-dbus-manager.lo `test -f 'src/core/nm-dbus-manager.c' || echo '$(srcdir)/'`src/core/nm-dbus-manager.c -src/libNetworkManager_la-nm-config.lo: src/nm-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-config.Tpo -c -o src/libNetworkManager_la-nm-config.lo `test -f 'src/nm-config.c' || echo '$(srcdir)/'`src/nm-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-config.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-config.c' object='src/libNetworkManager_la-nm-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-config.lo: src/core/nm-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Tpo -c -o src/core/libNetworkManager_la-nm-config.lo `test -f 'src/core/nm-config.c' || echo '$(srcdir)/'`src/core/nm-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-config.c' object='src/core/libNetworkManager_la-nm-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-config.lo `test -f 'src/nm-config.c' || echo '$(srcdir)/'`src/nm-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-config.lo `test -f 'src/core/nm-config.c' || echo '$(srcdir)/'`src/core/nm-config.c -src/libNetworkManager_la-nm-config-data.lo: src/nm-config-data.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-config-data.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Tpo -c -o src/libNetworkManager_la-nm-config-data.lo `test -f 'src/nm-config-data.c' || echo '$(srcdir)/'`src/nm-config-data.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-config-data.c' object='src/libNetworkManager_la-nm-config-data.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-config-data.lo: src/core/nm-config-data.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-config-data.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Tpo -c -o src/core/libNetworkManager_la-nm-config-data.lo `test -f 'src/core/nm-config-data.c' || echo '$(srcdir)/'`src/core/nm-config-data.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-config-data.c' object='src/core/libNetworkManager_la-nm-config-data.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-config-data.lo `test -f 'src/nm-config-data.c' || echo '$(srcdir)/'`src/nm-config-data.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-config-data.lo `test -f 'src/core/nm-config-data.c' || echo '$(srcdir)/'`src/core/nm-config-data.c -src/libNetworkManager_la-nm-connectivity.lo: src/nm-connectivity.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-connectivity.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Tpo -c -o src/libNetworkManager_la-nm-connectivity.lo `test -f 'src/nm-connectivity.c' || echo '$(srcdir)/'`src/nm-connectivity.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-connectivity.c' object='src/libNetworkManager_la-nm-connectivity.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-connectivity.lo: src/core/nm-connectivity.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-connectivity.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Tpo -c -o src/core/libNetworkManager_la-nm-connectivity.lo `test -f 'src/core/nm-connectivity.c' || echo '$(srcdir)/'`src/core/nm-connectivity.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-connectivity.c' object='src/core/libNetworkManager_la-nm-connectivity.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-connectivity.lo `test -f 'src/nm-connectivity.c' || echo '$(srcdir)/'`src/nm-connectivity.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-connectivity.lo `test -f 'src/core/nm-connectivity.c' || echo '$(srcdir)/'`src/core/nm-connectivity.c -src/libNetworkManager_la-nm-dcb.lo: src/nm-dcb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-dcb.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Tpo -c -o src/libNetworkManager_la-nm-dcb.lo `test -f 'src/nm-dcb.c' || echo '$(srcdir)/'`src/nm-dcb.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dcb.c' object='src/libNetworkManager_la-nm-dcb.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-dcb.lo: src/core/nm-dcb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-dcb.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Tpo -c -o src/core/libNetworkManager_la-nm-dcb.lo `test -f 'src/core/nm-dcb.c' || echo '$(srcdir)/'`src/core/nm-dcb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dcb.c' object='src/core/libNetworkManager_la-nm-dcb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-dcb.lo `test -f 'src/nm-dcb.c' || echo '$(srcdir)/'`src/nm-dcb.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-dcb.lo `test -f 'src/core/nm-dcb.c' || echo '$(srcdir)/'`src/core/nm-dcb.c -src/libNetworkManager_la-nm-dhcp-config.lo: src/nm-dhcp-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-dhcp-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Tpo -c -o src/libNetworkManager_la-nm-dhcp-config.lo `test -f 'src/nm-dhcp-config.c' || echo '$(srcdir)/'`src/nm-dhcp-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dhcp-config.c' object='src/libNetworkManager_la-nm-dhcp-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-dhcp-config.lo: src/core/nm-dhcp-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-dhcp-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Tpo -c -o src/core/libNetworkManager_la-nm-dhcp-config.lo `test -f 'src/core/nm-dhcp-config.c' || echo '$(srcdir)/'`src/core/nm-dhcp-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dhcp-config.c' object='src/core/libNetworkManager_la-nm-dhcp-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-dhcp-config.lo `test -f 'src/nm-dhcp-config.c' || echo '$(srcdir)/'`src/nm-dhcp-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-dhcp-config.lo `test -f 'src/core/nm-dhcp-config.c' || echo '$(srcdir)/'`src/core/nm-dhcp-config.c -src/libNetworkManager_la-nm-dispatcher.lo: src/nm-dispatcher.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-dispatcher.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Tpo -c -o src/libNetworkManager_la-nm-dispatcher.lo `test -f 'src/nm-dispatcher.c' || echo '$(srcdir)/'`src/nm-dispatcher.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dispatcher.c' object='src/libNetworkManager_la-nm-dispatcher.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-dispatcher.lo: src/core/nm-dispatcher.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-dispatcher.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Tpo -c -o src/core/libNetworkManager_la-nm-dispatcher.lo `test -f 'src/core/nm-dispatcher.c' || echo '$(srcdir)/'`src/core/nm-dispatcher.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dispatcher.c' object='src/core/libNetworkManager_la-nm-dispatcher.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-dispatcher.lo `test -f 'src/nm-dispatcher.c' || echo '$(srcdir)/'`src/nm-dispatcher.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-dispatcher.lo `test -f 'src/core/nm-dispatcher.c' || echo '$(srcdir)/'`src/core/nm-dispatcher.c -src/libNetworkManager_la-nm-firewall-manager.lo: src/nm-firewall-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-firewall-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Tpo -c -o src/libNetworkManager_la-nm-firewall-manager.lo `test -f 'src/nm-firewall-manager.c' || echo '$(srcdir)/'`src/nm-firewall-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-firewall-manager.c' object='src/libNetworkManager_la-nm-firewall-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-firewall-manager.lo: src/core/nm-firewall-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-firewall-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Tpo -c -o src/core/libNetworkManager_la-nm-firewall-manager.lo `test -f 'src/core/nm-firewall-manager.c' || echo '$(srcdir)/'`src/core/nm-firewall-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-firewall-manager.c' object='src/core/libNetworkManager_la-nm-firewall-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-firewall-manager.lo `test -f 'src/nm-firewall-manager.c' || echo '$(srcdir)/'`src/nm-firewall-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-firewall-manager.lo `test -f 'src/core/nm-firewall-manager.c' || echo '$(srcdir)/'`src/core/nm-firewall-manager.c -src/libNetworkManager_la-nm-proxy-config.lo: src/nm-proxy-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-proxy-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Tpo -c -o src/libNetworkManager_la-nm-proxy-config.lo `test -f 'src/nm-proxy-config.c' || echo '$(srcdir)/'`src/nm-proxy-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-proxy-config.c' object='src/libNetworkManager_la-nm-proxy-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-proxy-config.lo: src/core/nm-proxy-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-proxy-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Tpo -c -o src/core/libNetworkManager_la-nm-proxy-config.lo `test -f 'src/core/nm-proxy-config.c' || echo '$(srcdir)/'`src/core/nm-proxy-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-proxy-config.c' object='src/core/libNetworkManager_la-nm-proxy-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-proxy-config.lo `test -f 'src/nm-proxy-config.c' || echo '$(srcdir)/'`src/nm-proxy-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-proxy-config.lo `test -f 'src/core/nm-proxy-config.c' || echo '$(srcdir)/'`src/core/nm-proxy-config.c -src/libNetworkManager_la-nm-auth-manager.lo: src/nm-auth-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-auth-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Tpo -c -o src/libNetworkManager_la-nm-auth-manager.lo `test -f 'src/nm-auth-manager.c' || echo '$(srcdir)/'`src/nm-auth-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-auth-manager.c' object='src/libNetworkManager_la-nm-auth-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-auth-manager.lo: src/core/nm-auth-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-auth-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Tpo -c -o src/core/libNetworkManager_la-nm-auth-manager.lo `test -f 'src/core/nm-auth-manager.c' || echo '$(srcdir)/'`src/core/nm-auth-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-auth-manager.c' object='src/core/libNetworkManager_la-nm-auth-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-auth-manager.lo `test -f 'src/nm-auth-manager.c' || echo '$(srcdir)/'`src/nm-auth-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-auth-manager.lo `test -f 'src/core/nm-auth-manager.c' || echo '$(srcdir)/'`src/core/nm-auth-manager.c -src/libNetworkManager_la-nm-auth-utils.lo: src/nm-auth-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-auth-utils.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Tpo -c -o src/libNetworkManager_la-nm-auth-utils.lo `test -f 'src/nm-auth-utils.c' || echo '$(srcdir)/'`src/nm-auth-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-auth-utils.c' object='src/libNetworkManager_la-nm-auth-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-auth-utils.lo: src/core/nm-auth-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-auth-utils.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Tpo -c -o src/core/libNetworkManager_la-nm-auth-utils.lo `test -f 'src/core/nm-auth-utils.c' || echo '$(srcdir)/'`src/core/nm-auth-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-auth-utils.c' object='src/core/libNetworkManager_la-nm-auth-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-auth-utils.lo `test -f 'src/nm-auth-utils.c' || echo '$(srcdir)/'`src/nm-auth-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-auth-utils.lo `test -f 'src/core/nm-auth-utils.c' || echo '$(srcdir)/'`src/core/nm-auth-utils.c -src/libNetworkManager_la-nm-manager.lo: src/nm-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-manager.Tpo -c -o src/libNetworkManager_la-nm-manager.lo `test -f 'src/nm-manager.c' || echo '$(srcdir)/'`src/nm-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-manager.c' object='src/libNetworkManager_la-nm-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-manager.lo: src/core/nm-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Tpo -c -o src/core/libNetworkManager_la-nm-manager.lo `test -f 'src/core/nm-manager.c' || echo '$(srcdir)/'`src/core/nm-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-manager.c' object='src/core/libNetworkManager_la-nm-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-manager.lo `test -f 'src/nm-manager.c' || echo '$(srcdir)/'`src/nm-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-manager.lo `test -f 'src/core/nm-manager.c' || echo '$(srcdir)/'`src/core/nm-manager.c -src/libNetworkManager_la-nm-pacrunner-manager.lo: src/nm-pacrunner-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-pacrunner-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Tpo -c -o src/libNetworkManager_la-nm-pacrunner-manager.lo `test -f 'src/nm-pacrunner-manager.c' || echo '$(srcdir)/'`src/nm-pacrunner-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-pacrunner-manager.c' object='src/libNetworkManager_la-nm-pacrunner-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-pacrunner-manager.lo: src/core/nm-pacrunner-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-pacrunner-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Tpo -c -o src/core/libNetworkManager_la-nm-pacrunner-manager.lo `test -f 'src/core/nm-pacrunner-manager.c' || echo '$(srcdir)/'`src/core/nm-pacrunner-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-pacrunner-manager.c' object='src/core/libNetworkManager_la-nm-pacrunner-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-pacrunner-manager.lo `test -f 'src/nm-pacrunner-manager.c' || echo '$(srcdir)/'`src/nm-pacrunner-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-pacrunner-manager.lo `test -f 'src/core/nm-pacrunner-manager.c' || echo '$(srcdir)/'`src/core/nm-pacrunner-manager.c -src/libNetworkManager_la-nm-policy.lo: src/nm-policy.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-policy.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-policy.Tpo -c -o src/libNetworkManager_la-nm-policy.lo `test -f 'src/nm-policy.c' || echo '$(srcdir)/'`src/nm-policy.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-policy.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-policy.c' object='src/libNetworkManager_la-nm-policy.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-policy.lo: src/core/nm-policy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-policy.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Tpo -c -o src/core/libNetworkManager_la-nm-policy.lo `test -f 'src/core/nm-policy.c' || echo '$(srcdir)/'`src/core/nm-policy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-policy.c' object='src/core/libNetworkManager_la-nm-policy.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-policy.lo `test -f 'src/nm-policy.c' || echo '$(srcdir)/'`src/nm-policy.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-policy.lo `test -f 'src/core/nm-policy.c' || echo '$(srcdir)/'`src/core/nm-policy.c -src/libNetworkManager_la-nm-rfkill-manager.lo: src/nm-rfkill-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-rfkill-manager.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Tpo -c -o src/libNetworkManager_la-nm-rfkill-manager.lo `test -f 'src/nm-rfkill-manager.c' || echo '$(srcdir)/'`src/nm-rfkill-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-rfkill-manager.c' object='src/libNetworkManager_la-nm-rfkill-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-rfkill-manager.lo: src/core/nm-rfkill-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-rfkill-manager.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Tpo -c -o src/core/libNetworkManager_la-nm-rfkill-manager.lo `test -f 'src/core/nm-rfkill-manager.c' || echo '$(srcdir)/'`src/core/nm-rfkill-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-rfkill-manager.c' object='src/core/libNetworkManager_la-nm-rfkill-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-rfkill-manager.lo `test -f 'src/nm-rfkill-manager.c' || echo '$(srcdir)/'`src/nm-rfkill-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-rfkill-manager.lo `test -f 'src/core/nm-rfkill-manager.c' || echo '$(srcdir)/'`src/core/nm-rfkill-manager.c -src/libNetworkManager_la-nm-session-monitor.lo: src/nm-session-monitor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-session-monitor.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Tpo -c -o src/libNetworkManager_la-nm-session-monitor.lo `test -f 'src/nm-session-monitor.c' || echo '$(srcdir)/'`src/nm-session-monitor.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-session-monitor.c' object='src/libNetworkManager_la-nm-session-monitor.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-session-monitor.lo: src/core/nm-session-monitor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-session-monitor.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Tpo -c -o src/core/libNetworkManager_la-nm-session-monitor.lo `test -f 'src/core/nm-session-monitor.c' || echo '$(srcdir)/'`src/core/nm-session-monitor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-session-monitor.c' object='src/core/libNetworkManager_la-nm-session-monitor.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-session-monitor.lo `test -f 'src/nm-session-monitor.c' || echo '$(srcdir)/'`src/nm-session-monitor.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-session-monitor.lo `test -f 'src/core/nm-session-monitor.c' || echo '$(srcdir)/'`src/core/nm-session-monitor.c -src/libNetworkManager_la-nm-keep-alive.lo: src/nm-keep-alive.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-keep-alive.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Tpo -c -o src/libNetworkManager_la-nm-keep-alive.lo `test -f 'src/nm-keep-alive.c' || echo '$(srcdir)/'`src/nm-keep-alive.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-keep-alive.c' object='src/libNetworkManager_la-nm-keep-alive.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-keep-alive.lo: src/core/nm-keep-alive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-keep-alive.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Tpo -c -o src/core/libNetworkManager_la-nm-keep-alive.lo `test -f 'src/core/nm-keep-alive.c' || echo '$(srcdir)/'`src/core/nm-keep-alive.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-keep-alive.c' object='src/core/libNetworkManager_la-nm-keep-alive.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-keep-alive.lo `test -f 'src/nm-keep-alive.c' || echo '$(srcdir)/'`src/nm-keep-alive.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-keep-alive.lo `test -f 'src/core/nm-keep-alive.c' || echo '$(srcdir)/'`src/core/nm-keep-alive.c -src/libNetworkManager_la-nm-sleep-monitor.lo: src/nm-sleep-monitor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManager_la-nm-sleep-monitor.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Tpo -c -o src/libNetworkManager_la-nm-sleep-monitor.lo `test -f 'src/nm-sleep-monitor.c' || echo '$(srcdir)/'`src/nm-sleep-monitor.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Tpo src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-sleep-monitor.c' object='src/libNetworkManager_la-nm-sleep-monitor.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManager_la-nm-sleep-monitor.lo: src/core/nm-sleep-monitor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManager_la-nm-sleep-monitor.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Tpo -c -o src/core/libNetworkManager_la-nm-sleep-monitor.lo `test -f 'src/core/nm-sleep-monitor.c' || echo '$(srcdir)/'`src/core/nm-sleep-monitor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Tpo src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-sleep-monitor.c' object='src/core/libNetworkManager_la-nm-sleep-monitor.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManager_la-nm-sleep-monitor.lo `test -f 'src/nm-sleep-monitor.c' || echo '$(srcdir)/'`src/nm-sleep-monitor.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManager_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManager_la-nm-sleep-monitor.lo `test -f 'src/core/nm-sleep-monitor.c' || echo '$(srcdir)/'`src/core/nm-sleep-monitor.c -src/libNetworkManagerBase_la-nm-core-utils.lo: src/nm-core-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-core-utils.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Tpo -c -o src/libNetworkManagerBase_la-nm-core-utils.lo `test -f 'src/nm-core-utils.c' || echo '$(srcdir)/'`src/nm-core-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-core-utils.c' object='src/libNetworkManagerBase_la-nm-core-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-core-utils.lo: src/core/nm-core-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-core-utils.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Tpo -c -o src/core/libNetworkManagerBase_la-nm-core-utils.lo `test -f 'src/core/nm-core-utils.c' || echo '$(srcdir)/'`src/core/nm-core-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-core-utils.c' object='src/core/libNetworkManagerBase_la-nm-core-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-core-utils.lo `test -f 'src/nm-core-utils.c' || echo '$(srcdir)/'`src/nm-core-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-core-utils.lo `test -f 'src/core/nm-core-utils.c' || echo '$(srcdir)/'`src/core/nm-core-utils.c -src/libNetworkManagerBase_la-NetworkManagerUtils.lo: src/NetworkManagerUtils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-NetworkManagerUtils.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Tpo -c -o src/libNetworkManagerBase_la-NetworkManagerUtils.lo `test -f 'src/NetworkManagerUtils.c' || echo '$(srcdir)/'`src/NetworkManagerUtils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/NetworkManagerUtils.c' object='src/libNetworkManagerBase_la-NetworkManagerUtils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo: src/core/NetworkManagerUtils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Tpo -c -o src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo `test -f 'src/core/NetworkManagerUtils.c' || echo '$(srcdir)/'`src/core/NetworkManagerUtils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/NetworkManagerUtils.c' object='src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-NetworkManagerUtils.lo `test -f 'src/NetworkManagerUtils.c' || echo '$(srcdir)/'`src/NetworkManagerUtils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-NetworkManagerUtils.lo `test -f 'src/core/NetworkManagerUtils.c' || echo '$(srcdir)/'`src/core/NetworkManagerUtils.c -src/platform/libNetworkManagerBase_la-nmp-object.lo: src/platform/nmp-object.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/libNetworkManagerBase_la-nmp-object.lo -MD -MP -MF src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Tpo -c -o src/platform/libNetworkManagerBase_la-nmp-object.lo `test -f 'src/platform/nmp-object.c' || echo '$(srcdir)/'`src/platform/nmp-object.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Tpo src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/nmp-object.c' object='src/platform/libNetworkManagerBase_la-nmp-object.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/libNetworkManagerBase_la-nmp-object.lo: src/core/platform/nmp-object.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/libNetworkManagerBase_la-nmp-object.lo -MD -MP -MF src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Tpo -c -o src/core/platform/libNetworkManagerBase_la-nmp-object.lo `test -f 'src/core/platform/nmp-object.c' || echo '$(srcdir)/'`src/core/platform/nmp-object.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Tpo src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/nmp-object.c' object='src/core/platform/libNetworkManagerBase_la-nmp-object.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/libNetworkManagerBase_la-nmp-object.lo `test -f 'src/platform/nmp-object.c' || echo '$(srcdir)/'`src/platform/nmp-object.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/libNetworkManagerBase_la-nmp-object.lo `test -f 'src/core/platform/nmp-object.c' || echo '$(srcdir)/'`src/core/platform/nmp-object.c -src/platform/libNetworkManagerBase_la-nm-platform.lo: src/platform/nm-platform.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/libNetworkManagerBase_la-nm-platform.lo -MD -MP -MF src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Tpo -c -o src/platform/libNetworkManagerBase_la-nm-platform.lo `test -f 'src/platform/nm-platform.c' || echo '$(srcdir)/'`src/platform/nm-platform.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Tpo src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/nm-platform.c' object='src/platform/libNetworkManagerBase_la-nm-platform.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/libNetworkManagerBase_la-nm-platform.lo: src/core/platform/nm-platform.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/libNetworkManagerBase_la-nm-platform.lo -MD -MP -MF src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Tpo -c -o src/core/platform/libNetworkManagerBase_la-nm-platform.lo `test -f 'src/core/platform/nm-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-platform.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Tpo src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/nm-platform.c' object='src/core/platform/libNetworkManagerBase_la-nm-platform.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/libNetworkManagerBase_la-nm-platform.lo `test -f 'src/platform/nm-platform.c' || echo '$(srcdir)/'`src/platform/nm-platform.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/libNetworkManagerBase_la-nm-platform.lo `test -f 'src/core/platform/nm-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-platform.c -src/platform/libNetworkManagerBase_la-nm-linux-platform.lo: src/platform/nm-linux-platform.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/libNetworkManagerBase_la-nm-linux-platform.lo -MD -MP -MF src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Tpo -c -o src/platform/libNetworkManagerBase_la-nm-linux-platform.lo `test -f 'src/platform/nm-linux-platform.c' || echo '$(srcdir)/'`src/platform/nm-linux-platform.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Tpo src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/nm-linux-platform.c' object='src/platform/libNetworkManagerBase_la-nm-linux-platform.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo: src/core/platform/nm-linux-platform.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo -MD -MP -MF src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Tpo -c -o src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo `test -f 'src/core/platform/nm-linux-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-linux-platform.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Tpo src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/nm-linux-platform.c' object='src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/libNetworkManagerBase_la-nm-linux-platform.lo `test -f 'src/platform/nm-linux-platform.c' || echo '$(srcdir)/'`src/platform/nm-linux-platform.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/libNetworkManagerBase_la-nm-linux-platform.lo `test -f 'src/core/platform/nm-linux-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-linux-platform.c -src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo: src/platform/nmp-rules-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo -MD -MP -MF src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Tpo -c -o src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo `test -f 'src/platform/nmp-rules-manager.c' || echo '$(srcdir)/'`src/platform/nmp-rules-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Tpo src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/nmp-rules-manager.c' object='src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo: src/core/platform/nmp-rules-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo -MD -MP -MF src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Tpo -c -o src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo `test -f 'src/core/platform/nmp-rules-manager.c' || echo '$(srcdir)/'`src/core/platform/nmp-rules-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Tpo src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/nmp-rules-manager.c' object='src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/libNetworkManagerBase_la-nmp-rules-manager.lo `test -f 'src/platform/nmp-rules-manager.c' || echo '$(srcdir)/'`src/platform/nmp-rules-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/libNetworkManagerBase_la-nmp-rules-manager.lo `test -f 'src/core/platform/nmp-rules-manager.c' || echo '$(srcdir)/'`src/core/platform/nmp-rules-manager.c -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo: src/platform/wifi/nm-wifi-utils-nl80211.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo -MD -MP -MF src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Tpo -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo `test -f 'src/platform/wifi/nm-wifi-utils-nl80211.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils-nl80211.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Tpo src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/wifi/nm-wifi-utils-nl80211.c' object='src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo: src/core/platform/wifi/nm-wifi-utils-nl80211.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo -MD -MP -MF src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Tpo -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo `test -f 'src/core/platform/wifi/nm-wifi-utils-nl80211.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils-nl80211.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Tpo src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/wifi/nm-wifi-utils-nl80211.c' object='src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo `test -f 'src/platform/wifi/nm-wifi-utils-nl80211.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils-nl80211.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-nl80211.lo `test -f 'src/core/platform/wifi/nm-wifi-utils-nl80211.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils-nl80211.c -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo: src/platform/wifi/nm-wifi-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo -MD -MP -MF src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Tpo -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo `test -f 'src/platform/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Tpo src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/wifi/nm-wifi-utils.c' object='src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo: src/core/platform/wifi/nm-wifi-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo -MD -MP -MF src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Tpo -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo `test -f 'src/core/platform/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Tpo src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/wifi/nm-wifi-utils.c' object='src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo `test -f 'src/platform/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils.lo `test -f 'src/core/platform/wifi/nm-wifi-utils.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils.c -src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo: src/platform/wpan/nm-wpan-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo -MD -MP -MF src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Tpo -c -o src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo `test -f 'src/platform/wpan/nm-wpan-utils.c' || echo '$(srcdir)/'`src/platform/wpan/nm-wpan-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Tpo src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/wpan/nm-wpan-utils.c' object='src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo: src/core/platform/wpan/nm-wpan-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo -MD -MP -MF src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Tpo -c -o src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo `test -f 'src/core/platform/wpan/nm-wpan-utils.c' || echo '$(srcdir)/'`src/core/platform/wpan/nm-wpan-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Tpo src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/wpan/nm-wpan-utils.c' object='src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo `test -f 'src/platform/wpan/nm-wpan-utils.c' || echo '$(srcdir)/'`src/platform/wpan/nm-wpan-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/wpan/libNetworkManagerBase_la-nm-wpan-utils.lo `test -f 'src/core/platform/wpan/nm-wpan-utils.c' || echo '$(srcdir)/'`src/core/platform/wpan/nm-wpan-utils.c -src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo: src/ndisc/nm-lndp-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo -MD -MP -MF src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Tpo -c -o src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo `test -f 'src/ndisc/nm-lndp-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-lndp-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Tpo src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/nm-lndp-ndisc.c' object='src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo: src/core/ndisc/nm-lndp-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo -MD -MP -MF src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Tpo -c -o src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo `test -f 'src/core/ndisc/nm-lndp-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-lndp-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Tpo src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/nm-lndp-ndisc.c' object='src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo `test -f 'src/ndisc/nm-lndp-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-lndp-ndisc.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/libNetworkManagerBase_la-nm-lndp-ndisc.lo `test -f 'src/core/ndisc/nm-lndp-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-lndp-ndisc.c -src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo: src/ndisc/nm-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo -MD -MP -MF src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Tpo -c -o src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo `test -f 'src/ndisc/nm-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Tpo src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/nm-ndisc.c' object='src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo: src/core/ndisc/nm-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo -MD -MP -MF src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Tpo -c -o src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo `test -f 'src/core/ndisc/nm-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Tpo src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/nm-ndisc.c' object='src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/libNetworkManagerBase_la-nm-ndisc.lo `test -f 'src/ndisc/nm-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-ndisc.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/libNetworkManagerBase_la-nm-ndisc.lo `test -f 'src/core/ndisc/nm-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-ndisc.c -src/libNetworkManagerBase_la-nm-dbus-utils.lo: src/nm-dbus-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-dbus-utils.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Tpo -c -o src/libNetworkManagerBase_la-nm-dbus-utils.lo `test -f 'src/nm-dbus-utils.c' || echo '$(srcdir)/'`src/nm-dbus-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dbus-utils.c' object='src/libNetworkManagerBase_la-nm-dbus-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-dbus-utils.lo: src/core/nm-dbus-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-dbus-utils.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Tpo -c -o src/core/libNetworkManagerBase_la-nm-dbus-utils.lo `test -f 'src/core/nm-dbus-utils.c' || echo '$(srcdir)/'`src/core/nm-dbus-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dbus-utils.c' object='src/core/libNetworkManagerBase_la-nm-dbus-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-dbus-utils.lo `test -f 'src/nm-dbus-utils.c' || echo '$(srcdir)/'`src/nm-dbus-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-dbus-utils.lo `test -f 'src/core/nm-dbus-utils.c' || echo '$(srcdir)/'`src/core/nm-dbus-utils.c -src/libNetworkManagerBase_la-nm-dbus-object.lo: src/nm-dbus-object.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-dbus-object.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Tpo -c -o src/libNetworkManagerBase_la-nm-dbus-object.lo `test -f 'src/nm-dbus-object.c' || echo '$(srcdir)/'`src/nm-dbus-object.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-dbus-object.c' object='src/libNetworkManagerBase_la-nm-dbus-object.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-dbus-object.lo: src/core/nm-dbus-object.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-dbus-object.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Tpo -c -o src/core/libNetworkManagerBase_la-nm-dbus-object.lo `test -f 'src/core/nm-dbus-object.c' || echo '$(srcdir)/'`src/core/nm-dbus-object.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-dbus-object.c' object='src/core/libNetworkManagerBase_la-nm-dbus-object.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-dbus-object.lo `test -f 'src/nm-dbus-object.c' || echo '$(srcdir)/'`src/nm-dbus-object.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-dbus-object.lo `test -f 'src/core/nm-dbus-object.c' || echo '$(srcdir)/'`src/core/nm-dbus-object.c -src/libNetworkManagerBase_la-nm-netns.lo: src/nm-netns.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-netns.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Tpo -c -o src/libNetworkManagerBase_la-nm-netns.lo `test -f 'src/nm-netns.c' || echo '$(srcdir)/'`src/nm-netns.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-netns.c' object='src/libNetworkManagerBase_la-nm-netns.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-netns.lo: src/core/nm-netns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-netns.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Tpo -c -o src/core/libNetworkManagerBase_la-nm-netns.lo `test -f 'src/core/nm-netns.c' || echo '$(srcdir)/'`src/core/nm-netns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-netns.c' object='src/core/libNetworkManagerBase_la-nm-netns.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-netns.lo `test -f 'src/nm-netns.c' || echo '$(srcdir)/'`src/nm-netns.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-netns.lo `test -f 'src/core/nm-netns.c' || echo '$(srcdir)/'`src/core/nm-netns.c -src/libNetworkManagerBase_la-nm-l3-config-data.lo: src/nm-l3-config-data.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-l3-config-data.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Tpo -c -o src/libNetworkManagerBase_la-nm-l3-config-data.lo `test -f 'src/nm-l3-config-data.c' || echo '$(srcdir)/'`src/nm-l3-config-data.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-l3-config-data.c' object='src/libNetworkManagerBase_la-nm-l3-config-data.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-l3-config-data.lo: src/core/nm-l3-config-data.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-l3-config-data.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Tpo -c -o src/core/libNetworkManagerBase_la-nm-l3-config-data.lo `test -f 'src/core/nm-l3-config-data.c' || echo '$(srcdir)/'`src/core/nm-l3-config-data.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-l3-config-data.c' object='src/core/libNetworkManagerBase_la-nm-l3-config-data.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-l3-config-data.lo `test -f 'src/nm-l3-config-data.c' || echo '$(srcdir)/'`src/nm-l3-config-data.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-l3-config-data.lo `test -f 'src/core/nm-l3-config-data.c' || echo '$(srcdir)/'`src/core/nm-l3-config-data.c -src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo: src/nm-l3-ipv4ll.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Tpo -c -o src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo `test -f 'src/nm-l3-ipv4ll.c' || echo '$(srcdir)/'`src/nm-l3-ipv4ll.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-l3-ipv4ll.c' object='src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo: src/core/nm-l3-ipv4ll.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Tpo -c -o src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo `test -f 'src/core/nm-l3-ipv4ll.c' || echo '$(srcdir)/'`src/core/nm-l3-ipv4ll.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-l3-ipv4ll.c' object='src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-l3-ipv4ll.lo `test -f 'src/nm-l3-ipv4ll.c' || echo '$(srcdir)/'`src/nm-l3-ipv4ll.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-l3-ipv4ll.lo `test -f 'src/core/nm-l3-ipv4ll.c' || echo '$(srcdir)/'`src/core/nm-l3-ipv4ll.c -src/libNetworkManagerBase_la-nm-l3cfg.lo: src/nm-l3cfg.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-l3cfg.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Tpo -c -o src/libNetworkManagerBase_la-nm-l3cfg.lo `test -f 'src/nm-l3cfg.c' || echo '$(srcdir)/'`src/nm-l3cfg.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-l3cfg.c' object='src/libNetworkManagerBase_la-nm-l3cfg.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-l3cfg.lo: src/core/nm-l3cfg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-l3cfg.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Tpo -c -o src/core/libNetworkManagerBase_la-nm-l3cfg.lo `test -f 'src/core/nm-l3cfg.c' || echo '$(srcdir)/'`src/core/nm-l3cfg.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-l3cfg.c' object='src/core/libNetworkManagerBase_la-nm-l3cfg.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-l3cfg.lo `test -f 'src/nm-l3cfg.c' || echo '$(srcdir)/'`src/nm-l3cfg.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-l3cfg.lo `test -f 'src/core/nm-l3cfg.c' || echo '$(srcdir)/'`src/core/nm-l3cfg.c -src/libNetworkManagerBase_la-nm-ip-config.lo: src/nm-ip-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-ip-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Tpo -c -o src/libNetworkManagerBase_la-nm-ip-config.lo `test -f 'src/nm-ip-config.c' || echo '$(srcdir)/'`src/nm-ip-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-ip-config.c' object='src/libNetworkManagerBase_la-nm-ip-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-ip-config.lo: src/core/nm-ip-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-ip-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Tpo -c -o src/core/libNetworkManagerBase_la-nm-ip-config.lo `test -f 'src/core/nm-ip-config.c' || echo '$(srcdir)/'`src/core/nm-ip-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-ip-config.c' object='src/core/libNetworkManagerBase_la-nm-ip-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-ip-config.lo `test -f 'src/nm-ip-config.c' || echo '$(srcdir)/'`src/nm-ip-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-ip-config.lo `test -f 'src/core/nm-ip-config.c' || echo '$(srcdir)/'`src/core/nm-ip-config.c -src/libNetworkManagerBase_la-nm-ip4-config.lo: src/nm-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-ip4-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Tpo -c -o src/libNetworkManagerBase_la-nm-ip4-config.lo `test -f 'src/nm-ip4-config.c' || echo '$(srcdir)/'`src/nm-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-ip4-config.c' object='src/libNetworkManagerBase_la-nm-ip4-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-ip4-config.lo: src/core/nm-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-ip4-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Tpo -c -o src/core/libNetworkManagerBase_la-nm-ip4-config.lo `test -f 'src/core/nm-ip4-config.c' || echo '$(srcdir)/'`src/core/nm-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-ip4-config.c' object='src/core/libNetworkManagerBase_la-nm-ip4-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-ip4-config.lo `test -f 'src/nm-ip4-config.c' || echo '$(srcdir)/'`src/nm-ip4-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-ip4-config.lo `test -f 'src/core/nm-ip4-config.c' || echo '$(srcdir)/'`src/core/nm-ip4-config.c -src/libNetworkManagerBase_la-nm-ip6-config.lo: src/nm-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-nm-ip6-config.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Tpo -c -o src/libNetworkManagerBase_la-nm-ip6-config.lo `test -f 'src/nm-ip6-config.c' || echo '$(srcdir)/'`src/nm-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-ip6-config.c' object='src/libNetworkManagerBase_la-nm-ip6-config.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-nm-ip6-config.lo: src/core/nm-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-nm-ip6-config.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Tpo -c -o src/core/libNetworkManagerBase_la-nm-ip6-config.lo `test -f 'src/core/nm-ip6-config.c' || echo '$(srcdir)/'`src/core/nm-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-ip6-config.c' object='src/core/libNetworkManagerBase_la-nm-ip6-config.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-nm-ip6-config.lo `test -f 'src/nm-ip6-config.c' || echo '$(srcdir)/'`src/nm-ip6-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-nm-ip6-config.lo `test -f 'src/core/nm-ip6-config.c' || echo '$(srcdir)/'`src/core/nm-ip6-config.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo: src/dhcp/nm-dhcp-client.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo `test -f 'src/dhcp/nm-dhcp-client.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-client.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-client.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo: src/core/dhcp/nm-dhcp-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo `test -f 'src/core/dhcp/nm-dhcp-client.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-client.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo `test -f 'src/dhcp/nm-dhcp-client.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-client.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-client.lo `test -f 'src/core/dhcp/nm-dhcp-client.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-client.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo: src/dhcp/nm-dhcp-nettools.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo `test -f 'src/dhcp/nm-dhcp-nettools.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-nettools.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-nettools.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo: src/core/dhcp/nm-dhcp-nettools.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo `test -f 'src/core/dhcp/nm-dhcp-nettools.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-nettools.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-nettools.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo `test -f 'src/dhcp/nm-dhcp-nettools.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-nettools.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-nettools.lo `test -f 'src/core/dhcp/nm-dhcp-nettools.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-nettools.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo: src/dhcp/nm-dhcp-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo `test -f 'src/dhcp/nm-dhcp-utils.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-utils.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo: src/core/dhcp/nm-dhcp-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo `test -f 'src/core/dhcp/nm-dhcp-utils.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-utils.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo `test -f 'src/dhcp/nm-dhcp-utils.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-utils.lo `test -f 'src/core/dhcp/nm-dhcp-utils.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-utils.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo: src/dhcp/nm-dhcp-options.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo `test -f 'src/dhcp/nm-dhcp-options.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-options.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-options.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo: src/core/dhcp/nm-dhcp-options.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo `test -f 'src/core/dhcp/nm-dhcp-options.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-options.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-options.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo `test -f 'src/dhcp/nm-dhcp-options.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-options.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-options.lo `test -f 'src/core/dhcp/nm-dhcp-options.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-options.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo: src/dhcp/nm-dhcp-systemd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo `test -f 'src/dhcp/nm-dhcp-systemd.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-systemd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-systemd.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo: src/core/dhcp/nm-dhcp-systemd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo `test -f 'src/core/dhcp/nm-dhcp-systemd.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-systemd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-systemd.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo `test -f 'src/dhcp/nm-dhcp-systemd.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-systemd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-systemd.lo `test -f 'src/core/dhcp/nm-dhcp-systemd.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-systemd.c -src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo: src/dhcp/nm-dhcp-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo -MD -MP -MF src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Tpo -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo `test -f 'src/dhcp/nm-dhcp-manager.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Tpo src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-manager.c' object='src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo: src/core/dhcp/nm-dhcp-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo -MD -MP -MF src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Tpo -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo `test -f 'src/core/dhcp/nm-dhcp-manager.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Tpo src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-manager.c' object='src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo `test -f 'src/dhcp/nm-dhcp-manager.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/libNetworkManagerBase_la-nm-dhcp-manager.lo `test -f 'src/core/dhcp/nm-dhcp-manager.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-manager.c -src/libNetworkManagerBase_la-main-utils.lo: src/main-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/libNetworkManagerBase_la-main-utils.lo -MD -MP -MF src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Tpo -c -o src/libNetworkManagerBase_la-main-utils.lo `test -f 'src/main-utils.c' || echo '$(srcdir)/'`src/main-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Tpo src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main-utils.c' object='src/libNetworkManagerBase_la-main-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/libNetworkManagerBase_la-main-utils.lo: src/core/main-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/libNetworkManagerBase_la-main-utils.lo -MD -MP -MF src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Tpo -c -o src/core/libNetworkManagerBase_la-main-utils.lo `test -f 'src/core/main-utils.c' || echo '$(srcdir)/'`src/core/main-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Tpo src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/main-utils.c' object='src/core/libNetworkManagerBase_la-main-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/libNetworkManagerBase_la-main-utils.lo `test -f 'src/main-utils.c' || echo '$(srcdir)/'`src/main-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/libNetworkManagerBase_la-main-utils.lo `test -f 'src/core/main-utils.c' || echo '$(srcdir)/'`src/core/main-utils.c -src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo: src/platform/wifi/nm-wifi-utils-wext.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo -MD -MP -MF src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Tpo -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo `test -f 'src/platform/wifi/nm-wifi-utils-wext.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils-wext.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Tpo src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/wifi/nm-wifi-utils-wext.c' object='src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo: src/core/platform/wifi/nm-wifi-utils-wext.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo -MD -MP -MF src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Tpo -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo `test -f 'src/core/platform/wifi/nm-wifi-utils-wext.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils-wext.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Tpo src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/wifi/nm-wifi-utils-wext.c' object='src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo `test -f 'src/platform/wifi/nm-wifi-utils-wext.c' || echo '$(srcdir)/'`src/platform/wifi/nm-wifi-utils-wext.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerBase_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/wifi/libNetworkManagerBase_la-nm-wifi-utils-wext.lo `test -f 'src/core/platform/wifi/nm-wifi-utils-wext.c' || echo '$(srcdir)/'`src/core/platform/wifi/nm-wifi-utils-wext.c -src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo: src/ndisc/nm-fake-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo -MD -MP -MF src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Tpo -c -o src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo `test -f 'src/ndisc/nm-fake-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-fake-ndisc.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Tpo src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/nm-fake-ndisc.c' object='src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo: src/core/ndisc/nm-fake-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo -MD -MP -MF src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Tpo -c -o src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo `test -f 'src/core/ndisc/nm-fake-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-fake-ndisc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Tpo src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/nm-fake-ndisc.c' object='src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo `test -f 'src/ndisc/nm-fake-ndisc.c' || echo '$(srcdir)/'`src/ndisc/nm-fake-ndisc.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/libNetworkManagerTest_la-nm-fake-ndisc.lo `test -f 'src/core/ndisc/nm-fake-ndisc.c' || echo '$(srcdir)/'`src/core/ndisc/nm-fake-ndisc.c -src/platform/libNetworkManagerTest_la-nm-fake-platform.lo: src/platform/nm-fake-platform.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/libNetworkManagerTest_la-nm-fake-platform.lo -MD -MP -MF src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Tpo -c -o src/platform/libNetworkManagerTest_la-nm-fake-platform.lo `test -f 'src/platform/nm-fake-platform.c' || echo '$(srcdir)/'`src/platform/nm-fake-platform.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Tpo src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/nm-fake-platform.c' object='src/platform/libNetworkManagerTest_la-nm-fake-platform.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo: src/core/platform/nm-fake-platform.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo -MD -MP -MF src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Tpo -c -o src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo `test -f 'src/core/platform/nm-fake-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-fake-platform.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Tpo src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/nm-fake-platform.c' object='src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/libNetworkManagerTest_la-nm-fake-platform.lo `test -f 'src/platform/nm-fake-platform.c' || echo '$(srcdir)/'`src/platform/nm-fake-platform.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/libNetworkManagerTest_la-nm-fake-platform.lo `test -f 'src/core/platform/nm-fake-platform.c' || echo '$(srcdir)/'`src/core/platform/nm-fake-platform.c -src/platform/tests/libNetworkManagerTest_la-test-common.lo: src/platform/tests/test-common.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/libNetworkManagerTest_la-test-common.lo -MD -MP -MF src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Tpo -c -o src/platform/tests/libNetworkManagerTest_la-test-common.lo `test -f 'src/platform/tests/test-common.c' || echo '$(srcdir)/'`src/platform/tests/test-common.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Tpo src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-common.c' object='src/platform/tests/libNetworkManagerTest_la-test-common.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/platform/tests/libNetworkManagerTest_la-test-common.lo: src/core/platform/tests/test-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/libNetworkManagerTest_la-test-common.lo -MD -MP -MF src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Tpo -c -o src/core/platform/tests/libNetworkManagerTest_la-test-common.lo `test -f 'src/core/platform/tests/test-common.c' || echo '$(srcdir)/'`src/core/platform/tests/test-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Tpo src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-common.c' object='src/core/platform/tests/libNetworkManagerTest_la-test-common.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/libNetworkManagerTest_la-test-common.lo `test -f 'src/platform/tests/test-common.c' || echo '$(srcdir)/'`src/platform/tests/test-common.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libNetworkManagerTest_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/libNetworkManagerTest_la-test-common.lo `test -f 'src/core/platform/tests/test-common.c' || echo '$(srcdir)/'`src/core/platform/tests/test-common.c -src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo: src/systemd/nm-sd-utils-core.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo -MD -MP -MF src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Tpo -c -o src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo `test -f 'src/systemd/nm-sd-utils-core.c' || echo '$(srcdir)/'`src/systemd/nm-sd-utils-core.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Tpo src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/nm-sd-utils-core.c' object='src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo: src/core/systemd/nm-sd-utils-core.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo -MD -MP -MF src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Tpo -c -o src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo `test -f 'src/core/systemd/nm-sd-utils-core.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd-utils-core.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Tpo src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/nm-sd-utils-core.c' object='src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo `test -f 'src/systemd/nm-sd-utils-core.c' || echo '$(srcdir)/'`src/systemd/nm-sd-utils-core.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/libnm_systemd_core_la-nm-sd-utils-core.lo `test -f 'src/core/systemd/nm-sd-utils-core.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd-utils-core.c -src/systemd/libnm_systemd_core_la-nm-sd.lo: src/systemd/nm-sd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/libnm_systemd_core_la-nm-sd.lo -MD -MP -MF src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Tpo -c -o src/systemd/libnm_systemd_core_la-nm-sd.lo `test -f 'src/systemd/nm-sd.c' || echo '$(srcdir)/'`src/systemd/nm-sd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Tpo src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/nm-sd.c' object='src/systemd/libnm_systemd_core_la-nm-sd.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/libnm_systemd_core_la-nm-sd.lo: src/core/systemd/nm-sd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/libnm_systemd_core_la-nm-sd.lo -MD -MP -MF src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Tpo -c -o src/core/systemd/libnm_systemd_core_la-nm-sd.lo `test -f 'src/core/systemd/nm-sd.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Tpo src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/nm-sd.c' object='src/core/systemd/libnm_systemd_core_la-nm-sd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/libnm_systemd_core_la-nm-sd.lo `test -f 'src/systemd/nm-sd.c' || echo '$(srcdir)/'`src/systemd/nm-sd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/libnm_systemd_core_la-nm-sd.lo `test -f 'src/core/systemd/nm-sd.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd.c -src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo: src/systemd/nm-sd-utils-dhcp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo -MD -MP -MF src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Tpo -c -o src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo `test -f 'src/systemd/nm-sd-utils-dhcp.c' || echo '$(srcdir)/'`src/systemd/nm-sd-utils-dhcp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Tpo src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/nm-sd-utils-dhcp.c' object='src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo: src/core/systemd/nm-sd-utils-dhcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo -MD -MP -MF src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Tpo -c -o src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo `test -f 'src/core/systemd/nm-sd-utils-dhcp.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd-utils-dhcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Tpo src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/nm-sd-utils-dhcp.c' object='src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo `test -f 'src/systemd/nm-sd-utils-dhcp.c' || echo '$(srcdir)/'`src/systemd/nm-sd-utils-dhcp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/libnm_systemd_core_la-nm-sd-utils-dhcp.lo `test -f 'src/core/systemd/nm-sd-utils-dhcp.c' || echo '$(srcdir)/'`src/core/systemd/nm-sd-utils-dhcp.c -src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo: src/systemd/sd-adapt-core/nm-sd-adapt-core.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo -MD -MP -MF src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Tpo -c -o src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo `test -f 'src/systemd/sd-adapt-core/nm-sd-adapt-core.c' || echo '$(srcdir)/'`src/systemd/sd-adapt-core/nm-sd-adapt-core.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Tpo src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/sd-adapt-core/nm-sd-adapt-core.c' object='src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo: src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo -MD -MP -MF src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Tpo -c -o src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo `test -f 'src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c' || echo '$(srcdir)/'`src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Tpo src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c' object='src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo `test -f 'src/systemd/sd-adapt-core/nm-sd-adapt-core.c' || echo '$(srcdir)/'`src/systemd/sd-adapt-core/nm-sd-adapt-core.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/sd-adapt-core/libnm_systemd_core_la-nm-sd-adapt-core.lo `test -f 'src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c' || echo '$(srcdir)/'`src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo: src/systemd/src/libsystemd-network/arp-util.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo `test -f 'src/systemd/src/libsystemd-network/arp-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/arp-util.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/arp-util.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo: src/core/systemd/src/libsystemd-network/arp-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo `test -f 'src/core/systemd/src/libsystemd-network/arp-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/arp-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/arp-util.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo `test -f 'src/systemd/src/libsystemd-network/arp-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/arp-util.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-arp-util.lo `test -f 'src/core/systemd/src/libsystemd-network/arp-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/arp-util.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo: src/systemd/src/libsystemd-network/dhcp-identifier.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-identifier.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-identifier.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp-identifier.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo: src/core/systemd/src/libsystemd-network/dhcp-identifier.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-identifier.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-identifier.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp-identifier.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-identifier.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-identifier.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-identifier.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-identifier.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-identifier.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo: src/systemd/src/libsystemd-network/dhcp-network.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-network.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp-network.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo: src/core/systemd/src/libsystemd-network/dhcp-network.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-network.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp-network.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-network.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-network.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-network.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo: src/systemd/src/libsystemd-network/dhcp-option.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-option.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-option.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp-option.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo: src/core/systemd/src/libsystemd-network/dhcp-option.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-option.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-option.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp-option.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-option.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-option.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-option.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-option.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-option.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo: src/systemd/src/libsystemd-network/dhcp-packet.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-packet.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-packet.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp-packet.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo: src/core/systemd/src/libsystemd-network/dhcp-packet.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-packet.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-packet.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp-packet.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo `test -f 'src/systemd/src/libsystemd-network/dhcp-packet.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp-packet.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp-packet.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp-packet.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp-packet.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo: src/systemd/src/libsystemd-network/dhcp6-network.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo `test -f 'src/systemd/src/libsystemd-network/dhcp6-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp6-network.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp6-network.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo: src/core/systemd/src/libsystemd-network/dhcp6-network.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp6-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp6-network.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp6-network.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo `test -f 'src/systemd/src/libsystemd-network/dhcp6-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp6-network.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-network.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp6-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp6-network.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo: src/systemd/src/libsystemd-network/dhcp6-option.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo `test -f 'src/systemd/src/libsystemd-network/dhcp6-option.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp6-option.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/dhcp6-option.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo: src/core/systemd/src/libsystemd-network/dhcp6-option.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp6-option.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp6-option.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/dhcp6-option.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo `test -f 'src/systemd/src/libsystemd-network/dhcp6-option.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/dhcp6-option.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-dhcp6-option.lo `test -f 'src/core/systemd/src/libsystemd-network/dhcp6-option.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/dhcp6-option.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo: src/systemd/src/libsystemd-network/lldp-neighbor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo `test -f 'src/systemd/src/libsystemd-network/lldp-neighbor.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/lldp-neighbor.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/lldp-neighbor.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo: src/core/systemd/src/libsystemd-network/lldp-neighbor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo `test -f 'src/core/systemd/src/libsystemd-network/lldp-neighbor.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/lldp-neighbor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/lldp-neighbor.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo `test -f 'src/systemd/src/libsystemd-network/lldp-neighbor.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/lldp-neighbor.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-neighbor.lo `test -f 'src/core/systemd/src/libsystemd-network/lldp-neighbor.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/lldp-neighbor.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo: src/systemd/src/libsystemd-network/lldp-network.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo `test -f 'src/systemd/src/libsystemd-network/lldp-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/lldp-network.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/lldp-network.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo: src/core/systemd/src/libsystemd-network/lldp-network.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo `test -f 'src/core/systemd/src/libsystemd-network/lldp-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/lldp-network.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/lldp-network.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo `test -f 'src/systemd/src/libsystemd-network/lldp-network.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/lldp-network.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-lldp-network.lo `test -f 'src/core/systemd/src/libsystemd-network/lldp-network.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/lldp-network.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo: src/systemd/src/libsystemd-network/network-internal.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo `test -f 'src/systemd/src/libsystemd-network/network-internal.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/network-internal.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/network-internal.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo: src/core/systemd/src/libsystemd-network/network-internal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo `test -f 'src/core/systemd/src/libsystemd-network/network-internal.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/network-internal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/network-internal.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo `test -f 'src/systemd/src/libsystemd-network/network-internal.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/network-internal.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-network-internal.lo `test -f 'src/core/systemd/src/libsystemd-network/network-internal.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/network-internal.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo: src/systemd/src/libsystemd-network/sd-dhcp-client.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp-client.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp-client.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-dhcp-client.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo: src/core/systemd/src/libsystemd-network/sd-dhcp-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp-client.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-dhcp-client.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp-client.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp-client.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-client.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp-client.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp-client.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo: src/systemd/src/libsystemd-network/sd-dhcp-lease.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp-lease.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp-lease.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-dhcp-lease.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo: src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp-lease.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp-lease.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp-lease.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo: src/systemd/src/libsystemd-network/sd-dhcp6-client.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp6-client.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp6-client.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-dhcp6-client.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo: src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp6-client.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp6-client.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-client.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo: src/systemd/src/libsystemd-network/sd-dhcp6-lease.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp6-lease.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp6-lease.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-dhcp6-lease.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo: src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo `test -f 'src/systemd/src/libsystemd-network/sd-dhcp6-lease.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-dhcp6-lease.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-dhcp6-lease.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo: src/systemd/src/libsystemd-network/sd-ipv4acd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo `test -f 'src/systemd/src/libsystemd-network/sd-ipv4acd.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-ipv4acd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-ipv4acd.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo: src/core/systemd/src/libsystemd-network/sd-ipv4acd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-ipv4acd.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-ipv4acd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-ipv4acd.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo `test -f 'src/systemd/src/libsystemd-network/sd-ipv4acd.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-ipv4acd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4acd.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-ipv4acd.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-ipv4acd.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo: src/systemd/src/libsystemd-network/sd-ipv4ll.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo `test -f 'src/systemd/src/libsystemd-network/sd-ipv4ll.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-ipv4ll.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-ipv4ll.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo: src/core/systemd/src/libsystemd-network/sd-ipv4ll.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-ipv4ll.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-ipv4ll.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-ipv4ll.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo `test -f 'src/systemd/src/libsystemd-network/sd-ipv4ll.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-ipv4ll.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-ipv4ll.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-ipv4ll.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-ipv4ll.c -src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo: src/systemd/src/libsystemd-network/sd-lldp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo -MD -MP -MF src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Tpo -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo `test -f 'src/systemd/src/libsystemd-network/sd-lldp.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-lldp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Tpo src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd-network/sd-lldp.c' object='src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo: src/core/systemd/src/libsystemd-network/sd-lldp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo -MD -MP -MF src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Tpo -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-lldp.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-lldp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Tpo src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd-network/sd-lldp.c' object='src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo `test -f 'src/systemd/src/libsystemd-network/sd-lldp.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd-network/sd-lldp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd-network/libnm_systemd_core_la-sd-lldp.lo `test -f 'src/core/systemd/src/libsystemd-network/sd-lldp.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd-network/sd-lldp.c -src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo: src/systemd/src/libsystemd/sd-event/event-util.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo -MD -MP -MF src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Tpo -c -o src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo `test -f 'src/systemd/src/libsystemd/sd-event/event-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-event/event-util.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Tpo src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd/sd-event/event-util.c' object='src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo: src/core/systemd/src/libsystemd/sd-event/event-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo -MD -MP -MF src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Tpo -c -o src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo `test -f 'src/core/systemd/src/libsystemd/sd-event/event-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-event/event-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Tpo src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd/sd-event/event-util.c' object='src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo `test -f 'src/systemd/src/libsystemd/sd-event/event-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-event/event-util.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-event-util.lo `test -f 'src/core/systemd/src/libsystemd/sd-event/event-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-event/event-util.c -src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo: src/systemd/src/libsystemd/sd-event/sd-event.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo -MD -MP -MF src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Tpo -c -o src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo `test -f 'src/systemd/src/libsystemd/sd-event/sd-event.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-event/sd-event.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Tpo src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd/sd-event/sd-event.c' object='src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo: src/core/systemd/src/libsystemd/sd-event/sd-event.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo -MD -MP -MF src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Tpo -c -o src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo `test -f 'src/core/systemd/src/libsystemd/sd-event/sd-event.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-event/sd-event.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Tpo src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd/sd-event/sd-event.c' object='src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo `test -f 'src/systemd/src/libsystemd/sd-event/sd-event.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-event/sd-event.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd/sd-event/libnm_systemd_core_la-sd-event.lo `test -f 'src/core/systemd/src/libsystemd/sd-event/sd-event.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-event/sd-event.c -src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo: src/systemd/src/libsystemd/sd-id128/id128-util.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo -MD -MP -MF src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Tpo -c -o src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo `test -f 'src/systemd/src/libsystemd/sd-id128/id128-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-id128/id128-util.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Tpo src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd/sd-id128/id128-util.c' object='src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo: src/core/systemd/src/libsystemd/sd-id128/id128-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo -MD -MP -MF src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Tpo -c -o src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo `test -f 'src/core/systemd/src/libsystemd/sd-id128/id128-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-id128/id128-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Tpo src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd/sd-id128/id128-util.c' object='src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo `test -f 'src/systemd/src/libsystemd/sd-id128/id128-util.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-id128/id128-util.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-id128-util.lo `test -f 'src/core/systemd/src/libsystemd/sd-id128/id128-util.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-id128/id128-util.c -src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo: src/systemd/src/libsystemd/sd-id128/sd-id128.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo -MD -MP -MF src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Tpo -c -o src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo `test -f 'src/systemd/src/libsystemd/sd-id128/sd-id128.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-id128/sd-id128.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Tpo src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/systemd/src/libsystemd/sd-id128/sd-id128.c' object='src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo: src/core/systemd/src/libsystemd/sd-id128/sd-id128.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo -MD -MP -MF src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Tpo -c -o src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo `test -f 'src/core/systemd/src/libsystemd/sd-id128/sd-id128.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-id128/sd-id128.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Tpo src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/systemd/src/libsystemd/sd-id128/sd-id128.c' object='src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo `test -f 'src/systemd/src/libsystemd/sd-id128/sd-id128.c' || echo '$(srcdir)/'`src/systemd/src/libsystemd/sd-id128/sd-id128.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_libnm_systemd_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/systemd/src/libsystemd/sd-id128/libnm_systemd_core_la-sd-id128.lo `test -f 'src/core/systemd/src/libsystemd/sd-id128/sd-id128.c' || echo '$(srcdir)/'`src/core/systemd/src/libsystemd/sd-id128/sd-id128.c -src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo: src/ppp/nm-ppp-manager.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ppp_libnm_ppp_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo -MD -MP -MF src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Tpo -c -o src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo `test -f 'src/ppp/nm-ppp-manager.c' || echo '$(srcdir)/'`src/ppp/nm-ppp-manager.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Tpo src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ppp/nm-ppp-manager.c' object='src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo: src/core/ppp/nm-ppp-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ppp_libnm_ppp_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo -MD -MP -MF src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Tpo -c -o src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo `test -f 'src/core/ppp/nm-ppp-manager.c' || echo '$(srcdir)/'`src/core/ppp/nm-ppp-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Tpo src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ppp/nm-ppp-manager.c' object='src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ppp_libnm_ppp_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo `test -f 'src/ppp/nm-ppp-manager.c' || echo '$(srcdir)/'`src/ppp/nm-ppp-manager.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ppp_libnm_ppp_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ppp/libnm_ppp_plugin_la-nm-ppp-manager.lo `test -f 'src/core/ppp/nm-ppp-manager.c' || echo '$(srcdir)/'`src/core/ppp/nm-ppp-manager.c -src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo: src/ppp/nm-pppd-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ppp_nm_pppd_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo -MD -MP -MF src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Tpo -c -o src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo `test -f 'src/ppp/nm-pppd-plugin.c' || echo '$(srcdir)/'`src/ppp/nm-pppd-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Tpo src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ppp/nm-pppd-plugin.c' object='src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo: src/core/ppp/nm-pppd-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ppp_nm_pppd_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo -MD -MP -MF src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Tpo -c -o src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo `test -f 'src/core/ppp/nm-pppd-plugin.c' || echo '$(srcdir)/'`src/core/ppp/nm-pppd-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Tpo src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ppp/nm-pppd-plugin.c' object='src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ppp_nm_pppd_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo `test -f 'src/ppp/nm-pppd-plugin.c' || echo '$(srcdir)/'`src/ppp/nm-pppd-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ppp_nm_pppd_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ppp/nm_pppd_plugin_la-nm-pppd-plugin.lo `test -f 'src/core/ppp/nm-pppd-plugin.c' || echo '$(srcdir)/'`src/core/ppp/nm-pppd-plugin.c -src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo: src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Tpo -c -o src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' object='src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo: src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' object='src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c -src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo: src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Tpo -c -o src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' object='src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo: src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' object='src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c -src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo: src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Tpo -c -o src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo `test -f 'src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' object='src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo: src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' object='src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo `test -f 'src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnmdbus_ifcfg_rh_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo: src/settings/plugins/ifcfg-rh/shvar.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Tpo -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo `test -f 'src/settings/plugins/ifcfg-rh/shvar.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/shvar.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/shvar.c' object='src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo: src/core/settings/plugins/ifcfg-rh/shvar.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo `test -f 'src/core/settings/plugins/ifcfg-rh/shvar.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/shvar.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/shvar.c' object='src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo `test -f 'src/settings/plugins/ifcfg-rh/shvar.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/shvar.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-shvar.lo `test -f 'src/core/settings/plugins/ifcfg-rh/shvar.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/shvar.c -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo: src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Tpo -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' object='src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo: src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' object='src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo: src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Tpo -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' object='src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo: src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' object='src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c -src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo: src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo -MD -MP -MF src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Tpo -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Tpo src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' object='src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo: src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo -MD -MP -MF src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Tpo -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Tpo src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' object='src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo `test -f 'src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.lo `test -f 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c -src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo: src/settings/plugins/ifupdown/nms-ifupdown-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo -MD -MP -MF src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Tpo -c -o src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-plugin.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Tpo src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifupdown/nms-ifupdown-plugin.c' object='src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo: src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo -MD -MP -MF src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Tpo -c -o src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Tpo src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c' object='src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-plugin.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-plugin.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifupdown/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c -src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo: src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo -MD -MP -MF src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Tpo -c -o src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Tpo src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' object='src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo: src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo -MD -MP -MF src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Tpo -c -o src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Tpo src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' object='src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c -src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo: src/settings/plugins/ifupdown/nms-ifupdown-parser.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo -MD -MP -MF src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Tpo -c -o src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-parser.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-parser.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Tpo src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifupdown/nms-ifupdown-parser.c' object='src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo' libtool=yes @AMDEPBACKSLASH@ +src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo: src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo -MD -MP -MF src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Tpo -c -o src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Tpo src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c' object='src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo `test -f 'src/settings/plugins/ifupdown/nms-ifupdown-parser.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/nms-ifupdown-parser.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifupdown/libnms_ifupdown_core_la-nms-ifupdown-parser.lo `test -f 'src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c clients/cli/generate_docs_nm_settings_nmcli-generate-docs-nm-settings-nmcli.o: clients/cli/generate-docs-nm-settings-nmcli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(clients_cli_generate_docs_nm_settings_nmcli_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT clients/cli/generate_docs_nm_settings_nmcli-generate-docs-nm-settings-nmcli.o -MD -MP -MF clients/cli/$(DEPDIR)/generate_docs_nm_settings_nmcli-generate-docs-nm-settings-nmcli.Tpo -c -o clients/cli/generate_docs_nm_settings_nmcli-generate-docs-nm-settings-nmcli.o `test -f 'clients/cli/generate-docs-nm-settings-nmcli.c' || echo '$(srcdir)/'`clients/cli/generate-docs-nm-settings-nmcli.c @@ -16635,663 +16730,663 @@ shared/nm-platform/tests/test_nm_platform-test-nm-platform.obj: shared/nm-platfo @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(shared_nm_platform_tests_test_nm_platform_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o shared/nm-platform/tests/test_nm_platform-test-nm-platform.obj `if test -f 'shared/nm-platform/tests/test-nm-platform.c'; then $(CYGPATH_W) 'shared/nm-platform/tests/test-nm-platform.c'; else $(CYGPATH_W) '$(srcdir)/shared/nm-platform/tests/test-nm-platform.c'; fi` -src/NetworkManager-main.o: src/main.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/NetworkManager-main.o -MD -MP -MF src/$(DEPDIR)/NetworkManager-main.Tpo -c -o src/NetworkManager-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/NetworkManager-main.Tpo src/$(DEPDIR)/NetworkManager-main.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/NetworkManager-main.o' libtool=no @AMDEPBACKSLASH@ +src/core/NetworkManager-main.o: src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/NetworkManager-main.o -MD -MP -MF src/core/$(DEPDIR)/NetworkManager-main.Tpo -c -o src/core/NetworkManager-main.o `test -f 'src/core/main.c' || echo '$(srcdir)/'`src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/NetworkManager-main.Tpo src/core/$(DEPDIR)/NetworkManager-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/main.c' object='src/core/NetworkManager-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/NetworkManager-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/NetworkManager-main.o `test -f 'src/core/main.c' || echo '$(srcdir)/'`src/core/main.c -src/NetworkManager-main.obj: src/main.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/NetworkManager-main.obj -MD -MP -MF src/$(DEPDIR)/NetworkManager-main.Tpo -c -o src/NetworkManager-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/NetworkManager-main.Tpo src/$(DEPDIR)/NetworkManager-main.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/NetworkManager-main.obj' libtool=no @AMDEPBACKSLASH@ +src/core/NetworkManager-main.obj: src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/NetworkManager-main.obj -MD -MP -MF src/core/$(DEPDIR)/NetworkManager-main.Tpo -c -o src/core/NetworkManager-main.obj `if test -f 'src/core/main.c'; then $(CYGPATH_W) 'src/core/main.c'; else $(CYGPATH_W) '$(srcdir)/src/core/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/NetworkManager-main.Tpo src/core/$(DEPDIR)/NetworkManager-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/main.c' object='src/core/NetworkManager-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/NetworkManager-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/NetworkManager-main.obj `if test -f 'src/core/main.c'; then $(CYGPATH_W) 'src/core/main.c'; else $(CYGPATH_W) '$(srcdir)/src/core/main.c'; fi` -src/NetworkManager_all_sym-main.o: src/main.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/NetworkManager_all_sym-main.o -MD -MP -MF src/$(DEPDIR)/NetworkManager_all_sym-main.Tpo -c -o src/NetworkManager_all_sym-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/NetworkManager_all_sym-main.Tpo src/$(DEPDIR)/NetworkManager_all_sym-main.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/NetworkManager_all_sym-main.o' libtool=no @AMDEPBACKSLASH@ +src/core/NetworkManager_all_sym-main.o: src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/NetworkManager_all_sym-main.o -MD -MP -MF src/core/$(DEPDIR)/NetworkManager_all_sym-main.Tpo -c -o src/core/NetworkManager_all_sym-main.o `test -f 'src/core/main.c' || echo '$(srcdir)/'`src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/NetworkManager_all_sym-main.Tpo src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/main.c' object='src/core/NetworkManager_all_sym-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/NetworkManager_all_sym-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/NetworkManager_all_sym-main.o `test -f 'src/core/main.c' || echo '$(srcdir)/'`src/core/main.c -src/NetworkManager_all_sym-main.obj: src/main.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/NetworkManager_all_sym-main.obj -MD -MP -MF src/$(DEPDIR)/NetworkManager_all_sym-main.Tpo -c -o src/NetworkManager_all_sym-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/NetworkManager_all_sym-main.Tpo src/$(DEPDIR)/NetworkManager_all_sym-main.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/NetworkManager_all_sym-main.obj' libtool=no @AMDEPBACKSLASH@ +src/core/NetworkManager_all_sym-main.obj: src/core/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/NetworkManager_all_sym-main.obj -MD -MP -MF src/core/$(DEPDIR)/NetworkManager_all_sym-main.Tpo -c -o src/core/NetworkManager_all_sym-main.obj `if test -f 'src/core/main.c'; then $(CYGPATH_W) 'src/core/main.c'; else $(CYGPATH_W) '$(srcdir)/src/core/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/NetworkManager_all_sym-main.Tpo src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/main.c' object='src/core/NetworkManager_all_sym-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/NetworkManager_all_sym-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_NetworkManager_all_sym_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/NetworkManager_all_sym-main.obj `if test -f 'src/core/main.c'; then $(CYGPATH_W) 'src/core/main.c'; else $(CYGPATH_W) '$(srcdir)/src/core/main.c'; fi` -src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o: src/devices/bluetooth/tests/nm-bt-test.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o -MD -MP -MF src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo -c -o src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o `test -f 'src/devices/bluetooth/tests/nm-bt-test.c' || echo '$(srcdir)/'`src/devices/bluetooth/tests/nm-bt-test.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/tests/nm-bt-test.c' object='src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o: src/core/devices/bluetooth/tests/nm-bt-test.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o -MD -MP -MF src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo -c -o src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o `test -f 'src/core/devices/bluetooth/tests/nm-bt-test.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/tests/nm-bt-test.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/tests/nm-bt-test.c' object='src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o `test -f 'src/devices/bluetooth/tests/nm-bt-test.c' || echo '$(srcdir)/'`src/devices/bluetooth/tests/nm-bt-test.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.o `test -f 'src/core/devices/bluetooth/tests/nm-bt-test.c' || echo '$(srcdir)/'`src/core/devices/bluetooth/tests/nm-bt-test.c -src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj: src/devices/bluetooth/tests/nm-bt-test.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj -MD -MP -MF src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo -c -o src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj `if test -f 'src/devices/bluetooth/tests/nm-bt-test.c'; then $(CYGPATH_W) 'src/devices/bluetooth/tests/nm-bt-test.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/bluetooth/tests/nm-bt-test.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/bluetooth/tests/nm-bt-test.c' object='src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj: src/core/devices/bluetooth/tests/nm-bt-test.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj -MD -MP -MF src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo -c -o src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj `if test -f 'src/core/devices/bluetooth/tests/nm-bt-test.c'; then $(CYGPATH_W) 'src/core/devices/bluetooth/tests/nm-bt-test.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/bluetooth/tests/nm-bt-test.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Tpo src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/bluetooth/tests/nm-bt-test.c' object='src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj `if test -f 'src/devices/bluetooth/tests/nm-bt-test.c'; then $(CYGPATH_W) 'src/devices/bluetooth/tests/nm-bt-test.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/bluetooth/tests/nm-bt-test.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_bluetooth_tests_nm_bt_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/bluetooth/tests/nm_bt_test-nm-bt-test.obj `if test -f 'src/core/devices/bluetooth/tests/nm-bt-test.c'; then $(CYGPATH_W) 'src/core/devices/bluetooth/tests/nm-bt-test.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/bluetooth/tests/nm-bt-test.c'; fi` -src/devices/tests/test_acd-test-acd.o: src/devices/tests/test-acd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/tests/test_acd-test-acd.o -MD -MP -MF src/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo -c -o src/devices/tests/test_acd-test-acd.o `test -f 'src/devices/tests/test-acd.c' || echo '$(srcdir)/'`src/devices/tests/test-acd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/tests/test-acd.c' object='src/devices/tests/test_acd-test-acd.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/tests/test_acd-test-acd.o: src/core/devices/tests/test-acd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/tests/test_acd-test-acd.o -MD -MP -MF src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo -c -o src/core/devices/tests/test_acd-test-acd.o `test -f 'src/core/devices/tests/test-acd.c' || echo '$(srcdir)/'`src/core/devices/tests/test-acd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/tests/test-acd.c' object='src/core/devices/tests/test_acd-test-acd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/tests/test_acd-test-acd.o `test -f 'src/devices/tests/test-acd.c' || echo '$(srcdir)/'`src/devices/tests/test-acd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/tests/test_acd-test-acd.o `test -f 'src/core/devices/tests/test-acd.c' || echo '$(srcdir)/'`src/core/devices/tests/test-acd.c -src/devices/tests/test_acd-test-acd.obj: src/devices/tests/test-acd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/tests/test_acd-test-acd.obj -MD -MP -MF src/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo -c -o src/devices/tests/test_acd-test-acd.obj `if test -f 'src/devices/tests/test-acd.c'; then $(CYGPATH_W) 'src/devices/tests/test-acd.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/tests/test-acd.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/tests/test-acd.c' object='src/devices/tests/test_acd-test-acd.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/tests/test_acd-test-acd.obj: src/core/devices/tests/test-acd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/tests/test_acd-test-acd.obj -MD -MP -MF src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo -c -o src/core/devices/tests/test_acd-test-acd.obj `if test -f 'src/core/devices/tests/test-acd.c'; then $(CYGPATH_W) 'src/core/devices/tests/test-acd.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/tests/test-acd.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Tpo src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/tests/test-acd.c' object='src/core/devices/tests/test_acd-test-acd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/tests/test_acd-test-acd.obj `if test -f 'src/devices/tests/test-acd.c'; then $(CYGPATH_W) 'src/devices/tests/test-acd.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/tests/test-acd.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_acd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/tests/test_acd-test-acd.obj `if test -f 'src/core/devices/tests/test-acd.c'; then $(CYGPATH_W) 'src/core/devices/tests/test-acd.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/tests/test-acd.c'; fi` -src/devices/tests/test_lldp-test-lldp.o: src/devices/tests/test-lldp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/tests/test_lldp-test-lldp.o -MD -MP -MF src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo -c -o src/devices/tests/test_lldp-test-lldp.o `test -f 'src/devices/tests/test-lldp.c' || echo '$(srcdir)/'`src/devices/tests/test-lldp.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/tests/test-lldp.c' object='src/devices/tests/test_lldp-test-lldp.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/tests/test_lldp-test-lldp.o: src/core/devices/tests/test-lldp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/tests/test_lldp-test-lldp.o -MD -MP -MF src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo -c -o src/core/devices/tests/test_lldp-test-lldp.o `test -f 'src/core/devices/tests/test-lldp.c' || echo '$(srcdir)/'`src/core/devices/tests/test-lldp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/tests/test-lldp.c' object='src/core/devices/tests/test_lldp-test-lldp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/tests/test_lldp-test-lldp.o `test -f 'src/devices/tests/test-lldp.c' || echo '$(srcdir)/'`src/devices/tests/test-lldp.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/tests/test_lldp-test-lldp.o `test -f 'src/core/devices/tests/test-lldp.c' || echo '$(srcdir)/'`src/core/devices/tests/test-lldp.c -src/devices/tests/test_lldp-test-lldp.obj: src/devices/tests/test-lldp.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/tests/test_lldp-test-lldp.obj -MD -MP -MF src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo -c -o src/devices/tests/test_lldp-test-lldp.obj `if test -f 'src/devices/tests/test-lldp.c'; then $(CYGPATH_W) 'src/devices/tests/test-lldp.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/tests/test-lldp.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/tests/test-lldp.c' object='src/devices/tests/test_lldp-test-lldp.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/tests/test_lldp-test-lldp.obj: src/core/devices/tests/test-lldp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/tests/test_lldp-test-lldp.obj -MD -MP -MF src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo -c -o src/core/devices/tests/test_lldp-test-lldp.obj `if test -f 'src/core/devices/tests/test-lldp.c'; then $(CYGPATH_W) 'src/core/devices/tests/test-lldp.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/tests/test-lldp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Tpo src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/tests/test-lldp.c' object='src/core/devices/tests/test_lldp-test-lldp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/tests/test_lldp-test-lldp.obj `if test -f 'src/devices/tests/test-lldp.c'; then $(CYGPATH_W) 'src/devices/tests/test-lldp.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/tests/test-lldp.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_tests_test_lldp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/tests/test_lldp-test-lldp.obj `if test -f 'src/core/devices/tests/test-lldp.c'; then $(CYGPATH_W) 'src/core/devices/tests/test-lldp.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/tests/test-lldp.c'; fi` -src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o: src/devices/wifi/tests/test-devices-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o -MD -MP -MF src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo -c -o src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o `test -f 'src/devices/wifi/tests/test-devices-wifi.c' || echo '$(srcdir)/'`src/devices/wifi/tests/test-devices-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/tests/test-devices-wifi.c' object='src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o: src/core/devices/wifi/tests/test-devices-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o -MD -MP -MF src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo -c -o src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o `test -f 'src/core/devices/wifi/tests/test-devices-wifi.c' || echo '$(srcdir)/'`src/core/devices/wifi/tests/test-devices-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/tests/test-devices-wifi.c' object='src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o `test -f 'src/devices/wifi/tests/test-devices-wifi.c' || echo '$(srcdir)/'`src/devices/wifi/tests/test-devices-wifi.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.o `test -f 'src/core/devices/wifi/tests/test-devices-wifi.c' || echo '$(srcdir)/'`src/core/devices/wifi/tests/test-devices-wifi.c -src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj: src/devices/wifi/tests/test-devices-wifi.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj -MD -MP -MF src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo -c -o src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj `if test -f 'src/devices/wifi/tests/test-devices-wifi.c'; then $(CYGPATH_W) 'src/devices/wifi/tests/test-devices-wifi.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wifi/tests/test-devices-wifi.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wifi/tests/test-devices-wifi.c' object='src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj: src/core/devices/wifi/tests/test-devices-wifi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj -MD -MP -MF src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo -c -o src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj `if test -f 'src/core/devices/wifi/tests/test-devices-wifi.c'; then $(CYGPATH_W) 'src/core/devices/wifi/tests/test-devices-wifi.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wifi/tests/test-devices-wifi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Tpo src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wifi/tests/test-devices-wifi.c' object='src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj `if test -f 'src/devices/wifi/tests/test-devices-wifi.c'; then $(CYGPATH_W) 'src/devices/wifi/tests/test-devices-wifi.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wifi/tests/test-devices-wifi.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wifi_tests_test_devices_wifi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wifi/tests/test_devices_wifi-test-devices-wifi.obj `if test -f 'src/core/devices/wifi/tests/test-devices-wifi.c'; then $(CYGPATH_W) 'src/core/devices/wifi/tests/test-devices-wifi.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wifi/tests/test-devices-wifi.c'; fi` -src/devices/wwan/tests/test_service_providers-test-service-providers.o: src/devices/wwan/tests/test-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/tests/test_service_providers-test-service-providers.o -MD -MP -MF src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo -c -o src/devices/wwan/tests/test_service_providers-test-service-providers.o `test -f 'src/devices/wwan/tests/test-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/tests/test-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/tests/test-service-providers.c' object='src/devices/wwan/tests/test_service_providers-test-service-providers.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wwan/tests/test_service_providers-test-service-providers.o: src/core/devices/wwan/tests/test-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/tests/test_service_providers-test-service-providers.o -MD -MP -MF src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo -c -o src/core/devices/wwan/tests/test_service_providers-test-service-providers.o `test -f 'src/core/devices/wwan/tests/test-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/tests/test-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/tests/test-service-providers.c' object='src/core/devices/wwan/tests/test_service_providers-test-service-providers.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/tests/test_service_providers-test-service-providers.o `test -f 'src/devices/wwan/tests/test-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/tests/test-service-providers.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/tests/test_service_providers-test-service-providers.o `test -f 'src/core/devices/wwan/tests/test-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/tests/test-service-providers.c -src/devices/wwan/tests/test_service_providers-test-service-providers.obj: src/devices/wwan/tests/test-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/tests/test_service_providers-test-service-providers.obj -MD -MP -MF src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo -c -o src/devices/wwan/tests/test_service_providers-test-service-providers.obj `if test -f 'src/devices/wwan/tests/test-service-providers.c'; then $(CYGPATH_W) 'src/devices/wwan/tests/test-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wwan/tests/test-service-providers.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/tests/test-service-providers.c' object='src/devices/wwan/tests/test_service_providers-test-service-providers.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wwan/tests/test_service_providers-test-service-providers.obj: src/core/devices/wwan/tests/test-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/tests/test_service_providers-test-service-providers.obj -MD -MP -MF src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo -c -o src/core/devices/wwan/tests/test_service_providers-test-service-providers.obj `if test -f 'src/core/devices/wwan/tests/test-service-providers.c'; then $(CYGPATH_W) 'src/core/devices/wwan/tests/test-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wwan/tests/test-service-providers.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Tpo src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/tests/test-service-providers.c' object='src/core/devices/wwan/tests/test_service_providers-test-service-providers.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/tests/test_service_providers-test-service-providers.obj `if test -f 'src/devices/wwan/tests/test-service-providers.c'; then $(CYGPATH_W) 'src/devices/wwan/tests/test-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wwan/tests/test-service-providers.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/tests/test_service_providers-test-service-providers.obj `if test -f 'src/core/devices/wwan/tests/test-service-providers.c'; then $(CYGPATH_W) 'src/core/devices/wwan/tests/test-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wwan/tests/test-service-providers.c'; fi` -src/devices/wwan/tests_test_service_providers-nm-service-providers.o: src/devices/wwan/nm-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/tests_test_service_providers-nm-service-providers.o -MD -MP -MF src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo -c -o src/devices/wwan/tests_test_service_providers-nm-service-providers.o `test -f 'src/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/nm-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-service-providers.c' object='src/devices/wwan/tests_test_service_providers-nm-service-providers.o' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wwan/tests_test_service_providers-nm-service-providers.o: src/core/devices/wwan/nm-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/tests_test_service_providers-nm-service-providers.o -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo -c -o src/core/devices/wwan/tests_test_service_providers-nm-service-providers.o `test -f 'src/core/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-service-providers.c' object='src/core/devices/wwan/tests_test_service_providers-nm-service-providers.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/tests_test_service_providers-nm-service-providers.o `test -f 'src/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/devices/wwan/nm-service-providers.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/tests_test_service_providers-nm-service-providers.o `test -f 'src/core/devices/wwan/nm-service-providers.c' || echo '$(srcdir)/'`src/core/devices/wwan/nm-service-providers.c -src/devices/wwan/tests_test_service_providers-nm-service-providers.obj: src/devices/wwan/nm-service-providers.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/devices/wwan/tests_test_service_providers-nm-service-providers.obj -MD -MP -MF src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo -c -o src/devices/wwan/tests_test_service_providers-nm-service-providers.obj `if test -f 'src/devices/wwan/nm-service-providers.c'; then $(CYGPATH_W) 'src/devices/wwan/nm-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wwan/nm-service-providers.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/devices/wwan/nm-service-providers.c' object='src/devices/wwan/tests_test_service_providers-nm-service-providers.obj' libtool=no @AMDEPBACKSLASH@ +src/core/devices/wwan/tests_test_service_providers-nm-service-providers.obj: src/core/devices/wwan/nm-service-providers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/devices/wwan/tests_test_service_providers-nm-service-providers.obj -MD -MP -MF src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo -c -o src/core/devices/wwan/tests_test_service_providers-nm-service-providers.obj `if test -f 'src/core/devices/wwan/nm-service-providers.c'; then $(CYGPATH_W) 'src/core/devices/wwan/nm-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wwan/nm-service-providers.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Tpo src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/devices/wwan/nm-service-providers.c' object='src/core/devices/wwan/tests_test_service_providers-nm-service-providers.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/devices/wwan/tests_test_service_providers-nm-service-providers.obj `if test -f 'src/devices/wwan/nm-service-providers.c'; then $(CYGPATH_W) 'src/devices/wwan/nm-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/devices/wwan/nm-service-providers.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_devices_wwan_tests_test_service_providers_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/devices/wwan/tests_test_service_providers-nm-service-providers.obj `if test -f 'src/core/devices/wwan/nm-service-providers.c'; then $(CYGPATH_W) 'src/core/devices/wwan/nm-service-providers.c'; else $(CYGPATH_W) '$(srcdir)/src/core/devices/wwan/nm-service-providers.c'; fi` -src/dhcp/nm_dhcp_helper-nm-dhcp-helper.o: src/dhcp/nm-dhcp-helper.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/nm_dhcp_helper-nm-dhcp-helper.o -MD -MP -MF src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo -c -o src/dhcp/nm_dhcp_helper-nm-dhcp-helper.o `test -f 'src/dhcp/nm-dhcp-helper.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-helper.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-helper.c' object='src/dhcp/nm_dhcp_helper-nm-dhcp-helper.o' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.o: src/core/dhcp/nm-dhcp-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.o -MD -MP -MF src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo -c -o src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.o `test -f 'src/core/dhcp/nm-dhcp-helper.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-helper.c' object='src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/nm_dhcp_helper-nm-dhcp-helper.o `test -f 'src/dhcp/nm-dhcp-helper.c' || echo '$(srcdir)/'`src/dhcp/nm-dhcp-helper.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.o `test -f 'src/core/dhcp/nm-dhcp-helper.c' || echo '$(srcdir)/'`src/core/dhcp/nm-dhcp-helper.c -src/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj: src/dhcp/nm-dhcp-helper.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj -MD -MP -MF src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo -c -o src/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj `if test -f 'src/dhcp/nm-dhcp-helper.c'; then $(CYGPATH_W) 'src/dhcp/nm-dhcp-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/nm-dhcp-helper.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/nm-dhcp-helper.c' object='src/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj: src/core/dhcp/nm-dhcp-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj -MD -MP -MF src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo -c -o src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj `if test -f 'src/core/dhcp/nm-dhcp-helper.c'; then $(CYGPATH_W) 'src/core/dhcp/nm-dhcp-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/nm-dhcp-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Tpo src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/nm-dhcp-helper.c' object='src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj `if test -f 'src/dhcp/nm-dhcp-helper.c'; then $(CYGPATH_W) 'src/dhcp/nm-dhcp-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/nm-dhcp-helper.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_nm_dhcp_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/nm_dhcp_helper-nm-dhcp-helper.obj `if test -f 'src/core/dhcp/nm-dhcp-helper.c'; then $(CYGPATH_W) 'src/core/dhcp/nm-dhcp-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/nm-dhcp-helper.c'; fi` -src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o: src/dhcp/tests/test-dhcp-dhclient.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o -MD -MP -MF src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo -c -o src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o `test -f 'src/dhcp/tests/test-dhcp-dhclient.c' || echo '$(srcdir)/'`src/dhcp/tests/test-dhcp-dhclient.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/tests/test-dhcp-dhclient.c' object='src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o: src/core/dhcp/tests/test-dhcp-dhclient.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o -MD -MP -MF src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo -c -o src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o `test -f 'src/core/dhcp/tests/test-dhcp-dhclient.c' || echo '$(srcdir)/'`src/core/dhcp/tests/test-dhcp-dhclient.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/tests/test-dhcp-dhclient.c' object='src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o `test -f 'src/dhcp/tests/test-dhcp-dhclient.c' || echo '$(srcdir)/'`src/dhcp/tests/test-dhcp-dhclient.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.o `test -f 'src/core/dhcp/tests/test-dhcp-dhclient.c' || echo '$(srcdir)/'`src/core/dhcp/tests/test-dhcp-dhclient.c -src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj: src/dhcp/tests/test-dhcp-dhclient.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj -MD -MP -MF src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo -c -o src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj `if test -f 'src/dhcp/tests/test-dhcp-dhclient.c'; then $(CYGPATH_W) 'src/dhcp/tests/test-dhcp-dhclient.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/tests/test-dhcp-dhclient.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/tests/test-dhcp-dhclient.c' object='src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj: src/core/dhcp/tests/test-dhcp-dhclient.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj -MD -MP -MF src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo -c -o src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj `if test -f 'src/core/dhcp/tests/test-dhcp-dhclient.c'; then $(CYGPATH_W) 'src/core/dhcp/tests/test-dhcp-dhclient.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/tests/test-dhcp-dhclient.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Tpo src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/tests/test-dhcp-dhclient.c' object='src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj `if test -f 'src/dhcp/tests/test-dhcp-dhclient.c'; then $(CYGPATH_W) 'src/dhcp/tests/test-dhcp-dhclient.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/tests/test-dhcp-dhclient.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_dhclient_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/tests/test_dhcp_dhclient-test-dhcp-dhclient.obj `if test -f 'src/core/dhcp/tests/test-dhcp-dhclient.c'; then $(CYGPATH_W) 'src/core/dhcp/tests/test-dhcp-dhclient.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/tests/test-dhcp-dhclient.c'; fi` -src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o: src/dhcp/tests/test-dhcp-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o -MD -MP -MF src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo -c -o src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o `test -f 'src/dhcp/tests/test-dhcp-utils.c' || echo '$(srcdir)/'`src/dhcp/tests/test-dhcp-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/tests/test-dhcp-utils.c' object='src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o: src/core/dhcp/tests/test-dhcp-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o -MD -MP -MF src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo -c -o src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o `test -f 'src/core/dhcp/tests/test-dhcp-utils.c' || echo '$(srcdir)/'`src/core/dhcp/tests/test-dhcp-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/tests/test-dhcp-utils.c' object='src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o `test -f 'src/dhcp/tests/test-dhcp-utils.c' || echo '$(srcdir)/'`src/dhcp/tests/test-dhcp-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.o `test -f 'src/core/dhcp/tests/test-dhcp-utils.c' || echo '$(srcdir)/'`src/core/dhcp/tests/test-dhcp-utils.c -src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj: src/dhcp/tests/test-dhcp-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj -MD -MP -MF src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo -c -o src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj `if test -f 'src/dhcp/tests/test-dhcp-utils.c'; then $(CYGPATH_W) 'src/dhcp/tests/test-dhcp-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/tests/test-dhcp-utils.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dhcp/tests/test-dhcp-utils.c' object='src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj' libtool=no @AMDEPBACKSLASH@ +src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj: src/core/dhcp/tests/test-dhcp-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj -MD -MP -MF src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo -c -o src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj `if test -f 'src/core/dhcp/tests/test-dhcp-utils.c'; then $(CYGPATH_W) 'src/core/dhcp/tests/test-dhcp-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/tests/test-dhcp-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Tpo src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dhcp/tests/test-dhcp-utils.c' object='src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj `if test -f 'src/dhcp/tests/test-dhcp-utils.c'; then $(CYGPATH_W) 'src/dhcp/tests/test-dhcp-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/dhcp/tests/test-dhcp-utils.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dhcp_tests_test_dhcp_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dhcp/tests/test_dhcp_utils-test-dhcp-utils.obj `if test -f 'src/core/dhcp/tests/test-dhcp-utils.c'; then $(CYGPATH_W) 'src/core/dhcp/tests/test-dhcp-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dhcp/tests/test-dhcp-utils.c'; fi` -src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o: src/dnsmasq/tests/test-dnsmasq-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o -MD -MP -MF src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo -c -o src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o `test -f 'src/dnsmasq/tests/test-dnsmasq-utils.c' || echo '$(srcdir)/'`src/dnsmasq/tests/test-dnsmasq-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dnsmasq/tests/test-dnsmasq-utils.c' object='src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o' libtool=no @AMDEPBACKSLASH@ +src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o: src/core/dnsmasq/tests/test-dnsmasq-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o -MD -MP -MF src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo -c -o src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o `test -f 'src/core/dnsmasq/tests/test-dnsmasq-utils.c' || echo '$(srcdir)/'`src/core/dnsmasq/tests/test-dnsmasq-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dnsmasq/tests/test-dnsmasq-utils.c' object='src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o `test -f 'src/dnsmasq/tests/test-dnsmasq-utils.c' || echo '$(srcdir)/'`src/dnsmasq/tests/test-dnsmasq-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.o `test -f 'src/core/dnsmasq/tests/test-dnsmasq-utils.c' || echo '$(srcdir)/'`src/core/dnsmasq/tests/test-dnsmasq-utils.c -src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj: src/dnsmasq/tests/test-dnsmasq-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj -MD -MP -MF src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo -c -o src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj `if test -f 'src/dnsmasq/tests/test-dnsmasq-utils.c'; then $(CYGPATH_W) 'src/dnsmasq/tests/test-dnsmasq-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/dnsmasq/tests/test-dnsmasq-utils.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dnsmasq/tests/test-dnsmasq-utils.c' object='src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj' libtool=no @AMDEPBACKSLASH@ +src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj: src/core/dnsmasq/tests/test-dnsmasq-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj -MD -MP -MF src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo -c -o src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj `if test -f 'src/core/dnsmasq/tests/test-dnsmasq-utils.c'; then $(CYGPATH_W) 'src/core/dnsmasq/tests/test-dnsmasq-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dnsmasq/tests/test-dnsmasq-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Tpo src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/dnsmasq/tests/test-dnsmasq-utils.c' object='src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj `if test -f 'src/dnsmasq/tests/test-dnsmasq-utils.c'; then $(CYGPATH_W) 'src/dnsmasq/tests/test-dnsmasq-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/dnsmasq/tests/test-dnsmasq-utils.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_dnsmasq_tests_test_dnsmasq_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/dnsmasq/tests/test_dnsmasq_utils-test-dnsmasq-utils.obj `if test -f 'src/core/dnsmasq/tests/test-dnsmasq-utils.c'; then $(CYGPATH_W) 'src/core/dnsmasq/tests/test-dnsmasq-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/dnsmasq/tests/test-dnsmasq-utils.c'; fi` -src/initrd/nm_initrd_generator-nm-initrd-generator.o: src/initrd/nm-initrd-generator.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/nm_initrd_generator-nm-initrd-generator.o -MD -MP -MF src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo -c -o src/initrd/nm_initrd_generator-nm-initrd-generator.o `test -f 'src/initrd/nm-initrd-generator.c' || echo '$(srcdir)/'`src/initrd/nm-initrd-generator.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/nm-initrd-generator.c' object='src/initrd/nm_initrd_generator-nm-initrd-generator.o' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/nm_initrd_generator-nm-initrd-generator.o: src/core/initrd/nm-initrd-generator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/nm_initrd_generator-nm-initrd-generator.o -MD -MP -MF src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo -c -o src/core/initrd/nm_initrd_generator-nm-initrd-generator.o `test -f 'src/core/initrd/nm-initrd-generator.c' || echo '$(srcdir)/'`src/core/initrd/nm-initrd-generator.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/nm-initrd-generator.c' object='src/core/initrd/nm_initrd_generator-nm-initrd-generator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/nm_initrd_generator-nm-initrd-generator.o `test -f 'src/initrd/nm-initrd-generator.c' || echo '$(srcdir)/'`src/initrd/nm-initrd-generator.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/nm_initrd_generator-nm-initrd-generator.o `test -f 'src/core/initrd/nm-initrd-generator.c' || echo '$(srcdir)/'`src/core/initrd/nm-initrd-generator.c -src/initrd/nm_initrd_generator-nm-initrd-generator.obj: src/initrd/nm-initrd-generator.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/nm_initrd_generator-nm-initrd-generator.obj -MD -MP -MF src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo -c -o src/initrd/nm_initrd_generator-nm-initrd-generator.obj `if test -f 'src/initrd/nm-initrd-generator.c'; then $(CYGPATH_W) 'src/initrd/nm-initrd-generator.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/nm-initrd-generator.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/nm-initrd-generator.c' object='src/initrd/nm_initrd_generator-nm-initrd-generator.obj' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/nm_initrd_generator-nm-initrd-generator.obj: src/core/initrd/nm-initrd-generator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/nm_initrd_generator-nm-initrd-generator.obj -MD -MP -MF src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo -c -o src/core/initrd/nm_initrd_generator-nm-initrd-generator.obj `if test -f 'src/core/initrd/nm-initrd-generator.c'; then $(CYGPATH_W) 'src/core/initrd/nm-initrd-generator.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/nm-initrd-generator.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Tpo src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/nm-initrd-generator.c' object='src/core/initrd/nm_initrd_generator-nm-initrd-generator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/nm_initrd_generator-nm-initrd-generator.obj `if test -f 'src/initrd/nm-initrd-generator.c'; then $(CYGPATH_W) 'src/initrd/nm-initrd-generator.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/nm-initrd-generator.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_nm_initrd_generator_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/nm_initrd_generator-nm-initrd-generator.obj `if test -f 'src/core/initrd/nm-initrd-generator.c'; then $(CYGPATH_W) 'src/core/initrd/nm-initrd-generator.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/nm-initrd-generator.c'; fi` -src/initrd/tests/test_cmdline_reader-test-cmdline-reader.o: src/initrd/tests/test-cmdline-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_cmdline_reader-test-cmdline-reader.o -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo -c -o src/initrd/tests/test_cmdline_reader-test-cmdline-reader.o `test -f 'src/initrd/tests/test-cmdline-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-cmdline-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-cmdline-reader.c' object='src/initrd/tests/test_cmdline_reader-test-cmdline-reader.o' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.o: src/core/initrd/tests/test-cmdline-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.o -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo -c -o src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.o `test -f 'src/core/initrd/tests/test-cmdline-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-cmdline-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-cmdline-reader.c' object='src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_cmdline_reader-test-cmdline-reader.o `test -f 'src/initrd/tests/test-cmdline-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-cmdline-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.o `test -f 'src/core/initrd/tests/test-cmdline-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-cmdline-reader.c -src/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj: src/initrd/tests/test-cmdline-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo -c -o src/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj `if test -f 'src/initrd/tests/test-cmdline-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-cmdline-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-cmdline-reader.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-cmdline-reader.c' object='src/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj: src/core/initrd/tests/test-cmdline-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo -c -o src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj `if test -f 'src/core/initrd/tests/test-cmdline-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-cmdline-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-cmdline-reader.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-cmdline-reader.c' object='src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj `if test -f 'src/initrd/tests/test-cmdline-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-cmdline-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-cmdline-reader.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_cmdline_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_cmdline_reader-test-cmdline-reader.obj `if test -f 'src/core/initrd/tests/test-cmdline-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-cmdline-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-cmdline-reader.c'; fi` -src/initrd/tests/test_dt_reader-test-dt-reader.o: src/initrd/tests/test-dt-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_dt_reader-test-dt-reader.o -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo -c -o src/initrd/tests/test_dt_reader-test-dt-reader.o `test -f 'src/initrd/tests/test-dt-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-dt-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-dt-reader.c' object='src/initrd/tests/test_dt_reader-test-dt-reader.o' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_dt_reader-test-dt-reader.o: src/core/initrd/tests/test-dt-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_dt_reader-test-dt-reader.o -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo -c -o src/core/initrd/tests/test_dt_reader-test-dt-reader.o `test -f 'src/core/initrd/tests/test-dt-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-dt-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-dt-reader.c' object='src/core/initrd/tests/test_dt_reader-test-dt-reader.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_dt_reader-test-dt-reader.o `test -f 'src/initrd/tests/test-dt-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-dt-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_dt_reader-test-dt-reader.o `test -f 'src/core/initrd/tests/test-dt-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-dt-reader.c -src/initrd/tests/test_dt_reader-test-dt-reader.obj: src/initrd/tests/test-dt-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_dt_reader-test-dt-reader.obj -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo -c -o src/initrd/tests/test_dt_reader-test-dt-reader.obj `if test -f 'src/initrd/tests/test-dt-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-dt-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-dt-reader.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-dt-reader.c' object='src/initrd/tests/test_dt_reader-test-dt-reader.obj' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_dt_reader-test-dt-reader.obj: src/core/initrd/tests/test-dt-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_dt_reader-test-dt-reader.obj -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo -c -o src/core/initrd/tests/test_dt_reader-test-dt-reader.obj `if test -f 'src/core/initrd/tests/test-dt-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-dt-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-dt-reader.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-dt-reader.c' object='src/core/initrd/tests/test_dt_reader-test-dt-reader.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_dt_reader-test-dt-reader.obj `if test -f 'src/initrd/tests/test-dt-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-dt-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-dt-reader.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_dt_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_dt_reader-test-dt-reader.obj `if test -f 'src/core/initrd/tests/test-dt-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-dt-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-dt-reader.c'; fi` -src/initrd/tests/test_ibft_reader-test-ibft-reader.o: src/initrd/tests/test-ibft-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_ibft_reader-test-ibft-reader.o -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo -c -o src/initrd/tests/test_ibft_reader-test-ibft-reader.o `test -f 'src/initrd/tests/test-ibft-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-ibft-reader.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-ibft-reader.c' object='src/initrd/tests/test_ibft_reader-test-ibft-reader.o' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_ibft_reader-test-ibft-reader.o: src/core/initrd/tests/test-ibft-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_ibft_reader-test-ibft-reader.o -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo -c -o src/core/initrd/tests/test_ibft_reader-test-ibft-reader.o `test -f 'src/core/initrd/tests/test-ibft-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-ibft-reader.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-ibft-reader.c' object='src/core/initrd/tests/test_ibft_reader-test-ibft-reader.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_ibft_reader-test-ibft-reader.o `test -f 'src/initrd/tests/test-ibft-reader.c' || echo '$(srcdir)/'`src/initrd/tests/test-ibft-reader.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_ibft_reader-test-ibft-reader.o `test -f 'src/core/initrd/tests/test-ibft-reader.c' || echo '$(srcdir)/'`src/core/initrd/tests/test-ibft-reader.c -src/initrd/tests/test_ibft_reader-test-ibft-reader.obj: src/initrd/tests/test-ibft-reader.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/initrd/tests/test_ibft_reader-test-ibft-reader.obj -MD -MP -MF src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo -c -o src/initrd/tests/test_ibft_reader-test-ibft-reader.obj `if test -f 'src/initrd/tests/test-ibft-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-ibft-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-ibft-reader.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/initrd/tests/test-ibft-reader.c' object='src/initrd/tests/test_ibft_reader-test-ibft-reader.obj' libtool=no @AMDEPBACKSLASH@ +src/core/initrd/tests/test_ibft_reader-test-ibft-reader.obj: src/core/initrd/tests/test-ibft-reader.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/initrd/tests/test_ibft_reader-test-ibft-reader.obj -MD -MP -MF src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo -c -o src/core/initrd/tests/test_ibft_reader-test-ibft-reader.obj `if test -f 'src/core/initrd/tests/test-ibft-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-ibft-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-ibft-reader.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Tpo src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/initrd/tests/test-ibft-reader.c' object='src/core/initrd/tests/test_ibft_reader-test-ibft-reader.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/initrd/tests/test_ibft_reader-test-ibft-reader.obj `if test -f 'src/initrd/tests/test-ibft-reader.c'; then $(CYGPATH_W) 'src/initrd/tests/test-ibft-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/initrd/tests/test-ibft-reader.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_initrd_tests_test_ibft_reader_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/initrd/tests/test_ibft_reader-test-ibft-reader.obj `if test -f 'src/core/initrd/tests/test-ibft-reader.c'; then $(CYGPATH_W) 'src/core/initrd/tests/test-ibft-reader.c'; else $(CYGPATH_W) '$(srcdir)/src/core/initrd/tests/test-ibft-reader.c'; fi` -src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o: src/ndisc/tests/test-ndisc-fake.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o -MD -MP -MF src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo -c -o src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o `test -f 'src/ndisc/tests/test-ndisc-fake.c' || echo '$(srcdir)/'`src/ndisc/tests/test-ndisc-fake.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/tests/test-ndisc-fake.c' object='src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o' libtool=no @AMDEPBACKSLASH@ +src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o: src/core/ndisc/tests/test-ndisc-fake.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o -MD -MP -MF src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo -c -o src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o `test -f 'src/core/ndisc/tests/test-ndisc-fake.c' || echo '$(srcdir)/'`src/core/ndisc/tests/test-ndisc-fake.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/tests/test-ndisc-fake.c' object='src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o `test -f 'src/ndisc/tests/test-ndisc-fake.c' || echo '$(srcdir)/'`src/ndisc/tests/test-ndisc-fake.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.o `test -f 'src/core/ndisc/tests/test-ndisc-fake.c' || echo '$(srcdir)/'`src/core/ndisc/tests/test-ndisc-fake.c -src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj: src/ndisc/tests/test-ndisc-fake.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj -MD -MP -MF src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo -c -o src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj `if test -f 'src/ndisc/tests/test-ndisc-fake.c'; then $(CYGPATH_W) 'src/ndisc/tests/test-ndisc-fake.c'; else $(CYGPATH_W) '$(srcdir)/src/ndisc/tests/test-ndisc-fake.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/tests/test-ndisc-fake.c' object='src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj' libtool=no @AMDEPBACKSLASH@ +src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj: src/core/ndisc/tests/test-ndisc-fake.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj -MD -MP -MF src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo -c -o src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj `if test -f 'src/core/ndisc/tests/test-ndisc-fake.c'; then $(CYGPATH_W) 'src/core/ndisc/tests/test-ndisc-fake.c'; else $(CYGPATH_W) '$(srcdir)/src/core/ndisc/tests/test-ndisc-fake.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Tpo src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/tests/test-ndisc-fake.c' object='src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj `if test -f 'src/ndisc/tests/test-ndisc-fake.c'; then $(CYGPATH_W) 'src/ndisc/tests/test-ndisc-fake.c'; else $(CYGPATH_W) '$(srcdir)/src/ndisc/tests/test-ndisc-fake.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/tests/test_ndisc_fake-test-ndisc-fake.obj `if test -f 'src/core/ndisc/tests/test-ndisc-fake.c'; then $(CYGPATH_W) 'src/core/ndisc/tests/test-ndisc-fake.c'; else $(CYGPATH_W) '$(srcdir)/src/core/ndisc/tests/test-ndisc-fake.c'; fi` -src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o: src/ndisc/tests/test-ndisc-linux.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o -MD -MP -MF src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo -c -o src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o `test -f 'src/ndisc/tests/test-ndisc-linux.c' || echo '$(srcdir)/'`src/ndisc/tests/test-ndisc-linux.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/tests/test-ndisc-linux.c' object='src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o' libtool=no @AMDEPBACKSLASH@ +src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o: src/core/ndisc/tests/test-ndisc-linux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o -MD -MP -MF src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo -c -o src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o `test -f 'src/core/ndisc/tests/test-ndisc-linux.c' || echo '$(srcdir)/'`src/core/ndisc/tests/test-ndisc-linux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/tests/test-ndisc-linux.c' object='src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o `test -f 'src/ndisc/tests/test-ndisc-linux.c' || echo '$(srcdir)/'`src/ndisc/tests/test-ndisc-linux.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.o `test -f 'src/core/ndisc/tests/test-ndisc-linux.c' || echo '$(srcdir)/'`src/core/ndisc/tests/test-ndisc-linux.c -src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj: src/ndisc/tests/test-ndisc-linux.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj -MD -MP -MF src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo -c -o src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj `if test -f 'src/ndisc/tests/test-ndisc-linux.c'; then $(CYGPATH_W) 'src/ndisc/tests/test-ndisc-linux.c'; else $(CYGPATH_W) '$(srcdir)/src/ndisc/tests/test-ndisc-linux.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ndisc/tests/test-ndisc-linux.c' object='src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj' libtool=no @AMDEPBACKSLASH@ +src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj: src/core/ndisc/tests/test-ndisc-linux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj -MD -MP -MF src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo -c -o src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj `if test -f 'src/core/ndisc/tests/test-ndisc-linux.c'; then $(CYGPATH_W) 'src/core/ndisc/tests/test-ndisc-linux.c'; else $(CYGPATH_W) '$(srcdir)/src/core/ndisc/tests/test-ndisc-linux.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Tpo src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/ndisc/tests/test-ndisc-linux.c' object='src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj `if test -f 'src/ndisc/tests/test-ndisc-linux.c'; then $(CYGPATH_W) 'src/ndisc/tests/test-ndisc-linux.c'; else $(CYGPATH_W) '$(srcdir)/src/ndisc/tests/test-ndisc-linux.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_ndisc_tests_test_ndisc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/ndisc/tests/test_ndisc_linux-test-ndisc-linux.obj `if test -f 'src/core/ndisc/tests/test-ndisc-linux.c'; then $(CYGPATH_W) 'src/core/ndisc/tests/test-ndisc-linux.c'; else $(CYGPATH_W) '$(srcdir)/src/core/ndisc/tests/test-ndisc-linux.c'; fi` -src/nm_iface_helper-nm-iface-helper.o: src/nm-iface-helper.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/nm_iface_helper-nm-iface-helper.o -MD -MP -MF src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo -c -o src/nm_iface_helper-nm-iface-helper.o `test -f 'src/nm-iface-helper.c' || echo '$(srcdir)/'`src/nm-iface-helper.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-iface-helper.c' object='src/nm_iface_helper-nm-iface-helper.o' libtool=no @AMDEPBACKSLASH@ +src/core/nm_iface_helper-nm-iface-helper.o: src/core/nm-iface-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/nm_iface_helper-nm-iface-helper.o -MD -MP -MF src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo -c -o src/core/nm_iface_helper-nm-iface-helper.o `test -f 'src/core/nm-iface-helper.c' || echo '$(srcdir)/'`src/core/nm-iface-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-iface-helper.c' object='src/core/nm_iface_helper-nm-iface-helper.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/nm_iface_helper-nm-iface-helper.o `test -f 'src/nm-iface-helper.c' || echo '$(srcdir)/'`src/nm-iface-helper.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/nm_iface_helper-nm-iface-helper.o `test -f 'src/core/nm-iface-helper.c' || echo '$(srcdir)/'`src/core/nm-iface-helper.c -src/nm_iface_helper-nm-iface-helper.obj: src/nm-iface-helper.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/nm_iface_helper-nm-iface-helper.obj -MD -MP -MF src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo -c -o src/nm_iface_helper-nm-iface-helper.obj `if test -f 'src/nm-iface-helper.c'; then $(CYGPATH_W) 'src/nm-iface-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/nm-iface-helper.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/nm-iface-helper.c' object='src/nm_iface_helper-nm-iface-helper.obj' libtool=no @AMDEPBACKSLASH@ +src/core/nm_iface_helper-nm-iface-helper.obj: src/core/nm-iface-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/nm_iface_helper-nm-iface-helper.obj -MD -MP -MF src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo -c -o src/core/nm_iface_helper-nm-iface-helper.obj `if test -f 'src/core/nm-iface-helper.c'; then $(CYGPATH_W) 'src/core/nm-iface-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/core/nm-iface-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Tpo src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/nm-iface-helper.c' object='src/core/nm_iface_helper-nm-iface-helper.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/nm_iface_helper-nm-iface-helper.obj `if test -f 'src/nm-iface-helper.c'; then $(CYGPATH_W) 'src/nm-iface-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/nm-iface-helper.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_nm_iface_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/nm_iface_helper-nm-iface-helper.obj `if test -f 'src/core/nm-iface-helper.c'; then $(CYGPATH_W) 'src/core/nm-iface-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/core/nm-iface-helper.c'; fi` -src/platform/tests/monitor-monitor.o: src/platform/tests/monitor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/monitor-monitor.o -MD -MP -MF src/platform/tests/$(DEPDIR)/monitor-monitor.Tpo -c -o src/platform/tests/monitor-monitor.o `test -f 'src/platform/tests/monitor.c' || echo '$(srcdir)/'`src/platform/tests/monitor.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/monitor-monitor.Tpo src/platform/tests/$(DEPDIR)/monitor-monitor.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/monitor.c' object='src/platform/tests/monitor-monitor.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/monitor-monitor.o: src/core/platform/tests/monitor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/monitor-monitor.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/monitor-monitor.Tpo -c -o src/core/platform/tests/monitor-monitor.o `test -f 'src/core/platform/tests/monitor.c' || echo '$(srcdir)/'`src/core/platform/tests/monitor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/monitor-monitor.Tpo src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/monitor.c' object='src/core/platform/tests/monitor-monitor.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/monitor-monitor.o `test -f 'src/platform/tests/monitor.c' || echo '$(srcdir)/'`src/platform/tests/monitor.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/monitor-monitor.o `test -f 'src/core/platform/tests/monitor.c' || echo '$(srcdir)/'`src/core/platform/tests/monitor.c -src/platform/tests/monitor-monitor.obj: src/platform/tests/monitor.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/monitor-monitor.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/monitor-monitor.Tpo -c -o src/platform/tests/monitor-monitor.obj `if test -f 'src/platform/tests/monitor.c'; then $(CYGPATH_W) 'src/platform/tests/monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/monitor.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/monitor-monitor.Tpo src/platform/tests/$(DEPDIR)/monitor-monitor.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/monitor.c' object='src/platform/tests/monitor-monitor.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/monitor-monitor.obj: src/core/platform/tests/monitor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/monitor-monitor.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/monitor-monitor.Tpo -c -o src/core/platform/tests/monitor-monitor.obj `if test -f 'src/core/platform/tests/monitor.c'; then $(CYGPATH_W) 'src/core/platform/tests/monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/monitor.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/monitor-monitor.Tpo src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/monitor.c' object='src/core/platform/tests/monitor-monitor.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/monitor-monitor.obj `if test -f 'src/platform/tests/monitor.c'; then $(CYGPATH_W) 'src/platform/tests/monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/monitor.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_monitor_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/monitor-monitor.obj `if test -f 'src/core/platform/tests/monitor.c'; then $(CYGPATH_W) 'src/core/platform/tests/monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/monitor.c'; fi` -src/platform/tests/test_address_fake-test-address.o: src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_address_fake-test-address.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo -c -o src/platform/tests/test_address_fake-test-address.o `test -f 'src/platform/tests/test-address.c' || echo '$(srcdir)/'`src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-address.c' object='src/platform/tests/test_address_fake-test-address.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_address_fake-test-address.o: src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_address_fake-test-address.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo -c -o src/core/platform/tests/test_address_fake-test-address.o `test -f 'src/core/platform/tests/test-address.c' || echo '$(srcdir)/'`src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-address.c' object='src/core/platform/tests/test_address_fake-test-address.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_address_fake-test-address.o `test -f 'src/platform/tests/test-address.c' || echo '$(srcdir)/'`src/platform/tests/test-address.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_address_fake-test-address.o `test -f 'src/core/platform/tests/test-address.c' || echo '$(srcdir)/'`src/core/platform/tests/test-address.c -src/platform/tests/test_address_fake-test-address.obj: src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_address_fake-test-address.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo -c -o src/platform/tests/test_address_fake-test-address.obj `if test -f 'src/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-address.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-address.c' object='src/platform/tests/test_address_fake-test-address.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_address_fake-test-address.obj: src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_address_fake-test-address.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo -c -o src/core/platform/tests/test_address_fake-test-address.obj `if test -f 'src/core/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-address.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Tpo src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-address.c' object='src/core/platform/tests/test_address_fake-test-address.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_address_fake-test-address.obj `if test -f 'src/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-address.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_address_fake-test-address.obj `if test -f 'src/core/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-address.c'; fi` -src/platform/tests/test_address_linux-test-address.o: src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_address_linux-test-address.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo -c -o src/platform/tests/test_address_linux-test-address.o `test -f 'src/platform/tests/test-address.c' || echo '$(srcdir)/'`src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-address.c' object='src/platform/tests/test_address_linux-test-address.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_address_linux-test-address.o: src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_address_linux-test-address.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo -c -o src/core/platform/tests/test_address_linux-test-address.o `test -f 'src/core/platform/tests/test-address.c' || echo '$(srcdir)/'`src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-address.c' object='src/core/platform/tests/test_address_linux-test-address.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_address_linux-test-address.o `test -f 'src/platform/tests/test-address.c' || echo '$(srcdir)/'`src/platform/tests/test-address.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_address_linux-test-address.o `test -f 'src/core/platform/tests/test-address.c' || echo '$(srcdir)/'`src/core/platform/tests/test-address.c -src/platform/tests/test_address_linux-test-address.obj: src/platform/tests/test-address.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_address_linux-test-address.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo -c -o src/platform/tests/test_address_linux-test-address.obj `if test -f 'src/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-address.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-address.c' object='src/platform/tests/test_address_linux-test-address.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_address_linux-test-address.obj: src/core/platform/tests/test-address.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_address_linux-test-address.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo -c -o src/core/platform/tests/test_address_linux-test-address.obj `if test -f 'src/core/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-address.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Tpo src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-address.c' object='src/core/platform/tests/test_address_linux-test-address.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_address_linux-test-address.obj `if test -f 'src/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-address.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_address_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_address_linux-test-address.obj `if test -f 'src/core/platform/tests/test-address.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-address.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-address.c'; fi` -src/platform/tests/test_cleanup_fake-test-cleanup.o: src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_cleanup_fake-test-cleanup.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo -c -o src/platform/tests/test_cleanup_fake-test-cleanup.o `test -f 'src/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-cleanup.c' object='src/platform/tests/test_cleanup_fake-test-cleanup.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_cleanup_fake-test-cleanup.o: src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_cleanup_fake-test-cleanup.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo -c -o src/core/platform/tests/test_cleanup_fake-test-cleanup.o `test -f 'src/core/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-cleanup.c' object='src/core/platform/tests/test_cleanup_fake-test-cleanup.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_cleanup_fake-test-cleanup.o `test -f 'src/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/platform/tests/test-cleanup.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_cleanup_fake-test-cleanup.o `test -f 'src/core/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/core/platform/tests/test-cleanup.c -src/platform/tests/test_cleanup_fake-test-cleanup.obj: src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_cleanup_fake-test-cleanup.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo -c -o src/platform/tests/test_cleanup_fake-test-cleanup.obj `if test -f 'src/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-cleanup.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-cleanup.c' object='src/platform/tests/test_cleanup_fake-test-cleanup.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_cleanup_fake-test-cleanup.obj: src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_cleanup_fake-test-cleanup.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo -c -o src/core/platform/tests/test_cleanup_fake-test-cleanup.obj `if test -f 'src/core/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-cleanup.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Tpo src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-cleanup.c' object='src/core/platform/tests/test_cleanup_fake-test-cleanup.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_cleanup_fake-test-cleanup.obj `if test -f 'src/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-cleanup.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_cleanup_fake-test-cleanup.obj `if test -f 'src/core/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-cleanup.c'; fi` -src/platform/tests/test_cleanup_linux-test-cleanup.o: src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_cleanup_linux-test-cleanup.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo -c -o src/platform/tests/test_cleanup_linux-test-cleanup.o `test -f 'src/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-cleanup.c' object='src/platform/tests/test_cleanup_linux-test-cleanup.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_cleanup_linux-test-cleanup.o: src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_cleanup_linux-test-cleanup.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo -c -o src/core/platform/tests/test_cleanup_linux-test-cleanup.o `test -f 'src/core/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-cleanup.c' object='src/core/platform/tests/test_cleanup_linux-test-cleanup.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_cleanup_linux-test-cleanup.o `test -f 'src/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/platform/tests/test-cleanup.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_cleanup_linux-test-cleanup.o `test -f 'src/core/platform/tests/test-cleanup.c' || echo '$(srcdir)/'`src/core/platform/tests/test-cleanup.c -src/platform/tests/test_cleanup_linux-test-cleanup.obj: src/platform/tests/test-cleanup.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_cleanup_linux-test-cleanup.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo -c -o src/platform/tests/test_cleanup_linux-test-cleanup.obj `if test -f 'src/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-cleanup.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-cleanup.c' object='src/platform/tests/test_cleanup_linux-test-cleanup.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_cleanup_linux-test-cleanup.obj: src/core/platform/tests/test-cleanup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_cleanup_linux-test-cleanup.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo -c -o src/core/platform/tests/test_cleanup_linux-test-cleanup.obj `if test -f 'src/core/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-cleanup.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Tpo src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-cleanup.c' object='src/core/platform/tests/test_cleanup_linux-test-cleanup.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_cleanup_linux-test-cleanup.obj `if test -f 'src/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-cleanup.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_cleanup_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_cleanup_linux-test-cleanup.obj `if test -f 'src/core/platform/tests/test-cleanup.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-cleanup.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-cleanup.c'; fi` -src/platform/tests/test_link_fake-test-link.o: src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_link_fake-test-link.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo -c -o src/platform/tests/test_link_fake-test-link.o `test -f 'src/platform/tests/test-link.c' || echo '$(srcdir)/'`src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-link.c' object='src/platform/tests/test_link_fake-test-link.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_link_fake-test-link.o: src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_link_fake-test-link.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo -c -o src/core/platform/tests/test_link_fake-test-link.o `test -f 'src/core/platform/tests/test-link.c' || echo '$(srcdir)/'`src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-link.c' object='src/core/platform/tests/test_link_fake-test-link.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_link_fake-test-link.o `test -f 'src/platform/tests/test-link.c' || echo '$(srcdir)/'`src/platform/tests/test-link.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_link_fake-test-link.o `test -f 'src/core/platform/tests/test-link.c' || echo '$(srcdir)/'`src/core/platform/tests/test-link.c -src/platform/tests/test_link_fake-test-link.obj: src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_link_fake-test-link.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo -c -o src/platform/tests/test_link_fake-test-link.obj `if test -f 'src/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-link.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-link.c' object='src/platform/tests/test_link_fake-test-link.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_link_fake-test-link.obj: src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_link_fake-test-link.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo -c -o src/core/platform/tests/test_link_fake-test-link.obj `if test -f 'src/core/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-link.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Tpo src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-link.c' object='src/core/platform/tests/test_link_fake-test-link.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_link_fake-test-link.obj `if test -f 'src/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-link.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_link_fake-test-link.obj `if test -f 'src/core/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-link.c'; fi` -src/platform/tests/test_link_linux-test-link.o: src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_link_linux-test-link.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo -c -o src/platform/tests/test_link_linux-test-link.o `test -f 'src/platform/tests/test-link.c' || echo '$(srcdir)/'`src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-link.c' object='src/platform/tests/test_link_linux-test-link.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_link_linux-test-link.o: src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_link_linux-test-link.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo -c -o src/core/platform/tests/test_link_linux-test-link.o `test -f 'src/core/platform/tests/test-link.c' || echo '$(srcdir)/'`src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-link.c' object='src/core/platform/tests/test_link_linux-test-link.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_link_linux-test-link.o `test -f 'src/platform/tests/test-link.c' || echo '$(srcdir)/'`src/platform/tests/test-link.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_link_linux-test-link.o `test -f 'src/core/platform/tests/test-link.c' || echo '$(srcdir)/'`src/core/platform/tests/test-link.c -src/platform/tests/test_link_linux-test-link.obj: src/platform/tests/test-link.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_link_linux-test-link.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo -c -o src/platform/tests/test_link_linux-test-link.obj `if test -f 'src/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-link.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-link.c' object='src/platform/tests/test_link_linux-test-link.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_link_linux-test-link.obj: src/core/platform/tests/test-link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_link_linux-test-link.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo -c -o src/core/platform/tests/test_link_linux-test-link.obj `if test -f 'src/core/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-link.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Tpo src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-link.c' object='src/core/platform/tests/test_link_linux-test-link.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_link_linux-test-link.obj `if test -f 'src/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-link.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_link_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_link_linux-test-link.obj `if test -f 'src/core/platform/tests/test-link.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-link.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-link.c'; fi` -src/platform/tests/test_nmp_object-test-nmp-object.o: src/platform/tests/test-nmp-object.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_nmp_object-test-nmp-object.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo -c -o src/platform/tests/test_nmp_object-test-nmp-object.o `test -f 'src/platform/tests/test-nmp-object.c' || echo '$(srcdir)/'`src/platform/tests/test-nmp-object.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-nmp-object.c' object='src/platform/tests/test_nmp_object-test-nmp-object.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_nmp_object-test-nmp-object.o: src/core/platform/tests/test-nmp-object.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_nmp_object-test-nmp-object.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo -c -o src/core/platform/tests/test_nmp_object-test-nmp-object.o `test -f 'src/core/platform/tests/test-nmp-object.c' || echo '$(srcdir)/'`src/core/platform/tests/test-nmp-object.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-nmp-object.c' object='src/core/platform/tests/test_nmp_object-test-nmp-object.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_nmp_object-test-nmp-object.o `test -f 'src/platform/tests/test-nmp-object.c' || echo '$(srcdir)/'`src/platform/tests/test-nmp-object.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_nmp_object-test-nmp-object.o `test -f 'src/core/platform/tests/test-nmp-object.c' || echo '$(srcdir)/'`src/core/platform/tests/test-nmp-object.c -src/platform/tests/test_nmp_object-test-nmp-object.obj: src/platform/tests/test-nmp-object.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_nmp_object-test-nmp-object.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo -c -o src/platform/tests/test_nmp_object-test-nmp-object.obj `if test -f 'src/platform/tests/test-nmp-object.c'; then $(CYGPATH_W) 'src/platform/tests/test-nmp-object.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-nmp-object.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-nmp-object.c' object='src/platform/tests/test_nmp_object-test-nmp-object.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_nmp_object-test-nmp-object.obj: src/core/platform/tests/test-nmp-object.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_nmp_object-test-nmp-object.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo -c -o src/core/platform/tests/test_nmp_object-test-nmp-object.obj `if test -f 'src/core/platform/tests/test-nmp-object.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-nmp-object.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-nmp-object.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Tpo src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-nmp-object.c' object='src/core/platform/tests/test_nmp_object-test-nmp-object.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_nmp_object-test-nmp-object.obj `if test -f 'src/platform/tests/test-nmp-object.c'; then $(CYGPATH_W) 'src/platform/tests/test-nmp-object.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-nmp-object.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_nmp_object_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_nmp_object-test-nmp-object.obj `if test -f 'src/core/platform/tests/test-nmp-object.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-nmp-object.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-nmp-object.c'; fi` -src/platform/tests/test_platform_general-test-platform-general.o: src/platform/tests/test-platform-general.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_platform_general-test-platform-general.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo -c -o src/platform/tests/test_platform_general-test-platform-general.o `test -f 'src/platform/tests/test-platform-general.c' || echo '$(srcdir)/'`src/platform/tests/test-platform-general.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-platform-general.c' object='src/platform/tests/test_platform_general-test-platform-general.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_platform_general-test-platform-general.o: src/core/platform/tests/test-platform-general.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_platform_general-test-platform-general.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo -c -o src/core/platform/tests/test_platform_general-test-platform-general.o `test -f 'src/core/platform/tests/test-platform-general.c' || echo '$(srcdir)/'`src/core/platform/tests/test-platform-general.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-platform-general.c' object='src/core/platform/tests/test_platform_general-test-platform-general.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_platform_general-test-platform-general.o `test -f 'src/platform/tests/test-platform-general.c' || echo '$(srcdir)/'`src/platform/tests/test-platform-general.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_platform_general-test-platform-general.o `test -f 'src/core/platform/tests/test-platform-general.c' || echo '$(srcdir)/'`src/core/platform/tests/test-platform-general.c -src/platform/tests/test_platform_general-test-platform-general.obj: src/platform/tests/test-platform-general.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_platform_general-test-platform-general.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo -c -o src/platform/tests/test_platform_general-test-platform-general.obj `if test -f 'src/platform/tests/test-platform-general.c'; then $(CYGPATH_W) 'src/platform/tests/test-platform-general.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-platform-general.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-platform-general.c' object='src/platform/tests/test_platform_general-test-platform-general.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_platform_general-test-platform-general.obj: src/core/platform/tests/test-platform-general.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_platform_general-test-platform-general.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo -c -o src/core/platform/tests/test_platform_general-test-platform-general.obj `if test -f 'src/core/platform/tests/test-platform-general.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-platform-general.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-platform-general.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Tpo src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-platform-general.c' object='src/core/platform/tests/test_platform_general-test-platform-general.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_platform_general-test-platform-general.obj `if test -f 'src/platform/tests/test-platform-general.c'; then $(CYGPATH_W) 'src/platform/tests/test-platform-general.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-platform-general.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_platform_general_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_platform_general-test-platform-general.obj `if test -f 'src/core/platform/tests/test-platform-general.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-platform-general.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-platform-general.c'; fi` -src/platform/tests/test_route_fake-test-route.o: src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_route_fake-test-route.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo -c -o src/platform/tests/test_route_fake-test-route.o `test -f 'src/platform/tests/test-route.c' || echo '$(srcdir)/'`src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-route.c' object='src/platform/tests/test_route_fake-test-route.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_route_fake-test-route.o: src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_route_fake-test-route.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo -c -o src/core/platform/tests/test_route_fake-test-route.o `test -f 'src/core/platform/tests/test-route.c' || echo '$(srcdir)/'`src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-route.c' object='src/core/platform/tests/test_route_fake-test-route.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_route_fake-test-route.o `test -f 'src/platform/tests/test-route.c' || echo '$(srcdir)/'`src/platform/tests/test-route.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_route_fake-test-route.o `test -f 'src/core/platform/tests/test-route.c' || echo '$(srcdir)/'`src/core/platform/tests/test-route.c -src/platform/tests/test_route_fake-test-route.obj: src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_route_fake-test-route.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo -c -o src/platform/tests/test_route_fake-test-route.obj `if test -f 'src/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-route.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-route.c' object='src/platform/tests/test_route_fake-test-route.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_route_fake-test-route.obj: src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_route_fake-test-route.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo -c -o src/core/platform/tests/test_route_fake-test-route.obj `if test -f 'src/core/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-route.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Tpo src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-route.c' object='src/core/platform/tests/test_route_fake-test-route.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_route_fake-test-route.obj `if test -f 'src/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-route.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_route_fake-test-route.obj `if test -f 'src/core/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-route.c'; fi` -src/platform/tests/test_route_linux-test-route.o: src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_route_linux-test-route.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo -c -o src/platform/tests/test_route_linux-test-route.o `test -f 'src/platform/tests/test-route.c' || echo '$(srcdir)/'`src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-route.c' object='src/platform/tests/test_route_linux-test-route.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_route_linux-test-route.o: src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_route_linux-test-route.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo -c -o src/core/platform/tests/test_route_linux-test-route.o `test -f 'src/core/platform/tests/test-route.c' || echo '$(srcdir)/'`src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-route.c' object='src/core/platform/tests/test_route_linux-test-route.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_route_linux-test-route.o `test -f 'src/platform/tests/test-route.c' || echo '$(srcdir)/'`src/platform/tests/test-route.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_route_linux-test-route.o `test -f 'src/core/platform/tests/test-route.c' || echo '$(srcdir)/'`src/core/platform/tests/test-route.c -src/platform/tests/test_route_linux-test-route.obj: src/platform/tests/test-route.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_route_linux-test-route.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo -c -o src/platform/tests/test_route_linux-test-route.obj `if test -f 'src/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-route.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-route.c' object='src/platform/tests/test_route_linux-test-route.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_route_linux-test-route.obj: src/core/platform/tests/test-route.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_route_linux-test-route.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo -c -o src/core/platform/tests/test_route_linux-test-route.obj `if test -f 'src/core/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-route.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Tpo src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-route.c' object='src/core/platform/tests/test_route_linux-test-route.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_route_linux-test-route.obj `if test -f 'src/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-route.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_route_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_route_linux-test-route.obj `if test -f 'src/core/platform/tests/test-route.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-route.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-route.c'; fi` -src/platform/tests/test_tc_fake-test-tc.o: src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_tc_fake-test-tc.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo -c -o src/platform/tests/test_tc_fake-test-tc.o `test -f 'src/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-tc.c' object='src/platform/tests/test_tc_fake-test-tc.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_tc_fake-test-tc.o: src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_tc_fake-test-tc.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo -c -o src/core/platform/tests/test_tc_fake-test-tc.o `test -f 'src/core/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-tc.c' object='src/core/platform/tests/test_tc_fake-test-tc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_tc_fake-test-tc.o `test -f 'src/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/platform/tests/test-tc.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_tc_fake-test-tc.o `test -f 'src/core/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/core/platform/tests/test-tc.c -src/platform/tests/test_tc_fake-test-tc.obj: src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_tc_fake-test-tc.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo -c -o src/platform/tests/test_tc_fake-test-tc.obj `if test -f 'src/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-tc.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-tc.c' object='src/platform/tests/test_tc_fake-test-tc.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_tc_fake-test-tc.obj: src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_tc_fake-test-tc.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo -c -o src/core/platform/tests/test_tc_fake-test-tc.obj `if test -f 'src/core/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-tc.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Tpo src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-tc.c' object='src/core/platform/tests/test_tc_fake-test-tc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_tc_fake-test-tc.obj `if test -f 'src/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-tc.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_tc_fake-test-tc.obj `if test -f 'src/core/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-tc.c'; fi` -src/platform/tests/test_tc_linux-test-tc.o: src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_tc_linux-test-tc.o -MD -MP -MF src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo -c -o src/platform/tests/test_tc_linux-test-tc.o `test -f 'src/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-tc.c' object='src/platform/tests/test_tc_linux-test-tc.o' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_tc_linux-test-tc.o: src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_tc_linux-test-tc.o -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo -c -o src/core/platform/tests/test_tc_linux-test-tc.o `test -f 'src/core/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-tc.c' object='src/core/platform/tests/test_tc_linux-test-tc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_tc_linux-test-tc.o `test -f 'src/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/platform/tests/test-tc.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_tc_linux-test-tc.o `test -f 'src/core/platform/tests/test-tc.c' || echo '$(srcdir)/'`src/core/platform/tests/test-tc.c -src/platform/tests/test_tc_linux-test-tc.obj: src/platform/tests/test-tc.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/platform/tests/test_tc_linux-test-tc.obj -MD -MP -MF src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo -c -o src/platform/tests/test_tc_linux-test-tc.obj `if test -f 'src/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-tc.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/platform/tests/test-tc.c' object='src/platform/tests/test_tc_linux-test-tc.obj' libtool=no @AMDEPBACKSLASH@ +src/core/platform/tests/test_tc_linux-test-tc.obj: src/core/platform/tests/test-tc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/platform/tests/test_tc_linux-test-tc.obj -MD -MP -MF src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo -c -o src/core/platform/tests/test_tc_linux-test-tc.obj `if test -f 'src/core/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-tc.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Tpo src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/platform/tests/test-tc.c' object='src/core/platform/tests/test_tc_linux-test-tc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/platform/tests/test_tc_linux-test-tc.obj `if test -f 'src/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/platform/tests/test-tc.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_platform_tests_test_tc_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/platform/tests/test_tc_linux-test-tc.obj `if test -f 'src/core/platform/tests/test-tc.c'; then $(CYGPATH_W) 'src/core/platform/tests/test-tc.c'; else $(CYGPATH_W) '$(srcdir)/src/core/platform/tests/test-tc.c'; fi` -src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o: src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o -MD -MP -MF src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo -c -o src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o `test -f 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' object='src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o: src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o -MD -MP -MF src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo -c -o src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o `test -f 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' object='src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o `test -f 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' || echo '$(srcdir)/'`src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.o `test -f 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj: src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj -MD -MP -MF src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo -c -o src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj `if test -f 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; then $(CYGPATH_W) 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' object='src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj: src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj -MD -MP -MF src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo -c -o src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj `if test -f 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; then $(CYGPATH_W) 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Tpo src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c' object='src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj `if test -f 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; then $(CYGPATH_W) 'src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifcfg-rh/tests/test_ifcfg_rh-test-ifcfg-rh.obj `if test -f 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; then $(CYGPATH_W) 'src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c'; fi` -src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o: src/settings/plugins/ifupdown/tests/test-ifupdown.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o -MD -MP -MF src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo -c -o src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o `test -f 'src/settings/plugins/ifupdown/tests/test-ifupdown.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/tests/test-ifupdown.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifupdown/tests/test-ifupdown.c' object='src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o: src/core/settings/plugins/ifupdown/tests/test-ifupdown.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o -MD -MP -MF src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo -c -o src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o `test -f 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/tests/test-ifupdown.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifupdown/tests/test-ifupdown.c' object='src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o `test -f 'src/settings/plugins/ifupdown/tests/test-ifupdown.c' || echo '$(srcdir)/'`src/settings/plugins/ifupdown/tests/test-ifupdown.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.o `test -f 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c' || echo '$(srcdir)/'`src/core/settings/plugins/ifupdown/tests/test-ifupdown.c -src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj: src/settings/plugins/ifupdown/tests/test-ifupdown.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj -MD -MP -MF src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo -c -o src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj `if test -f 'src/settings/plugins/ifupdown/tests/test-ifupdown.c'; then $(CYGPATH_W) 'src/settings/plugins/ifupdown/tests/test-ifupdown.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/ifupdown/tests/test-ifupdown.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/ifupdown/tests/test-ifupdown.c' object='src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj: src/core/settings/plugins/ifupdown/tests/test-ifupdown.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj -MD -MP -MF src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo -c -o src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj `if test -f 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; then $(CYGPATH_W) 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Tpo src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/ifupdown/tests/test-ifupdown.c' object='src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj `if test -f 'src/settings/plugins/ifupdown/tests/test-ifupdown.c'; then $(CYGPATH_W) 'src/settings/plugins/ifupdown/tests/test-ifupdown.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/ifupdown/tests/test-ifupdown.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_ifupdown_tests_test_ifupdown_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/ifupdown/tests/test_ifupdown-test-ifupdown.obj `if test -f 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; then $(CYGPATH_W) 'src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c'; fi` -src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o: src/settings/plugins/keyfile/tests/test-keyfile-settings.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o -MD -MP -MF src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo -c -o src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o `test -f 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/tests/test-keyfile-settings.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/tests/test-keyfile-settings.c' object='src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o: src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o -MD -MP -MF src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo -c -o src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o `test -f 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c' object='src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o `test -f 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c' || echo '$(srcdir)/'`src/settings/plugins/keyfile/tests/test-keyfile-settings.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.o `test -f 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c' || echo '$(srcdir)/'`src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c -src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj: src/settings/plugins/keyfile/tests/test-keyfile-settings.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj -MD -MP -MF src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo -c -o src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj `if test -f 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; then $(CYGPATH_W) 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings/plugins/keyfile/tests/test-keyfile-settings.c' object='src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj' libtool=no @AMDEPBACKSLASH@ +src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj: src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj -MD -MP -MF src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo -c -o src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj `if test -f 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; then $(CYGPATH_W) 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Tpo src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c' object='src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj `if test -f 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; then $(CYGPATH_W) 'src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; else $(CYGPATH_W) '$(srcdir)/src/settings/plugins/keyfile/tests/test-keyfile-settings.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/settings/plugins/keyfile/tests/test_keyfile_settings-test-keyfile-settings.obj `if test -f 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; then $(CYGPATH_W) 'src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; else $(CYGPATH_W) '$(srcdir)/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c'; fi` -src/supplicant/tests/test_supplicant_config-test-supplicant-config.o: src/supplicant/tests/test-supplicant-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/tests/test_supplicant_config-test-supplicant-config.o -MD -MP -MF src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo -c -o src/supplicant/tests/test_supplicant_config-test-supplicant-config.o `test -f 'src/supplicant/tests/test-supplicant-config.c' || echo '$(srcdir)/'`src/supplicant/tests/test-supplicant-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/tests/test-supplicant-config.c' object='src/supplicant/tests/test_supplicant_config-test-supplicant-config.o' libtool=no @AMDEPBACKSLASH@ +src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.o: src/core/supplicant/tests/test-supplicant-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.o -MD -MP -MF src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo -c -o src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.o `test -f 'src/core/supplicant/tests/test-supplicant-config.c' || echo '$(srcdir)/'`src/core/supplicant/tests/test-supplicant-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/tests/test-supplicant-config.c' object='src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/tests/test_supplicant_config-test-supplicant-config.o `test -f 'src/supplicant/tests/test-supplicant-config.c' || echo '$(srcdir)/'`src/supplicant/tests/test-supplicant-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.o `test -f 'src/core/supplicant/tests/test-supplicant-config.c' || echo '$(srcdir)/'`src/core/supplicant/tests/test-supplicant-config.c -src/supplicant/tests/test_supplicant_config-test-supplicant-config.obj: src/supplicant/tests/test-supplicant-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/supplicant/tests/test_supplicant_config-test-supplicant-config.obj -MD -MP -MF src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo -c -o src/supplicant/tests/test_supplicant_config-test-supplicant-config.obj `if test -f 'src/supplicant/tests/test-supplicant-config.c'; then $(CYGPATH_W) 'src/supplicant/tests/test-supplicant-config.c'; else $(CYGPATH_W) '$(srcdir)/src/supplicant/tests/test-supplicant-config.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/supplicant/tests/test-supplicant-config.c' object='src/supplicant/tests/test_supplicant_config-test-supplicant-config.obj' libtool=no @AMDEPBACKSLASH@ +src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.obj: src/core/supplicant/tests/test-supplicant-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.obj -MD -MP -MF src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo -c -o src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.obj `if test -f 'src/core/supplicant/tests/test-supplicant-config.c'; then $(CYGPATH_W) 'src/core/supplicant/tests/test-supplicant-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/supplicant/tests/test-supplicant-config.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Tpo src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/supplicant/tests/test-supplicant-config.c' object='src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/supplicant/tests/test_supplicant_config-test-supplicant-config.obj `if test -f 'src/supplicant/tests/test-supplicant-config.c'; then $(CYGPATH_W) 'src/supplicant/tests/test-supplicant-config.c'; else $(CYGPATH_W) '$(srcdir)/src/supplicant/tests/test-supplicant-config.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_supplicant_tests_test_supplicant_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/supplicant/tests/test_supplicant_config-test-supplicant-config.obj `if test -f 'src/core/supplicant/tests/test-supplicant-config.c'; then $(CYGPATH_W) 'src/core/supplicant/tests/test-supplicant-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/supplicant/tests/test-supplicant-config.c'; fi` -src/tests/config/test_config-nm-test-device.o: src/tests/config/nm-test-device.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/config/test_config-nm-test-device.o -MD -MP -MF src/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo -c -o src/tests/config/test_config-nm-test-device.o `test -f 'src/tests/config/nm-test-device.c' || echo '$(srcdir)/'`src/tests/config/nm-test-device.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/config/nm-test-device.c' object='src/tests/config/test_config-nm-test-device.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/config/test_config-nm-test-device.o: src/core/tests/config/nm-test-device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/config/test_config-nm-test-device.o -MD -MP -MF src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo -c -o src/core/tests/config/test_config-nm-test-device.o `test -f 'src/core/tests/config/nm-test-device.c' || echo '$(srcdir)/'`src/core/tests/config/nm-test-device.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/config/nm-test-device.c' object='src/core/tests/config/test_config-nm-test-device.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/config/test_config-nm-test-device.o `test -f 'src/tests/config/nm-test-device.c' || echo '$(srcdir)/'`src/tests/config/nm-test-device.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/config/test_config-nm-test-device.o `test -f 'src/core/tests/config/nm-test-device.c' || echo '$(srcdir)/'`src/core/tests/config/nm-test-device.c -src/tests/config/test_config-nm-test-device.obj: src/tests/config/nm-test-device.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/config/test_config-nm-test-device.obj -MD -MP -MF src/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo -c -o src/tests/config/test_config-nm-test-device.obj `if test -f 'src/tests/config/nm-test-device.c'; then $(CYGPATH_W) 'src/tests/config/nm-test-device.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/config/nm-test-device.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/config/nm-test-device.c' object='src/tests/config/test_config-nm-test-device.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/config/test_config-nm-test-device.obj: src/core/tests/config/nm-test-device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/config/test_config-nm-test-device.obj -MD -MP -MF src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo -c -o src/core/tests/config/test_config-nm-test-device.obj `if test -f 'src/core/tests/config/nm-test-device.c'; then $(CYGPATH_W) 'src/core/tests/config/nm-test-device.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/config/nm-test-device.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Tpo src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/config/nm-test-device.c' object='src/core/tests/config/test_config-nm-test-device.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/config/test_config-nm-test-device.obj `if test -f 'src/tests/config/nm-test-device.c'; then $(CYGPATH_W) 'src/tests/config/nm-test-device.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/config/nm-test-device.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/config/test_config-nm-test-device.obj `if test -f 'src/core/tests/config/nm-test-device.c'; then $(CYGPATH_W) 'src/core/tests/config/nm-test-device.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/config/nm-test-device.c'; fi` -src/tests/config/test_config-test-config.o: src/tests/config/test-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/config/test_config-test-config.o -MD -MP -MF src/tests/config/$(DEPDIR)/test_config-test-config.Tpo -c -o src/tests/config/test_config-test-config.o `test -f 'src/tests/config/test-config.c' || echo '$(srcdir)/'`src/tests/config/test-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/config/$(DEPDIR)/test_config-test-config.Tpo src/tests/config/$(DEPDIR)/test_config-test-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/config/test-config.c' object='src/tests/config/test_config-test-config.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/config/test_config-test-config.o: src/core/tests/config/test-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/config/test_config-test-config.o -MD -MP -MF src/core/tests/config/$(DEPDIR)/test_config-test-config.Tpo -c -o src/core/tests/config/test_config-test-config.o `test -f 'src/core/tests/config/test-config.c' || echo '$(srcdir)/'`src/core/tests/config/test-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/config/$(DEPDIR)/test_config-test-config.Tpo src/core/tests/config/$(DEPDIR)/test_config-test-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/config/test-config.c' object='src/core/tests/config/test_config-test-config.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/config/test_config-test-config.o `test -f 'src/tests/config/test-config.c' || echo '$(srcdir)/'`src/tests/config/test-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/config/test_config-test-config.o `test -f 'src/core/tests/config/test-config.c' || echo '$(srcdir)/'`src/core/tests/config/test-config.c -src/tests/config/test_config-test-config.obj: src/tests/config/test-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/config/test_config-test-config.obj -MD -MP -MF src/tests/config/$(DEPDIR)/test_config-test-config.Tpo -c -o src/tests/config/test_config-test-config.obj `if test -f 'src/tests/config/test-config.c'; then $(CYGPATH_W) 'src/tests/config/test-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/config/test-config.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/config/$(DEPDIR)/test_config-test-config.Tpo src/tests/config/$(DEPDIR)/test_config-test-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/config/test-config.c' object='src/tests/config/test_config-test-config.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/config/test_config-test-config.obj: src/core/tests/config/test-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/config/test_config-test-config.obj -MD -MP -MF src/core/tests/config/$(DEPDIR)/test_config-test-config.Tpo -c -o src/core/tests/config/test_config-test-config.obj `if test -f 'src/core/tests/config/test-config.c'; then $(CYGPATH_W) 'src/core/tests/config/test-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/config/test-config.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/config/$(DEPDIR)/test_config-test-config.Tpo src/core/tests/config/$(DEPDIR)/test_config-test-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/config/test-config.c' object='src/core/tests/config/test_config-test-config.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/config/test_config-test-config.obj `if test -f 'src/tests/config/test-config.c'; then $(CYGPATH_W) 'src/tests/config/test-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/config/test-config.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_config_test_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/config/test_config-test-config.obj `if test -f 'src/core/tests/config/test-config.c'; then $(CYGPATH_W) 'src/core/tests/config/test-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/config/test-config.c'; fi` -src/tests/test_core-test-core.o: src/tests/test-core.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_core-test-core.o -MD -MP -MF src/tests/$(DEPDIR)/test_core-test-core.Tpo -c -o src/tests/test_core-test-core.o `test -f 'src/tests/test-core.c' || echo '$(srcdir)/'`src/tests/test-core.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_core-test-core.Tpo src/tests/$(DEPDIR)/test_core-test-core.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-core.c' object='src/tests/test_core-test-core.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_core-test-core.o: src/core/tests/test-core.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_core-test-core.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_core-test-core.Tpo -c -o src/core/tests/test_core-test-core.o `test -f 'src/core/tests/test-core.c' || echo '$(srcdir)/'`src/core/tests/test-core.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_core-test-core.Tpo src/core/tests/$(DEPDIR)/test_core-test-core.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-core.c' object='src/core/tests/test_core-test-core.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_core-test-core.o `test -f 'src/tests/test-core.c' || echo '$(srcdir)/'`src/tests/test-core.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_core-test-core.o `test -f 'src/core/tests/test-core.c' || echo '$(srcdir)/'`src/core/tests/test-core.c -src/tests/test_core-test-core.obj: src/tests/test-core.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_core-test-core.obj -MD -MP -MF src/tests/$(DEPDIR)/test_core-test-core.Tpo -c -o src/tests/test_core-test-core.obj `if test -f 'src/tests/test-core.c'; then $(CYGPATH_W) 'src/tests/test-core.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-core.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_core-test-core.Tpo src/tests/$(DEPDIR)/test_core-test-core.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-core.c' object='src/tests/test_core-test-core.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_core-test-core.obj: src/core/tests/test-core.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_core-test-core.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_core-test-core.Tpo -c -o src/core/tests/test_core-test-core.obj `if test -f 'src/core/tests/test-core.c'; then $(CYGPATH_W) 'src/core/tests/test-core.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-core.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_core-test-core.Tpo src/core/tests/$(DEPDIR)/test_core-test-core.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-core.c' object='src/core/tests/test_core-test-core.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_core-test-core.obj `if test -f 'src/tests/test-core.c'; then $(CYGPATH_W) 'src/tests/test-core.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-core.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_core-test-core.obj `if test -f 'src/core/tests/test-core.c'; then $(CYGPATH_W) 'src/core/tests/test-core.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-core.c'; fi` -src/tests/test_core_with_expect-test-core-with-expect.o: src/tests/test-core-with-expect.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_core_with_expect-test-core-with-expect.o -MD -MP -MF src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo -c -o src/tests/test_core_with_expect-test-core-with-expect.o `test -f 'src/tests/test-core-with-expect.c' || echo '$(srcdir)/'`src/tests/test-core-with-expect.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-core-with-expect.c' object='src/tests/test_core_with_expect-test-core-with-expect.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_core_with_expect-test-core-with-expect.o: src/core/tests/test-core-with-expect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_core_with_expect-test-core-with-expect.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo -c -o src/core/tests/test_core_with_expect-test-core-with-expect.o `test -f 'src/core/tests/test-core-with-expect.c' || echo '$(srcdir)/'`src/core/tests/test-core-with-expect.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-core-with-expect.c' object='src/core/tests/test_core_with_expect-test-core-with-expect.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_core_with_expect-test-core-with-expect.o `test -f 'src/tests/test-core-with-expect.c' || echo '$(srcdir)/'`src/tests/test-core-with-expect.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_core_with_expect-test-core-with-expect.o `test -f 'src/core/tests/test-core-with-expect.c' || echo '$(srcdir)/'`src/core/tests/test-core-with-expect.c -src/tests/test_core_with_expect-test-core-with-expect.obj: src/tests/test-core-with-expect.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_core_with_expect-test-core-with-expect.obj -MD -MP -MF src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo -c -o src/tests/test_core_with_expect-test-core-with-expect.obj `if test -f 'src/tests/test-core-with-expect.c'; then $(CYGPATH_W) 'src/tests/test-core-with-expect.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-core-with-expect.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-core-with-expect.c' object='src/tests/test_core_with_expect-test-core-with-expect.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_core_with_expect-test-core-with-expect.obj: src/core/tests/test-core-with-expect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_core_with_expect-test-core-with-expect.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo -c -o src/core/tests/test_core_with_expect-test-core-with-expect.obj `if test -f 'src/core/tests/test-core-with-expect.c'; then $(CYGPATH_W) 'src/core/tests/test-core-with-expect.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-core-with-expect.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Tpo src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-core-with-expect.c' object='src/core/tests/test_core_with_expect-test-core-with-expect.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_core_with_expect-test-core-with-expect.obj `if test -f 'src/tests/test-core-with-expect.c'; then $(CYGPATH_W) 'src/tests/test-core-with-expect.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-core-with-expect.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_core_with_expect_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_core_with_expect-test-core-with-expect.obj `if test -f 'src/core/tests/test-core-with-expect.c'; then $(CYGPATH_W) 'src/core/tests/test-core-with-expect.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-core-with-expect.c'; fi` -src/tests/test_dcb-test-dcb.o: src/tests/test-dcb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_dcb-test-dcb.o -MD -MP -MF src/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo -c -o src/tests/test_dcb-test-dcb.o `test -f 'src/tests/test-dcb.c' || echo '$(srcdir)/'`src/tests/test-dcb.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo src/tests/$(DEPDIR)/test_dcb-test-dcb.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-dcb.c' object='src/tests/test_dcb-test-dcb.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_dcb-test-dcb.o: src/core/tests/test-dcb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_dcb-test-dcb.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo -c -o src/core/tests/test_dcb-test-dcb.o `test -f 'src/core/tests/test-dcb.c' || echo '$(srcdir)/'`src/core/tests/test-dcb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-dcb.c' object='src/core/tests/test_dcb-test-dcb.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_dcb-test-dcb.o `test -f 'src/tests/test-dcb.c' || echo '$(srcdir)/'`src/tests/test-dcb.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_dcb-test-dcb.o `test -f 'src/core/tests/test-dcb.c' || echo '$(srcdir)/'`src/core/tests/test-dcb.c -src/tests/test_dcb-test-dcb.obj: src/tests/test-dcb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_dcb-test-dcb.obj -MD -MP -MF src/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo -c -o src/tests/test_dcb-test-dcb.obj `if test -f 'src/tests/test-dcb.c'; then $(CYGPATH_W) 'src/tests/test-dcb.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-dcb.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo src/tests/$(DEPDIR)/test_dcb-test-dcb.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-dcb.c' object='src/tests/test_dcb-test-dcb.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_dcb-test-dcb.obj: src/core/tests/test-dcb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_dcb-test-dcb.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo -c -o src/core/tests/test_dcb-test-dcb.obj `if test -f 'src/core/tests/test-dcb.c'; then $(CYGPATH_W) 'src/core/tests/test-dcb.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-dcb.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Tpo src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-dcb.c' object='src/core/tests/test_dcb-test-dcb.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_dcb-test-dcb.obj `if test -f 'src/tests/test-dcb.c'; then $(CYGPATH_W) 'src/tests/test-dcb.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-dcb.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_dcb_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_dcb-test-dcb.obj `if test -f 'src/core/tests/test-dcb.c'; then $(CYGPATH_W) 'src/core/tests/test-dcb.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-dcb.c'; fi` -src/tests/test_ip4_config-test-ip4-config.o: src/tests/test-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_ip4_config-test-ip4-config.o -MD -MP -MF src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo -c -o src/tests/test_ip4_config-test-ip4-config.o `test -f 'src/tests/test-ip4-config.c' || echo '$(srcdir)/'`src/tests/test-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-ip4-config.c' object='src/tests/test_ip4_config-test-ip4-config.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_ip4_config-test-ip4-config.o: src/core/tests/test-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_ip4_config-test-ip4-config.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo -c -o src/core/tests/test_ip4_config-test-ip4-config.o `test -f 'src/core/tests/test-ip4-config.c' || echo '$(srcdir)/'`src/core/tests/test-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-ip4-config.c' object='src/core/tests/test_ip4_config-test-ip4-config.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_ip4_config-test-ip4-config.o `test -f 'src/tests/test-ip4-config.c' || echo '$(srcdir)/'`src/tests/test-ip4-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_ip4_config-test-ip4-config.o `test -f 'src/core/tests/test-ip4-config.c' || echo '$(srcdir)/'`src/core/tests/test-ip4-config.c -src/tests/test_ip4_config-test-ip4-config.obj: src/tests/test-ip4-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_ip4_config-test-ip4-config.obj -MD -MP -MF src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo -c -o src/tests/test_ip4_config-test-ip4-config.obj `if test -f 'src/tests/test-ip4-config.c'; then $(CYGPATH_W) 'src/tests/test-ip4-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-ip4-config.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-ip4-config.c' object='src/tests/test_ip4_config-test-ip4-config.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_ip4_config-test-ip4-config.obj: src/core/tests/test-ip4-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_ip4_config-test-ip4-config.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo -c -o src/core/tests/test_ip4_config-test-ip4-config.obj `if test -f 'src/core/tests/test-ip4-config.c'; then $(CYGPATH_W) 'src/core/tests/test-ip4-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-ip4-config.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Tpo src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-ip4-config.c' object='src/core/tests/test_ip4_config-test-ip4-config.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_ip4_config-test-ip4-config.obj `if test -f 'src/tests/test-ip4-config.c'; then $(CYGPATH_W) 'src/tests/test-ip4-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-ip4-config.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip4_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_ip4_config-test-ip4-config.obj `if test -f 'src/core/tests/test-ip4-config.c'; then $(CYGPATH_W) 'src/core/tests/test-ip4-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-ip4-config.c'; fi` -src/tests/test_ip6_config-test-ip6-config.o: src/tests/test-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_ip6_config-test-ip6-config.o -MD -MP -MF src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo -c -o src/tests/test_ip6_config-test-ip6-config.o `test -f 'src/tests/test-ip6-config.c' || echo '$(srcdir)/'`src/tests/test-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-ip6-config.c' object='src/tests/test_ip6_config-test-ip6-config.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_ip6_config-test-ip6-config.o: src/core/tests/test-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_ip6_config-test-ip6-config.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo -c -o src/core/tests/test_ip6_config-test-ip6-config.o `test -f 'src/core/tests/test-ip6-config.c' || echo '$(srcdir)/'`src/core/tests/test-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-ip6-config.c' object='src/core/tests/test_ip6_config-test-ip6-config.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_ip6_config-test-ip6-config.o `test -f 'src/tests/test-ip6-config.c' || echo '$(srcdir)/'`src/tests/test-ip6-config.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_ip6_config-test-ip6-config.o `test -f 'src/core/tests/test-ip6-config.c' || echo '$(srcdir)/'`src/core/tests/test-ip6-config.c -src/tests/test_ip6_config-test-ip6-config.obj: src/tests/test-ip6-config.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_ip6_config-test-ip6-config.obj -MD -MP -MF src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo -c -o src/tests/test_ip6_config-test-ip6-config.obj `if test -f 'src/tests/test-ip6-config.c'; then $(CYGPATH_W) 'src/tests/test-ip6-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-ip6-config.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-ip6-config.c' object='src/tests/test_ip6_config-test-ip6-config.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_ip6_config-test-ip6-config.obj: src/core/tests/test-ip6-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_ip6_config-test-ip6-config.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo -c -o src/core/tests/test_ip6_config-test-ip6-config.obj `if test -f 'src/core/tests/test-ip6-config.c'; then $(CYGPATH_W) 'src/core/tests/test-ip6-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-ip6-config.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Tpo src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-ip6-config.c' object='src/core/tests/test_ip6_config-test-ip6-config.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_ip6_config-test-ip6-config.obj `if test -f 'src/tests/test-ip6-config.c'; then $(CYGPATH_W) 'src/tests/test-ip6-config.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-ip6-config.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_ip6_config_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_ip6_config-test-ip6-config.obj `if test -f 'src/core/tests/test-ip6-config.c'; then $(CYGPATH_W) 'src/core/tests/test-ip6-config.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-ip6-config.c'; fi` -src/tests/test_l3cfg-test-l3cfg.o: src/tests/test-l3cfg.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_l3cfg-test-l3cfg.o -MD -MP -MF src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo -c -o src/tests/test_l3cfg-test-l3cfg.o `test -f 'src/tests/test-l3cfg.c' || echo '$(srcdir)/'`src/tests/test-l3cfg.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-l3cfg.c' object='src/tests/test_l3cfg-test-l3cfg.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_l3cfg-test-l3cfg.o: src/core/tests/test-l3cfg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_l3cfg-test-l3cfg.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo -c -o src/core/tests/test_l3cfg-test-l3cfg.o `test -f 'src/core/tests/test-l3cfg.c' || echo '$(srcdir)/'`src/core/tests/test-l3cfg.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-l3cfg.c' object='src/core/tests/test_l3cfg-test-l3cfg.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_l3cfg-test-l3cfg.o `test -f 'src/tests/test-l3cfg.c' || echo '$(srcdir)/'`src/tests/test-l3cfg.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_l3cfg-test-l3cfg.o `test -f 'src/core/tests/test-l3cfg.c' || echo '$(srcdir)/'`src/core/tests/test-l3cfg.c -src/tests/test_l3cfg-test-l3cfg.obj: src/tests/test-l3cfg.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_l3cfg-test-l3cfg.obj -MD -MP -MF src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo -c -o src/tests/test_l3cfg-test-l3cfg.obj `if test -f 'src/tests/test-l3cfg.c'; then $(CYGPATH_W) 'src/tests/test-l3cfg.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-l3cfg.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-l3cfg.c' object='src/tests/test_l3cfg-test-l3cfg.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_l3cfg-test-l3cfg.obj: src/core/tests/test-l3cfg.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_l3cfg-test-l3cfg.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo -c -o src/core/tests/test_l3cfg-test-l3cfg.obj `if test -f 'src/core/tests/test-l3cfg.c'; then $(CYGPATH_W) 'src/core/tests/test-l3cfg.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-l3cfg.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Tpo src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-l3cfg.c' object='src/core/tests/test_l3cfg-test-l3cfg.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_l3cfg-test-l3cfg.obj `if test -f 'src/tests/test-l3cfg.c'; then $(CYGPATH_W) 'src/tests/test-l3cfg.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-l3cfg.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_l3cfg_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_l3cfg-test-l3cfg.obj `if test -f 'src/core/tests/test-l3cfg.c'; then $(CYGPATH_W) 'src/core/tests/test-l3cfg.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-l3cfg.c'; fi` -src/tests/test_systemd-test-systemd.o: src/tests/test-systemd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_systemd-test-systemd.o -MD -MP -MF src/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo -c -o src/tests/test_systemd-test-systemd.o `test -f 'src/tests/test-systemd.c' || echo '$(srcdir)/'`src/tests/test-systemd.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo src/tests/$(DEPDIR)/test_systemd-test-systemd.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-systemd.c' object='src/tests/test_systemd-test-systemd.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_systemd-test-systemd.o: src/core/tests/test-systemd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_systemd-test-systemd.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo -c -o src/core/tests/test_systemd-test-systemd.o `test -f 'src/core/tests/test-systemd.c' || echo '$(srcdir)/'`src/core/tests/test-systemd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-systemd.c' object='src/core/tests/test_systemd-test-systemd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_systemd-test-systemd.o `test -f 'src/tests/test-systemd.c' || echo '$(srcdir)/'`src/tests/test-systemd.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_systemd-test-systemd.o `test -f 'src/core/tests/test-systemd.c' || echo '$(srcdir)/'`src/core/tests/test-systemd.c -src/tests/test_systemd-test-systemd.obj: src/tests/test-systemd.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_systemd-test-systemd.obj -MD -MP -MF src/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo -c -o src/tests/test_systemd-test-systemd.obj `if test -f 'src/tests/test-systemd.c'; then $(CYGPATH_W) 'src/tests/test-systemd.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-systemd.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo src/tests/$(DEPDIR)/test_systemd-test-systemd.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-systemd.c' object='src/tests/test_systemd-test-systemd.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_systemd-test-systemd.obj: src/core/tests/test-systemd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_systemd-test-systemd.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo -c -o src/core/tests/test_systemd-test-systemd.obj `if test -f 'src/core/tests/test-systemd.c'; then $(CYGPATH_W) 'src/core/tests/test-systemd.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-systemd.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Tpo src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-systemd.c' object='src/core/tests/test_systemd-test-systemd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_systemd-test-systemd.obj `if test -f 'src/tests/test-systemd.c'; then $(CYGPATH_W) 'src/tests/test-systemd.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-systemd.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_systemd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_systemd-test-systemd.obj `if test -f 'src/core/tests/test-systemd.c'; then $(CYGPATH_W) 'src/core/tests/test-systemd.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-systemd.c'; fi` -src/tests/test_utils-test-utils.o: src/tests/test-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_utils-test-utils.o -MD -MP -MF src/tests/$(DEPDIR)/test_utils-test-utils.Tpo -c -o src/tests/test_utils-test-utils.o `test -f 'src/tests/test-utils.c' || echo '$(srcdir)/'`src/tests/test-utils.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_utils-test-utils.Tpo src/tests/$(DEPDIR)/test_utils-test-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-utils.c' object='src/tests/test_utils-test-utils.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_utils-test-utils.o: src/core/tests/test-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_utils-test-utils.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_utils-test-utils.Tpo -c -o src/core/tests/test_utils-test-utils.o `test -f 'src/core/tests/test-utils.c' || echo '$(srcdir)/'`src/core/tests/test-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_utils-test-utils.Tpo src/core/tests/$(DEPDIR)/test_utils-test-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-utils.c' object='src/core/tests/test_utils-test-utils.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_utils-test-utils.o `test -f 'src/tests/test-utils.c' || echo '$(srcdir)/'`src/tests/test-utils.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_utils-test-utils.o `test -f 'src/core/tests/test-utils.c' || echo '$(srcdir)/'`src/core/tests/test-utils.c -src/tests/test_utils-test-utils.obj: src/tests/test-utils.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_utils-test-utils.obj -MD -MP -MF src/tests/$(DEPDIR)/test_utils-test-utils.Tpo -c -o src/tests/test_utils-test-utils.obj `if test -f 'src/tests/test-utils.c'; then $(CYGPATH_W) 'src/tests/test-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-utils.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_utils-test-utils.Tpo src/tests/$(DEPDIR)/test_utils-test-utils.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-utils.c' object='src/tests/test_utils-test-utils.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_utils-test-utils.obj: src/core/tests/test-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_utils-test-utils.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_utils-test-utils.Tpo -c -o src/core/tests/test_utils-test-utils.obj `if test -f 'src/core/tests/test-utils.c'; then $(CYGPATH_W) 'src/core/tests/test-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_utils-test-utils.Tpo src/core/tests/$(DEPDIR)/test_utils-test-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-utils.c' object='src/core/tests/test_utils-test-utils.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_utils-test-utils.obj `if test -f 'src/tests/test-utils.c'; then $(CYGPATH_W) 'src/tests/test-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-utils.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_utils_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_utils-test-utils.obj `if test -f 'src/core/tests/test-utils.c'; then $(CYGPATH_W) 'src/core/tests/test-utils.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-utils.c'; fi` -src/tests/test_wired_defname-test-wired-defname.o: src/tests/test-wired-defname.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_wired_defname-test-wired-defname.o -MD -MP -MF src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo -c -o src/tests/test_wired_defname-test-wired-defname.o `test -f 'src/tests/test-wired-defname.c' || echo '$(srcdir)/'`src/tests/test-wired-defname.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-wired-defname.c' object='src/tests/test_wired_defname-test-wired-defname.o' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_wired_defname-test-wired-defname.o: src/core/tests/test-wired-defname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_wired_defname-test-wired-defname.o -MD -MP -MF src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo -c -o src/core/tests/test_wired_defname-test-wired-defname.o `test -f 'src/core/tests/test-wired-defname.c' || echo '$(srcdir)/'`src/core/tests/test-wired-defname.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-wired-defname.c' object='src/core/tests/test_wired_defname-test-wired-defname.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_wired_defname-test-wired-defname.o `test -f 'src/tests/test-wired-defname.c' || echo '$(srcdir)/'`src/tests/test-wired-defname.c +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_wired_defname-test-wired-defname.o `test -f 'src/core/tests/test-wired-defname.c' || echo '$(srcdir)/'`src/core/tests/test-wired-defname.c -src/tests/test_wired_defname-test-wired-defname.obj: src/tests/test-wired-defname.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/tests/test_wired_defname-test-wired-defname.obj -MD -MP -MF src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo -c -o src/tests/test_wired_defname-test-wired-defname.obj `if test -f 'src/tests/test-wired-defname.c'; then $(CYGPATH_W) 'src/tests/test-wired-defname.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-wired-defname.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/tests/test-wired-defname.c' object='src/tests/test_wired_defname-test-wired-defname.obj' libtool=no @AMDEPBACKSLASH@ +src/core/tests/test_wired_defname-test-wired-defname.obj: src/core/tests/test-wired-defname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/core/tests/test_wired_defname-test-wired-defname.obj -MD -MP -MF src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo -c -o src/core/tests/test_wired_defname-test-wired-defname.obj `if test -f 'src/core/tests/test-wired-defname.c'; then $(CYGPATH_W) 'src/core/tests/test-wired-defname.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-wired-defname.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Tpo src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/core/tests/test-wired-defname.c' object='src/core/tests/test_wired_defname-test-wired-defname.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/tests/test_wired_defname-test-wired-defname.obj `if test -f 'src/tests/test-wired-defname.c'; then $(CYGPATH_W) 'src/tests/test-wired-defname.c'; else $(CYGPATH_W) '$(srcdir)/src/tests/test-wired-defname.c'; fi` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_core_tests_test_wired_defname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/core/tests/test_wired_defname-test-wired-defname.obj `if test -f 'src/core/tests/test-wired-defname.c'; then $(CYGPATH_W) 'src/core/tests/test-wired-defname.c'; else $(CYGPATH_W) '$(srcdir)/src/core/tests/test-wired-defname.c'; fi` .cpp.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -17416,49 +17511,49 @@ clean-libtool: -rm -rf shared/systemd/.libs shared/systemd/_libs -rm -rf shared/systemd/src/basic/.libs shared/systemd/src/basic/_libs -rm -rf shared/systemd/src/shared/.libs shared/systemd/src/shared/_libs - -rm -rf src/.libs src/_libs - -rm -rf src/devices/.libs src/devices/_libs - -rm -rf src/devices/adsl/.libs src/devices/adsl/_libs - -rm -rf src/devices/bluetooth/.libs src/devices/bluetooth/_libs - -rm -rf src/devices/bluetooth/tests/.libs src/devices/bluetooth/tests/_libs - -rm -rf src/devices/ovs/.libs src/devices/ovs/_libs - -rm -rf src/devices/team/.libs src/devices/team/_libs - -rm -rf src/devices/tests/.libs src/devices/tests/_libs - -rm -rf src/devices/wifi/.libs src/devices/wifi/_libs - -rm -rf src/devices/wifi/tests/.libs src/devices/wifi/tests/_libs - -rm -rf src/devices/wwan/.libs src/devices/wwan/_libs - -rm -rf src/devices/wwan/tests/.libs src/devices/wwan/tests/_libs - -rm -rf src/dhcp/.libs src/dhcp/_libs - -rm -rf src/dhcp/tests/.libs src/dhcp/tests/_libs - -rm -rf src/dns/.libs src/dns/_libs - -rm -rf src/dnsmasq/.libs src/dnsmasq/_libs - -rm -rf src/dnsmasq/tests/.libs src/dnsmasq/tests/_libs - -rm -rf src/initrd/.libs src/initrd/_libs - -rm -rf src/initrd/tests/.libs src/initrd/tests/_libs - -rm -rf src/ndisc/.libs src/ndisc/_libs - -rm -rf src/ndisc/tests/.libs src/ndisc/tests/_libs - -rm -rf src/platform/.libs src/platform/_libs - -rm -rf src/platform/tests/.libs src/platform/tests/_libs - -rm -rf src/platform/wifi/.libs src/platform/wifi/_libs - -rm -rf src/platform/wpan/.libs src/platform/wpan/_libs - -rm -rf src/ppp/.libs src/ppp/_libs - -rm -rf src/settings/.libs src/settings/_libs - -rm -rf src/settings/plugins/ifcfg-rh/.libs src/settings/plugins/ifcfg-rh/_libs - -rm -rf src/settings/plugins/ifcfg-rh/tests/.libs src/settings/plugins/ifcfg-rh/tests/_libs - -rm -rf src/settings/plugins/ifupdown/.libs src/settings/plugins/ifupdown/_libs - -rm -rf src/settings/plugins/ifupdown/tests/.libs src/settings/plugins/ifupdown/tests/_libs - -rm -rf src/settings/plugins/keyfile/.libs src/settings/plugins/keyfile/_libs - -rm -rf src/settings/plugins/keyfile/tests/.libs src/settings/plugins/keyfile/tests/_libs - -rm -rf src/supplicant/.libs src/supplicant/_libs - -rm -rf src/supplicant/tests/.libs src/supplicant/tests/_libs - -rm -rf src/systemd/.libs src/systemd/_libs - -rm -rf src/systemd/sd-adapt-core/.libs src/systemd/sd-adapt-core/_libs - -rm -rf src/systemd/src/libsystemd-network/.libs src/systemd/src/libsystemd-network/_libs - -rm -rf src/systemd/src/libsystemd/sd-event/.libs src/systemd/src/libsystemd/sd-event/_libs - -rm -rf src/systemd/src/libsystemd/sd-id128/.libs src/systemd/src/libsystemd/sd-id128/_libs - -rm -rf src/tests/.libs src/tests/_libs - -rm -rf src/tests/config/.libs src/tests/config/_libs - -rm -rf src/vpn/.libs src/vpn/_libs + -rm -rf src/core/.libs src/core/_libs + -rm -rf src/core/devices/.libs src/core/devices/_libs + -rm -rf src/core/devices/adsl/.libs src/core/devices/adsl/_libs + -rm -rf src/core/devices/bluetooth/.libs src/core/devices/bluetooth/_libs + -rm -rf src/core/devices/bluetooth/tests/.libs src/core/devices/bluetooth/tests/_libs + -rm -rf src/core/devices/ovs/.libs src/core/devices/ovs/_libs + -rm -rf src/core/devices/team/.libs src/core/devices/team/_libs + -rm -rf src/core/devices/tests/.libs src/core/devices/tests/_libs + -rm -rf src/core/devices/wifi/.libs src/core/devices/wifi/_libs + -rm -rf src/core/devices/wifi/tests/.libs src/core/devices/wifi/tests/_libs + -rm -rf src/core/devices/wwan/.libs src/core/devices/wwan/_libs + -rm -rf src/core/devices/wwan/tests/.libs src/core/devices/wwan/tests/_libs + -rm -rf src/core/dhcp/.libs src/core/dhcp/_libs + -rm -rf src/core/dhcp/tests/.libs src/core/dhcp/tests/_libs + -rm -rf src/core/dns/.libs src/core/dns/_libs + -rm -rf src/core/dnsmasq/.libs src/core/dnsmasq/_libs + -rm -rf src/core/dnsmasq/tests/.libs src/core/dnsmasq/tests/_libs + -rm -rf src/core/initrd/.libs src/core/initrd/_libs + -rm -rf src/core/initrd/tests/.libs src/core/initrd/tests/_libs + -rm -rf src/core/ndisc/.libs src/core/ndisc/_libs + -rm -rf src/core/ndisc/tests/.libs src/core/ndisc/tests/_libs + -rm -rf src/core/platform/.libs src/core/platform/_libs + -rm -rf src/core/platform/tests/.libs src/core/platform/tests/_libs + -rm -rf src/core/platform/wifi/.libs src/core/platform/wifi/_libs + -rm -rf src/core/platform/wpan/.libs src/core/platform/wpan/_libs + -rm -rf src/core/ppp/.libs src/core/ppp/_libs + -rm -rf src/core/settings/.libs src/core/settings/_libs + -rm -rf src/core/settings/plugins/ifcfg-rh/.libs src/core/settings/plugins/ifcfg-rh/_libs + -rm -rf src/core/settings/plugins/ifcfg-rh/tests/.libs src/core/settings/plugins/ifcfg-rh/tests/_libs + -rm -rf src/core/settings/plugins/ifupdown/.libs src/core/settings/plugins/ifupdown/_libs + -rm -rf src/core/settings/plugins/ifupdown/tests/.libs src/core/settings/plugins/ifupdown/tests/_libs + -rm -rf src/core/settings/plugins/keyfile/.libs src/core/settings/plugins/keyfile/_libs + -rm -rf src/core/settings/plugins/keyfile/tests/.libs src/core/settings/plugins/keyfile/tests/_libs + -rm -rf src/core/supplicant/.libs src/core/supplicant/_libs + -rm -rf src/core/supplicant/tests/.libs src/core/supplicant/tests/_libs + -rm -rf src/core/systemd/.libs src/core/systemd/_libs + -rm -rf src/core/systemd/sd-adapt-core/.libs src/core/systemd/sd-adapt-core/_libs + -rm -rf src/core/systemd/src/libsystemd-network/.libs src/core/systemd/src/libsystemd-network/_libs + -rm -rf src/core/systemd/src/libsystemd/sd-event/.libs src/core/systemd/src/libsystemd/sd-event/_libs + -rm -rf src/core/systemd/src/libsystemd/sd-id128/.libs src/core/systemd/src/libsystemd/sd-id128/_libs + -rm -rf src/core/tests/.libs src/core/tests/_libs + -rm -rf src/core/tests/config/.libs src/core/tests/config/_libs + -rm -rf src/core/vpn/.libs src/core/vpn/_libs distclean-libtool: -rm -f libtool config.lt @@ -18313,261 +18408,261 @@ libnm/tests/test-secret-agent.log: libnm/tests/test-secret-agent$(EXEEXT) --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/initrd/tests/test-dt-reader.log: src/initrd/tests/test-dt-reader$(EXEEXT) - @p='src/initrd/tests/test-dt-reader$(EXEEXT)'; \ - b='src/initrd/tests/test-dt-reader'; \ +src/core/initrd/tests/test-dt-reader.log: src/core/initrd/tests/test-dt-reader$(EXEEXT) + @p='src/core/initrd/tests/test-dt-reader$(EXEEXT)'; \ + b='src/core/initrd/tests/test-dt-reader'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/initrd/tests/test-ibft-reader.log: src/initrd/tests/test-ibft-reader$(EXEEXT) - @p='src/initrd/tests/test-ibft-reader$(EXEEXT)'; \ - b='src/initrd/tests/test-ibft-reader'; \ +src/core/initrd/tests/test-ibft-reader.log: src/core/initrd/tests/test-ibft-reader$(EXEEXT) + @p='src/core/initrd/tests/test-ibft-reader$(EXEEXT)'; \ + b='src/core/initrd/tests/test-ibft-reader'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/initrd/tests/test-cmdline-reader.log: src/initrd/tests/test-cmdline-reader$(EXEEXT) - @p='src/initrd/tests/test-cmdline-reader$(EXEEXT)'; \ - b='src/initrd/tests/test-cmdline-reader'; \ +src/core/initrd/tests/test-cmdline-reader.log: src/core/initrd/tests/test-cmdline-reader$(EXEEXT) + @p='src/core/initrd/tests/test-cmdline-reader$(EXEEXT)'; \ + b='src/core/initrd/tests/test-cmdline-reader'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/dhcp/tests/test-dhcp-dhclient.log: src/dhcp/tests/test-dhcp-dhclient$(EXEEXT) - @p='src/dhcp/tests/test-dhcp-dhclient$(EXEEXT)'; \ - b='src/dhcp/tests/test-dhcp-dhclient'; \ +src/core/dhcp/tests/test-dhcp-dhclient.log: src/core/dhcp/tests/test-dhcp-dhclient$(EXEEXT) + @p='src/core/dhcp/tests/test-dhcp-dhclient$(EXEEXT)'; \ + b='src/core/dhcp/tests/test-dhcp-dhclient'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/dhcp/tests/test-dhcp-utils.log: src/dhcp/tests/test-dhcp-utils$(EXEEXT) - @p='src/dhcp/tests/test-dhcp-utils$(EXEEXT)'; \ - b='src/dhcp/tests/test-dhcp-utils'; \ +src/core/dhcp/tests/test-dhcp-utils.log: src/core/dhcp/tests/test-dhcp-utils$(EXEEXT) + @p='src/core/dhcp/tests/test-dhcp-utils$(EXEEXT)'; \ + b='src/core/dhcp/tests/test-dhcp-utils'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/settings/plugins/keyfile/tests/test-keyfile-settings.log: src/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) - @p='src/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT)'; \ - b='src/settings/plugins/keyfile/tests/test-keyfile-settings'; \ +src/core/settings/plugins/keyfile/tests/test-keyfile-settings.log: src/core/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT) + @p='src/core/settings/plugins/keyfile/tests/test-keyfile-settings$(EXEEXT)'; \ + b='src/core/settings/plugins/keyfile/tests/test-keyfile-settings'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.log: src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) - @p='src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT)'; \ - b='src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh'; \ +src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.log: src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT) + @p='src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh$(EXEEXT)'; \ + b='src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/settings/plugins/ifupdown/tests/test-ifupdown.log: src/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) - @p='src/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT)'; \ - b='src/settings/plugins/ifupdown/tests/test-ifupdown'; \ +src/core/settings/plugins/ifupdown/tests/test-ifupdown.log: src/core/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT) + @p='src/core/settings/plugins/ifupdown/tests/test-ifupdown$(EXEEXT)'; \ + b='src/core/settings/plugins/ifupdown/tests/test-ifupdown'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/devices/wwan/tests/test-service-providers.log: src/devices/wwan/tests/test-service-providers$(EXEEXT) - @p='src/devices/wwan/tests/test-service-providers$(EXEEXT)'; \ - b='src/devices/wwan/tests/test-service-providers'; \ +src/core/devices/wwan/tests/test-service-providers.log: src/core/devices/wwan/tests/test-service-providers$(EXEEXT) + @p='src/core/devices/wwan/tests/test-service-providers$(EXEEXT)'; \ + b='src/core/devices/wwan/tests/test-service-providers'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/devices/wifi/tests/test-devices-wifi.log: src/devices/wifi/tests/test-devices-wifi$(EXEEXT) - @p='src/devices/wifi/tests/test-devices-wifi$(EXEEXT)'; \ - b='src/devices/wifi/tests/test-devices-wifi'; \ +src/core/devices/wifi/tests/test-devices-wifi.log: src/core/devices/wifi/tests/test-devices-wifi$(EXEEXT) + @p='src/core/devices/wifi/tests/test-devices-wifi$(EXEEXT)'; \ + b='src/core/devices/wifi/tests/test-devices-wifi'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/dnsmasq/tests/test-dnsmasq-utils.log: src/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) - @p='src/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT)'; \ - b='src/dnsmasq/tests/test-dnsmasq-utils'; \ +src/core/dnsmasq/tests/test-dnsmasq-utils.log: src/core/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT) + @p='src/core/dnsmasq/tests/test-dnsmasq-utils$(EXEEXT)'; \ + b='src/core/dnsmasq/tests/test-dnsmasq-utils'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-address-fake.log: src/platform/tests/test-address-fake$(EXEEXT) - @p='src/platform/tests/test-address-fake$(EXEEXT)'; \ - b='src/platform/tests/test-address-fake'; \ +src/core/platform/tests/test-address-fake.log: src/core/platform/tests/test-address-fake$(EXEEXT) + @p='src/core/platform/tests/test-address-fake$(EXEEXT)'; \ + b='src/core/platform/tests/test-address-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-address-linux.log: src/platform/tests/test-address-linux$(EXEEXT) - @p='src/platform/tests/test-address-linux$(EXEEXT)'; \ - b='src/platform/tests/test-address-linux'; \ +src/core/platform/tests/test-address-linux.log: src/core/platform/tests/test-address-linux$(EXEEXT) + @p='src/core/platform/tests/test-address-linux$(EXEEXT)'; \ + b='src/core/platform/tests/test-address-linux'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-cleanup-fake.log: src/platform/tests/test-cleanup-fake$(EXEEXT) - @p='src/platform/tests/test-cleanup-fake$(EXEEXT)'; \ - b='src/platform/tests/test-cleanup-fake'; \ +src/core/platform/tests/test-cleanup-fake.log: src/core/platform/tests/test-cleanup-fake$(EXEEXT) + @p='src/core/platform/tests/test-cleanup-fake$(EXEEXT)'; \ + b='src/core/platform/tests/test-cleanup-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-cleanup-linux.log: src/platform/tests/test-cleanup-linux$(EXEEXT) - @p='src/platform/tests/test-cleanup-linux$(EXEEXT)'; \ - b='src/platform/tests/test-cleanup-linux'; \ +src/core/platform/tests/test-cleanup-linux.log: src/core/platform/tests/test-cleanup-linux$(EXEEXT) + @p='src/core/platform/tests/test-cleanup-linux$(EXEEXT)'; \ + b='src/core/platform/tests/test-cleanup-linux'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-link-fake.log: src/platform/tests/test-link-fake$(EXEEXT) - @p='src/platform/tests/test-link-fake$(EXEEXT)'; \ - b='src/platform/tests/test-link-fake'; \ +src/core/platform/tests/test-link-fake.log: src/core/platform/tests/test-link-fake$(EXEEXT) + @p='src/core/platform/tests/test-link-fake$(EXEEXT)'; \ + b='src/core/platform/tests/test-link-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-link-linux.log: src/platform/tests/test-link-linux$(EXEEXT) - @p='src/platform/tests/test-link-linux$(EXEEXT)'; \ - b='src/platform/tests/test-link-linux'; \ +src/core/platform/tests/test-link-linux.log: src/core/platform/tests/test-link-linux$(EXEEXT) + @p='src/core/platform/tests/test-link-linux$(EXEEXT)'; \ + b='src/core/platform/tests/test-link-linux'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-nmp-object.log: src/platform/tests/test-nmp-object$(EXEEXT) - @p='src/platform/tests/test-nmp-object$(EXEEXT)'; \ - b='src/platform/tests/test-nmp-object'; \ +src/core/platform/tests/test-nmp-object.log: src/core/platform/tests/test-nmp-object$(EXEEXT) + @p='src/core/platform/tests/test-nmp-object$(EXEEXT)'; \ + b='src/core/platform/tests/test-nmp-object'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-platform-general.log: src/platform/tests/test-platform-general$(EXEEXT) - @p='src/platform/tests/test-platform-general$(EXEEXT)'; \ - b='src/platform/tests/test-platform-general'; \ +src/core/platform/tests/test-platform-general.log: src/core/platform/tests/test-platform-general$(EXEEXT) + @p='src/core/platform/tests/test-platform-general$(EXEEXT)'; \ + b='src/core/platform/tests/test-platform-general'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-route-fake.log: src/platform/tests/test-route-fake$(EXEEXT) - @p='src/platform/tests/test-route-fake$(EXEEXT)'; \ - b='src/platform/tests/test-route-fake'; \ +src/core/platform/tests/test-route-fake.log: src/core/platform/tests/test-route-fake$(EXEEXT) + @p='src/core/platform/tests/test-route-fake$(EXEEXT)'; \ + b='src/core/platform/tests/test-route-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-route-linux.log: src/platform/tests/test-route-linux$(EXEEXT) - @p='src/platform/tests/test-route-linux$(EXEEXT)'; \ - b='src/platform/tests/test-route-linux'; \ +src/core/platform/tests/test-route-linux.log: src/core/platform/tests/test-route-linux$(EXEEXT) + @p='src/core/platform/tests/test-route-linux$(EXEEXT)'; \ + b='src/core/platform/tests/test-route-linux'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-tc-fake.log: src/platform/tests/test-tc-fake$(EXEEXT) - @p='src/platform/tests/test-tc-fake$(EXEEXT)'; \ - b='src/platform/tests/test-tc-fake'; \ +src/core/platform/tests/test-tc-fake.log: src/core/platform/tests/test-tc-fake$(EXEEXT) + @p='src/core/platform/tests/test-tc-fake$(EXEEXT)'; \ + b='src/core/platform/tests/test-tc-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/platform/tests/test-tc-linux.log: src/platform/tests/test-tc-linux$(EXEEXT) - @p='src/platform/tests/test-tc-linux$(EXEEXT)'; \ - b='src/platform/tests/test-tc-linux'; \ +src/core/platform/tests/test-tc-linux.log: src/core/platform/tests/test-tc-linux$(EXEEXT) + @p='src/core/platform/tests/test-tc-linux$(EXEEXT)'; \ + b='src/core/platform/tests/test-tc-linux'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/devices/tests/test-lldp.log: src/devices/tests/test-lldp$(EXEEXT) - @p='src/devices/tests/test-lldp$(EXEEXT)'; \ - b='src/devices/tests/test-lldp'; \ +src/core/devices/tests/test-lldp.log: src/core/devices/tests/test-lldp$(EXEEXT) + @p='src/core/devices/tests/test-lldp$(EXEEXT)'; \ + b='src/core/devices/tests/test-lldp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/devices/tests/test-acd.log: src/devices/tests/test-acd$(EXEEXT) - @p='src/devices/tests/test-acd$(EXEEXT)'; \ - b='src/devices/tests/test-acd'; \ +src/core/devices/tests/test-acd.log: src/core/devices/tests/test-acd$(EXEEXT) + @p='src/core/devices/tests/test-acd$(EXEEXT)'; \ + b='src/core/devices/tests/test-acd'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/ndisc/tests/test-ndisc-fake.log: src/ndisc/tests/test-ndisc-fake$(EXEEXT) - @p='src/ndisc/tests/test-ndisc-fake$(EXEEXT)'; \ - b='src/ndisc/tests/test-ndisc-fake'; \ +src/core/ndisc/tests/test-ndisc-fake.log: src/core/ndisc/tests/test-ndisc-fake$(EXEEXT) + @p='src/core/ndisc/tests/test-ndisc-fake$(EXEEXT)'; \ + b='src/core/ndisc/tests/test-ndisc-fake'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/supplicant/tests/test-supplicant-config.log: src/supplicant/tests/test-supplicant-config$(EXEEXT) - @p='src/supplicant/tests/test-supplicant-config$(EXEEXT)'; \ - b='src/supplicant/tests/test-supplicant-config'; \ +src/core/supplicant/tests/test-supplicant-config.log: src/core/supplicant/tests/test-supplicant-config$(EXEEXT) + @p='src/core/supplicant/tests/test-supplicant-config$(EXEEXT)'; \ + b='src/core/supplicant/tests/test-supplicant-config'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/config/test-config.log: src/tests/config/test-config$(EXEEXT) - @p='src/tests/config/test-config$(EXEEXT)'; \ - b='src/tests/config/test-config'; \ +src/core/tests/config/test-config.log: src/core/tests/config/test-config$(EXEEXT) + @p='src/core/tests/config/test-config$(EXEEXT)'; \ + b='src/core/tests/config/test-config'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-core.log: src/tests/test-core$(EXEEXT) - @p='src/tests/test-core$(EXEEXT)'; \ - b='src/tests/test-core'; \ +src/core/tests/test-core.log: src/core/tests/test-core$(EXEEXT) + @p='src/core/tests/test-core$(EXEEXT)'; \ + b='src/core/tests/test-core'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-core-with-expect.log: src/tests/test-core-with-expect$(EXEEXT) - @p='src/tests/test-core-with-expect$(EXEEXT)'; \ - b='src/tests/test-core-with-expect'; \ +src/core/tests/test-core-with-expect.log: src/core/tests/test-core-with-expect$(EXEEXT) + @p='src/core/tests/test-core-with-expect$(EXEEXT)'; \ + b='src/core/tests/test-core-with-expect'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-dcb.log: src/tests/test-dcb$(EXEEXT) - @p='src/tests/test-dcb$(EXEEXT)'; \ - b='src/tests/test-dcb'; \ +src/core/tests/test-dcb.log: src/core/tests/test-dcb$(EXEEXT) + @p='src/core/tests/test-dcb$(EXEEXT)'; \ + b='src/core/tests/test-dcb'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-ip4-config.log: src/tests/test-ip4-config$(EXEEXT) - @p='src/tests/test-ip4-config$(EXEEXT)'; \ - b='src/tests/test-ip4-config'; \ +src/core/tests/test-ip4-config.log: src/core/tests/test-ip4-config$(EXEEXT) + @p='src/core/tests/test-ip4-config$(EXEEXT)'; \ + b='src/core/tests/test-ip4-config'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-ip6-config.log: src/tests/test-ip6-config$(EXEEXT) - @p='src/tests/test-ip6-config$(EXEEXT)'; \ - b='src/tests/test-ip6-config'; \ +src/core/tests/test-ip6-config.log: src/core/tests/test-ip6-config$(EXEEXT) + @p='src/core/tests/test-ip6-config$(EXEEXT)'; \ + b='src/core/tests/test-ip6-config'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-l3cfg.log: src/tests/test-l3cfg$(EXEEXT) - @p='src/tests/test-l3cfg$(EXEEXT)'; \ - b='src/tests/test-l3cfg'; \ +src/core/tests/test-l3cfg.log: src/core/tests/test-l3cfg$(EXEEXT) + @p='src/core/tests/test-l3cfg$(EXEEXT)'; \ + b='src/core/tests/test-l3cfg'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-systemd.log: src/tests/test-systemd$(EXEEXT) - @p='src/tests/test-systemd$(EXEEXT)'; \ - b='src/tests/test-systemd'; \ +src/core/tests/test-systemd.log: src/core/tests/test-systemd$(EXEEXT) + @p='src/core/tests/test-systemd$(EXEEXT)'; \ + b='src/core/tests/test-systemd'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-utils.log: src/tests/test-utils$(EXEEXT) - @p='src/tests/test-utils$(EXEEXT)'; \ - b='src/tests/test-utils'; \ +src/core/tests/test-utils.log: src/core/tests/test-utils$(EXEEXT) + @p='src/core/tests/test-utils$(EXEEXT)'; \ + b='src/core/tests/test-utils'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -src/tests/test-wired-defname.log: src/tests/test-wired-defname$(EXEEXT) - @p='src/tests/test-wired-defname$(EXEEXT)'; \ - b='src/tests/test-wired-defname'; \ +src/core/tests/test-wired-defname.log: src/core/tests/test-wired-defname$(EXEEXT) + @p='src/core/tests/test-wired-defname$(EXEEXT)'; \ + b='src/core/tests/test-wired-defname'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ @@ -18693,6 +18788,10 @@ dist-xz: distdir tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz $(am__post_remove_distdir) +dist-zstd: distdir + tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst + $(am__post_remove_distdir) + dist-tarZ: distdir @echo WARNING: "Support for distribution archives compressed with" \ "legacy program 'compress' is deprecated." >&2 @@ -18735,6 +18834,8 @@ distcheck: dist eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ + *.tar.zst*) \ + zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ esac chmod -R a-w $(distdir) chmod u+w $(distdir) @@ -18924,92 +19025,92 @@ distclean-generic: -rm -f shared/systemd/src/basic/$(am__dirstamp) -rm -f shared/systemd/src/shared/$(DEPDIR)/$(am__dirstamp) -rm -f shared/systemd/src/shared/$(am__dirstamp) - -rm -f src/$(DEPDIR)/$(am__dirstamp) - -rm -f src/$(am__dirstamp) - -rm -f src/devices/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/$(am__dirstamp) - -rm -f src/devices/adsl/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/adsl/$(am__dirstamp) - -rm -f src/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/bluetooth/$(am__dirstamp) - -rm -f src/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/bluetooth/tests/$(am__dirstamp) - -rm -f src/devices/ovs/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/ovs/$(am__dirstamp) - -rm -f src/devices/team/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/team/$(am__dirstamp) - -rm -f src/devices/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/tests/$(am__dirstamp) - -rm -f src/devices/wifi/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/wifi/$(am__dirstamp) - -rm -f src/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/wifi/tests/$(am__dirstamp) - -rm -f src/devices/wwan/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/wwan/$(am__dirstamp) - -rm -f src/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/devices/wwan/tests/$(am__dirstamp) - -rm -f src/dhcp/$(DEPDIR)/$(am__dirstamp) - -rm -f src/dhcp/$(am__dirstamp) - -rm -f src/dhcp/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/dhcp/tests/$(am__dirstamp) - -rm -f src/dns/$(DEPDIR)/$(am__dirstamp) - -rm -f src/dns/$(am__dirstamp) - -rm -f src/dnsmasq/$(DEPDIR)/$(am__dirstamp) - -rm -f src/dnsmasq/$(am__dirstamp) - -rm -f src/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/dnsmasq/tests/$(am__dirstamp) - -rm -f src/initrd/$(DEPDIR)/$(am__dirstamp) - -rm -f src/initrd/$(am__dirstamp) - -rm -f src/initrd/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/initrd/tests/$(am__dirstamp) - -rm -f src/ndisc/$(DEPDIR)/$(am__dirstamp) - -rm -f src/ndisc/$(am__dirstamp) - -rm -f src/ndisc/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/ndisc/tests/$(am__dirstamp) - -rm -f src/platform/$(DEPDIR)/$(am__dirstamp) - -rm -f src/platform/$(am__dirstamp) - -rm -f src/platform/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/platform/tests/$(am__dirstamp) - -rm -f src/platform/wifi/$(DEPDIR)/$(am__dirstamp) - -rm -f src/platform/wifi/$(am__dirstamp) - -rm -f src/platform/wpan/$(DEPDIR)/$(am__dirstamp) - -rm -f src/platform/wpan/$(am__dirstamp) - -rm -f src/ppp/$(DEPDIR)/$(am__dirstamp) - -rm -f src/ppp/$(am__dirstamp) - -rm -f src/settings/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/$(am__dirstamp) - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/ifcfg-rh/$(am__dirstamp) - -rm -f src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/ifupdown/$(am__dirstamp) - -rm -f src/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/ifupdown/tests/$(am__dirstamp) - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/keyfile/$(am__dirstamp) - -rm -f src/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/settings/plugins/keyfile/tests/$(am__dirstamp) - -rm -f src/supplicant/$(DEPDIR)/$(am__dirstamp) - -rm -f src/supplicant/$(am__dirstamp) - -rm -f src/supplicant/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/supplicant/tests/$(am__dirstamp) - -rm -f src/systemd/$(DEPDIR)/$(am__dirstamp) - -rm -f src/systemd/$(am__dirstamp) - -rm -f src/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) - -rm -f src/systemd/sd-adapt-core/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd-network/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd/sd-event/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) - -rm -f src/systemd/src/libsystemd/sd-id128/$(am__dirstamp) - -rm -f src/tests/$(DEPDIR)/$(am__dirstamp) - -rm -f src/tests/$(am__dirstamp) - -rm -f src/tests/config/$(DEPDIR)/$(am__dirstamp) - -rm -f src/tests/config/$(am__dirstamp) - -rm -f src/vpn/$(DEPDIR)/$(am__dirstamp) - -rm -f src/vpn/$(am__dirstamp) + -rm -f src/core/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/$(am__dirstamp) + -rm -f src/core/devices/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/$(am__dirstamp) + -rm -f src/core/devices/adsl/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/adsl/$(am__dirstamp) + -rm -f src/core/devices/bluetooth/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/bluetooth/$(am__dirstamp) + -rm -f src/core/devices/bluetooth/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/bluetooth/tests/$(am__dirstamp) + -rm -f src/core/devices/ovs/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/ovs/$(am__dirstamp) + -rm -f src/core/devices/team/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/team/$(am__dirstamp) + -rm -f src/core/devices/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/tests/$(am__dirstamp) + -rm -f src/core/devices/wifi/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/wifi/$(am__dirstamp) + -rm -f src/core/devices/wifi/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/wifi/tests/$(am__dirstamp) + -rm -f src/core/devices/wwan/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/wwan/$(am__dirstamp) + -rm -f src/core/devices/wwan/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/devices/wwan/tests/$(am__dirstamp) + -rm -f src/core/dhcp/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/dhcp/$(am__dirstamp) + -rm -f src/core/dhcp/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/dhcp/tests/$(am__dirstamp) + -rm -f src/core/dns/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/dns/$(am__dirstamp) + -rm -f src/core/dnsmasq/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/dnsmasq/$(am__dirstamp) + -rm -f src/core/dnsmasq/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/dnsmasq/tests/$(am__dirstamp) + -rm -f src/core/initrd/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/initrd/$(am__dirstamp) + -rm -f src/core/initrd/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/initrd/tests/$(am__dirstamp) + -rm -f src/core/ndisc/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/ndisc/$(am__dirstamp) + -rm -f src/core/ndisc/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/ndisc/tests/$(am__dirstamp) + -rm -f src/core/platform/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/platform/$(am__dirstamp) + -rm -f src/core/platform/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/platform/tests/$(am__dirstamp) + -rm -f src/core/platform/wifi/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/platform/wifi/$(am__dirstamp) + -rm -f src/core/platform/wpan/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/platform/wpan/$(am__dirstamp) + -rm -f src/core/ppp/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/ppp/$(am__dirstamp) + -rm -f src/core/settings/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifcfg-rh/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifcfg-rh/tests/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifupdown/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/ifupdown/tests/$(am__dirstamp) + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/keyfile/$(am__dirstamp) + -rm -f src/core/settings/plugins/keyfile/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/settings/plugins/keyfile/tests/$(am__dirstamp) + -rm -f src/core/supplicant/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/supplicant/$(am__dirstamp) + -rm -f src/core/supplicant/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/supplicant/tests/$(am__dirstamp) + -rm -f src/core/systemd/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/systemd/$(am__dirstamp) + -rm -f src/core/systemd/sd-adapt-core/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/systemd/sd-adapt-core/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd-network/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd/sd-event/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(am__dirstamp) + -rm -f src/core/tests/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/tests/$(am__dirstamp) + -rm -f src/core/tests/config/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/tests/config/$(am__dirstamp) + -rm -f src/core/vpn/$(DEPDIR)/$(am__dirstamp) + -rm -f src/core/vpn/$(am__dirstamp) -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) maintainer-clean-generic: @@ -19255,59 +19356,59 @@ distclean: distclean-recursive -rm -f libnm-core/tests/$(DEPDIR)/test_secrets-test-secrets.Po -rm -f libnm-core/tests/$(DEPDIR)/test_setting-test-setting.Po -rm -f libnm-core/tests/$(DEPDIR)/test_settings_defaults-test-settings-defaults.Po - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-client.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-object.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-client.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-object.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Plo -rm -f libnm/nm-libnm-aux/$(DEPDIR)/libnm_libnm_aux_la-nm-libnm-aux.Plo -rm -f libnm/tests/$(DEPDIR)/test_libnm-test-libnm.Po -rm -f libnm/tests/$(DEPDIR)/test_nm_client-test-nm-client.Po @@ -19400,232 +19501,232 @@ distclean: distclean-recursive -rm -f shared/systemd/src/basic/$(DEPDIR)/libnm_systemd_shared_la-util.Plo -rm -f shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-dns-domain.Plo -rm -f shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-web-util.Plo - -rm -f src/$(DEPDIR)/NetworkManager-main.Po - -rm -f src/$(DEPDIR)/NetworkManager_all_sym-main.Po - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo - -rm -f src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo - -rm -f src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo - -rm -f src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo - -rm -f src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo - -rm -f src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo - -rm -f src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo - -rm -f src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po - -rm -f src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po - -rm -f src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo - -rm -f src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po - -rm -f src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo - -rm -f src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po - -rm -f src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo - -rm -f src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po - -rm -f src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po - -rm -f src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo - -rm -f src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo - -rm -f src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo - -rm -f src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo - -rm -f src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo - -rm -f src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po - -rm -f src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo - -rm -f src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo - -rm -f src/platform/tests/$(DEPDIR)/monitor-monitor.Po - -rm -f src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po - -rm -f src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po - -rm -f src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po - -rm -f src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po - -rm -f src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po - -rm -f src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po - -rm -f src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po - -rm -f src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po - -rm -f src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po - -rm -f src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po - -rm -f src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po - -rm -f src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo - -rm -f src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo - -rm -f src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo - -rm -f src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo - -rm -f src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo - -rm -f src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo - -rm -f src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo - -rm -f src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo - -rm -f src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo - -rm -f src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo - -rm -f src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo - -rm -f src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo - -rm -f src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo - -rm -f src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo - -rm -f src/tests/$(DEPDIR)/test_core-test-core.Po - -rm -f src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po - -rm -f src/tests/$(DEPDIR)/test_dcb-test-dcb.Po - -rm -f src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po - -rm -f src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po - -rm -f src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po - -rm -f src/tests/$(DEPDIR)/test_systemd-test-systemd.Po - -rm -f src/tests/$(DEPDIR)/test_utils-test-utils.Po - -rm -f src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po - -rm -f src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po - -rm -f src/tests/config/$(DEPDIR)/test_config-test-config.Po - -rm -f src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo - -rm -f src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo + -rm -f src/core/$(DEPDIR)/NetworkManager-main.Po + -rm -f src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo + -rm -f src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo + -rm -f src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo + -rm -f src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo + -rm -f src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo + -rm -f src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo + -rm -f src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo + -rm -f src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po + -rm -f src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo + -rm -f src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po + -rm -f src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo + -rm -f src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po + -rm -f src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po + -rm -f src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo + -rm -f src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo + -rm -f src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo + -rm -f src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo + -rm -f src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po + -rm -f src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo + -rm -f src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo + -rm -f src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo + -rm -f src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo + -rm -f src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo + -rm -f src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo + -rm -f src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo + -rm -f src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo + -rm -f src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo + -rm -f src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo + -rm -f src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo + -rm -f src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo + -rm -f src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo + -rm -f src/core/tests/$(DEPDIR)/test_core-test-core.Po + -rm -f src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po + -rm -f src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po + -rm -f src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po + -rm -f src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po + -rm -f src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po + -rm -f src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po + -rm -f src/core/tests/$(DEPDIR)/test_utils-test-utils.Po + -rm -f src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po + -rm -f src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po + -rm -f src/core/tests/config/$(DEPDIR)/test_config-test-config.Po + -rm -f src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo + -rm -f src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-libtool distclean-tags @@ -19914,59 +20015,59 @@ maintainer-clean: maintainer-clean-recursive -rm -f libnm-core/tests/$(DEPDIR)/test_secrets-test-secrets.Po -rm -f libnm-core/tests/$(DEPDIR)/test_setting-test-setting.Po -rm -f libnm-core/tests/$(DEPDIR)/test_settings_defaults-test-settings-defaults.Po - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-access-point.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-active-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-checkpoint.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-client.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dbus-helpers.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-6lowpan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-adsl.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bond.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bridge.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-bt.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-dummy.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ethernet.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-generic.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-infiniband.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ip-tunnel.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-macsec.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-macvlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-modem.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-olpc-mesh.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-bridge.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-interface.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ovs-port.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-ppp.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-team.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-tun.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-veth.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vrf.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-vxlan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi-p2p.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wifi.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wimax.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wireguard.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device-wpan.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-device.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp4-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dhcp6-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-dns-manager.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-enum-types.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip4-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-ip6-config.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-libnm-utils.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-object.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-remote-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-secret-agent-old.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-connection.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-editor.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-plugin-old.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-vpn-service-plugin.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-wifi-p2p-peer.Plo - -rm -f libnm/$(DEPDIR)/liblibnm_la-nm-wimax-nsp.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-access-point.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-active-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-checkpoint.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-client.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dbus-helpers.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-6lowpan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-adsl.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bond.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bridge.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-bt.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-dummy.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ethernet.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-generic.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-infiniband.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ip-tunnel.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-macsec.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-macvlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-modem.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-olpc-mesh.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-bridge.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-interface.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ovs-port.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-ppp.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-team.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-tun.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-veth.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vrf.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-vxlan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi-p2p.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wifi.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wimax.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wireguard.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device-wpan.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-device.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp4-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dhcp6-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-dns-manager.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-enum-types.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip4-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-ip6-config.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-libnm-utils.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-object.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-remote-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-secret-agent-old.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-connection.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-editor.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-plugin-old.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-vpn-service-plugin.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-wifi-p2p-peer.Plo + -rm -f libnm/$(DEPDIR)/libnm_static_la-nm-wimax-nsp.Plo -rm -f libnm/nm-libnm-aux/$(DEPDIR)/libnm_libnm_aux_la-nm-libnm-aux.Plo -rm -f libnm/tests/$(DEPDIR)/test_libnm-test-libnm.Po -rm -f libnm/tests/$(DEPDIR)/test_nm_client-test-nm-client.Po @@ -20059,232 +20160,232 @@ maintainer-clean: maintainer-clean-recursive -rm -f shared/systemd/src/basic/$(DEPDIR)/libnm_systemd_shared_la-util.Plo -rm -f shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-dns-domain.Plo -rm -f shared/systemd/src/shared/$(DEPDIR)/libnm_systemd_shared_la-web-util.Plo - -rm -f src/$(DEPDIR)/NetworkManager-main.Po - -rm -f src/$(DEPDIR)/NetworkManager_all_sym-main.Po - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo - -rm -f src/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo - -rm -f src/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo - -rm -f src/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo - -rm -f src/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo - -rm -f src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo - -rm -f src/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo - -rm -f src/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo - -rm -f src/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo - -rm -f src/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo - -rm -f src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo - -rm -f src/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo - -rm -f src/devices/tests/$(DEPDIR)/test_acd-test-acd.Po - -rm -f src/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po - -rm -f src/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo - -rm -f src/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo - -rm -f src/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po - -rm -f src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo - -rm -f src/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo - -rm -f src/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po - -rm -f src/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo - -rm -f src/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo - -rm -f src/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po - -rm -f src/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po - -rm -f src/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo - -rm -f src/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo - -rm -f src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo - -rm -f src/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo - -rm -f src/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo - -rm -f src/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo - -rm -f src/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po - -rm -f src/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo - -rm -f src/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo - -rm -f src/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po - -rm -f src/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo - -rm -f src/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo - -rm -f src/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo - -rm -f src/platform/tests/$(DEPDIR)/monitor-monitor.Po - -rm -f src/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po - -rm -f src/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po - -rm -f src/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po - -rm -f src/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po - -rm -f src/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po - -rm -f src/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po - -rm -f src/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po - -rm -f src/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po - -rm -f src/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po - -rm -f src/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po - -rm -f src/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po - -rm -f src/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo - -rm -f src/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo - -rm -f src/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo - -rm -f src/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo - -rm -f src/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo - -rm -f src/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo - -rm -f src/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo - -rm -f src/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo - -rm -f src/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo - -rm -f src/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo - -rm -f src/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo - -rm -f src/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo - -rm -f src/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo - -rm -f src/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo - -rm -f src/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo - -rm -f src/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo - -rm -f src/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo - -rm -f src/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo - -rm -f src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo - -rm -f src/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo - -rm -f src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo - -rm -f src/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo - -rm -f src/tests/$(DEPDIR)/test_core-test-core.Po - -rm -f src/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po - -rm -f src/tests/$(DEPDIR)/test_dcb-test-dcb.Po - -rm -f src/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po - -rm -f src/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po - -rm -f src/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po - -rm -f src/tests/$(DEPDIR)/test_systemd-test-systemd.Po - -rm -f src/tests/$(DEPDIR)/test_utils-test-utils.Po - -rm -f src/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po - -rm -f src/tests/config/$(DEPDIR)/test_config-nm-test-device.Po - -rm -f src/tests/config/$(DEPDIR)/test_config-test-config.Po - -rm -f src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo - -rm -f src/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo + -rm -f src/core/$(DEPDIR)/NetworkManager-main.Po + -rm -f src/core/$(DEPDIR)/NetworkManager_all_sym-main.Po + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-NetworkManagerUtils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-main-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-core-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-object.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-dbus-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip4-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-ip6-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-config-data.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3-ipv4ll.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-l3cfg.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManagerBase_la-nm-netns.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-act-request.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-active-connection.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-audit-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-auth-utils.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-checkpoint.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-config-data.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-connectivity.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dbus-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dcb.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dhcp-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-dispatcher.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-firewall-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-hostname-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-keep-alive.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-pacrunner-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-policy.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-proxy-config.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-rfkill-manager.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-session-monitor.Plo + -rm -f src/core/$(DEPDIR)/libNetworkManager_la-nm-sleep-monitor.Plo + -rm -f src/core/$(DEPDIR)/nm_iface_helper-nm-iface-helper.Po + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-acd-manager.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-6lowpan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bond.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-bridge.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-dummy.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet-utils.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ethernet.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-factory.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-generic.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-infiniband.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ip-tunnel.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macsec.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-macvlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-ppp.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-tun.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-veth.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vrf.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-vxlan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wireguard.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device-wpan.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-device.Plo + -rm -f src/core/devices/$(DEPDIR)/libNetworkManager_la-nm-lldp-listener.Plo + -rm -f src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-atm-manager.Plo + -rm -f src/core/devices/adsl/$(DEPDIR)/libnm_device_plugin_adsl_la-nm-device-adsl.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bluez5-dun.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_bluetooth_utils_la-nm-bt-error.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-bluez-manager.Plo + -rm -f src/core/devices/bluetooth/$(DEPDIR)/libnm_device_plugin_bluetooth_la-nm-device-bt.Plo + -rm -f src/core/devices/bluetooth/tests/$(DEPDIR)/nm_bt_test-nm-bt-test.Po + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-bridge.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-interface.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-device-ovs-port.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovs-factory.Plo + -rm -f src/core/devices/ovs/$(DEPDIR)/libnm_device_plugin_ovs_la-nm-ovsdb.Plo + -rm -f src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-device-team.Plo + -rm -f src/core/devices/team/$(DEPDIR)/libnm_device_plugin_team_la-nm-team-factory.Plo + -rm -f src/core/devices/tests/$(DEPDIR)/test_acd-test-acd.Po + -rm -f src/core/devices/tests/$(DEPDIR)/test_lldp-test-lldp.Po + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_device_plugin_wifi_la-nm-wifi-factory.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-iwd.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-olpc-mesh.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi-p2p.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-device-wifi.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-iwd-manager.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-ap.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-common.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-p2p-peer.Plo + -rm -f src/core/devices/wifi/$(DEPDIR)/libnm_wifi_base_la-nm-wifi-utils.Plo + -rm -f src/core/devices/wifi/tests/$(DEPDIR)/test_devices_wifi-test-devices-wifi.Po + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-device-modem.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_device_plugin_wwan_la-nm-wwan-factory.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-broadband.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-manager.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem-ofono.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-modem.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/libnm_wwan_la-nm-service-providers.Plo + -rm -f src/core/devices/wwan/$(DEPDIR)/tests_test_service_providers-nm-service-providers.Po + -rm -f src/core/devices/wwan/tests/$(DEPDIR)/test_service_providers-test-service-providers.Po + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-client.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-manager.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-nettools.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-options.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-systemd.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManagerBase_la-nm-dhcp-utils.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient-utils.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhclient.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcanon.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-dhcpcd.Plo + -rm -f src/core/dhcp/$(DEPDIR)/libNetworkManager_la-nm-dhcp-listener.Plo + -rm -f src/core/dhcp/$(DEPDIR)/nm_dhcp_helper-nm-dhcp-helper.Po + -rm -f src/core/dhcp/tests/$(DEPDIR)/test_dhcp_dhclient-test-dhcp-dhclient.Po + -rm -f src/core/dhcp/tests/$(DEPDIR)/test_dhcp_utils-test-dhcp-utils.Po + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-dnsmasq.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-manager.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-plugin.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-systemd-resolved.Plo + -rm -f src/core/dns/$(DEPDIR)/libNetworkManager_la-nm-dns-unbound.Plo + -rm -f src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-manager.Plo + -rm -f src/core/dnsmasq/$(DEPDIR)/libNetworkManager_la-nm-dnsmasq-utils.Plo + -rm -f src/core/dnsmasq/tests/$(DEPDIR)/test_dnsmasq_utils-test-dnsmasq-utils.Po + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-cmdline-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-dt-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/libnmi_core_la-nmi-ibft-reader.Plo + -rm -f src/core/initrd/$(DEPDIR)/nm_initrd_generator-nm-initrd-generator.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_cmdline_reader-test-cmdline-reader.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_dt_reader-test-dt-reader.Po + -rm -f src/core/initrd/tests/$(DEPDIR)/test_ibft_reader-test-ibft-reader.Po + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-lndp-ndisc.Plo + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerBase_la-nm-ndisc.Plo + -rm -f src/core/ndisc/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-ndisc.Plo + -rm -f src/core/ndisc/tests/$(DEPDIR)/test_ndisc_fake-test-ndisc-fake.Po + -rm -f src/core/ndisc/tests/$(DEPDIR)/test_ndisc_linux-test-ndisc-linux.Po + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-linux-platform.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nm-platform.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-object.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerBase_la-nmp-rules-manager.Plo + -rm -f src/core/platform/$(DEPDIR)/libNetworkManagerTest_la-nm-fake-platform.Plo + -rm -f src/core/platform/tests/$(DEPDIR)/libNetworkManagerTest_la-test-common.Plo + -rm -f src/core/platform/tests/$(DEPDIR)/monitor-monitor.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_address_fake-test-address.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_address_linux-test-address.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_cleanup_fake-test-cleanup.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_cleanup_linux-test-cleanup.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_link_fake-test-link.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_link_linux-test-link.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_nmp_object-test-nmp-object.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_platform_general-test-platform-general.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_route_fake-test-route.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_route_linux-test-route.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_tc_fake-test-tc.Po + -rm -f src/core/platform/tests/$(DEPDIR)/test_tc_linux-test-tc.Po + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-nl80211.Plo + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils-wext.Plo + -rm -f src/core/platform/wifi/$(DEPDIR)/libNetworkManagerBase_la-nm-wifi-utils.Plo + -rm -f src/core/platform/wpan/$(DEPDIR)/libNetworkManagerBase_la-nm-wpan-utils.Plo + -rm -f src/core/ppp/$(DEPDIR)/libNetworkManager_la-nm-ppp-manager-call.Plo + -rm -f src/core/ppp/$(DEPDIR)/libnm_ppp_plugin_la-nm-ppp-manager.Plo + -rm -f src/core/ppp/$(DEPDIR)/nm_pppd_plugin_la-nm-pppd-plugin.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-agent-manager.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-secret-agent.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-connection.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-plugin.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-storage.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings-utils.Plo + -rm -f src/core/settings/$(DEPDIR)/libNetworkManager_la-nm-settings.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-plugin.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnm_settings_plugin_ifcfg_rh_la-nms-ifcfg-rh-storage.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnmdbus_ifcfg_rh_la-nmdbus-ifcfg-rh.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-reader.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-utils.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-nms-ifcfg-rh-writer.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/$(DEPDIR)/libnms_ifcfg_rh_core_la-shvar.Plo + -rm -f src/core/settings/plugins/ifcfg-rh/tests/$(DEPDIR)/test_ifcfg_rh-test-ifcfg-rh.Po + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnm_settings_plugin_ifupdown_la-nms-ifupdown-plugin.Plo + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-interface-parser.Plo + -rm -f src/core/settings/plugins/ifupdown/$(DEPDIR)/libnms_ifupdown_core_la-nms-ifupdown-parser.Plo + -rm -f src/core/settings/plugins/ifupdown/tests/$(DEPDIR)/test_ifupdown-test-ifupdown.Po + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-plugin.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-reader.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-storage.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-utils.Plo + -rm -f src/core/settings/plugins/keyfile/$(DEPDIR)/libNetworkManager_la-nms-keyfile-writer.Plo + -rm -f src/core/settings/plugins/keyfile/tests/$(DEPDIR)/test_keyfile_settings-test-keyfile-settings.Po + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-config.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-interface.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-manager.Plo + -rm -f src/core/supplicant/$(DEPDIR)/libNetworkManager_la-nm-supplicant-settings-verify.Plo + -rm -f src/core/supplicant/tests/$(DEPDIR)/test_supplicant_config-test-supplicant-config.Po + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-core.Plo + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd-utils-dhcp.Plo + -rm -f src/core/systemd/$(DEPDIR)/libnm_systemd_core_la-nm-sd.Plo + -rm -f src/core/systemd/sd-adapt-core/$(DEPDIR)/libnm_systemd_core_la-nm-sd-adapt-core.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-arp-util.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-identifier.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-option.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp-packet.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-dhcp6-option.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-neighbor.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-lldp-network.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-network-internal.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-client.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp-lease.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-client.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-dhcp6-lease.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4acd.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-ipv4ll.Plo + -rm -f src/core/systemd/src/libsystemd-network/$(DEPDIR)/libnm_systemd_core_la-sd-lldp.Plo + -rm -f src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-event-util.Plo + -rm -f src/core/systemd/src/libsystemd/sd-event/$(DEPDIR)/libnm_systemd_core_la-sd-event.Plo + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-id128-util.Plo + -rm -f src/core/systemd/src/libsystemd/sd-id128/$(DEPDIR)/libnm_systemd_core_la-sd-id128.Plo + -rm -f src/core/tests/$(DEPDIR)/test_core-test-core.Po + -rm -f src/core/tests/$(DEPDIR)/test_core_with_expect-test-core-with-expect.Po + -rm -f src/core/tests/$(DEPDIR)/test_dcb-test-dcb.Po + -rm -f src/core/tests/$(DEPDIR)/test_ip4_config-test-ip4-config.Po + -rm -f src/core/tests/$(DEPDIR)/test_ip6_config-test-ip6-config.Po + -rm -f src/core/tests/$(DEPDIR)/test_l3cfg-test-l3cfg.Po + -rm -f src/core/tests/$(DEPDIR)/test_systemd-test-systemd.Po + -rm -f src/core/tests/$(DEPDIR)/test_utils-test-utils.Po + -rm -f src/core/tests/$(DEPDIR)/test_wired_defname-test-wired-defname.Po + -rm -f src/core/tests/config/$(DEPDIR)/test_config-nm-test-device.Po + -rm -f src/core/tests/config/$(DEPDIR)/test_config-test-config.Po + -rm -f src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-connection.Plo + -rm -f src/core/vpn/$(DEPDIR)/libNetworkManager_la-nm-vpn-manager.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -20331,7 +20432,7 @@ uninstall-man: uninstall-man1 uninstall-man5 uninstall-man7 \ clean-pppd_pluginLTLIBRARIES clean-sbinPROGRAMS cscope \ cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \ dist-gzip dist-hook dist-lzip dist-shar dist-tarZ dist-xz \ - dist-zip distcheck distclean distclean-compile \ + dist-zip dist-zstd distcheck distclean distclean-compile \ distclean-generic distclean-hdr distclean-libtool \ distclean-tags distcleancheck distdir distuninstallcheck dvi \ dvi-am html html-am info info-am install install-am \ @@ -20436,7 +20537,7 @@ shared/nm-udev-aux/.dirstamp: config-extra.h shared/systemd/.dirstamp: config-extra.h shared/systemd/src/basic/.dirstamp: config-extra.h shared/systemd/src/shared/.dirstamp: config-extra.h -src/dhcp/.dirstamp: config-extra.h +src/core/dhcp/.dirstamp: config-extra.h dist-configure-check: @echo "*** 'make dist' requires '--enable-gtk-doc --enable-introspection'. ***" @@ -20483,7 +20584,7 @@ introspection/%.h: introspection/%.c docs/api/dbus-%.xml: introspection/%.c $() -$(libnm_liblibnm_la_OBJECTS): $(introspection_sources) +$(libnm_libnm_static_la_OBJECTS): $(introspection_sources) $(libnm_libnm_la_OBJECTS): $(introspection_sources) check-docs: @@ -20497,13 +20598,13 @@ $(dispatcher_nm_dispatcher_OBJECTS): $(li $(libnm_core_libnm_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_libnm_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_tests_libnm_vpn_plugin_utils_test_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_team_libnm_device_plugin_team_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_adsl_libnm_device_plugin_adsl_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_bluetooth_libnm_device_plugin_bluetooth_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_team_libnm_device_plugin_team_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wifi_libnm_device_plugin_wifi_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_wwan_libnm_device_plugin_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_ovs_libnm_device_plugin_ovs_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) libnm-core/nm-vpn-dbus-types.xml: libnm-core/nm-vpn-dbus-interface.h tools/enums-to-docbook.pl @$(MKDIR_P) libnm-core/ @@ -20526,8 +20627,8 @@ $(libnm_core_tests_test_secrets_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_core_tests_test_setting_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_core_tests_test_settings_defaults_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(libnm_liblibnm_la_OBJECTS) : $(libnm_lib_h_pub_mkenums) -$(libnm_liblibnm_la_OBJECTS) : $(libnm_core_lib_h_pub_mkenums) +$(libnm_libnm_static_la_OBJECTS) : $(libnm_lib_h_pub_mkenums) +$(libnm_libnm_static_la_OBJECTS) : $(libnm_core_lib_h_pub_mkenums) $(dispatcher_nm_dispatcher_OBJECTS): $(libnm_lib_h_pub_mkenums) $(dispatcher_libnm_dispatcher_core_la_OBJECTS): $(libnm_lib_h_pub_mkenums) @@ -20582,151 +20683,151 @@ $(libnm_tests_test_secret_agent_OBJECTS): $(libnm_NM_1_0_typelib) check-config-options: $(srcdir)/tools/check-config-options.sh "$(srcdir)" -$(src_libnm_systemd_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libnm_systemd_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_libNetworkManagerBase_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManagerBase_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_libNetworkManager_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManager_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_libNetworkManagerTest_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_libNetworkManagerTest_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_NetworkManager_all_sym_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_all_sym_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -src/NetworkManager.ver: src/NetworkManager-all-sym $(core_plugins) +src/core/NetworkManager.ver: src/core/NetworkManager-all-sym $(core_plugins) $(AM_V_GEN) NM="$(NM)" "$(srcdir)/tools/create-exports-NetworkManager.sh" --called-from-build "$(srcdir)" -$(src_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_NetworkManager_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_nm_iface_helper_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_nm_iface_helper_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_libnmi_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_nm_initrd_generator_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_cmdline_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_ibft_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_initrd_tests_test_dt_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_libnmi_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_nm_initrd_generator_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_cmdline_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_ibft_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_initrd_tests_test_dt_reader_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_dhcp_tests_test_dhcp_dhclient_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_dhcp_tests_test_dhcp_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dhcp_tests_test_dhcp_dhclient_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dhcp_tests_test_dhcp_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_PPP_TRUE@$(src_ppp_nm_pppd_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_PPP_TRUE@$(src_core_ppp_nm_pppd_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_PPP_TRUE@$(src_ppp_libnm_ppp_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_PPP_TRUE@$(src_core_ppp_libnm_ppp_plugin_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_settings_plugins_keyfile_tests_test_keyfile_settings_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h: src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ @$(MKDIR_P) src/settings/plugins/ifcfg-rh/ +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h: src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ @$(MKDIR_P) src/core/settings/plugins/ifcfg-rh/ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(AM_V_GEN) gdbus-codegen \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ --generate-c-code $(basename $@) \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ --c-namespace NMDBus \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ --interface-prefix com.redhat \ @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $< -@CONFIG_PLUGIN_IFCFG_RH_TRUE@src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c: src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +@CONFIG_PLUGIN_IFCFG_RH_TRUE@src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.c: src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h @CONFIG_PLUGIN_IFCFG_RH_TRUE@ @true -@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_core_settings_plugins_ifcfg_rh_libnms_ifcfg_rh_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): src/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h -@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): src/core/settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h +@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_core_settings_plugins_ifcfg_rh_libnm_settings_plugin_ifcfg_rh_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@check-local-symbols-settings-ifcfg-rh: src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la -@CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(call check_so_symbols,$(builddir)/src/settings/plugins/ifcfg-rh/.libs/libnm-settings-plugin-ifcfg-rh.so) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@check-local-symbols-settings-ifcfg-rh: src/core/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la +@CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(call check_so_symbols,$(builddir)/src/core/settings/plugins/ifcfg-rh/.libs/libnm-settings-plugin-ifcfg-rh.so) -@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFCFG_RH_TRUE@$(src_core_settings_plugins_ifcfg_rh_tests_test_ifcfg_rh_OBJECTS): $(libnm_core_lib_h_pub_mkenums) @CONFIG_PLUGIN_IFCFG_RH_TRUE@install-data-hook-ifcfg-rh: @CONFIG_PLUGIN_IFCFG_RH_TRUE@ $(mkinstalldirs) -m 0755 $(DESTDIR)$(sysconfdir)/sysconfig/network-scripts dist-hook-settings-ifcfg-rh-alias-files: - @for f in $(src_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files); do \ - cp $(abs_srcdir)/$$f $(distdir)/src/settings/plugins/ifcfg-rh/tests/network-scripts/; \ + @for f in $(src_core_settings_plugins_ifcfg_rh_tests_network_scripts_alias_files); do \ + cp $(abs_srcdir)/$$f $(distdir)/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/; \ done -@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_core_settings_plugins_ifupdown_libnm_settings_plugin_ifupdown_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_core_settings_plugins_ifupdown_libnms_ifupdown_core_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@check-local-symbols-settings-ifupdown: src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la -@CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(call check_so_symbols,$(builddir)/src/settings/plugins/ifupdown/.libs/libnm-settings-plugin-ifupdown.so) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@check-local-symbols-settings-ifupdown: src/core/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la +@CONFIG_PLUGIN_IFUPDOWN_TRUE@ $(call check_so_symbols,$(builddir)/src/core/settings/plugins/ifupdown/.libs/libnm-settings-plugin-ifupdown.so) -@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@CONFIG_PLUGIN_IFUPDOWN_TRUE@$(src_core_settings_plugins_ifupdown_tests_test_ifupdown_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -check-local-devices-adsl: src/devices/adsl/libnm-device-plugin-adsl.la - $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver" - $(call check_so_symbols,$(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so) +check-local-devices-adsl: src/core/devices/adsl/libnm-device-plugin-adsl.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/core/devices/adsl/.libs/libnm-device-plugin-adsl.so) -@WITH_MODEM_MANAGER_1_TRUE@$(src_devices_wwan_libnm_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_MODEM_MANAGER_1_TRUE@$(src_core_devices_wwan_libnm_wwan_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_MODEM_MANAGER_1_TRUE@check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devices/wwan/libnm-wwan.la -@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver" -@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so) -@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/devices/wwan/libnm-wwan.ver" -@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-wwan.so) +@WITH_MODEM_MANAGER_1_TRUE@check-local-devices-wwan: src/core/devices/wwan/libnm-device-plugin-wwan.la src/core/devices/wwan/libnm-wwan.la +@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver" +@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/wwan/.libs/libnm-device-plugin-wwan.so) +@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/core/devices/wwan/libnm-wwan.ver" +@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/wwan/.libs/libnm-wwan.so) -@WITH_MODEM_MANAGER_1_TRUE@$(src_devices_wwan_tests_test_service_providers_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_MODEM_MANAGER_1_TRUE@$(src_core_devices_wwan_tests_test_service_providers_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_MODEM_MANAGER_1_TRUE@$(src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_MODEM_MANAGER_1_TRUE@$(src_core_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_MODEM_MANAGER_1_TRUE@$(src_devices_bluetooth_tests_nm_bt_test_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_MODEM_MANAGER_1_TRUE@$(src_core_devices_bluetooth_tests_nm_bt_test_OBJECTS): $(libnm_core_lib_h_pub_mkenums) ############################################################################### -@WITH_MODEM_MANAGER_1_TRUE@check-local-devices-bluetooth: src/devices/bluetooth/libnm-device-plugin-bluetooth.la -@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver" -@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so) +@WITH_MODEM_MANAGER_1_TRUE@check-local-devices-bluetooth: src/core/devices/bluetooth/libnm-device-plugin-bluetooth.la +@WITH_MODEM_MANAGER_1_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver" +@WITH_MODEM_MANAGER_1_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so) -@WITH_WIFI_TRUE@$(src_devices_wifi_libnm_wifi_base_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_WIFI_TRUE@$(src_core_devices_wifi_libnm_wifi_base_la_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_WIFI_TRUE@check-local-devices-wifi: src/devices/wifi/libnm-device-plugin-wifi.la -@WITH_WIFI_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver" -@WITH_WIFI_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so) +@WITH_WIFI_TRUE@check-local-devices-wifi: src/core/devices/wifi/libnm-device-plugin-wifi.la +@WITH_WIFI_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver" +@WITH_WIFI_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/wifi/.libs/libnm-device-plugin-wifi.so) -@WITH_WIFI_TRUE@$(src_devices_wifi_tests_test_devices_wifi_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +@WITH_WIFI_TRUE@$(src_core_devices_wifi_tests_test_devices_wifi_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -@WITH_TEAMDCTL_TRUE@check-local-devices-team: src/devices/team/libnm-device-plugin-team.la -@WITH_TEAMDCTL_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver" -@WITH_TEAMDCTL_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so) +@WITH_TEAMDCTL_TRUE@check-local-devices-team: src/core/devices/team/libnm-device-plugin-team.la +@WITH_TEAMDCTL_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver" +@WITH_TEAMDCTL_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/team/.libs/libnm-device-plugin-team.so) -@WITH_OPENVSWITCH_TRUE@check-local-devices-ovs: src/devices/ovs/libnm-device-plugin-ovs.la -@WITH_OPENVSWITCH_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver" -@WITH_OPENVSWITCH_TRUE@ $(call check_so_symbols,$(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so) +@WITH_OPENVSWITCH_TRUE@check-local-devices-ovs: src/core/devices/ovs/libnm-device-plugin-ovs.la +@WITH_OPENVSWITCH_TRUE@ $(srcdir)/tools/check-exports.sh $(builddir)/src/core/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver" +@WITH_OPENVSWITCH_TRUE@ $(call check_so_symbols,$(builddir)/src/core/devices/ovs/.libs/libnm-device-plugin-ovs.so) -$(src_dnsmasq_tests_test_dnsmasq_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_dnsmasq_tests_test_dnsmasq_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_monitor_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_address_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_address_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_cleanup_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_cleanup_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_link_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_link_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_nmp_object_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_platform_general_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_route_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_route_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_tc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_platform_tests_test_tc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_monitor_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_address_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_address_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_cleanup_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_cleanup_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_link_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_link_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_nmp_object_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_platform_general_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_route_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_route_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_tc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_platform_tests_test_tc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_tests_test_lldp_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_devices_tests_test_acd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_tests_test_lldp_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_devices_tests_test_acd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_ndisc_tests_test_ndisc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_ndisc_tests_test_ndisc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ndisc_tests_test_ndisc_linux_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_ndisc_tests_test_ndisc_fake_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_supplicant_tests_test_supplicant_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_supplicant_tests_test_supplicant_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_config_test_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_config_test_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_core_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_core_with_expect_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_dcb_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_ip4_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_ip6_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_l3cfg_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_wired_defname_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_core_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_core_with_expect_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_dcb_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_ip4_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_ip6_config_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_l3cfg_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_utils_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_wired_defname_OBJECTS): $(libnm_core_lib_h_pub_mkenums) -$(src_tests_test_systemd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +$(src_core_tests_test_systemd_OBJECTS): $(libnm_core_lib_h_pub_mkenums) dispatcher/nmdbus-dispatcher.h: dispatcher/nm-dispatcher.xml @$(MKDIR_P) dispatcher/ @@ -20941,9 +21042,13 @@ cscope: ############################################################################### +check-progs: all $(check_PROGRAMS) $(check_LTLIBRARIES) + +############################################################################### + .PRECIOUS: test-suite.log .DELETE_ON_ERROR: -.PHONY: cscope dist-configure-check $(check_local) $(dist_hook) +.PHONY: check-progs cscope dist-configure-check $(check_local) $(dist_hook) # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/aclocal.m4 b/aclocal.m4 index 4e36943..e3d5eee 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,6 +1,6 @@ -# generated automatically by aclocal 1.16.1 -*- Autoconf -*- +# generated automatically by aclocal 1.16.2 -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2020 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -364,7 +364,7 @@ AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) ])dnl PKG_HAVE_DEFINE_WITH_MODULES -# Copyright (C) 2002-2018 Free Software Foundation, Inc. +# Copyright (C) 2002-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -379,7 +379,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.16' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.16.1], [], +m4_if([$1], [1.16.2], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -395,14 +395,14 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.16.1])dnl +[AM_AUTOMAKE_VERSION([1.16.2])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -454,7 +454,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd` # AM_CONDITIONAL -*- Autoconf -*- -# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# Copyright (C) 1997-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -485,7 +485,7 @@ AC_CONFIG_COMMANDS_PRE( Usually this means the macro was only invoked conditionally.]]) fi])]) -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -676,7 +676,7 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl # Generate code to set up dependency tracking. -*- Autoconf -*- -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -715,7 +715,9 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], done if test $am_rc -ne 0; then AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments - for automatic dependency tracking. Try re-running configure with the + for automatic dependency tracking. If GNU make was not used, consider + re-running the configure script with MAKE="gmake" (or whatever is + necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking).]) fi @@ -742,7 +744,7 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], # Do all the work for Automake. -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -939,7 +941,7 @@ for _am_header in $config_headers :; do done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -960,7 +962,7 @@ if test x"${install_sh+set}" != xset; then fi AC_SUBST([install_sh])]) -# Copyright (C) 2003-2018 Free Software Foundation, Inc. +# Copyright (C) 2003-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -982,7 +984,7 @@ AC_SUBST([am__leading_dot])]) # Add --enable-maintainer-mode option to configure. -*- Autoconf -*- # From Jim Meyering -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1017,7 +1019,7 @@ AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) # Check to see how 'make' treats includes. -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1060,7 +1062,7 @@ AC_SUBST([am__quote])]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- -# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# Copyright (C) 1997-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1097,7 +1099,7 @@ else fi ]) -# Copyright (C) 2003-2018 Free Software Foundation, Inc. +# Copyright (C) 2003-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1131,7 +1133,7 @@ esac # Helper functions for option handling. -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1160,7 +1162,7 @@ AC_DEFUN([_AM_SET_OPTIONS], AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1207,7 +1209,7 @@ AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1445,7 +1447,7 @@ for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[[i]] sys.exit(sys.hexversion < minverhex)" AS_IF([AM_RUN_LOG([$1 -c "$prog"])], [$3], [$4])]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1464,7 +1466,7 @@ AC_DEFUN([AM_RUN_LOG], # Check to make sure that the build environment is sane. -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1545,7 +1547,7 @@ AC_CONFIG_COMMANDS_PRE( rm -f conftest.file ]) -# Copyright (C) 2009-2018 Free Software Foundation, Inc. +# Copyright (C) 2009-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1605,7 +1607,7 @@ AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl ]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1633,7 +1635,7 @@ fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) -# Copyright (C) 2006-2018 Free Software Foundation, Inc. +# Copyright (C) 2006-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1652,7 +1654,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- -# Copyright (C) 2004-2018 Free Software Foundation, Inc. +# Copyright (C) 2004-2020 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/autogen.sh b/autogen.sh index ca9253f..8809806 100755 --- a/autogen.sh +++ b/autogen.sh @@ -14,7 +14,7 @@ REQUIRED_AUTOMAKE_VERSION=1.9 PKG_NAME=NetworkManager (test -f $srcdir/configure.ac \ - && test -f $srcdir/src/main.c) || { + && test -f $srcdir/src/core/main.c) || { printf "**Error**: Directory "\`$srcdir\'" does not look like the" >&2 echo " top-level $PKG_NAME directory" >&2 exit 1 diff --git a/build-aux/compile b/build-aux/compile index 99e5052..23fcba0 100755 --- a/build-aux/compile +++ b/build-aux/compile @@ -3,7 +3,7 @@ scriptversion=2018-03-07.03; # UTC -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify @@ -53,7 +53,7 @@ func_file_conv () MINGW*) file_conv=mingw ;; - CYGWIN*) + CYGWIN* | MSYS*) file_conv=cygwin ;; *) @@ -67,7 +67,7 @@ func_file_conv () mingw/*) file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` ;; - cygwin/*) + cygwin/* | msys/*) file=`cygpath -m "$file" || echo "$file"` ;; wine/*) diff --git a/build-aux/depcomp b/build-aux/depcomp index 65cbf70..6b39162 100755 --- a/build-aux/depcomp +++ b/build-aux/depcomp @@ -3,7 +3,7 @@ scriptversion=2018-03-07.03; # UTC -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2020 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/build-aux/install-sh b/build-aux/install-sh index 8175c64..20d8b2e 100755 --- a/build-aux/install-sh +++ b/build-aux/install-sh @@ -451,7 +451,18 @@ do trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. - (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + (umask $cp_umask && + { test -z "$stripcmd" || { + # Create $dsttmp read-write so that cp doesn't create it read-only, + # which would cause strip to fail. + if test -z "$doit"; then + : >"$dsttmp" # No need to fork-exec 'touch'. + else + $doit touch "$dsttmp" + fi + } + } && + $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # diff --git a/build-aux/missing b/build-aux/missing index 625aeb1..8d0eaad 100755 --- a/build-aux/missing +++ b/build-aux/missing @@ -3,7 +3,7 @@ scriptversion=2018-03-07.03; # UTC -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2020 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify diff --git a/build-aux/tap-driver.sh b/build-aux/tap-driver.sh index 2516e9c..865761d 100755 --- a/build-aux/tap-driver.sh +++ b/build-aux/tap-driver.sh @@ -1,5 +1,5 @@ #! /bin/sh -# Copyright (C) 2011-2018 Free Software Foundation, Inc. +# Copyright (C) 2011-2020 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/build-aux/test-driver b/build-aux/test-driver index b8521a4..89dba1e 100755 --- a/build-aux/test-driver +++ b/build-aux/test-driver @@ -3,7 +3,7 @@ scriptversion=2018-03-07.03; # UTC -# Copyright (C) 2011-2018 Free Software Foundation, Inc. +# Copyright (C) 2011-2020 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/clients/cloud-setup/nm-cloud-setup.service.in b/clients/cloud-setup/nm-cloud-setup.service.in index 57f9e93..809f707 100644 --- a/clients/cloud-setup/nm-cloud-setup.service.in +++ b/clients/cloud-setup/nm-cloud-setup.service.in @@ -1,5 +1,6 @@ [Unit] Description=Automatically configure NetworkManager in cloud +Documentation=man:nm-cloud-setup(8) After=NetworkManager.service [Service] @@ -11,6 +12,9 @@ ExecStart=@libexecdir@/nm-cloud-setup # Cloud providers are disabled by default. You need to # Opt-in by setting the right environment variable for # the provider. +# +# Create a drop-in file to overwrite these variables or +# use systemctl edit. #Environment=NM_CLOUD_SETUP_EC2=yes #Environment=NM_CLOUD_SETUP_GCP=yes #Environment=NM_CLOUD_SETUP_AZURE=yes diff --git a/configure b/configure index 5195308..03330b0 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for NetworkManager 1.29.10. +# Generated by GNU Autoconf 2.69 for NetworkManager 1.29.11. # # Report bugs to . # @@ -591,8 +591,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='NetworkManager' PACKAGE_TARNAME='NetworkManager' -PACKAGE_VERSION='1.29.10' -PACKAGE_STRING='NetworkManager 1.29.10' +PACKAGE_VERSION='1.29.11' +PACKAGE_STRING='NetworkManager 1.29.11' PACKAGE_BUGREPORT='http://bugzilla.gnome.org/enter_bug.cgi?product=NetworkManager' PACKAGE_URL='' @@ -1726,7 +1726,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures NetworkManager 1.29.10 to adapt to many kinds of systems. +\`configure' configures NetworkManager 1.29.11 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1796,7 +1796,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of NetworkManager 1.29.10:";; + short | recursive ) echo "Configuration of NetworkManager 1.29.11:";; esac cat <<\_ACEOF @@ -2142,7 +2142,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -NetworkManager configure 1.29.10 +NetworkManager configure 1.29.11 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2915,7 +2915,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by NetworkManager $as_me 1.29.10, which was +It was created by NetworkManager $as_me 1.29.11, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3785,7 +3785,7 @@ fi # Define the identity of the package. PACKAGE='NetworkManager' - VERSION='1.29.10' + VERSION='1.29.11' cat >>confdefs.h <<_ACEOF @@ -17969,8 +17969,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu NM_MAJOR_VERSION=1 NM_MINOR_VERSION=29 -NM_MICRO_VERSION=10 -NM_VERSION=1.29.10 +NM_MICRO_VERSION=11 +NM_VERSION=1.29.11 @@ -17978,7 +17978,7 @@ NM_VERSION=1.29.10 -NM_GIT_SHA=05745cf0798ca07a3e8a2b1df8fa53b2cfcb9c6c +NM_GIT_SHA=49ea978f0849ef9bcf3b0fd05fbb9fa05964206b if test """" != "no-config-h" ; then @@ -29448,7 +29448,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by NetworkManager $as_me 1.29.10, which was +This file was extended by NetworkManager $as_me 1.29.11, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -29514,7 +29514,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -NetworkManager config.status 1.29.10 +NetworkManager config.status 1.29.11 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -30724,7 +30724,9 @@ $as_echo X/"$am_mf" | { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments - for automatic dependency tracking. Try re-running configure with the + for automatic dependency tracking. If GNU make was not used, consider + re-running the configure script with MAKE=\"gmake\" (or whatever is + necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See \`config.log' for more details" "$LINENO" 5; } diff --git a/configure.ac b/configure.ac index 627989f..cd5837e 100644 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ dnl "shared/nm-version-macros.h.in" dnl - update number in meson.build m4_define([nm_major_version], [1]) m4_define([nm_minor_version], [29]) -m4_define([nm_micro_version], [10]) +m4_define([nm_micro_version], [11]) m4_define([nm_version], [nm_major_version.nm_minor_version.nm_micro_version]) diff --git a/dispatcher/tests/meson.build b/dispatcher/tests/meson.build index 59169c0..90f3cad 100644 --- a/dispatcher/tests/meson.build +++ b/dispatcher/tests/meson.build @@ -8,7 +8,7 @@ exe = executable( include_directories: dispatcher_inc, dependencies: [ libnm_nm_default_dep, - libnm_utils_base_dep, + libnm_glib_aux_dep, ], c_args: [ '-DNETWORKMANAGER_COMPILATION_TEST', diff --git a/docs/api/Makefile.in b/docs/api/Makefile.in index b82b032..fcbc944 100644 --- a/docs/api/Makefile.in +++ b/docs/api/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -555,6 +555,7 @@ DOC_STAMPS = setup-build.stamp scan-build.stamp sgml-build.stamp \ sgml.stamp html.stamp pdf.stamp SCANOBJ_FILES = \ + $(DOC_MODULE).actions \ $(DOC_MODULE).args \ $(DOC_MODULE).hierarchy \ $(DOC_MODULE).interfaces \ diff --git a/docs/api/html/NetworkManager.conf.html b/docs/api/html/NetworkManager.conf.html index 3655be2..05250d1 100644 --- a/docs/api/html/NetworkManager.conf.html +++ b/docs/api/html/NetworkManager.conf.html @@ -8,7 +8,7 @@ - + @@ -1588,6 +1588,6 @@ interface-name:vboxnet*,except:interface-name:vboxnet2 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/NetworkManager.html b/docs/api/html/NetworkManager.html index ad0e679..ff497a1 100644 --- a/docs/api/html/NetworkManager.html +++ b/docs/api/html/NetworkManager.html @@ -8,7 +8,7 @@ - + @@ -594,6 +594,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ch01.html b/docs/api/html/ch01.html index 4cbe2f6..0715709 100644 --- a/docs/api/html/ch01.html +++ b/docs/api/html/ch01.html @@ -8,7 +8,7 @@ - + @@ -179,6 +179,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/dbus-secret-agent.html b/docs/api/html/dbus-secret-agent.html index b8a0c99..71d7ea5 100644 --- a/docs/api/html/dbus-secret-agent.html +++ b/docs/api/html/dbus-secret-agent.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/dbus-types.html b/docs/api/html/dbus-types.html index cfdccb5..b5eb97f 100644 --- a/docs/api/html/dbus-types.html +++ b/docs/api/html/dbus-types.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/dbus-vpn-plugin.html b/docs/api/html/dbus-vpn-plugin.html index 1c6124e..7bec382 100644 --- a/docs/api/html/dbus-vpn-plugin.html +++ b/docs/api/html/dbus-vpn-plugin.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/dbus-vpn-types.html b/docs/api/html/dbus-vpn-types.html index a6c5d51..bb2edb3 100644 --- a/docs/api/html/dbus-vpn-types.html +++ b/docs/api/html/dbus-vpn-types.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.AccessPoint.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.AccessPoint.html index 4b3f1a1..550a116 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.AccessPoint.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.AccessPoint.html @@ -8,7 +8,7 @@ - + @@ -186,6 +186,6 @@ LastSeen readable i +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.AgentManager.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.AgentManager.html index 4a2591b..a237c1b 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.AgentManager.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.AgentManager.html @@ -8,7 +8,7 @@ - + @@ -105,6 +105,6 @@ Unregister (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Checkpoint.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Checkpoint.html index fd78205..8f4f7b2 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Checkpoint.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Checkpoint.html @@ -8,7 +8,7 @@ - + @@ -105,6 +105,6 @@ RollbackTimeout readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Connection.Active.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Connection.Active.html index ee122d8..63e4612 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Connection.Active.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Connection.Active.html @@ -8,7 +8,7 @@ - + @@ -297,6 +297,6 @@ Master readable o +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP4Config.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP4Config.html index f33cfc0..96238c2 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP4Config.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP4Config.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ Options readable a{sv} +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP6Config.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP6Config.html index e0b70ca..f26712a 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP6Config.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DHCP6Config.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ Options readable a{sv} +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Adsl.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Adsl.html index 623a76b..f886c18 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Adsl.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Adsl.html @@ -8,7 +8,7 @@ - + @@ -86,6 +86,6 @@ Carrier readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bluetooth.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bluetooth.html index a138c19..bcd45b4 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bluetooth.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bluetooth.html @@ -8,7 +8,7 @@ - + @@ -107,6 +107,6 @@ BtCapabilities readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bond.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bond.html index f670573..a7e9cf2 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bond.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bond.html @@ -8,7 +8,7 @@ - + @@ -110,6 +110,6 @@ Slaves readable ao +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bridge.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bridge.html index 34785c0..b820333 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bridge.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Bridge.html @@ -8,7 +8,7 @@ - + @@ -110,6 +110,6 @@ Slaves readable ao +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Dummy.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Dummy.html index fa9e6dc..a11a705 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Dummy.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Dummy.html @@ -8,7 +8,7 @@ - + @@ -85,6 +85,6 @@ n +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Generic.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Generic.html index 1894866..477eb58 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Generic.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Generic.html @@ -8,7 +8,7 @@ - + @@ -95,6 +95,6 @@ TypeDescription readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.IPTunnel.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.IPTunnel.html index b0da4a5..2a00c9e 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.IPTunnel.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.IPTunnel.html @@ -8,7 +8,7 @@ - + @@ -197,6 +197,6 @@ Flags readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Infiniband.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Infiniband.html index d81e12f..84668e7 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Infiniband.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Infiniband.html @@ -8,7 +8,7 @@ - + @@ -99,6 +99,6 @@ Carrier readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Lowpan.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Lowpan.html index b45c0fb..c6182a6 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Lowpan.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Lowpan.html @@ -8,7 +8,7 @@ - + @@ -67,6 +67,6 @@ Parent readable o +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macsec.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macsec.html index c248f9f..11ec7cc 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macsec.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macsec.html @@ -8,7 +8,7 @@ - + @@ -209,6 +209,6 @@ ReplayProtect readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macvlan.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macvlan.html index a227ff1..62daf00 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macvlan.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Macvlan.html @@ -8,7 +8,7 @@ - + @@ -113,6 +113,6 @@ Tap readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Modem.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Modem.html index ae6a3a3..fbe9a32 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Modem.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Modem.html @@ -8,7 +8,7 @@ - + @@ -140,6 +140,6 @@ Apn readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OlpcMesh.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OlpcMesh.html index ec2a78c..3f6eef5 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OlpcMesh.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OlpcMesh.html @@ -8,7 +8,7 @@ - + @@ -105,6 +105,6 @@ ActiveChannel readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsBridge.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsBridge.html index 04c5215..6bb2c8b 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsBridge.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsBridge.html @@ -8,7 +8,7 @@ - + @@ -86,6 +86,6 @@ Slaves readable ao +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsInterface.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsInterface.html index 87c76b5..473cafc 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsInterface.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsInterface.html @@ -8,7 +8,7 @@ - + @@ -65,6 +65,6 @@ PropertiesChanged (a{sv} properties); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsPort.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsPort.html index 4f6d8df..78517f5 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsPort.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.OvsPort.html @@ -8,7 +8,7 @@ - + @@ -86,6 +86,6 @@ Slaves readable ao +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Ppp.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Ppp.html index 31e153b..a0af822 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Ppp.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Ppp.html @@ -8,7 +8,7 @@ - + @@ -65,6 +65,6 @@ PropertiesChanged (a{sv} properties); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Statistics.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Statistics.html index ea3eca7..e5fc091 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Statistics.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Statistics.html @@ -8,7 +8,7 @@ - + @@ -106,6 +106,6 @@ RxBytes readable t +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Team.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Team.html index 208f5a5..2b52be6 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Team.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Team.html @@ -8,7 +8,7 @@ - + @@ -120,6 +120,6 @@ Config readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Tun.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Tun.html index 314b17d..39f4c6b 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Tun.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Tun.html @@ -8,7 +8,7 @@ - + @@ -148,6 +148,6 @@ n +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Veth.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Veth.html index 3913dae..6dd7273 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Veth.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Veth.html @@ -8,7 +8,7 @@ - + @@ -83,6 +83,6 @@ Peer readable o +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vlan.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vlan.html index 8220f53..3b5d09f 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vlan.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vlan.html @@ -8,7 +8,7 @@ - + @@ -119,6 +119,6 @@ VlanId readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vrf.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vrf.html index afd573f..3e9572d 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vrf.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vrf.html @@ -8,7 +8,7 @@ - + @@ -55,6 +55,6 @@ Table readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vxlan.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vxlan.html index 2a1067b..587feaf 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vxlan.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Vxlan.html @@ -8,7 +8,7 @@ - + @@ -252,6 +252,6 @@ L3miss readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WifiP2P.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WifiP2P.html index b426b89..91dd84a 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WifiP2P.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WifiP2P.html @@ -8,7 +8,7 @@ - + @@ -168,6 +168,6 @@ Peers readable ao +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WireGuard.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WireGuard.html index 5bb89c9..f07fb94 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WireGuard.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.WireGuard.html @@ -8,7 +8,7 @@ - + @@ -76,6 +76,6 @@ FwMark readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wired.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wired.html index 46f84e3..6a65cbf 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wired.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wired.html @@ -8,7 +8,7 @@ - + @@ -129,6 +129,6 @@ Carrier readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html index 98d8160..022e2ae 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html @@ -8,7 +8,7 @@ - + @@ -274,6 +274,6 @@ LastScan readable x +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wpan.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wpan.html index 0b20a21..f894cc0 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wpan.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.Wpan.html @@ -8,7 +8,7 @@ - + @@ -57,6 +57,6 @@ HwAddress readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.html index 058b5b5..0bb8e85 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Device.html @@ -8,7 +8,7 @@ - + @@ -605,6 +605,6 @@ HwAddress readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DnsManager.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DnsManager.html index fa0bd56..f6091bf 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.DnsManager.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.DnsManager.html @@ -8,7 +8,7 @@ - + @@ -82,6 +82,6 @@ Configuration readable aa{sv} +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP4Config.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP4Config.html index c941c60..4a891b9 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP4Config.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP4Config.html @@ -8,7 +8,7 @@ - + @@ -220,6 +220,6 @@ WinsServerData readable as +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP6Config.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP6Config.html index e046dfe..82db2ff 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP6Config.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.IP6Config.html @@ -8,7 +8,7 @@ - + @@ -179,6 +179,6 @@ DnsPriority readable i +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.PPP.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.PPP.html index c8b3d2f..4670db2 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.PPP.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.PPP.html @@ -8,7 +8,7 @@ - + @@ -147,6 +147,6 @@ SetIfindex (IN i ifindex); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.SecretAgent.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.SecretAgent.html index a141eb1..c8d1d6b 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.SecretAgent.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.SecretAgent.html @@ -8,7 +8,7 @@ - + @@ -197,6 +197,6 @@ DeleteSecrets (IN a{sa{sv}} connection, +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html index 8149cdb..478de12 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html @@ -8,7 +8,7 @@ - + @@ -355,6 +355,6 @@ Filename readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.html index 4fa9a5e..da39bc0 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.Settings.html @@ -8,7 +8,7 @@ - + @@ -423,6 +423,6 @@ CanModify readable b +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Connection.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Connection.html index b8945e0..b19d4c5 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Connection.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Connection.html @@ -8,7 +8,7 @@ - + @@ -124,6 +124,6 @@ Banner readable s +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Plugin.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Plugin.html index 1eb6ca2..b63ea74 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Plugin.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.VPN.Plugin.html @@ -8,7 +8,7 @@ - + @@ -415,6 +415,6 @@ State readable u +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.WifiP2PPeer.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.WifiP2PPeer.html index 12ef5c7..478368a 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.WifiP2PPeer.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.WifiP2PPeer.html @@ -8,7 +8,7 @@ - + @@ -170,6 +170,6 @@ LastSeen readable i +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/gdbus-org.freedesktop.NetworkManager.html b/docs/api/html/gdbus-org.freedesktop.NetworkManager.html index 8843c3a..a3993d5 100644 --- a/docs/api/html/gdbus-org.freedesktop.NetworkManager.html +++ b/docs/api/html/gdbus-org.freedesktop.NetworkManager.html @@ -8,7 +8,7 @@ - + @@ -1039,6 +1039,6 @@ GlobalDnsConfiguration readwrite a{sv} +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/index.html b/docs/api/html/index.html index 5a7b708..6b5b647 100644 --- a/docs/api/html/index.html +++ b/docs/api/html/index.html @@ -7,7 +7,7 @@ - + @@ -16,7 +16,7 @@

- for NetworkManager 1.29.10 + for NetworkManager 1.29.11 The latest version of this documentation can be found on-line at https://developer.gnome.org/NetworkManager/stable/. @@ -452,6 +452,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ix01.html b/docs/api/html/ix01.html index 7539665..5be626f 100644 --- a/docs/api/html/ix01.html +++ b/docs/api/html/ix01.html @@ -8,7 +8,7 @@ - + @@ -26,1694 +26,1694 @@

Symbols

-
org.freedesktop.NetworkManager.AccessPoint, org.freedesktop.NetworkManager.AccessPoint +
org.freedesktop.NetworkManager.AccessPoint, org.freedesktop.NetworkManager.AccessPoint
-
org.freedesktop.NetworkManager.AccessPoint::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.AccessPoint::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.AccessPoint:Flags, The "Flags" property +
org.freedesktop.NetworkManager.AccessPoint:Flags, The "Flags" property
-
org.freedesktop.NetworkManager.AccessPoint:Frequency, The "Frequency" property +
org.freedesktop.NetworkManager.AccessPoint:Frequency, The "Frequency" property
-
org.freedesktop.NetworkManager.AccessPoint:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.AccessPoint:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.AccessPoint:LastSeen, The "LastSeen" property +
org.freedesktop.NetworkManager.AccessPoint:LastSeen, The "LastSeen" property
-
org.freedesktop.NetworkManager.AccessPoint:MaxBitrate, The "MaxBitrate" property +
org.freedesktop.NetworkManager.AccessPoint:MaxBitrate, The "MaxBitrate" property
-
org.freedesktop.NetworkManager.AccessPoint:Mode, The "Mode" property +
org.freedesktop.NetworkManager.AccessPoint:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.AccessPoint:RsnFlags, The "RsnFlags" property +
org.freedesktop.NetworkManager.AccessPoint:RsnFlags, The "RsnFlags" property
-
org.freedesktop.NetworkManager.AccessPoint:Ssid, The "Ssid" property +
org.freedesktop.NetworkManager.AccessPoint:Ssid, The "Ssid" property
-
org.freedesktop.NetworkManager.AccessPoint:Strength, The "Strength" property +
org.freedesktop.NetworkManager.AccessPoint:Strength, The "Strength" property
-
org.freedesktop.NetworkManager.AccessPoint:WpaFlags, The "WpaFlags" property +
org.freedesktop.NetworkManager.AccessPoint:WpaFlags, The "WpaFlags" property
-
org.freedesktop.NetworkManager.AgentManager, org.freedesktop.NetworkManager.AgentManager +
org.freedesktop.NetworkManager.AgentManager, org.freedesktop.NetworkManager.AgentManager
-
org.freedesktop.NetworkManager.AgentManager.Register(), The Register() method +
org.freedesktop.NetworkManager.AgentManager.Register(), The Register() method
-
org.freedesktop.NetworkManager.AgentManager.RegisterWithCapabilities(), The RegisterWithCapabilities() method +
org.freedesktop.NetworkManager.AgentManager.RegisterWithCapabilities(), The RegisterWithCapabilities() method
-
org.freedesktop.NetworkManager.AgentManager.Unregister(), The Unregister() method +
org.freedesktop.NetworkManager.AgentManager.Unregister(), The Unregister() method
-
org.freedesktop.NetworkManager.Device, org.freedesktop.NetworkManager.Device +
org.freedesktop.NetworkManager.Device, org.freedesktop.NetworkManager.Device
-
org.freedesktop.NetworkManager.Device.Adsl, org.freedesktop.NetworkManager.Device.Adsl +
org.freedesktop.NetworkManager.Device.Adsl, org.freedesktop.NetworkManager.Device.Adsl
-
org.freedesktop.NetworkManager.Device.Adsl::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Adsl::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Adsl:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Adsl:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Bluetooth, org.freedesktop.NetworkManager.Device.Bluetooth +
org.freedesktop.NetworkManager.Device.Bluetooth, org.freedesktop.NetworkManager.Device.Bluetooth
-
org.freedesktop.NetworkManager.Device.Bluetooth::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Bluetooth::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Bluetooth:BtCapabilities, The "BtCapabilities" property +
org.freedesktop.NetworkManager.Device.Bluetooth:BtCapabilities, The "BtCapabilities" property
-
org.freedesktop.NetworkManager.Device.Bluetooth:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Bluetooth:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Bluetooth:Name, The "Name" property +
org.freedesktop.NetworkManager.Device.Bluetooth:Name, The "Name" property
-
org.freedesktop.NetworkManager.Device.Bond, org.freedesktop.NetworkManager.Device.Bond +
org.freedesktop.NetworkManager.Device.Bond, org.freedesktop.NetworkManager.Device.Bond
-
org.freedesktop.NetworkManager.Device.Bond::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Bond::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Bond:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Bond:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Bond:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Bond:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Bond:Slaves, The "Slaves" property +
org.freedesktop.NetworkManager.Device.Bond:Slaves, The "Slaves" property
-
org.freedesktop.NetworkManager.Device.Bridge, org.freedesktop.NetworkManager.Device.Bridge +
org.freedesktop.NetworkManager.Device.Bridge, org.freedesktop.NetworkManager.Device.Bridge
-
org.freedesktop.NetworkManager.Device.Bridge::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Bridge::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Bridge:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Bridge:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Bridge:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Bridge:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Bridge:Slaves, The "Slaves" property +
org.freedesktop.NetworkManager.Device.Bridge:Slaves, The "Slaves" property
-
org.freedesktop.NetworkManager.Device.Delete(), The Delete() method +
org.freedesktop.NetworkManager.Device.Delete(), The Delete() method
-
org.freedesktop.NetworkManager.Device.Disconnect(), The Disconnect() method +
org.freedesktop.NetworkManager.Device.Disconnect(), The Disconnect() method
-
org.freedesktop.NetworkManager.Device.Dummy, org.freedesktop.NetworkManager.Device.Dummy +
org.freedesktop.NetworkManager.Device.Dummy, org.freedesktop.NetworkManager.Device.Dummy
-
org.freedesktop.NetworkManager.Device.Dummy::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Dummy::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Dummy:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Dummy:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Generic, org.freedesktop.NetworkManager.Device.Generic +
org.freedesktop.NetworkManager.Device.Generic, org.freedesktop.NetworkManager.Device.Generic
-
org.freedesktop.NetworkManager.Device.Generic::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Generic::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Generic:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Generic:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Generic:TypeDescription, The "TypeDescription" property +
org.freedesktop.NetworkManager.Device.Generic:TypeDescription, The "TypeDescription" property
-
org.freedesktop.NetworkManager.Device.GetAppliedConnection(), The GetAppliedConnection() method +
org.freedesktop.NetworkManager.Device.GetAppliedConnection(), The GetAppliedConnection() method
-
org.freedesktop.NetworkManager.Device.Infiniband, org.freedesktop.NetworkManager.Device.Infiniband +
org.freedesktop.NetworkManager.Device.Infiniband, org.freedesktop.NetworkManager.Device.Infiniband
-
org.freedesktop.NetworkManager.Device.Infiniband::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Infiniband::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Infiniband:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Infiniband:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Infiniband:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Infiniband:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.IPTunnel, org.freedesktop.NetworkManager.Device.IPTunnel +
org.freedesktop.NetworkManager.Device.IPTunnel, org.freedesktop.NetworkManager.Device.IPTunnel
-
org.freedesktop.NetworkManager.Device.IPTunnel::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.IPTunnel::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.IPTunnel:EncapsulationLimit, The "EncapsulationLimit" property +
org.freedesktop.NetworkManager.Device.IPTunnel:EncapsulationLimit, The "EncapsulationLimit" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Flags, The "Flags" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Flags, The "Flags" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:FlowLabel, The "FlowLabel" property +
org.freedesktop.NetworkManager.Device.IPTunnel:FlowLabel, The "FlowLabel" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:InputKey, The "InputKey" property +
org.freedesktop.NetworkManager.Device.IPTunnel:InputKey, The "InputKey" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Local, The "Local" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Local, The "Local" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Mode, The "Mode" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:OutputKey, The "OutputKey" property +
org.freedesktop.NetworkManager.Device.IPTunnel:OutputKey, The "OutputKey" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:PathMtuDiscovery, The "PathMtuDiscovery" property +
org.freedesktop.NetworkManager.Device.IPTunnel:PathMtuDiscovery, The "PathMtuDiscovery" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Remote, The "Remote" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Remote, The "Remote" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Tos, The "Tos" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Tos, The "Tos" property
-
org.freedesktop.NetworkManager.Device.IPTunnel:Ttl, The "Ttl" property +
org.freedesktop.NetworkManager.Device.IPTunnel:Ttl, The "Ttl" property
-
org.freedesktop.NetworkManager.Device.Lowpan, org.freedesktop.NetworkManager.Device.Lowpan +
org.freedesktop.NetworkManager.Device.Lowpan, org.freedesktop.NetworkManager.Device.Lowpan
-
org.freedesktop.NetworkManager.Device.Lowpan:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Lowpan:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Lowpan:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.Lowpan:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.Macsec, org.freedesktop.NetworkManager.Device.Macsec +
org.freedesktop.NetworkManager.Device.Macsec, org.freedesktop.NetworkManager.Device.Macsec
-
org.freedesktop.NetworkManager.Device.Macsec::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Macsec::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Macsec:CipherSuite, The "CipherSuite" property +
org.freedesktop.NetworkManager.Device.Macsec:CipherSuite, The "CipherSuite" property
-
org.freedesktop.NetworkManager.Device.Macsec:EncodingSa, The "EncodingSa" property +
org.freedesktop.NetworkManager.Device.Macsec:EncodingSa, The "EncodingSa" property
-
org.freedesktop.NetworkManager.Device.Macsec:Encrypt, The "Encrypt" property +
org.freedesktop.NetworkManager.Device.Macsec:Encrypt, The "Encrypt" property
-
org.freedesktop.NetworkManager.Device.Macsec:Es, The "Es" property +
org.freedesktop.NetworkManager.Device.Macsec:Es, The "Es" property
-
org.freedesktop.NetworkManager.Device.Macsec:IcvLength, The "IcvLength" property +
org.freedesktop.NetworkManager.Device.Macsec:IcvLength, The "IcvLength" property
-
org.freedesktop.NetworkManager.Device.Macsec:IncludeSci, The "IncludeSci" property +
org.freedesktop.NetworkManager.Device.Macsec:IncludeSci, The "IncludeSci" property
-
org.freedesktop.NetworkManager.Device.Macsec:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.Macsec:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.Macsec:Protect, The "Protect" property +
org.freedesktop.NetworkManager.Device.Macsec:Protect, The "Protect" property
-
org.freedesktop.NetworkManager.Device.Macsec:ReplayProtect, The "ReplayProtect" property +
org.freedesktop.NetworkManager.Device.Macsec:ReplayProtect, The "ReplayProtect" property
-
org.freedesktop.NetworkManager.Device.Macsec:Scb, The "Scb" property +
org.freedesktop.NetworkManager.Device.Macsec:Scb, The "Scb" property
-
org.freedesktop.NetworkManager.Device.Macsec:Sci, The "Sci" property +
org.freedesktop.NetworkManager.Device.Macsec:Sci, The "Sci" property
-
org.freedesktop.NetworkManager.Device.Macsec:Validation, The "Validation" property +
org.freedesktop.NetworkManager.Device.Macsec:Validation, The "Validation" property
-
org.freedesktop.NetworkManager.Device.Macsec:Window, The "Window" property +
org.freedesktop.NetworkManager.Device.Macsec:Window, The "Window" property
-
org.freedesktop.NetworkManager.Device.Macvlan, org.freedesktop.NetworkManager.Device.Macvlan +
org.freedesktop.NetworkManager.Device.Macvlan, org.freedesktop.NetworkManager.Device.Macvlan
-
org.freedesktop.NetworkManager.Device.Macvlan::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Macvlan::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Macvlan:Mode, The "Mode" property +
org.freedesktop.NetworkManager.Device.Macvlan:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.Device.Macvlan:NoPromisc, The "NoPromisc" property +
org.freedesktop.NetworkManager.Device.Macvlan:NoPromisc, The "NoPromisc" property
-
org.freedesktop.NetworkManager.Device.Macvlan:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.Macvlan:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.Macvlan:Tap, The "Tap" property +
org.freedesktop.NetworkManager.Device.Macvlan:Tap, The "Tap" property
-
org.freedesktop.NetworkManager.Device.Modem, org.freedesktop.NetworkManager.Device.Modem +
org.freedesktop.NetworkManager.Device.Modem, org.freedesktop.NetworkManager.Device.Modem
-
org.freedesktop.NetworkManager.Device.Modem::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Modem::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Modem:Apn, The "Apn" property +
org.freedesktop.NetworkManager.Device.Modem:Apn, The "Apn" property
-
org.freedesktop.NetworkManager.Device.Modem:CurrentCapabilities, The "CurrentCapabilities" property +
org.freedesktop.NetworkManager.Device.Modem:CurrentCapabilities, The "CurrentCapabilities" property
-
org.freedesktop.NetworkManager.Device.Modem:DeviceId, The "DeviceId" property +
org.freedesktop.NetworkManager.Device.Modem:DeviceId, The "DeviceId" property
-
org.freedesktop.NetworkManager.Device.Modem:ModemCapabilities, The "ModemCapabilities" property +
org.freedesktop.NetworkManager.Device.Modem:ModemCapabilities, The "ModemCapabilities" property
-
org.freedesktop.NetworkManager.Device.Modem:OperatorCode, The "OperatorCode" property +
org.freedesktop.NetworkManager.Device.Modem:OperatorCode, The "OperatorCode" property
-
org.freedesktop.NetworkManager.Device.OlpcMesh, org.freedesktop.NetworkManager.Device.OlpcMesh +
org.freedesktop.NetworkManager.Device.OlpcMesh, org.freedesktop.NetworkManager.Device.OlpcMesh
-
org.freedesktop.NetworkManager.Device.OlpcMesh::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.OlpcMesh::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.OlpcMesh:ActiveChannel, The "ActiveChannel" property +
org.freedesktop.NetworkManager.Device.OlpcMesh:ActiveChannel, The "ActiveChannel" property
-
org.freedesktop.NetworkManager.Device.OlpcMesh:Companion, The "Companion" property +
org.freedesktop.NetworkManager.Device.OlpcMesh:Companion, The "Companion" property
-
org.freedesktop.NetworkManager.Device.OlpcMesh:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.OlpcMesh:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.OvsBridge, org.freedesktop.NetworkManager.Device.OvsBridge +
org.freedesktop.NetworkManager.Device.OvsBridge, org.freedesktop.NetworkManager.Device.OvsBridge
-
org.freedesktop.NetworkManager.Device.OvsBridge::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.OvsBridge::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.OvsBridge:Slaves, The "Slaves" property +
org.freedesktop.NetworkManager.Device.OvsBridge:Slaves, The "Slaves" property
-
org.freedesktop.NetworkManager.Device.OvsInterface, org.freedesktop.NetworkManager.Device.OvsInterface +
org.freedesktop.NetworkManager.Device.OvsInterface, org.freedesktop.NetworkManager.Device.OvsInterface
-
org.freedesktop.NetworkManager.Device.OvsInterface::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.OvsInterface::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.OvsPort, org.freedesktop.NetworkManager.Device.OvsPort +
org.freedesktop.NetworkManager.Device.OvsPort, org.freedesktop.NetworkManager.Device.OvsPort
-
org.freedesktop.NetworkManager.Device.OvsPort::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.OvsPort::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.OvsPort:Slaves, The "Slaves" property +
org.freedesktop.NetworkManager.Device.OvsPort:Slaves, The "Slaves" property
-
org.freedesktop.NetworkManager.Device.Ppp, org.freedesktop.NetworkManager.Device.Ppp +
org.freedesktop.NetworkManager.Device.Ppp, org.freedesktop.NetworkManager.Device.Ppp
-
org.freedesktop.NetworkManager.Device.Ppp::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Ppp::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Reapply(), The Reapply() method +
org.freedesktop.NetworkManager.Device.Reapply(), The Reapply() method
-
org.freedesktop.NetworkManager.Device.Statistics, org.freedesktop.NetworkManager.Device.Statistics +
org.freedesktop.NetworkManager.Device.Statistics, org.freedesktop.NetworkManager.Device.Statistics
-
org.freedesktop.NetworkManager.Device.Statistics::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Statistics::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Statistics:RefreshRateMs, The "RefreshRateMs" property +
org.freedesktop.NetworkManager.Device.Statistics:RefreshRateMs, The "RefreshRateMs" property
-
org.freedesktop.NetworkManager.Device.Statistics:RxBytes, The "RxBytes" property +
org.freedesktop.NetworkManager.Device.Statistics:RxBytes, The "RxBytes" property
-
org.freedesktop.NetworkManager.Device.Statistics:TxBytes, The "TxBytes" property +
org.freedesktop.NetworkManager.Device.Statistics:TxBytes, The "TxBytes" property
-
org.freedesktop.NetworkManager.Device.Team, org.freedesktop.NetworkManager.Device.Team +
org.freedesktop.NetworkManager.Device.Team, org.freedesktop.NetworkManager.Device.Team
-
org.freedesktop.NetworkManager.Device.Team::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Team::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Team:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Team:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Team:Config, The "Config" property +
org.freedesktop.NetworkManager.Device.Team:Config, The "Config" property
-
org.freedesktop.NetworkManager.Device.Team:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Team:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Team:Slaves, The "Slaves" property +
org.freedesktop.NetworkManager.Device.Team:Slaves, The "Slaves" property
-
org.freedesktop.NetworkManager.Device.Tun, org.freedesktop.NetworkManager.Device.Tun +
org.freedesktop.NetworkManager.Device.Tun, org.freedesktop.NetworkManager.Device.Tun
-
org.freedesktop.NetworkManager.Device.Tun::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Tun::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Tun:Group, The "Group" property +
org.freedesktop.NetworkManager.Device.Tun:Group, The "Group" property
-
org.freedesktop.NetworkManager.Device.Tun:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Tun:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Tun:Mode, The "Mode" property +
org.freedesktop.NetworkManager.Device.Tun:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.Device.Tun:MultiQueue, The "MultiQueue" property +
org.freedesktop.NetworkManager.Device.Tun:MultiQueue, The "MultiQueue" property
-
org.freedesktop.NetworkManager.Device.Tun:NoPi, The "NoPi" property +
org.freedesktop.NetworkManager.Device.Tun:NoPi, The "NoPi" property
-
org.freedesktop.NetworkManager.Device.Tun:Owner, The "Owner" property +
org.freedesktop.NetworkManager.Device.Tun:Owner, The "Owner" property
-
org.freedesktop.NetworkManager.Device.Tun:VnetHdr, The "VnetHdr" property +
org.freedesktop.NetworkManager.Device.Tun:VnetHdr, The "VnetHdr" property
-
org.freedesktop.NetworkManager.Device.Veth, org.freedesktop.NetworkManager.Device.Veth +
org.freedesktop.NetworkManager.Device.Veth, org.freedesktop.NetworkManager.Device.Veth
-
org.freedesktop.NetworkManager.Device.Veth::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Veth::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Veth:Peer, The "Peer" property +
org.freedesktop.NetworkManager.Device.Veth:Peer, The "Peer" property
-
org.freedesktop.NetworkManager.Device.Vlan, org.freedesktop.NetworkManager.Device.Vlan +
org.freedesktop.NetworkManager.Device.Vlan, org.freedesktop.NetworkManager.Device.Vlan
-
org.freedesktop.NetworkManager.Device.Vlan::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Vlan::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Vlan:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Vlan:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Vlan:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Vlan:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Vlan:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.Vlan:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.Vlan:VlanId, The "VlanId" property +
org.freedesktop.NetworkManager.Device.Vlan:VlanId, The "VlanId" property
-
org.freedesktop.NetworkManager.Device.Vrf, org.freedesktop.NetworkManager.Device.Vrf +
org.freedesktop.NetworkManager.Device.Vrf, org.freedesktop.NetworkManager.Device.Vrf
-
org.freedesktop.NetworkManager.Device.Vrf:Table, The "Table" property +
org.freedesktop.NetworkManager.Device.Vrf:Table, The "Table" property
-
org.freedesktop.NetworkManager.Device.Vxlan, org.freedesktop.NetworkManager.Device.Vxlan +
org.freedesktop.NetworkManager.Device.Vxlan, org.freedesktop.NetworkManager.Device.Vxlan
-
org.freedesktop.NetworkManager.Device.Vxlan::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Vxlan::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Vxlan:Ageing, The "Ageing" property +
org.freedesktop.NetworkManager.Device.Vxlan:Ageing, The "Ageing" property
-
org.freedesktop.NetworkManager.Device.Vxlan:DstPort, The "DstPort" property +
org.freedesktop.NetworkManager.Device.Vxlan:DstPort, The "DstPort" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Group, The "Group" property +
org.freedesktop.NetworkManager.Device.Vxlan:Group, The "Group" property
-
org.freedesktop.NetworkManager.Device.Vxlan:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Vxlan:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Id, The "Id" property +
org.freedesktop.NetworkManager.Device.Vxlan:Id, The "Id" property
-
org.freedesktop.NetworkManager.Device.Vxlan:L2miss, The "L2miss" property +
org.freedesktop.NetworkManager.Device.Vxlan:L2miss, The "L2miss" property
-
org.freedesktop.NetworkManager.Device.Vxlan:L3miss, The "L3miss" property +
org.freedesktop.NetworkManager.Device.Vxlan:L3miss, The "L3miss" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Learning, The "Learning" property +
org.freedesktop.NetworkManager.Device.Vxlan:Learning, The "Learning" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Limit, The "Limit" property +
org.freedesktop.NetworkManager.Device.Vxlan:Limit, The "Limit" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Local, The "Local" property +
org.freedesktop.NetworkManager.Device.Vxlan:Local, The "Local" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Parent, The "Parent" property +
org.freedesktop.NetworkManager.Device.Vxlan:Parent, The "Parent" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Proxy, The "Proxy" property +
org.freedesktop.NetworkManager.Device.Vxlan:Proxy, The "Proxy" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Rsc, The "Rsc" property +
org.freedesktop.NetworkManager.Device.Vxlan:Rsc, The "Rsc" property
-
org.freedesktop.NetworkManager.Device.Vxlan:SrcPortMax, The "SrcPortMax" property +
org.freedesktop.NetworkManager.Device.Vxlan:SrcPortMax, The "SrcPortMax" property
-
org.freedesktop.NetworkManager.Device.Vxlan:SrcPortMin, The "SrcPortMin" property +
org.freedesktop.NetworkManager.Device.Vxlan:SrcPortMin, The "SrcPortMin" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Tos, The "Tos" property +
org.freedesktop.NetworkManager.Device.Vxlan:Tos, The "Tos" property
-
org.freedesktop.NetworkManager.Device.Vxlan:Ttl, The "Ttl" property +
org.freedesktop.NetworkManager.Device.Vxlan:Ttl, The "Ttl" property
-
org.freedesktop.NetworkManager.Device.WireGuard, org.freedesktop.NetworkManager.Device.WireGuard +
org.freedesktop.NetworkManager.Device.WireGuard, org.freedesktop.NetworkManager.Device.WireGuard
-
org.freedesktop.NetworkManager.Device.WireGuard:FwMark, The "FwMark" property +
org.freedesktop.NetworkManager.Device.WireGuard:FwMark, The "FwMark" property
-
org.freedesktop.NetworkManager.Device.WireGuard:ListenPort, The "ListenPort" property +
org.freedesktop.NetworkManager.Device.WireGuard:ListenPort, The "ListenPort" property
-
org.freedesktop.NetworkManager.Device.WireGuard:PublicKey, The "PublicKey" property +
org.freedesktop.NetworkManager.Device.WireGuard:PublicKey, The "PublicKey" property
-
org.freedesktop.NetworkManager.Device.Wpan, org.freedesktop.NetworkManager.Device.Wpan +
org.freedesktop.NetworkManager.Device.Wpan, org.freedesktop.NetworkManager.Device.Wpan
-
org.freedesktop.NetworkManager.Device.Wpan:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Wpan:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device::StateChanged, The "StateChanged" signal +
org.freedesktop.NetworkManager.Device::StateChanged, The "StateChanged" signal
-
org.freedesktop.NetworkManager.Device:ActiveConnection, The "ActiveConnection" property +
org.freedesktop.NetworkManager.Device:ActiveConnection, The "ActiveConnection" property
-
org.freedesktop.NetworkManager.Device:Autoconnect, The "Autoconnect" property +
org.freedesktop.NetworkManager.Device:Autoconnect, The "Autoconnect" property
-
org.freedesktop.NetworkManager.Device:AvailableConnections, The "AvailableConnections" property +
org.freedesktop.NetworkManager.Device:AvailableConnections, The "AvailableConnections" property
-
org.freedesktop.NetworkManager.Device:Capabilities, The "Capabilities" property +
org.freedesktop.NetworkManager.Device:Capabilities, The "Capabilities" property
-
org.freedesktop.NetworkManager.Device:DeviceType, The "DeviceType" property +
org.freedesktop.NetworkManager.Device:DeviceType, The "DeviceType" property
-
org.freedesktop.NetworkManager.Device:Dhcp4Config, The "Dhcp4Config" property +
org.freedesktop.NetworkManager.Device:Dhcp4Config, The "Dhcp4Config" property
-
org.freedesktop.NetworkManager.Device:Dhcp6Config, The "Dhcp6Config" property +
org.freedesktop.NetworkManager.Device:Dhcp6Config, The "Dhcp6Config" property
-
org.freedesktop.NetworkManager.Device:Driver, The "Driver" property +
org.freedesktop.NetworkManager.Device:Driver, The "Driver" property
-
org.freedesktop.NetworkManager.Device:DriverVersion, The "DriverVersion" property +
org.freedesktop.NetworkManager.Device:DriverVersion, The "DriverVersion" property
-
org.freedesktop.NetworkManager.Device:FirmwareMissing, The "FirmwareMissing" property +
org.freedesktop.NetworkManager.Device:FirmwareMissing, The "FirmwareMissing" property
-
org.freedesktop.NetworkManager.Device:FirmwareVersion, The "FirmwareVersion" property +
org.freedesktop.NetworkManager.Device:FirmwareVersion, The "FirmwareVersion" property
-
org.freedesktop.NetworkManager.Device:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device:Interface, The "Interface" property +
org.freedesktop.NetworkManager.Device:Interface, The "Interface" property
-
org.freedesktop.NetworkManager.Device:InterfaceFlags, The "InterfaceFlags" property +
org.freedesktop.NetworkManager.Device:InterfaceFlags, The "InterfaceFlags" property
-
org.freedesktop.NetworkManager.Device:Ip4Address, The "Ip4Address" property +
org.freedesktop.NetworkManager.Device:Ip4Address, The "Ip4Address" property
-
org.freedesktop.NetworkManager.Device:Ip4Config, The "Ip4Config" property +
org.freedesktop.NetworkManager.Device:Ip4Config, The "Ip4Config" property
-
org.freedesktop.NetworkManager.Device:Ip4Connectivity, The "Ip4Connectivity" property +
org.freedesktop.NetworkManager.Device:Ip4Connectivity, The "Ip4Connectivity" property
-
org.freedesktop.NetworkManager.Device:Ip6Config, The "Ip6Config" property +
org.freedesktop.NetworkManager.Device:Ip6Config, The "Ip6Config" property
-
org.freedesktop.NetworkManager.Device:Ip6Connectivity, The "Ip6Connectivity" property +
org.freedesktop.NetworkManager.Device:Ip6Connectivity, The "Ip6Connectivity" property
-
org.freedesktop.NetworkManager.Device:IpInterface, The "IpInterface" property +
org.freedesktop.NetworkManager.Device:IpInterface, The "IpInterface" property
-
org.freedesktop.NetworkManager.Device:LldpNeighbors, The "LldpNeighbors" property +
org.freedesktop.NetworkManager.Device:LldpNeighbors, The "LldpNeighbors" property
-
org.freedesktop.NetworkManager.Device:Managed, The "Managed" property +
org.freedesktop.NetworkManager.Device:Managed, The "Managed" property
-
org.freedesktop.NetworkManager.Device:Metered, The "Metered" property +
org.freedesktop.NetworkManager.Device:Metered, The "Metered" property
-
org.freedesktop.NetworkManager.Device:Mtu, The "Mtu" property +
org.freedesktop.NetworkManager.Device:Mtu, The "Mtu" property
-
org.freedesktop.NetworkManager.Device:NmPluginMissing, The "NmPluginMissing" property +
org.freedesktop.NetworkManager.Device:NmPluginMissing, The "NmPluginMissing" property
-
org.freedesktop.NetworkManager.Device:Path, The "Path" property +
org.freedesktop.NetworkManager.Device:Path, The "Path" property
-
org.freedesktop.NetworkManager.Device:PhysicalPortId, The "PhysicalPortId" property +
org.freedesktop.NetworkManager.Device:PhysicalPortId, The "PhysicalPortId" property
-
org.freedesktop.NetworkManager.Device:Real, The "Real" property +
org.freedesktop.NetworkManager.Device:Real, The "Real" property
-
org.freedesktop.NetworkManager.Device:State, The "State" property +
org.freedesktop.NetworkManager.Device:State, The "State" property
-
org.freedesktop.NetworkManager.Device:StateReason, The "StateReason" property +
org.freedesktop.NetworkManager.Device:StateReason, The "StateReason" property
-
org.freedesktop.NetworkManager.Device:Udi, The "Udi" property +
org.freedesktop.NetworkManager.Device:Udi, The "Udi" property
-
org.freedesktop.NetworkManager.DnsManager, org.freedesktop.NetworkManager.DnsManager +
org.freedesktop.NetworkManager.DnsManager, org.freedesktop.NetworkManager.DnsManager
-
org.freedesktop.NetworkManager.DnsManager:Configuration, The "Configuration" property +
org.freedesktop.NetworkManager.DnsManager:Configuration, The "Configuration" property
-
org.freedesktop.NetworkManager.DnsManager:Mode, The "Mode" property +
org.freedesktop.NetworkManager.DnsManager:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.DnsManager:RcManager, The "RcManager" property +
org.freedesktop.NetworkManager.DnsManager:RcManager, The "RcManager" property
-
org.freedesktop.NetworkManager.IP4Config, org.freedesktop.NetworkManager.IP4Config +
org.freedesktop.NetworkManager.IP4Config, org.freedesktop.NetworkManager.IP4Config
-
org.freedesktop.NetworkManager.IP4Config::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.IP4Config::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.IP4Config:AddressData, The "AddressData" property +
org.freedesktop.NetworkManager.IP4Config:AddressData, The "AddressData" property
-
org.freedesktop.NetworkManager.IP4Config:Addresses, The "Addresses" property +
org.freedesktop.NetworkManager.IP4Config:Addresses, The "Addresses" property
-
org.freedesktop.NetworkManager.IP4Config:DnsOptions, The "DnsOptions" property +
org.freedesktop.NetworkManager.IP4Config:DnsOptions, The "DnsOptions" property
-
org.freedesktop.NetworkManager.IP4Config:DnsPriority, The "DnsPriority" property +
org.freedesktop.NetworkManager.IP4Config:DnsPriority, The "DnsPriority" property
-
org.freedesktop.NetworkManager.IP4Config:Domains, The "Domains" property +
org.freedesktop.NetworkManager.IP4Config:Domains, The "Domains" property
-
org.freedesktop.NetworkManager.IP4Config:Gateway, The "Gateway" property +
org.freedesktop.NetworkManager.IP4Config:Gateway, The "Gateway" property
-
org.freedesktop.NetworkManager.IP4Config:NameserverData, The "NameserverData" property +
org.freedesktop.NetworkManager.IP4Config:NameserverData, The "NameserverData" property
-
org.freedesktop.NetworkManager.IP4Config:Nameservers, The "Nameservers" property +
org.freedesktop.NetworkManager.IP4Config:Nameservers, The "Nameservers" property
-
org.freedesktop.NetworkManager.IP4Config:RouteData, The "RouteData" property +
org.freedesktop.NetworkManager.IP4Config:RouteData, The "RouteData" property
-
org.freedesktop.NetworkManager.IP4Config:Routes, The "Routes" property +
org.freedesktop.NetworkManager.IP4Config:Routes, The "Routes" property
-
org.freedesktop.NetworkManager.IP4Config:Searches, The "Searches" property +
org.freedesktop.NetworkManager.IP4Config:Searches, The "Searches" property
-
org.freedesktop.NetworkManager.IP4Config:WinsServerData, The "WinsServerData" property +
org.freedesktop.NetworkManager.IP4Config:WinsServerData, The "WinsServerData" property
-
org.freedesktop.NetworkManager.IP4Config:WinsServers, The "WinsServers" property +
org.freedesktop.NetworkManager.IP4Config:WinsServers, The "WinsServers" property
-
org.freedesktop.NetworkManager.IP6Config, org.freedesktop.NetworkManager.IP6Config +
org.freedesktop.NetworkManager.IP6Config, org.freedesktop.NetworkManager.IP6Config
-
org.freedesktop.NetworkManager.IP6Config::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.IP6Config::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.IP6Config:AddressData, The "AddressData" property +
org.freedesktop.NetworkManager.IP6Config:AddressData, The "AddressData" property
-
org.freedesktop.NetworkManager.IP6Config:Addresses, The "Addresses" property +
org.freedesktop.NetworkManager.IP6Config:Addresses, The "Addresses" property
-
org.freedesktop.NetworkManager.IP6Config:DnsOptions, The "DnsOptions" property +
org.freedesktop.NetworkManager.IP6Config:DnsOptions, The "DnsOptions" property
-
org.freedesktop.NetworkManager.IP6Config:DnsPriority, The "DnsPriority" property +
org.freedesktop.NetworkManager.IP6Config:DnsPriority, The "DnsPriority" property
-
org.freedesktop.NetworkManager.IP6Config:Domains, The "Domains" property +
org.freedesktop.NetworkManager.IP6Config:Domains, The "Domains" property
-
org.freedesktop.NetworkManager.IP6Config:Gateway, The "Gateway" property +
org.freedesktop.NetworkManager.IP6Config:Gateway, The "Gateway" property
-
org.freedesktop.NetworkManager.IP6Config:Nameservers, The "Nameservers" property +
org.freedesktop.NetworkManager.IP6Config:Nameservers, The "Nameservers" property
-
org.freedesktop.NetworkManager.IP6Config:RouteData, The "RouteData" property +
org.freedesktop.NetworkManager.IP6Config:RouteData, The "RouteData" property
-
org.freedesktop.NetworkManager.IP6Config:Routes, The "Routes" property +
org.freedesktop.NetworkManager.IP6Config:Routes, The "Routes" property
-
org.freedesktop.NetworkManager.IP6Config:Searches, The "Searches" property +
org.freedesktop.NetworkManager.IP6Config:Searches, The "Searches" property
-
org.freedesktop.NetworkManager.SecretAgent, org.freedesktop.NetworkManager.SecretAgent +
org.freedesktop.NetworkManager.SecretAgent, org.freedesktop.NetworkManager.SecretAgent
-
org.freedesktop.NetworkManager.SecretAgent.CancelGetSecrets(), The CancelGetSecrets() method +
org.freedesktop.NetworkManager.SecretAgent.CancelGetSecrets(), The CancelGetSecrets() method
-
org.freedesktop.NetworkManager.SecretAgent.DeleteSecrets(), The DeleteSecrets() method +
org.freedesktop.NetworkManager.SecretAgent.DeleteSecrets(), The DeleteSecrets() method
-
org.freedesktop.NetworkManager.SecretAgent.GetSecrets(), The GetSecrets() method +
org.freedesktop.NetworkManager.SecretAgent.GetSecrets(), The GetSecrets() method
-
org.freedesktop.NetworkManager.SecretAgent.SaveSecrets(), The SaveSecrets() method +
org.freedesktop.NetworkManager.SecretAgent.SaveSecrets(), The SaveSecrets() method
-
org.freedesktop.NetworkManager.Settings, org.freedesktop.NetworkManager.Settings +
org.freedesktop.NetworkManager.Settings, org.freedesktop.NetworkManager.Settings
-
org.freedesktop.NetworkManager.Settings.AddConnection(), The AddConnection() method +
org.freedesktop.NetworkManager.Settings.AddConnection(), The AddConnection() method
-
org.freedesktop.NetworkManager.Settings.AddConnection2(), The AddConnection2() method +
org.freedesktop.NetworkManager.Settings.AddConnection2(), The AddConnection2() method
-
org.freedesktop.NetworkManager.Settings.AddConnectionUnsaved(), The AddConnectionUnsaved() method +
org.freedesktop.NetworkManager.Settings.AddConnectionUnsaved(), The AddConnectionUnsaved() method
-
org.freedesktop.NetworkManager.Settings.Connection, org.freedesktop.NetworkManager.Settings.Connection +
org.freedesktop.NetworkManager.Settings.Connection, org.freedesktop.NetworkManager.Settings.Connection
-
org.freedesktop.NetworkManager.Settings.Connection.ClearSecrets(), The ClearSecrets() method +
org.freedesktop.NetworkManager.Settings.Connection.ClearSecrets(), The ClearSecrets() method
-
org.freedesktop.NetworkManager.Settings.Connection.Delete(), The Delete() method +
org.freedesktop.NetworkManager.Settings.Connection.Delete(), The Delete() method
-
org.freedesktop.NetworkManager.Settings.Connection.GetSecrets(), The GetSecrets() method +
org.freedesktop.NetworkManager.Settings.Connection.GetSecrets(), The GetSecrets() method
-
org.freedesktop.NetworkManager.Settings.Connection.GetSettings(), The GetSettings() method +
org.freedesktop.NetworkManager.Settings.Connection.GetSettings(), The GetSettings() method
-
org.freedesktop.NetworkManager.Settings.Connection.Save(), The Save() method +
org.freedesktop.NetworkManager.Settings.Connection.Save(), The Save() method
-
org.freedesktop.NetworkManager.Settings.Connection.Update(), The Update() method +
org.freedesktop.NetworkManager.Settings.Connection.Update(), The Update() method
-
org.freedesktop.NetworkManager.Settings.Connection.Update2(), The Update2() method +
org.freedesktop.NetworkManager.Settings.Connection.Update2(), The Update2() method
-
org.freedesktop.NetworkManager.Settings.Connection.UpdateUnsaved(), The UpdateUnsaved() method +
org.freedesktop.NetworkManager.Settings.Connection.UpdateUnsaved(), The UpdateUnsaved() method
-
org.freedesktop.NetworkManager.Settings.Connection::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Settings.Connection::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Settings.Connection::Removed, The "Removed" signal +
org.freedesktop.NetworkManager.Settings.Connection::Removed, The "Removed" signal
-
org.freedesktop.NetworkManager.Settings.Connection::Updated, The "Updated" signal +
org.freedesktop.NetworkManager.Settings.Connection::Updated, The "Updated" signal
-
org.freedesktop.NetworkManager.Settings.Connection:Filename, The "Filename" property +
org.freedesktop.NetworkManager.Settings.Connection:Filename, The "Filename" property
-
org.freedesktop.NetworkManager.Settings.Connection:Flags, The "Flags" property +
org.freedesktop.NetworkManager.Settings.Connection:Flags, The "Flags" property
-
org.freedesktop.NetworkManager.Settings.Connection:Unsaved, The "Unsaved" property +
org.freedesktop.NetworkManager.Settings.Connection:Unsaved, The "Unsaved" property
-
org.freedesktop.NetworkManager.Settings.GetConnectionByUuid(), The GetConnectionByUuid() method +
org.freedesktop.NetworkManager.Settings.GetConnectionByUuid(), The GetConnectionByUuid() method
-
org.freedesktop.NetworkManager.Settings.ListConnections(), The ListConnections() method +
org.freedesktop.NetworkManager.Settings.ListConnections(), The ListConnections() method
-
org.freedesktop.NetworkManager.Settings.LoadConnections(), The LoadConnections() method +
org.freedesktop.NetworkManager.Settings.LoadConnections(), The LoadConnections() method
-
org.freedesktop.NetworkManager.Settings.ReloadConnections(), The ReloadConnections() method +
org.freedesktop.NetworkManager.Settings.ReloadConnections(), The ReloadConnections() method
-
org.freedesktop.NetworkManager.Settings.SaveHostname(), The SaveHostname() method +
org.freedesktop.NetworkManager.Settings.SaveHostname(), The SaveHostname() method
-
org.freedesktop.NetworkManager.Settings::ConnectionRemoved, The "ConnectionRemoved" signal +
org.freedesktop.NetworkManager.Settings::ConnectionRemoved, The "ConnectionRemoved" signal
-
org.freedesktop.NetworkManager.Settings::NewConnection, The "NewConnection" signal +
org.freedesktop.NetworkManager.Settings::NewConnection, The "NewConnection" signal
-
org.freedesktop.NetworkManager.Settings::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Settings::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Settings:CanModify, The "CanModify" property +
org.freedesktop.NetworkManager.Settings:CanModify, The "CanModify" property
-
org.freedesktop.NetworkManager.Settings:Connections, The "Connections" property +
org.freedesktop.NetworkManager.Settings:Connections, The "Connections" property
-
org.freedesktop.NetworkManager.Settings:Hostname, The "Hostname" property +
org.freedesktop.NetworkManager.Settings:Hostname, The "Hostname" property

A

-
org.freedesktop.NetworkManager.Connection.Active, org.freedesktop.NetworkManager.Connection.Active +
org.freedesktop.NetworkManager.Connection.Active, org.freedesktop.NetworkManager.Connection.Active
-
org.freedesktop.NetworkManager.Connection.Active::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Connection.Active::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Connection.Active::StateChanged, The "StateChanged" signal +
org.freedesktop.NetworkManager.Connection.Active::StateChanged, The "StateChanged" signal
-
org.freedesktop.NetworkManager.Connection.Active:Connection, The "Connection" property +
org.freedesktop.NetworkManager.Connection.Active:Connection, The "Connection" property
-
org.freedesktop.NetworkManager.Connection.Active:Default, The "Default" property +
org.freedesktop.NetworkManager.Connection.Active:Default, The "Default" property
-
org.freedesktop.NetworkManager.Connection.Active:Default6, The "Default6" property +
org.freedesktop.NetworkManager.Connection.Active:Default6, The "Default6" property
-
org.freedesktop.NetworkManager.Connection.Active:Devices, The "Devices" property +
org.freedesktop.NetworkManager.Connection.Active:Devices, The "Devices" property
-
org.freedesktop.NetworkManager.Connection.Active:Dhcp4Config, The "Dhcp4Config" property +
org.freedesktop.NetworkManager.Connection.Active:Dhcp4Config, The "Dhcp4Config" property
-
org.freedesktop.NetworkManager.Connection.Active:Dhcp6Config, The "Dhcp6Config" property +
org.freedesktop.NetworkManager.Connection.Active:Dhcp6Config, The "Dhcp6Config" property
-
org.freedesktop.NetworkManager.Connection.Active:Id, The "Id" property +
org.freedesktop.NetworkManager.Connection.Active:Id, The "Id" property
-
org.freedesktop.NetworkManager.Connection.Active:Ip4Config, The "Ip4Config" property +
org.freedesktop.NetworkManager.Connection.Active:Ip4Config, The "Ip4Config" property
-
org.freedesktop.NetworkManager.Connection.Active:Ip6Config, The "Ip6Config" property +
org.freedesktop.NetworkManager.Connection.Active:Ip6Config, The "Ip6Config" property
-
org.freedesktop.NetworkManager.Connection.Active:Master, The "Master" property +
org.freedesktop.NetworkManager.Connection.Active:Master, The "Master" property
-
org.freedesktop.NetworkManager.Connection.Active:SpecificObject, The "SpecificObject" property +
org.freedesktop.NetworkManager.Connection.Active:SpecificObject, The "SpecificObject" property
-
org.freedesktop.NetworkManager.Connection.Active:State, The "State" property +
org.freedesktop.NetworkManager.Connection.Active:State, The "State" property
-
org.freedesktop.NetworkManager.Connection.Active:StateFlags, The "StateFlags" property +
org.freedesktop.NetworkManager.Connection.Active:StateFlags, The "StateFlags" property
-
org.freedesktop.NetworkManager.Connection.Active:Type, The "Type" property +
org.freedesktop.NetworkManager.Connection.Active:Type, The "Type" property
-
org.freedesktop.NetworkManager.Connection.Active:Uuid, The "Uuid" property +
org.freedesktop.NetworkManager.Connection.Active:Uuid, The "Uuid" property
-
org.freedesktop.NetworkManager.Connection.Active:Vpn, The "Vpn" property +
org.freedesktop.NetworkManager.Connection.Active:Vpn, The "Vpn" property
-
addr-gen-mode, ipv6 +
addr-gen-mode, ipv6
-
address-data, ipv4, ipv6 +
address-data, ipv4, ipv6
-
addresses, ipv4, ipv6 +
addresses, ipv4, ipv6
-
ageing, vxlan +
ageing, vxlan
-
ageing-time, bridge +
ageing-time, bridge
-
altsubject-matches, 802-1x +
altsubject-matches, 802-1x
-
anonymous-identity, 802-1x +
anonymous-identity, 802-1x
-
ap-isolation, 802-11-wireless +
ap-isolation, 802-11-wireless
-
apn, gsm +
apn, gsm
-
app-fcoe-flags, dcb +
app-fcoe-flags, dcb
-
app-fcoe-mode, dcb +
app-fcoe-mode, dcb
-
app-fcoe-priority, dcb +
app-fcoe-priority, dcb
-
app-fip-flags, dcb +
app-fip-flags, dcb
-
app-fip-priority, dcb +
app-fip-priority, dcb
-
app-iscsi-flags, dcb +
app-iscsi-flags, dcb
-
app-iscsi-priority, dcb +
app-iscsi-priority, dcb
-
assigned-mac-address, 802-3-ethernet, 802-11-wireless +
assigned-mac-address, 802-3-ethernet, 802-11-wireless
-
auth-alg, 802-11-wireless-security +
auth-alg, 802-11-wireless-security
-
auth-retries, connection +
auth-retries, connection
-
auth-timeout, 802-1x +
auth-timeout, 802-1x
-
auto-config, gsm +
auto-config, gsm
-
auto-negotiate, 802-3-ethernet +
auto-negotiate, 802-3-ethernet
-
autoconnect, connection +
autoconnect, connection
-
autoconnect-priority, connection +
autoconnect-priority, connection
-
autoconnect-retries, connection +
autoconnect-retries, connection
-
autoconnect-slaves, connection +
autoconnect-slaves, connection
-
autoprobe-drivers, sriov +
autoprobe-drivers, sriov

B

-
band, 802-11-wireless +
band, 802-11-wireless
-
baud, ppp, serial +
baud, ppp, serial
-
bdaddr, bluetooth +
bdaddr, bluetooth
-
bits, serial +
bits, serial
-
bond-downdelay, ovs-port +
bond-downdelay, ovs-port
-
bond-mode, ovs-port +
bond-mode, ovs-port
-
bond-updelay, ovs-port +
bond-updelay, ovs-port
-
browser-only, proxy +
browser-only, proxy
-
bssid, 802-11-wireless +
bssid, 802-11-wireless

C

-
ca-cert, 802-1x +
ca-cert, 802-1x
-
ca-cert-password, 802-1x +
ca-cert-password, 802-1x
-
ca-cert-password-flags, 802-1x +
ca-cert-password-flags, 802-1x
-
ca-path, 802-1x +
ca-path, 802-1x
-
channel, 802-11-olpc-mesh, 802-11-wireless, wpan +
channel, 802-11-olpc-mesh, 802-11-wireless, wpan
-
org.freedesktop.NetworkManager.Checkpoint, org.freedesktop.NetworkManager.Checkpoint +
org.freedesktop.NetworkManager.Checkpoint, org.freedesktop.NetworkManager.Checkpoint
-
org.freedesktop.NetworkManager.Checkpoint::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Checkpoint::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Checkpoint:Created, The "Created" property +
org.freedesktop.NetworkManager.Checkpoint:Created, The "Created" property
-
org.freedesktop.NetworkManager.Checkpoint:Devices, The "Devices" property +
org.freedesktop.NetworkManager.Checkpoint:Devices, The "Devices" property
-
org.freedesktop.NetworkManager.Checkpoint:RollbackTimeout, The "RollbackTimeout" property +
org.freedesktop.NetworkManager.Checkpoint:RollbackTimeout, The "RollbackTimeout" property
-
client-cert, 802-1x +
client-cert, 802-1x
-
client-cert-password, 802-1x +
client-cert-password, 802-1x
-
client-cert-password-flags, 802-1x +
client-cert-password-flags, 802-1x
-
cloned-mac-address, 802-3-ethernet, 802-11-wireless +
cloned-mac-address, 802-3-ethernet, 802-11-wireless
-
config, team, team-port +
config, team, team-port
-
crtscts, ppp +
crtscts, ppp

D

-
dad-timeout, ipv4, ipv6 +
dad-timeout, ipv4, ipv6
-
data, user, vpn, ovs-external-ids +
data, user, vpn, ovs-external-ids
-
datapath-type, ovs-bridge +
datapath-type, ovs-bridge
-
destination-port, vxlan +
destination-port, vxlan
-
devargs, ovs-dpdk +
devargs, ovs-dpdk
-
device-id, gsm +
device-id, gsm
-
org.freedesktop.NetworkManager.Device.Wired, org.freedesktop.NetworkManager.Device.Wired +
org.freedesktop.NetworkManager.Device.Wired, org.freedesktop.NetworkManager.Device.Wired
-
org.freedesktop.NetworkManager.Device.Wired::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Wired::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Wired:Carrier, The "Carrier" property +
org.freedesktop.NetworkManager.Device.Wired:Carrier, The "Carrier" property
-
org.freedesktop.NetworkManager.Device.Wired:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Wired:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Wired:PermHwAddress, The "PermHwAddress" property +
org.freedesktop.NetworkManager.Device.Wired:PermHwAddress, The "PermHwAddress" property
-
org.freedesktop.NetworkManager.Device.Wired:S390Subchannels, The "S390Subchannels" property +
org.freedesktop.NetworkManager.Device.Wired:S390Subchannels, The "S390Subchannels" property
-
org.freedesktop.NetworkManager.Device.Wired:Speed, The "Speed" property +
org.freedesktop.NetworkManager.Device.Wired:Speed, The "Speed" property
-
org.freedesktop.NetworkManager.Device.Wireless, org.freedesktop.NetworkManager.Device.Wireless +
org.freedesktop.NetworkManager.Device.Wireless, org.freedesktop.NetworkManager.Device.Wireless
-
org.freedesktop.NetworkManager.Device.Wireless.GetAccessPoints(), The GetAccessPoints() method +
org.freedesktop.NetworkManager.Device.Wireless.GetAccessPoints(), The GetAccessPoints() method
-
org.freedesktop.NetworkManager.Device.Wireless.GetAllAccessPoints(), The GetAllAccessPoints() method +
org.freedesktop.NetworkManager.Device.Wireless.GetAllAccessPoints(), The GetAllAccessPoints() method
-
org.freedesktop.NetworkManager.Device.Wireless.RequestScan(), The RequestScan() method +
org.freedesktop.NetworkManager.Device.Wireless.RequestScan(), The RequestScan() method
-
org.freedesktop.NetworkManager.Device.Wireless::AccessPointAdded, The "AccessPointAdded" signal +
org.freedesktop.NetworkManager.Device.Wireless::AccessPointAdded, The "AccessPointAdded" signal
-
org.freedesktop.NetworkManager.Device.Wireless::AccessPointRemoved, The "AccessPointRemoved" signal +
org.freedesktop.NetworkManager.Device.Wireless::AccessPointRemoved, The "AccessPointRemoved" signal
-
org.freedesktop.NetworkManager.Device.Wireless::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.Device.Wireless::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.Device.Wireless:AccessPoints, The "AccessPoints" property +
org.freedesktop.NetworkManager.Device.Wireless:AccessPoints, The "AccessPoints" property
-
org.freedesktop.NetworkManager.Device.Wireless:ActiveAccessPoint, The "ActiveAccessPoint" property +
org.freedesktop.NetworkManager.Device.Wireless:ActiveAccessPoint, The "ActiveAccessPoint" property
-
org.freedesktop.NetworkManager.Device.Wireless:Bitrate, The "Bitrate" property +
org.freedesktop.NetworkManager.Device.Wireless:Bitrate, The "Bitrate" property
-
org.freedesktop.NetworkManager.Device.Wireless:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.Wireless:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.Wireless:LastScan, The "LastScan" property +
org.freedesktop.NetworkManager.Device.Wireless:LastScan, The "LastScan" property
-
org.freedesktop.NetworkManager.Device.Wireless:Mode, The "Mode" property +
org.freedesktop.NetworkManager.Device.Wireless:Mode, The "Mode" property
-
org.freedesktop.NetworkManager.Device.Wireless:PermHwAddress, The "PermHwAddress" property +
org.freedesktop.NetworkManager.Device.Wireless:PermHwAddress, The "PermHwAddress" property
-
org.freedesktop.NetworkManager.Device.Wireless:WirelessCapabilities, The "WirelessCapabilities" property +
org.freedesktop.NetworkManager.Device.Wireless:WirelessCapabilities, The "WirelessCapabilities" property
-
org.freedesktop.NetworkManager.Device.WifiP2P, org.freedesktop.NetworkManager.Device.WifiP2P +
org.freedesktop.NetworkManager.Device.WifiP2P, org.freedesktop.NetworkManager.Device.WifiP2P
-
org.freedesktop.NetworkManager.Device.WifiP2P.StartFind(), The StartFind() method +
org.freedesktop.NetworkManager.Device.WifiP2P.StartFind(), The StartFind() method
-
org.freedesktop.NetworkManager.Device.WifiP2P.StopFind(), The StopFind() method +
org.freedesktop.NetworkManager.Device.WifiP2P.StopFind(), The StopFind() method
-
org.freedesktop.NetworkManager.Device.WifiP2P::PeerAdded, The "PeerAdded" signal +
org.freedesktop.NetworkManager.Device.WifiP2P::PeerAdded, The "PeerAdded" signal
-
org.freedesktop.NetworkManager.Device.WifiP2P::PeerRemoved, The "PeerRemoved" signal +
org.freedesktop.NetworkManager.Device.WifiP2P::PeerRemoved, The "PeerRemoved" signal
-
org.freedesktop.NetworkManager.Device.WifiP2P:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.Device.WifiP2P:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.Device.WifiP2P:Peers, The "Peers" property +
org.freedesktop.NetworkManager.Device.WifiP2P:Peers, The "Peers" property
-
dhcp-anycast-address, 802-11-olpc-mesh +
dhcp-anycast-address, 802-11-olpc-mesh
-
dhcp-client-id, ipv4 +
dhcp-client-id, ipv4
-
dhcp-duid, ipv6 +
dhcp-duid, ipv6
-
dhcp-fqdn, ipv4 +
dhcp-fqdn, ipv4
-
dhcp-hostname, ipv4, ipv6 +
dhcp-hostname, ipv4, ipv6
-
dhcp-hostname-flags, ipv4, ipv6 +
dhcp-hostname-flags, ipv4, ipv6
-
dhcp-iaid, ipv4, ipv6 +
dhcp-iaid, ipv4, ipv6
-
dhcp-reject-servers, ipv4, ipv6 +
dhcp-reject-servers, ipv4, ipv6
-
dhcp-send-hostname, ipv4, ipv6 +
dhcp-send-hostname, ipv4, ipv6
-
dhcp-timeout, ipv4, ipv6 +
dhcp-timeout, ipv4, ipv6
-
dhcp-vendor-class-identifier, ipv4 +
dhcp-vendor-class-identifier, ipv4
-
org.freedesktop.NetworkManager.DHCP4Config, org.freedesktop.NetworkManager.DHCP4Config +
org.freedesktop.NetworkManager.DHCP4Config, org.freedesktop.NetworkManager.DHCP4Config
-
org.freedesktop.NetworkManager.DHCP4Config::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.DHCP4Config::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.DHCP4Config:Options, The "Options" property +
org.freedesktop.NetworkManager.DHCP4Config:Options, The "Options" property
-
org.freedesktop.NetworkManager.DHCP6Config, org.freedesktop.NetworkManager.DHCP6Config +
org.freedesktop.NetworkManager.DHCP6Config, org.freedesktop.NetworkManager.DHCP6Config
-
org.freedesktop.NetworkManager.DHCP6Config::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.DHCP6Config::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.DHCP6Config:Options, The "Options" property +
org.freedesktop.NetworkManager.DHCP6Config:Options, The "Options" property
-
dns, ipv4, ipv6 +
dns, ipv4, ipv6
-
dns-options, ipv4, ipv6 +
dns-options, ipv4, ipv6
-
dns-priority, ipv4, ipv6 +
dns-priority, ipv4, ipv6
-
dns-search, ipv4, ipv6 +
dns-search, ipv4, ipv6
-
domain-match, 802-1x +
domain-match, 802-1x
-
domain-suffix-match, 802-1x +
domain-suffix-match, 802-1x
-
driver, match +
driver, match
-
duplex, 802-3-ethernet +
duplex, 802-3-ethernet

E

-
eap, 802-1x +
eap, 802-1x
-
egress-priority-map, vlan +
egress-priority-map, vlan
-
encapsulation, adsl +
encapsulation, adsl
-
encapsulation-limit, ip-tunnel +
encapsulation-limit, ip-tunnel
-
encrypt, macsec +
encrypt, macsec

F

-
fail-mode, ovs-bridge +
fail-mode, ovs-bridge
-
fils, 802-11-wireless-security +
fils, 802-11-wireless-security
-
flags, ip-tunnel, vlan +
flags, ip-tunnel, vlan
-
flow-label, ip-tunnel +
flow-label, ip-tunnel
-
forward-delay, bridge +
forward-delay, bridge
-
from-dhcp, hostname +
from-dhcp, hostname
-
from-dns-lookup, hostname +
from-dns-lookup, hostname
-
fwmark, wireguard +
fwmark, wireguard

G

-
gateway, ipv4, ipv6 +
gateway, ipv4, ipv6
-
gateway-ping-timeout, connection +
gateway-ping-timeout, connection
-
generate-mac-address-mask, 802-3-ethernet, 802-11-wireless +
generate-mac-address-mask, 802-3-ethernet, 802-11-wireless
-
group, tun, 802-11-wireless-security +
group, tun, 802-11-wireless-security
-
group-address, bridge +
group-address, bridge
-
group-forward-mask, bridge +
group-forward-mask, bridge

H

-
hairpin-mode, bridge-port +
hairpin-mode, bridge-port
-
hello-time, bridge +
hello-time, bridge
-
hidden, 802-11-wireless +
hidden, 802-11-wireless
-
home-only, gsm +
home-only, gsm

I

-
id, connection, vlan, vxlan +
id, connection, vlan, vxlan
-
identity, 802-1x +
identity, 802-1x
-
ignore-auto-dns, ipv4, ipv6 +
ignore-auto-dns, ipv4, ipv6
-
ignore-auto-routes, ipv4, ipv6 +
ignore-auto-routes, ipv4, ipv6
-
ingress-priority-map, vlan +
ingress-priority-map, vlan
-
input-key, ip-tunnel +
input-key, ip-tunnel
-
interface-name, connection, bond, bridge, match, team, vlan +
interface-name, connection, bond, bridge, match, team, vlan
-
ip4-auto-default-route, wireguard +
ip4-auto-default-route, wireguard
-
ip6-auto-default-route, wireguard +
ip6-auto-default-route, wireguard
-
ip6-privacy, ipv6 +
ip6-privacy, ipv6

K

-
kernel-command-line, match +
kernel-command-line, match
-
key-mgmt, 802-11-wireless-security +
key-mgmt, 802-11-wireless-security

L

-
l2-miss, vxlan +
l2-miss, vxlan
-
l3-miss, vxlan +
l3-miss, vxlan
-
lacp, ovs-port +
lacp, ovs-port
-
lacp-key, team-port +
lacp-key, team-port
-
lacp-prio, team-port +
lacp-prio, team-port
-
lcp-echo-failure, ppp +
lcp-echo-failure, ppp
-
lcp-echo-interval, ppp +
lcp-echo-interval, ppp
-
leap-password, 802-11-wireless-security +
leap-password, 802-11-wireless-security
-
leap-password-flags, 802-11-wireless-security +
leap-password-flags, 802-11-wireless-security
-
leap-username, 802-11-wireless-security +
leap-username, 802-11-wireless-security
-
learning, vxlan +
learning, vxlan
-
limit, vxlan +
limit, vxlan
-
link-watchers, team, team-port +
link-watchers, team, team-port
-
listen-port, wireguard +
listen-port, wireguard
-
lldp, connection +
lldp, connection
-
llmnr, connection +
llmnr, connection
-
local, ip-tunnel, vxlan +
local, ip-tunnel, vxlan

M

-
mac-address, bridge, infiniband, wimax, 802-3-ethernet, 802-11-wireless, wpan +
mac-address, bridge, infiniband, wimax, 802-3-ethernet, 802-11-wireless, wpan
-
mac-address-blacklist, 802-3-ethernet, 802-11-wireless +
mac-address-blacklist, 802-3-ethernet, 802-11-wireless
-
mac-address-randomization, 802-11-wireless +
mac-address-randomization, 802-11-wireless
-
org.freedesktop.NetworkManager, org.freedesktop.NetworkManager +
org.freedesktop.NetworkManager, org.freedesktop.NetworkManager
-
org.freedesktop.NetworkManager.ActivateConnection(), The ActivateConnection() method +
org.freedesktop.NetworkManager.ActivateConnection(), The ActivateConnection() method
-
org.freedesktop.NetworkManager.AddAndActivateConnection(), The AddAndActivateConnection() method +
org.freedesktop.NetworkManager.AddAndActivateConnection(), The AddAndActivateConnection() method
-
org.freedesktop.NetworkManager.AddAndActivateConnection2(), The AddAndActivateConnection2() method +
org.freedesktop.NetworkManager.AddAndActivateConnection2(), The AddAndActivateConnection2() method
-
org.freedesktop.NetworkManager.CheckConnectivity(), The CheckConnectivity() method +
org.freedesktop.NetworkManager.CheckConnectivity(), The CheckConnectivity() method
-
org.freedesktop.NetworkManager.CheckpointAdjustRollbackTimeout(), The CheckpointAdjustRollbackTimeout() method +
org.freedesktop.NetworkManager.CheckpointAdjustRollbackTimeout(), The CheckpointAdjustRollbackTimeout() method
-
org.freedesktop.NetworkManager.CheckpointCreate(), The CheckpointCreate() method +
org.freedesktop.NetworkManager.CheckpointCreate(), The CheckpointCreate() method
-
org.freedesktop.NetworkManager.CheckpointDestroy(), The CheckpointDestroy() method +
org.freedesktop.NetworkManager.CheckpointDestroy(), The CheckpointDestroy() method
-
org.freedesktop.NetworkManager.CheckpointRollback(), The CheckpointRollback() method +
org.freedesktop.NetworkManager.CheckpointRollback(), The CheckpointRollback() method
-
org.freedesktop.NetworkManager.DeactivateConnection(), The DeactivateConnection() method +
org.freedesktop.NetworkManager.DeactivateConnection(), The DeactivateConnection() method
-
org.freedesktop.NetworkManager.Enable(), The Enable() method +
org.freedesktop.NetworkManager.Enable(), The Enable() method
-
org.freedesktop.NetworkManager.GetAllDevices(), The GetAllDevices() method +
org.freedesktop.NetworkManager.GetAllDevices(), The GetAllDevices() method
-
org.freedesktop.NetworkManager.GetDeviceByIpIface(), The GetDeviceByIpIface() method +
org.freedesktop.NetworkManager.GetDeviceByIpIface(), The GetDeviceByIpIface() method
-
org.freedesktop.NetworkManager.GetDevices(), The GetDevices() method +
org.freedesktop.NetworkManager.GetDevices(), The GetDevices() method
-
org.freedesktop.NetworkManager.GetLogging(), The GetLogging() method +
org.freedesktop.NetworkManager.GetLogging(), The GetLogging() method
-
org.freedesktop.NetworkManager.GetPermissions(), The GetPermissions() method +
org.freedesktop.NetworkManager.GetPermissions(), The GetPermissions() method
-
org.freedesktop.NetworkManager.Reload(), The Reload() method +
org.freedesktop.NetworkManager.Reload(), The Reload() method
-
org.freedesktop.NetworkManager.SetLogging(), The SetLogging() method +
org.freedesktop.NetworkManager.SetLogging(), The SetLogging() method
-
org.freedesktop.NetworkManager.Sleep(), The Sleep() method +
org.freedesktop.NetworkManager.Sleep(), The Sleep() method
-
org.freedesktop.NetworkManager.state(), The state() method +
org.freedesktop.NetworkManager.state(), The state() method
-
org.freedesktop.NetworkManager::CheckPermissions, The "CheckPermissions" signal +
org.freedesktop.NetworkManager::CheckPermissions, The "CheckPermissions" signal
-
org.freedesktop.NetworkManager::DeviceAdded, The "DeviceAdded" signal +
org.freedesktop.NetworkManager::DeviceAdded, The "DeviceAdded" signal
-
org.freedesktop.NetworkManager::DeviceRemoved, The "DeviceRemoved" signal +
org.freedesktop.NetworkManager::DeviceRemoved, The "DeviceRemoved" signal
-
org.freedesktop.NetworkManager::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager::StateChanged, The "StateChanged" signal +
org.freedesktop.NetworkManager::StateChanged, The "StateChanged" signal
-
org.freedesktop.NetworkManager:ActivatingConnection, The "ActivatingConnection" property +
org.freedesktop.NetworkManager:ActivatingConnection, The "ActivatingConnection" property
-
org.freedesktop.NetworkManager:ActiveConnections, The "ActiveConnections" property +
org.freedesktop.NetworkManager:ActiveConnections, The "ActiveConnections" property
-
org.freedesktop.NetworkManager:AllDevices, The "AllDevices" property +
org.freedesktop.NetworkManager:AllDevices, The "AllDevices" property
-
org.freedesktop.NetworkManager:Capabilities, The "Capabilities" property +
org.freedesktop.NetworkManager:Capabilities, The "Capabilities" property
-
org.freedesktop.NetworkManager:Checkpoints, The "Checkpoints" property +
org.freedesktop.NetworkManager:Checkpoints, The "Checkpoints" property
-
org.freedesktop.NetworkManager:Connectivity, The "Connectivity" property +
org.freedesktop.NetworkManager:Connectivity, The "Connectivity" property
-
org.freedesktop.NetworkManager:ConnectivityCheckAvailable, The "ConnectivityCheckAvailable" property +
org.freedesktop.NetworkManager:ConnectivityCheckAvailable, The "ConnectivityCheckAvailable" property
-
org.freedesktop.NetworkManager:ConnectivityCheckEnabled, The "ConnectivityCheckEnabled" property +
org.freedesktop.NetworkManager:ConnectivityCheckEnabled, The "ConnectivityCheckEnabled" property
-
org.freedesktop.NetworkManager:ConnectivityCheckUri, The "ConnectivityCheckUri" property +
org.freedesktop.NetworkManager:ConnectivityCheckUri, The "ConnectivityCheckUri" property
-
org.freedesktop.NetworkManager:Devices, The "Devices" property +
org.freedesktop.NetworkManager:Devices, The "Devices" property
-
org.freedesktop.NetworkManager:GlobalDnsConfiguration, The "GlobalDnsConfiguration" property +
org.freedesktop.NetworkManager:GlobalDnsConfiguration, The "GlobalDnsConfiguration" property
-
org.freedesktop.NetworkManager:Metered, The "Metered" property +
org.freedesktop.NetworkManager:Metered, The "Metered" property
-
org.freedesktop.NetworkManager:NetworkingEnabled, The "NetworkingEnabled" property +
org.freedesktop.NetworkManager:NetworkingEnabled, The "NetworkingEnabled" property
-
org.freedesktop.NetworkManager:PrimaryConnection, The "PrimaryConnection" property +
org.freedesktop.NetworkManager:PrimaryConnection, The "PrimaryConnection" property
-
org.freedesktop.NetworkManager:PrimaryConnectionType, The "PrimaryConnectionType" property +
org.freedesktop.NetworkManager:PrimaryConnectionType, The "PrimaryConnectionType" property
-
org.freedesktop.NetworkManager:Startup, The "Startup" property +
org.freedesktop.NetworkManager:Startup, The "Startup" property
-
org.freedesktop.NetworkManager:State, The "State" property +
org.freedesktop.NetworkManager:State, The "State" property
-
org.freedesktop.NetworkManager:Version, The "Version" property +
org.freedesktop.NetworkManager:Version, The "Version" property
-
org.freedesktop.NetworkManager:WimaxEnabled, The "WimaxEnabled" property +
org.freedesktop.NetworkManager:WimaxEnabled, The "WimaxEnabled" property
-
org.freedesktop.NetworkManager:WimaxHardwareEnabled, The "WimaxHardwareEnabled" property +
org.freedesktop.NetworkManager:WimaxHardwareEnabled, The "WimaxHardwareEnabled" property
-
org.freedesktop.NetworkManager:WirelessEnabled, The "WirelessEnabled" property +
org.freedesktop.NetworkManager:WirelessEnabled, The "WirelessEnabled" property
-
org.freedesktop.NetworkManager:WirelessHardwareEnabled, The "WirelessHardwareEnabled" property +
org.freedesktop.NetworkManager:WirelessHardwareEnabled, The "WirelessHardwareEnabled" property
-
org.freedesktop.NetworkManager:WwanEnabled, The "WwanEnabled" property +
org.freedesktop.NetworkManager:WwanEnabled, The "WwanEnabled" property
-
org.freedesktop.NetworkManager:WwanHardwareEnabled, The "WwanHardwareEnabled" property +
org.freedesktop.NetworkManager:WwanHardwareEnabled, The "WwanHardwareEnabled" property
-
master, connection +
master, connection
-
max-age, bridge +
max-age, bridge
-
may-fail, ipv4, ipv6 +
may-fail, ipv4, ipv6
-
mcast-rejoin-count, team +
mcast-rejoin-count, team
-
mcast-rejoin-interval, team +
mcast-rejoin-interval, team
-
mcast-snooping-enable, ovs-bridge +
mcast-snooping-enable, ovs-bridge
-
mdns, connection +
mdns, connection
-
metered, connection +
metered, connection
-
method, ipv4, ipv6, proxy +
method, ipv4, ipv6, proxy
-
mka-cak, macsec +
mka-cak, macsec
-
mka-cak-flags, macsec +
mka-cak-flags, macsec
-
mka-ckn, macsec +
mka-ckn, macsec
-
mode, ip-tunnel, macsec, macvlan, tun, 802-11-wireless +
mode, ip-tunnel, macsec, macvlan, tun, 802-11-wireless
-
mppe-stateful, ppp +
mppe-stateful, ppp
-
mru, ppp +
mru, ppp
-
mtu, cdma, gsm, infiniband, ip-tunnel, ppp, 802-3-ethernet, wireguard, 802-11-wireless +
mtu, cdma, gsm, infiniband, ip-tunnel, ppp, 802-3-ethernet, wireguard, 802-11-wireless
-
mud-url, connection +
mud-url, connection
-
multi-connect, connection +
multi-connect, connection
-
multi-queue, tun +
multi-queue, tun
-
multicast-hash-max, bridge +
multicast-hash-max, bridge
-
multicast-last-member-count, bridge +
multicast-last-member-count, bridge
-
multicast-last-member-interval, bridge +
multicast-last-member-interval, bridge
-
multicast-membership-interval, bridge +
multicast-membership-interval, bridge
-
multicast-querier, bridge +
multicast-querier, bridge
-
multicast-querier-interval, bridge +
multicast-querier-interval, bridge
-
multicast-query-interval, bridge +
multicast-query-interval, bridge
-
multicast-query-response-interval, bridge +
multicast-query-response-interval, bridge
-
multicast-query-use-ifaddr, bridge +
multicast-query-use-ifaddr, bridge
-
multicast-router, bridge +
multicast-router, bridge
-
multicast-snooping, bridge +
multicast-snooping, bridge
-
multicast-startup-query-count, bridge +
multicast-startup-query-count, bridge
-
multicast-startup-query-interval, bridge +
multicast-startup-query-interval, bridge

N

-
network-id, gsm +
network-id, gsm
-
network-name, wimax +
network-name, wimax
-
never-default, ipv4, ipv6 +
never-default, ipv4, ipv6
-
NM80211ApFlags, enum NM80211ApFlags +
NM80211ApFlags, enum NM80211ApFlags
-
NM80211ApSecurityFlags, enum NM80211ApSecurityFlags +
NM80211ApSecurityFlags, enum NM80211ApSecurityFlags
-
NM80211Mode, enum NM80211Mode +
NM80211Mode, enum NM80211Mode
-
NMActivationStateFlags, enum NMActivationStateFlags +
NMActivationStateFlags, enum NMActivationStateFlags
-
NMActiveConnectionState, enum NMActiveConnectionState +
NMActiveConnectionState, enum NMActiveConnectionState
-
NMActiveConnectionStateReason, enum NMActiveConnectionStateReason +
NMActiveConnectionStateReason, enum NMActiveConnectionStateReason
-
NMBluetoothCapabilities, enum NMBluetoothCapabilities +
NMBluetoothCapabilities, enum NMBluetoothCapabilities
-
NMCapability, enum NMCapability +
NMCapability, enum NMCapability
-
NMCheckpointCreateFlags, enum NMCheckpointCreateFlags +
NMCheckpointCreateFlags, enum NMCheckpointCreateFlags
-
NMClientPermission, enum NMClientPermission +
NMClientPermission, enum NMClientPermission
-
NMClientPermissionResult, enum NMClientPermissionResult +
NMClientPermissionResult, enum NMClientPermissionResult
-
NMConnectionMultiConnect, enum NMConnectionMultiConnect +
NMConnectionMultiConnect, enum NMConnectionMultiConnect
-
NMConnectivityState, enum NMConnectivityState +
NMConnectivityState, enum NMConnectivityState
-
NMDeviceCapabilities, enum NMDeviceCapabilities +
NMDeviceCapabilities, enum NMDeviceCapabilities
-
NMDeviceInterfaceFlags, enum NMDeviceInterfaceFlags +
NMDeviceInterfaceFlags, enum NMDeviceInterfaceFlags
-
NMDeviceModemCapabilities, enum NMDeviceModemCapabilities +
NMDeviceModemCapabilities, enum NMDeviceModemCapabilities
-
NMDeviceState, enum NMDeviceState +
NMDeviceState, enum NMDeviceState
-
NMDeviceStateReason, enum NMDeviceStateReason +
NMDeviceStateReason, enum NMDeviceStateReason
-
NMDeviceType, enum NMDeviceType +
NMDeviceType, enum NMDeviceType
-
NMDeviceWifiCapabilities, enum NMDeviceWifiCapabilities +
NMDeviceWifiCapabilities, enum NMDeviceWifiCapabilities
-
NMIPTunnelMode, enum NMIPTunnelMode +
NMIPTunnelMode, enum NMIPTunnelMode
-
NMManagerReloadFlags, enum NMManagerReloadFlags +
NMManagerReloadFlags, enum NMManagerReloadFlags
-
NMMetered, enum NMMetered +
NMMetered, enum NMMetered
-
NMRollbackResult, enum NMRollbackResult +
NMRollbackResult, enum NMRollbackResult
-
NMSecretAgentCapabilities, enum NMSecretAgentCapabilities +
NMSecretAgentCapabilities, enum NMSecretAgentCapabilities
-
NMSecretAgentGetSecretsFlags, enum NMSecretAgentGetSecretsFlags +
NMSecretAgentGetSecretsFlags, enum NMSecretAgentGetSecretsFlags
-
NMSettingsAddConnection2Flags, enum NMSettingsAddConnection2Flags +
NMSettingsAddConnection2Flags, enum NMSettingsAddConnection2Flags
-
NMSettingsConnectionFlags, enum NMSettingsConnectionFlags +
NMSettingsConnectionFlags, enum NMSettingsConnectionFlags
-
NMSettingsUpdate2Flags, enum NMSettingsUpdate2Flags +
NMSettingsUpdate2Flags, enum NMSettingsUpdate2Flags
-
NMState, enum NMState +
NMState, enum NMState
-
NMTernary, enum NMTernary +
NMTernary, enum NMTernary
-
NMVpnConnectionState, enum NMVpnConnectionState +
NMVpnConnectionState, enum NMVpnConnectionState
-
NMVpnConnectionStateReason, enum NMVpnConnectionStateReason +
NMVpnConnectionStateReason, enum NMVpnConnectionStateReason
-
NMVpnPluginFailure, enum NMVpnPluginFailure +
NMVpnPluginFailure, enum NMVpnPluginFailure
-
NMVpnServiceState, enum NMVpnServiceState +
NMVpnServiceState, enum NMVpnServiceState
-
NMWimaxNspNetworkType, enum NMWimaxNspNetworkType +
NMWimaxNspNetworkType, enum NMWimaxNspNetworkType
-
no-vj-comp, ppp +
no-vj-comp, ppp
-
noauth, ppp +
noauth, ppp
-
nobsdcomp, ppp +
nobsdcomp, ppp
-
nodeflate, ppp +
nodeflate, ppp
-
notify-peers-count, team +
notify-peers-count, team
-
notify-peers-interval, team +
notify-peers-interval, team
-
number, cdma, gsm +
number, cdma, gsm

O

-
only-from-default, hostname +
only-from-default, hostname
-
optional, 802-1x +
optional, 802-1x
-
options, bond +
options, bond
-
output-key, ip-tunnel +
output-key, ip-tunnel
-
owner, tun +
owner, tun

P

-
p-key, infiniband +
p-key, infiniband
-
pac-file, 802-1x +
pac-file, 802-1x
-
pac-script, proxy +
pac-script, proxy
-
pac-url, proxy +
pac-url, proxy
-
page, wpan +
page, wpan
-
pairwise, 802-11-wireless-security +
pairwise, 802-11-wireless-security
-
pan-id, wpan +
pan-id, wpan
-
parent, 6lowpan, infiniband, ip-tunnel, macsec, macvlan, pppoe, vlan, vxlan +
parent, 6lowpan, infiniband, ip-tunnel, macsec, macvlan, pppoe, vlan, vxlan
-
parity, serial +
parity, serial
-
password, 802-1x, adsl, cdma, gsm, pppoe +
password, 802-1x, adsl, cdma, gsm, pppoe
-
password-flags, 802-1x, adsl, cdma, gsm, pppoe +
password-flags, 802-1x, adsl, cdma, gsm, pppoe
-
password-raw, 802-1x +
password-raw, 802-1x
-
password-raw-flags, 802-1x +
password-raw-flags, 802-1x
-
path, match +
path, match
-
path-cost, bridge-port +
path-cost, bridge-port
-
path-mtu-discovery, ip-tunnel +
path-mtu-discovery, ip-tunnel
-
peer, ovs-patch, wifi-p2p, veth +
peer, ovs-patch, wifi-p2p, veth
-
peer-routes, wireguard +
peer-routes, wireguard
-
peers, wireguard +
peers, wireguard
-
permissions, connection +
permissions, connection
-
persistent, vpn +
persistent, vpn
-
phase1-auth-flags, 802-1x +
phase1-auth-flags, 802-1x
-
phase1-fast-provisioning, 802-1x +
phase1-fast-provisioning, 802-1x
-
phase1-peaplabel, 802-1x +
phase1-peaplabel, 802-1x
-
phase1-peapver, 802-1x +
phase1-peapver, 802-1x
-
phase2-altsubject-matches, 802-1x +
phase2-altsubject-matches, 802-1x
-
phase2-auth, 802-1x +
phase2-auth, 802-1x
-
phase2-autheap, 802-1x +
phase2-autheap, 802-1x
-
phase2-ca-cert, 802-1x +
phase2-ca-cert, 802-1x
-
phase2-ca-cert-password, 802-1x +
phase2-ca-cert-password, 802-1x
-
phase2-ca-cert-password-flags, 802-1x +
phase2-ca-cert-password-flags, 802-1x
-
phase2-ca-path, 802-1x +
phase2-ca-path, 802-1x
-
phase2-client-cert, 802-1x +
phase2-client-cert, 802-1x
-
phase2-client-cert-password, 802-1x +
phase2-client-cert-password, 802-1x
-
phase2-client-cert-password-flags, 802-1x +
phase2-client-cert-password-flags, 802-1x
-
phase2-domain-match, 802-1x +
phase2-domain-match, 802-1x
-
phase2-domain-suffix-match, 802-1x +
phase2-domain-suffix-match, 802-1x
-
phase2-private-key, 802-1x +
phase2-private-key, 802-1x
-
phase2-private-key-password, 802-1x +
phase2-private-key-password, 802-1x
-
phase2-private-key-password-flags, 802-1x +
phase2-private-key-password-flags, 802-1x
-
phase2-subject-match, 802-1x +
phase2-subject-match, 802-1x
-
pi, tun +
pi, tun
-
pin, 802-1x, gsm +
pin, 802-1x, gsm
-
pin-flags, 802-1x, gsm +
pin-flags, 802-1x, gsm
-
pmf, 802-11-wireless-security +
pmf, 802-11-wireless-security
-
port, macsec, 802-3-ethernet +
port, macsec, 802-3-ethernet
-
powersave, 802-11-wireless +
powersave, 802-11-wireless
-
org.freedesktop.NetworkManager.PPP, org.freedesktop.NetworkManager.PPP +
org.freedesktop.NetworkManager.PPP, org.freedesktop.NetworkManager.PPP
-
org.freedesktop.NetworkManager.PPP.NeedSecrets(), The NeedSecrets() method +
org.freedesktop.NetworkManager.PPP.NeedSecrets(), The NeedSecrets() method
-
org.freedesktop.NetworkManager.PPP.SetIfindex(), The SetIfindex() method +
org.freedesktop.NetworkManager.PPP.SetIfindex(), The SetIfindex() method
-
org.freedesktop.NetworkManager.PPP.SetIp4Config(), The SetIp4Config() method +
org.freedesktop.NetworkManager.PPP.SetIp4Config(), The SetIp4Config() method
-
org.freedesktop.NetworkManager.PPP.SetIp6Config(), The SetIp6Config() method +
org.freedesktop.NetworkManager.PPP.SetIp6Config(), The SetIp6Config() method
-
org.freedesktop.NetworkManager.PPP.SetState(), The SetState() method +
org.freedesktop.NetworkManager.PPP.SetState(), The SetState() method
-
prio, team-port +
prio, team-port
-
priority, bridge, bridge-port, hostname +
priority, bridge, bridge-port, hostname
-
priority-bandwidth, dcb +
priority-bandwidth, dcb
-
priority-flow-control, dcb +
priority-flow-control, dcb
-
priority-flow-control-flags, dcb +
priority-flow-control-flags, dcb
-
priority-group-bandwidth, dcb +
priority-group-bandwidth, dcb
-
priority-group-flags, dcb +
priority-group-flags, dcb
-
priority-group-id, dcb +
priority-group-id, dcb
-
priority-strict-bandwidth, dcb +
priority-strict-bandwidth, dcb
-
priority-traffic-class, dcb +
priority-traffic-class, dcb
-
private-key, 802-1x, wireguard +
private-key, 802-1x, wireguard
-
private-key-flags, wireguard +
private-key-flags, wireguard
-
private-key-password, 802-1x +
private-key-password, 802-1x
-
private-key-password-flags, 802-1x +
private-key-password-flags, 802-1x
-
promiscuous, macvlan +
promiscuous, macvlan
-
proto, 802-11-wireless-security +
proto, 802-11-wireless-security
-
protocol, adsl +
protocol, adsl
-
proxy, vxlan +
proxy, vxlan
-
psk, 802-11-wireless-security +
psk, 802-11-wireless-security
-
psk-flags, 802-11-wireless-security +
psk-flags, 802-11-wireless-security

Q

-
qdiscs, tc +
qdiscs, tc
-
queue-id, team-port +
queue-id, team-port

R

-
ra-timeout, ipv6 +
ra-timeout, ipv6
-
rate, 802-11-wireless +
rate, 802-11-wireless
-
read-only, connection +
read-only, connection
-
refuse-chap, ppp +
refuse-chap, ppp
-
refuse-eap, ppp +
refuse-eap, ppp
-
refuse-mschap, ppp +
refuse-mschap, ppp
-
refuse-mschapv2, ppp +
refuse-mschapv2, ppp
-
refuse-pap, ppp +
refuse-pap, ppp
-
remote, ip-tunnel, vxlan +
remote, ip-tunnel, vxlan
-
require-mppe, ppp +
require-mppe, ppp
-
require-mppe-128, ppp +
require-mppe-128, ppp
-
route-data, ipv4, ipv6 +
route-data, ipv4, ipv6
-
route-metric, ipv4, ipv6 +
route-metric, ipv4, ipv6
-
route-table, ipv4, ipv6 +
route-table, ipv4, ipv6
-
routes, ipv4, ipv6 +
routes, ipv4, ipv6
-
rsc, vxlan +
rsc, vxlan
-
rstp-enable, ovs-bridge +
rstp-enable, ovs-bridge
-
runner, team +
runner, team
-
runner-active, team +
runner-active, team
-
runner-agg-select-policy, team +
runner-agg-select-policy, team
-
runner-fast-rate, team +
runner-fast-rate, team
-
runner-hwaddr-policy, team +
runner-hwaddr-policy, team
-
runner-min-ports, team +
runner-min-ports, team
-
runner-sys-prio, team +
runner-sys-prio, team
-
runner-tx-balancer, team +
runner-tx-balancer, team
-
runner-tx-balancer-interval, team +
runner-tx-balancer-interval, team
-
runner-tx-hash, team +
runner-tx-hash, team

S

-
s390-nettype, 802-3-ethernet +
s390-nettype, 802-3-ethernet
-
s390-options, 802-3-ethernet +
s390-options, 802-3-ethernet
-
s390-subchannels, 802-3-ethernet +
s390-subchannels, 802-3-ethernet
-
secondaries, connection +
secondaries, connection
-
secrets, vpn +
secrets, vpn
-
security, 802-11-wireless +
security, 802-11-wireless
-
seen-bssids, 802-11-wireless +
seen-bssids, 802-11-wireless
-
send-delay, serial +
send-delay, serial
-
send-sci, macsec +
send-sci, macsec
-
service, pppoe +
service, pppoe
-
service-type, vpn +
service-type, vpn
-
short-address, wpan +
short-address, wpan
-
sim-id, gsm +
sim-id, gsm
-
sim-operator-id, gsm +
sim-operator-id, gsm
-
slave-type, connection +
slave-type, connection
-
source-port-max, vxlan +
source-port-max, vxlan
-
source-port-min, vxlan +
source-port-min, vxlan
-
speed, 802-3-ethernet +
speed, 802-3-ethernet
-
ssid, 802-11-olpc-mesh, 802-11-wireless +
ssid, 802-11-olpc-mesh, 802-11-wireless
-
stable-id, connection +
stable-id, connection
-
sticky, team-port +
sticky, team-port
-
stopbits, serial +
stopbits, serial
-
stp, bridge +
stp, bridge
-
stp-enable, ovs-bridge +
stp-enable, ovs-bridge
-
subject-match, 802-1x +
subject-match, 802-1x
-
system-ca-certs, 802-1x +
system-ca-certs, 802-1x

T

-
table, vrf +
table, vrf
-
tag, ovs-port +
tag, ovs-port
-
tap, macvlan +
tap, macvlan
-
tfilters, tc +
tfilters, tc
-
timeout, vpn +
timeout, vpn
-
timestamp, connection +
timestamp, connection
-
token, ipv6 +
token, ipv6
-
tos, ip-tunnel, vxlan +
tos, ip-tunnel, vxlan
-
total-vfs, sriov +
total-vfs, sriov
-
transport-mode, infiniband +
transport-mode, infiniband
-
ttl, ip-tunnel, vxlan +
ttl, ip-tunnel, vxlan
-
tx-power, 802-11-wireless +
tx-power, 802-11-wireless
-
type, connection, bluetooth, ovs-interface +
type, connection, bluetooth, ovs-interface

U

-
user-name, vpn +
user-name, vpn
-
username, adsl, cdma, gsm, pppoe +
username, adsl, cdma, gsm, pppoe
-
uuid, connection +
uuid, connection

V

-
validation, macsec +
validation, macsec
-
vci, adsl +
vci, adsl
-
vfs, sriov +
vfs, sriov
-
vlan-default-pvid, bridge +
vlan-default-pvid, bridge
-
vlan-filtering, bridge +
vlan-filtering, bridge
-
vlan-mode, ovs-port +
vlan-mode, ovs-port
-
vlan-protocol, bridge +
vlan-protocol, bridge
-
vlan-stats-enabled, bridge +
vlan-stats-enabled, bridge
-
vlans, bridge, bridge-port +
vlans, bridge, bridge-port
-
vnet-hdr, tun +
vnet-hdr, tun
-
vpi, adsl +
vpi, adsl
-
org.freedesktop.NetworkManager.VPN.Connection, org.freedesktop.NetworkManager.VPN.Connection +
org.freedesktop.NetworkManager.VPN.Connection, org.freedesktop.NetworkManager.VPN.Connection
-
org.freedesktop.NetworkManager.VPN.Connection::PropertiesChanged, The "PropertiesChanged" signal +
org.freedesktop.NetworkManager.VPN.Connection::PropertiesChanged, The "PropertiesChanged" signal
-
org.freedesktop.NetworkManager.VPN.Connection::VpnStateChanged, The "VpnStateChanged" signal +
org.freedesktop.NetworkManager.VPN.Connection::VpnStateChanged, The "VpnStateChanged" signal
-
org.freedesktop.NetworkManager.VPN.Connection:Banner, The "Banner" property +
org.freedesktop.NetworkManager.VPN.Connection:Banner, The "Banner" property
-
org.freedesktop.NetworkManager.VPN.Connection:VpnState, The "VpnState" property +
org.freedesktop.NetworkManager.VPN.Connection:VpnState, The "VpnState" property
-
org.freedesktop.NetworkManager.VPN.Plugin, org.freedesktop.NetworkManager.VPN.Plugin +
org.freedesktop.NetworkManager.VPN.Plugin, org.freedesktop.NetworkManager.VPN.Plugin
-
org.freedesktop.NetworkManager.VPN.Plugin.Connect(), The Connect() method +
org.freedesktop.NetworkManager.VPN.Plugin.Connect(), The Connect() method
-
org.freedesktop.NetworkManager.VPN.Plugin.ConnectInteractive(), The ConnectInteractive() method +
org.freedesktop.NetworkManager.VPN.Plugin.ConnectInteractive(), The ConnectInteractive() method
-
org.freedesktop.NetworkManager.VPN.Plugin.Disconnect(), The Disconnect() method +
org.freedesktop.NetworkManager.VPN.Plugin.Disconnect(), The Disconnect() method
-
org.freedesktop.NetworkManager.VPN.Plugin.NeedSecrets(), The NeedSecrets() method +
org.freedesktop.NetworkManager.VPN.Plugin.NeedSecrets(), The NeedSecrets() method
-
org.freedesktop.NetworkManager.VPN.Plugin.NewSecrets(), The NewSecrets() method +
org.freedesktop.NetworkManager.VPN.Plugin.NewSecrets(), The NewSecrets() method
-
org.freedesktop.NetworkManager.VPN.Plugin.SetConfig(), The SetConfig() method +
org.freedesktop.NetworkManager.VPN.Plugin.SetConfig(), The SetConfig() method
-
org.freedesktop.NetworkManager.VPN.Plugin.SetFailure(), The SetFailure() method +
org.freedesktop.NetworkManager.VPN.Plugin.SetFailure(), The SetFailure() method
-
org.freedesktop.NetworkManager.VPN.Plugin.SetIp4Config(), The SetIp4Config() method +
org.freedesktop.NetworkManager.VPN.Plugin.SetIp4Config(), The SetIp4Config() method
-
org.freedesktop.NetworkManager.VPN.Plugin.SetIp6Config(), The SetIp6Config() method +
org.freedesktop.NetworkManager.VPN.Plugin.SetIp6Config(), The SetIp6Config() method
-
org.freedesktop.NetworkManager.VPN.Plugin::Config, The "Config" signal +
org.freedesktop.NetworkManager.VPN.Plugin::Config, The "Config" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::Failure, The "Failure" signal +
org.freedesktop.NetworkManager.VPN.Plugin::Failure, The "Failure" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::Ip4Config, The "Ip4Config" signal +
org.freedesktop.NetworkManager.VPN.Plugin::Ip4Config, The "Ip4Config" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::Ip6Config, The "Ip6Config" signal +
org.freedesktop.NetworkManager.VPN.Plugin::Ip6Config, The "Ip6Config" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::LoginBanner, The "LoginBanner" signal +
org.freedesktop.NetworkManager.VPN.Plugin::LoginBanner, The "LoginBanner" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::SecretsRequired, The "SecretsRequired" signal +
org.freedesktop.NetworkManager.VPN.Plugin::SecretsRequired, The "SecretsRequired" signal
-
org.freedesktop.NetworkManager.VPN.Plugin::StateChanged, The "StateChanged" signal +
org.freedesktop.NetworkManager.VPN.Plugin::StateChanged, The "StateChanged" signal
-
org.freedesktop.NetworkManager.VPN.Plugin:State, The "State" property +
org.freedesktop.NetworkManager.VPN.Plugin:State, The "State" property

W

-
wait-device-timeout, connection +
wait-device-timeout, connection
-
wake-on-lan, 802-3-ethernet +
wake-on-lan, 802-3-ethernet
-
wake-on-lan-password, 802-3-ethernet +
wake-on-lan-password, 802-3-ethernet
-
wake-on-wlan, 802-11-wireless +
wake-on-wlan, 802-11-wireless
-
wep-key-flags, 802-11-wireless-security +
wep-key-flags, 802-11-wireless-security
-
wep-key-type, 802-11-wireless-security +
wep-key-type, 802-11-wireless-security
-
wep-key0, 802-11-wireless-security +
wep-key0, 802-11-wireless-security
-
wep-key1, 802-11-wireless-security +
wep-key1, 802-11-wireless-security
-
wep-key2, 802-11-wireless-security +
wep-key2, 802-11-wireless-security
-
wep-key3, 802-11-wireless-security +
wep-key3, 802-11-wireless-security
-
wep-tx-keyidx, 802-11-wireless-security +
wep-tx-keyidx, 802-11-wireless-security
-
wfd-ies, wifi-p2p +
wfd-ies, wifi-p2p
-
org.freedesktop.NetworkManager.WifiP2PPeer, org.freedesktop.NetworkManager.WifiP2PPeer +
org.freedesktop.NetworkManager.WifiP2PPeer, org.freedesktop.NetworkManager.WifiP2PPeer
-
org.freedesktop.NetworkManager.WifiP2PPeer:Flags, The "Flags" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Flags, The "Flags" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:HwAddress, The "HwAddress" property +
org.freedesktop.NetworkManager.WifiP2PPeer:HwAddress, The "HwAddress" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:LastSeen, The "LastSeen" property +
org.freedesktop.NetworkManager.WifiP2PPeer:LastSeen, The "LastSeen" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:Manufacturer, The "Manufacturer" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Manufacturer, The "Manufacturer" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:Model, The "Model" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Model, The "Model" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:ModelNumber, The "ModelNumber" property +
org.freedesktop.NetworkManager.WifiP2PPeer:ModelNumber, The "ModelNumber" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:Name, The "Name" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Name, The "Name" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:Serial, The "Serial" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Serial, The "Serial" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:Strength, The "Strength" property +
org.freedesktop.NetworkManager.WifiP2PPeer:Strength, The "Strength" property
-
org.freedesktop.NetworkManager.WifiP2PPeer:WfdIEs, The "WfdIEs" property +
org.freedesktop.NetworkManager.WifiP2PPeer:WfdIEs, The "WfdIEs" property
-
wps-method, wifi-p2p, 802-11-wireless-security +
wps-method, wifi-p2p, 802-11-wireless-security

Z

-
zone, connection +
zone, connection
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/license.html b/docs/api/html/license.html index 45ca298..e1b6848 100644 --- a/docs/api/html/license.html +++ b/docs/api/html/license.html @@ -7,7 +7,7 @@ - + @@ -55,6 +55,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/manpages.html b/docs/api/html/manpages.html index 7507854..64c083b 100644 --- a/docs/api/html/manpages.html +++ b/docs/api/html/manpages.html @@ -8,7 +8,7 @@ - + @@ -68,6 +68,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-cloud-setup.html b/docs/api/html/nm-cloud-setup.html index 80e7c2e..13bc2d7 100644 --- a/docs/api/html/nm-cloud-setup.html +++ b/docs/api/html/nm-cloud-setup.html @@ -8,7 +8,7 @@ - + @@ -127,8 +127,12 @@

Environment Variables

-

The environment variables are used to configure /usr/libexec/nm-cloud-setup. - You may want to configure them in the systemd service with systemctl edit nm-cloud-setup.service.

+

The following environment variables are used to configure /usr/libexec/nm-cloud-setup. + You may want to configure them with a drop-in for the systemd service. + For example by calling systemctl edit nm-cloud-setup.service + and configuring [Service] Environment=, as described in + systemd.exec(5) + manual.

  • NM_CLOUD_SETUP_LOG: control the logging verbosity. Set it to one of TRACE, DEBUG, INFO, @@ -260,6 +264,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-dbus-types.html b/docs/api/html/nm-dbus-types.html index af1ee6c..ceff810 100644 --- a/docs/api/html/nm-dbus-types.html +++ b/docs/api/html/nm-dbus-types.html @@ -8,7 +8,7 @@ - + @@ -5196,6 +5196,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-initrd-generator.html b/docs/api/html/nm-initrd-generator.html index bef22dc..515ba4d 100644 --- a/docs/api/html/nm-initrd-generator.html +++ b/docs/api/html/nm-initrd-generator.html @@ -8,7 +8,7 @@ - + @@ -138,6 +138,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-online.html b/docs/api/html/nm-online.html index 84fef32..ab6a6a6 100644 --- a/docs/api/html/nm-online.html +++ b/docs/api/html/nm-online.html @@ -8,7 +8,7 @@ - + @@ -149,6 +149,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-openvswitch.html b/docs/api/html/nm-openvswitch.html index 74825fc..39878e9 100644 --- a/docs/api/html/nm-openvswitch.html +++ b/docs/api/html/nm-openvswitch.html @@ -8,7 +8,7 @@ - + @@ -172,6 +172,6 @@ Connection 'ovs-slave-eth3' (8dedeecb-ed12-482b-b77a-24a4fb835136) successfully +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-settings-dbus.html b/docs/api/html/nm-settings-dbus.html index f49d8ee..d428591 100644 --- a/docs/api/html/nm-settings-dbus.html +++ b/docs/api/html/nm-settings-dbus.html @@ -8,7 +8,7 @@ - + @@ -4215,6 +4215,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-settings-ifcfg-rh.html b/docs/api/html/nm-settings-ifcfg-rh.html index d8d3728..28ea908 100644 --- a/docs/api/html/nm-settings-ifcfg-rh.html +++ b/docs/api/html/nm-settings-ifcfg-rh.html @@ -8,7 +8,7 @@ - + @@ -1430,7 +1430,7 @@ Example: DNS1=1.2.3.4 DNS2=10.0.0.254 DNS3=8.8.8.8 addresses -IPADDR, PREFIX, IPADDR1, PREFIX1, ... +IPADDR, PREFIX (NETMASK), IPADDR1, PREFIX1 (NETMASK1), ...   List of static IP addresses. @@ -2722,6 +2722,6 @@ Allowed values: "yes", "no" +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-settings-keyfile.html b/docs/api/html/nm-settings-keyfile.html index a2a0799..d635adf 100644 --- a/docs/api/html/nm-settings-keyfile.html +++ b/docs/api/html/nm-settings-keyfile.html @@ -8,7 +8,7 @@ - + @@ -567,6 +567,6 @@ Example: mac-address-blacklist= 00:22:68:12:79:A6;00:22:68:12:79 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-settings-nmcli.html b/docs/api/html/nm-settings-nmcli.html index 4b758f6..5d5c86f 100644 --- a/docs/api/html/nm-settings-nmcli.html +++ b/docs/api/html/nm-settings-nmcli.html @@ -8,7 +8,7 @@ - + @@ -4751,6 +4751,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nm-vpn-dbus-types.html b/docs/api/html/nm-vpn-dbus-types.html index c7b0078..31c1d32 100644 --- a/docs/api/html/nm-vpn-dbus-types.html +++ b/docs/api/html/nm-vpn-dbus-types.html @@ -8,7 +8,7 @@ - + @@ -311,7 +311,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN

+

= 0

@@ -326,7 +326,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_NONE

+

= 1

@@ -341,7 +341,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED

+

= 2

@@ -356,7 +356,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED

+

= 3

@@ -371,7 +371,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED

+

= 4

@@ -386,7 +386,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID

+

= 5

@@ -401,7 +401,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT

+

= 6

@@ -416,7 +416,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT

+

= 7

@@ -431,7 +431,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED

+

= 8

@@ -446,7 +446,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS

+

= 9

@@ -461,7 +461,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED

+

= 10

@@ -476,7 +476,7 @@

-

= NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED

+

= 11

@@ -555,6 +555,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nmcli-examples.html b/docs/api/html/nmcli-examples.html index ad56ea7..8b15995 100644 --- a/docs/api/html/nmcli-examples.html +++ b/docs/api/html/nmcli-examples.html @@ -8,7 +8,7 @@ - + @@ -649,6 +649,6 @@ Connection 'ethernet-4' (de89cdeb-a3e1-4d53-8fa0-c22546c775f4) successfully +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nmcli.html b/docs/api/html/nmcli.html index 33d8eaf..bcb6657 100644 --- a/docs/api/html/nmcli.html +++ b/docs/api/html/nmcli.html @@ -8,7 +8,7 @@ - + @@ -1716,6 +1716,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/nmtui.html b/docs/api/html/nmtui.html index edf90ab..ee9ab9f 100644 --- a/docs/api/html/nmtui.html +++ b/docs/api/html/nmtui.html @@ -8,7 +8,7 @@ - + @@ -83,6 +83,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-access-points.html b/docs/api/html/ref-dbus-access-points.html index 0b4bb69..f59402c 100644 --- a/docs/api/html/ref-dbus-access-points.html +++ b/docs/api/html/ref-dbus-access-points.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-active-connections.html b/docs/api/html/ref-dbus-active-connections.html index 4c2cd24..a46f4dd 100644 --- a/docs/api/html/ref-dbus-active-connections.html +++ b/docs/api/html/ref-dbus-active-connections.html @@ -8,7 +8,7 @@ - + @@ -32,6 +32,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-agent-manager.html b/docs/api/html/ref-dbus-agent-manager.html index eccc57b..a64f484 100644 --- a/docs/api/html/ref-dbus-agent-manager.html +++ b/docs/api/html/ref-dbus-agent-manager.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-checkpoint.html b/docs/api/html/ref-dbus-checkpoint.html index 1795082..59f7aae 100644 --- a/docs/api/html/ref-dbus-checkpoint.html +++ b/docs/api/html/ref-dbus-checkpoint.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-devices.html b/docs/api/html/ref-dbus-devices.html index 6537de2..8abca65 100644 --- a/docs/api/html/ref-dbus-devices.html +++ b/docs/api/html/ref-dbus-devices.html @@ -8,7 +8,7 @@ - + @@ -119,6 +119,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-dhcp4-configs.html b/docs/api/html/ref-dbus-dhcp4-configs.html index 1f222e0..9c77a26 100644 --- a/docs/api/html/ref-dbus-dhcp4-configs.html +++ b/docs/api/html/ref-dbus-dhcp4-configs.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-dhcp6-configs.html b/docs/api/html/ref-dbus-dhcp6-configs.html index 0845bf0..b318d44 100644 --- a/docs/api/html/ref-dbus-dhcp6-configs.html +++ b/docs/api/html/ref-dbus-dhcp6-configs.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-dns-manager.html b/docs/api/html/ref-dbus-dns-manager.html index 1ab7f8a..ba431bc 100644 --- a/docs/api/html/ref-dbus-dns-manager.html +++ b/docs/api/html/ref-dbus-dns-manager.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-ip4-configs.html b/docs/api/html/ref-dbus-ip4-configs.html index 2bc3e16..b9c03bd 100644 --- a/docs/api/html/ref-dbus-ip4-configs.html +++ b/docs/api/html/ref-dbus-ip4-configs.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-ip6-configs.html b/docs/api/html/ref-dbus-ip6-configs.html index befd32e..b301432 100644 --- a/docs/api/html/ref-dbus-ip6-configs.html +++ b/docs/api/html/ref-dbus-ip6-configs.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-manager.html b/docs/api/html/ref-dbus-manager.html index ba94fe4..28b4399 100644 --- a/docs/api/html/ref-dbus-manager.html +++ b/docs/api/html/ref-dbus-manager.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-settings-manager.html b/docs/api/html/ref-dbus-settings-manager.html index d122fcc..b31916f 100644 --- a/docs/api/html/ref-dbus-settings-manager.html +++ b/docs/api/html/ref-dbus-settings-manager.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-settings.html b/docs/api/html/ref-dbus-settings.html index d1d5c70..ace2d15 100644 --- a/docs/api/html/ref-dbus-settings.html +++ b/docs/api/html/ref-dbus-settings.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-dbus-wifi-p2p-peers.html b/docs/api/html/ref-dbus-wifi-p2p-peers.html index 99d0470..5299114 100644 --- a/docs/api/html/ref-dbus-wifi-p2p-peers.html +++ b/docs/api/html/ref-dbus-wifi-p2p-peers.html @@ -8,7 +8,7 @@ - + @@ -27,6 +27,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/ref-settings.html b/docs/api/html/ref-settings.html index e09dc7b..feefbec 100644 --- a/docs/api/html/ref-settings.html +++ b/docs/api/html/ref-settings.html @@ -8,7 +8,7 @@ - + @@ -193,6 +193,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/secret-agents.html b/docs/api/html/secret-agents.html index b3df856..72c0696 100644 --- a/docs/api/html/secret-agents.html +++ b/docs/api/html/secret-agents.html @@ -8,7 +8,7 @@ - + @@ -33,6 +33,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/secrets-flags.html b/docs/api/html/secrets-flags.html index 1d59a93..66c07ce 100644 --- a/docs/api/html/secrets-flags.html +++ b/docs/api/html/secrets-flags.html @@ -8,7 +8,7 @@ - + @@ -58,6 +58,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-6lowpan.html b/docs/api/html/settings-6lowpan.html index 9c89ffd..29f8401 100644 --- a/docs/api/html/settings-6lowpan.html +++ b/docs/api/html/settings-6lowpan.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-802-11-olpc-mesh.html b/docs/api/html/settings-802-11-olpc-mesh.html index 7ff80ef..a9d9818 100644 --- a/docs/api/html/settings-802-11-olpc-mesh.html +++ b/docs/api/html/settings-802-11-olpc-mesh.html @@ -8,7 +8,7 @@ - + @@ -78,6 +78,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-802-11-wireless-security.html b/docs/api/html/settings-802-11-wireless-security.html index 7e915e4..48cddf4 100644 --- a/docs/api/html/settings-802-11-wireless-security.html +++ b/docs/api/html/settings-802-11-wireless-security.html @@ -8,7 +8,7 @@ - + @@ -180,6 +180,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-802-11-wireless.html b/docs/api/html/settings-802-11-wireless.html index 71cafdf..b718430 100644 --- a/docs/api/html/settings-802-11-wireless.html +++ b/docs/api/html/settings-802-11-wireless.html @@ -8,7 +8,7 @@ - + @@ -180,6 +180,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-802-1x.html b/docs/api/html/settings-802-1x.html index 20d0e7a..319d608 100644 --- a/docs/api/html/settings-802-1x.html +++ b/docs/api/html/settings-802-1x.html @@ -8,7 +8,7 @@ - + @@ -342,6 +342,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-802-3-ethernet.html b/docs/api/html/settings-802-3-ethernet.html index 1533c60..fb828bf 100644 --- a/docs/api/html/settings-802-3-ethernet.html +++ b/docs/api/html/settings-802-3-ethernet.html @@ -8,7 +8,7 @@ - + @@ -150,6 +150,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-adsl.html b/docs/api/html/settings-adsl.html index 1eb087d..a861bf9 100644 --- a/docs/api/html/settings-adsl.html +++ b/docs/api/html/settings-adsl.html @@ -8,7 +8,7 @@ - + @@ -102,6 +102,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-bluetooth.html b/docs/api/html/settings-bluetooth.html index e7ceaf7..02dc2e1 100644 --- a/docs/api/html/settings-bluetooth.html +++ b/docs/api/html/settings-bluetooth.html @@ -8,7 +8,7 @@ - + @@ -72,6 +72,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-bond.html b/docs/api/html/settings-bond.html index 268ae38..6c5bc7d 100644 --- a/docs/api/html/settings-bond.html +++ b/docs/api/html/settings-bond.html @@ -8,7 +8,7 @@ - + @@ -72,6 +72,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-bridge-port.html b/docs/api/html/settings-bridge-port.html index 6eb7d93..7842fdb 100644 --- a/docs/api/html/settings-bridge-port.html +++ b/docs/api/html/settings-bridge-port.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-bridge.html b/docs/api/html/settings-bridge.html index 5cee3c1..92e1dec 100644 --- a/docs/api/html/settings-bridge.html +++ b/docs/api/html/settings-bridge.html @@ -8,7 +8,7 @@ - + @@ -228,6 +228,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-cdma.html b/docs/api/html/settings-cdma.html index a32fa53..2c66448 100644 --- a/docs/api/html/settings-cdma.html +++ b/docs/api/html/settings-cdma.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-connection.html b/docs/api/html/settings-connection.html index 79025e4..3c57ca0 100644 --- a/docs/api/html/settings-connection.html +++ b/docs/api/html/settings-connection.html @@ -8,7 +8,7 @@ - + @@ -210,6 +210,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-dcb.html b/docs/api/html/settings-dcb.html index d3274cf..b7319d6 100644 --- a/docs/api/html/settings-dcb.html +++ b/docs/api/html/settings-dcb.html @@ -8,7 +8,7 @@ - + @@ -150,6 +150,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-dummy.html b/docs/api/html/settings-dummy.html index 6bc0ab4..403c68b 100644 --- a/docs/api/html/settings-dummy.html +++ b/docs/api/html/settings-dummy.html @@ -8,7 +8,7 @@ - + @@ -59,6 +59,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ethtool.html b/docs/api/html/settings-ethtool.html index adf9071..0a6033b 100644 --- a/docs/api/html/settings-ethtool.html +++ b/docs/api/html/settings-ethtool.html @@ -8,7 +8,7 @@ - + @@ -59,6 +59,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-generic.html b/docs/api/html/settings-generic.html index cdc71b1..ad8cd08 100644 --- a/docs/api/html/settings-generic.html +++ b/docs/api/html/settings-generic.html @@ -8,7 +8,7 @@ - + @@ -59,6 +59,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-gsm.html b/docs/api/html/settings-gsm.html index a48daf4..8e97b6e 100644 --- a/docs/api/html/settings-gsm.html +++ b/docs/api/html/settings-gsm.html @@ -8,7 +8,7 @@ - + @@ -144,6 +144,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-hostname.html b/docs/api/html/settings-hostname.html index 0caf2ce..0a4eddc 100644 --- a/docs/api/html/settings-hostname.html +++ b/docs/api/html/settings-hostname.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-infiniband.html b/docs/api/html/settings-infiniband.html index 702d1bf..39a5004 100644 --- a/docs/api/html/settings-infiniband.html +++ b/docs/api/html/settings-infiniband.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ip-tunnel.html b/docs/api/html/settings-ip-tunnel.html index 929ffa4..e092220 100644 --- a/docs/api/html/settings-ip-tunnel.html +++ b/docs/api/html/settings-ip-tunnel.html @@ -8,7 +8,7 @@ - + @@ -138,6 +138,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ipv4.html b/docs/api/html/settings-ipv4.html index 2152a9e..63ef91f 100644 --- a/docs/api/html/settings-ipv4.html +++ b/docs/api/html/settings-ipv4.html @@ -8,7 +8,7 @@ - + @@ -216,6 +216,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ipv6.html b/docs/api/html/settings-ipv6.html index 1680a5a..3c11795 100644 --- a/docs/api/html/settings-ipv6.html +++ b/docs/api/html/settings-ipv6.html @@ -8,7 +8,7 @@ - + @@ -228,6 +228,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-macsec.html b/docs/api/html/settings-macsec.html index ecc6f69..3baaf03 100644 --- a/docs/api/html/settings-macsec.html +++ b/docs/api/html/settings-macsec.html @@ -8,7 +8,7 @@ - + @@ -114,6 +114,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-macvlan.html b/docs/api/html/settings-macvlan.html index 3cf2fc0..cf6f9f8 100644 --- a/docs/api/html/settings-macvlan.html +++ b/docs/api/html/settings-macvlan.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-match.html b/docs/api/html/settings-match.html index e1afd6a..3c79f2a 100644 --- a/docs/api/html/settings-match.html +++ b/docs/api/html/settings-match.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-bridge.html b/docs/api/html/settings-ovs-bridge.html index 29239f4..a30bc7e 100644 --- a/docs/api/html/settings-ovs-bridge.html +++ b/docs/api/html/settings-ovs-bridge.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-dpdk.html b/docs/api/html/settings-ovs-dpdk.html index a40d5fd..00c5882 100644 --- a/docs/api/html/settings-ovs-dpdk.html +++ b/docs/api/html/settings-ovs-dpdk.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-external-ids.html b/docs/api/html/settings-ovs-external-ids.html index 0ec0c4e..feaafcb 100644 --- a/docs/api/html/settings-ovs-external-ids.html +++ b/docs/api/html/settings-ovs-external-ids.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-interface.html b/docs/api/html/settings-ovs-interface.html index 71aff77..8c842ab 100644 --- a/docs/api/html/settings-ovs-interface.html +++ b/docs/api/html/settings-ovs-interface.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-patch.html b/docs/api/html/settings-ovs-patch.html index 9fb817b..3dddfa9 100644 --- a/docs/api/html/settings-ovs-patch.html +++ b/docs/api/html/settings-ovs-patch.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ovs-port.html b/docs/api/html/settings-ovs-port.html index 56f4583..7eac441 100644 --- a/docs/api/html/settings-ovs-port.html +++ b/docs/api/html/settings-ovs-port.html @@ -8,7 +8,7 @@ - + @@ -96,6 +96,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-ppp.html b/docs/api/html/settings-ppp.html index faff1d8..30b845b 100644 --- a/docs/api/html/settings-ppp.html +++ b/docs/api/html/settings-ppp.html @@ -8,7 +8,7 @@ - + @@ -168,6 +168,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-pppoe.html b/docs/api/html/settings-pppoe.html index 339e0aa..f8ba530 100644 --- a/docs/api/html/settings-pppoe.html +++ b/docs/api/html/settings-pppoe.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-proxy.html b/docs/api/html/settings-proxy.html index 3cfe06f..cb591d1 100644 --- a/docs/api/html/settings-proxy.html +++ b/docs/api/html/settings-proxy.html @@ -8,7 +8,7 @@ - + @@ -84,6 +84,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-serial.html b/docs/api/html/settings-serial.html index 5caaa48..50f3877 100644 --- a/docs/api/html/settings-serial.html +++ b/docs/api/html/settings-serial.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-sriov.html b/docs/api/html/settings-sriov.html index 0317c77..34dc54b 100644 --- a/docs/api/html/settings-sriov.html +++ b/docs/api/html/settings-sriov.html @@ -8,7 +8,7 @@ - + @@ -78,6 +78,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-tc.html b/docs/api/html/settings-tc.html index 1edc8ed..e57f62a 100644 --- a/docs/api/html/settings-tc.html +++ b/docs/api/html/settings-tc.html @@ -8,7 +8,7 @@ - + @@ -72,6 +72,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-team-port.html b/docs/api/html/settings-team-port.html index e493d42..0bc1023 100644 --- a/docs/api/html/settings-team-port.html +++ b/docs/api/html/settings-team-port.html @@ -8,7 +8,7 @@ - + @@ -102,6 +102,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-team.html b/docs/api/html/settings-team.html index c4ae06b..3b9e94b 100644 --- a/docs/api/html/settings-team.html +++ b/docs/api/html/settings-team.html @@ -8,7 +8,7 @@ - + @@ -162,6 +162,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-tun.html b/docs/api/html/settings-tun.html index 20af929..1b4cfd6 100644 --- a/docs/api/html/settings-tun.html +++ b/docs/api/html/settings-tun.html @@ -8,7 +8,7 @@ - + @@ -96,6 +96,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-user.html b/docs/api/html/settings-user.html index a00fac4..39d7be2 100644 --- a/docs/api/html/settings-user.html +++ b/docs/api/html/settings-user.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-veth.html b/docs/api/html/settings-veth.html index 761b4f7..f0f7c5a 100644 --- a/docs/api/html/settings-veth.html +++ b/docs/api/html/settings-veth.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-vlan.html b/docs/api/html/settings-vlan.html index 33787e8..82296a4 100644 --- a/docs/api/html/settings-vlan.html +++ b/docs/api/html/settings-vlan.html @@ -8,7 +8,7 @@ - + @@ -96,6 +96,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-vpn.html b/docs/api/html/settings-vpn.html index db05951..bad0a84 100644 --- a/docs/api/html/settings-vpn.html +++ b/docs/api/html/settings-vpn.html @@ -8,7 +8,7 @@ - + @@ -96,6 +96,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-vrf.html b/docs/api/html/settings-vrf.html index 839bf8c..f909e88 100644 --- a/docs/api/html/settings-vrf.html +++ b/docs/api/html/settings-vrf.html @@ -8,7 +8,7 @@ - + @@ -64,6 +64,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-vxlan.html b/docs/api/html/settings-vxlan.html index 6da8eb5..9195620 100644 --- a/docs/api/html/settings-vxlan.html +++ b/docs/api/html/settings-vxlan.html @@ -8,7 +8,7 @@ - + @@ -156,6 +156,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-wifi-p2p.html b/docs/api/html/settings-wifi-p2p.html index dc9c259..e1a0578 100644 --- a/docs/api/html/settings-wifi-p2p.html +++ b/docs/api/html/settings-wifi-p2p.html @@ -8,7 +8,7 @@ - + @@ -78,6 +78,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-wimax.html b/docs/api/html/settings-wimax.html index ee0f111..993ca96 100644 --- a/docs/api/html/settings-wimax.html +++ b/docs/api/html/settings-wimax.html @@ -8,7 +8,7 @@ - + @@ -72,6 +72,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-wireguard.html b/docs/api/html/settings-wireguard.html index e632ffd..01f20f6 100644 --- a/docs/api/html/settings-wireguard.html +++ b/docs/api/html/settings-wireguard.html @@ -8,7 +8,7 @@ - + @@ -114,6 +114,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/settings-wpan.html b/docs/api/html/settings-wpan.html index 6fdbaa2..6475e04 100644 --- a/docs/api/html/settings-wpan.html +++ b/docs/api/html/settings-wpan.html @@ -8,7 +8,7 @@ - + @@ -90,6 +90,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/spec.html b/docs/api/html/spec.html index 4138e61..05d19ae 100644 --- a/docs/api/html/spec.html +++ b/docs/api/html/spec.html @@ -8,7 +8,7 @@ - + @@ -190,6 +190,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/html/vpn-plugins.html b/docs/api/html/vpn-plugins.html index 76cfc09..f38251c 100644 --- a/docs/api/html/vpn-plugins.html +++ b/docs/api/html/vpn-plugins.html @@ -8,7 +8,7 @@ - + @@ -37,6 +37,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/api/version.xml b/docs/api/version.xml index 799f8f9..df48db0 100644 --- a/docs/api/version.xml +++ b/docs/api/version.xml @@ -1 +1 @@ -1.29.10 +1.29.11 diff --git a/docs/libnm/Makefile.in b/docs/libnm/Makefile.in index 1251a72..ff0504a 100644 --- a/docs/libnm/Makefile.in +++ b/docs/libnm/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -575,6 +575,7 @@ DOC_STAMPS = setup-build.stamp scan-build.stamp sgml-build.stamp \ sgml.stamp html.stamp pdf.stamp SCANOBJ_FILES = \ + $(DOC_MODULE).actions \ $(DOC_MODULE).args \ $(DOC_MODULE).hierarchy \ $(DOC_MODULE).interfaces \ diff --git a/docs/libnm/html/NMAccessPoint.html b/docs/libnm/html/NMAccessPoint.html index e587fdf..e12e1c6 100644 --- a/docs/libnm/html/NMAccessPoint.html +++ b/docs/libnm/html/NMAccessPoint.html @@ -8,7 +8,7 @@ - + @@ -300,7 +300,7 @@ nm_access_point_get_ssid (

Returns

the GBytes containing the SSID, or NULL if the SSID is unknown.

-

[transfer none]

+

[transfer none]


@@ -494,7 +494,7 @@ with this function.

connections

-

an array of NMConnections to +

an array of NMConnections to filter.

[element-type NMConnection] @@ -504,13 +504,13 @@ filter.

Returns

an array of -NMConnections that could be activated with the given ap +NMConnections that could be activated with the given ap . The array should be freed with g_ptr_array_unref() when it is no longer required.

WARNING: the transfer annotation for this function may not work correctly with bindings. See https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/305. You can filter the list yourself with nm_access_point_connection_valid().

-

[transfer full][element-type NMConnection]

+

[transfer full][element-type NMConnection]


@@ -625,6 +625,6 @@ against

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMActiveConnection.html b/docs/libnm/html/NMActiveConnection.html index 4bbd147..b385dc6 100644 --- a/docs/libnm/html/NMActiveConnection.html +++ b/docs/libnm/html/NMActiveConnection.html @@ -8,7 +8,7 @@ - + @@ -278,7 +278,7 @@ nm_active_connection_get_connection (

Returns

the NMRemoteConnection which this NMActiveConnection is an active instance of.

-

[transfer none]

+

[transfer none]


@@ -529,7 +529,7 @@ nm_active_connection_get_master (

Returns

the master NMDevice of the NMActiveConnection.

-

[transfer none]

+

[transfer none]


@@ -584,7 +584,7 @@ nm_active_connection_get_ip4_config (

Returns

the IPv4 NMIPConfig, or NULL if the connection is not in the NM_ACTIVE_CONNECTION_STATE_ACTIVATED state.

-

[transfer none]

+

[transfer none]


@@ -614,7 +614,7 @@ nm_active_connection_get_dhcp4_config (NMDhcpConfig, or NULL if the connection does not use DHCP, or is not in the NM_ACTIVE_CONNECTION_STATE_ACTIVATED state.

-

[transfer none]

+

[transfer none]


@@ -669,7 +669,7 @@ nm_active_connection_get_ip6_config (

Returns

the IPv6 NMIPConfig, or NULL if the connection is not in the NM_ACTIVE_CONNECTION_STATE_ACTIVATED state.

-

[transfer none]

+

[transfer none]


@@ -699,7 +699,7 @@ nm_active_connection_get_dhcp6_config (NMDhcpConfig, or NULL if the connection does not use DHCPv6, or is not in the NM_ACTIVE_CONNECTION_STATE_ACTIVATED state.

-

[transfer none]

+

[transfer none]


@@ -829,6 +829,6 @@ nm_active_connection_get_vpn ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMCheckpoint.html b/docs/libnm/html/NMCheckpoint.html index c9283c0..87f2a9c 100644 --- a/docs/libnm/html/NMCheckpoint.html +++ b/docs/libnm/html/NMCheckpoint.html @@ -8,7 +8,7 @@ - + @@ -199,6 +199,6 @@ nm_checkpoint_get_rollback_timeout (
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMClient.html b/docs/libnm/html/NMClient.html index 6338076..7e7c131 100644 --- a/docs/libnm/html/NMClient.html +++ b/docs/libnm/html/NMClient.html @@ -8,7 +8,7 @@ - + @@ -1068,7 +1068,7 @@ nm_dns_entry_get_interface (

Returns

the interface name.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -1096,7 +1096,7 @@ nm_dns_entry_get_nameservers (

Returns

the list of name servers.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -1124,7 +1124,7 @@ nm_dns_entry_get_domains (

Returns

the list of DNS domains.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -1346,7 +1346,7 @@ automatically initialized during async/sync init.

Returns

the D-Bus connection of the client, or NULL if none is set.

-

[transfer none]

+

[transfer none]

Since: 1.22

@@ -1381,7 +1381,7 @@ the

Returns

the GMainContext of the client.

-

[transfer none]

+

[transfer none]

Since: 1.22

@@ -1424,7 +1424,7 @@ iterating the main context until the object got unreferenced.

Note that after the NMClient instance gets destroyed, the remaining callbacks will be invoked right away. That means, the user won't have to iterate the main context much longer.

-

[transfer none]

+

[transfer none]

Since: 1.22

@@ -1451,7 +1451,7 @@ nm_client_get_dbus_name_owner (

Returns

the current name owner of the D-Bus service of NetworkManager.

-

[transfer none]

+

[transfer none]

Since: 1.22

@@ -1593,7 +1593,7 @@ nm_client_get_object_by_path (NMObject instance that is cached under dbus_path , or NULL if no such object exists.

-

[transfer none]

+

[transfer none]

Since: 1.24

@@ -1686,7 +1686,7 @@ The numeric values correspond to length
.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.24

@@ -2128,7 +2128,7 @@ connectivity.

Returns

the connectivity URI in use.

-

[transfer none]

+

[transfer none]

Since: 1.20

@@ -2640,7 +2640,7 @@ methods such as GPtrArray containing all the NMDevices. The returned array is owned by the NMClient object and should not be modified.

-

[transfer none][element-type NMDevice]

+

[transfer none][element-type NMDevice]


@@ -2676,7 +2676,7 @@ use device-specific methods such as GPtrArray containing all the NMDevices. The returned array is owned by the NMClient object and should not be modified.

-

[transfer none][element-type NMDevice]

+

[transfer none][element-type NMDevice]

Since: 1.2

@@ -2713,7 +2713,7 @@ nm_client_get_device_by_path (

Returns

the NMDevice for the given object_path or NULL if none is found.

-

[transfer none]

+

[transfer none]


@@ -2749,7 +2749,7 @@ nm_client_get_device_by_iface (

Returns

the NMDevice for the given iface or NULL if none is found.

-

[transfer none]

+

[transfer none]


@@ -2778,7 +2778,7 @@ nm_client_get_active_connections (a GPtrArray containing all the active NMActiveConnections. The returned array is owned by the client and should not be modified.

-

[transfer none][element-type NMActiveConnection]

+

[transfer none][element-type NMActiveConnection]


@@ -2814,7 +2814,7 @@ non-NetworkManager-recognized device, this will return

Returns

the appropriate NMActiveConnection, if any.

-

[transfer none]

+

[transfer none]


@@ -2824,7 +2824,7 @@ any.

nm_client_get_activating_connection (NMClient *client);

Gets the NMActiveConnection corresponding to a currently-activating connection that is expected to become the new -“primary-connection” upon successful activation.

+“primary-connection” upon successful activation.

Parameters

@@ -2844,7 +2844,7 @@ currently-activating connection that is expected to become the new

Returns

the appropriate NMActiveConnection, if any.

-

[transfer none]

+

[transfer none]


@@ -2874,7 +2874,7 @@ in connection picks the best available connection for the device and activates it.

Note that the callback is invoked when NetworkManager has started activating the new connection, not when it finishes. You can use the returned -NMActiveConnection object (in particular, “state”) to +NMActiveConnection object (in particular, “state”) to track the activation to its completion.

Parameters

@@ -2972,7 +2972,7 @@ nm_client_activate_connection_finish (NMActiveConnection on success, NULL on failure, in which case error will be set.

-

[transfer full]

+

[transfer full]


@@ -2994,7 +2994,7 @@ activated as with NMActiveConnection object (in particular, “state”) to +NMActiveConnection object (in particular, “state”) to track the activation to its completion.

Parameters

@@ -3100,7 +3100,7 @@ nm_client_add_and_activate_connection_finish

the new NMActiveConnection on success, NULL on failure, in which case error will be set.

-

[transfer full]

+

[transfer full]


@@ -3123,7 +3123,7 @@ activated as with NMActiveConnection object (in particular, “state”) to +NMActiveConnection object (in particular, “state”) to track the activation to its completion.

This is identical to nm_client_add_and_activate_connection_async() but takes a further options @@ -3245,7 +3245,7 @@ nm_client_add_and_activate_connection2_finish

- +

the output result of type "a{sv}" returned by D-Bus' AddAndActivate2 call. Currently, no output is implemented yet.

[allow-none][transfer full][allow-none][transfer full]
@@ -3255,7 +3255,7 @@ output is implemented yet.

the new NMActiveConnection on success, NULL on failure, in which case error will be set.

-

[transfer full]

+

[transfer full]


@@ -3425,7 +3425,7 @@ containing all connections provided by the remote settings service. The returned array is owned by the NMClient object and should not be modified.

The connections are as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer none][element-type NMRemoteConnection]

+

[transfer none][element-type NMRemoteConnection]


@@ -3464,7 +3464,7 @@ nm_client_get_connection_by_id (

The connection is as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer none]

+

[transfer none]


@@ -3503,7 +3503,7 @@ nm_client_get_connection_by_path (

The connection is as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer none]

+

[transfer none]


@@ -3542,7 +3542,7 @@ nm_client_get_connection_by_uuid (

The connection is as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer none]

+

[transfer none]


@@ -3657,7 +3657,7 @@ nm_client_add_connection_finish (the new NMRemoteConnection on success, NULL on failure, in which case error will be set.

-

[transfer full]

+

[transfer full]


@@ -3768,7 +3768,7 @@ nm_client_add_connection2_finish (AddConnection2(). If you care about the output result, then the "ignore_out_result" parameter of nm_client_add_connection2() must not be set to TRUE.

-[allow-none][transfer full][out] +[allow-none][transfer full][out]

error

@@ -3782,7 +3782,7 @@ parameter of

Returns

on success, a pointer to the added NMRemoteConnection.

-

[transfer full]

+

[transfer full]

Since: 1.20

@@ -3834,7 +3834,7 @@ filenames that failed to load.

failures

on return, a NULL-terminated array of filenames that failed to load.

-[out][transfer full] +[out][transfer full]

cancellable

@@ -3945,7 +3945,7 @@ nm_client_load_connections_finish (

failures

on return, a NULL-terminated array of filenames that failed to load.

-[out][transfer full][array zero-terminated=1] +[out][transfer full][array zero-terminated=1]

result

@@ -4181,7 +4181,7 @@ nm_client_get_dns_configuration (NMDnsEntry elements or NULL in case the value is not available. The returned array is owned by the NMClient object and should not be modified.

-

[transfer none][element-type NMDnsEntry]

+

[transfer none][element-type NMDnsEntry]

Since: 1.6

@@ -4211,7 +4211,7 @@ nm_client_get_checkpoints (GPtrArray containing all the NMCheckpoint. The returned array is owned by the NMClient object and should not be modified.

-

[transfer none][element-type NMCheckpoint]

+

[transfer none][element-type NMCheckpoint]

Since: 1.12

@@ -4323,7 +4323,7 @@ nm_client_checkpoint_create_finish (the new NMCheckpoint on success, NULL on failure, in which case error will be set.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -4510,7 +4510,7 @@ nm_client_checkpoint_rollback_finish (NMRollbackResult.

-

[transfer full][element-type utf8 guint32]

+

[transfer full][element-type utf8 guint32]

Since: 1.12

@@ -4842,7 +4842,7 @@ nm_client_dbus_call_finish (

Returns

the result GVariant or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.24

@@ -5352,6 +5352,6 @@ operation succeeded, but the object that was allegedly created (eg, +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMConnection.html b/docs/libnm/html/NMConnection.html index c652965..44e1f9b 100644 --- a/docs/libnm/html/NMConnection.html +++ b/docs/libnm/html/NMConnection.html @@ -8,7 +8,7 @@ - + @@ -660,7 +660,7 @@ the setting object's reference count.

setting

the NMSetting to add to the connection object.

-[transfer full] +[transfer full] @@ -731,7 +731,7 @@ to the

Returns

the NMSetting, or NULL if no setting of that type was previously added to the NMConnection.

-

[transfer none]

+

[transfer none]


@@ -768,7 +768,7 @@ the

Returns

the NMSetting, or NULL if no setting with that name was previously added to the NMConnection.

-

[transfer none]

+

[transfer none]


@@ -806,7 +806,7 @@ marshalling over D-Bus or otherwise serializing.

Returns

a new floating GVariant describing the connection, its settings, and each setting's properties.

-

[transfer none]

+

[transfer none]


@@ -1178,7 +1178,7 @@ return points to an allocated NMSetting which may be required; the caller owns the array and must free the array itself with g_ptr_array_free(), but not free its elements.

-[out][element-type utf8][allow-none][transfer container] +[out][element-type utf8][allow-none][transfer container] @@ -1314,7 +1314,7 @@ failed (tried to update secrets for a setting that doesn't exist, etc)

nm_connection_set_path (NMConnection *connection, const char *path);

Sets the D-Bus path of the connection. This property is not serialized, and -is only for the reference of the caller. Sets the “path” +is only for the reference of the caller. Sets the “path” property.

Parameters

@@ -1513,7 +1513,7 @@ nm_connection_get_settings (connection . If the connection has no settings, NULL is returned.

-

[array length=out_length][transfer container]

+

[array length=out_length][transfer container]

Since: 1.10

@@ -1680,7 +1680,7 @@ Eg, "VLAN (eth1.1)".

's device, or NULL if connection is not a virtual connection type.

-

[transfer full]

+

[transfer full]


@@ -1707,7 +1707,7 @@ nm_connection_get_setting_802_1x (

Returns

an NMSetting8021x if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1734,7 +1734,7 @@ nm_connection_get_setting_bluetooth (

Returns

an NMSettingBluetooth if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1761,7 +1761,7 @@ nm_connection_get_setting_bond (

Returns

an NMSettingBond if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1788,7 +1788,7 @@ nm_connection_get_setting_team (

Returns

an NMSettingTeam if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1815,7 +1815,7 @@ nm_connection_get_setting_team_port (

Returns

an NMSettingTeamPort if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1842,7 +1842,7 @@ nm_connection_get_setting_bridge (

Returns

an NMSettingBridge if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1869,7 +1869,7 @@ nm_connection_get_setting_bridge_port (

Returns

an NMSettingBridgePort if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1896,7 +1896,7 @@ nm_connection_get_setting_cdma (

Returns

an NMSettingCdma if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1923,7 +1923,7 @@ nm_connection_get_setting_connection (

Returns

an NMSettingConnection if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1950,7 +1950,7 @@ nm_connection_get_setting_dcb (

Returns

an NMSettingDcb if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -1977,7 +1977,7 @@ nm_connection_get_setting_dummy (

Returns

an NMSettingDummy if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.8

@@ -2005,7 +2005,7 @@ nm_connection_get_setting_generic (

Returns

an NMSettingGeneric if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2032,7 +2032,7 @@ nm_connection_get_setting_gsm (

Returns

an NMSettingGsm if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2059,7 +2059,7 @@ nm_connection_get_setting_infiniband (

Returns

an NMSettingInfiniband if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2086,7 +2086,7 @@ nm_connection_get_setting_ip_tunnel (

Returns

an NMSettingIPTunnel if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -2118,7 +2118,7 @@ majority of IPv4-setting-related methods are on that type, not

Returns

an NMSettingIP4Config if the connection contains one, otherwise NULL.

-

[type NMSettingIP4Config][transfer none]

+

[type NMSettingIP4Config][transfer none]


@@ -2149,7 +2149,7 @@ majority of IPv6-setting-related methods are on that type, not

Returns

an NMSettingIP6Config if the connection contains one, otherwise NULL.

-

[type NMSettingIP6Config][transfer none]

+

[type NMSettingIP6Config][transfer none]


@@ -2176,7 +2176,7 @@ nm_connection_get_setting_macsec (

Returns

an NMSettingMacsec if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -2204,7 +2204,7 @@ nm_connection_get_setting_macvlan (

Returns

an NMSettingMacvlan if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -2232,7 +2232,7 @@ nm_connection_get_setting_olpc_mesh (

Returns

an NMSettingOlpcMesh if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2259,7 +2259,7 @@ nm_connection_get_setting_ovs_bridge (

Returns

an NMSettingOvsBridge if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.10

@@ -2288,7 +2288,7 @@ nm_connection_get_setting_ovs_interface

Returns

an NMSettingOvsInterface if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.10

@@ -2316,7 +2316,7 @@ nm_connection_get_setting_ovs_patch (

Returns

an NMSettingOvsPatch if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.10

@@ -2344,7 +2344,7 @@ nm_connection_get_setting_ovs_port (

Returns

an NMSettingOvsPort if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.10

@@ -2372,7 +2372,7 @@ nm_connection_get_setting_ppp (

Returns

an NMSettingPpp if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2399,7 +2399,7 @@ nm_connection_get_setting_pppoe (

Returns

an NMSettingPppoe if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2426,7 +2426,7 @@ nm_connection_get_setting_proxy (

Returns

an NMSettingProxy if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -2454,7 +2454,7 @@ nm_connection_get_setting_serial (

Returns

an NMSettingSerial if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2481,7 +2481,7 @@ nm_connection_get_setting_tc_config (

Returns

an NMSettingTCConfig if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -2509,7 +2509,7 @@ nm_connection_get_setting_tun (

Returns

an NMSettingTun if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -2537,7 +2537,7 @@ nm_connection_get_setting_vpn (

Returns

an NMSettingVpn if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2564,7 +2564,7 @@ nm_connection_get_setting_wimax (

Returns

an NMSettingWimax if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2591,7 +2591,7 @@ nm_connection_get_setting_adsl (

Returns

an NMSettingAdsl if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2618,7 +2618,7 @@ nm_connection_get_setting_wired (

Returns

an NMSettingWired if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2645,7 +2645,7 @@ nm_connection_get_setting_wireless (

Returns

an NMSettingWireless if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2673,7 +2673,7 @@ nm_connection_get_setting_wireless_security

Returns

an NMSettingWirelessSecurity if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2700,7 +2700,7 @@ nm_connection_get_setting_vlan (

Returns

an NMSettingVlan if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]


@@ -2727,7 +2727,7 @@ nm_connection_get_setting_vxlan (

Returns

an NMSettingVxlan if the connection contains one, otherwise NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -2861,6 +2861,6 @@ are agent owned will be serialized. Since: 1.20.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDevice.html b/docs/libnm/html/NMDevice.html index 2cabe6d..c1d5d86 100644 --- a/docs/libnm/html/NMDevice.html +++ b/docs/libnm/html/NMDevice.html @@ -8,7 +8,7 @@ - + @@ -1178,7 +1178,7 @@ works with VPN connections.

Returns

the IPv4 NMIPConfig, or NULL if the device is not activated.

-

[transfer none]

+

[transfer none]


@@ -1208,7 +1208,7 @@ works with VPN connections.

Returns

the IPv4 NMDhcpConfig, or NULL if the device is not activated or not using DHCP.

-

[transfer none]

+

[transfer none]


@@ -1237,7 +1237,7 @@ works with VPN connections.

Returns

the IPv6 NMIPConfig or NULL if the device is not activated.

-

[transfer none]

+

[transfer none]


@@ -1267,7 +1267,7 @@ works with VPN connections.

Returns

the IPv6 NMDhcpConfig, or NULL if the device is not activated or not using DHCPv6.

-

[transfer none]

+

[transfer none]


@@ -1384,7 +1384,7 @@ nm_device_get_active_connection (

Returns

the NMActiveConnection or NULL if the device is not part of an active connection.

-

[transfer none]

+

[transfer none]


@@ -1499,7 +1499,7 @@ nm_device_is_real (

Returns

TRUE if the device exists, or FALSE if it is a placeholder device that could be automatically created by NetworkManager if one of its -“available-connections” was activated.

+“available-connections” was activated.

Since: 1.2

@@ -1671,7 +1671,7 @@ nm_device_get_lldp_neighbors (NMLldpNeighbors. This is the internal copy used by the device and must not be modified. The library never modifies the returned array and thus it is safe for callers to reference and keep using it.

-

[element-type NMLldpNeighbor][transfer none]

+

[element-type NMLldpNeighbor][transfer none]

Since: 1.2

@@ -1737,7 +1737,7 @@ devices in devices

Returns

the device names.

-

[transfer full][array zero-terminated=1]

+

[transfer full][array zero-terminated=1]


@@ -1975,7 +1975,7 @@ the applied connection.

or NULL on error.

The connection is as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -2079,7 +2079,7 @@ connection.

of error.

The connection is as received from D-Bus and might not validate according to nm_connection_verify().

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -2378,7 +2378,7 @@ incompatible with the device. To get the full list of connections see

connections

-

an array of NMConnections to filter.

+

an array of NMConnections to filter.

[element-type NMConnection] @@ -2387,13 +2387,13 @@ incompatible with the device. To get the full list of connections see

Returns

an array of -NMConnections that could be activated with the given device +NMConnections that could be activated with the given device . The array should be freed with g_ptr_array_unref() when it is no longer required.

WARNING: the transfer annotation for this function may not work correctly with bindings. See https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/305. You can filter the list yourself with nm_device_connection_valid().

-

[transfer full][element-type NMConnection]

+

[transfer full][element-type NMConnection]


@@ -2531,7 +2531,7 @@ nm_lldp_neighbor_new (void

Returns

the new NMLldpNeighbor object.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -2607,7 +2607,7 @@ nm_lldp_neighbor_get_attr_names (

Returns

a NULL-terminated array of attribute names.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -2644,7 +2644,7 @@ nm_lldp_neighbor_get_attr_string_value

out_value

on return, the attribute value.

-[out][allow-none][transfer none] +[out][allow-none][transfer none] @@ -2772,7 +2772,7 @@ nm_lldp_neighbor_get_attr_value (the value or NULL if the attribute with name was not found.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -2974,6 +2974,6 @@ not found.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDevice6Lowpan.html b/docs/libnm/html/NMDevice6Lowpan.html index d5d3772..e111db8 100644 --- a/docs/libnm/html/NMDevice6Lowpan.html +++ b/docs/libnm/html/NMDevice6Lowpan.html @@ -8,7 +8,7 @@ - + @@ -104,7 +104,7 @@ nm_device_6lowpan_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]

Since: 1.14

@@ -132,6 +132,6 @@ NM_DEPRECATED_IN_1_24_FOR (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceAdsl.html b/docs/libnm/html/NMDeviceAdsl.html index 0e8a73a..b314c32 100644 --- a/docs/libnm/html/NMDeviceAdsl.html +++ b/docs/libnm/html/NMDeviceAdsl.html @@ -8,7 +8,7 @@ - + @@ -102,6 +102,6 @@ nm_device_adsl_get_carrier ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceBond.html b/docs/libnm/html/NMDeviceBond.html index bfff1b9..fd2f4d3 100644 --- a/docs/libnm/html/NMDeviceBond.html +++ b/docs/libnm/html/NMDeviceBond.html @@ -8,7 +8,7 @@ - + @@ -203,6 +203,6 @@ copy used by the device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceBridge.html b/docs/libnm/html/NMDeviceBridge.html index 53948c1..dc35f55 100644 --- a/docs/libnm/html/NMDeviceBridge.html +++ b/docs/libnm/html/NMDeviceBridge.html @@ -8,7 +8,7 @@ - + @@ -203,6 +203,6 @@ copy used by the device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceBt.html b/docs/libnm/html/NMDeviceBt.html index 576f152..203f66f 100644 --- a/docs/libnm/html/NMDeviceBt.html +++ b/docs/libnm/html/NMDeviceBt.html @@ -8,7 +8,7 @@ - + @@ -198,6 +198,6 @@ nm_device_bt_get_capabilities (
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceDummy.html b/docs/libnm/html/NMDeviceDummy.html index 6b20f59..0ec953c 100644 --- a/docs/libnm/html/NMDeviceDummy.html +++ b/docs/libnm/html/NMDeviceDummy.html @@ -8,7 +8,7 @@ - + @@ -83,6 +83,6 @@ NM_DEPRECATED_IN_1_24_FOR (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceEthernet.html b/docs/libnm/html/NMDeviceEthernet.html index 69c6b37..8d4f2af 100644 --- a/docs/libnm/html/NMDeviceEthernet.html +++ b/docs/libnm/html/NMDeviceEthernet.html @@ -8,7 +8,7 @@ - + @@ -252,7 +252,7 @@ nm_device_ethernet_get_s390_subchannels

Returns

array of strings, each specifying one subchannel the s390 device uses to communicate to the host.

-

[transfer none][element-type utf8]

+

[transfer none][element-type utf8]

Since: 1.2

@@ -291,6 +291,6 @@ one subchannel the s390 device uses to communicate to the host.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceGeneric.html b/docs/libnm/html/NMDeviceGeneric.html index ad57f2b..3adb441 100644 --- a/docs/libnm/html/NMDeviceGeneric.html +++ b/docs/libnm/html/NMDeviceGeneric.html @@ -8,7 +8,7 @@ - + @@ -119,6 +119,6 @@ device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceIPTunnel.html b/docs/libnm/html/NMDeviceIPTunnel.html index b4fa295..fcc97c2 100644 --- a/docs/libnm/html/NMDeviceIPTunnel.html +++ b/docs/libnm/html/NMDeviceIPTunnel.html @@ -8,7 +8,7 @@ - + @@ -220,7 +220,7 @@ nm_device_ip_tunnel_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -590,6 +590,6 @@ nm_device_ip_tunnel_get_flags ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceInfiniband.html b/docs/libnm/html/NMDeviceInfiniband.html index 6e035cc..2be02c0 100644 --- a/docs/libnm/html/NMDeviceInfiniband.html +++ b/docs/libnm/html/NMDeviceInfiniband.html @@ -8,7 +8,7 @@ - + @@ -155,6 +155,6 @@ nm_device_infiniband_get_carrier (
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceMacsec.html b/docs/libnm/html/NMDeviceMacsec.html index e8b49b9..b5bc353 100644 --- a/docs/libnm/html/NMDeviceMacsec.html +++ b/docs/libnm/html/NMDeviceMacsec.html @@ -8,7 +8,7 @@ - + @@ -247,7 +247,7 @@ nm_device_macsec_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]

Since: 1.6

@@ -676,6 +676,6 @@ nm_device_macsec_get_replay_protect ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceMacvlan.html b/docs/libnm/html/NMDeviceMacvlan.html index 21dd5f5..70be76f 100644 --- a/docs/libnm/html/NMDeviceMacvlan.html +++ b/docs/libnm/html/NMDeviceMacvlan.html @@ -8,7 +8,7 @@ - + @@ -139,7 +139,7 @@ nm_device_macvlan_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -267,6 +267,6 @@ NM_DEPRECATED_IN_1_24_FOR (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceModem.html b/docs/libnm/html/NMDeviceModem.html index af31343..3bc2f5c 100644 --- a/docs/libnm/html/NMDeviceModem.html +++ b/docs/libnm/html/NMDeviceModem.html @@ -8,7 +8,7 @@ - + @@ -291,6 +291,6 @@ nm_device_modem_get_apn ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceOlpcMesh.html b/docs/libnm/html/NMDeviceOlpcMesh.html index d1c9e9e..e005a5b 100644 --- a/docs/libnm/html/NMDeviceOlpcMesh.html +++ b/docs/libnm/html/NMDeviceOlpcMesh.html @@ -8,7 +8,7 @@ - + @@ -148,7 +148,7 @@ nm_device_olpc_mesh_get_companion (

Returns

the companion of the device of NULL.

-

[transfer none]

+

[transfer none]


@@ -201,6 +201,6 @@ nm_device_olpc_mesh_get_active_channel +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceOvsBridge.html b/docs/libnm/html/NMDeviceOvsBridge.html index 3ac38e0..3c5cbc5 100644 --- a/docs/libnm/html/NMDeviceOvsBridge.html +++ b/docs/libnm/html/NMDeviceOvsBridge.html @@ -8,7 +8,7 @@ - + @@ -107,6 +107,6 @@ copy used by the device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceOvsInterface.html b/docs/libnm/html/NMDeviceOvsInterface.html index 407048f..e629de3 100644 --- a/docs/libnm/html/NMDeviceOvsInterface.html +++ b/docs/libnm/html/NMDeviceOvsInterface.html @@ -8,7 +8,7 @@ - + @@ -40,6 +40,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceOvsPort.html b/docs/libnm/html/NMDeviceOvsPort.html index bf62db1..5b479ca 100644 --- a/docs/libnm/html/NMDeviceOvsPort.html +++ b/docs/libnm/html/NMDeviceOvsPort.html @@ -8,7 +8,7 @@ - + @@ -107,6 +107,6 @@ copy used by the device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDevicePpp.html b/docs/libnm/html/NMDevicePpp.html index 8485b5d..0dfb007 100644 --- a/docs/libnm/html/NMDevicePpp.html +++ b/docs/libnm/html/NMDevicePpp.html @@ -8,7 +8,7 @@ - + @@ -40,6 +40,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceTeam.html b/docs/libnm/html/NMDeviceTeam.html index 84a600b..79c161e 100644 --- a/docs/libnm/html/NMDeviceTeam.html +++ b/docs/libnm/html/NMDeviceTeam.html @@ -8,7 +8,7 @@ - + @@ -248,6 +248,6 @@ device, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceTun.html b/docs/libnm/html/NMDeviceTun.html index 2cd9edc..9d41ed9 100644 --- a/docs/libnm/html/NMDeviceTun.html +++ b/docs/libnm/html/NMDeviceTun.html @@ -8,7 +8,7 @@ - + @@ -335,6 +335,6 @@ nm_device_tun_get_multi_queue ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceVeth.html b/docs/libnm/html/NMDeviceVeth.html index d972fb7..dd03515 100644 --- a/docs/libnm/html/NMDeviceVeth.html +++ b/docs/libnm/html/NMDeviceVeth.html @@ -8,7 +8,7 @@ - + @@ -88,7 +88,7 @@ nm_device_veth_get_peer (

Returns

the device's peer device.

-

[transfer none]

+

[transfer none]

Since: 1.30

@@ -103,6 +103,6 @@ nm_device_veth_get_peer ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceVlan.html b/docs/libnm/html/NMDeviceVlan.html index 301fc98..463b421 100644 --- a/docs/libnm/html/NMDeviceVlan.html +++ b/docs/libnm/html/NMDeviceVlan.html @@ -8,7 +8,7 @@ - + @@ -185,7 +185,7 @@ nm_device_vlan_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]


@@ -242,6 +242,6 @@ nm_device_vlan_get_vlan_id ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceVrf.html b/docs/libnm/html/NMDeviceVrf.html index 8681e7f..8eef1ea 100644 --- a/docs/libnm/html/NMDeviceVrf.html +++ b/docs/libnm/html/NMDeviceVrf.html @@ -8,7 +8,7 @@ - + @@ -102,6 +102,6 @@ nm_device_vrf_get_table (
-
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceVxlan.html b/docs/libnm/html/NMDeviceVxlan.html index 62c1fba..745d07a 100644 --- a/docs/libnm/html/NMDeviceVxlan.html +++ b/docs/libnm/html/NMDeviceVxlan.html @@ -8,7 +8,7 @@ - + @@ -328,7 +328,7 @@ nm_device_vxlan_get_parent (

Returns

the device's parent device.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -837,6 +837,6 @@ nm_device_vxlan_get_l3miss ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceWifi.html b/docs/libnm/html/NMDeviceWifi.html index 3965254..b889034 100644 --- a/docs/libnm/html/NMDeviceWifi.html +++ b/docs/libnm/html/NMDeviceWifi.html @@ -8,7 +8,7 @@ - + @@ -361,7 +361,7 @@ nm_device_wifi_get_active_access_point

Returns

the access point or NULL if none is active.

-

[transfer none]

+

[transfer none]


@@ -397,7 +397,7 @@ nm_device_wifi_get_access_point_by_path

Returns

the access point or NULL if none is found.

-

[transfer none]

+

[transfer none]


@@ -775,6 +775,6 @@ set.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceWifiP2P.html b/docs/libnm/html/NMDeviceWifiP2P.html index 3ccd1f7..e40f069 100644 --- a/docs/libnm/html/NMDeviceWifiP2P.html +++ b/docs/libnm/html/NMDeviceWifiP2P.html @@ -8,7 +8,7 @@ - + @@ -163,7 +163,7 @@ nm_device_wifi_p2p_get_peer_by_path (

Returns

the peer or NULL if none is found.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -402,6 +402,6 @@ nm_device_wifi_p2p_stop_find_finish ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceWimax.html b/docs/libnm/html/NMDeviceWimax.html index 7104429..dea41ff 100644 --- a/docs/libnm/html/NMDeviceWimax.html +++ b/docs/libnm/html/NMDeviceWimax.html @@ -8,7 +8,7 @@ - + @@ -217,7 +217,7 @@ nm_device_wimax_get_active_nsp (

Returns

the access point or NULL if none is active.

-

[transfer full]

+

[transfer full]


@@ -256,7 +256,7 @@ nm_device_wimax_get_nsp_by_path (

Returns

the access point or NULL if none is found.

-

[transfer none]

+

[transfer none]


@@ -504,6 +504,6 @@ nm_device_wimax_get_bsid ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceWireGuard.html b/docs/libnm/html/NMDeviceWireGuard.html index 8e87bc7..f31103f 100644 --- a/docs/libnm/html/NMDeviceWireGuard.html +++ b/docs/libnm/html/NMDeviceWireGuard.html @@ -8,7 +8,7 @@ - + @@ -117,7 +117,7 @@ nm_device_wireguard_get_public_key (

Returns

the GBytes containing the 32-byte public key.

-

[transfer none]

+

[transfer none]

Since: 1.14

@@ -200,6 +200,6 @@ See: ip-rule(8)

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDeviceWpan.html b/docs/libnm/html/NMDeviceWpan.html index 080a6dd..5455ddf 100644 --- a/docs/libnm/html/NMDeviceWpan.html +++ b/docs/libnm/html/NMDeviceWpan.html @@ -8,7 +8,7 @@ - + @@ -83,6 +83,6 @@ NM_DEPRECATED_IN_1_24_FOR (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMDhcpConfig.html b/docs/libnm/html/NMDhcpConfig.html index f4cc1bd..d0b5774 100644 --- a/docs/libnm/html/NMDhcpConfig.html +++ b/docs/libnm/html/NMDhcpConfig.html @@ -8,7 +8,7 @@ - + @@ -141,7 +141,7 @@ nm_dhcp_config_get_options (GHashTable containing strings for keys and values. This is the internal copy used by the configuration, and must not be modified.

-

[transfer none][element-type utf8 utf8]

+

[transfer none][element-type utf8 utf8]


@@ -196,6 +196,6 @@ configuration, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMIPConfig.html b/docs/libnm/html/NMIPConfig.html index b3d4adb..2c9c22b 100644 --- a/docs/libnm/html/NMIPConfig.html +++ b/docs/libnm/html/NMIPConfig.html @@ -8,7 +8,7 @@ - + @@ -199,7 +199,7 @@ nm_ip_config_get_gateway (

Returns

the IP address of the gateway.

-

[transfer none]

+

[transfer none]


@@ -229,7 +229,7 @@ nm_ip_config_get_addresses (NMIPAddresses. This is the internal copy used by the configuration and must not be modified. The library never modifies the returned array and thus it is safe for callers to reference and keep using it.

-

[element-type NMIPAddress][transfer none]

+

[element-type NMIPAddress][transfer none]


@@ -259,7 +259,7 @@ nm_ip_config_get_routes (NMIPRoutes. This is the internal copy used by the configuration, and must not be modified. The library never modifies the returned array and thus it is safe for callers to reference and keep using it.

-

[element-type NMIPRoute][transfer none]

+

[element-type NMIPRoute][transfer none]


@@ -286,7 +286,7 @@ nm_ip_config_get_nameservers (

Returns

the array of nameserver IP addresses.

-

[transfer none]

+

[transfer none]


@@ -314,7 +314,7 @@ nm_ip_config_get_domains (

Returns

the array of domains. (This is never NULL, though it may be 0-length).

-

[transfer none]

+

[transfer none]


@@ -342,7 +342,7 @@ nm_ip_config_get_searches (

Returns

the array of DNS search strings. (This is never NULL, though it may be 0-length).

-

[transfer none]

+

[transfer none]


@@ -370,7 +370,7 @@ nm_ip_config_get_wins_servers (

Returns

the arry of WINS server IP address strings. (This is never NULL, though it may be 0-length.).

-

[transfer none]

+

[transfer none]

@@ -426,6 +426,6 @@ nm_ip_config_get_wins_servers ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMObject.html b/docs/libnm/html/NMObject.html index 32699b7..b022de3 100644 --- a/docs/libnm/html/NMObject.html +++ b/docs/libnm/html/NMObject.html @@ -8,7 +8,7 @@ - + @@ -133,7 +133,7 @@ object is still alive.

the NMClient cache in which the object can be found, or NULL if the object is no longer cached.

-

[transfer none]

+

[transfer none]

Since: 1.24

@@ -148,6 +148,6 @@ cached.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMRemoteConnection.html b/docs/libnm/html/NMRemoteConnection.html index 84980e9..f589f9e 100644 --- a/docs/libnm/html/NMRemoteConnection.html +++ b/docs/libnm/html/NMRemoteConnection.html @@ -8,7 +8,7 @@ - + @@ -327,7 +327,7 @@ nm_remote_connection_update2_finish (

Returns

on success, a GVariant of type "a{sv}" with the result. On failure, NULL.

-

[transfer full]

+

[transfer full]


@@ -793,7 +793,7 @@ not a simple property accessor.

a GVariant of type NM_VARIANT_TYPE_CONNECTION containing connection 's secrets, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -887,7 +887,7 @@ nm_remote_connection_get_secrets_finish

a GVariant of type NM_VARIANT_TYPE_CONNECTION containing connection 's secrets, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -1044,6 +1044,6 @@ user, -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSecretAgentOld.html b/docs/libnm/html/NMSecretAgentOld.html index e5405a7..7ca3840 100644 --- a/docs/libnm/html/NMSecretAgentOld.html +++ b/docs/libnm/html/NMSecretAgentOld.html @@ -8,7 +8,7 @@ - + @@ -365,7 +365,7 @@ something like this:

note that this object will be unrefed after the callback has returned, use
g_object_ref()/g_object_unref() if you want to use this object after the callback has returned.

-[transfer none] +[transfer none]

secrets

@@ -422,7 +422,7 @@ should be called.

note that this object will be unrefed after the callback has returned, use g_object_ref()/g_object_unref() if you want to use this object after the callback has returned.

-[transfer none] +[transfer none]

error

@@ -469,7 +469,7 @@ should be called.

note that this object will be unrefed after the callback has returned, use g_object_ref()/g_object_unref() if you want to use this object after the callback has returned.

-[transfer none] +[transfer none]

error

@@ -511,7 +511,7 @@ nm_secret_agent_old_get_dbus_connection

the GDBusConnection used by the secret agent. You may either set this as construct property NM_SECRET_AGENT_OLD_DBUS_CONNECTION, or it will automatically set during initialization.

-

[transfer none]

+

[transfer none]

Since: 1.24

@@ -540,7 +540,7 @@ nm_secret_agent_old_get_main_context (GMainContext instance associate with the instance. This is the g_main_context_get_thread_default() at the time when creating the instance.

-

[transfer none]

+

[transfer none]

Since: 1.24

@@ -579,7 +579,7 @@ that all remains are cleaned up.

a GObject that you may register a weak pointer to know that the GMainContext is still kept busy by self .

-

[transfer none]

+

[transfer none]

Since: 1.24

@@ -1152,6 +1152,6 @@ of 3 characters. An example valid identifier is 'org.gnome.nm-applet' +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSetting.html b/docs/libnm/html/NMSetting.html index 3ca80cc..5c00d03 100644 --- a/docs/libnm/html/NMSetting.html +++ b/docs/libnm/html/NMSetting.html @@ -8,7 +8,7 @@ - + @@ -513,7 +513,7 @@ nm_setting_duplicate (

Returns

a new NMSetting containing the same properties and values as the source NMSetting.

-

[transfer full]

+

[transfer full]


@@ -732,7 +732,7 @@ settings differ, on return a hash table mapping the differing keys to one or more NMSettingDiffResult values OR-ed together. If the settings do not differ, any hash table passed in is unmodified. If no hash table is passed in and the settings differ, a new one is created and returned.

-[inout][transfer full][element-type utf8 guint32] +[inout][transfer full][element-type utf8 guint32] @@ -939,7 +939,7 @@ nm_setting_option_get (

Returns

the GVariant or NULL if the option is not set.

-

[transfer none]

+

[transfer none]

Since: 1.26.

@@ -1170,7 +1170,7 @@ nm_setting_option_get_all_names (A NULL terminated array of key names. If no names are present, this returns NULL. The returned array and the names are owned by NMSetting and might be invalidated by the next operation.

-

[array length=out_len zero-terminated=1][transfer none]

+

[array length=out_len zero-terminated=1][transfer none]

Since: 1.26

@@ -1551,6 +1551,6 @@ example "ppp" or "802-11-wireless" or "802-3-ethernet".

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSetting6Lowpan.html b/docs/libnm/html/NMSetting6Lowpan.html index a00b307..f179af2 100644 --- a/docs/libnm/html/NMSetting6Lowpan.html +++ b/docs/libnm/html/NMSetting6Lowpan.html @@ -8,7 +8,7 @@ - + @@ -91,7 +91,7 @@ nm_setting_6lowpan_new (void

Returns

the new empty NMSetting6Lowpan object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -138,6 +138,6 @@ nm_setting_6lowpan_get_parent ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSetting8021x.html b/docs/libnm/html/NMSetting8021x.html index 93dec00..f9f0c30 100644 --- a/docs/libnm/html/NMSetting8021x.html +++ b/docs/libnm/html/NMSetting8021x.html @@ -8,7 +8,7 @@ - + @@ -1742,7 +1742,7 @@ of the network cannot be confirmed by the client.

Returns

the CA certificate data.

-

[transfer none]

+

[transfer none]


@@ -2257,7 +2257,7 @@ authentication method.

Returns

the client certificate data.

-

[transfer none]

+

[transfer none]


@@ -2640,7 +2640,7 @@ of the network cannot be confirmed by the client.

Returns

the "phase 2" CA certificate data.

-

[transfer none]

+

[transfer none]


@@ -3163,7 +3163,7 @@ authentication method.

Returns

the "phase 2" client certificate data.

-

[transfer none]

+

[transfer none]


@@ -3426,7 +3426,7 @@ nm_setting_802_1x_get_password_raw (the password used by the authentication method as a UTF-8-encoded array of bytes, as specified by the “password-raw” property.

-

[transfer none]

+

[transfer none]


@@ -3571,7 +3571,7 @@ keys should always be encrypted with a private key password.

Returns

the private key data.

-

[transfer none]

+

[transfer none]


@@ -3867,7 +3867,7 @@ keys should always be encrypted with a private key password.

Returns

the "phase 2" private key data.

-

[transfer none]

+

[transfer none]


@@ -5284,6 +5284,6 @@ properties instead (sets ca_cert/ca_cert2 options for wpa_supplicant).

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingAdsl.html b/docs/libnm/html/NMSettingAdsl.html index b2efd9e..32f07ac 100644 --- a/docs/libnm/html/NMSettingAdsl.html +++ b/docs/libnm/html/NMSettingAdsl.html @@ -8,7 +8,7 @@ - + @@ -576,6 +576,6 @@ nm_setting_adsl_get_password_flags ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingBluetooth.html b/docs/libnm/html/NMSettingBluetooth.html index 234dd5b..5ef3cc4 100644 --- a/docs/libnm/html/NMSettingBluetooth.html +++ b/docs/libnm/html/NMSettingBluetooth.html @@ -8,7 +8,7 @@ - + @@ -153,7 +153,7 @@ nm_setting_bluetooth_new (void

Returns

the new empty NMSettingBluetooth object.

-

[transfer full]

+

[transfer full]


@@ -286,6 +286,6 @@ Area Networking connections to devices supporting the NAP profile.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingBond.html b/docs/libnm/html/NMSettingBond.html index 980f930..7dcaa60 100644 --- a/docs/libnm/html/NMSettingBond.html +++ b/docs/libnm/html/NMSettingBond.html @@ -8,7 +8,7 @@ - + @@ -289,7 +289,7 @@ nm_setting_bond_new (void<

Returns

the new empty NMSettingBond object.

-

[transfer full]

+

[transfer full]


@@ -357,14 +357,14 @@ such as during option iteration.

out_name

on return, the name of the bonding option; this value is owned by the setting and should not be modified.

-[out][transfer none] +[out][transfer none]

out_value

on return, the value of the name of the bonding option; this value is owned by the setting and should not be modified.

-[out][transfer none] +[out][transfer none] @@ -568,7 +568,7 @@ nm_setting_bond_get_valid_options (

Returns

a NULL-terminated array of strings of valid bond options.

-

[transfer none]

+

[transfer none]


@@ -835,6 +835,6 @@ to the connection.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingBridge.html b/docs/libnm/html/NMSettingBridge.html index 645a338..3171089 100644 --- a/docs/libnm/html/NMSettingBridge.html +++ b/docs/libnm/html/NMSettingBridge.html @@ -8,7 +8,7 @@ - + @@ -567,7 +567,7 @@ nm_setting_bridge_new (void

Returns

the new empty NMSettingBridge object.

-

[transfer full]

+

[transfer full]


@@ -592,7 +592,7 @@ nm_setting_bridge_get_mac_address (

Returns

-

the “mac-address” property of the setting

+

the “mac-address” property of the setting


@@ -617,7 +617,7 @@ nm_setting_bridge_get_stp (

Returns

-

the “stp” property of the setting

+

the “stp” property of the setting


@@ -642,7 +642,7 @@ nm_setting_bridge_get_priority (

Returns

-

the “priority” property of the setting

+

the “priority” property of the setting


@@ -667,7 +667,7 @@ nm_setting_bridge_get_forward_delay (

Returns

-

the “forward-delay” property of the setting

+

the “forward-delay” property of the setting


@@ -692,7 +692,7 @@ nm_setting_bridge_get_hello_time (

Returns

-

the “hello-time” property of the setting

+

the “hello-time” property of the setting


@@ -717,7 +717,7 @@ nm_setting_bridge_get_max_age (

Returns

-

the “max-age” property of the setting

+

the “max-age” property of the setting


@@ -742,7 +742,7 @@ nm_setting_bridge_get_ageing_time (

Returns

-

the “ageing-time” property of the setting

+

the “ageing-time” property of the setting


@@ -942,7 +942,7 @@ nm_setting_bridge_get_vlan (

Returns

the VLAN at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -1086,7 +1086,7 @@ and 4094.

Returns

the new NMBridgeVlan object.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -1167,7 +1167,7 @@ nm_bridge_vlan_new_clone (const a clone of vlan . This instance is always unsealed.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -2081,6 +2081,6 @@ nm_setting_bridge_get_multicast_startup_query_interval +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingBridgePort.html b/docs/libnm/html/NMSettingBridgePort.html index dc7d9e7..5e13ff1 100644 --- a/docs/libnm/html/NMSettingBridgePort.html +++ b/docs/libnm/html/NMSettingBridgePort.html @@ -8,7 +8,7 @@ - + @@ -214,7 +214,7 @@ nm_setting_bridge_port_new (void<

Returns

the new empty NMSettingBridgePort object.

-

[transfer full]

+

[transfer full]


@@ -383,7 +383,7 @@ nm_setting_bridge_port_get_vlan (

Returns

the VLAN at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -583,6 +583,6 @@ range, represented as a couple of ids separated by a dash.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingCdma.html b/docs/libnm/html/NMSettingCdma.html index 1b846de..29dc840 100644 --- a/docs/libnm/html/NMSettingCdma.html +++ b/docs/libnm/html/NMSettingCdma.html @@ -8,7 +8,7 @@ - + @@ -420,6 +420,6 @@ username is required, it is specified here.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingConnection.html b/docs/libnm/html/NMSettingConnection.html index 9a77adb..35d8afb 100644 --- a/docs/libnm/html/NMSettingConnection.html +++ b/docs/libnm/html/NMSettingConnection.html @@ -8,7 +8,7 @@ - + @@ -2414,7 +2414,7 @@ provider, or nm_utils_uuid_generate() or -nm_utils_uuid_generate_from_string().

+nm_utils_uuid_generate_from_string().

Owner: NMSettingConnection

Flags: Read / Write

Default value: NULL

@@ -2454,6 +2454,6 @@ the change takes effect immediately.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingDcb.html b/docs/libnm/html/NMSettingDcb.html index 1f045b1..e2facee 100644 --- a/docs/libnm/html/NMSettingDcb.html +++ b/docs/libnm/html/NMSettingDcb.html @@ -8,7 +8,7 @@ - + @@ -431,7 +431,7 @@ nm_setting_dcb_new (void

Returns

the new empty NMSettingDcb object.

-

[transfer full]

+

[transfer full]


@@ -1418,6 +1418,6 @@ which the priority is mapped.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingDummy.html b/docs/libnm/html/NMSettingDummy.html index 7e66dd4..49d0609 100644 --- a/docs/libnm/html/NMSettingDummy.html +++ b/docs/libnm/html/NMSettingDummy.html @@ -8,7 +8,7 @@ - + @@ -90,7 +90,7 @@ nm_setting_dummy_new (void

Returns

the new empty NMSettingDummy object.

-

[transfer full]

+

[transfer full]

Since: 1.8

@@ -111,6 +111,6 @@ nm_setting_dummy_new (void +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingEthtool.html b/docs/libnm/html/NMSettingEthtool.html index c2e2f43..0651e06 100644 --- a/docs/libnm/html/NMSettingEthtool.html +++ b/docs/libnm/html/NMSettingEthtool.html @@ -8,7 +8,7 @@ - + @@ -211,7 +211,7 @@ nm_setting_ethtool_new (void

Returns

the new empty NMSettingEthtool object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -257,7 +257,7 @@ names or setting
and may get invalidated when setting gets modified.

-

[array zero-terminated=1][transfer container]

+

[array zero-terminated=1][transfer container]

Since: 1.20

@@ -388,6 +388,6 @@ nm_setting_ethtool_clear_features (
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingGeneric.html b/docs/libnm/html/NMSettingGeneric.html index 17224ab..c0c4d7f 100644 --- a/docs/libnm/html/NMSettingGeneric.html +++ b/docs/libnm/html/NMSettingGeneric.html @@ -8,7 +8,7 @@ - + @@ -81,7 +81,7 @@ optional properties that apply to "generic" devices (ie, devices that NetworkManager does not specifically recognize).

There are currently no properties on this object; it exists only to be -the "connection type" setting on NMConnections for generic devices.

+the "connection type" setting on
NMConnections for generic devices.

Functions

@@ -93,7 +93,7 @@ nm_setting_generic_new (void

Returns

the new empty NMSettingGeneric object.

-

[transfer full]

+

[transfer full]

@@ -113,6 +113,6 @@ nm_setting_generic_new (void +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingGsm.html b/docs/libnm/html/NMSettingGsm.html index 652b6a4..ae3c093 100644 --- a/docs/libnm/html/NMSettingGsm.html +++ b/docs/libnm/html/NMSettingGsm.html @@ -8,7 +8,7 @@ - + @@ -971,6 +971,6 @@ username is required, it is specified here.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingHostname.html b/docs/libnm/html/NMSettingHostname.html index 9a660e9..5ad796a 100644 --- a/docs/libnm/html/NMSettingHostname.html +++ b/docs/libnm/html/NMSettingHostname.html @@ -8,7 +8,7 @@ - + @@ -131,7 +131,7 @@ nm_setting_hostname_new (void

Returns

the new empty NMSettingHostname object.

-

[transfer full]

+

[transfer full]

Since: 1.30

@@ -284,6 +284,6 @@ property.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingIP4Config.html b/docs/libnm/html/NMSettingIP4Config.html index 328d05b..491abbf 100644 --- a/docs/libnm/html/NMSettingIP4Config.html +++ b/docs/libnm/html/NMSettingIP4Config.html @@ -8,7 +8,7 @@ - + @@ -191,7 +191,7 @@ nm_setting_ip4_config_new (void

Returns

the new empty NMSettingIP4Config object.

-

[transfer full]

+

[transfer full]


@@ -421,6 +421,6 @@ If still unspecified, the DHCP option is not sent to the server.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingIP6Config.html b/docs/libnm/html/NMSettingIP6Config.html index b343cb7..95fa1a7 100644 --- a/docs/libnm/html/NMSettingIP6Config.html +++ b/docs/libnm/html/NMSettingIP6Config.html @@ -8,7 +8,7 @@ - + @@ -243,7 +243,7 @@ nm_setting_ip6_config_new (void

Returns

the new empty NMSettingIP6Config object.

-

[transfer full]

+

[transfer full]


@@ -694,6 +694,6 @@ IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingIPConfig.html b/docs/libnm/html/NMSettingIPConfig.html index 5a9a903..b2b0b8e 100644 --- a/docs/libnm/html/NMSettingIPConfig.html +++ b/docs/libnm/html/NMSettingIPConfig.html @@ -8,7 +8,7 @@ - + @@ -1613,7 +1613,7 @@ nm_ip_address_new (int fam

Returns

the new NMIPAddress object, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -1664,7 +1664,7 @@ correct size for family

Returns

the new NMIPAddress object, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -1820,7 +1820,7 @@ nm_ip_address_dup (

Returns

a copy of address .

-

[transfer full]

+

[transfer full]


@@ -2060,7 +2060,7 @@ nm_ip_address_get_attribute_names (

Returns

a NULL-terminated array of attribute names,.

-

[transfer full]

+

[transfer full]


@@ -2101,7 +2101,7 @@ on address , or NULL if address has no such attribute.

-

[transfer none]

+

[transfer none]


@@ -2135,7 +2135,7 @@ nm_ip_address_set_attribute (

value

the value.

-[transfer none][allow-none] +[transfer none][allow-none] @@ -2198,7 +2198,7 @@ nm_ip_route_new (int famil

Returns

the new NMIPRoute object, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -2262,7 +2262,7 @@ point to buffers of the correct size for family

Returns

the new NMIPRoute object, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -2417,7 +2417,7 @@ nm_ip_route_dup (

Returns

a copy of route .

-

[transfer full]

+

[transfer full]


@@ -2852,7 +2852,7 @@ nm_ip_route_get_attribute_names (

Returns

a NULL-terminated array of attribute names.

-

[transfer full]

+

[transfer full]


@@ -2893,7 +2893,7 @@ on route , or NULL if route has no such attribute.

-

[transfer none]

+

[transfer none]


@@ -2927,7 +2927,7 @@ nm_ip_route_set_attribute (

value

the value.

-[transfer none][allow-none] +[transfer none][allow-none] @@ -3024,7 +3024,7 @@ nm_ip_routing_rule_new (int

Returns

a newly created rule instance with the provided address family. The instance is unsealed.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -3054,7 +3054,7 @@ nm_ip_routing_rule_new_clone (const rule . Note that the instance will always be unsealred.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -3085,7 +3085,7 @@ This is not thread-safe.

the self argument with incremented reference count.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -3741,7 +3741,7 @@ nm_ip_routing_rule_get_from (const

Returns

the set from/src parameter or NULL, if no value is set.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -3834,7 +3834,7 @@ nm_ip_routing_rule_get_to (const

Returns

the set to/dst parameter or NULL, if no value is set.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -3902,7 +3902,7 @@ nm_ip_routing_rule_get_iifname (const

Returns

the set iifname or NULL if unset.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -3963,7 +3963,7 @@ nm_ip_routing_rule_get_oifname (const

Returns

the set oifname or NULL if unset.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -4288,7 +4288,7 @@ conversion. Currently, not extra arguments are supported.

Returns

the new NMIPRoutingRule or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -4337,7 +4337,7 @@ conversion. Currently, not extra arguments are supported.

Returns

the string representation or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.18

@@ -5051,7 +5051,7 @@ nm_setting_ip_config_get_address (

Returns

the address at index idx .

-

[transfer none]

+

[transfer none]


@@ -5262,7 +5262,7 @@ nm_setting_ip_config_get_route (

Returns

the route at index idx .

-

[transfer none]

+

[transfer none]


@@ -5512,7 +5512,7 @@ nm_setting_ip_config_get_routing_rule (

Returns

the routing rule at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -5926,7 +5926,7 @@ nm_setting_ip_config_get_dhcp_reject_servers

Returns

A NULL terminated array of DHCP reject servers. Even if no reject servers are configured, this always returns a non NULL value.

-

[array length=out_len zero-terminated=1][transfer none]

+

[array length=out_len zero-terminated=1][transfer none]

Since: 1.28

@@ -6895,6 +6895,6 @@ of NetworkManager.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingIPTunnel.html b/docs/libnm/html/NMSettingIPTunnel.html index 6093e7d..b81a7a3 100644 --- a/docs/libnm/html/NMSettingIPTunnel.html +++ b/docs/libnm/html/NMSettingIPTunnel.html @@ -8,7 +8,7 @@ - + @@ -330,7 +330,7 @@ nm_setting_ip_tunnel_new (void

Returns

the new empty NMSettingIPTunnel object.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -964,6 +964,6 @@ packets inherit the TTL value.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingInfiniband.html b/docs/libnm/html/NMSettingInfiniband.html index 3df145c..576342e 100644 --- a/docs/libnm/html/NMSettingInfiniband.html +++ b/docs/libnm/html/NMSettingInfiniband.html @@ -8,7 +8,7 @@ - + @@ -197,7 +197,7 @@ nm_setting_infiniband_new (void

Returns

the new empty NMSettingInfiniband object.

-

[transfer full]

+

[transfer full]


@@ -466,6 +466,6 @@ specify the base device by setting either this property or +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingMacsec.html b/docs/libnm/html/NMSettingMacsec.html index 0091396..66325c6 100644 --- a/docs/libnm/html/NMSettingMacsec.html +++ b/docs/libnm/html/NMSettingMacsec.html @@ -8,7 +8,7 @@ - + @@ -277,7 +277,7 @@ nm_setting_macsec_new (void

Returns

the new empty NMSettingMacsec object.

-

[transfer full]

+

[transfer full]

Since: 1.6

@@ -773,6 +773,6 @@ in every packet.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingMacvlan.html b/docs/libnm/html/NMSettingMacvlan.html index 9821d08..8659048 100644 --- a/docs/libnm/html/NMSettingMacvlan.html +++ b/docs/libnm/html/NMSettingMacvlan.html @@ -8,7 +8,7 @@ - + @@ -179,7 +179,7 @@ nm_setting_macvlan_new (void

Returns

the new empty NMSettingMacvlan object.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -442,6 +442,6 @@ with a +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingMatch.html b/docs/libnm/html/NMSettingMatch.html index f47dd2c..5fe5160 100644 --- a/docs/libnm/html/NMSettingMatch.html +++ b/docs/libnm/html/NMSettingMatch.html @@ -8,7 +8,7 @@ - + @@ -315,7 +315,7 @@ nm_setting_match_new (void

Returns

the new empty NMSettingMatch object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -540,7 +540,7 @@ nm_setting_match_get_interface_names (NULL terminated and you MUST provide a length.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.14

@@ -766,7 +766,7 @@ nm_setting_match_get_kernel_command_lines

Returns

the configured interface names.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.26

@@ -986,7 +986,7 @@ nm_setting_match_get_drivers (

Returns

the configured drivers.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.26

@@ -1205,7 +1205,7 @@ nm_setting_match_get_paths (

Returns

the configured paths.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.26

@@ -1244,6 +1244,6 @@ nm_setting_match_get_paths ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOlpcMesh.html b/docs/libnm/html/NMSettingOlpcMesh.html index 4e383fc..a129051 100644 --- a/docs/libnm/html/NMSettingOlpcMesh.html +++ b/docs/libnm/html/NMSettingOlpcMesh.html @@ -8,7 +8,7 @@ - + @@ -182,7 +182,7 @@ nm_setting_olpc_mesh_get_ssid (

Returns

.

-

[transfer none]

+

[transfer none]


@@ -263,6 +263,6 @@ answers the request.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsBridge.html b/docs/libnm/html/NMSettingOvsBridge.html index 1533db0..8064160 100644 --- a/docs/libnm/html/NMSettingOvsBridge.html +++ b/docs/libnm/html/NMSettingOvsBridge.html @@ -8,7 +8,7 @@ - + @@ -138,7 +138,7 @@ nm_setting_ovs_bridge_new (void

Returns

the new empty NMSettingOvsBridge object.

-

[transfer full]

+

[transfer full]

Since: 1.10

@@ -315,6 +315,6 @@ nm_setting_ovs_bridge_get_datapath_type +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsDpdk.html b/docs/libnm/html/NMSettingOvsDpdk.html index 8ec3b30..0051798 100644 --- a/docs/libnm/html/NMSettingOvsDpdk.html +++ b/docs/libnm/html/NMSettingOvsDpdk.html @@ -8,7 +8,7 @@ - + @@ -91,7 +91,7 @@ nm_setting_ovs_dpdk_new (void

Returns

the new empty NMSettingOvsDpdk object.

-

[transfer full]

+

[transfer full]

Since: 1.20

@@ -138,6 +138,6 @@ nm_setting_ovs_dpdk_get_devargs ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsExternalIDs.html b/docs/libnm/html/NMSettingOvsExternalIDs.html index de9579f..9a04d3d 100644 --- a/docs/libnm/html/NMSettingOvsExternalIDs.html +++ b/docs/libnm/html/NMSettingOvsExternalIDs.html @@ -8,7 +8,7 @@ - + @@ -123,7 +123,7 @@ nm_setting_ovs_external_ids_new (

Returns

the new empty NMSettingOvsExternalIDs object.

-

[transfer full][type NMSettingOvsExternalIDs]

+

[transfer full][type NMSettingOvsExternalIDs]

Since: 1.30

@@ -160,7 +160,7 @@ nm_setting_ovs_external_ids_get_data_keys

Returns

a NULL-terminated array containing each key from the table.

-

[array length=out_len][transfer none]

+

[array length=out_len][transfer none]


@@ -196,7 +196,7 @@ nm_setting_ovs_external_ids_get_data (key or NULL if no such value exists.

-

[transfer none]

+

[transfer none]

Since: 1.30

@@ -331,6 +331,6 @@ is a valid user data value.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsInterface.html b/docs/libnm/html/NMSettingOvsInterface.html index 6b673a0..6499cf9 100644 --- a/docs/libnm/html/NMSettingOvsInterface.html +++ b/docs/libnm/html/NMSettingOvsInterface.html @@ -8,7 +8,7 @@ - + @@ -91,7 +91,7 @@ nm_setting_ovs_interface_new (voi

Returns

the new empty NMSettingOvsInterface object.

-

[transfer full]

+

[transfer full]

Since: 1.10

@@ -139,6 +139,6 @@ nm_setting_ovs_interface_get_interface_type +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsPatch.html b/docs/libnm/html/NMSettingOvsPatch.html index 3bdab4a..5ed7cc1 100644 --- a/docs/libnm/html/NMSettingOvsPatch.html +++ b/docs/libnm/html/NMSettingOvsPatch.html @@ -8,7 +8,7 @@ - + @@ -91,7 +91,7 @@ nm_setting_ovs_patch_new (void

Returns

the new empty NMSettingOvsPatch object.

-

[transfer full]

+

[transfer full]

Since: 1.10

@@ -138,6 +138,6 @@ nm_setting_ovs_patch_get_peer ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingOvsPort.html b/docs/libnm/html/NMSettingOvsPort.html index ea6dcae..b3784ac 100644 --- a/docs/libnm/html/NMSettingOvsPort.html +++ b/docs/libnm/html/NMSettingOvsPort.html @@ -8,7 +8,7 @@ - + @@ -149,7 +149,7 @@ nm_setting_ovs_port_new (void

Returns

the new empty NMSettingOvsPort object.

-

[transfer full]

+

[transfer full]

Since: 1.10

@@ -357,6 +357,6 @@ nm_setting_ovs_port_get_bond_downdelay +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingPpp.html b/docs/libnm/html/NMSettingPpp.html index 614a4c7..64fe6bb 100644 --- a/docs/libnm/html/NMSettingPpp.html +++ b/docs/libnm/html/NMSettingPpp.html @@ -8,7 +8,7 @@ - + @@ -413,7 +413,7 @@ nm_setting_ppp_new (void

Returns

the new empty NMSettingPpp object.

-

[transfer full]

+

[transfer full]


@@ -1177,6 +1177,6 @@ be set to -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingPppoe.html b/docs/libnm/html/NMSettingPppoe.html index 28e9a59..174f491 100644 --- a/docs/libnm/html/NMSettingPppoe.html +++ b/docs/libnm/html/NMSettingPppoe.html @@ -8,7 +8,7 @@ - + @@ -191,7 +191,7 @@ nm_setting_pppoe_new (void

Returns

the new empty NMSettingPppoe object.

-

[transfer full]

+

[transfer full]


@@ -420,6 +420,6 @@ access concentrators or a specific service is known to be required.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingProxy.html b/docs/libnm/html/NMSettingProxy.html index 9191af7..9869929 100644 --- a/docs/libnm/html/NMSettingProxy.html +++ b/docs/libnm/html/NMSettingProxy.html @@ -8,7 +8,7 @@ - + @@ -410,6 +410,6 @@ nm_setting_proxy_get_pac_script ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingSerial.html b/docs/libnm/html/NMSettingSerial.html index 26d5c5b..4b28f16 100644 --- a/docs/libnm/html/NMSettingSerial.html +++ b/docs/libnm/html/NMSettingSerial.html @@ -8,7 +8,7 @@ - + @@ -198,7 +198,7 @@ nm_setting_serial_new (void

Returns

the new empty NMSettingSerial object.

-

[transfer full]

+

[transfer full]


@@ -463,6 +463,6 @@ The 1 in "8n1" for example.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingSriov.html b/docs/libnm/html/NMSettingSriov.html index 10be809..1884421 100644 --- a/docs/libnm/html/NMSettingSriov.html +++ b/docs/libnm/html/NMSettingSriov.html @@ -8,7 +8,7 @@ - + @@ -326,7 +326,7 @@ nm_setting_sriov_new (void

Returns

the new empty NMSettingSriov object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -416,7 +416,7 @@ nm_setting_sriov_get_vf (

Returns

the VF at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.14

@@ -675,7 +675,7 @@ nm_sriov_vf_get_vlan_ids (const

Returns

a list of VLAN ids configured on the VF.

-

[transfer none][array length=length]

+

[transfer none][array length=length]

Since: 1.14

@@ -819,7 +819,7 @@ nm_sriov_vf_new (

Returns

the new NMSriovVF object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -934,7 +934,7 @@ nm_sriov_vf_dup (const

Returns

a copy of vf .

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -996,7 +996,7 @@ nm_sriov_vf_set_attribute (

value

the value.

-[transfer none][allow-none] +[transfer none][allow-none] @@ -1028,7 +1028,7 @@ nm_sriov_vf_get_attribute_names (const

Returns

a NULL-terminated array of attribute names.

-

[transfer container]

+

[transfer container]

Since: 1.14

@@ -1070,7 +1070,7 @@ on vf , or NULL if vf has no such attribute.

-

[transfer none]

+

[transfer none]

Since: 1.14

@@ -1213,6 +1213,6 @@ the value is of the correct type and well-formed.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingTCConfig.html b/docs/libnm/html/NMSettingTCConfig.html index 5e82df0..50bebca 100644 --- a/docs/libnm/html/NMSettingTCConfig.html +++ b/docs/libnm/html/NMSettingTCConfig.html @@ -8,7 +8,7 @@ - + @@ -477,7 +477,7 @@ nm_tc_qdisc_new (const char

Returns

the new NMTCQdisc object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -591,7 +591,7 @@ nm_tc_qdisc_dup (

Returns

a copy of qdisc .

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -726,7 +726,7 @@ nm_tc_qdisc_get_attribute_names (

Returns

a NULL-terminated array of attribute names or NULL if no attributes are set.

-

[transfer container]

+

[transfer container]

Since: 1.18

@@ -768,7 +768,7 @@ on qdisc , or NULL if qdisc has no such attribute.

-

[transfer none]

+

[transfer none]

Since: 1.18

@@ -803,7 +803,7 @@ nm_tc_qdisc_set_attribute (

value

the value.

-[transfer none][allow-none] +[transfer none][allow-none] @@ -842,7 +842,7 @@ nm_tc_action_new (const char

Returns

the new NMTCAction object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -956,7 +956,7 @@ nm_tc_action_dup (

Returns

a copy of action .

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1007,7 +1007,7 @@ nm_tc_action_get_attribute_names (

Returns

a NULL-terminated array of attribute names,.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1049,7 +1049,7 @@ on action , or NULL if action has no such attribute.

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -1084,7 +1084,7 @@ nm_tc_action_set_attribute (

value

the value.

-[transfer none][allow-none] +[transfer none][allow-none] @@ -1129,7 +1129,7 @@ nm_tc_tfilter_new (const char

Returns

the new NMTCTfilter object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1243,7 +1243,7 @@ nm_tc_tfilter_dup (

Returns

a copy of tfilter .

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1418,7 +1418,7 @@ nm_setting_tc_config_new (void

Returns

the new empty NMSettingTCConfig object.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1480,7 +1480,7 @@ nm_setting_tc_config_get_qdisc (

Returns

the qdisc at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -1672,7 +1672,7 @@ nm_setting_tc_config_get_tfilter (

Returns

the tfilter at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -1829,6 +1829,6 @@ nm_setting_tc_config_clear_tfilters ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingTeam.html b/docs/libnm/html/NMSettingTeam.html index 0faf7bf..fe3b44c 100644 --- a/docs/libnm/html/NMSettingTeam.html +++ b/docs/libnm/html/NMSettingTeam.html @@ -8,7 +8,7 @@ - + @@ -724,7 +724,7 @@ consistency with the link_watcher constructors of other type

Returns

the new NMTeamLinkWatcher object.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -779,7 +779,7 @@ target address in the NS packet

Returns

the new NMTeamLinkWatcher object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -847,7 +847,7 @@ address in the arp request

Returns

the new NMTeamLinkWatcher object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -921,7 +921,7 @@ address in the arp request

Returns

the new NMTeamLinkWatcher object, or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.16

@@ -1035,7 +1035,7 @@ nm_team_link_watcher_dup (const

Returns

a copy of watcher .

-

[transfer full]

+

[transfer full]

Since: 1.12

@@ -1283,7 +1283,7 @@ nm_setting_team_new (void<

Returns

the new empty NMSettingTeam object.

-

[transfer full]

+

[transfer full]


@@ -1881,7 +1881,7 @@ nm_setting_team_get_link_watcher (

Returns

the link watcher at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -2488,6 +2488,6 @@ Permitted values are: "roundrobin", "broadcast", "activebackup", +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingTeamPort.html b/docs/libnm/html/NMSettingTeamPort.html index be73107..5ec9a74 100644 --- a/docs/libnm/html/NMSettingTeamPort.html +++ b/docs/libnm/html/NMSettingTeamPort.html @@ -8,7 +8,7 @@ - + @@ -273,7 +273,7 @@ nm_setting_team_port_new (void

Returns

the new empty NMSettingTeamPort object.

-

[transfer full]

+

[transfer full]


@@ -490,7 +490,7 @@ nm_setting_team_port_get_link_watcher (

Returns

the link watcher at index idx .

-

[transfer none]

+

[transfer none]

Since: 1.12

@@ -775,6 +775,6 @@ When set to -1 means the parameter is skipped from the json config.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingTun.html b/docs/libnm/html/NMSettingTun.html index 71ee6d3..5a31dea 100644 --- a/docs/libnm/html/NMSettingTun.html +++ b/docs/libnm/html/NMSettingTun.html @@ -8,7 +8,7 @@ - + @@ -213,7 +213,7 @@ nm_setting_tun_new (void

Returns

the new empty NMSettingTun object.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -536,6 +536,6 @@ network header.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingUser.html b/docs/libnm/html/NMSettingUser.html index 432fbce..d268e29 100644 --- a/docs/libnm/html/NMSettingUser.html +++ b/docs/libnm/html/NMSettingUser.html @@ -8,7 +8,7 @@ - + @@ -156,7 +156,7 @@ nm_setting_user_get_keys (

Returns

a NULL-terminated array containing each key from the table.

-

[array length=out_len][transfer none]

+

[array length=out_len][transfer none]


@@ -192,7 +192,7 @@ nm_setting_user_get_data (key or NULL if no such value exists.

-

[transfer none]

+

[transfer none]

Since: 1.8

@@ -342,6 +342,6 @@ is a valid user data value.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingVeth.html b/docs/libnm/html/NMSettingVeth.html index 405f5e9..dee004a 100644 --- a/docs/libnm/html/NMSettingVeth.html +++ b/docs/libnm/html/NMSettingVeth.html @@ -8,7 +8,7 @@ - + @@ -91,7 +91,7 @@ nm_setting_veth_new (void<

Returns

the new empty NMSettingVeth object.

-

[transfer full]

+

[transfer full]

Since: 1.30

@@ -138,6 +138,6 @@ nm_setting_veth_get_peer ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingVlan.html b/docs/libnm/html/NMSettingVlan.html index 3f32377..f5966c2 100644 --- a/docs/libnm/html/NMSettingVlan.html +++ b/docs/libnm/html/NMSettingVlan.html @@ -8,7 +8,7 @@ - + @@ -254,7 +254,7 @@ nm_setting_vlan_new (void<

Returns

the new empty NMSettingVlan object.

-

[transfer full]

+

[transfer full]


@@ -895,6 +895,6 @@ with a +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingVpn.html b/docs/libnm/html/NMSettingVpn.html index 0e4be1f..7891c7a 100644 --- a/docs/libnm/html/NMSettingVpn.html +++ b/docs/libnm/html/NMSettingVpn.html @@ -8,7 +8,7 @@ - + @@ -334,7 +334,7 @@ nm_setting_vpn_new (void

Returns

the new empty NMSettingVpn object.

-

[transfer full]

+

[transfer full]


@@ -633,7 +633,7 @@ nm_setting_vpn_get_data_keys (NULL-terminated array containing each data key or NULL if there are no data items.

-

[array length=out_length][transfer container]

+

[array length=out_length][transfer container]

Since: 1.12

@@ -855,7 +855,7 @@ nm_setting_vpn_get_secret_keys (a NULL-terminated array containing each secret key or NULL if there are no secrets.

-

[array length=out_length][transfer container]

+

[array length=out_length][transfer container]

Since: 1.12

@@ -1004,6 +1004,6 @@ VPN connection.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingVrf.html b/docs/libnm/html/NMSettingVrf.html index 835923c..2a129b7 100644 --- a/docs/libnm/html/NMSettingVrf.html +++ b/docs/libnm/html/NMSettingVrf.html @@ -8,7 +8,7 @@ - + @@ -92,7 +92,7 @@ nm_setting_vrf_new (void

Returns

the new empty NMSettingVrf object.

-

[transfer full]

+

[transfer full]

Since: 1.24

@@ -139,6 +139,6 @@ nm_setting_vrf_get_table ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingVxlan.html b/docs/libnm/html/NMSettingVxlan.html index b722e1f..4660c43 100644 --- a/docs/libnm/html/NMSettingVxlan.html +++ b/docs/libnm/html/NMSettingVxlan.html @@ -8,7 +8,7 @@ - + @@ -377,7 +377,7 @@ nm_setting_vxlan_new (void

Returns

the new empty NMSettingVxlan object.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -1086,6 +1086,6 @@ tunnel endpoint.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWifiP2P.html b/docs/libnm/html/NMSettingWifiP2P.html index 2a00b2c..8cd438e 100644 --- a/docs/libnm/html/NMSettingWifiP2P.html +++ b/docs/libnm/html/NMSettingWifiP2P.html @@ -8,7 +8,7 @@ - + @@ -115,7 +115,7 @@ nm_setting_wifi_p2p_new (void

Returns

the new empty NMSettingWifiP2P object.

-

[transfer full]

+

[transfer full]

Since: 1.16

@@ -194,7 +194,7 @@ nm_setting_wifi_p2p_get_wfd_ies (

Returns

the “wfd-ies” property of the setting.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -228,6 +228,6 @@ nm_setting_wifi_p2p_get_wfd_ies ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWimax.html b/docs/libnm/html/NMSettingWimax.html index 20eeb57..3832fb7 100644 --- a/docs/libnm/html/NMSettingWimax.html +++ b/docs/libnm/html/NMSettingWimax.html @@ -8,7 +8,7 @@ - + @@ -270,6 +270,6 @@ should use.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWireGuard.html b/docs/libnm/html/NMSettingWireGuard.html index 00d9e98..d143b1b 100644 --- a/docs/libnm/html/NMSettingWireGuard.html +++ b/docs/libnm/html/NMSettingWireGuard.html @@ -8,7 +8,7 @@ - + @@ -453,7 +453,7 @@ nm_wireguard_peer_new (void

Returns

a new, default, unsealed NMWireGuardPeer instance.

-

[transfer full]

+

[transfer full]

Since: 1.16

@@ -491,7 +491,7 @@ as well. Otherwise, they will be removed.

a clone of self . This instance is always unsealed.

-

[transfer full]

+

[transfer full]

Since: 1.16

@@ -626,7 +626,7 @@ nm_wireguard_peer_get_public_key (const

Returns

the public key or NULL if unset.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -658,7 +658,7 @@ will be normalized (which may or may not modify the set value).

public_key

the new public key or NULL to clear the public key.

-[allow-none][transfer none] +[allow-none][transfer none]

accept_invalid

@@ -701,7 +701,7 @@ nm_wireguard_peer_get_preshared_key (const

Returns

the preshared key or NULL if unset.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -738,7 +738,7 @@ be accepted.

preshared_key

the new preshared key or NULL to clear the preshared key.

-[allow-none][transfer none] +[allow-none][transfer none]

accept_invalid

@@ -903,7 +903,7 @@ nm_wireguard_peer_get_endpoint (const

Returns

the endpoint or NULL if none was set.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -1024,7 +1024,7 @@ setting.

. If idx is out of range, NULL will be returned.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -1250,7 +1250,7 @@ nm_setting_wireguard_new (void

Returns

the new empty NMSettingWireGuard object.

-

[transfer full]

+

[transfer full]

Since: 1.16

@@ -1277,7 +1277,7 @@ nm_setting_wireguard_get_private_key (

Returns

the set private-key or NULL.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -1419,7 +1419,7 @@ nm_setting_wireguard_get_peer (the NMWireGuardPeer entry at index idx . If the index is out of range, NULL is returned.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -1465,7 +1465,7 @@ this is set to the

Returns

the NMWireGuardPeer instance with a matching public key. If no such peer exists, NULL is returned.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -1828,6 +1828,6 @@ nm_setting_wireguard_get_ip6_auto_default_route +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWired.html b/docs/libnm/html/NMSettingWired.html index d297663..d0be426 100644 --- a/docs/libnm/html/NMSettingWired.html +++ b/docs/libnm/html/NMSettingWired.html @@ -8,7 +8,7 @@ - + @@ -433,7 +433,7 @@ nm_setting_wired_new (void

Returns

the new empty NMSettingWired object.

-

[transfer full]

+

[transfer full]


@@ -882,7 +882,7 @@ conjunction with that device.

Returns

array of strings, each specifying one subchannel the s390 device uses to communicate to the host.

-

[transfer none][element-type utf8]

+

[transfer none][element-type utf8]


@@ -977,14 +977,14 @@ such as during option iteration.

out_key

on return, the key name of the s390 specific option; this value is owned by the setting and should not be modified.

-[out][transfer none] +[out][transfer none]

out_value

on return, the value of the key of the s390 specific option; this value is owned by the setting and should not be modified.

-[out][transfer none] +[out][transfer none] @@ -1143,7 +1143,7 @@ and you may pass

Returns

a NULL-terminated array of strings of valid s390 options.

-

[transfer none]

+

[transfer none]


@@ -1622,6 +1622,6 @@ no password will be required.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWireless.html b/docs/libnm/html/NMSettingWireless.html index 0ce428a..25462c1 100644 --- a/docs/libnm/html/NMSettingWireless.html +++ b/docs/libnm/html/NMSettingWireless.html @@ -8,7 +8,7 @@ - + @@ -509,7 +509,7 @@ nm_setting_wireless_new (void

Returns

the new empty NMSettingWireless object.

-

[transfer full]

+

[transfer full]


@@ -535,7 +535,7 @@ nm_setting_wireless_get_ssid (

Returns

the “ssid” property of the setting.

-

[transfer none]

+

[transfer none]


@@ -1901,6 +1901,6 @@ NetworkManager).

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWirelessSecurity.html b/docs/libnm/html/NMSettingWirelessSecurity.html index 4e1e517..b15b246 100644 --- a/docs/libnm/html/NMSettingWirelessSecurity.html +++ b/docs/libnm/html/NMSettingWirelessSecurity.html @@ -8,7 +8,7 @@ - + @@ -582,7 +582,7 @@ nm_setting_wireless_security_new (

Returns

the new empty NMSettingWirelessSecurity object.

-

[transfer full]

+

[transfer full]


@@ -2143,6 +2143,6 @@ the Access Point capabilities.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSettingWpan.html b/docs/libnm/html/NMSettingWpan.html index 23ebdac..3c13f38 100644 --- a/docs/libnm/html/NMSettingWpan.html +++ b/docs/libnm/html/NMSettingWpan.html @@ -8,7 +8,7 @@ - + @@ -147,7 +147,7 @@ nm_setting_wpan_new (void<

Returns

the new empty NMSettingWpan object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -334,6 +334,6 @@ nm_setting_wpan_get_channel ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMSimpleConnection.html b/docs/libnm/html/NMSimpleConnection.html index a44a69d..2f07195 100644 --- a/docs/libnm/html/NMSimpleConnection.html +++ b/docs/libnm/html/NMSimpleConnection.html @@ -8,7 +8,7 @@ - + @@ -108,7 +108,7 @@ nm_simple_connection_new (void

Returns

the new empty NMConnection object.

-

[transfer full]

+

[transfer full]


@@ -147,7 +147,7 @@ description of the expected hash table.

the new NMSimpleConnection object, populated with settings created from the values in the hash table, or NULL if the connection failed to normalize.

-

[transfer full]

+

[transfer full]


@@ -175,7 +175,7 @@ nm_simple_connection_new_clone (

Returns

a new NMConnection containing the same settings and properties as the source NMConnection.

-

[transfer full]

+

[transfer full]

@@ -188,6 +188,6 @@ and properties as the source -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnConnection.html b/docs/libnm/html/NMVpnConnection.html index 7f7a18b..812ce21 100644 --- a/docs/libnm/html/NMVpnConnection.html +++ b/docs/libnm/html/NMVpnConnection.html @@ -8,7 +8,7 @@ - + @@ -150,6 +150,6 @@ string used by the connection, and must not be modified.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnEditor.html b/docs/libnm/html/NMVpnEditor.html index e90577b..ce6d9da 100644 --- a/docs/libnm/html/NMVpnEditor.html +++ b/docs/libnm/html/NMVpnEditor.html @@ -8,7 +8,7 @@ - + @@ -98,7 +98,7 @@ nm_vpn_editor_get_widget (

Returns

.

-

[transfer none]

+

[transfer none]


@@ -173,6 +173,6 @@ to write values to the connection.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnEditorPlugin.html b/docs/libnm/html/NMVpnEditorPlugin.html index a3269b8..cd12816 100644 --- a/docs/libnm/html/NMVpnEditorPlugin.html +++ b/docs/libnm/html/NMVpnEditorPlugin.html @@ -8,7 +8,7 @@ - + @@ -325,7 +325,7 @@ nm_vpn_editor_plugin_get_editor (

Returns

a new NMVpnEditor or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -421,7 +421,7 @@ nm_vpn_editor_plugin_import (NULL on error or if the file at path was not recognized by this plugin.

-

[transfer full]

+

[transfer full]


@@ -515,7 +515,7 @@ loading the shared library.

Returns

a new plugin instance or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -567,7 +567,7 @@ the given service.

Returns

a new plugin instance or NULL on error.

-

[transfer full]

+

[transfer full]

Since: 1.4

@@ -594,7 +594,7 @@ nm_vpn_editor_plugin_get_plugin_info (

Returns

if set, return the NMVpnPluginInfo instance.

-

[transfer none]

+

[transfer none]

Since: 1.4

@@ -875,6 +875,6 @@ user_function ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnPluginInfo.html b/docs/libnm/html/NMVpnPluginInfo.html index 65214da..45880d1 100644 --- a/docs/libnm/html/NMVpnPluginInfo.html +++ b/docs/libnm/html/NMVpnPluginInfo.html @@ -8,7 +8,7 @@ - + @@ -398,7 +398,7 @@ must be present.

Returns

a newly created instance of plugin info or NULL if no matching value was found.

-

[transfer full]

+

[transfer full]

Since: 1.4

@@ -425,7 +425,7 @@ nm_vpn_plugin_info_get_name (

Returns

the name. Cannot be NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -452,7 +452,7 @@ nm_vpn_plugin_info_get_filename (

Returns

the filename. Can be NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -479,7 +479,7 @@ nm_vpn_plugin_info_get_service (

Returns

the service. Cannot be NULL.

-

[transfer none]

+

[transfer none]

Since: 1.4

@@ -506,7 +506,7 @@ nm_vpn_plugin_info_get_plugin (

Returns

the plugin. Can be NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -533,7 +533,7 @@ nm_vpn_plugin_info_get_program (

Returns

the program. Can be NULL.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -638,7 +638,7 @@ nm_vpn_plugin_info_get_aliases (

Returns

the aliases from the name-file.

-

[array zero-terminated=1][element-type utf8][transfer none]

+

[array zero-terminated=1][element-type utf8][transfer none]

Since: 1.4

@@ -680,7 +680,7 @@ nm_vpn_plugin_info_lookup_property (

Returns

NMVpnPluginInfo is internally a GKeyFile. Returns the matching property.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -719,7 +719,7 @@ nm_vpn_plugin_info_list_load (voi

Returns

list of plugins loaded from the default directories rejecting duplicates.

-

[element-type NMVpnPluginInfo][transfer full]

+

[element-type NMVpnPluginInfo][transfer full]

Since: 1.2

@@ -836,7 +836,7 @@ nm_vpn_plugin_info_list_find_by_name (

Returns

the first plugin with a matching name (or NULL).

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -873,7 +873,7 @@ nm_vpn_plugin_info_list_find_by_filename

Returns

the first plugin with a matching filename (or NULL).

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -911,7 +911,7 @@ or one of the provided aliases.

Returns

the first plugin with a matching service (or NULL).

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -957,7 +957,7 @@ but it could be retrieved via

Returns

the resolved service-type or NULL on failure.

-

[transfer full]

+

[transfer full]

Since: 1.4

@@ -1005,7 +1005,7 @@ with

Returns

a NULL terminated strv list of strings. The list itself and the values must be freed with g_strfreev().

-

[transfer full]

+

[transfer full]

Since: 1.4

@@ -1032,7 +1032,7 @@ nm_vpn_plugin_info_get_editor_plugin (

Returns

the cached NMVpnEditorPlugin instance.

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -1103,7 +1103,7 @@ and can be later retrieved again via nm_vpn_plugin_info_get_editor_plugin(). You can load the plugin only once, unless you reset the state via nm_vpn_plugin_info_set_editor_plugin().

-

[transfer none]

+

[transfer none]

Since: 1.2

@@ -1148,6 +1148,6 @@ plugin only once, unless you reset the state via +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnPluginOld.html b/docs/libnm/html/NMVpnPluginOld.html index 3b8fed7..07669e2 100644 --- a/docs/libnm/html/NMVpnPluginOld.html +++ b/docs/libnm/html/NMVpnPluginOld.html @@ -8,7 +8,7 @@ - + @@ -268,7 +268,7 @@ nm_vpn_plugin_old_get_connection (

Returns

.

-

[transfer full]

+

[transfer full]


@@ -433,13 +433,13 @@ an applet when the applet calls the authentication dialog of the VPN plugin.

out_data

on successful return, a hash table (mapping char*:char*) containing the key/value pairs of VPN data items.

-[out][transfer full] +[out][transfer full]

out_secrets

on successful return, a hash table (mapping char*:char*) containing the key/value pairsof VPN secrets.

-[out][transfer full] +[out][transfer full] @@ -623,6 +623,6 @@ user_function ( +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMVpnServicePlugin.html b/docs/libnm/html/NMVpnServicePlugin.html index a016cd5..965727a 100644 --- a/docs/libnm/html/NMVpnServicePlugin.html +++ b/docs/libnm/html/NMVpnServicePlugin.html @@ -8,7 +8,7 @@ - + @@ -265,7 +265,7 @@ nm_vpn_service_plugin_get_connection (

Returns

.

-

[transfer full]

+

[transfer full]

Since: 1.2

@@ -411,13 +411,13 @@ an applet when the applet calls the authentication dialog of the VPN plugin.

out_data

on successful return, a hash table (mapping char*:char*) containing the key/value pairs of VPN data items.

-[out][transfer full] +[out][transfer full]

out_secrets

on successful return, a hash table (mapping char*:char*) containing the key/value pairsof VPN secrets.

-[out][transfer full] +[out][transfer full] @@ -609,6 +609,6 @@ user_function ( -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMWifiP2PPeer.html b/docs/libnm/html/NMWifiP2PPeer.html index b4ddd6e..aa6dbc4 100644 --- a/docs/libnm/html/NMWifiP2PPeer.html +++ b/docs/libnm/html/NMWifiP2PPeer.html @@ -8,7 +8,7 @@ - + @@ -373,7 +373,7 @@ nm_wifi_p2p_peer_get_wfd_ies (

Returns

the GBytes containing the WFD IEs, or NULL.

-

[transfer none]

+

[transfer none]

Since: 1.16

@@ -490,7 +490,7 @@ with this function.

connections

-

an array of NMConnections to +

an array of NMConnections to filter.

[element-type NMConnection] @@ -500,10 +500,10 @@ filter.

Returns

an array of -NMConnections that could be activated with the given peer +NMConnections that could be activated with the given peer . The array should be freed with g_ptr_array_unref() when it is no longer required.

-

[transfer container][element-type NMConnection]

+

[transfer container][element-type NMConnection]

Since: 1.16

@@ -613,6 +613,6 @@ against

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/NMWimaxNsp.html b/docs/libnm/html/NMWimaxNsp.html index c055698..ed16e0f 100644 --- a/docs/libnm/html/NMWimaxNsp.html +++ b/docs/libnm/html/NMWimaxNsp.html @@ -8,7 +8,7 @@ - + @@ -228,7 +228,7 @@ connections will match the nsp

connections

-

an array of NMConnections to +

an array of NMConnections to filter.

[element-type NMConnection] @@ -238,10 +238,10 @@ filter.

Returns

an array of -NMConnections that could be activated with the given nsp +NMConnections that could be activated with the given nsp . The array should be freed with g_ptr_array_unref() when it is no longer required.

-

[transfer full][element-type NMConnection]

+

[transfer full][element-type NMConnection]


@@ -311,6 +311,6 @@ against

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/annotation-glossary.html b/docs/libnm/html/annotation-glossary.html index 9591c45..3b043d1 100644 --- a/docs/libnm/html/annotation-glossary.html +++ b/docs/libnm/html/annotation-glossary.html @@ -8,7 +8,7 @@ - + @@ -49,7 +49,7 @@

Generics and defining elements of containers and arrays.

I

inout
-

Parameter for input and for returning results. Default is transfer full.

+

Parameter for input and for returning results. Default is transfer full.

N

nullable

NULL may be passed as the value in, out, in-out; or as a return value.

@@ -57,7 +57,7 @@
optional

NULL may be passed instead of a pointer to a location.

out
-

Parameter for returning results. Default is transfer full.

+

Parameter for returning results. Default is transfer full.

S

scope async

The callback is valid until first called.

@@ -67,15 +67,15 @@

Exposed in C code, not necessarily available in other languages.

T

transfer container
-

Free data container after the code is done.

+

The caller owns the data container, but not the data inside it.

transfer full
-

Free data after the code is done.

+

The caller owns the data, and is responsible for free it.

transfer none
-

Don't free data after the code is done.

+

The data is owned by the callee, which is responsible of freeing it.

type

Override the parsed C type with given type.

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/api-index-full.html b/docs/libnm/html/api-index-full.html index 2aba7ac..b526e52 100644 --- a/docs/libnm/html/api-index-full.html +++ b/docs/libnm/html/api-index-full.html @@ -8,7 +8,7 @@ - + @@ -49,47 +49,47 @@ NMAccessPoint, struct in NMDeviceOvs
-NMAccessPoint:bssid, object property in NMDeviceOvs +NMAccessPoint:bssid, object property in NMDeviceOvs
-NMAccessPoint:flags, object property in NMDeviceOvs +NMAccessPoint:flags, object property in NMDeviceOvs
-NMAccessPoint:frequency, object property in NMDeviceOvs +NMAccessPoint:frequency, object property in NMDeviceOvs
-NMAccessPoint:hw-address, object property in NMDeviceOvs +NMAccessPoint:hw-address, object property in NMDeviceOvs
-NMAccessPoint:last-seen, object property in NMDeviceOvs +NMAccessPoint:last-seen, object property in NMDeviceOvs
-NMAccessPoint:max-bitrate, object property in NMDeviceOvs +NMAccessPoint:max-bitrate, object property in NMDeviceOvs
-NMAccessPoint:mode, object property in NMDeviceOvs +NMAccessPoint:mode, object property in NMDeviceOvs
-NMAccessPoint:rsn-flags, object property in NMDeviceOvs +NMAccessPoint:rsn-flags, object property in NMDeviceOvs
-NMAccessPoint:ssid, object property in NMDeviceOvs +NMAccessPoint:ssid, object property in NMDeviceOvs
-NMAccessPoint:strength, object property in NMDeviceOvs +NMAccessPoint:strength, object property in NMDeviceOvs
-NMAccessPoint:wpa-flags, object property in NMDeviceOvs +NMAccessPoint:wpa-flags, object property in NMDeviceOvs
@@ -105,43 +105,43 @@ NMActiveConnection::state-changed, object signal in NMDeviceOvs
-NMActiveConnection:connection, object property in NMDeviceOvs +NMActiveConnection:connection, object property in NMDeviceOvs
-NMActiveConnection:default, object property in NMDeviceOvs +NMActiveConnection:default, object property in NMDeviceOvs
-NMActiveConnection:default6, object property in NMDeviceOvs +NMActiveConnection:default6, object property in NMDeviceOvs
-NMActiveConnection:devices, object property in NMDeviceOvs +NMActiveConnection:devices, object property in NMDeviceOvs
-NMActiveConnection:dhcp4-config, object property in NMDeviceOvs +NMActiveConnection:dhcp4-config, object property in NMDeviceOvs
-NMActiveConnection:dhcp6-config, object property in NMDeviceOvs +NMActiveConnection:dhcp6-config, object property in NMDeviceOvs
-NMActiveConnection:id, object property in NMDeviceOvs +NMActiveConnection:id, object property in NMDeviceOvs
-NMActiveConnection:ip4-config, object property in NMDeviceOvs +NMActiveConnection:ip4-config, object property in NMDeviceOvs
-NMActiveConnection:ip6-config, object property in NMDeviceOvs +NMActiveConnection:ip6-config, object property in NMDeviceOvs
-NMActiveConnection:master, object property in NMDeviceOvs +NMActiveConnection:master, object property in NMDeviceOvs
@@ -149,7 +149,7 @@ NMActiveConnection:specific-object-path, object property in NMDeviceOvs
-NMActiveConnection:state, object property in NMDeviceOvs +NMActiveConnection:state, object property in NMDeviceOvs
@@ -157,15 +157,15 @@ NMActiveConnection:state-flags, object property in NMDeviceOvs
-NMActiveConnection:type, object property in NMDeviceOvs +NMActiveConnection:type, object property in NMDeviceOvs
-NMActiveConnection:uuid, object property in NMDeviceOvs +NMActiveConnection:uuid, object property in NMDeviceOvs
-NMActiveConnection:vpn, object property in NMDeviceOvs +NMActiveConnection:vpn, object property in NMDeviceOvs
@@ -225,11 +225,11 @@ NMClient::active-connection-removed, object signal in NMDeviceOvs
-NMClient::any-device-added, object signal in NMDeviceOvs +NMClient::any-device-added, object signal in NMDeviceOvs
-NMClient::any-device-removed, object signal in NMDeviceOvs +NMClient::any-device-removed, object signal in NMDeviceOvs
@@ -241,27 +241,27 @@ NMClient::connection-removed, object signal in NMDeviceOvs
-NMClient::device-added, object signal in NMDeviceOvs +NMClient::device-added, object signal in NMDeviceOvs
-NMClient::device-removed, object signal in NMDeviceOvs +NMClient::device-removed, object signal in NMDeviceOvs
-NMClient::permission-changed, object signal in NMDeviceOvs +NMClient::permission-changed, object signal in NMDeviceOvs
-NMClient:activating-connection, object property in NMDeviceOvs +NMClient:activating-connection, object property in NMDeviceOvs
-NMClient:active-connections, object property in NMDeviceOvs +NMClient:active-connections, object property in NMDeviceOvs
-NMClient:all-devices, object property in NMDeviceOvs +NMClient:all-devices, object property in NMDeviceOvs
@@ -281,7 +281,7 @@ NMClient:connections, object property in NMDeviceOvs
-NMClient:connectivity, object property in NMDeviceOvs +NMClient:connectivity, object property in NMDeviceOvs
@@ -305,7 +305,7 @@ NMClient:dbus-name-owner, object property in NMDeviceOvs
-NMClient:devices, object property in NMDeviceOvs +NMClient:devices, object property in NMDeviceOvs
@@ -333,7 +333,7 @@ NMClient:metered, object property in NMDeviceOvs
-NMClient:networking-enabled, object property in NMDeviceOvs +NMClient:networking-enabled, object property in NMDeviceOvs
@@ -345,43 +345,43 @@ NMClient:permissions-state, object property in NMDeviceOvs
-NMClient:primary-connection, object property in NMDeviceOvs +NMClient:primary-connection, object property in NMDeviceOvs
-NMClient:startup, object property in NMDeviceOvs +NMClient:startup, object property in NMDeviceOvs
-NMClient:state, object property in NMDeviceOvs +NMClient:state, object property in NMDeviceOvs
-NMClient:version, object property in NMDeviceOvs +NMClient:version, object property in NMDeviceOvs
-NMClient:wimax-enabled, object property in NMDeviceOvs +NMClient:wimax-enabled, object property in NMDeviceOvs
-NMClient:wimax-hardware-enabled, object property in NMDeviceOvs +NMClient:wimax-hardware-enabled, object property in NMDeviceOvs
-NMClient:wireless-enabled, object property in NMDeviceOvs +NMClient:wireless-enabled, object property in NMDeviceOvs
-NMClient:wireless-hardware-enabled, object property in NMDeviceOvs +NMClient:wireless-hardware-enabled, object property in NMDeviceOvs
-NMClient:wwan-enabled, object property in NMDeviceOvs +NMClient:wwan-enabled, object property in NMDeviceOvs
-NMClient:wwan-hardware-enabled, object property in NMDeviceOvs +NMClient:wwan-hardware-enabled, object property in NMDeviceOvs
@@ -417,19 +417,19 @@ NMClientNotifyEventWithPtrCb, user_function in NMConnection, struct in NMSettingOvs
-NMConnection::changed, object signal in NMSettingOvs +NMConnection::changed, object signal in NMSettingOvs
-NMConnection::secrets-cleared, object signal in NMSettingOvs +NMConnection::secrets-cleared, object signal in NMSettingOvs
-NMConnection::secrets-updated, object signal in NMSettingOvs +NMConnection::secrets-updated, object signal in NMSettingOvs
@@ -473,51 +473,51 @@ NMDevice6Lowpan:parent, object property in NMDeviceOvs
-NMDevice::state-changed, object signal in NMDeviceOvs +NMDevice::state-changed, object signal in NMDeviceOvs
-NMDevice:active-connection, object property in NMDeviceOvs +NMDevice:active-connection, object property in NMDeviceOvs
-NMDevice:autoconnect, object property in NMDeviceOvs +NMDevice:autoconnect, object property in NMDeviceOvs
-NMDevice:available-connections, object property in NMDeviceOvs +NMDevice:available-connections, object property in NMDeviceOvs
-NMDevice:capabilities, object property in NMDeviceOvs +NMDevice:capabilities, object property in NMDeviceOvs
-NMDevice:device-type, object property in NMDeviceOvs +NMDevice:device-type, object property in NMDeviceOvs
-NMDevice:dhcp4-config, object property in NMDeviceOvs +NMDevice:dhcp4-config, object property in NMDeviceOvs
-NMDevice:dhcp6-config, object property in NMDeviceOvs +NMDevice:dhcp6-config, object property in NMDeviceOvs
-NMDevice:driver, object property in NMDeviceOvs +NMDevice:driver, object property in NMDeviceOvs
-NMDevice:driver-version, object property in NMDeviceOvs +NMDevice:driver-version, object property in NMDeviceOvs
-NMDevice:firmware-missing, object property in NMDeviceOvs +NMDevice:firmware-missing, object property in NMDeviceOvs
-NMDevice:firmware-version, object property in NMDeviceOvs +NMDevice:firmware-version, object property in NMDeviceOvs
@@ -525,7 +525,7 @@ NMDevice:hw-address, object property in NMDeviceOvs
-NMDevice:interface, object property in NMDeviceOvs +NMDevice:interface, object property in NMDeviceOvs
@@ -533,11 +533,11 @@ NMDevice:interface-flags, object property in NMDeviceOvs
-NMDevice:ip-interface, object property in NMDeviceOvs +NMDevice:ip-interface, object property in NMDeviceOvs
-NMDevice:ip4-config, object property in NMDeviceOvs +NMDevice:ip4-config, object property in NMDeviceOvs
@@ -545,7 +545,7 @@ NMDevice:ip4-connectivity, object property in NMDeviceOvs
-NMDevice:ip6-config, object property in NMDeviceOvs +NMDevice:ip6-config, object property in NMDeviceOvs
@@ -557,7 +557,7 @@ NMDevice:lldp-neighbors, object property in NMDeviceOvs
-NMDevice:managed, object property in NMDeviceOvs +NMDevice:managed, object property in NMDeviceOvs
@@ -565,7 +565,7 @@ NMDevice:metered, object property in NMDeviceOvs
-NMDevice:mtu, object property in NMDeviceOvs +NMDevice:mtu, object property in NMDeviceOvs
@@ -577,31 +577,31 @@ NMDevice:path, object property in NMDeviceOvs
-NMDevice:physical-port-id, object property in NMDeviceOvs +NMDevice:physical-port-id, object property in NMDeviceOvs
-NMDevice:product, object property in NMDeviceOvs +NMDevice:product, object property in NMDeviceOvs
-NMDevice:real, object property in NMDeviceOvs +NMDevice:real, object property in NMDeviceOvs
-NMDevice:state, object property in NMDeviceOvs +NMDevice:state, object property in NMDeviceOvs
-NMDevice:state-reason, object property in NMDeviceOvs +NMDevice:state-reason, object property in NMDeviceOvs
-NMDevice:udi, object property in NMDeviceOvs +NMDevice:udi, object property in NMDeviceOvs
-NMDevice:vendor, object property in NMDeviceOvs +NMDevice:vendor, object property in NMDeviceOvs
@@ -609,7 +609,7 @@ NMDeviceAdsl, struct in NMDeviceOvs
-NMDeviceAdsl:carrier, object property in NMDeviceOvs +NMDeviceAdsl:carrier, object property in NMDeviceOvs
@@ -617,11 +617,11 @@ NMDeviceBond, struct in NMDeviceOvs
-NMDeviceBond:carrier, object property in NMDeviceOvs +NMDeviceBond:carrier, object property in NMDeviceOvs
-NMDeviceBond:slaves, object property in NMDeviceOvs +NMDeviceBond:slaves, object property in NMDeviceOvs
@@ -629,11 +629,11 @@ NMDeviceBridge, struct in NMDeviceOvs
-NMDeviceBridge:carrier, object property in NMDeviceOvs +NMDeviceBridge:carrier, object property in NMDeviceOvs
-NMDeviceBridge:slaves, object property in NMDeviceOvs +NMDeviceBridge:slaves, object property in NMDeviceOvs
@@ -641,11 +641,11 @@ NMDeviceBt, struct in NMDeviceOvs
-NMDeviceBt:bt-capabilities, object property in NMDeviceOvs +NMDeviceBt:bt-capabilities, object property in NMDeviceOvs
-NMDeviceBt:name, object property in NMDeviceOvs +NMDeviceBt:name, object property in NMDeviceOvs
@@ -669,11 +669,11 @@ NMDeviceEthernet, struct in NMDeviceOvs
-NMDeviceEthernet:carrier, object property in NMDeviceOvs +NMDeviceEthernet:carrier, object property in NMDeviceOvs
-NMDeviceEthernet:perm-hw-address, object property in NMDeviceOvs +NMDeviceEthernet:perm-hw-address, object property in NMDeviceOvs
@@ -681,7 +681,7 @@ NMDeviceEthernet:s390-subchannels, object property in NMDeviceOvs
-NMDeviceEthernet:speed, object property in NMDeviceOvs +NMDeviceEthernet:speed, object property in NMDeviceOvs
@@ -693,7 +693,7 @@ NMDeviceGeneric, struct in NMDeviceOvs
-NMDeviceGeneric:type-description, object property in NMDeviceOvs +NMDeviceGeneric:type-description, object property in NMDeviceOvs
@@ -701,7 +701,7 @@ NMDeviceInfiniband, struct in NMDeviceOvs
-NMDeviceInfiniband:carrier, object property in NMDeviceOvs +NMDeviceInfiniband:carrier, object property in NMDeviceOvs
@@ -845,7 +845,7 @@ NMDeviceModem:apn, object property in NMDeviceOvs
-NMDeviceModem:current-capabilities, object property in NMDeviceOvs +NMDeviceModem:current-capabilities, object property in NMDeviceOvs
@@ -853,7 +853,7 @@ NMDeviceModem:device-id, object property in NMDeviceOvs
-NMDeviceModem:modem-capabilities, object property in NMDeviceOvs +NMDeviceModem:modem-capabilities, object property in NMDeviceOvs
@@ -869,11 +869,11 @@ NMDeviceOlpcMesh, struct in NMDeviceOvs
-NMDeviceOlpcMesh:active-channel, object property in NMDeviceOvs +NMDeviceOlpcMesh:active-channel, object property in NMDeviceOvs
-NMDeviceOlpcMesh:companion, object property in NMDeviceOvs +NMDeviceOlpcMesh:companion, object property in NMDeviceOvs
@@ -913,7 +913,7 @@ NMDeviceTeam, struct in NMDeviceOvs
-NMDeviceTeam:carrier, object property in NMDeviceOvs +NMDeviceTeam:carrier, object property in NMDeviceOvs
@@ -921,7 +921,7 @@ NMDeviceTeam:config, object property in NMDeviceOvs
-NMDeviceTeam:slaves, object property in NMDeviceOvs +NMDeviceTeam:slaves, object property in NMDeviceOvs
@@ -969,15 +969,15 @@ NMDeviceVlan, struct in NMDeviceOvs
-NMDeviceVlan:carrier, object property in NMDeviceOvs +NMDeviceVlan:carrier, object property in NMDeviceOvs
-NMDeviceVlan:parent, object property in NMDeviceOvs +NMDeviceVlan:parent, object property in NMDeviceOvs
-NMDeviceVlan:vlan-id, object property in NMDeviceOvs +NMDeviceVlan:vlan-id, object property in NMDeviceOvs
@@ -1065,23 +1065,23 @@ NMDeviceWifi, struct in NMDeviceOvs
-NMDeviceWifi::access-point-added, object signal in NMDeviceOvs +NMDeviceWifi::access-point-added, object signal in NMDeviceOvs
-NMDeviceWifi::access-point-removed, object signal in NMDeviceOvs +NMDeviceWifi::access-point-removed, object signal in NMDeviceOvs
-NMDeviceWifi:access-points, object property in NMDeviceOvs +NMDeviceWifi:access-points, object property in NMDeviceOvs
-NMDeviceWifi:active-access-point, object property in NMDeviceOvs +NMDeviceWifi:active-access-point, object property in NMDeviceOvs
-NMDeviceWifi:bitrate, object property in NMDeviceOvs +NMDeviceWifi:bitrate, object property in NMDeviceOvs
@@ -1089,15 +1089,15 @@ NMDeviceWifi:last-scan, object property in NMDeviceOvs
-NMDeviceWifi:mode, object property in NMDeviceOvs +NMDeviceWifi:mode, object property in NMDeviceOvs
-NMDeviceWifi:perm-hw-address, object property in NMDeviceOvs +NMDeviceWifi:perm-hw-address, object property in NMDeviceOvs
-NMDeviceWifi:wireless-capabilities, object property in NMDeviceOvs +NMDeviceWifi:wireless-capabilities, object property in NMDeviceOvs
@@ -1125,43 +1125,43 @@ NMDeviceWimax, struct in NMDeviceOvs
-NMDeviceWimax::nsp-added, object signal in NMDeviceOvs +NMDeviceWimax::nsp-added, object signal in NMDeviceOvs
-NMDeviceWimax::nsp-removed, object signal in NMDeviceOvs +NMDeviceWimax::nsp-removed, object signal in NMDeviceOvs
-NMDeviceWimax:active-nsp, object property in NMDeviceOvs +NMDeviceWimax:active-nsp, object property in NMDeviceOvs
-NMDeviceWimax:bsid, object property in NMDeviceOvs +NMDeviceWimax:bsid, object property in NMDeviceOvs
-NMDeviceWimax:center-frequency, object property in NMDeviceOvs +NMDeviceWimax:center-frequency, object property in NMDeviceOvs
-NMDeviceWimax:cinr, object property in NMDeviceOvs +NMDeviceWimax:cinr, object property in NMDeviceOvs
-NMDeviceWimax:hw-address, object property in NMDeviceOvs +NMDeviceWimax:hw-address, object property in NMDeviceOvs
-NMDeviceWimax:nsps, object property in NMDeviceOvs +NMDeviceWimax:nsps, object property in NMDeviceOvs
-NMDeviceWimax:rssi, object property in NMDeviceOvs +NMDeviceWimax:rssi, object property in NMDeviceOvs
-NMDeviceWimax:tx-power, object property in NMDeviceOvs +NMDeviceWimax:tx-power, object property in NMDeviceOvs
@@ -1261,27 +1261,27 @@ NMIPConfigClass, struct in NMKeyfileHandlerData, struct in nm-keyfile
-NMKeyfileHandlerFlags, enum in nm-keyfile +NMKeyfileHandlerFlags, enum in nm-keyfile
-NMKeyfileHandlerType, enum in nm-keyfile +NMKeyfileHandlerType, enum in nm-keyfile
-NMKeyfileReadHandler, user_function in nm-keyfile +NMKeyfileReadHandler, user_function in nm-keyfile
-NMKeyfileWarnSeverity, enum in nm-keyfile +NMKeyfileWarnSeverity, enum in nm-keyfile
-NMKeyfileWriteHandler, user_function in nm-keyfile +NMKeyfileWriteHandler, user_function in nm-keyfile
@@ -1597,7 +1597,7 @@ NMRemoteConnection:flags, object property in NMDeviceOvs
-NMRemoteConnection:unsaved, object property in NMDeviceOvs +NMRemoteConnection:unsaved, object property in NMDeviceOvs
@@ -1933,11 +1933,11 @@ NMSettingBridge, struct in NMSettingOvs
-NMSettingBridge:ageing-time, object property in NMSettingOvs +NMSettingBridge:ageing-time, object property in NMSettingOvs
-NMSettingBridge:forward-delay, object property in NMSettingOvs +NMSettingBridge:forward-delay, object property in NMSettingOvs
@@ -1949,15 +1949,15 @@ NMSettingBridge:group-forward-mask, object property in NMSettingOvs
-NMSettingBridge:hello-time, object property in NMSettingOvs +NMSettingBridge:hello-time, object property in NMSettingOvs
-NMSettingBridge:mac-address, object property in NMSettingOvs +NMSettingBridge:mac-address, object property in NMSettingOvs
-NMSettingBridge:max-age, object property in NMSettingOvs +NMSettingBridge:max-age, object property in NMSettingOvs
@@ -2013,11 +2013,11 @@ NMSettingBridge:multicast-startup-query-interval, object property in NMSettingOv
-NMSettingBridge:priority, object property in NMSettingOvs +NMSettingBridge:priority, object property in NMSettingOvs
-NMSettingBridge:stp, object property in NMSettingOvs +NMSettingBridge:stp, object property in NMSettingOvs
@@ -3905,15 +3905,15 @@ NMWimaxNsp, struct in NMDeviceOvs
-NMWimaxNsp:name, object property in NMDeviceOvs +NMWimaxNsp:name, object property in NMDeviceOvs
-NMWimaxNsp:network-type, object property in NMDeviceOvs +NMWimaxNsp:network-type, object property in NMDeviceOvs
-NMWimaxNsp:signal-quality, object property in NMDeviceOvs +NMWimaxNsp:signal-quality, object property in NMDeviceOvs
@@ -5357,11 +5357,11 @@ nm_context_busy_watcher_quark, function in NM_DBUS_INTERFACE_VPN, macro in nm-vpn-dbus-interface
-NM_DBUS_INTERFACE_VPN_CONNECTION, macro in nm-vpn-dbus-interface +NM_DBUS_INTERFACE_VPN_CONNECTION, macro in nm-vpn-dbus-interface
@@ -5373,15 +5373,15 @@ NM_DBUS_INTERFACE_VPN_CONNECTION, macro in nm-vpn-dbus-interface
-NM_DBUS_INVALID_VPN_CONNECTION, macro in nm-vpn-dbus-interface +NM_DBUS_INVALID_VPN_CONNECTION, macro in nm-vpn-dbus-interface
-NM_DBUS_NO_ACTIVE_VPN_CONNECTION, macro in nm-vpn-dbus-interface +NM_DBUS_NO_ACTIVE_VPN_CONNECTION, macro in nm-vpn-dbus-interface
-NM_DBUS_NO_VPN_CONNECTIONS, macro in nm-vpn-dbus-interface +NM_DBUS_NO_VPN_CONNECTIONS, macro in nm-vpn-dbus-interface
@@ -5417,11 +5417,11 @@ nm_dbus_path_not_empty, function in
-NM_DBUS_PATH_VPN, macro in nm-vpn-dbus-interface +NM_DBUS_PATH_VPN, macro in nm-vpn-dbus-interface
-NM_DBUS_PATH_VPN_CONNECTION, macro in nm-vpn-dbus-interface +NM_DBUS_PATH_VPN_CONNECTION, macro in nm-vpn-dbus-interface
@@ -5437,67 +5437,67 @@ NM_DBUS_PATH_VPN_CONNECTION, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_ALREADY_STARTED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_ALREADY_STARTED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_ALREADY_STOPPED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_ALREADY_STOPPED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_BAD_ARGUMENTS, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_BAD_ARGUMENTS, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_ERROR_PREFIX, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_ERROR_PREFIX, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_INTERACTIVE_NOT_SUPPORTED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_INTERACTIVE_NOT_SUPPORTED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_CONNECT_FAILED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_CONNECT_FAILED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_IP4_CONFIG, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_IP4_CONFIG, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_IP_CONFIG_BAD, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_IP_CONFIG_BAD, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_LAUNCH_FAILED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_LAUNCH_FAILED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_LOGIN_BANNER, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_LOGIN_BANNER, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_LOGIN_FAILED, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_LOGIN_FAILED, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_STATE_CHANGE, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_STATE_CHANGE, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_SIGNAL_VPN_CONFIG_BAD, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_SIGNAL_VPN_CONFIG_BAD, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_STARTING_IN_PROGRESS, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_STARTING_IN_PROGRESS, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_STOPPING_IN_PROGRESS, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_STOPPING_IN_PROGRESS, macro in nm-vpn-dbus-interface
-NM_DBUS_VPN_WRONG_STATE, macro in nm-vpn-dbus-interface +NM_DBUS_VPN_WRONG_STATE, macro in nm-vpn-dbus-interface
@@ -7809,23 +7809,23 @@ NM_ETHTOOL_OPTNAME_RING_TX, macro in nm-ethtool-utils
-nm_keyfile_handler_data_fail_with_error, function in nm-keyfile +nm_keyfile_handler_data_fail_with_error, function in nm-keyfile
-nm_keyfile_handler_data_get_context, function in nm-keyfile +nm_keyfile_handler_data_get_context, function in nm-keyfile
-nm_keyfile_handler_data_warn_get, function in nm-keyfile +nm_keyfile_handler_data_warn_get, function in nm-keyfile
-nm_keyfile_read, function in nm-keyfile +nm_keyfile_read, function in nm-keyfile
-nm_keyfile_write, function in nm-keyfile +nm_keyfile_write, function in nm-keyfile
@@ -14013,11 +14013,11 @@ nm_utils_wincaps_to_dash, function in
-NM_VPN_DBUS_PLUGIN_INTERFACE, macro in nm-vpn-dbus-interface +NM_VPN_DBUS_PLUGIN_INTERFACE, macro in nm-vpn-dbus-interface
-NM_VPN_DBUS_PLUGIN_PATH, macro in nm-vpn-dbus-interface +NM_VPN_DBUS_PLUGIN_PATH, macro in nm-vpn-dbus-interface
@@ -14085,27 +14085,27 @@ NM_VPN_DBUS_PLUGIN_PATH, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CAN_PERSIST, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CAN_PERSIST, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_BANNER, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_BANNER, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_HAS_IP4, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_HAS_IP4, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_HAS_IP6, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_HAS_IP6, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_MTU, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_MTU, macro in nm-vpn-dbus-interface
@@ -14113,7 +14113,7 @@ NM_VPN_PLUGIN_CONFIG_PROXY_PAC, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_CONFIG_TUNDEV, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_CONFIG_TUNDEV, macro in nm-vpn-dbus-interface
@@ -14249,55 +14249,55 @@ NM_VPN_PLUGIN_CONFIG_TUNDEV, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_BANNER, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_BANNER, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_DNS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_DNS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_MSS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_MSS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_MTU, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_MTU, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_NBNS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_NBNS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, macro in nm-vpn-dbus-interface
@@ -14305,47 +14305,47 @@ NM_VPN_PLUGIN_IP4_CONFIG_PRESERVE_ROUTES, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_PTP, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_PTP, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_DNS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_DNS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_DOMAINS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_DOMAINS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_MSS, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_MSS, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_PREFIX, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_PREFIX, macro in nm-vpn-dbus-interface
@@ -14353,11 +14353,11 @@ NM_VPN_PLUGIN_IP6_CONFIG_PRESERVE_ROUTES, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_PTP, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_PTP, macro in nm-vpn-dbus-interface
-NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, macro in nm-vpn-dbus-interface +NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, macro in nm-vpn-dbus-interface
@@ -14723,6 +14723,6 @@ NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, macro in nm-vpn-dbus-interface
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ch02.html b/docs/libnm/html/ch02.html index bd869fe..47a7dab 100644 --- a/docs/libnm/html/ch02.html +++ b/docs/libnm/html/ch02.html @@ -8,7 +8,7 @@ - + @@ -41,6 +41,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ch03.html b/docs/libnm/html/ch03.html index e6b5dbe..90068a2 100644 --- a/docs/libnm/html/ch03.html +++ b/docs/libnm/html/ch03.html @@ -8,7 +8,7 @@ - + @@ -198,6 +198,6 @@ use WEP, LEAP, WPA or WPA2/RSN security
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ch04.html b/docs/libnm/html/ch04.html index 6667285..bc92ddc 100644 --- a/docs/libnm/html/ch04.html +++ b/docs/libnm/html/ch04.html @@ -8,7 +8,7 @@ - + @@ -140,6 +140,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ch05.html b/docs/libnm/html/ch05.html index e87ee25..1fabc6f 100644 --- a/docs/libnm/html/ch05.html +++ b/docs/libnm/html/ch05.html @@ -7,8 +7,8 @@ - - + + @@ -17,13 +17,16 @@ Home Prev -Next +Next

Utility API Reference

+nm-keyfile +
+
nm-utils — Utility functions
@@ -32,6 +35,6 @@
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ch06.html b/docs/libnm/html/ch06.html index 5ba9000..b8bcee8 100644 --- a/docs/libnm/html/ch06.html +++ b/docs/libnm/html/ch06.html @@ -8,7 +8,7 @@ - + @@ -41,6 +41,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/index.html b/docs/libnm/html/index.html index 80ea87f..3431fe9 100644 --- a/docs/libnm/html/index.html +++ b/docs/libnm/html/index.html @@ -6,7 +6,7 @@ - + @@ -15,7 +15,7 @@

- for libnm 1.29.10 + for libnm 1.29.11 The latest version of this documentation can be found on-line at https://developer.gnome.org/libnm/stable/. @@ -365,6 +365,9 @@ use WEP, LEAP, WPA or WPA2/RSN security

Utility API Reference
+nm-keyfile +
+
nm-utils — Utility functions
@@ -396,6 +399,6 @@ use WEP, LEAP, WPA or WPA2/RSN security
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/libnm-nm-dbus-interface.html b/docs/libnm/html/libnm-nm-dbus-interface.html index aeeb13e..4a8561d 100644 --- a/docs/libnm/html/libnm-nm-dbus-interface.html +++ b/docs/libnm/html/libnm-nm-dbus-interface.html @@ -8,7 +8,7 @@ - + @@ -4007,6 +4007,6 @@ denied by system policy

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/libnm-nm-errors.html b/docs/libnm/html/libnm-nm-errors.html index 2977a95..4757417 100644 --- a/docs/libnm/html/libnm-nm-errors.html +++ b/docs/libnm/html/libnm-nm-errors.html @@ -8,7 +8,7 @@ - + @@ -254,7 +254,7 @@ nm_vpn_plugin_error_quark (void

enum NMAgentManagerError

Errors returned from the secret-agent manager.

These errors may be returned from operations that could cause secrets to be -requested (such as nm_client_activate_connection()), and correspond to D-Bus +requested (such as nm_client_activate_connection()), and correspond to D-Bus errors in the "org.freedesktop.NetworkManager.AgentManager" namespace.

Members

@@ -737,7 +737,7 @@ activation but is not available.

when they encounter problems retrieving secrets on behalf of NM. They correspond to errors in the "org.freedesktop.NetworkManager.SecretManager" namespace.

-

Client APIs such as nm_client_activate_connection() will not see these error +

Client APIs such as nm_client_activate_connection() will not see these error codes; instead, the secret agent manager will translate them to the corresponding NMAgentManagerError codes.

@@ -998,6 +998,6 @@ performed as the plugin does not support interactive operations, such as
+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/libnm-nm-keyfile.html b/docs/libnm/html/libnm-nm-keyfile.html new file mode 100644 index 0000000..ddd85e1 --- /dev/null +++ b/docs/libnm/html/libnm-nm-keyfile.html @@ -0,0 +1,639 @@ + + + + +nm-keyfile: libnm Reference Manual + + + + + + + + + + + + + + + + +
+
+
+ + +
+

nm-keyfile

+

nm-keyfile

+
+
+

Functions

+
++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+gboolean + +(*NMKeyfileReadHandler) () +
+NMConnection * + +nm_keyfile_read () +
+gboolean + +(*NMKeyfileWriteHandler) () +
+GKeyFile * + +nm_keyfile_write () +
+void + +nm_keyfile_handler_data_fail_with_error () +
+void + +nm_keyfile_handler_data_get_context () +
+void + +nm_keyfile_handler_data_warn_get () +
+
+
+

Types and Values

+
++++ + + + + + + + + + + + + + + + + + + +
enumNMKeyfileHandlerFlags
enumNMKeyfileHandlerType
enumNMKeyfileWarnSeverity
 NMKeyfileHandlerData
+
+
+

Object Hierarchy

+
    GEnum
+    ├── NMKeyfileHandlerType
+    ╰── NMKeyfileWarnSeverity
+    GFlags
+    ╰── NMKeyfileHandlerFlags
+
+
+
+

Description

+
+
+

Functions

+
+

NMKeyfileReadHandler ()

+
gboolean
+(*NMKeyfileReadHandler) (GKeyFile *keyfile,
+                         NMConnection *connection,
+                         NMKeyfileHandlerType handler_type,
+                         NMKeyfileHandlerData *handler_data,
+                         void *user_data);
+

Hook to nm_keyfile_read().

+

The callee may abort the reading by setting an error via nm_keyfile_handler_data_fail_with_error().

+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

keyfile

the GKeyFile that is currently read

 

connection

the NMConnection that is being constructed.

 

handler_type

the NMKeyfileHandlerType that indicates which type +the request is.

 

handler_data

the NMKeyfileHandlerData. What you can do with it +depends on the handler_type +.

 

user_data

the user-data argument to nm_keyfile_read().

 
+
+
+

Returns

+

the callee should return TRUE, if the event was handled and/or recognized. +Otherwise, a default action will be performed that depends on the type +. +For NM_KEYFILE_HANDLER_TYPE_WARN type, the default action is doing nothing.

+
+

Since: 1.30

+
+
+
+

nm_keyfile_read ()

+
NMConnection *
+nm_keyfile_read (GKeyFile *keyfile,
+                 const char *base_dir,
+                 NMKeyfileHandlerFlags handler_flags,
+                 NMKeyfileReadHandler handler,
+                 void *user_data,
+                 GError **error);
+

Tries to create a NMConnection from a keyfile. The resulting keyfile is +not normalized and might not even verify.

+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

keyfile

the keyfile from which to create the connection

 

base_dir

when reading certificates from files with relative name, +the relative path is made absolute using base_dir +. This must +be an absolute path.

 

handler_flags

the NMKeyfileHandlerFlags.

 

handler

read handler.

[allow-none][scope call]

user_data

user data for read handler

 

error

error.

[allow-none][out]
+
+
+

Returns

+

on success, returns the created connection.

+

[transfer full]

+
+

Since: 1.30

+
+
+
+

NMKeyfileWriteHandler ()

+
gboolean
+(*NMKeyfileWriteHandler) (NMConnection *connection,
+                          GKeyFile *keyfile,
+                          NMKeyfileHandlerType handler_type,
+                          NMKeyfileHandlerData *handler_data,
+                          void *user_data);
+

This is a hook to tweak the serialization.

+

Handler for certain properties or events that are not entirely contained +within the keyfile or that might be serialized differently. The type + and +handler_data + arguments tell which kind of argument we have at hand.

+

Currently only the type NM_KEYFILE_HANDLER_TYPE_WRITE_CERT is supported.

+

The callee may call nm_keyfile_handler_data_fail_with_error() to abort +the writing with error.

+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

connection

the NMConnection that is currently written.

 

keyfile

the GKeyFile that is currently constructed.

 

handler_type

the NMKeyfileHandlerType that indicates which type +the request is.

 

handler_data

the NMKeyfileHandlerData. What you can do with it +depends on the handler_type +.

 

user_data

the user-data argument to nm_keyfile_read().

 
+
+
+

Returns

+

the callee should return TRUE if the event was handled. If the +event was unhandled, a default action will be performed that depends on +the handler_type +.

+
+

Since: 1.30

+
+
+
+

nm_keyfile_write ()

+
GKeyFile *
+nm_keyfile_write (NMConnection *connection,
+                  NMKeyfileHandlerFlags handler_flags,
+                  NMKeyfileWriteHandler handler,
+                  void *user_data,
+                  GError **error);
+

connection + must verify as a valid profile according to +nm_connection_verify().

+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

connection

the NMConnection to persist to keyfile.

 

handler_flags

the NMKeyfileHandlerFlags.

 

handler

optional handler for events and +to override the default behavior.

[allow-none][scope call]

user_data

argument for handler +.

 

error

the GError in case writing fails.

 
+
+
+

Returns

+

a new GKeyFile or NULL on error.

+

[transfer full]

+
+

Since: 1.30

+
+
+
+

nm_keyfile_handler_data_fail_with_error ()

+
void
+nm_keyfile_handler_data_fail_with_error
+                               (NMKeyfileHandlerData *handler_data,
+                                GError *src);
+

Set the error for the handler. This lets the operation fail +with the provided error. You may only set the error once.

+

src + must be non-NULL.

+

Note that src + is no longer valid after this call. If you want +to keep using the same GError*, you need to set it to NULL +after calling this function on it.

+
+

Parameters

+
+++++ + + + + + + + + + + + + +

handler_data

the NMKeyfileHandlerData

 

src

error to move into the return location.

[transfer full]
+
+

Since: 1.30

+
+
+
+

nm_keyfile_handler_data_get_context ()

+
void
+nm_keyfile_handler_data_get_context (const NMKeyfileHandlerData *handler_data,
+                                     const char **out_kf_group_name,
+                                     const char **out_kf_key_name,
+                                     NMSetting **out_cur_setting,
+                                     const char **out_cur_property_name);
+

Get context information of the current event. This function can be called +on all events, but the context information may be unset.

+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

handler_data

the NMKeyfileHandlerData for any event.

 

out_kf_group_name

if the event is in the +context of a keyfile group, the group name.

[out][allow-none][transfer none]

out_kf_key_name

if the event is in the +context of a keyfile value, the key name.

[out][allow-none][transfer none]

out_cur_setting

if the event happens while +handling a particular NMSetting instance.

[out][allow-none][transfer none]

out_cur_property_name

the property name if applicable.

[out][allow-none][transfer none]
+
+

Since: 1.30

+
+
+
+

nm_keyfile_handler_data_warn_get ()

+
void
+nm_keyfile_handler_data_warn_get (const NMKeyfileHandlerData *handler_data,
+                                  const char **out_message,
+                                  NMKeyfileWarnSeverity *out_severity);
+
+

Parameters

+
+++++ + + + + + + + + + + + + + + + + + +

handler_data

the NMKeyfileHandlerData for a NM_KEYFILE_HANDLER_TYPE_WARN +event.

 

out_message

the warning message.

[out][allow-none][transfer none]

out_severity

the NMKeyfileWarnSeverity warning severity.

[out][allow-none]
+
+

Since: 1.30

+
+
+
+

Types and Values

+
+

enum NMKeyfileHandlerFlags

+

Flags for customizing nm_keyfile_read() and nm_keyfile_write().

+

Currently no flags are implemented.

+
+

Members

+
+++++ + + + + + +

NM_KEYFILE_HANDLER_FLAGS_NONE

+

no flags set.

+
 
+
+

Since: 1.30

+
+
+
+

enum NMKeyfileHandlerType

+

The type of the callback for NMKeyfileReadHandler and NMKeyfileWriteHandler. +Depending on the type, you can interpret NMKeyfileHandlerData.

+
+

Members

+
+++++ + + + + + + + + + + + + +

NM_KEYFILE_HANDLER_TYPE_WARN

+

a warning.

+
 

NM_KEYFILE_HANDLER_TYPE_WRITE_CERT

+

for handling certificates while writing +a connection to keyfile.

+
 
+
+

Since: 1.30

+
+
+
+

enum NMKeyfileWarnSeverity

+

The severity level of NM_KEYFILE_HANDLER_TYPE_WARN events.

+
+

Members

+
+++++ + + + + + + + + + + + + + + + + + + + + + + +

NM_KEYFILE_WARN_SEVERITY_DEBUG

+

debug message

+
 

NM_KEYFILE_WARN_SEVERITY_INFO

+

info message

+
 

NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE

+

info message about a missing file

+
 

NM_KEYFILE_WARN_SEVERITY_WARN

+

a warning message

+
 
+
+

Since: 1.30

+
+
+
+

NMKeyfileHandlerData

+
typedef struct _NMKeyfileHandlerData NMKeyfileHandlerData;
+

Opaque type with parameters for the callback. The actual content +depends on the NMKeyfileHandlerType.

+

Since: 1.30

+
+
+
+ + + \ No newline at end of file diff --git a/docs/libnm/html/libnm-nm-utils.html b/docs/libnm/html/libnm-nm-utils.html index e73f9ed..b4e0e2b 100644 --- a/docs/libnm/html/libnm-nm-utils.html +++ b/docs/libnm/html/libnm-nm-utils.html @@ -6,9 +6,9 @@ - + - + @@ -20,7 +20,7 @@ Home Up -Prev +Prev Next
@@ -877,7 +877,7 @@ _only_.

an allocated string containing a UTF-8 representation of the SSID, which must be freed by the caller using g_free(). Returns NULL on errors.

-

[transfer full]

+

[transfer full]


@@ -1124,7 +1124,7 @@ type 'au' representing an array of IPv4 addresses.

Returns

a new floating GVariant representing dns .

-

[transfer none]

+

[transfer none]


@@ -1152,7 +1152,7 @@ IPv4 addresses into an array of IP address strings.

Returns

a NULL-terminated array of IP address strings.

-

[transfer full][type utf8]

+

[transfer full][type utf8]


@@ -1193,7 +1193,7 @@ gateway). The "gateway" field of the first address will get the value of

Returns

a new floating GVariant representing addresses .

-

[transfer none]

+

[transfer none]


@@ -1225,7 +1225,7 @@ of the other addresses are ignored.

out_gateway

on return, will contain the IP gateway.

-[out][allow-none][transfer full] +[out][allow-none][transfer full] @@ -1234,7 +1234,7 @@ of the other addresses are ignored.

Returns

a newly allocated GPtrArray of NMIPAddress objects.

-

[transfer full][element-type NMIPAddress]

+

[transfer full][element-type NMIPAddress]


@@ -1265,7 +1265,7 @@ metric).

Returns

a new floating GVariant representing routes .

-

[transfer none]

+

[transfer none]


@@ -1295,7 +1295,7 @@ and metric) into a

Returns

a newly allocated GPtrArray of NMIPRoute objects.

-

[transfer full][element-type NMIPRoute]

+

[transfer full][element-type NMIPRoute]


@@ -1404,7 +1404,7 @@ type 'aay' representing an array of IPv6 addresses.

Returns

a new floating GVariant representing dns .

-

[transfer none]

+

[transfer none]


@@ -1434,7 +1434,7 @@ ignored.

Returns

a NULL-terminated array of IP address strings.

-

[transfer full][type utf8]

+

[transfer full][type utf8]


@@ -1476,7 +1476,7 @@ all 0s.

Returns

a new floating GVariant representing addresses .

-

[transfer none]

+

[transfer none]


@@ -1508,7 +1508,7 @@ fields of the other addresses are ignored.

out_gateway

on return, will contain the IP gateway.

-[out][allow-none][transfer full] +[out][allow-none][transfer full] @@ -1517,7 +1517,7 @@ fields of the other addresses are ignored.

Returns

a newly allocated GPtrArray of NMIPAddress objects.

-

[transfer full][element-type NMIPAddress]

+

[transfer full][element-type NMIPAddress]


@@ -1548,7 +1548,7 @@ metric).

Returns

a new floating GVariant representing routes .

-

[transfer none]

+

[transfer none]


@@ -1578,7 +1578,7 @@ hop, and metric) into a

Returns

a newly allocated GPtrArray of NMIPRoute objects.

-

[transfer full][element-type NMIPRoute]

+

[transfer full][element-type NMIPRoute]


@@ -1610,7 +1610,7 @@ include additional attributes.

Returns

a new floating GVariant representing addresses .

-

[transfer none]

+

[transfer none]


@@ -1649,7 +1649,7 @@ objects.

Returns

a newly allocated GPtrArray of NMIPAddress objects.

-

[transfer full][element-type NMIPAddress]

+

[transfer full][element-type NMIPAddress]


@@ -1680,7 +1680,7 @@ prefix, next hop, metric, and additional attributes).

Returns

a new floating GVariant representing routes .

-

[transfer none]

+

[transfer none]


@@ -1719,7 +1719,7 @@ prefix, next hop, metric, and additional attributes) into a

Returns

a newly allocated GPtrArray of NMIPRoute objects.

-

[transfer full][element-type NMIPRoute]

+

[transfer full][element-type NMIPRoute]


@@ -1920,7 +1920,7 @@ function.

the full path to the helper, if found, or NULL if not found. The returned string is not owned by the caller, but later invocations of the function might overwrite it.

-

[transfer none]

+

[transfer none]


@@ -2180,7 +2180,7 @@ nm_utils_hwaddr_ntoa (

Returns

the textual form of addr .

-

[transfer full]

+

[transfer full]


@@ -2219,7 +2219,7 @@ nm_utils_hwaddr_atoba (const char

a new GByteArray, or NULL if asc couldn't be parsed.

-

[transfer full]

+

[transfer full]


@@ -2350,7 +2350,7 @@ is expected to convert to

the canonicalized address if asc appears to be a valid hardware address of the indicated length, NULL if not.

-

[transfer full]

+

[transfer full]


@@ -2480,7 +2480,7 @@ array

Returns

the textual form of bytes .

-

[transfer full]

+

[transfer full]


@@ -2513,7 +2513,7 @@ may not start or end with ':'.

Returns

the converted bytes, or NULL on error.

-

[transfer full]

+

[transfer full]


@@ -2945,7 +2945,7 @@ containing the unrecognized token in err_token

err_token

location to store the first unrecognized token.

-[out][allow-none][transfer full] +[out][allow-none][transfer full] @@ -2995,7 +2995,7 @@ nm_utils_enum_get_values (

Returns

a NULL-terminated dynamically-allocated array of static strings or NULL on error.

-

[transfer container]

+

[transfer container]

Since: 1.2

@@ -3070,7 +3070,7 @@ nm_utils_parse_variant_attributes (const GVariant values. Warning: the variant are still floating references, owned by the hash table. If you take a reference, ensure to sink the one of the hash table first.

-

[transfer full][element-type utf8 GVariant]

+

[transfer full][element-type utf8 GVariant]

Since: 1.8

@@ -3371,7 +3371,7 @@ nm_utils_sriov_vf_from_str (const

Returns

the virtual function object.

-

[transfer full]

+

[transfer full]

Since: 1.14

@@ -3568,6 +3568,6 @@ for both -
Generated by GTK-Doc V1.33.0 +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/libnm-nm-version.html b/docs/libnm/html/libnm-nm-version.html index 4f7a6dd..3c12ebe 100644 --- a/docs/libnm/html/libnm-nm-version.html +++ b/docs/libnm/html/libnm-nm-version.html @@ -8,7 +8,7 @@ - + @@ -626,6 +626,6 @@ NM_DEPRECATED_IN_1_24_FOR (); +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/libnm.devhelp2 b/docs/libnm/html/libnm.devhelp2 index a29974d..3962206 100644 --- a/docs/libnm/html/libnm.devhelp2 +++ b/docs/libnm/html/libnm.devhelp2 @@ -111,6 +111,7 @@ + @@ -2899,6 +2900,17 @@ + + + + + + + + + + + @@ -3689,6 +3701,13 @@ + + + + + + + diff --git a/docs/libnm/html/license.html b/docs/libnm/html/license.html index 490c941..207a9d4 100644 --- a/docs/libnm/html/license.html +++ b/docs/libnm/html/license.html @@ -7,7 +7,7 @@ - + @@ -55,6 +55,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/object-tree.html b/docs/libnm/html/object-tree.html index 3f2188e..f282d72 100644 --- a/docs/libnm/html/object-tree.html +++ b/docs/libnm/html/object-tree.html @@ -8,7 +8,7 @@ - + @@ -143,7 +143,7 @@ ├──
NMIPAddressCmpFlags ├── NMIPRoutingRuleAsStringFlags ├── NMIPTunnelFlags - ├── NMKeyfileHandlerFlags + ├── NMKeyfileHandlerFlags ├── NMManagerReloadFlags ├── NMSecretAgentCapabilities ├── NMSecretAgentGetSecretsFlags @@ -181,8 +181,8 @@ ├── NMDeviceStateReason ├── NMDeviceType ├── NMIPTunnelMode - ├── NMKeyfileHandlerType - ├── NMKeyfileWarnSeverity + ├── NMKeyfileHandlerType + ├── NMKeyfileWarnSeverity ├── NMMetered ├── NMSetting8021xCKFormat ├── NMSetting8021xCKScheme @@ -231,6 +231,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/ref-overview.html b/docs/libnm/html/ref-overview.html index 675649f..9e1af21 100644 --- a/docs/libnm/html/ref-overview.html +++ b/docs/libnm/html/ref-overview.html @@ -8,7 +8,7 @@ - + @@ -62,6 +62,6 @@ +
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/html/usage.html b/docs/libnm/html/usage.html index 454b0ca..6bcba45 100644 --- a/docs/libnm/html/usage.html +++ b/docs/libnm/html/usage.html @@ -8,7 +8,7 @@ - + @@ -96,7 +96,7 @@

$ cc $(pkg-config --libs --cflags libnm) -o hello-nm hello-nm.c
   $ ./hello-nm
-  NetworkManager version: 1.29.10
+  NetworkManager version: 1.29.11
 
   $ 

@@ -271,6 +271,6 @@

+
Generated by GTK-Doc V1.33.1 \ No newline at end of file diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml index 3ff07c3..5e554c2 100644 --- a/docs/libnm/libnm-docs.xml +++ b/docs/libnm/libnm-docs.xml @@ -411,6 +411,7 @@ print ("NetworkManager version " + client.get_version())]]> Utility API Reference + diff --git a/docs/libnm/version.xml b/docs/libnm/version.xml index 799f8f9..df48db0 100644 --- a/docs/libnm/version.xml +++ b/docs/libnm/version.xml @@ -1 +1 @@ -1.29.10 +1.29.11 diff --git a/examples/python/gi/ovs-external-ids.py b/examples/python/gi/ovs-external-ids.py index 5255e45..63f9695 100755 --- a/examples/python/gi/ovs-external-ids.py +++ b/examples/python/gi/ovs-external-ids.py @@ -189,7 +189,7 @@ def ovs_print_external_ids(prefix): out = "" for cmd in cmds: - p = subprocess.run(cmd, stdout=subprocess.PIPE, check=True,) + p = subprocess.run(cmd, stdout=subprocess.PIPE, check=True) out += p.stdout.decode("utf-8") + "\n" out = "\n".join([prefix + s for s in out.split("\n")]) _print(out) @@ -636,7 +636,7 @@ if __name__ == "__main__": if len(devices) != 1: _print( "To apply the external-ids of a device, exactly one connection must be selected. Instead, %s devices matched ([%s])" - % (len(devices), ", ".join([device_to_str(c) for c in devices]),) + % (len(devices), ", ".join([device_to_str(c) for c in devices])) ) die_usage("Select unique device to apply") do_apply(nmc, devices[0], args["ids_arg"], do_test=args["do_test"]) diff --git a/gtk-doc.make b/gtk-doc.make index 7d9a27f..c673175 100644 --- a/gtk-doc.make +++ b/gtk-doc.make @@ -57,6 +57,7 @@ DOC_STAMPS=setup-build.stamp scan-build.stamp sgml-build.stamp \ sgml.stamp html.stamp pdf.stamp SCANOBJ_FILES = \ + $(DOC_MODULE).actions \ $(DOC_MODULE).args \ $(DOC_MODULE).hierarchy \ $(DOC_MODULE).interfaces \ diff --git a/libnm-core/meson.build b/libnm-core/meson.build index 843ff67..a02bbe1 100644 --- a/libnm-core/meson.build +++ b/libnm-core/meson.build @@ -8,11 +8,6 @@ libnm_core_nm_default_dep = declare_dependency( dependencies: glib_nm_default_dep, ) -common_c_flags = [ - '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), - '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', -] - libnm_core_headers = files( 'nm-connection.h', 'nm-core-types.h', @@ -80,10 +75,8 @@ libnm_core_headers = files( 'nm-vpn-plugin-info.h', ) -enum_types = 'nm-core-enum-types' - libnm_core_enum_sources = gnome.mkenums_simple( - enum_types, + 'nm-core-enum-types', sources: libnm_core_headers + [nm_version_macro_header], identifier_prefix: nm_id_prefix, body_prefix: '#include "nm-default.h"', @@ -101,29 +94,33 @@ libnm_libnm_core_intern = static_library( libnm_core_nm_default_dep, libnm_base_dep, ], - c_args: common_c_flags, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', + ], ) libnm_libnm_core_intern_dep = declare_dependency( sources: [libnm_core_enum_sources[1], nm_version_macro_header], include_directories: [libnm_core_inc, shared_inc], dependencies: [ - libnm_utils_base_dep, + libnm_glib_aux_dep, libnm_base_dep, ], link_with: libnm_libnm_core_intern, ) -deps = [ - crypto_dep, - libnm_utils_base_dep, -] - libnm_crypto = static_library( 'nm-crypto', sources: 'nm-crypto-@0@.c'.format(crypto), - dependencies: deps, - c_args: common_c_flags, + dependencies: [ + crypto_dep, + libnm_glib_aux_dep, + ], + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', + ], ) libnm_core_settings_sources = files( @@ -195,27 +192,24 @@ libnm_core_sources = libnm_core_settings_sources + files( 'nm-vpn-plugin-info.c', ) -deps = [ - dl_dep, - libnm_systemd_shared_dep, - libnm_utils_base_dep, - uuid_dep, -] - -links = [ - libnm_crypto, - libnm_libnm_core_intern, -] - -libnm_core_c_args = common_c_flags - libnm_core = static_library( 'nm-core', sources: libnm_core_sources + libnm_core_enum_sources + nm_meta_setting_source + [nm_version_macro_header], include_directories: top_inc, - dependencies: deps, - c_args: libnm_core_c_args, - link_with: links, + dependencies: [ + dl_dep, + libnm_systemd_shared_dep, + libnm_glib_aux_dep, + uuid_dep, + ], + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', + ], + link_with: [ + libnm_crypto, + libnm_libnm_core_intern, + ], ) libnm_core_dep = declare_dependency( @@ -225,18 +219,16 @@ libnm_core_dep = declare_dependency( link_with: libnm_core, ) -c_flags = [ - '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), - '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', -] - libnm_libnm_core_aux = static_library( 'nm-libnm-core-aux', sources: files( 'nm-libnm-core-aux/nm-libnm-core-aux.c', ) + [libnm_core_enum_sources[1]], - dependencies: libnm_utils_base_dep, - c_args: c_flags, + dependencies: libnm_glib_aux_dep, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', + ], link_with: [ libnm_libnm_core_intern, libnm_base, @@ -258,7 +250,7 @@ nm_keyfile_source = files( libnm_keyfile = static_library( 'nm-keyfile', sources: nm_keyfile_source + [libnm_core_enum_sources[1]], - dependencies: libnm_utils_base_dep, + dependencies: libnm_glib_aux_dep, c_args: [ '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', @@ -273,10 +265,8 @@ libnm_keyfile_dep = declare_dependency( link_with: libnm_keyfile, ) -enums_to_docbook = join_paths(source_root, 'tools', 'enums-to-docbook.pl') - docbooks = [ - ['nm-dbus-types', 'nm-dbus-interface.h', 'NetworkManager D-Bus API Types'], + ['nm-dbus-types', 'nm-dbus-interface.h', 'NetworkManager D-Bus API Types'], ['nm-vpn-dbus-types', 'nm-vpn-dbus-interface.h', 'VPN Plugin D-Bus API Types'], ] @@ -288,7 +278,13 @@ foreach docbook: docbooks input: docbook[1], output: output, capture: true, - command: [perl, enums_to_docbook, docbook[0], docbook[2], '@INPUT@'], + command: [ + perl, + join_paths(source_root, 'tools', 'enums-to-docbook.pl'), + docbook[0], + docbook[2], + '@INPUT@', + ], # FIXME: gtkdoc does not depend directly on this. # https://github.com/mesonbuild/meson/pull/2806 build_by_default: true, diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c index 026e30d..8d9c12d 100644 --- a/libnm-core/nm-setting-ip4-config.c +++ b/libnm-core/nm-setting-ip4-config.c @@ -667,7 +667,7 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass) * ---end--- * ---ifcfg-rh--- * property: addresses - * variable: IPADDR, PREFIX, IPADDR1, PREFIX1, ... + * variable: IPADDR, PREFIX (NETMASK), IPADDR1, PREFIX1 (NETMASK1), ... * description: List of static IP addresses. * example: IPADDR=10.5.5.23 PREFIX=24 IPADDR1=1.1.1.2 PREFIX1=16 * ---end--- diff --git a/libnm-core/nm-vpn-dbus-interface.h b/libnm-core/nm-vpn-dbus-interface.h index 46abda6..c0e23f6 100644 --- a/libnm-core/nm-vpn-dbus-interface.h +++ b/libnm-core/nm-vpn-dbus-interface.h @@ -141,26 +141,18 @@ typedef enum { */ NM_DEPRECATED_IN_1_8_FOR(NMActiveConnectionStateReason) typedef enum { - NM_VPN_CONNECTION_STATE_REASON_UNKNOWN = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, - NM_VPN_CONNECTION_STATE_REASON_NONE = NM_ACTIVE_CONNECTION_STATE_REASON_NONE, - NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED = - NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED, - NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED = - NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED = - NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED, - NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID = - NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, - NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT = - NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT = - NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED = - NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, - NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS = NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, - NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED = NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED, - NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED = - NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED, + NM_VPN_CONNECTION_STATE_REASON_UNKNOWN = 0, + NM_VPN_CONNECTION_STATE_REASON_NONE = 1, + NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED = 2, + NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED = 3, + NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED = 4, + NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID = 5, + NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT = 6, + NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT = 7, + NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED = 8, + NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS = 9, + NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED = 10, + NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED = 11, } NMVpnConnectionStateReason; /** diff --git a/libnm-core/nm-vpn-dbus-types.xml b/libnm-core/nm-vpn-dbus-types.xml index 571b437..5da0242 100644 --- a/libnm-core/nm-vpn-dbus-types.xml +++ b/libnm-core/nm-vpn-dbus-types.xml @@ -144,62 +144,62 @@ NM_VPN_CONNECTION_STATE_REASON_UNKNOWN - = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN + = 0 The reason for the VPN connection state change is unknown. NM_VPN_CONNECTION_STATE_REASON_NONE - = NM_ACTIVE_CONNECTION_STATE_REASON_NONE + = 1 No reason was given for the VPN connection state change. NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED - = NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED + = 2 The VPN connection changed state because the user disconnected it. NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED - = NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED + = 3 The VPN connection changed state because the device it was using was disconnected. NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED - = NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED + = 4 The service providing the VPN connection was stopped. NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID - = NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID + = 5 The IP config of the VPN connection was invalid. NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT - = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT + = 6 The connection attempt to the VPN service timed out. NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT - = NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT + = 7 A timeout occurred while starting the service providing the VPN connection. NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED - = NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED + = 8 Starting the service starting the service providing the VPN connection failed. NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS - = NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS + = 9 Necessary secrets for the VPN connection were not provided. NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED - = NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED + = 10 Authentication to the VPN server failed. NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED - = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED + = 11 The connection was deleted from settings. diff --git a/libnm-core/tests/certs/ca-no-ending-newline.pem b/libnm-core/tests/certs/ca-no-ending-newline.pem index bfbaf1e..664e299 100644 --- a/libnm-core/tests/certs/ca-no-ending-newline.pem +++ b/libnm-core/tests/certs/ca-no-ending-newline.pem @@ -1,15 +1,15 @@ ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ -----END CERTIFICATE----- \ No newline at end of file diff --git a/libnm-core/tests/meson.build b/libnm-core/tests/meson.build index 0f6a4f5..83ba89c 100644 --- a/libnm-core/tests/meson.build +++ b/libnm-core/tests/meson.build @@ -1,30 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -# test-cert.p12 created with: -# -# openssl pkcs12 -export \ -# -in test_key_and_cert.pem \ -# -inkey test_key_and_cert.pem \ -# -certfile test_ca_cert.pem \ -# -name "test-pkcs12" \ -# -out test-cert.p12 - -enum_types = 'nm-core-tests-enum-types' - enum_sources = gnome.mkenums_simple( - enum_types, + 'nm-core-tests-enum-types', sources: 'test-general-enums.h', identifier_prefix: nm_id_prefix, body_prefix: '#include "nm-default.h"', ) -c_flags = [ - '-DNETWORKMANAGER_COMPILATION_TEST', - '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', -] - -test_cert_dir = join_paths(meson.current_source_dir(), 'certs') - test_units = [ 'test-compare', 'test-crypto', @@ -45,7 +27,10 @@ foreach test_unit: test_units libnm_core_nm_default_dep, libnm_base_dep, ], - c_args: c_flags, + c_args: [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE', + ], link_with: libnm_systemd_logging_stub, ) diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 00f6feb..9894064 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -10273,6 +10273,45 @@ test_nm_utils_wifi_ghz_freqs(void) /*****************************************************************************/ +static void +test_vpn_connection_state_reason(void) +{ +#define ASSERT(v1, v2) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT((gint64)(v1) == v2); \ + G_STATIC_ASSERT((gint64)(v2) == v1); \ + \ + nm_assert(((NMActiveConnectionStateReason)(int) (v1)) == v2); \ + nm_assert(((NMVpnConnectionStateReason)(int) (v2)) == v1); \ + } \ + G_STMT_END + + ASSERT(NM_VPN_CONNECTION_STATE_REASON_UNKNOWN, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_NONE, NM_ACTIVE_CONNECTION_STATE_REASON_NONE); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, + NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED); +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -10603,6 +10642,8 @@ main(int argc, char **argv) g_test_add_func("/core/general/test_nm_utils_wifi_ghz_freqs", test_nm_utils_wifi_ghz_freqs); g_test_add_func("/core/general/test_strsplit_quoted", test_strsplit_quoted); + g_test_add_func("/core/general/test_vpn_connection_state_reason", + test_vpn_connection_state_reason); return g_test_run(); } diff --git a/libnm/meson.build b/libnm/meson.build index 27f51df..a9d38f8 100644 --- a/libnm/meson.build +++ b/libnm/meson.build @@ -8,11 +8,6 @@ libnm_nm_default_dep = declare_dependency( dependencies: libnm_core_nm_default_dep, ) -c_flags = [ - '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), - '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', -] - libnm_headers = files( 'NetworkManager.h', 'nm-access-point.h', @@ -70,14 +65,12 @@ install_headers( subdir: libnm_name, ) -enum_types = 'nm-enum-types' - libnm_enum_sources = gnome.mkenums( - enum_types, + 'nm-enum-types', sources: libnm_headers + [nm_version_macro_header], identifier_prefix: nm_id_prefix, - c_template: enum_types + '.c.template', - h_template: enum_types + '.h.template', + c_template: 'nm-enum-types.c.template', + h_template: 'nm-enum-types.h.template', install_header: true, install_dir: libnm_pkgincludedir, ) @@ -137,39 +130,38 @@ libnm_sources = files( 'nm-wimax-nsp.c', ) -deps = [ - libnmdbus_dep, - libnm_libnm_core_intern_dep, - libnm_nm_default_dep, - libnm_keyfile_dep, - libnm_udev_aux_dep, - libudev_dep, -] - -liblibnm = static_library( - 'libnm', +libnm_static = static_library( + 'nm-static', sources: libnm_sources + libnm_enum_sources, - dependencies: deps, - c_args: c_flags, + dependencies: [ + libnmdbus_dep, + libnm_libnm_core_intern_dep, + libnm_nm_default_dep, + libnm_keyfile_dep, + libnm_udev_aux_dep, + libudev_dep, + ], + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', + ], link_with: libnm_systemd_logging_stub, ) -links = [ - liblibnm, - libnm_core, - libnm_keyfile, - libnmdbus, - libnm_systemd_logging_stub, - libnm_utils_base, -] - linker_script = join_paths(meson.current_source_dir(), 'libnm.ver') libnm = shared_library( 'nm', version: libnm_version, dependencies: libnm_nm_default_dep, - link_whole: links, + link_whole: [ + libnm_static, + libnm_core, + libnm_keyfile, + libnmdbus, + libnm_systemd_logging_stub, + libnm_glib_aux, + ], link_args: '-Wl,--version-script,@0@'.format(linker_script), link_depends: linker_script, install: true, @@ -213,7 +205,10 @@ if enable_introspection symbol_prefix: nm_id_prefix.to_lower(), header: 'NetworkManager.h', export_packages: libnm_name, - extra_args: c_flags, + extra_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', + ], install: true, ) @@ -299,10 +294,6 @@ if enable_introspection endif -if enable_tests - subdir('tests') -endif - libnm_libnm_aux = static_library( 'nm-libnm-aux', sources: files( @@ -323,3 +314,7 @@ libnm_libnm_aux_dep = declare_dependency( include_directories: [shared_inc], link_with: [libnm_libnm_aux], ) + +if enable_tests + subdir('tests') +endif diff --git a/libnm/nm-property-infos-ifcfg-rh.xml b/libnm/nm-property-infos-ifcfg-rh.xml index 3d8aaec..e9a39e5 100644 --- a/libnm/nm-property-infos-ifcfg-rh.xml +++ b/libnm/nm-property-infos-ifcfg-rh.xml @@ -198,7 +198,7 @@ When PKEY_ID is specified, PHYSDEV and DEVICE also must be specified."/> - + diff --git a/libnm/tests/meson.build b/libnm/tests/meson.build index 36e2a1b..71dc83e 100644 --- a/libnm/tests/meson.build +++ b/libnm/tests/meson.build @@ -1,35 +1,35 @@ # SPDX-License-Identifier: LGPL-2.1-or-later test_units = [ - ['test-libnm', 30], - ['test-nm-client', 90], - ['test-remote-settings-client', 90], - ['test-secret-agent', 90], -] - -deps = [ - libnm_core_dep, - libnm_nm_default_dep, -] - -c_flags = [ - '-DNETWORKMANAGER_COMPILATION_TEST', - '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', + 'test-libnm', + 'test-nm-client', + 'test-remote-settings-client', + 'test-secret-agent', ] foreach test_unit: test_units exe = executable( - test_unit[0], - [test_unit[0] + '.c', nm_test_utils_impl_source, libnm_enum_sources[1]], - dependencies: deps, - c_args: c_flags, - link_with: liblibnm, + test_unit, + [ + test_unit + '.c', + nm_test_utils_impl_source, + libnm_enum_sources[1], + ], + dependencies: [ + libnm_core_dep, + libnm_nm_default_dep, + ], + c_args: [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', + ], + link_with: libnm_static, ) test( - test_unit[0], + test_unit, test_script, - timeout: test_unit[1], + timeout: 90, args: test_args + [exe.full_path()], ) endforeach @@ -40,5 +40,8 @@ libnm_vpn_plugin_utils_test = static_library( sources: nm_vpn_plugin_utils_source + [libnm_enum_sources[1]], include_directories: libnm_inc, dependencies: libnm_nm_default_dep, - c_args: c_flags, + c_args: [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIBNM', + ], ) diff --git a/man/NetworkManager.8 b/man/NetworkManager.8 index 81e5bb8..88c0857 100644 --- a/man/NetworkManager.8 +++ b/man/NetworkManager.8 @@ -2,12 +2,12 @@ .\" Title: NetworkManager .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Network management daemons -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NETWORKMANAGER" "8" "" "NetworkManager 1\&.29\&.10" "Network management daemons" +.TH "NETWORKMANAGER" "8" "" "NetworkManager 1\&.29\&.11" "Network management daemons" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/NetworkManager.conf.5 b/man/NetworkManager.conf.5 index fd7bd26..ca3e759 100644 --- a/man/NetworkManager.conf.5 +++ b/man/NetworkManager.conf.5 @@ -2,12 +2,12 @@ .\" Title: NetworkManager.conf .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Configuration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NETWORKMANAGER\&.CONF" "5" "" "NetworkManager 1\&.29\&.10" "Configuration" +.TH "NETWORKMANAGER\&.CONF" "5" "" "NetworkManager 1\&.29\&.11" "Configuration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-cloud-setup.8 b/man/nm-cloud-setup.8 index e6095a2..0cd843f 100644 --- a/man/nm-cloud-setup.8 +++ b/man/nm-cloud-setup.8 @@ -2,12 +2,12 @@ .\" Title: nm-cloud-setup .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Automatic Network Configuration in Cloud with NetworkManager -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-CLOUD\-SETUP" "8" "" "NetworkManager 1\&.29\&.10" "Automatic Network Configuratio" +.TH "NM\-CLOUD\-SETUP" "8" "" "NetworkManager 1\&.29\&.11" "Automatic Network Configuratio" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -95,9 +95,13 @@ The dispatcher script will do nothing, unless the systemd service is enabled\&. once\&. .SH "ENVIRONMENT VARIABLES" .PP -The environment variables are used to configure -\fB/usr/libexec/nm\-cloud\-setup\fR\&. You may want to configure them in the systemd service with -\fBsystemctl edit nm\-cloud\-setup\&.service\fR\&. +The following environment variables are used to configure +\fB/usr/libexec/nm\-cloud\-setup\fR\&. You may want to configure them with a drop\-in for the systemd service\&. For example by calling +\fBsystemctl edit nm\-cloud\-setup\&.service\fR +and configuring +[Service] Environment=, as described in +\fBsystemd.exec\fR(5) +manual\&. .sp .RS 4 .ie n \{\ diff --git a/man/nm-cloud-setup.xml b/man/nm-cloud-setup.xml index 2ffee5b..388ef3b 100644 --- a/man/nm-cloud-setup.xml +++ b/man/nm-cloud-setup.xml @@ -161,8 +161,12 @@ Environment Variables - The environment variables are used to configure /usr/libexec/nm-cloud-setup. - You may want to configure them in the systemd service with systemctl edit nm-cloud-setup.service. + The following environment variables are used to configure /usr/libexec/nm-cloud-setup. + You may want to configure them with a drop-in for the systemd service. + For example by calling systemctl edit nm-cloud-setup.service + and configuring [Service] Environment=, as described in + systemd.exec5 + manual. diff --git a/man/nm-initrd-generator.8 b/man/nm-initrd-generator.8 index 765ca03..6278d66 100644 --- a/man/nm-initrd-generator.8 +++ b/man/nm-initrd-generator.8 @@ -2,12 +2,12 @@ .\" Title: nm-initrd-generator .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: System Administration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-INITRD\-GENERATOR" "8" "" "NetworkManager 1\&.29\&.10" "System Administration" +.TH "NM\-INITRD\-GENERATOR" "8" "" "NetworkManager 1\&.29\&.11" "System Administration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-online.1 b/man/nm-online.1 index b4c7a41..be18f7a 100644 --- a/man/nm-online.1 +++ b/man/nm-online.1 @@ -2,12 +2,12 @@ .\" Title: nm-online .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: General Commands Manual -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-ONLINE" "1" "" "NetworkManager 1\&.29\&.10" "General Commands Manual" +.TH "NM\-ONLINE" "1" "" "NetworkManager 1\&.29\&.11" "General Commands Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-openvswitch.7 b/man/nm-openvswitch.7 index d099c8f..2dabd2a 100644 --- a/man/nm-openvswitch.7 +++ b/man/nm-openvswitch.7 @@ -2,12 +2,12 @@ .\" Title: nm-openvswitch .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Open vSwitch support overview -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-OPENVSWITCH" "7" "" "NetworkManager 1\&.29\&.10" "Open vSwitch support overview" +.TH "NM\-OPENVSWITCH" "7" "" "NetworkManager 1\&.29\&.11" "Open vSwitch support overview" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-settings-dbus.5 b/man/nm-settings-dbus.5 index 0b3438f..292ff4c 100644 --- a/man/nm-settings-dbus.5 +++ b/man/nm-settings-dbus.5 @@ -2,12 +2,12 @@ .\" Title: nm-settings-dbus .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Configuration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-SETTINGS\-DBUS" "5" "" "NetworkManager 1\&.29\&.10" "Configuration" +.TH "NM\-SETTINGS\-DBUS" "5" "" "NetworkManager 1\&.29\&.11" "Configuration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-settings-dbus.xml b/man/nm-settings-dbus.xml index 98fe0d7..c65413d 100644 --- a/man/nm-settings-dbus.xml +++ b/man/nm-settings-dbus.xml @@ -1,6 +1,6 @@ -nm-settings-dbusNetworkManager developersnm-settings-dbus5NetworkManagerConfiguration1.29.10nm-settings-dbusDescription of settings and properties of NetworkManager connection profiles on the D-Bus APIDescription +nm-settings-dbusNetworkManager developersnm-settings-dbus5NetworkManagerConfiguration1.29.11nm-settings-dbusDescription of settings and properties of NetworkManager connection profiles on the D-Bus APIDescription NetworkManager is based on a concept of connection profiles, sometimes referred to as connections only. These connection profiles contain a network configuration. When NetworkManager activates a connection profile on a network device the configuration will diff --git a/man/nm-settings-ifcfg-rh.5 b/man/nm-settings-ifcfg-rh.5 index f1167e6..4550e52 100644 --- a/man/nm-settings-ifcfg-rh.5 +++ b/man/nm-settings-ifcfg-rh.5 @@ -2,12 +2,12 @@ .\" Title: nm-settings-ifcfg-rh .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Configuration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-SETTINGS\-IFCFG\-RH" "5" "" "NetworkManager 1\&.29\&.10" "Configuration" +.TH "NM\-SETTINGS\-IFCFG\-RH" "5" "" "NetworkManager 1\&.29\&.11" "Configuration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -1958,7 +1958,7 @@ T} T{ addresses T}:T{ -IPADDR, PREFIX, IPADDR1, PREFIX1, \&.\&.\&. +IPADDR, PREFIX (NETMASK), IPADDR1, PREFIX1 (NETMASK1), \&.\&.\&. T}:T{ \ \& T}:T{ diff --git a/man/nm-settings-ifcfg-rh.xml b/man/nm-settings-ifcfg-rh.xml index eace0f6..278d443 100644 --- a/man/nm-settings-ifcfg-rh.xml +++ b/man/nm-settings-ifcfg-rh.xml @@ -1,6 +1,6 @@ -nm-settings-ifcfg-rhNetworkManager developersnm-settings-ifcfg-rh5NetworkManagerConfiguration1.29.10nm-settings-ifcfg-rhDescription of ifcfg-rh settings pluginDescription +nm-settings-ifcfg-rhNetworkManager developersnm-settings-ifcfg-rh5NetworkManagerConfiguration1.29.11nm-settings-ifcfg-rhDescription of ifcfg-rh settings pluginDescription NetworkManager is based on the concept of connection profiles that contain network configuration (see nm-settings5 for details). The profiles can be stored in various formats. NetworkManager uses plugins for reading and writing @@ -357,7 +357,7 @@ Example: PHYSDEV=ib0none, dhcp (bootp), static, ibft, autoip, shareddnsDNS1, DNS2, ...List of DNS servers. Even if NetworkManager supports many DNS servers, initscripts and resolver only care about the first three, usually. -Example: DNS1=1.2.3.4 DNS2=10.0.0.254 DNS3=8.8.8.8dns-searchDOMAINList of DNS search domains.addressesIPADDR, PREFIX, IPADDR1, PREFIX1, ...List of static IP addresses. +Example: DNS1=1.2.3.4 DNS2=10.0.0.254 DNS3=8.8.8.8dns-searchDOMAINList of DNS search domains.addressesIPADDR, PREFIX (NETMASK), IPADDR1, PREFIX1 (NETMASK1), ...List of static IP addresses. Example: IPADDR=10.5.5.23 PREFIX=24 IPADDR1=1.1.1.2 PREFIX1=16gatewayGATEWAYGateway IP address. diff --git a/man/nm-settings-keyfile.5 b/man/nm-settings-keyfile.5 index a957103..c93ac56 100644 --- a/man/nm-settings-keyfile.5 +++ b/man/nm-settings-keyfile.5 @@ -2,12 +2,12 @@ .\" Title: nm-settings-keyfile .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Configuration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-SETTINGS\-KEYFILE" "5" "" "NetworkManager 1\&.29\&.10" "Configuration" +.TH "NM\-SETTINGS\-KEYFILE" "5" "" "NetworkManager 1\&.29\&.11" "Configuration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-settings-keyfile.xml b/man/nm-settings-keyfile.xml index 99c5115..d02ac6e 100644 --- a/man/nm-settings-keyfile.xml +++ b/man/nm-settings-keyfile.xml @@ -1,6 +1,6 @@ -nm-settings-keyfileNetworkManager developersnm-settings-keyfile5NetworkManagerConfiguration1.29.10nm-settings-keyfileDescription of keyfile settings pluginDescription +nm-settings-keyfileNetworkManager developersnm-settings-keyfile5NetworkManagerConfiguration1.29.11nm-settings-keyfileDescription of keyfile settings pluginDescription NetworkManager is based on the concept of connection profiles that contain network configuration (see nm-settings5 for details). The profiles can be stored in various formats. NetworkManager uses plugins for reading and writing diff --git a/man/nm-settings-nmcli.5 b/man/nm-settings-nmcli.5 index caffcde..9a6b00e 100644 --- a/man/nm-settings-nmcli.5 +++ b/man/nm-settings-nmcli.5 @@ -2,12 +2,12 @@ .\" Title: nm-settings-nmcli .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Configuration -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NM\-SETTINGS\-NMCLI" "5" "" "NetworkManager 1\&.29\&.10" "Configuration" +.TH "NM\-SETTINGS\-NMCLI" "5" "" "NetworkManager 1\&.29\&.11" "Configuration" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nm-settings-nmcli.xml b/man/nm-settings-nmcli.xml index 301b4f7..4db1fac 100644 --- a/man/nm-settings-nmcli.xml +++ b/man/nm-settings-nmcli.xml @@ -1,6 +1,6 @@ -nm-settings-nmcliNetworkManager developersnm-settings-nmcli5NetworkManagerConfiguration1.29.10nm-settings-nmcliDescription of settings and properties of NetworkManager connection profiles for nmcliDescription +nm-settings-nmcliNetworkManager developersnm-settings-nmcli5NetworkManagerConfiguration1.29.11nm-settings-nmcliDescription of settings and properties of NetworkManager connection profiles for nmcliDescription NetworkManager is based on a concept of connection profiles, sometimes referred to as connections only. These connection profiles contain a network configuration. When NetworkManager activates a connection profile on a network device the configuration will diff --git a/man/nmcli-examples.7 b/man/nmcli-examples.7 index 042e796..688478e 100644 --- a/man/nmcli-examples.7 +++ b/man/nmcli-examples.7 @@ -2,12 +2,12 @@ .\" Title: nmcli-examples .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: Examples -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NMCLI\-EXAMPLES" "7" "" "NetworkManager 1\&.29\&.10" "Examples" +.TH "NMCLI\-EXAMPLES" "7" "" "NetworkManager 1\&.29\&.11" "Examples" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nmcli.1 b/man/nmcli.1 index 30a6a78..f7833c1 100644 --- a/man/nmcli.1 +++ b/man/nmcli.1 @@ -2,12 +2,12 @@ .\" Title: nmcli .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: General Commands Manual -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NMCLI" "1" "" "NetworkManager 1\&.29\&.10" "General Commands Manual" +.TH "NMCLI" "1" "" "NetworkManager 1\&.29\&.11" "General Commands Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/man/nmtui.1 b/man/nmtui.1 index 3ca0eec..d82dd95 100644 --- a/man/nmtui.1 +++ b/man/nmtui.1 @@ -2,12 +2,12 @@ .\" Title: nmtui .\" Author: .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 01/27/2021 +.\" Date: 02/08/2021 .\" Manual: General Commands Manual -.\" Source: NetworkManager 1.29.10 +.\" Source: NetworkManager 1.29.11 .\" Language: English .\" -.TH "NMTUI" "1" "" "NetworkManager 1\&.29\&.10" "General Commands Manual" +.TH "NMTUI" "1" "" "NetworkManager 1\&.29\&.11" "General Commands Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff --git a/meson.build b/meson.build index 5b58c96..59ff11b 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,7 @@ project( # - add corresponding NM_VERSION_x_y_z macros in # "shared/nm-version-macros.h.in" # - update number in configure.ac - version: '1.29.10', + version: '1.29.11', license: 'GPL2+', default_options: [ 'buildtype=debugoptimized', @@ -56,7 +56,9 @@ nm_pkgstatedir = join_paths(nm_localstatedir, 'lib', nm_name) nm_vpndir = join_paths(nm_libdir, nm_name) nm_plugindir = join_paths(nm_libdir, nm_name, dist_version) -introspection_extra_cflags = ['-Wno-incompatible-pointer-types-discards-qualifiers'] +introspection_extra_cflags = [ + '-Wno-incompatible-pointer-types-discards-qualifiers', +] libnm_name = 'libnm' @@ -582,6 +584,8 @@ enable_bluez5_dun = get_option('bluez5_dun') if enable_bluez5_dun bluez5_dep = dependency('bluez', version: '>= 5', required: false) assert(bluez5_dep.found(), 'Bluez 5.x development headers are required') +else + bluez5_dep = declare_dependency() endif config_h.set10('WITH_BLUEZ5_DUN', enable_bluez5_dun) @@ -901,7 +905,7 @@ subdir('introspection') subdir('shared') subdir('libnm-core') subdir('libnm') -subdir('src') +subdir('src/core') subdir('dispatcher') subdir('clients') subdir('data') diff --git a/po/POTFILES.in b/po/POTFILES.in index b3d3348..e48d9a8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -148,38 +148,38 @@ libnm/nm-vpn-service-plugin.c data/org.freedesktop.NetworkManager.policy.in.in shared/nm-glib-aux/nm-shared-utils.c shared/nm-log-core/nm-logging.c -src/NetworkManagerUtils.c -src/main.c -src/main-utils.c -src/dhcp/nm-dhcp-dhclient.c -src/dhcp/nm-dhcp-dhclient-utils.c -src/dhcp/nm-dhcp-manager.c -src/dns/nm-dns-manager.c -src/devices/adsl/nm-device-adsl.c -src/devices/bluetooth/nm-bluez-manager.c -src/devices/bluetooth/nm-device-bt.c -src/devices/nm-device-6lowpan.c -src/devices/nm-device-bond.c -src/devices/nm-device-bridge.c -src/devices/nm-device-dummy.c -src/devices/nm-device-ethernet.c -src/devices/nm-device-ethernet-utils.c -src/devices/nm-device-infiniband.c -src/devices/nm-device-ip-tunnel.c -src/devices/nm-device-macvlan.c -src/devices/nm-device-tun.c -src/devices/nm-device-veth.c -src/devices/nm-device-vlan.c -src/devices/nm-device-vrf.c -src/devices/nm-device-vxlan.c -src/devices/nm-device-wpan.c -src/devices/team/nm-device-team.c -src/devices/wifi/nm-device-olpc-mesh.c -src/devices/wifi/nm-device-wifi.c -src/devices/wifi/nm-wifi-utils.c -src/devices/wwan/nm-modem-broadband.c -src/nm-config.c -src/nm-iface-helper.c -src/nm-manager.c -src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c -src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +src/core/NetworkManagerUtils.c +src/core/main.c +src/core/main-utils.c +src/core/dhcp/nm-dhcp-dhclient.c +src/core/dhcp/nm-dhcp-dhclient-utils.c +src/core/dhcp/nm-dhcp-manager.c +src/core/dns/nm-dns-manager.c +src/core/devices/adsl/nm-device-adsl.c +src/core/devices/bluetooth/nm-bluez-manager.c +src/core/devices/bluetooth/nm-device-bt.c +src/core/devices/nm-device-6lowpan.c +src/core/devices/nm-device-bond.c +src/core/devices/nm-device-bridge.c +src/core/devices/nm-device-dummy.c +src/core/devices/nm-device-ethernet.c +src/core/devices/nm-device-ethernet-utils.c +src/core/devices/nm-device-infiniband.c +src/core/devices/nm-device-ip-tunnel.c +src/core/devices/nm-device-macvlan.c +src/core/devices/nm-device-tun.c +src/core/devices/nm-device-veth.c +src/core/devices/nm-device-vlan.c +src/core/devices/nm-device-vrf.c +src/core/devices/nm-device-vxlan.c +src/core/devices/nm-device-wpan.c +src/core/devices/team/nm-device-team.c +src/core/devices/wifi/nm-device-olpc-mesh.c +src/core/devices/wifi/nm-device-wifi.c +src/core/devices/wifi/nm-wifi-utils.c +src/core/devices/wwan/nm-modem-broadband.c +src/core/nm-config.c +src/core/nm-iface-helper.c +src/core/nm-manager.c +src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c diff --git a/shared/meson.build b/shared/meson.build index 58c29b7..86a50dd 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -2,9 +2,15 @@ shared_inc = include_directories('.') -nm_default_dep = declare_dependency(include_directories: [top_inc, shared_inc]) - -glib_nm_default_dep = declare_dependency(dependencies: [glib_dep, nm_default_dep]) +glib_nm_default_dep = declare_dependency( + include_directories: [ + top_inc, + shared_inc, + ], + dependencies: [ + glib_dep, + ], +) libc_siphash = static_library( 'c-siphash', @@ -19,44 +25,37 @@ libc_rbtree = static_library( c_args: '-std=c11', ) -sources = files( - 'n-acd/src/n-acd.c', - 'n-acd/src/n-acd-probe.c', - 'n-acd/src/util/timer.c', -) - if enable_ebpf - sources += files('n-acd/src/n-acd-bpf.c') + n_acd_bpf_source = 'n-acd/src/n-acd-bpf.c' else - sources += files('n-acd/src/n-acd-bpf-fallback.c') + n_acd_bpf_source = 'n-acd/src/n-acd-bpf-fallback.c' endif -incs = include_directories( - 'c-list/src', - 'c-rbtree/src', - 'c-siphash/src', - 'c-stdaux/src', -) - -c_flags = [ - '-D_GNU_SOURCE', - '-DSO_ATTACH_BPF=50', - '-std=c11', - '-Wno-pointer-arith', - '-Wno-vla', -] - -links = [ - libc_rbtree, - libc_siphash, -] - libn_acd = static_library( 'n-acd', - sources: sources, - include_directories: incs, - c_args: c_flags, - link_with: links, + sources: files( + 'n-acd/src/n-acd.c', + 'n-acd/src/n-acd-probe.c', + 'n-acd/src/util/timer.c', + n_acd_bpf_source, + ), + include_directories: include_directories( + 'c-list/src', + 'c-rbtree/src', + 'c-siphash/src', + 'c-stdaux/src', + ), + c_args: [ + '-D_GNU_SOURCE', + '-DSO_ATTACH_BPF=50', + '-std=c11', + '-Wno-pointer-arith', + '-Wno-vla', + ], + link_with: [ + libc_rbtree, + libc_siphash, + ], ) libn_acd_dep = declare_dependency( @@ -64,35 +63,29 @@ libn_acd_dep = declare_dependency( link_with: libn_acd, ) -sources = files( - 'n-dhcp4/src/n-dhcp4-c-connection.c', - 'n-dhcp4/src/n-dhcp4-c-lease.c', - 'n-dhcp4/src/n-dhcp4-client.c', - 'n-dhcp4/src/n-dhcp4-c-probe.c', - 'n-dhcp4/src/n-dhcp4-incoming.c', - 'n-dhcp4/src/n-dhcp4-outgoing.c', - 'n-dhcp4/src/n-dhcp4-socket.c', - 'n-dhcp4/src/util/packet.c', - 'n-dhcp4/src/util/socket.c', -) - -incs = include_directories( - 'c-list/src', - 'c-siphash/src', - 'c-stdaux/src', -) - -c_flags = [ - '-D_GNU_SOURCE', - '-Wno-declaration-after-statement', - '-Wno-pointer-arith', -] - libn_dhcp4 = static_library( 'n-dhcp4', - sources: sources, - c_args: c_flags, - include_directories: incs, + sources: files( + 'n-dhcp4/src/n-dhcp4-c-connection.c', + 'n-dhcp4/src/n-dhcp4-c-lease.c', + 'n-dhcp4/src/n-dhcp4-client.c', + 'n-dhcp4/src/n-dhcp4-c-probe.c', + 'n-dhcp4/src/n-dhcp4-incoming.c', + 'n-dhcp4/src/n-dhcp4-outgoing.c', + 'n-dhcp4/src/n-dhcp4-socket.c', + 'n-dhcp4/src/util/packet.c', + 'n-dhcp4/src/util/socket.c', + ), + c_args: [ + '-D_GNU_SOURCE', + '-Wno-declaration-after-statement', + '-Wno-pointer-arith', + ], + include_directories: include_directories( + 'c-list/src', + 'c-siphash/src', + 'c-stdaux/src', + ), link_with: libc_siphash, ) @@ -126,47 +119,40 @@ libnm_std_aux = static_library( ], ) -sources = files( - 'nm-glib-aux/nm-dbus-aux.c', - 'nm-glib-aux/nm-dedup-multi.c', - 'nm-glib-aux/nm-enum-utils.c', - 'nm-glib-aux/nm-errno.c', - 'nm-glib-aux/nm-hash-utils.c', - 'nm-glib-aux/nm-io-utils.c', - 'nm-glib-aux/nm-json-aux.c', - 'nm-glib-aux/nm-keyfile-aux.c', - 'nm-glib-aux/nm-logging-base.c', - 'nm-glib-aux/nm-random-utils.c', - 'nm-glib-aux/nm-ref-string.c', - 'nm-glib-aux/nm-secret-utils.c', - 'nm-glib-aux/nm-shared-utils.c', - 'nm-glib-aux/nm-time-utils.c', -) - -c_flags = [ - '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), - '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', -] - -links = [ - libc_siphash, - libnm_std_aux, -] - -libnm_utils_base = static_library( - 'nm-utils-base', - sources: sources, +libnm_glib_aux = static_library( + 'nm-glib-aux', + sources: files( + 'nm-glib-aux/nm-dbus-aux.c', + 'nm-glib-aux/nm-dedup-multi.c', + 'nm-glib-aux/nm-enum-utils.c', + 'nm-glib-aux/nm-errno.c', + 'nm-glib-aux/nm-hash-utils.c', + 'nm-glib-aux/nm-io-utils.c', + 'nm-glib-aux/nm-json-aux.c', + 'nm-glib-aux/nm-keyfile-aux.c', + 'nm-glib-aux/nm-logging-base.c', + 'nm-glib-aux/nm-random-utils.c', + 'nm-glib-aux/nm-ref-string.c', + 'nm-glib-aux/nm-secret-utils.c', + 'nm-glib-aux/nm-shared-utils.c', + 'nm-glib-aux/nm-time-utils.c', + ), dependencies: glib_nm_default_dep, - c_args: c_flags, - link_with: links, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', + ], + link_with: [ + libc_siphash, + libnm_std_aux, + ], ) -libnm_utils_base_dep = declare_dependency( +libnm_glib_aux_dep = declare_dependency( dependencies: glib_nm_default_dep, - link_with: libnm_utils_base, + link_with: libnm_glib_aux, ) - libnm_udev_aux = static_library( 'nm-udev-aux', sources: 'nm-udev-aux/nm-udev-utils.c', @@ -174,7 +160,10 @@ libnm_udev_aux = static_library( glib_nm_default_dep, libudev_dep, ], - c_args: c_flags, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', + ], ) libnm_udev_aux_dep = declare_dependency( @@ -187,13 +176,16 @@ libnm_base = static_library( sources: files( 'nm-base/nm-ethtool-base.c', ), - dependencies: libnm_utils_base_dep, - c_args: c_flags, + dependencies: libnm_glib_aux_dep, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)', + ], ) libnm_base_dep = declare_dependency( include_directories: shared_inc, - dependencies: libnm_utils_base_dep, + dependencies: libnm_glib_aux_dep, link_with: libnm_base, ) @@ -213,7 +205,7 @@ libnm_log_core = static_library( libnm_log_core_dep = declare_dependency( include_directories: shared_inc, dependencies: [ - libnm_utils_base_dep, + libnm_glib_aux_dep, ], link_with: libnm_log_core, ) @@ -237,73 +229,71 @@ libnm_platform = static_library( libnm_platform_dep = declare_dependency( include_directories: shared_inc, dependencies: [ - libnm_utils_base_dep, + libnm_glib_aux_dep, ], link_with: libnm_platform, ) -sources = files( - 'systemd/nm-sd-utils-shared.c', - 'systemd/src/basic/alloc-util.c', - 'systemd/src/basic/env-file.c', - 'systemd/src/basic/env-util.c', - 'systemd/src/basic/escape.c', - 'systemd/src/basic/ether-addr-util.c', - 'systemd/src/basic/extract-word.c', - 'systemd/src/basic/fd-util.c', - 'systemd/src/basic/fileio.c', - 'systemd/src/basic/format-util.c', - 'systemd/src/basic/fs-util.c', - 'systemd/src/basic/hash-funcs.c', - 'systemd/src/basic/hashmap.c', - 'systemd/src/basic/hexdecoct.c', - 'systemd/src/basic/hostname-util.c', - 'systemd/src/basic/in-addr-util.c', - 'systemd/src/basic/io-util.c', - 'systemd/src/basic/memory-util.c', - 'systemd/src/basic/mempool.c', - 'systemd/src/basic/parse-util.c', - 'systemd/src/basic/path-util.c', - 'systemd/src/basic/prioq.c', - 'systemd/src/basic/process-util.c', - 'systemd/src/basic/random-util.c', - 'systemd/src/basic/ratelimit.c', - 'systemd/src/basic/signal-util.c', - 'systemd/src/basic/socket-util.c', - 'systemd/src/basic/stat-util.c', - 'systemd/src/basic/string-table.c', - 'systemd/src/basic/string-util.c', - 'systemd/src/basic/strv.c', - 'systemd/src/basic/strxcpyx.c', - 'systemd/src/basic/time-util.c', - 'systemd/src/basic/tmpfile-util.c', - 'systemd/src/basic/utf8.c', - 'systemd/src/basic/util.c', - 'systemd/src/shared/dns-domain.c', - 'systemd/src/shared/web-util.c', -) - -incs = include_directories( - 'systemd/sd-adapt-shared', - 'systemd/src/basic', - 'systemd/src/shared', -) - -c_flags = [ - '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), - '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED', -] - libnm_systemd_shared = static_library( 'nm-systemd-shared', - sources: sources, - include_directories: incs, + sources: files( + 'systemd/nm-sd-utils-shared.c', + 'systemd/src/basic/alloc-util.c', + 'systemd/src/basic/env-file.c', + 'systemd/src/basic/env-util.c', + 'systemd/src/basic/escape.c', + 'systemd/src/basic/ether-addr-util.c', + 'systemd/src/basic/extract-word.c', + 'systemd/src/basic/fd-util.c', + 'systemd/src/basic/fileio.c', + 'systemd/src/basic/format-util.c', + 'systemd/src/basic/fs-util.c', + 'systemd/src/basic/hash-funcs.c', + 'systemd/src/basic/hashmap.c', + 'systemd/src/basic/hexdecoct.c', + 'systemd/src/basic/hostname-util.c', + 'systemd/src/basic/in-addr-util.c', + 'systemd/src/basic/io-util.c', + 'systemd/src/basic/memory-util.c', + 'systemd/src/basic/mempool.c', + 'systemd/src/basic/parse-util.c', + 'systemd/src/basic/path-util.c', + 'systemd/src/basic/prioq.c', + 'systemd/src/basic/process-util.c', + 'systemd/src/basic/random-util.c', + 'systemd/src/basic/ratelimit.c', + 'systemd/src/basic/signal-util.c', + 'systemd/src/basic/socket-util.c', + 'systemd/src/basic/stat-util.c', + 'systemd/src/basic/string-table.c', + 'systemd/src/basic/string-util.c', + 'systemd/src/basic/strv.c', + 'systemd/src/basic/strxcpyx.c', + 'systemd/src/basic/time-util.c', + 'systemd/src/basic/tmpfile-util.c', + 'systemd/src/basic/utf8.c', + 'systemd/src/basic/util.c', + 'systemd/src/shared/dns-domain.c', + 'systemd/src/shared/web-util.c', + ), + include_directories: include_directories( + 'systemd/sd-adapt-shared', + 'systemd/src/basic', + 'systemd/src/shared', + ), dependencies: glib_nm_default_dep, - c_args: c_flags, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED', + ], ) libnm_systemd_shared_dep = declare_dependency( - include_directories: incs, + include_directories: include_directories( + 'systemd/sd-adapt-shared', + 'systemd/src/basic', + 'systemd/src/shared', + ), dependencies: glib_dep, link_with: libnm_systemd_shared, ) @@ -312,7 +302,10 @@ libnm_systemd_logging_stub = static_library( 'nm-systemd-logging-stub', sources: 'systemd/nm-logging-stub.c', dependencies: glib_nm_default_dep, - c_args: c_flags, + c_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED', + ], ) if enable_tests diff --git a/shared/nm-default.h b/shared/nm-default.h index f2cb024..c285f3e 100644 --- a/shared/nm-default.h +++ b/shared/nm-default.h @@ -196,72 +196,7 @@ /*****************************************************************************/ - #if NM_MORE_ASSERTS == 0 - -/* glib assertions (g_return_*(), g_assert*()) contain a textual representation - * of the checked statement. This part of the assertion blows up the size of the - * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these - * parts. Note that the failed assertion still prints the file and line where the - * assertion fails. That shall suffice. */ - -static inline void -_nm_g_return_if_fail_warning(const char *log_domain, const char *file, int line) -{ - char file_buf[256 + 15]; - - g_snprintf(file_buf, sizeof(file_buf), "((%s:%d))", file, line); - g_return_if_fail_warning(log_domain, file_buf, ""); -} - - #define g_return_if_fail_warning(log_domain, pretty_function, expression) \ - _nm_g_return_if_fail_warning(log_domain, __FILE__, __LINE__) - - #define g_assertion_message_expr(domain, file, line, func, expr) \ - g_assertion_message_expr(domain, \ - file, \ - line, \ - "", \ - (expr) ? "" : NULL) - - #undef g_return_val_if_reached - #define g_return_val_if_reached(val) \ - G_STMT_START \ - { \ - g_log(G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "file %s: line %d (%s): should not be reached", \ - __FILE__, \ - __LINE__, \ - ""); \ - return (val); \ - } \ - G_STMT_END - - #undef g_return_if_reached - #define g_return_if_reached() \ - G_STMT_START \ - { \ - g_log(G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "file %s: line %d (%s): should not be reached", \ - __FILE__, \ - __LINE__, \ - ""); \ - return; \ - } \ - G_STMT_END - - #define NM_ASSERT_G_RETURN_EXPR(expr) "" - #define NM_ASSERT_NO_MSG 1 - - #else - - #define NM_ASSERT_G_RETURN_EXPR(expr) "" expr "" - #define NM_ASSERT_NO_MSG 0 - - #endif - - /*****************************************************************************/ + #include "nm-gassert-patch.h" #include "nm-std-aux/nm-std-aux.h" #include "nm-std-aux/nm-std-utils.h" diff --git a/shared/nm-gassert-patch.h b/shared/nm-gassert-patch.h new file mode 100644 index 0000000..bac8697 --- /dev/null +++ b/shared/nm-gassert-patch.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_GASSERT_PATCH_H__ +#define __NM_GASSERT_PATCH_H__ + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + +/* glib assertions (g_return_*(), g_assert*()) contain a textual representation + * of the checked statement. This part of the assertion blows up the size of the + * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these + * parts. Note that the failed assertion still prints the file and line where the + * assertion fails. That shall suffice. */ + +static inline void +_nm_g_return_if_fail_warning(const char *log_domain, const char *file, int line) +{ + char file_buf[256 + 15]; + + g_snprintf(file_buf, sizeof(file_buf), "((%s:%d))", file, line); + g_return_if_fail_warning(log_domain, file_buf, ""); +} + + #define g_return_if_fail_warning(log_domain, pretty_function, expression) \ + _nm_g_return_if_fail_warning(log_domain, __FILE__, __LINE__) + + #define g_assertion_message_expr(domain, file, line, func, expr) \ + g_assertion_message_expr(domain, file, line, "", (expr) ? "" : NULL) + + #undef g_return_val_if_reached + #define g_return_val_if_reached(val) \ + G_STMT_START \ + { \ + g_log(G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + ""); \ + return (val); \ + } \ + G_STMT_END + + #undef g_return_if_reached + #define g_return_if_reached() \ + G_STMT_START \ + { \ + g_log(G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + ""); \ + return; \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + #define NM_ASSERT_G_RETURN_EXPR(expr) "" + #define NM_ASSERT_NO_MSG 1 + +#else + #define NM_ASSERT_G_RETURN_EXPR(expr) "" expr "" + #define NM_ASSERT_NO_MSG 0 +#endif + +/*****************************************************************************/ + +#endif /* __NM_GASSERT_PATCH_H__ */ diff --git a/shared/nm-glib-aux/tests/meson.build b/shared/nm-glib-aux/tests/meson.build index 3e3b6a5..d1f23ee 100644 --- a/shared/nm-glib-aux/tests/meson.build +++ b/shared/nm-glib-aux/tests/meson.build @@ -7,7 +7,7 @@ exe = executable( '-DNETWORKMANAGER_COMPILATION_TEST', '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)', ], - dependencies: libnm_utils_base_dep, + dependencies: libnm_glib_aux_dep, link_with: libnm_systemd_logging_stub, ) @@ -27,7 +27,7 @@ if jansson_dep.found() '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)', ], dependencies: [ - libnm_utils_base_dep, + libnm_glib_aux_dep, jansson_dep, dl_dep, ], diff --git a/shared/nm-udev-aux/nm-udev-utils.c b/shared/nm-udev-aux/nm-udev-utils.c index 3e78cc0..f7e8e6d 100644 --- a/shared/nm-udev-aux/nm-udev-utils.c +++ b/shared/nm-udev-aux/nm-udev-utils.c @@ -241,11 +241,11 @@ nm_udev_client_new(const char *const *subsystems, return self; fail: - return nm_udev_client_unref(self); + return nm_udev_client_destroy(self); } NMUdevClient * -nm_udev_client_unref(NMUdevClient *self) +nm_udev_client_destroy(NMUdevClient *self) { if (!self) return NULL; diff --git a/shared/nm-udev-aux/nm-udev-utils.h b/shared/nm-udev-aux/nm-udev-utils.h index cdf9b83..191f9a8 100644 --- a/shared/nm-udev-aux/nm-udev-utils.h +++ b/shared/nm-udev-aux/nm-udev-utils.h @@ -24,7 +24,7 @@ NMUdevClient *nm_udev_client_new(const char *const *subsystems, NMUdevClientEvent event_handler, gpointer event_user_data); -NMUdevClient *nm_udev_client_unref(NMUdevClient *self); +NMUdevClient *nm_udev_client_destroy(NMUdevClient *self); struct udev *nm_udev_client_get_udev(NMUdevClient *self); diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index 78463c9..ac13cda 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -30,7 +30,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (10) +#define NM_MICRO_VERSION (11) /** * NM_CHECK_VERSION: diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c new file mode 100644 index 0000000..d55df95 --- /dev/null +++ b/src/core/NetworkManagerUtils.c @@ -0,0 +1,1831 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2016 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "NetworkManagerUtils.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-c-list.h" + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-core-internal.h" +#include "platform/nmp-object.h" + +#include "platform/nm-platform.h" +#include "nm-auth-utils.h" +#include "systemd/nm-sd-utils-shared.h" + +/*****************************************************************************/ + +/** + * nm_utils_get_shared_wifi_permission: + * @connection: the NMConnection to lookup the permission. + * + * Returns: a static string of the wifi-permission (if any) or %NULL. + */ +const char * +nm_utils_get_shared_wifi_permission(NMConnection *connection) +{ + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + const char * method; + + method = nm_utils_get_ip_config_method(connection, AF_INET); + if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + return NULL; + + s_wifi = nm_connection_get_setting_wireless(connection); + if (s_wifi) { + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (s_wsec) + return NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED; + else + return NM_AUTH_PERMISSION_WIFI_SHARE_OPEN; + } + + return NULL; +} + +/*****************************************************************************/ + +static char * +get_new_connection_name(NMConnection *const *existing_connections, + const char * preferred, + const char * fallback_prefix) +{ + gs_free const char **existing_names = NULL; + guint i, existing_len = 0; + + g_assert(fallback_prefix); + + if (existing_connections) { + existing_len = NM_PTRARRAY_LEN(existing_connections); + existing_names = g_new(const char *, existing_len); + for (i = 0; i < existing_len; i++) { + NMConnection *candidate; + const char * id; + + candidate = existing_connections[i]; + nm_assert(NM_IS_CONNECTION(candidate)); + + id = nm_connection_get_id(candidate); + nm_assert(id); + + existing_names[i] = id; + + if (preferred && nm_streq(preferred, id)) { + /* the preferred name is already taken. Forget about it. */ + preferred = NULL; + } + } + nm_assert(!existing_connections[i]); + } + + /* Return the preferred name if it was unique */ + if (preferred) + return g_strdup(preferred); + + /* Otherwise, find the next available unique connection name using the given + * connection name template. + */ + for (i = 1; TRUE; i++) { + char *temp; + + /* TRANSLATORS: the first %s is a prefix for the connection id, such + * as "Wired Connection" or "VPN Connection". The %d is a number + * that is combined with the first argument to create a unique + * connection id. */ + temp = g_strdup_printf(C_("connection id fallback", "%s %u"), fallback_prefix, i); + + if (nm_utils_strv_find_first((char **) existing_names, existing_len, temp) < 0) + return temp; + + g_free(temp); + } +} + +static char * +get_new_connection_ifname(NMPlatform * platform, + NMConnection *const *existing_connections, + const char * prefix) +{ + guint i, j; + + for (i = 0; TRUE; i++) { + char *name; + + name = g_strdup_printf("%s%d", prefix, i); + + if (nm_platform_link_get_by_ifname(platform, name)) + goto next; + + if (existing_connections) { + for (j = 0; existing_connections[j]; j++) { + if (nm_streq0(nm_connection_get_interface_name(existing_connections[j]), name)) + goto next; + } + } + + return name; + +next: + g_free(name); + } +} + +const char * +nm_utils_get_ip_config_method(NMConnection *connection, int addr_family) +{ + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip; + const char * method; + + s_con = nm_connection_get_setting_connection(connection); + + if (NM_IS_IPv4(addr_family)) { + g_return_val_if_fail(s_con != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip = nm_connection_get_setting_ip4_config(connection); + if (!s_ip) + return NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + + method = nm_setting_ip_config_get_method(s_ip); + g_return_val_if_fail(method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + return method; + } + + g_return_val_if_fail(s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + s_ip = nm_connection_get_setting_ip6_config(connection); + if (!s_ip) + return NM_SETTING_IP6_CONFIG_METHOD_IGNORE; + + method = nm_setting_ip_config_get_method(s_ip); + g_return_val_if_fail(method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + return method; +} + +gboolean +nm_utils_connection_has_default_route(NMConnection *connection, + int addr_family, + gboolean * out_is_never_default) +{ + const char * method; + NMSettingIPConfig *s_ip; + gboolean is_never_default = FALSE; + gboolean has_default_route = FALSE; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); + + if (!connection) + goto out; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (!s_ip) + goto out; + if (nm_setting_ip_config_get_never_default(s_ip)) { + is_never_default = TRUE; + goto out; + } + + method = nm_utils_get_ip_config_method(connection, addr_family); + if (NM_IS_IPv4(addr_family)) { + if (NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) + goto out; + } else { + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) + goto out; + } + + has_default_route = TRUE; +out: + NM_SET_OUT(out_is_never_default, is_never_default); + return has_default_route; +} + +/*****************************************************************************/ + +void +nm_utils_complete_generic(NMPlatform * platform, + NMConnection * connection, + const char * ctype, + NMConnection *const *existing_connections, + const char * preferred_id, + const char * fallback_id_prefix, + const char * ifname_prefix, + const char * ifname, + gboolean default_enable_ipv6) +{ + NMSettingConnection *s_con; + char * id, *generated_ifname; + GHashTable * parameters; + + g_assert(fallback_id_prefix); + g_return_if_fail(ifname_prefix == NULL || ifname == NULL); + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + } + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_TYPE, ctype, NULL); + + if (!nm_setting_connection_get_uuid(s_con)) { + char uuid[37]; + + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_buf(uuid), + NULL); + } + + /* Add a connection ID if absent */ + if (!nm_setting_connection_get_id(s_con)) { + id = get_new_connection_name(existing_connections, preferred_id, fallback_id_prefix); + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_ID, id, NULL); + g_free(id); + } + + /* Add an interface name, if requested */ + if (ifname) { + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL); + } else if (ifname_prefix && !nm_setting_connection_get_interface_name(s_con)) { + generated_ifname = get_new_connection_ifname(platform, existing_connections, ifname_prefix); + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, generated_ifname, NULL); + g_free(generated_ifname); + } + + /* Normalize */ + parameters = g_hash_table_new(nm_str_hash, g_str_equal); + g_hash_table_insert(parameters, + NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD, + default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO + : NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + nm_connection_normalize(connection, parameters, NULL, NULL); + g_hash_table_destroy(parameters); +} + +/*****************************************************************************/ + +static GHashTable * +check_property_in_hash(GHashTable *hash, const char *s_name, const char *p_name) +{ + GHashTable *props; + + props = g_hash_table_lookup(hash, s_name); + if (!props || !g_hash_table_lookup(props, p_name)) { + return NULL; + } + return props; +} + +static void +remove_from_hash(GHashTable *s_hash, GHashTable *p_hash, const char *s_name, const char *p_name) +{ + if (!p_hash) + return; + + g_hash_table_remove(p_hash, p_name); + if (g_hash_table_size(p_hash) == 0) + g_hash_table_remove(s_hash, s_name); +} + +static gboolean +check_ip6_method(NMConnection *orig, NMConnection *candidate, GHashTable *settings) +{ + GHashTable * props; + const char * orig_ip6_method, *candidate_ip6_method; + NMSettingIPConfig *candidate_ip6; + gboolean allow = FALSE; + + props = check_property_in_hash(settings, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + if (!props) + return TRUE; + + orig_ip6_method = nm_utils_get_ip_config_method(orig, AF_INET6); + candidate_ip6_method = nm_utils_get_ip_config_method(candidate, AF_INET6); + candidate_ip6 = nm_connection_get_setting_ip6_config(candidate); + + if (nm_streq(orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) + && nm_streq(candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) + && (!candidate_ip6 || nm_setting_ip_config_get_may_fail(candidate_ip6))) { + /* If the generated connection is 'link-local' and the candidate is both 'auto' + * and may-fail=TRUE, then the candidate is OK to use. may-fail is included + * in the decision because if the candidate is 'auto' but may-fail=FALSE, then + * the connection could not possibly have been previously activated on the + * device if the device has no non-link-local IPv6 address. + */ + allow = TRUE; + } else if (NM_IN_STRSET(orig_ip6_method, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_AUTO) + && nm_streq0(candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { + /* If the generated connection method is 'link-local', disabled' or 'auto' and + * the candidate method is 'ignore' we can take the connection, because NM didn't + * simply take care of IPv6. + */ + allow = TRUE; + } + + if (allow) { + remove_from_hash(settings, + props, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + } + + return allow; +} + +static int +route_compare(NMIPRoute *route1, NMIPRoute *route2, gint64 default_metric) +{ + NMIPAddr a1; + NMIPAddr a2; + guint64 m1; + guint64 m2; + int family; + guint plen; + + family = nm_ip_route_get_family(route1); + NM_CMP_DIRECT(family, nm_ip_route_get_family(route2)); + + nm_assert_addr_family(family); + + plen = nm_ip_route_get_prefix(route1); + NM_CMP_DIRECT(plen, nm_ip_route_get_prefix(route2)); + + m1 = nm_ip_route_get_metric(route1); + m2 = nm_ip_route_get_metric(route2); + NM_CMP_DIRECT(m1 == -1 ? default_metric : m1, m2 == -1 ? default_metric : m2); + + NM_CMP_DIRECT_STRCMP0(nm_ip_route_get_next_hop(route1), nm_ip_route_get_next_hop(route2)); + + if (!inet_pton(family, nm_ip_route_get_dest(route1), &a1)) + nm_assert_not_reached(); + if (!inet_pton(family, nm_ip_route_get_dest(route2), &a2)) + nm_assert_not_reached(); + nm_utils_ipx_address_clear_host_address(family, &a1, NULL, plen); + nm_utils_ipx_address_clear_host_address(family, &a2, NULL, plen); + NM_CMP_DIRECT_MEMCMP(&a1, &a2, nm_utils_addr_family_to_size(family)); + + return 0; +} + +static int +route_ptr_compare(const void *a, const void *b, gpointer metric) +{ + return route_compare(*(NMIPRoute **) a, *(NMIPRoute **) b, *((gint64 *) metric)); +} + +static gboolean +check_ip_routes(NMConnection *orig, + NMConnection *candidate, + GHashTable * settings, + gint64 default_metric, + gboolean v4) +{ + gs_free NMIPRoute **routes1 = NULL; + NMIPRoute ** routes2; + NMSettingIPConfig * s_ip1, *s_ip2; + gint64 m; + const char * s_name; + GHashTable * props; + guint i, i1, i2, num1, num2; + const guint8 PLEN = v4 ? 32 : 128; + + s_name = v4 ? NM_SETTING_IP4_CONFIG_SETTING_NAME : NM_SETTING_IP6_CONFIG_SETTING_NAME; + + props = check_property_in_hash(settings, s_name, NM_SETTING_IP_CONFIG_ROUTES); + if (!props) + return TRUE; + + s_ip1 = (NMSettingIPConfig *) nm_connection_get_setting_by_name(orig, s_name); + s_ip2 = (NMSettingIPConfig *) nm_connection_get_setting_by_name(candidate, s_name); + + if (!s_ip1 || !s_ip2) + return FALSE; + + num1 = nm_setting_ip_config_get_num_routes(s_ip1); + num2 = nm_setting_ip_config_get_num_routes(s_ip2); + + routes1 = g_new(NMIPRoute *, (gsize) num1 + num2); + routes2 = &routes1[num1]; + + for (i = 0; i < num1; i++) + routes1[i] = nm_setting_ip_config_get_route(s_ip1, i); + for (i = 0; i < num2; i++) + routes2[i] = nm_setting_ip_config_get_route(s_ip2, i); + + m = nm_setting_ip_config_get_route_metric(s_ip2); + if (m != -1) + default_metric = m; + + g_qsort_with_data(routes1, num1, sizeof(NMIPRoute *), route_ptr_compare, &default_metric); + g_qsort_with_data(routes2, num2, sizeof(NMIPRoute *), route_ptr_compare, &default_metric); + + for (i1 = 0, i2 = 0; i2 < num2; i1++) { + if (i1 >= num1) + return FALSE; + if (route_compare(routes1[i1], routes2[i2], default_metric) == 0) { + i2++; + continue; + } + + /* if @orig (@routes1) contains /32 routes that are missing in @candidate, + * we accept that. + * + * A /32 may have been added automatically, as a direct-route to the gateway. + * The generated connection (@orig) would contain that route, so we shall ignore + * it. + * + * Likeweise for /128 for IPv6. */ + if (nm_ip_route_get_prefix(routes1[i1]) == PLEN) + continue; + + return FALSE; + } + + /* check that @orig has no left-over (except host routes that we ignore). */ + for (; i1 < num1; i1++) { + if (nm_ip_route_get_prefix(routes1[i1]) != PLEN) + return FALSE; + } + + remove_from_hash(settings, props, s_name, NM_SETTING_IP_CONFIG_ROUTES); + return TRUE; +} + +static gboolean +check_ip4_method(NMConnection *orig, + NMConnection *candidate, + GHashTable * settings, + gboolean device_has_carrier) +{ + GHashTable * props; + const char * orig_ip4_method, *candidate_ip4_method; + NMSettingIPConfig *candidate_ip4; + + props = check_property_in_hash(settings, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + if (!props) + return TRUE; + + orig_ip4_method = nm_utils_get_ip_config_method(orig, AF_INET); + candidate_ip4_method = nm_utils_get_ip_config_method(candidate, AF_INET); + candidate_ip4 = nm_connection_get_setting_ip4_config(candidate); + + if (nm_streq(orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) + && nm_streq(candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) + && (!candidate_ip4 || nm_setting_ip_config_get_may_fail(candidate_ip4)) + && !device_has_carrier) { + /* If the generated connection is 'disabled' (device had no IP addresses) + * but it has no carrier, that most likely means that IP addressing could + * not complete and thus no IP addresses were assigned. In that case, allow + * matching to the "auto" method. + */ + remove_from_hash(settings, + props, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return TRUE; + } + return FALSE; +} + +static gboolean +check_connection_interface_name(NMConnection *orig, NMConnection *candidate, GHashTable *settings) +{ + GHashTable * props; + const char * orig_ifname, *cand_ifname; + NMSettingConnection *s_con_orig, *s_con_cand; + + props = check_property_in_hash(settings, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME); + if (!props) + return TRUE; + + /* If one of the interface names is NULL, we accept that connection */ + s_con_orig = nm_connection_get_setting_connection(orig); + s_con_cand = nm_connection_get_setting_connection(candidate); + orig_ifname = nm_setting_connection_get_interface_name(s_con_orig); + cand_ifname = nm_setting_connection_get_interface_name(s_con_cand); + + if (!orig_ifname || !cand_ifname) { + remove_from_hash(settings, + props, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME); + return TRUE; + } + return FALSE; +} + +static gboolean +check_connection_mac_address(NMConnection *orig, NMConnection *candidate, GHashTable *settings) +{ + GHashTable * props; + const char * orig_mac = NULL, *cand_mac = NULL; + NMSettingWired *s_wired_orig, *s_wired_cand; + + props = check_property_in_hash(settings, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + if (!props) + return TRUE; + + /* If one of the MAC addresses is NULL, we accept that connection */ + s_wired_orig = nm_connection_get_setting_wired(orig); + if (s_wired_orig) + orig_mac = nm_setting_wired_get_mac_address(s_wired_orig); + + s_wired_cand = nm_connection_get_setting_wired(candidate); + if (s_wired_cand) + cand_mac = nm_setting_wired_get_mac_address(s_wired_cand); + + if (!orig_mac || !cand_mac) { + remove_from_hash(settings, + props, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + return TRUE; + } + return FALSE; +} + +static gboolean +check_connection_infiniband_mac_address(NMConnection *orig, + NMConnection *candidate, + GHashTable * settings) +{ + GHashTable * props; + const char * orig_mac = NULL, *cand_mac = NULL; + NMSettingInfiniband *s_infiniband_orig, *s_infiniband_cand; + + props = check_property_in_hash(settings, + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_MAC_ADDRESS); + if (!props) + return TRUE; + + /* If one of the MAC addresses is NULL, we accept that connection */ + s_infiniband_orig = nm_connection_get_setting_infiniband(orig); + if (s_infiniband_orig) + orig_mac = nm_setting_infiniband_get_mac_address(s_infiniband_orig); + + s_infiniband_cand = nm_connection_get_setting_infiniband(candidate); + if (s_infiniband_cand) + cand_mac = nm_setting_infiniband_get_mac_address(s_infiniband_cand); + + if (!orig_mac || !cand_mac) { + remove_from_hash(settings, + props, + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_MAC_ADDRESS); + return TRUE; + } + return FALSE; +} + +static gboolean +check_connection_cloned_mac_address(NMConnection *orig, + NMConnection *candidate, + GHashTable * settings) +{ + GHashTable * props; + const char * orig_mac = NULL, *cand_mac = NULL; + NMSettingWired *s_wired_orig, *s_wired_cand; + + props = check_property_in_hash(settings, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS); + if (!props) + return TRUE; + + /* If one of the MAC addresses is NULL, we accept that connection */ + s_wired_orig = nm_connection_get_setting_wired(orig); + if (s_wired_orig) + orig_mac = nm_setting_wired_get_cloned_mac_address(s_wired_orig); + + s_wired_cand = nm_connection_get_setting_wired(candidate); + if (s_wired_cand) + cand_mac = nm_setting_wired_get_cloned_mac_address(s_wired_cand); + + /* special cloned mac address entries are accepted. */ + if (NM_CLONED_MAC_IS_SPECIAL(orig_mac)) + orig_mac = NULL; + if (NM_CLONED_MAC_IS_SPECIAL(cand_mac)) + cand_mac = NULL; + + if (!orig_mac || !cand_mac) { + remove_from_hash(settings, + props, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS); + return TRUE; + } + return FALSE; +} + +static gboolean +check_connection_s390_props(NMConnection *orig, NMConnection *candidate, GHashTable *settings) +{ + GHashTable * props1, *props2, *props3; + NMSettingWired *s_wired_orig, *s_wired_cand; + + props1 = check_property_in_hash(settings, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_SUBCHANNELS); + props2 = check_property_in_hash(settings, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_NETTYPE); + props3 = check_property_in_hash(settings, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_OPTIONS); + if (!props1 && !props2 && !props3) + return TRUE; + + /* If the generated connection did not contain wired setting, + * allow it to match to a connection with a wired setting, + * but default (empty) s390-* properties */ + s_wired_orig = nm_connection_get_setting_wired(orig); + s_wired_cand = nm_connection_get_setting_wired(candidate); + if (!s_wired_orig && s_wired_cand) { + const char *const *subchans = nm_setting_wired_get_s390_subchannels(s_wired_cand); + const char * nettype = nm_setting_wired_get_s390_nettype(s_wired_cand); + guint32 num_options = nm_setting_wired_get_num_s390_options(s_wired_cand); + + if ((!subchans || !*subchans) && !nettype && num_options == 0) { + remove_from_hash(settings, + props1, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_SUBCHANNELS); + remove_from_hash(settings, + props2, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_NETTYPE); + remove_from_hash(settings, + props3, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_OPTIONS); + return TRUE; + } + } + return FALSE; +} + +static NMConnection * +check_possible_match(NMConnection *orig, + NMConnection *candidate, + GHashTable * settings, + gboolean device_has_carrier, + gint64 default_v4_metric, + gint64 default_v6_metric) +{ + g_return_val_if_fail(settings != NULL, NULL); + + if (!check_ip6_method(orig, candidate, settings)) + return NULL; + + if (!check_ip4_method(orig, candidate, settings, device_has_carrier)) + return NULL; + + if (!check_ip_routes(orig, candidate, settings, default_v4_metric, TRUE)) + return NULL; + + if (!check_ip_routes(orig, candidate, settings, default_v6_metric, FALSE)) + return NULL; + + if (!check_connection_interface_name(orig, candidate, settings)) + return NULL; + + if (!check_connection_mac_address(orig, candidate, settings)) + return NULL; + + if (!check_connection_infiniband_mac_address(orig, candidate, settings)) + return NULL; + + if (!check_connection_cloned_mac_address(orig, candidate, settings)) + return NULL; + + if (!check_connection_s390_props(orig, candidate, settings)) + return NULL; + + if (g_hash_table_size(settings) == 0) + return candidate; + else + return NULL; +} + +/** + * nm_utils_match_connection: + * @connections: a (optionally pre-sorted) list of connections from which to + * find a matching connection to @original based on "inferrable" properties + * @original: the #NMConnection to find a match for from @connections + * @indicated: whether the match is already hinted/indicated. That is the + * case when we found the connection in the state file from a previous run. + * In this case, we perform a relexed check, as we have a good hint + * that the connection actually matches. + * @device_has_carrier: pass %TRUE if the device that generated @original has + * a carrier, %FALSE if not + * @match_filter_func: a function to check whether each connection from @connections + * should be considered for matching. This function should return %TRUE if the + * connection should be considered, %FALSE if the connection should be ignored + * @match_compat_data: data pointer passed to @match_filter_func + * + * Checks each connection from @connections until a matching connection is found + * considering only setting properties marked with %NM_SETTING_PARAM_INFERRABLE + * and checking a few other characteristics like IPv6 method. If the caller + * desires some priority order of the connections, @connections should be + * sorted before calling this function. + * + * Returns: the best #NMConnection matching @original, or %NULL if no connection + * matches well enough. + */ +NMConnection * +nm_utils_match_connection(NMConnection *const * connections, + NMConnection * original, + gboolean indicated, + gboolean device_has_carrier, + gint64 default_v4_metric, + gint64 default_v6_metric, + NMUtilsMatchFilterFunc match_filter_func, + gpointer match_filter_data) +{ + NMConnection *best_match = NULL; + + if (!connections) + return NULL; + + for (; *connections; connections++) { + NMConnection *candidate = *connections; + GHashTable * diffs = NULL; + + nm_assert(NM_IS_CONNECTION(candidate)); + + if (match_filter_func) { + if (!match_filter_func(candidate, match_filter_data)) + continue; + } + + if (indicated) { + NMSettingConnection *s_orig, *s_cand; + + s_orig = nm_connection_get_setting_connection(original); + s_cand = nm_connection_get_setting_connection(candidate); + + /* It is indicated that this connection matches. Assume we have + * a match, but check for particular differences that let us + * reject the candidate. */ + if (!nm_streq0(nm_setting_connection_get_connection_type(s_orig), + nm_setting_connection_get_connection_type(s_cand))) + continue; + if (!nm_streq0(nm_setting_connection_get_slave_type(s_orig), + nm_setting_connection_get_slave_type(s_cand))) + continue; + + /* this is good enough for a match */ + } else if (!nm_connection_diff(original, + candidate, + NM_SETTING_COMPARE_FLAG_INFERRABLE, + &diffs)) { + if (!best_match) { + best_match = check_possible_match(original, + candidate, + diffs, + device_has_carrier, + default_v4_metric, + default_v6_metric); + } + + if (!best_match && nm_logging_enabled(LOGL_DEBUG, LOGD_CORE)) { + GString * diff_string; + GHashTableIter s_iter, p_iter; + gpointer setting_name, setting; + gpointer property_name, value; + + diff_string = g_string_new(NULL); + g_hash_table_iter_init(&s_iter, diffs); + while (g_hash_table_iter_next(&s_iter, &setting_name, &setting)) { + g_hash_table_iter_init(&p_iter, setting); + while (g_hash_table_iter_next(&p_iter, &property_name, &value)) { + if (diff_string->len) + g_string_append(diff_string, ", "); + g_string_append_printf(diff_string, + "%s.%s", + (char *) setting_name, + (char *) property_name); + } + } + + nm_log_dbg(LOGD_CORE, + "Connection '%s' differs from candidate '%s' in %s", + nm_connection_get_id(original), + nm_connection_get_id(candidate), + diff_string->str); + g_string_free(diff_string, TRUE); + } + + g_hash_table_unref(diffs); + continue; + } + + /* Exact match */ + return candidate; + } + + /* Best match (if any) */ + return best_match; +} + +/*****************************************************************************/ + +int +nm_match_spec_device_by_pllink(const NMPlatformLink *pllink, + const char * match_device_type, + const char * match_dhcp_plugin, + const GSList * specs, + int no_match_value) +{ + NMMatchSpecMatchType m; + + /* we can only match by certain properties that are available on the + * platform link (and even @pllink might be missing. + * + * It's still useful because of specs like "*" and "except:interface-name:eth0", + * which match even in that case. */ + m = nm_match_spec_device(specs, + pllink ? pllink->name : NULL, + match_device_type, + pllink ? pllink->driver : NULL, + NULL, + NULL, + NULL, + match_dhcp_plugin); + + switch (m) { + case NM_MATCH_SPEC_MATCH: + return TRUE; + case NM_MATCH_SPEC_NEG_MATCH: + return FALSE; + case NM_MATCH_SPEC_NO_MATCH: + return no_match_value; + } + nm_assert_not_reached(); + return no_match_value; +} + +/*****************************************************************************/ + +NMPlatformRoutingRule * +nm_ip_routing_rule_to_platform(const NMIPRoutingRule *rule, NMPlatformRoutingRule *out_pl) +{ + nm_assert(rule); + nm_assert(nm_ip_routing_rule_validate(rule, NULL)); + nm_assert(out_pl); + + *out_pl = (NMPlatformRoutingRule){ + .addr_family = nm_ip_routing_rule_get_addr_family(rule), + .flags = (nm_ip_routing_rule_get_invert(rule) ? FIB_RULE_INVERT : 0), + .priority = nm_ip_routing_rule_get_priority(rule), + .tos = nm_ip_routing_rule_get_tos(rule), + .ip_proto = nm_ip_routing_rule_get_ipproto(rule), + .fwmark = nm_ip_routing_rule_get_fwmark(rule), + .fwmask = nm_ip_routing_rule_get_fwmask(rule), + .sport_range = + { + .start = nm_ip_routing_rule_get_source_port_start(rule), + .end = nm_ip_routing_rule_get_source_port_end(rule), + }, + .dport_range = + { + .start = nm_ip_routing_rule_get_destination_port_start(rule), + .end = nm_ip_routing_rule_get_destination_port_end(rule), + }, + .src = *(nm_ip_routing_rule_get_from_bin(rule) ?: &nm_ip_addr_zero), + .dst = *(nm_ip_routing_rule_get_to_bin(rule) ?: &nm_ip_addr_zero), + .src_len = nm_ip_routing_rule_get_from_len(rule), + .dst_len = nm_ip_routing_rule_get_to_len(rule), + .action = nm_ip_routing_rule_get_action(rule), + .table = nm_ip_routing_rule_get_table(rule), + .suppress_prefixlen_inverse = + ~((guint32) nm_ip_routing_rule_get_suppress_prefixlength(rule)), + }; + + nm_ip_routing_rule_get_xifname_bin(rule, TRUE, out_pl->iifname); + nm_ip_routing_rule_get_xifname_bin(rule, FALSE, out_pl->oifname); + + return out_pl; +} + +/*****************************************************************************/ + +struct _NMShutdownWaitObjHandle { + CList lst; + gpointer watched_obj; + char * msg_reason; + bool free_msg_reason : 1; + bool is_cancellable : 1; +}; + +static CList _shutdown_waitobj_lst_head; + +static void +_shutdown_waitobj_unregister(NMShutdownWaitObjHandle *handle) +{ + c_list_unlink_stale(&handle->lst); + if (handle->free_msg_reason) + g_free(handle->msg_reason); + g_slice_free(NMShutdownWaitObjHandle, handle); + + /* FIXME(shutdown): check whether the object list is empty, and + * signal shutdown-complete */ +} + +static void +_shutdown_waitobj_cb(gpointer user_data, GObject *where_the_object_was) +{ + NMShutdownWaitObjHandle *handle = user_data; + + nm_assert(handle); + nm_assert(handle->watched_obj == where_the_object_was); + _shutdown_waitobj_unregister(handle); +} + +/** + * nm_shutdown_wait_obj_register_full: + * @watched_obj: the object to watch. Takes a weak reference on the object + * to be notified when it gets destroyed. + * If wait_type is %NM_SHUTDOWN_WAIT_TYPE_HANDLE, this must be %NULL. + * @wait_type: whether @watched_obj is just a plain GObject or a GCancellable + * that should be cancelled. + * @msg_reason: a reason message, for debugging and logging purposes. + * @free_msg_reason: if %TRUE, then ownership of @msg_reason will be taken + * and the string will be freed with g_free() afterwards. If %FALSE, + * the caller must ensure that @msg_reason string outlives the watched + * objects (e.g. being a static strings). + * + * Keep track of @watched_obj until it gets destroyed. During shutdown, + * we wait until all watched objects are destroyed. This is useful, if + * this object still conducts some asynchronous action, which needs to + * complete before NetworkManager is allowed to terminate. We re-use + * the reference-counter of @watched_obj as signal, that the object + * is still used. + * + * If @wait_type is %NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE, then during shutdown + * (after %NM_SHUTDOWN_TIMEOUT_MS), the cancellable will be cancelled to notify + * the source of the shutdown. Note that otherwise, in this mode also @watched_obj + * is only tracked with a weak-pointer. Especially, it does not register to the + * "cancelled" signal to automatically unregister (otherwise, you would never + * know whether the returned NMShutdownWaitObjHandle is still valid. + * + * FIXME(shutdown): proper shutdown is not yet implemented, and registering + * an object (currently) has no effect. + * + * FIXME(shutdown): during shutdown, after %NM_SHUTDOWN_TIMEOUT_MS timeout, cancel + * all remaining %NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE instances. Also, when somebody + * enqueues a cancellable after that point, cancel it right away on an idle handler. + * + * Returns: a handle to unregister the object. The caller may choose to ignore + * the handle, in which case, the object will be automatically unregistered, + * once it gets destroyed. + * Note that the handle is only valid as long as @watched_obj exists. If + * you plan to use it, ensure that you take care of not using it after + * destroying @watched_obj. + */ +NMShutdownWaitObjHandle * +nm_shutdown_wait_obj_register_full(gpointer watched_obj, + NMShutdownWaitType wait_type, + char * msg_reason, + gboolean free_msg_reason) +{ + NMShutdownWaitObjHandle *handle; + + if (wait_type == NM_SHUTDOWN_WAIT_TYPE_OBJECT) + g_return_val_if_fail(G_IS_OBJECT(watched_obj), NULL); + else if (wait_type == NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE) + g_return_val_if_fail(G_IS_CANCELLABLE(watched_obj), NULL); + else if (wait_type == NM_SHUTDOWN_WAIT_TYPE_HANDLE) + g_return_val_if_fail(!watched_obj, NULL); + else + g_return_val_if_reached(NULL); + + if (G_UNLIKELY(!_shutdown_waitobj_lst_head.next)) + c_list_init(&_shutdown_waitobj_lst_head); + + handle = g_slice_new(NMShutdownWaitObjHandle); + *handle = (NMShutdownWaitObjHandle){ + /* depending on @free_msg_reason, we take ownership of @msg_reason. + * In either case, we just reference the string without cloning + * it. */ + .watched_obj = watched_obj, + .msg_reason = msg_reason, + .free_msg_reason = free_msg_reason, + .is_cancellable = (wait_type == NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE), + }; + c_list_link_tail(&_shutdown_waitobj_lst_head, &handle->lst); + if (watched_obj) + g_object_weak_ref(watched_obj, _shutdown_waitobj_cb, handle); + return handle; +} + +void +nm_shutdown_wait_obj_unregister(NMShutdownWaitObjHandle *handle) +{ + g_return_if_fail(handle); + + nm_assert(!handle->watched_obj || G_IS_OBJECT(handle->watched_obj)); + nm_assert(nm_c_list_contains_entry(&_shutdown_waitobj_lst_head, handle, lst)); + + if (handle->watched_obj) + g_object_weak_unref(handle->watched_obj, _shutdown_waitobj_cb, handle); + _shutdown_waitobj_unregister(handle); +} + +/*****************************************************************************/ + +/** + * nm_utils_file_is_in_path: + * @abs_filename: the absolute filename to test + * @abs_path: the absolute path, to check whether filename is in. + * + * This tests, whether @abs_filename is a file which lies inside @abs_path. + * Basically, this checks whether @abs_filename is the same as @abs_path + + * basename(@abs_filename). It allows simple normalizations, like coalescing + * multiple "//". + * + * However, beware that this function is purely filename based. That means, + * it will reject files that reference the same file (i.e. inode) via + * symlinks or bind mounts. Maybe one would like to check for file (inode) + * identity, but that is not really possible based on the file name alone. + * + * This means, that nm_utils_file_is_in_path("/var/run/some-file", "/var/run") + * will succeed, but nm_utils_file_is_in_path("/run/some-file", "/var/run") + * will not (although, it's well known that they reference the same path). + * + * Also, note that @abs_filename must not have trailing slashes itself. + * So, this will reject nm_utils_file_is_in_path("/usr/lib/", "/usr") as + * invalid, because the function searches for file names (and "lib/" is + * clearly a directory). + * + * Returns: if @abs_filename is a file inside @abs_path, returns the + * trailing part of @abs_filename which is the filename. Otherwise, + * %NULL. + */ +const char * +nm_utils_file_is_in_path(const char *abs_filename, const char *abs_path) +{ + const char *path; + + g_return_val_if_fail(abs_filename && abs_filename[0] == '/', NULL); + g_return_val_if_fail(abs_path && abs_path[0] == '/', NULL); + + path = nm_sd_utils_path_startswith(abs_filename, abs_path); + if (!path) + return NULL; + + nm_assert(path[0] != '/'); + nm_assert(path > abs_filename); + nm_assert(path <= &abs_filename[strlen(abs_filename)]); + + /* we require a non-empty remainder with no slashes. That is, only a filename. + * + * Note this will reject "/var/run/" as not being in "/var", + * while "/var/run" would pass. The function searches for files + * only, so a trailing slash (indicating a directory) is not allowed). + * This is despite that the function cannot determine whether "/var/run" + * is itself a file or a directory. "*/ + return path[0] && !strchr(path, '/') ? path : NULL; +} + +/* The returned qdisc array is valid as long as s_tc is not modified */ +GPtrArray * +nm_utils_qdiscs_from_tc_setting(NMPlatform *platform, NMSettingTCConfig *s_tc, int ip_ifindex) +{ + GPtrArray *qdiscs; + guint nqdiscs; + guint i; + + nqdiscs = nm_setting_tc_config_get_num_qdiscs(s_tc); + qdiscs = g_ptr_array_new_full(nqdiscs, (GDestroyNotify) nmp_object_unref); + + for (i = 0; i < nqdiscs; i++) { + NMTCQdisc * s_qdisc = nm_setting_tc_config_get_qdisc(s_tc, i); + NMPObject * q = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL); + NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC(q); + + qdisc->ifindex = ip_ifindex; + qdisc->kind = nm_tc_qdisc_get_kind(s_qdisc); + + qdisc->addr_family = AF_UNSPEC; + qdisc->handle = nm_tc_qdisc_get_handle(s_qdisc); + qdisc->parent = nm_tc_qdisc_get_parent(s_qdisc); + qdisc->info = 0; + +#define GET_ATTR(name, dst, variant_type, type, dflt) \ + G_STMT_START \ + { \ + GVariant *_variant = nm_tc_qdisc_get_attribute(s_qdisc, "" name ""); \ + \ + if (_variant && g_variant_is_of_type(_variant, G_VARIANT_TYPE_##variant_type)) \ + (dst) = g_variant_get_##type(_variant); \ + else \ + (dst) = (dflt); \ + } \ + G_STMT_END + + if (strcmp(qdisc->kind, "fq_codel") == 0) { + GET_ATTR("limit", qdisc->fq_codel.limit, UINT32, uint32, 0); + GET_ATTR("flows", qdisc->fq_codel.flows, UINT32, uint32, 0); + GET_ATTR("target", qdisc->fq_codel.target, UINT32, uint32, 0); + GET_ATTR("interval", qdisc->fq_codel.interval, UINT32, uint32, 0); + GET_ATTR("quantum", qdisc->fq_codel.quantum, UINT32, uint32, 0); + GET_ATTR("ce_threshold", + qdisc->fq_codel.ce_threshold, + UINT32, + uint32, + NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED); + GET_ATTR("memory_limit", + qdisc->fq_codel.memory_limit, + UINT32, + uint32, + NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET); + GET_ATTR("ecn", qdisc->fq_codel.ecn, BOOLEAN, boolean, FALSE); + } else if (nm_streq(qdisc->kind, "sfq")) { + GET_ATTR("limit", qdisc->sfq.limit, UINT32, uint32, 0); + GET_ATTR("flows", qdisc->sfq.flows, UINT32, uint32, 0); + GET_ATTR("divisor", qdisc->sfq.divisor, UINT32, uint32, 0); + GET_ATTR("perturb", qdisc->sfq.perturb_period, INT32, int32, 0); + GET_ATTR("quantum", qdisc->sfq.quantum, UINT32, uint32, 0); + GET_ATTR("depth", qdisc->sfq.depth, UINT32, uint32, 0); + } else if (nm_streq(qdisc->kind, "tbf")) { + GET_ATTR("rate", qdisc->tbf.rate, UINT64, uint64, 0); + GET_ATTR("burst", qdisc->tbf.burst, UINT32, uint32, 0); + GET_ATTR("limit", qdisc->tbf.limit, UINT32, uint32, 0); + GET_ATTR("latency", qdisc->tbf.latency, UINT32, uint32, 0); + } +#undef GET_ATTR + + g_ptr_array_add(qdiscs, q); + } + + return qdiscs; +} + +/* The returned tfilter array is valid as long as s_tc is not modified */ +GPtrArray * +nm_utils_tfilters_from_tc_setting(NMPlatform *platform, NMSettingTCConfig *s_tc, int ip_ifindex) +{ + GPtrArray *tfilters; + guint ntfilters; + guint i; + + ntfilters = nm_setting_tc_config_get_num_tfilters(s_tc); + tfilters = g_ptr_array_new_full(ntfilters, (GDestroyNotify) nmp_object_unref); + + for (i = 0; i < ntfilters; i++) { + NMTCTfilter * s_tfilter = nm_setting_tc_config_get_tfilter(s_tc, i); + NMTCAction * action; + NMPObject * t = nmp_object_new(NMP_OBJECT_TYPE_TFILTER, NULL); + NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER(t); + + tfilter->ifindex = ip_ifindex; + tfilter->kind = nm_tc_tfilter_get_kind(s_tfilter); + tfilter->addr_family = AF_UNSPEC; + tfilter->handle = nm_tc_tfilter_get_handle(s_tfilter); + tfilter->parent = nm_tc_tfilter_get_parent(s_tfilter); + tfilter->info = TC_H_MAKE(0, htons(ETH_P_ALL)); + + action = nm_tc_tfilter_get_action(s_tfilter); + if (action) { + GVariant *var; + + tfilter->action.kind = nm_tc_action_get_kind(action); + + if (strcmp(tfilter->action.kind, "simple") == 0) { + var = nm_tc_action_get_attribute(action, "sdata"); + if (var && g_variant_is_of_type(var, G_VARIANT_TYPE_BYTESTRING)) { + g_strlcpy(tfilter->action.simple.sdata, + g_variant_get_bytestring(var), + sizeof(tfilter->action.simple.sdata)); + } + } else if (strcmp(tfilter->action.kind, "mirred") == 0) { + if (nm_tc_action_get_attribute(action, "egress")) + tfilter->action.mirred.egress = TRUE; + + if (nm_tc_action_get_attribute(action, "ingress")) + tfilter->action.mirred.ingress = TRUE; + + if (nm_tc_action_get_attribute(action, "mirror")) + tfilter->action.mirred.mirror = TRUE; + + if (nm_tc_action_get_attribute(action, "redirect")) + tfilter->action.mirred.redirect = TRUE; + + var = nm_tc_action_get_attribute(action, "dev"); + if (var && g_variant_is_of_type(var, G_VARIANT_TYPE_STRING)) { + int ifindex; + + ifindex = + nm_platform_link_get_ifindex(platform, g_variant_get_string(var, NULL)); + if (ifindex > 0) + tfilter->action.mirred.ifindex = ifindex; + } + } + } + + g_ptr_array_add(tfilters, t); + } + + return tfilters; +} + +void +nm_utils_ip_route_attribute_to_platform(int addr_family, + NMIPRoute * s_route, + NMPlatformIPRoute *r, + guint32 route_table) +{ + GVariant * variant; + guint32 table; + NMIPAddr addr; + NMPlatformIP4Route *r4 = (NMPlatformIP4Route *) r; + NMPlatformIP6Route *r6 = (NMPlatformIP6Route *) r; + gboolean onlink; + + nm_assert(s_route); + nm_assert_addr_family(addr_family); + nm_assert(r); + +#define GET_ATTR(name, dst, variant_type, type, dflt) \ + G_STMT_START \ + { \ + GVariant *_variant = nm_ip_route_get_attribute(s_route, "" name ""); \ + \ + if (_variant && g_variant_is_of_type(_variant, G_VARIANT_TYPE_##variant_type)) \ + (dst) = g_variant_get_##type(_variant); \ + else \ + (dst) = (dflt); \ + } \ + G_STMT_END + + if ((variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_TYPE)) + && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) { + guint8 type; + + type = nm_utils_route_type_by_name(g_variant_get_string(variant, NULL)); + nm_assert(NM_IN_SET(type, RTN_UNICAST, RTN_LOCAL)); + + r->type_coerced = nm_platform_route_type_coerce(type); + } else + r->type_coerced = nm_platform_route_type_coerce(RTN_UNICAST); + + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_TABLE, table, UINT32, uint32, 0); + + if (!table && r->type_coerced == nm_platform_route_type_coerce(RTN_LOCAL)) + r->table_coerced = nm_platform_route_table_coerce(RT_TABLE_LOCAL); + else + r->table_coerced = nm_platform_route_table_coerce(table ?: (route_table ?: RT_TABLE_MAIN)); + + if (NM_IS_IPv4(addr_family)) { + guint8 scope; + + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_TOS, r4->tos, BYTE, byte, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_SCOPE, scope, BYTE, byte, RT_SCOPE_NOWHERE); + r4->scope_inv = nm_platform_route_scope_inv(scope); + } + + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_ONLINK, onlink, BOOLEAN, boolean, FALSE); + + r->r_rtm_flags = ((onlink) ? (unsigned) RTNH_F_ONLINK : 0u); + + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_WINDOW, r->window, UINT32, uint32, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_CWND, r->cwnd, UINT32, uint32, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_INITCWND, r->initcwnd, UINT32, uint32, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_INITRWND, r->initrwnd, UINT32, uint32, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_MTU, r->mtu, UINT32, uint32, 0); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, r->lock_window, BOOLEAN, boolean, FALSE); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, r->lock_cwnd, BOOLEAN, boolean, FALSE); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, r->lock_initcwnd, BOOLEAN, boolean, FALSE); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND, r->lock_initrwnd, BOOLEAN, boolean, FALSE); + GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, r->lock_mtu, BOOLEAN, boolean, FALSE); + + if ((variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_SRC)) + && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) { + if (inet_pton(addr_family, g_variant_get_string(variant, NULL), &addr) == 1) { + if (NM_IS_IPv4(addr_family)) + r4->pref_src = addr.addr4; + else + r6->pref_src = addr.addr6; + } + } + + if (!NM_IS_IPv4(addr_family) + && (variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_FROM)) + && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) { + int prefix; + + if (nm_utils_parse_inaddr_prefix_bin(addr_family, + g_variant_get_string(variant, NULL), + NULL, + &addr, + &prefix)) { + if (prefix < 0) + prefix = 128; + r6->src = addr.addr6; + r6->src_plen = prefix; + } + } +#undef GET_ATTR +} + +/*****************************************************************************/ + +static int +_addresses_sort_cmp_4(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return nm_platform_ip4_address_pretty_sort_cmp( + NMP_OBJECT_CAST_IP4_ADDRESS(*((const NMPObject **) a)), + NMP_OBJECT_CAST_IP4_ADDRESS(*((const NMPObject **) b))); +} + +static int +_addresses_sort_cmp_6(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return nm_platform_ip6_address_pretty_sort_cmp( + NMP_OBJECT_CAST_IP6_ADDRESS(*((const NMPObject **) a)), + NMP_OBJECT_CAST_IP6_ADDRESS(*((const NMPObject **) b)), + (((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT(user_data)) + == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); +} + +void +nm_utils_ip_addresses_to_dbus(int addr_family, + const NMDedupMultiHeadEntry *head_entry, + const NMPObject * best_default_route, + NMSettingIP6ConfigPrivacy ipv6_privacy, + GVariant ** out_address_data, + GVariant ** out_addresses) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + GVariantBuilder builder_data; + GVariantBuilder builder_legacy; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + gs_free const NMPObject **addresses = NULL; + guint naddr; + guint i; + + nm_assert_addr_family(addr_family); + + if (out_address_data) + g_variant_builder_init(&builder_data, G_VARIANT_TYPE("aa{sv}")); + if (out_addresses) { + if (IS_IPv4) + g_variant_builder_init(&builder_legacy, G_VARIANT_TYPE("aau")); + else + g_variant_builder_init(&builder_legacy, G_VARIANT_TYPE("a(ayuay)")); + } + + if (!head_entry) + goto out; + + addresses = + (const NMPObject **) nm_dedup_multi_objs_to_array_head(head_entry, NULL, NULL, &naddr); + + nm_assert(addresses && naddr); + + g_qsort_with_data(addresses, + naddr, + sizeof(addresses[0]), + IS_IPv4 ? _addresses_sort_cmp_4 : _addresses_sort_cmp_6, + GINT_TO_POINTER(ipv6_privacy)); + + for (i = 0; i < naddr; i++) { + const NMPlatformIPXAddress *address = NMP_OBJECT_CAST_IPX_ADDRESS(addresses[i]); + + if (out_address_data) { + GVariantBuilder addr_builder; + gconstpointer p; + + g_variant_builder_init(&addr_builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_add( + &addr_builder, + "{sv}", + "address", + g_variant_new_string( + nm_utils_inet_ntop(addr_family, address->ax.address_ptr, addr_str))); + + g_variant_builder_add(&addr_builder, + "{sv}", + "prefix", + g_variant_new_uint32(address->ax.plen)); + + p = NULL; + if (IS_IPv4) { + if (address->a4.peer_address != address->a4.address) + p = &address->a4.peer_address; + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&address->a6.peer_address) + && !IN6_ARE_ADDR_EQUAL(&address->a6.peer_address, &address->a6.address)) + p = &address->a6.peer_address; + } + if (p) { + g_variant_builder_add( + &addr_builder, + "{sv}", + "peer", + g_variant_new_string(nm_utils_inet_ntop(addr_family, p, addr_str))); + } + + if (IS_IPv4) { + if (*address->a4.label) { + g_variant_builder_add(&addr_builder, + "{sv}", + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string(address->a4.label)); + } + } + + g_variant_builder_add(&builder_data, "a{sv}", &addr_builder); + } + + if (out_addresses) { + if (IS_IPv4) { + const guint32 dbus_addr[3] = { + address->a4.address, + address->a4.plen, + (i == 0 && best_default_route) + ? NMP_OBJECT_CAST_IP4_ROUTE(best_default_route)->gateway + : (guint32) 0, + }; + + g_variant_builder_add(&builder_legacy, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + dbus_addr, + 3, + sizeof(guint32))); + } else { + g_variant_builder_add( + &builder_legacy, + "(@ayu@ay)", + nm_g_variant_new_ay_in6addr(&address->a6.address), + address->a6.plen, + nm_g_variant_new_ay_in6addr( + (i == 0 && best_default_route) + ? &NMP_OBJECT_CAST_IP6_ROUTE(best_default_route)->gateway + : &in6addr_any)); + } + } + } + +out: + NM_SET_OUT(out_address_data, g_variant_builder_end(&builder_data)); + NM_SET_OUT(out_addresses, g_variant_builder_end(&builder_legacy)); +} + +void +nm_utils_ip_routes_to_dbus(int addr_family, + const NMDedupMultiHeadEntry *head_entry, + GVariant ** out_route_data, + GVariant ** out_routes) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDedupMultiIter iter; + const NMPObject *obj; + GVariantBuilder builder_data; + GVariantBuilder builder_legacy; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert_addr_family(addr_family); + + if (out_route_data) + g_variant_builder_init(&builder_data, G_VARIANT_TYPE("aa{sv}")); + if (out_routes) { + if (IS_IPv4) + g_variant_builder_init(&builder_legacy, G_VARIANT_TYPE("aau")); + else + g_variant_builder_init(&builder_legacy, G_VARIANT_TYPE("a(ayuayu)")); + } + + nm_dedup_multi_iter_init(&iter, head_entry); + while (nm_platform_dedup_multi_iter_next_obj(&iter, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))) { + const NMPlatformIPXRoute *r = NMP_OBJECT_CAST_IPX_ROUTE(obj); + struct in6_addr n; + + nm_assert(r); + nm_assert(r->rx.plen <= 8u * nm_utils_addr_family_to_size(addr_family)); + nm_assert(!IS_IPv4 + || r->r4.network + == nm_utils_ip4_address_clear_host_address(r->r4.network, r->r4.plen)); + nm_assert( + IS_IPv4 + || (memcmp(&r->r6.network, + nm_utils_ip6_address_clear_host_address(&n, &r->r6.network, r->r6.plen), + sizeof(n)) + == 0)); + + if (r->rx.type_coerced != nm_platform_route_type_coerce(RTN_UNICAST)) + continue; + + if (out_route_data) { + GVariantBuilder route_builder; + gconstpointer gateway; + + g_variant_builder_init(&route_builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_add( + &route_builder, + "{sv}", + "dest", + g_variant_new_string(nm_utils_inet_ntop(addr_family, r->rx.network_ptr, addr_str))); + + g_variant_builder_add(&route_builder, + "{sv}", + "prefix", + g_variant_new_uint32(r->rx.plen)); + + gateway = nm_platform_ip_route_get_gateway(addr_family, &r->rx); + if (!nm_ip_addr_is_null(addr_family, gateway)) { + g_variant_builder_add( + &route_builder, + "{sv}", + "next-hop", + g_variant_new_string(nm_utils_inet_ntop(addr_family, gateway, addr_str))); + } + + g_variant_builder_add(&route_builder, + "{sv}", + "metric", + g_variant_new_uint32(r->rx.metric)); + + if (!nm_platform_route_table_is_main(r->rx.table_coerced)) { + g_variant_builder_add( + &route_builder, + "{sv}", + "table", + g_variant_new_uint32( + nm_platform_route_table_uncoerce(r->rx.table_coerced, TRUE))); + } + + g_variant_builder_add(&builder_data, "a{sv}", &route_builder); + } + + if (out_routes) { + /* legacy versions of nm_ip[46]_route_set_prefix() in libnm-util assert that the + * plen is positive. Skip the default routes not to break older clients. */ + if (!nm_platform_route_table_is_main(r->rx.table_coerced) + || NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r)) + continue; + + if (IS_IPv4) { + const guint32 dbus_route[4] = { + r->r4.network, + r->r4.plen, + r->r4.gateway, + r->r4.metric, + }; + + g_variant_builder_add(&builder_legacy, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + dbus_route, + 4, + sizeof(guint32))); + } else { + g_variant_builder_add(&builder_legacy, + "(@ayu@ayu)", + nm_g_variant_new_ay_in6addr(&r->r6.network), + (guint32) r->r6.plen, + nm_g_variant_new_ay_in6addr(&r->r6.gateway), + (guint32) r->r6.metric); + } + } + } + + NM_SET_OUT(out_route_data, g_variant_builder_end(&builder_data)); + NM_SET_OUT(out_routes, g_variant_builder_end(&builder_legacy)); +} + +/*****************************************************************************/ + +typedef struct { + char *table; + char *rule; +} ShareRule; + +struct _NMUtilsShareRules { + GArray *rules; +}; + +static void +_share_rule_clear(gpointer data) +{ + ShareRule *rule = data; + + g_free(rule->table); + g_free(rule->rule); +} + +NMUtilsShareRules * +nm_utils_share_rules_new(void) +{ + NMUtilsShareRules *self; + + self = g_slice_new(NMUtilsShareRules); + *self = (NMUtilsShareRules){ + .rules = g_array_sized_new(FALSE, FALSE, sizeof(ShareRule), 10), + }; + + g_array_set_clear_func(self->rules, _share_rule_clear); + return self; +} + +void +nm_utils_share_rules_free(NMUtilsShareRules *self) +{ + if (!self) + return; + + g_array_unref(self->rules); + nm_g_slice_free(self); +} + +void +nm_utils_share_rules_add_rule_take(NMUtilsShareRules *self, const char *table, char *rule_take) +{ + ShareRule *rule; + + g_return_if_fail(self); + g_return_if_fail(table); + g_return_if_fail(rule_take); + + rule = nm_g_array_append_new(self->rules, ShareRule); + *rule = (ShareRule){ + .table = g_strdup(table), + .rule = g_steal_pointer(&rule_take), + }; +} + +void +nm_utils_share_rules_apply(NMUtilsShareRules *self, gboolean shared) +{ + guint i; + + g_return_if_fail(self); + + if (self->rules->len == 0) + return; + + /* depending on whether we share or unshare, we add/remote the rules + * in opposite order. */ + if (shared) + i = self->rules->len - 1; + else + i = 0; + + for (;;) { + gs_free_error GError *error = NULL; + ShareRule * rule; + gs_free const char ** argv = NULL; + gs_free char * cmd = NULL; + int status; + + rule = &g_array_index(self->rules, ShareRule, i); + + cmd = g_strdup_printf("%s --table %s %s %s", + IPTABLES_PATH, + rule->table, + shared ? "--insert" : "--delete", + rule->rule); + argv = nm_utils_strsplit_set(cmd, " "); + + nm_log_info(LOGD_SHARING, "Executing: %s", cmd); + if (!g_spawn_sync("/", + (char **) argv, + (char **) NM_PTRARRAY_EMPTY(const char *), + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, + NULL, + NULL, + NULL, + &status, + &error)) { + nm_log_warn(LOGD_SHARING, "Error executing command: %s", error->message); + goto next; + } + if (WEXITSTATUS(status)) { + nm_log_warn(LOGD_SHARING, "** Command returned exit status %d.", WEXITSTATUS(status)); + } + +next: + if (shared) { + if (i == 0) + break; + i--; + } else { + i++; + if (i >= self->rules->len) + break; + } + } +} + +void +nm_utils_share_rules_add_all_rules(NMUtilsShareRules *self, + const char * ip_iface, + in_addr_t addr, + guint plen) +{ + in_addr_t netmask; + in_addr_t network; + char str_mask[NM_UTILS_INET_ADDRSTRLEN]; + char str_addr[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert(self); + + netmask = _nm_utils_ip4_prefix_to_netmask(plen); + _nm_utils_inet4_ntop(netmask, str_mask); + + network = addr & netmask; + _nm_utils_inet4_ntop(network, str_addr); + + nm_utils_share_rules_add_rule_v( + self, + "nat", + "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE", + str_addr, + str_mask, + str_addr, + str_mask); + nm_utils_share_rules_add_rule_v( + self, + "filter", + "FORWARD --destination %s/%s --out-interface %s --match state --state " + "ESTABLISHED,RELATED --jump ACCEPT", + str_addr, + str_mask, + ip_iface); + nm_utils_share_rules_add_rule_v(self, + "filter", + "FORWARD --source %s/%s --in-interface %s --jump ACCEPT", + str_addr, + str_mask, + ip_iface); + nm_utils_share_rules_add_rule_v(self, + "filter", + "FORWARD --in-interface %s --out-interface %s --jump ACCEPT", + ip_iface, + ip_iface); + nm_utils_share_rules_add_rule_v(self, + "filter", + "FORWARD --out-interface %s --jump REJECT", + ip_iface); + nm_utils_share_rules_add_rule_v(self, + "filter", + "FORWARD --in-interface %s --jump REJECT", + ip_iface); + nm_utils_share_rules_add_rule_v( + self, + "filter", + "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT", + ip_iface); + nm_utils_share_rules_add_rule_v( + self, + "filter", + "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT", + ip_iface); + nm_utils_share_rules_add_rule_v( + self, + "filter", + "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT", + ip_iface); + nm_utils_share_rules_add_rule_v( + self, + "filter", + "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT", + ip_iface); +} diff --git a/src/core/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h new file mode 100644 index 0000000..2afb5a3 --- /dev/null +++ b/src/core/NetworkManagerUtils.h @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2016 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_UTILS_H__ +#define __NETWORKMANAGER_UTILS_H__ + +#include "nm-core-utils.h" +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-setting-ip-config.h" +#include "nm-setting-ip6-config.h" +#include "platform/nm-platform.h" + +/*****************************************************************************/ + +const char *nm_utils_get_ip_config_method(NMConnection *connection, int addr_family); + +const char *nm_utils_get_shared_wifi_permission(NMConnection *connection); + +void nm_utils_complete_generic(NMPlatform * platform, + NMConnection * connection, + const char * ctype, + NMConnection *const *existing_connections, + const char * preferred_id, + const char * fallback_id_prefix, + const char * ifname_prefix, + const char * ifname, + gboolean default_enable_ipv6); + +typedef gboolean(NMUtilsMatchFilterFunc)(NMConnection *connection, gpointer user_data); + +NMConnection *nm_utils_match_connection(NMConnection *const * connections, + NMConnection * original, + gboolean indicated, + gboolean device_has_carrier, + gint64 default_v4_metric, + gint64 default_v6_metric, + NMUtilsMatchFilterFunc match_filter_func, + gpointer match_filter_data); + +int nm_match_spec_device_by_pllink(const NMPlatformLink *pllink, + const char * match_device_type, + const char * match_dhcp_plugin, + const GSList * specs, + int no_match_value); + +/*****************************************************************************/ + +NMPlatformRoutingRule *nm_ip_routing_rule_to_platform(const NMIPRoutingRule *rule, + NMPlatformRoutingRule *out_pl); + +/*****************************************************************************/ + +/* during shutdown, there are two relevant timeouts. One is + * NM_SHUTDOWN_TIMEOUT_MS which is plenty of time, that we give for all + * actions to complete. Of course, during shutdown components should hurry + * to cleanup. + * + * When we initiate shutdown, we should start killing child processes + * with SIGTERM. If they don't complete within NM_SHUTDOWN_TIMEOUT_MS, we send + * SIGKILL. + * + * After NM_SHUTDOWN_TIMEOUT_MS, NetworkManager will however not yet terminate right + * away. It iterates the mainloop for another NM_SHUTDOWN_TIMEOUT_MS_WATCHDOG. This + * should give time to reap the child process (after SIGKILL). + * + * So, the maximum time we should wait before sending SIGKILL should be at most + * NM_SHUTDOWN_TIMEOUT_MS. + */ +#define NM_SHUTDOWN_TIMEOUT_MS 1500 +#define NM_SHUTDOWN_TIMEOUT_MS_WATCHDOG 500 + +typedef enum { + /* There is no watched_obj argument, and the shutdown is delayed until the user + * explicitly calls unregister on the returned handle. */ + NM_SHUTDOWN_WAIT_TYPE_HANDLE, + + /* The watched_obj argument is a GObject, and shutdown is delayed until the object + * gets destroyed (or unregistered). */ + NM_SHUTDOWN_WAIT_TYPE_OBJECT, + + /* The watched_obj argument is a GCancellable, and shutdown is delayed until the object + * gets destroyed (or unregistered). Note that after NM_SHUTDOWN_TIMEOUT_MS, the + * cancellable will be cancelled to notify listeners about the shutdown. */ + NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE, +} NMShutdownWaitType; + +typedef struct _NMShutdownWaitObjHandle NMShutdownWaitObjHandle; + +NMShutdownWaitObjHandle *nm_shutdown_wait_obj_register_full(gpointer watched_obj, + NMShutdownWaitType wait_type, + char * msg_reason, + gboolean free_msg_reason); + +static inline NMShutdownWaitObjHandle * +nm_shutdown_wait_obj_register_object_full(gpointer watched_obj, + char * msg_reason, + gboolean free_msg_reason) +{ + return nm_shutdown_wait_obj_register_full(watched_obj, + NM_SHUTDOWN_WAIT_TYPE_OBJECT, + msg_reason, + free_msg_reason); +} + +#define nm_shutdown_wait_obj_register_object(watched_obj, msg_reason) \ + nm_shutdown_wait_obj_register_object_full((watched_obj), ("" msg_reason ""), FALSE) + +static inline NMShutdownWaitObjHandle * +nm_shutdown_wait_obj_register_handle_full(char *msg_reason, gboolean free_msg_reason) +{ + return nm_shutdown_wait_obj_register_full(NULL, + NM_SHUTDOWN_WAIT_TYPE_HANDLE, + msg_reason, + free_msg_reason); +} + +#define nm_shutdown_wait_obj_register_handle(msg_reason) \ + nm_shutdown_wait_obj_register_handle_full(("" msg_reason ""), FALSE) + +static inline NMShutdownWaitObjHandle * +nm_shutdown_wait_obj_register_cancellable_full(GCancellable *watched_obj, + char * msg_reason, + gboolean free_msg_reason) +{ + return nm_shutdown_wait_obj_register_full(watched_obj, + NM_SHUTDOWN_WAIT_TYPE_CANCELLABLE, + msg_reason, + free_msg_reason); +} + +#define nm_shutdown_wait_obj_register_cancellable(watched_obj, msg_reason) \ + nm_shutdown_wait_obj_register_cancellable_full((watched_obj), ("" msg_reason ""), FALSE) + +void nm_shutdown_wait_obj_unregister(NMShutdownWaitObjHandle *handle); + +/*****************************************************************************/ + +const char *nm_utils_file_is_in_path(const char *abs_filename, const char *abs_path); + +/*****************************************************************************/ + +GPtrArray * +nm_utils_qdiscs_from_tc_setting(NMPlatform *platform, NMSettingTCConfig *s_tc, int ip_ifindex); +GPtrArray * +nm_utils_tfilters_from_tc_setting(NMPlatform *platform, NMSettingTCConfig *s_tc, int ip_ifindex); + +void nm_utils_ip_route_attribute_to_platform(int addr_family, + NMIPRoute * s_route, + NMPlatformIPRoute *r, + guint32 route_table); + +void nm_utils_ip_addresses_to_dbus(int addr_family, + const NMDedupMultiHeadEntry *head_entry, + const NMPObject * best_default_route, + NMSettingIP6ConfigPrivacy ipv6_privacy, + GVariant ** out_address_data, + GVariant ** out_addresses); + +void nm_utils_ip_routes_to_dbus(int addr_family, + const NMDedupMultiHeadEntry *head_entry, + GVariant ** out_route_data, + GVariant ** out_routes); + +/*****************************************************************************/ + +/* For now, all we track about a DHCP lease is the GHashTable with + * the options. + * + * We don't add a separate type for that, but we also don't want to use + * GHashTable directly (because most importantly leases should be immutable + * and passing a GHashTable pointer around neither makes it clear that + * this is a lease nor that it's immutable. + * + * Instead, add a simple opaque pointer and accessors that cast to a GHashTable. + * + * It has no overhead at run time, but gives some rudimentary type safety. */ + +typedef struct _NMDhcpLease NMDhcpLease; + +static inline NMDhcpLease * +nm_dhcp_lease_new_from_options(GHashTable *options_take) +{ + /* a NMDhcpLease is really just a GHashTable. But it's also supposed to be *immutable*. + * + * Hence, the API here takes over ownership of the reference to @options_take, that + * is to emphasize that we acquire ownership of the hash, and it should not be modified + * anymore. */ + return (NMDhcpLease *) options_take; +} + +static inline GHashTable * +nm_dhcp_lease_get_options(NMDhcpLease *lease) +{ + return (GHashTable *) lease; +} + +static inline void +nm_dhcp_lease_ref(NMDhcpLease *lease) +{ + if (lease) + g_hash_table_ref((GHashTable *) lease); +} + +static inline void +nm_dhcp_lease_unref(NMDhcpLease *lease) +{ + if (lease) + g_hash_table_unref((GHashTable *) lease); +} + +static inline const char * +nm_dhcp_lease_lookup_option(NMDhcpLease *lease, const char *option) +{ + nm_assert(option); + + return nm_g_hash_table_lookup((GHashTable *) lease, option); +} + +NM_AUTO_DEFINE_FCN(NMDhcpLease *, _nm_auto_unref_dhcplease, nm_dhcp_lease_unref); +#define nm_auto_unref_dhcplease nm_auto(_nm_auto_unref_dhcplease) + +/*****************************************************************************/ + +typedef struct _NMUtilsShareRules NMUtilsShareRules; + +NMUtilsShareRules *nm_utils_share_rules_new(void); + +void nm_utils_share_rules_free(NMUtilsShareRules *self); + +void +nm_utils_share_rules_add_rule_take(NMUtilsShareRules *self, const char *table, char *rule_take); + +static inline void +nm_utils_share_rules_add_rule(NMUtilsShareRules *self, const char *table, const char *rule) +{ + nm_utils_share_rules_add_rule_take(self, table, g_strdup(rule)); +} + +#define nm_utils_share_rules_add_rule_v(self, table, ...) \ + nm_utils_share_rules_add_rule_take((self), (table), g_strdup_printf(__VA_ARGS__)) + +void nm_utils_share_rules_add_all_rules(NMUtilsShareRules *self, + const char * ip_iface, + in_addr_t addr, + guint plen); + +void nm_utils_share_rules_apply(NMUtilsShareRules *self, gboolean shared); + +/*****************************************************************************/ + +#endif /* __NETWORKMANAGER_UTILS_H__ */ diff --git a/src/core/devices/adsl/meson.build b/src/core/devices/adsl/meson.build new file mode 100644 index 0000000..95f61d9 --- /dev/null +++ b/src/core/devices/adsl/meson.build @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_device_plugin_adsl = shared_module( + 'nm-device-plugin-adsl', + sources: files( + 'nm-atm-manager.c', + 'nm-device-adsl.c', + ), + dependencies: core_plugin_dep, + c_args: daemon_c_flags, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_device_plugin_adsl + +test( + 'check-local-devices-adsl', + check_exports, + args: [ + libnm_device_plugin_adsl.full_path(), + linker_script_devices, + ], +) diff --git a/src/core/devices/adsl/nm-atm-manager.c b/src/core/devices/adsl/nm-atm-manager.c new file mode 100644 index 0000000..eab723d --- /dev/null +++ b/src/core/devices/adsl/nm-atm-manager.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-setting-adsl.h" +#include "nm-device-adsl.h" +#include "devices/nm-device-factory.h" +#include "platform/nm-platform.h" +#include "nm-udev-aux/nm-udev-utils.h" + +/*****************************************************************************/ + +#define NM_TYPE_ATM_MANAGER (nm_atm_manager_get_type()) +#define NM_ATM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_ATM_MANAGER, NMAtmManager)) +#define NM_ATM_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_ATM_MANAGER, NMAtmManagerClass)) +#define NM_IS_ATM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_ATM_MANAGER)) +#define NM_IS_ATM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_ATM_MANAGER)) +#define NM_ATM_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_ATM_MANAGER, NMAtmManagerClass)) + +typedef struct { + NMUdevClient *udev_client; + GSList * devices; +} NMAtmManagerPrivate; + +typedef struct { + NMDeviceFactory parent; + NMAtmManagerPrivate _priv; +} NMAtmManager; + +typedef struct { + NMDeviceFactoryClass parent; +} NMAtmManagerClass; + +static GType nm_atm_manager_get_type(void); + +G_DEFINE_TYPE(NMAtmManager, nm_atm_manager, NM_TYPE_DEVICE_FACTORY); + +#define NM_ATM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAtmManager, NM_IS_ATM_MANAGER) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES( + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_ADSL_SETTING_NAME)); + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + return g_object_new(NM_TYPE_ATM_MANAGER, NULL); +} + +/*****************************************************************************/ + +static gboolean +dev_get_attrs(struct udev_device *udev_device, const char **out_path, char **out_driver) +{ + struct udev_device *parent = NULL; + const char * driver, *path; + + g_return_val_if_fail(udev_device != NULL, FALSE); + g_return_val_if_fail(out_path != NULL, FALSE); + g_return_val_if_fail(out_driver != NULL, FALSE); + + path = udev_device_get_syspath(udev_device); + if (!path) { + nm_log_warn(LOGD_PLATFORM, "couldn't determine device path; ignoring..."); + return FALSE; + } + + driver = udev_device_get_driver(udev_device); + if (!driver) { + /* Try the parent */ + parent = udev_device_get_parent(udev_device); + if (parent) + driver = udev_device_get_driver(parent); + } + + *out_path = path; + *out_driver = g_strdup(driver); + + return TRUE; +} + +static void +device_destroyed(gpointer user_data, GObject *dead) +{ + NMAtmManager * self = NM_ATM_MANAGER(user_data); + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self); + + priv->devices = g_slist_remove(priv->devices, dead); +} + +static void +adsl_add(NMAtmManager *self, struct udev_device *udev_device) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self); + const char * ifname, *sysfs_path = NULL; + char * driver = NULL; + gs_free char * atm_index_path = NULL; + int atm_index; + NMDevice * device; + + g_return_if_fail(udev_device != NULL); + + ifname = udev_device_get_sysname(udev_device); + if (!ifname) { + nm_log_warn(LOGD_PLATFORM, "failed to get device's interface name"); + return; + } + + nm_log_dbg(LOGD_PLATFORM, "(%s): found ATM device", ifname); + + atm_index_path = + g_strdup_printf("/sys/class/atm/%s/atmindex", NM_ASSERT_VALID_PATH_COMPONENT(ifname)); + atm_index = (int) nm_platform_sysctl_get_int_checked(NM_PLATFORM_GET, + NMP_SYSCTL_PATHID_ABSOLUTE(atm_index_path), + 10, + 0, + G_MAXINT, + -1); + if (atm_index < 0) { + nm_log_warn(LOGD_PLATFORM, "(%s): failed to get ATM index", ifname); + return; + } + + if (!dev_get_attrs(udev_device, &sysfs_path, &driver)) { + nm_log_warn(LOGD_PLATFORM, "(%s): failed to get ATM attributes", ifname); + return; + } + + g_assert(sysfs_path); + + device = nm_device_adsl_new(sysfs_path, ifname, driver, atm_index); + g_assert(device); + + priv->devices = g_slist_prepend(priv->devices, device); + g_object_weak_ref(G_OBJECT(device), device_destroyed, self); + + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); + g_object_unref(device); + + g_free(driver); +} + +static void +adsl_remove(NMAtmManager *self, struct udev_device *udev_device) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self); + const char * iface = udev_device_get_sysname(udev_device); + GSList * iter; + + nm_log_dbg(LOGD_PLATFORM, "(%s): removing ATM device", iface); + + for (iter = priv->devices; iter; iter = iter->next) { + NMDevice *device = iter->data; + + /* Match 'iface' not 'ip_iface' to the ATM device instead of the + * NAS bridge interface or PPPoE interface. + */ + if (g_strcmp0(nm_device_get_iface(device), iface) != 0) + continue; + + g_object_weak_unref(G_OBJECT(iter->data), device_destroyed, self); + priv->devices = g_slist_remove(priv->devices, device); + g_signal_emit_by_name(device, NM_DEVICE_REMOVED); + break; + } +} + +static void +start(NMDeviceFactory *factory) +{ + NMAtmManager * self = NM_ATM_MANAGER(factory); + NMAtmManagerPrivate * priv = NM_ATM_MANAGER_GET_PRIVATE(self); + struct udev_enumerate * enumerate; + struct udev_list_entry *devices; + + enumerate = nm_udev_client_enumerate_new(priv->udev_client); + udev_enumerate_add_match_is_initialized(enumerate); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + for (; devices; devices = udev_list_entry_get_next(devices)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(devices)); + if (udevice) { + adsl_add(self, udevice); + udev_device_unref(udevice); + } + } + udev_enumerate_unref(enumerate); +} + +static void +handle_uevent(NMUdevClient *client, struct udev_device *device, gpointer user_data) +{ + NMAtmManager *self = NM_ATM_MANAGER(user_data); + const char * subsys; + const char * ifindex; + guint64 seqnum; + const char * action; + + action = udev_device_get_action(device); + + g_return_if_fail(action != NULL); + + /* A bit paranoid */ + subsys = udev_device_get_subsystem(device); + g_return_if_fail(!g_strcmp0(subsys, "atm")); + + ifindex = udev_device_get_property_value(device, "IFINDEX"); + seqnum = udev_device_get_seqnum(device); + nm_log_dbg(LOGD_PLATFORM, + "UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, + action, + subsys, + udev_device_get_sysname(device), + ifindex ?: "unknown", + seqnum); + + if (!strcmp(action, "add")) + adsl_add(self, device); + else if (!strcmp(action, "remove")) + adsl_remove(self, device); +} + +/*****************************************************************************/ + +static void +nm_atm_manager_init(NMAtmManager *self) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self); + + priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("atm"), handle_uevent, self); +} + +static void +dispose(GObject *object) +{ + NMAtmManager * self = NM_ATM_MANAGER(object); + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self); + GSList * iter; + + for (iter = priv->devices; iter; iter = iter->next) + g_object_weak_unref(G_OBJECT(iter->data), device_destroyed, self); + nm_clear_pointer(&priv->devices, g_slist_free); + + priv->udev_client = nm_udev_client_destroy(priv->udev_client); + + G_OBJECT_CLASS(nm_atm_manager_parent_class)->dispose(object); +} + +static void +nm_atm_manager_class_init(NMAtmManagerClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + object_class->dispose = dispose; + + factory_class->get_supported_types = get_supported_types; + factory_class->start = start; +} diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c new file mode 100644 index 0000000..29a73ba --- /dev/null +++ b/src/core/devices/adsl/nm-device-adsl.c @@ -0,0 +1,720 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Pantelis Koukousoulas + */ + +#include "nm-default.h" + +#include "nm-device-adsl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-ip4-config.h" +#include "devices/nm-device-private.h" +#include "platform/nm-platform.h" +#include "ppp/nm-ppp-manager-call.h" +#include "ppp/nm-ppp-status.h" +#include "nm-setting-adsl.h" +#include "nm-utils.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceAdsl +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_ATM_INDEX, ); + +typedef struct { + guint carrier_poll_id; + int atm_index; + + /* PPP */ + NMPPPManager *ppp_manager; + + /* RFC 2684 bridging (PPPoE over ATM) */ + int brfd; + int nas_ifindex; + char *nas_ifname; + guint nas_update_id; + guint nas_update_count; +} NMDeviceAdslPrivate; + +struct _NMDeviceAdsl { + NMDevice parent; + NMDeviceAdslPrivate _priv; +}; + +struct _NMDeviceAdslClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceAdsl, nm_device_adsl, NM_TYPE_DEVICE) + +#define NM_DEVICE_ADSL_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceAdsl, NM_IS_DEVICE_ADSL, NMDevice) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return (NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER + | NM_DEVICE_CAP_IS_NON_KERNEL); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingAdsl *s_adsl; + const char * protocol; + + if (!NM_DEVICE_CLASS(nm_device_adsl_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_adsl = nm_connection_get_setting_adsl(connection); + + protocol = nm_setting_adsl_get_protocol(s_adsl); + if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM)) { + /* FIXME: we don't yet support IPoATM */ + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "IPoATM protocol is not yet supported"); + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingAdsl *s_adsl; + + /* + * We can't telepathically figure out the username, so if + * it wasn't given, we can't complete the connection. + */ + s_adsl = nm_connection_get_setting_adsl(connection); + if (s_adsl && !nm_setting_verify(NM_SETTING(s_adsl), NULL, error)) + return FALSE; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_ADSL_SETTING_NAME, + existing_connections, + NULL, + _("ADSL connection"), + NULL, + NULL, + FALSE); /* No IPv6 yet by default */ + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +br2684_assign_vcc(NMDeviceAdsl *self, NMSettingAdsl *s_adsl) +{ + NMDeviceAdslPrivate * priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + struct sockaddr_atmpvc addr; + struct atm_backend_br2684 be; + struct atm_qos qos; + int errsv, err, bufsize = 8192; + const char * encapsulation; + gboolean is_llc; + + g_return_val_if_fail(priv->brfd == -1, FALSE); + g_return_val_if_fail(priv->nas_ifname != NULL, FALSE); + + priv->brfd = socket(PF_ATMPVC, SOCK_DGRAM | SOCK_CLOEXEC, ATM_AAL5); + if (priv->brfd < 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to open ATM control socket (%d)", errsv); + priv->brfd = -1; + return FALSE; + } + + err = setsockopt(priv->brfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); + if (err != 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to set SNDBUF option (%d)", errsv); + goto error; + } + + /* QoS */ + memset(&qos, 0, sizeof(qos)); + qos.aal = ATM_AAL5; + qos.txtp.traffic_class = ATM_UBR; + qos.txtp.max_sdu = 1524; + qos.txtp.pcr = ATM_MAX_PCR; + qos.rxtp = qos.txtp; + + err = setsockopt(priv->brfd, SOL_ATM, SO_ATMQOS, &qos, sizeof(qos)); + if (err != 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to set QoS (%d)", errsv); + goto error; + } + + encapsulation = nm_setting_adsl_get_encapsulation(s_adsl); + + /* VPI/VCI */ + memset(&addr, 0, sizeof(addr)); + addr.sap_family = AF_ATMPVC; + addr.sap_addr.itf = priv->atm_index; + addr.sap_addr.vpi = (guint16) nm_setting_adsl_get_vpi(s_adsl); + addr.sap_addr.vci = (int) nm_setting_adsl_get_vci(s_adsl); + + _LOGD(LOGD_ADSL, + "assigning address %d.%d.%d encapsulation %s", + priv->atm_index, + addr.sap_addr.vpi, + addr.sap_addr.vci, + encapsulation ?: "(none)"); + + err = connect(priv->brfd, (struct sockaddr *) &addr, sizeof(addr)); + if (err != 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to set VPI/VCI (%d)", errsv); + goto error; + } + + /* And last attach the VCC to the interface */ + is_llc = (g_strcmp0(encapsulation, "llc") == 0); + + memset(&be, 0, sizeof(be)); + be.backend_num = ATM_BACKEND_BR2684; + be.ifspec.method = BR2684_FIND_BYIFNAME; + nm_utils_ifname_cpy(be.ifspec.spec.ifname, priv->nas_ifname); + be.fcs_in = BR2684_FCSIN_NO; + be.fcs_out = BR2684_FCSOUT_NO; + be.encaps = is_llc ? BR2684_ENCAPS_LLC : BR2684_ENCAPS_VC; + err = ioctl(priv->brfd, ATM_SETBACKEND, &be); + if (err != 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to attach VCC (%d)", errsv); + goto error; + } + + return TRUE; + +error: + nm_close(priv->brfd); + priv->brfd = -1; + return FALSE; +} + +static void +link_changed_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *info, + int change_type_i, + NMDeviceAdsl * self) +{ + const NMPlatformSignalChangeType change_type = change_type_i; + + if (change_type == NM_PLATFORM_SIGNAL_REMOVED) { + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + + /* This only gets called for PPPoE connections and "nas" interfaces */ + + if (priv->nas_ifindex > 0 && ifindex == priv->nas_ifindex) { + /* NAS device went away for some reason; kill the connection */ + _LOGD(LOGD_ADSL, "br2684 interface disappeared"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BR2684_FAILED); + } + } +} + +static gboolean +pppoe_vcc_config(NMDeviceAdsl *self) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMSettingAdsl * s_adsl; + + s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL); + + g_return_val_if_fail(s_adsl, FALSE); + + /* Set up the VCC */ + if (!br2684_assign_vcc(self, s_adsl)) + return FALSE; + + /* Watch for the 'nas' interface going away */ + g_signal_connect(nm_device_get_platform(device), + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(link_changed_cb), + self); + + _LOGD(LOGD_ADSL, "ATM setup successful"); + + /* otherwise we're good for stage3 */ + nm_platform_link_set_up(nm_device_get_platform(device), priv->nas_ifindex, NULL); + + return TRUE; +} + +static gboolean +nas_update_cb(gpointer user_data) +{ + NMDeviceAdsl * self = NM_DEVICE_ADSL(user_data); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + + nm_assert(priv->nas_ifname); + + priv->nas_update_count++; + + nm_assert(priv->nas_ifindex <= 0); + priv->nas_ifindex = + nm_platform_link_get_ifindex(nm_device_get_platform(device), priv->nas_ifname); + if (priv->nas_ifindex <= 0) { + if (priv->nas_update_count <= 10) { + /* Keep waiting for it to appear */ + return G_SOURCE_CONTINUE; + } + priv->nas_update_id = 0; + _LOGW(LOGD_ADSL, + "failed to find br2684 interface %s ifindex after timeout", + priv->nas_ifname); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BR2684_FAILED); + return G_SOURCE_REMOVE; + } + + priv->nas_update_id = 0; + _LOGD(LOGD_ADSL, "using br2684 iface '%s' index %d", priv->nas_ifname, priv->nas_ifindex); + + if (!pppoe_vcc_config(self)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BR2684_FAILED); + return G_SOURCE_REMOVE; + } + + nm_device_activate_schedule_stage2_device_config(device, TRUE); + return G_SOURCE_REMOVE; +} + +static gboolean +br2684_create_iface(NMDeviceAdsl *self) +{ + NMDeviceAdslPrivate * priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + struct atm_newif_br2684 ni; + nm_auto_close int fd = -1; + int err, errsv; + guint num = 0; + + if (nm_clear_g_source(&priv->nas_update_id)) + nm_assert_not_reached(); + + fd = socket(PF_ATMPVC, SOCK_DGRAM | SOCK_CLOEXEC, ATM_AAL5); + if (fd < 0) { + errsv = errno; + _LOGE(LOGD_ADSL, "failed to open ATM control socket (%d)", errsv); + return FALSE; + } + + memset(&ni, 0, sizeof(ni)); + ni.backend_num = ATM_BACKEND_BR2684; + ni.media = BR2684_MEDIA_ETHERNET; + ni.mtu = 1500; + + /* Loop attempting to create an interface that doesn't exist yet. The + * kernel can create one for us automatically, but due to API issues it + * cannot return that name to us. Since we want to know the name right + * away, just brute-force it. + */ + while (TRUE) { + memset(&ni.ifname, 0, sizeof(ni.ifname)); + g_snprintf(ni.ifname, sizeof(ni.ifname), "nas%u", num++); + + err = ioctl(fd, ATM_NEWBACKENDIF, &ni); + if (err != 0) { + errsv = errno; + if (errsv == EEXIST) + continue; + + _LOGW(LOGD_ADSL, "failed to create br2684 interface (%d)", errsv); + return FALSE; + } + + nm_utils_strdup_reset(&priv->nas_ifname, ni.ifname); + _LOGD(LOGD_ADSL, "waiting for br2684 iface '%s' to appear", priv->nas_ifname); + priv->nas_update_count = 0; + priv->nas_update_id = g_timeout_add(100, nas_update_cb, self); + return TRUE; + } +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceAdsl * self = NM_DEVICE_ADSL(device); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + NMSettingAdsl * s_adsl; + const char * protocol; + + s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL); + + g_return_val_if_fail(s_adsl, NM_ACT_STAGE_RETURN_FAILURE); + + protocol = nm_setting_adsl_get_protocol(s_adsl); + _LOGD(LOGD_ADSL, "using ADSL protocol '%s'", protocol); + + if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) { + /* PPPoA doesn't need anything special */ + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) { + /* PPPoE needs RFC2684 bridging before we can do PPP over it */ + if (priv->nas_ifindex <= 0) { + if (priv->nas_update_id == 0) { + if (!br2684_create_iface(self)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + return NM_ACT_STAGE_RETURN_POSTPONE; + } + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + _LOGW(LOGD_ADSL, "unhandled ADSL protocol '%s'", protocol); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + if (!nm_device_set_ip_ifindex(device, ifindex)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } +} + +static void +ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + /* Ignore PPP IP4 events that come in after initial configuration */ + if (nm_device_activate_ip4_state_in_conf(device)) + nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config)); +} + +static NMActStageReturn +act_stage3_ip4_config_start(NMDevice * device, + NMIP4Config ** out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceAdsl * self = NM_DEVICE_ADSL(device); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + NMSettingAdsl * s_adsl; + NMActRequest * req; + GError * err = NULL; + const char * ppp_iface; + + req = nm_device_get_act_request(device); + + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL); + + g_return_val_if_fail(s_adsl, NM_ACT_STAGE_RETURN_FAILURE); + + /* PPPoE uses the NAS interface, not the ATM interface */ + if (nm_streq0(nm_setting_adsl_get_protocol(s_adsl), NM_SETTING_ADSL_PROTOCOL_PPPOE)) { + nm_assert(priv->nas_ifname); + ppp_iface = priv->nas_ifname; + + _LOGD(LOGD_ADSL, "starting PPPoE on br2684 interface %s", priv->nas_ifname); + } else { + ppp_iface = nm_device_get_iface(device); + _LOGD(LOGD_ADSL, "starting PPPoA"); + } + + priv->ppp_manager = nm_ppp_manager_create(ppp_iface, &err); + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + nm_device_get_route_table(device, AF_INET), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_table(device, AF_INET6), + nm_device_get_route_metric(device, AF_INET6)); + } + + if (!priv->ppp_manager + || !nm_ppp_manager_start(priv->ppp_manager, + req, + nm_setting_adsl_get_username(s_adsl), + 30, + 0, + &err)) { + _LOGW(LOGD_ADSL, "PPP failed to start: %s", err->message); + g_error_free(err); + + g_clear_object(&priv->ppp_manager); + + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_CALLBACK(ppp_state_changed), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_CALLBACK(ppp_ifindex_set), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_CALLBACK(ppp_ip4_config), + self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + if (addr_family == AF_INET) + return act_stage3_ip4_config_start(device, (NMIP4Config **) out_config, out_failure_reason); + + return NM_DEVICE_CLASS(nm_device_adsl_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static void +adsl_cleanup(NMDeviceAdsl *self) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + + if (priv->ppp_manager) { + g_signal_handlers_disconnect_by_func(priv->ppp_manager, + G_CALLBACK(ppp_state_changed), + self); + g_signal_handlers_disconnect_by_func(priv->ppp_manager, G_CALLBACK(ppp_ip4_config), self); + nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL); + g_clear_object(&priv->ppp_manager); + } + + g_signal_handlers_disconnect_by_func(nm_device_get_platform(NM_DEVICE(self)), + G_CALLBACK(link_changed_cb), + self); + + nm_close(priv->brfd); + priv->brfd = -1; + + nm_clear_g_source(&priv->nas_update_id); + + /* FIXME: kernel has no way of explicitly deleting the 'nasX' interface yet, + * so it gets leaked. It does get destroyed when it's no longer in use, + * but we have no control over that. + */ + priv->nas_ifindex = 0; + nm_clear_g_free(&priv->nas_ifname); +} + +static void +deactivate(NMDevice *device) +{ + adsl_cleanup(NM_DEVICE_ADSL(device)); +} + +/*****************************************************************************/ + +static gboolean +carrier_update_cb(gpointer user_data) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL(user_data); + int carrier; + char * path; + + path = g_strdup_printf("/sys/class/atm/%s/carrier", + NM_ASSERT_VALID_PATH_COMPONENT(nm_device_get_iface(NM_DEVICE(self)))); + carrier = (int) nm_platform_sysctl_get_int_checked(nm_device_get_platform(NM_DEVICE(self)), + NMP_SYSCTL_PATHID_ABSOLUTE(path), + 10, + 0, + 1, + -1); + g_free(path); + + if (carrier != -1) + nm_device_set_carrier(NM_DEVICE(self), carrier); + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_ATM_INDEX: + g_value_set_int(value, NM_DEVICE_ADSL_GET_PRIVATE(object)->atm_index); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_ATM_INDEX: + /* construct-only */ + NM_DEVICE_ADSL_GET_PRIVATE(object)->atm_index = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_adsl_init(NMDeviceAdsl *self) +{} + +static void +constructed(GObject *object) +{ + NMDeviceAdsl * self = NM_DEVICE_ADSL(object); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_device_adsl_parent_class)->constructed(object); + + priv->carrier_poll_id = g_timeout_add_seconds(5, carrier_update_cb, self); + + _LOGD(LOGD_ADSL, "ATM device index %d", priv->atm_index); + + g_return_if_fail(priv->atm_index >= 0); +} + +NMDevice * +nm_device_adsl_new(const char *udi, const char *iface, const char *driver, int atm_index) +{ + g_return_val_if_fail(udi != NULL, NULL); + g_return_val_if_fail(atm_index >= 0, NULL); + + return g_object_new(NM_TYPE_DEVICE_ADSL, + NM_DEVICE_UDI, + udi, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_DRIVER, + driver, + NM_DEVICE_ADSL_ATM_INDEX, + atm_index, + NM_DEVICE_TYPE_DESC, + "ADSL", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_ADSL, + NULL); +} + +static void +dispose(GObject *object) +{ + adsl_cleanup(NM_DEVICE_ADSL(object)); + + nm_clear_g_source(&NM_DEVICE_ADSL_GET_PRIVATE(object)->carrier_poll_id); + + G_OBJECT_CLASS(nm_device_adsl_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_adsl = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_ADSL, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", + "b", + NM_DEVICE_CARRIER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_adsl_class_init(NMDeviceAdslClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_adsl); + + device_class->connection_type_check_compatible = NM_SETTING_ADSL_SETTING_NAME; + + device_class->get_generic_capabilities = get_generic_capabilities; + + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->deactivate = deactivate; + + obj_properties[PROP_ATM_INDEX] = + g_param_spec_int(NM_DEVICE_ADSL_ATM_INDEX, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/adsl/nm-device-adsl.h b/src/core/devices/adsl/nm-device-adsl.h new file mode 100644 index 0000000..c5c1890 --- /dev/null +++ b/src/core/devices/adsl/nm-device-adsl.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Author: Pantelis Koukousoulas + * Copyright (C) 2009 - 2011 Red Hat Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_ADSL_H__ +#define __NETWORKMANAGER_DEVICE_ADSL_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_DEVICE_ADSL (nm_device_adsl_get_type()) +#define NM_DEVICE_ADSL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_ADSL, NMDeviceAdsl)) +#define NM_DEVICE_ADSL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_ADSL, NMDeviceAdslClass)) +#define NM_IS_DEVICE_ADSL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_ADSL)) +#define NM_IS_DEVICE_ADSL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_ADSL)) +#define NM_DEVICE_ADSL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_ADSL, NMDeviceAdslClass)) + +#define NM_DEVICE_ADSL_ATM_INDEX "atm-index" + +typedef struct _NMDeviceAdsl NMDeviceAdsl; +typedef struct _NMDeviceAdslClass NMDeviceAdslClass; + +GType nm_device_adsl_get_type(void); + +NMDevice *nm_device_adsl_new(const char *udi, const char *iface, const char *driver, int atm_index); + +#endif /* NM_DEVICE_ADSL_H */ diff --git a/src/core/devices/bluetooth/meson.build b/src/core/devices/bluetooth/meson.build new file mode 100644 index 0000000..d5f2606 --- /dev/null +++ b/src/core/devices/bluetooth/meson.build @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_device_plugin_bluetooth_static = static_library( + 'nm-device-plugin-bluetooth-static', + sources: files( + 'nm-bluez-manager.c', + 'nm-bt-error.c', + 'nm-device-bt.c', + ) + (enable_bluez5_dun ? files('nm-bluez5-dun.c') : files()), + dependencies: [ + core_default_dep, + libnm_wwan_dep, + bluez5_dep, + ], + c_args: daemon_c_flags, +) + +libnm_device_plugin_bluetooth_static_dep = declare_dependency( + link_whole: libnm_device_plugin_bluetooth_static, +) + +libnm_device_plugin_bluetooth = shared_module( + 'nm-device-plugin-bluetooth', + dependencies: [ + core_plugin_dep, + libnm_wwan_dep, + bluez5_dep, + libnm_device_plugin_bluetooth_static_dep, + ], + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, + install_rpath: nm_plugindir, +) + +core_plugins += libnm_device_plugin_bluetooth + +test( + 'check-local-devices-bluetooth', + check_exports, + args: [ + libnm_device_plugin_bluetooth.full_path(), + linker_script_devices + ], +) + +if enable_tests + executable( + 'nm-bt-test', + 'tests/nm-bt-test.c', + dependencies: [ + libNetworkManagerTest_dep, + core_default_dep, + libnm_wwan_dep, + bluez5_dep, + libnm_device_plugin_bluetooth_static_dep, + ], + c_args: test_c_flags, + ) +endif diff --git a/src/core/devices/bluetooth/nm-bluez-common.h b/src/core/devices/bluetooth/nm-bluez-common.h new file mode 100644 index 0000000..868a985 --- /dev/null +++ b/src/core/devices/bluetooth/nm-bluez-common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_BLUEZ_COMMON_H__ +#define __NETWORKMANAGER_BLUEZ_COMMON_H__ + +#define BLUETOOTH_CONNECT_DUN "dun" +#define BLUETOOTH_CONNECT_NAP "nap" + +#define NM_BLUEZ_SERVICE "org.bluez" + +#define NM_BLUEZ_MANAGER_PATH "/" + +#define NM_BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1" +#define NM_BLUEZ5_DEVICE_INTERFACE "org.bluez.Device1" +#define NM_BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1" +#define NM_BLUEZ5_NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1" + +#define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added" +#define NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED "network-server-added" + +#endif /* NM_BLUEZ_COMMON_H */ diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c new file mode 100644 index 0000000..3343b6e --- /dev/null +++ b/src/core/devices/bluetooth/nm-bluez-manager.c @@ -0,0 +1,2896 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-bluez-manager.h" + +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-dbus-manager.h" +#include "devices/nm-device-factory.h" +#include "devices/nm-device-bridge.h" +#include "nm-setting-bluetooth.h" +#include "settings/nm-settings.h" +#include "nm-bluez-common.h" +#include "nm-device-bt.h" +#include "nm-manager.h" +#include "nm-bluez5-dun.h" +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-std-aux/nm-dbus-compat.h" + +/*****************************************************************************/ + +#if WITH_BLUEZ5_DUN + #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN +#else + #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE +#endif +#define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN) + +typedef struct { + const char * bdaddr; + CList lst_head; + NMBluetoothCapabilities bt_type : 8; + char bdaddr_data[]; +} ConnDataHead; + +typedef struct { + NMSettingsConnection *sett_conn; + ConnDataHead * cdata_hd; + CList lst; +} ConnDataElem; + +typedef struct { + GCancellable * ext_cancellable; + GCancellable * int_cancellable; + NMBtVTableRegisterCallback callback; + gpointer callback_user_data; + gulong ext_cancelled_id; +} NetworkServerRegisterReqData; + +typedef struct { + GCancellable * ext_cancellable; + GCancellable * int_cancellable; + NMBluezManagerConnectCb callback; + gpointer callback_user_data; + char * device_name; + gulong ext_cancelled_id; + guint timeout_id; + guint timeout_wait_connect_id; +} DeviceConnectReqData; + +typedef struct { + const char *object_path; + + NMBluezManager *self; + + /* Fields name with "d_" prefix are purely cached values from BlueZ's + * ObjectManager D-Bus interface. There is no logic whatsoever about + * them. + */ + + CList process_change_lst; + + struct { + char *address; + } d_adapter; + + struct { + char *address; + char *name; + char *adapter; + } d_device; + + struct { + char *interface; + } d_network; + + struct { + CList lst; + char * adapter_address; + NMDevice * device_br; + NetworkServerRegisterReqData *r_req_data; + } x_network_server; + + struct { + NMSettingsConnection *panu_connection; + NMDeviceBt * device_bt; + DeviceConnectReqData *c_req_data; + NMBluez5DunContext * connect_dun_context; + gulong device_bt_signal_id; + } x_device; + + /* indicate whether the D-Bus object has the particular D-Bus interface. */ + bool d_has_adapter_iface : 1; + bool d_has_device_iface : 1; + bool d_has_network_iface : 1; + bool d_has_network_server_iface : 1; + + /* cached D-Bus properties for Device1 ("d_device*"). */ + NMBluetoothCapabilities d_device_capabilities : 6; + bool d_device_connected : 1; + bool d_device_paired : 1; + + /* cached D-Bus properties for Network1 ("d_network*"). */ + bool d_network_connected : 1; + + /* cached D-Bus properties for Adapter1 ("d_adapter*"). */ + bool d_adapter_powered : 1; + + /* properties related to device ("x_device*"). */ + NMBluetoothCapabilities x_device_connect_bt_type : 6; + bool x_device_is_usable : 1; + bool x_device_is_connected : 1; + + bool x_device_panu_connection_allow_create : 1; + + /* flag to remember last time when we checked wether the object + * was a suitable adapter that is usable to a device. */ + bool was_usable_adapter_for_device_before : 1; + + char _object_path_intern[]; +} BzDBusObj; + +typedef struct { + NMManager * manager; + NMSettings *settings; + + GDBusConnection *dbus_connection; + + NMBtVTableNetworkServer vtable_network_server; + + GCancellable *name_owner_get_cancellable; + GCancellable *get_managed_objects_cancellable; + + GHashTable *bzobjs; + + char *name_owner; + + GHashTable *conn_data_heads; + GHashTable *conn_data_elems; + + CList network_server_lst_head; + + CList process_change_lst_head; + + guint name_owner_changed_id; + + guint managed_objects_changed_id; + + guint properties_changed_id; + + guint process_change_idle_id; + + bool settings_registered : 1; +} NMBluezManagerPrivate; + +struct _NMBluezManager { + NMDeviceFactory parent; + NMBluezManagerPrivate _priv; +}; + +struct _NMBluezManagerClass { + NMDeviceFactoryClass parent; +}; + +G_DEFINE_TYPE(NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY); + +#define NM_BLUEZ_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMBluezManager, NM_IS_BLUEZ_MANAGER) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES( + NM_LINK_TYPE_BNEP) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BLUETOOTH_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + return g_object_new(NM_TYPE_BLUEZ_MANAGER, NULL); +} + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "bluez", __VA_ARGS__) + +/*****************************************************************************/ + +static NMBluetoothCapabilities +convert_uuids_to_capabilities(const char *const *strv) +{ + NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; + + if (strv) { + for (; strv[0]; strv++) { + gs_free char *s_part1 = NULL; + const char * str = strv[0]; + const char * s; + + s = strchr(str, '-'); + if (!s) + continue; + + s_part1 = g_strndup(str, s - str); + switch (_nm_utils_ascii_str_to_int64(s_part1, 16, 0, G_MAXINT, -1)) { + case 0x1103: + capabilities |= NM_BT_CAPABILITY_DUN; + break; + case 0x1116: + capabilities |= NM_BT_CAPABILITY_NAP; + break; + default: + break; + } + } + } + + return capabilities; +} + +/*****************************************************************************/ + +static void _cleanup_for_name_owner(NMBluezManager *self); +static void _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason); +static gboolean _bzobjs_network_server_is_usable(const BzDBusObj *bzobj, gboolean require_powered); +static gboolean _bzobjs_is_dead(const BzDBusObj *bzobj); +static gboolean _bzobjs_device_is_usable(const BzDBusObj *bzobj, + BzDBusObj ** out_adapter_bzobj, + gboolean * out_create_panu_connection); +static gboolean _bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj); +static ConnDataHead * +_conn_track_find_head(NMBluezManager *self, NMBluetoothCapabilities bt_type, const char *bdaddr); +static void _process_change_idle_schedule(NMBluezManager *self, BzDBusObj *bzobj); +static void +_network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const char *reason); +static gboolean _connect_timeout_wait_connected_cb(gpointer user_data); + +/*****************************************************************************/ + +static void +_dbus_call_complete_cb_nop(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + /* we don't do anything at all. The only reason to register this + * callback is so that GDBusConnection keeps the cancellable alive + * long enough until the call completes. + * + * Note that this cancellable in turn is registered via + * nm_shutdown_wait_obj_register_*(), to block shutdown until + * we are done. */ +} + +/*****************************************************************************/ + +static void +_network_server_register_req_data_complete(NetworkServerRegisterReqData *r_req_data, GError *error) +{ + nm_clear_g_signal_handler(r_req_data->ext_cancellable, &r_req_data->ext_cancelled_id); + + nm_clear_g_cancellable(&r_req_data->int_cancellable); + + if (r_req_data->callback) { + gs_free_error GError *error_cancelled = NULL; + + if (g_cancellable_set_error_if_cancelled(r_req_data->ext_cancellable, &error_cancelled)) + error = error_cancelled; + + r_req_data->callback(error, r_req_data->callback_user_data); + } + + g_object_unref(r_req_data->ext_cancellable); + nm_g_slice_free(r_req_data); +} + +static void +_device_connect_req_data_complete(DeviceConnectReqData *c_req_data, + NMBluezManager * self, + const char * device_name, + GError * error) +{ + nm_assert((!!device_name) != (!!error)); + + nm_clear_g_signal_handler(c_req_data->ext_cancellable, &c_req_data->ext_cancelled_id); + + nm_clear_g_cancellable(&c_req_data->int_cancellable); + nm_clear_g_source(&c_req_data->timeout_id); + nm_clear_g_source(&c_req_data->timeout_wait_connect_id); + + if (c_req_data->callback) { + gs_free_error GError *error_cancelled = NULL; + + if (g_cancellable_set_error_if_cancelled(c_req_data->ext_cancellable, &error_cancelled)) { + error = error_cancelled; + device_name = NULL; + } + + c_req_data->callback(self, TRUE, device_name, error, c_req_data->callback_user_data); + } + + g_object_unref(c_req_data->ext_cancellable); + nm_clear_g_free(&c_req_data->device_name); + nm_g_slice_free(c_req_data); +} + +/*****************************************************************************/ + +static BzDBusObj * +_bz_dbus_obj_new(NMBluezManager *self, const char *object_path) +{ + BzDBusObj *bzobj; + gsize l; + + nm_assert(NM_IS_BLUEZ_MANAGER(self)); + + l = strlen(object_path) + 1; + + bzobj = g_malloc(sizeof(BzDBusObj) + l); + *bzobj = (BzDBusObj){ + .object_path = bzobj->_object_path_intern, + .self = self, + .x_network_server.lst = C_LIST_INIT(bzobj->x_network_server.lst), + .process_change_lst = C_LIST_INIT(bzobj->process_change_lst), + .x_device_panu_connection_allow_create = TRUE, + }; + memcpy(bzobj->_object_path_intern, object_path, l); + + return bzobj; +} + +static void +_bz_dbus_obj_free(BzDBusObj *bzobj) +{ + nm_assert(bzobj); + nm_assert(NM_IS_BLUEZ_MANAGER(bzobj->self)); + nm_assert(!bzobj->x_network_server.device_br); + nm_assert(!bzobj->x_network_server.r_req_data); + nm_assert(!bzobj->x_device.c_req_data); + + c_list_unlink_stale(&bzobj->process_change_lst); + c_list_unlink_stale(&bzobj->x_network_server.lst); + g_free(bzobj->x_network_server.adapter_address); + g_free(bzobj->d_adapter.address); + g_free(bzobj->d_network.interface); + g_free(bzobj->d_device.address); + g_free(bzobj->d_device.name); + g_free(bzobj->d_device.adapter); + g_free(bzobj); +} + +/*****************************************************************************/ + +static const char * +_bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len) +{ + char * buf0 = buf; + const char *prefix = ""; + gboolean device_is_usable; + gboolean create_panu_connection = FALSE; + gboolean network_server_is_usable; + char sbuf_cap[100]; + + if (len > 0) + buf[0] = '\0'; + + if (bzobj->d_has_adapter_iface) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str(&buf, &len, "Adapter1 {"); + if (bzobj->d_adapter.address) { + nm_utils_strbuf_append(&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address); + if (bzobj->d_adapter_powered) + nm_utils_strbuf_append_str(&buf, &len, ","); + } + if (bzobj->d_adapter_powered) + nm_utils_strbuf_append(&buf, &len, " d.powered: 1"); + nm_utils_strbuf_append_str(&buf, &len, " }"); + } + + if (bzobj->d_has_device_iface) { + const char *prefix1 = ""; + + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str(&buf, &len, "Device1 {"); + if (bzobj->d_device.address) { + nm_utils_strbuf_append(&buf, + &len, + "%s d.address: \"%s\"", + prefix1, + bzobj->d_device.address); + prefix1 = ","; + } + if (bzobj->d_device.name) { + nm_utils_strbuf_append(&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name); + prefix1 = ","; + } + if (bzobj->d_device.adapter) { + nm_utils_strbuf_append(&buf, + &len, + "%s d.adapter: \"%s\"", + prefix1, + bzobj->d_device.adapter); + prefix1 = ","; + } + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + nm_utils_strbuf_append(&buf, + &len, + "%s d.capabilities: \"%s\"", + prefix1, + nm_bluetooth_capability_to_string(bzobj->d_device_capabilities, + sbuf_cap, + sizeof(sbuf_cap))); + prefix1 = ","; + } + if (bzobj->d_device_connected) { + nm_utils_strbuf_append(&buf, &len, "%s d.connected: 1", prefix1); + prefix1 = ","; + } + if (bzobj->d_device_paired) { + nm_utils_strbuf_append(&buf, &len, "%s d.paired: 1", prefix1); + prefix1 = ","; + } + nm_utils_strbuf_append_str(&buf, &len, " }"); + } + + network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); + + if (bzobj->d_has_network_server_iface + || network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst)) + || !c_list_is_empty(&bzobj->x_network_server.lst) + || !nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, + bzobj->x_network_server.adapter_address) + || bzobj->x_network_server.device_br || bzobj->x_network_server.r_req_data) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + + nm_utils_strbuf_append(&buf, &len, "NetworkServer1 { "); + + if (!bzobj->d_has_network_server_iface) + nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, "); + + if (network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst))) + nm_utils_strbuf_append(&buf, + &len, + "usable: %d, used: %d", + !!network_server_is_usable, + !network_server_is_usable); + else if (network_server_is_usable) + nm_utils_strbuf_append(&buf, &len, "used: 1"); + else + nm_utils_strbuf_append(&buf, &len, "usable: 0"); + + if (!nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, + bzobj->x_network_server.adapter_address)) { + if (bzobj->x_network_server.adapter_address) + nm_utils_strbuf_append(&buf, + &len, + ", adapter-address: \"%s\"", + bzobj->x_network_server.adapter_address); + else + nm_utils_strbuf_append(&buf, &len, ", adapter-address: "); + } + + if (bzobj->x_network_server.device_br) + nm_utils_strbuf_append(&buf, &len, ", bridge-device: 1"); + + if (bzobj->x_network_server.r_req_data) + nm_utils_strbuf_append(&buf, &len, ", register-in-progress: 1"); + + nm_utils_strbuf_append_str(&buf, &len, " }"); + } + + device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, &create_panu_connection); + + if (bzobj->d_has_network_iface || bzobj->d_network.interface || bzobj->d_network_connected + || create_panu_connection || bzobj->x_device.panu_connection + || device_is_usable != bzobj->x_device_is_usable || bzobj->x_device.device_bt + || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE + || bzobj->x_device.connect_dun_context || bzobj->x_device.c_req_data + || bzobj->x_device_is_connected != bzobj->d_network_connected) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str(&buf, &len, "Network1 {"); + if (bzobj->d_network.interface) + nm_utils_strbuf_append(&buf, + &len, + " d.interface: \"%s\", ", + bzobj->d_network.interface); + if (bzobj->d_network_connected) + nm_utils_strbuf_append(&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected); + if (!bzobj->d_has_network_iface) + nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, "); + if (device_is_usable != bzobj->x_device_is_usable) + nm_utils_strbuf_append(&buf, + &len, + " usable: %d, used: %d", + !!device_is_usable, + !device_is_usable); + else if (device_is_usable) + nm_utils_strbuf_append(&buf, &len, " used: 1"); + else + nm_utils_strbuf_append(&buf, &len, " usable: 0"); + + if (create_panu_connection) + nm_utils_strbuf_append(&buf, &len, ", create-panu-connection: 1"); + + if (bzobj->x_device.panu_connection) + nm_utils_strbuf_append(&buf, &len, ", has-panu-connection: 1"); + + if (bzobj->x_device.device_bt) + nm_utils_strbuf_append(&buf, &len, ", has-device: 1"); + + if (bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE + || bzobj->x_device.connect_dun_context) { + nm_utils_strbuf_append( + &buf, + &len, + ", connect: %s%s", + nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, + sbuf_cap, + sizeof(sbuf_cap)), + bzobj->x_device.connect_dun_context ? ",with-dun-context" : ""); + } + + if (bzobj->x_device.c_req_data) + nm_utils_strbuf_append(&buf, &len, ", connecting: 1"); + + if (bzobj->x_device_is_connected != bzobj->d_network_connected) + nm_utils_strbuf_append(&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected); + + nm_utils_strbuf_append_str(&buf, &len, " }"); + } + + if (_bzobjs_is_dead(bzobj)) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str(&buf, &len, "dead: 1"); + } + + if (!c_list_is_empty(&bzobj->process_change_lst)) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append(&buf, &len, "change-pending-on-idle: 1"); + } + + if (_bzobjs_adapter_is_usable_for_device(bzobj) + != bzobj->was_usable_adapter_for_device_before) { + nm_utils_strbuf_append_str(&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append(&buf, &len, "change-usable-adapter-for-device: 1"); + } + + return buf0; +} + +#define _LOG_bzobj(bzobj, context) \ + G_STMT_START \ + { \ + const BzDBusObj *const _bzobj = (bzobj); \ + char _buf[500]; \ + \ + _LOGT("change %-21s %s : { %s }", \ + (context), \ + _bzobj->object_path, \ + _bzobj_to_string(_bzobj, _buf, sizeof(_buf))); \ + } \ + G_STMT_END + +static gboolean +_bzobjs_is_dead(const BzDBusObj *bzobj) +{ + return !bzobj->d_has_adapter_iface && !bzobj->d_has_device_iface && !bzobj->d_has_network_iface + && !bzobj->d_has_network_server_iface && c_list_is_empty(&bzobj->process_change_lst); +} + +static BzDBusObj * +_bzobjs_get(NMBluezManager *self, const char *object_path) +{ + return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->bzobjs, &object_path); +} + +static BzDBusObj * +_bzobjs_add(NMBluezManager *self, const char *object_path) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + BzDBusObj * bzobj; + + bzobj = _bz_dbus_obj_new(self, object_path); + if (!g_hash_table_add(priv->bzobjs, bzobj)) + nm_assert_not_reached(); + return bzobj; +} + +static void +_bzobjs_del(BzDBusObj *bzobj) +{ + nm_assert(bzobj); + nm_assert(bzobj == _bzobjs_get(bzobj->self, bzobj->object_path)); + + if (!g_hash_table_remove(NM_BLUEZ_MANAGER_GET_PRIVATE(bzobj->self)->bzobjs, bzobj)) + nm_assert_not_reached(); +} + +static void +_bzobjs_del_if_dead(BzDBusObj *bzobj) +{ + if (_bzobjs_is_dead(bzobj)) + _bzobjs_del(bzobj); +} + +static BzDBusObj * +_bzobjs_init(NMBluezManager *self, BzDBusObj **inout, const char *object_path) +{ + nm_assert(NM_IS_BLUEZ_MANAGER(self)); + nm_assert(object_path); + nm_assert(inout); + + if (!*inout) { + *inout = _bzobjs_get(self, object_path); + if (!*inout) + *inout = _bzobjs_add(self, object_path); + } + + nm_assert(nm_streq((*inout)->object_path, object_path)); + nm_assert(*inout == _bzobjs_get(self, object_path)); + return *inout; +} + +static gboolean +_bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj) +{ + return bzobj->d_has_adapter_iface && bzobj->d_adapter.address && bzobj->d_adapter_powered; +} + +static gboolean +_bzobjs_device_is_usable(const BzDBusObj *bzobj, + BzDBusObj ** out_adapter_bzobj, + gboolean * out_create_panu_connection) +{ + NMBluezManager * self; + NMBluezManagerPrivate *priv; + gboolean usable_dun = FALSE; + gboolean usable_nap = FALSE; + BzDBusObj * bzobj_adapter; + gboolean create_panu_connection = FALSE; + + if (!bzobj->d_has_device_iface + || !NM_FLAGS_ANY((NMBluetoothCapabilities) bzobj->d_device_capabilities, + _NM_BT_CAPABILITY_SUPPORTED) + || !bzobj->d_device.name || !bzobj->d_device.address || !bzobj->d_device_paired + || !bzobj->d_device.adapter) + goto out_unusable; + + self = bzobj->self; + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + if (!priv->settings_registered) + goto out_unusable; + + bzobj_adapter = _bzobjs_get(self, bzobj->d_device.adapter); + if (!bzobj_adapter || !_bzobjs_adapter_is_usable_for_device(bzobj_adapter)) + goto out_unusable; + +#if WITH_BLUEZ5_DUN + if (NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_DUN)) { + if (_conn_track_find_head(self, NM_BT_CAPABILITY_DUN, bzobj->d_device.address)) + usable_dun = TRUE; + } +#endif + + if (NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)) { + if (!bzobj->d_has_network_iface) + usable_nap = FALSE; + else if (_conn_track_find_head(self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) + usable_nap = TRUE; + else if (bzobj->x_device_panu_connection_allow_create) { + /* We didn't yet try to create a connection. Presume we are going to create + * it when the time comes... */ + usable_nap = TRUE; + create_panu_connection = TRUE; + } + } + + if (!usable_dun && !usable_nap) { + if (bzobj->x_device.device_bt + && nm_device_get_state(NM_DEVICE(bzobj->x_device.device_bt)) + > NM_DEVICE_STATE_DISCONNECTED) { + /* The device is still activated... the absence of a profile does not + * render it unusable (yet). But since there is no more profile, the + * device is probably about to disconnect. */ + } else + goto out_unusable; + } + + NM_SET_OUT(out_create_panu_connection, create_panu_connection); + NM_SET_OUT(out_adapter_bzobj, bzobj_adapter); + return TRUE; + +out_unusable: + NM_SET_OUT(out_create_panu_connection, FALSE); + NM_SET_OUT(out_adapter_bzobj, NULL); + return FALSE; +} + +static gboolean +_bzobjs_device_is_connected(const BzDBusObj *bzobj) +{ + nm_assert(_bzobjs_device_is_usable(bzobj, NULL, NULL)); + + if (!bzobj->d_has_device_iface || !bzobj->d_device_connected) + return FALSE; + + if (bzobj->d_has_network_iface && bzobj->d_network_connected) + return TRUE; + if (bzobj->x_device.connect_dun_context) { + /* As long as we have a dun-context, we consider it connected. + * + * We require NMDeviceBt to try to connect to the modem, and if that fails, + * it will disconnect. */ + return TRUE; + } + return FALSE; +} + +static gboolean +_bzobjs_network_server_is_usable(const BzDBusObj *bzobj, gboolean require_powered) +{ + return bzobj->d_has_network_server_iface && bzobj->d_has_adapter_iface + && bzobj->d_adapter.address && (!require_powered || bzobj->d_adapter_powered); +} + +/*****************************************************************************/ + +static ConnDataHead * +_conn_data_head_new(NMBluetoothCapabilities bt_type, const char *bdaddr) +{ + ConnDataHead *cdata_hd; + gsize l; + + nm_assert(NM_IN_SET(bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP)); + nm_assert(bdaddr); + + l = strlen(bdaddr) + 1; + cdata_hd = g_malloc(sizeof(ConnDataHead) + l); + *cdata_hd = (ConnDataHead){ + .bdaddr = cdata_hd->bdaddr_data, + .lst_head = C_LIST_INIT(cdata_hd->lst_head), + .bt_type = bt_type, + }; + memcpy(cdata_hd->bdaddr_data, bdaddr, l); + + nm_assert(cdata_hd->bt_type == bt_type); + + return cdata_hd; +} + +static guint +_conn_data_head_hash(gconstpointer ptr) +{ + const ConnDataHead *cdata_hd = ptr; + NMHashState h; + + nm_hash_init(&h, 520317467u); + nm_hash_update_val(&h, (NMBluetoothCapabilities) cdata_hd->bt_type); + nm_hash_update_str(&h, cdata_hd->bdaddr); + return nm_hash_complete(&h); +} + +static gboolean +_conn_data_head_equal(gconstpointer a, gconstpointer b) +{ + const ConnDataHead *cdata_hd_a = a; + const ConnDataHead *cdata_hd_b = b; + + return cdata_hd_a->bt_type == cdata_hd_b->bt_type + && nm_streq(cdata_hd_a->bdaddr, cdata_hd_b->bdaddr); +} + +static ConnDataHead * +_conn_track_find_head(NMBluezManager *self, NMBluetoothCapabilities bt_type, const char *bdaddr) +{ + ConnDataHead cdata_hd = { + .bt_type = bt_type, + .bdaddr = bdaddr, + }; + + return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->conn_data_heads, &cdata_hd); +} + +static ConnDataElem * +_conn_track_find_elem(NMBluezManager *self, NMSettingsConnection *sett_conn) +{ + G_STATIC_ASSERT(G_STRUCT_OFFSET(ConnDataElem, sett_conn) == 0); + + return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->conn_data_elems, &sett_conn); +} + +static gboolean +_conn_track_is_relevant_connection(NMConnection * connection, + NMBluetoothCapabilities *out_bt_type, + const char ** out_bdaddr) +{ + NMSettingBluetooth * s_bt; + NMBluetoothCapabilities bt_type; + const char * bdaddr; + const char * b_type; + + s_bt = nm_connection_get_setting_bluetooth(connection); + if (!s_bt) + return FALSE; + + if (!nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) + return FALSE; + + bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt); + if (!bdaddr) + return FALSE; + + b_type = nm_setting_bluetooth_get_connection_type(s_bt); + + if (nm_streq(b_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) + bt_type = NM_BT_CAPABILITY_DUN; + else if (nm_streq(b_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) + bt_type = NM_BT_CAPABILITY_NAP; + else + return FALSE; + + NM_SET_OUT(out_bt_type, bt_type); + NM_SET_OUT(out_bdaddr, bdaddr); + return TRUE; +} + +static gboolean +_conn_track_is_relevant_sett_conn(NMSettingsConnection * sett_conn, + NMBluetoothCapabilities *out_bt_type, + const char ** out_bdaddr) +{ + NMConnection *connection; + + connection = nm_settings_connection_get_connection(sett_conn); + if (!connection) + return FALSE; + + return _conn_track_is_relevant_connection(connection, out_bt_type, out_bdaddr); +} + +static gboolean +_conn_track_is_relevant_for_sett_conn(NMSettingsConnection * sett_conn, + NMBluetoothCapabilities bt_type, + const char * bdaddr) +{ + NMBluetoothCapabilities x_bt_type; + const char * x_bdaddr; + + return bdaddr && _conn_track_is_relevant_sett_conn(sett_conn, &x_bt_type, &x_bdaddr) + && x_bt_type == bt_type && nm_streq(x_bdaddr, bdaddr); +} + +static void +_conn_track_schedule_notify(NMBluezManager * self, + NMBluetoothCapabilities bt_type, + const char * bdaddr) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + GHashTableIter iter; + BzDBusObj * bzobj; + + g_hash_table_iter_init(&iter, priv->bzobjs); + while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { + gboolean device_is_usable; + + device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); + if (bzobj->x_device_is_usable != device_is_usable) + _process_change_idle_schedule(self, bzobj); + } +} + +static void +_conn_track_update(NMBluezManager * self, + NMSettingsConnection *sett_conn, + gboolean track, + gboolean * out_changed, + gboolean * out_changed_usable, + ConnDataElem ** out_conn_data_elem) +{ + NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + ConnDataHead * cdata_hd; + ConnDataElem * cdata_el; + ConnDataElem * cdata_el_remove = NULL; + NMBluetoothCapabilities bt_type; + const char * bdaddr; + gboolean changed = FALSE; + gboolean changed_usable = FALSE; + char sbuf_cap[100]; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + cdata_el = _conn_track_find_elem(self, sett_conn); + + if (track) + track = _conn_track_is_relevant_sett_conn(sett_conn, &bt_type, &bdaddr); + + if (!track) { + cdata_el_remove = g_steal_pointer(&cdata_el); + goto out_remove; + } + + if (cdata_el) { + cdata_hd = cdata_el->cdata_hd; + if (cdata_hd->bt_type != bt_type || !nm_streq(cdata_hd->bdaddr, bdaddr)) + cdata_el_remove = g_steal_pointer(&cdata_el); + } + + if (!cdata_el) { + _LOGT("connection: track for %s, %s: %s (%s)", + nm_bluetooth_capability_to_string(bt_type, sbuf_cap, sizeof(sbuf_cap)), + bdaddr, + nm_settings_connection_get_uuid(sett_conn), + nm_settings_connection_get_id(sett_conn)); + changed = TRUE; + cdata_hd = _conn_track_find_head(self, bt_type, bdaddr); + if (!cdata_hd) { + changed_usable = TRUE; + cdata_hd = _conn_data_head_new(bt_type, bdaddr); + if (!g_hash_table_add(priv->conn_data_heads, cdata_hd)) + nm_assert_not_reached(); + _conn_track_schedule_notify(self, bt_type, bdaddr); + } + cdata_el = g_slice_new(ConnDataElem); + cdata_el->sett_conn = sett_conn; + cdata_el->cdata_hd = cdata_hd; + c_list_link_tail(&cdata_hd->lst_head, &cdata_el->lst); + if (!g_hash_table_add(priv->conn_data_elems, cdata_el)) + nm_assert_not_reached(); + } + +out_remove: + if (cdata_el_remove) { + GHashTableIter iter; + BzDBusObj * bzobj; + + _LOGT("connection: untrack for %s, %s: %s (%s)", + nm_bluetooth_capability_to_string(cdata_el_remove->cdata_hd->bt_type, + sbuf_cap, + sizeof(sbuf_cap)), + cdata_el_remove->cdata_hd->bdaddr, + nm_settings_connection_get_uuid(sett_conn), + nm_settings_connection_get_id(sett_conn)); + + g_hash_table_iter_init(&iter, priv->bzobjs); + while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { + if (bzobj->x_device.panu_connection == sett_conn) + bzobj->x_device.panu_connection = NULL; + } + + changed = TRUE; + cdata_hd = cdata_el_remove->cdata_hd; + c_list_unlink_stale(&cdata_el_remove->lst); + if (!g_hash_table_remove(priv->conn_data_elems, cdata_el_remove)) + nm_assert_not_reached(); + if (c_list_is_empty(&cdata_hd->lst_head)) { + changed_usable = TRUE; + _conn_track_schedule_notify(self, cdata_hd->bt_type, cdata_hd->bdaddr); + if (!g_hash_table_remove(priv->conn_data_heads, cdata_hd)) + nm_assert_not_reached(); + } + } + + NM_SET_OUT(out_changed, changed); + NM_SET_OUT(out_changed_usable, changed_usable); + NM_SET_OUT(out_conn_data_elem, cdata_el); +} + +/*****************************************************************************/ + +static void +cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, NMBluezManager *self) +{ + _conn_track_update(self, sett_conn, TRUE, NULL, NULL, NULL); +} + +static void +cp_connection_updated(NMSettings * settings, + NMSettingsConnection *sett_conn, + guint update_reason_u, + NMBluezManager * self) +{ + _conn_track_update(self, sett_conn, TRUE, NULL, NULL, NULL); +} + +static void +cp_connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, NMBluezManager *self) +{ + _conn_track_update(self, sett_conn, FALSE, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +static NMBluezManager * +_network_server_get_bluez_manager(const NMBtVTableNetworkServer *vtable_network_server) +{ + NMBluezManager *self; + + self = (NMBluezManager *) (((char *) vtable_network_server) + - G_STRUCT_OFFSET(NMBluezManager, _priv.vtable_network_server)); + + g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(self), NULL); + + return self; +} + +static BzDBusObj * +_network_server_find_has_device(NMBluezManagerPrivate *priv, NMDevice *device) +{ + BzDBusObj *bzobj; + + c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { + if (bzobj->x_network_server.device_br == device) + return bzobj; + } + return NULL; +} + +static BzDBusObj * +_network_server_find_available(NMBluezManagerPrivate *priv, + const char * addr, + NMDevice * device_accept_busy) +{ + BzDBusObj *bzobj; + + c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { + if (bzobj->x_network_server.device_br) { + if (bzobj->x_network_server.device_br != device_accept_busy) + continue; + } + if (addr && !nm_streq(addr, bzobj->d_adapter.address)) + continue; + nm_assert(!bzobj->x_network_server.r_req_data); + return bzobj; + } + return NULL; +} + +static gboolean +_network_server_vt_is_available(const NMBtVTableNetworkServer *vtable, + const char * addr, + NMDevice * device_accept_busy) +{ + NMBluezManager * self = _network_server_get_bluez_manager(vtable); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + return !!_network_server_find_available(priv, addr, device_accept_busy); +} + +static void +_network_server_register_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + BzDBusObj * bzobj; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); + if (!ret && nm_utils_error_is_cancelled(error)) + return; + + bzobj = user_data; + + if (!ret) { + _LOGT("NAP: [%s]: registering failed: %s", bzobj->object_path, error->message); + } else + _LOGT("NAP: [%s]: registration successful", bzobj->object_path); + + g_clear_object(&bzobj->x_network_server.r_req_data->int_cancellable); + _network_server_register_req_data_complete(g_steal_pointer(&bzobj->x_network_server.r_req_data), + error); +} + +static void +_network_server_register_cancelled_cb(GCancellable *cancellable, BzDBusObj *bzobj) +{ + _network_server_unregister_bridge(bzobj->self, bzobj, "registration cancelled"); +} + +static gboolean +_network_server_vt_register_bridge(const NMBtVTableNetworkServer *vtable, + const char * addr, + NMDevice * device, + GCancellable * cancellable, + NMBtVTableRegisterCallback callback, + gpointer callback_user_data, + GError ** error) +{ + NMBluezManager * self = _network_server_get_bluez_manager(vtable); + NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + NetworkServerRegisterReqData *r_req_data; + BzDBusObj * bzobj; + const char * ifname; + + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + g_return_val_if_fail(G_IS_CANCELLABLE(cancellable), FALSE); + + nm_assert(!g_cancellable_is_cancelled(cancellable)); + nm_assert(!_network_server_find_has_device(priv, device)); + + ifname = nm_device_get_iface(device); + g_return_val_if_fail(ifname, FALSE); + + g_return_val_if_fail(ifname, FALSE); + + bzobj = _network_server_find_available(priv, addr, NULL); + if (!bzobj) { + /* The device checked that a network server is available, before + * starting the activation, but for some reason it no longer is. + * Indicate that the activation should not proceed. */ + if (addr) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "adapter %s is not available for %s", + addr, + ifname); + } else { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "no adapter available for %s", + ifname); + } + return FALSE; + } + + _LOGD("NAP: [%s]: registering \"%s\" on adapter %s", + bzobj->object_path, + ifname, + bzobj->d_adapter.address); + + r_req_data = g_slice_new(NetworkServerRegisterReqData); + *r_req_data = (NetworkServerRegisterReqData){ + .int_cancellable = g_cancellable_new(), + .ext_cancellable = g_object_ref(cancellable), + .callback = callback, + .callback_user_data = callback_user_data, + .ext_cancelled_id = g_signal_connect(cancellable, + "cancelled", + G_CALLBACK(_network_server_register_cancelled_cb), + bzobj), + }; + + bzobj->x_network_server.device_br = g_object_ref(device); + bzobj->x_network_server.r_req_data = r_req_data; + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_SERVER_INTERFACE, + "Register", + g_variant_new("(ss)", BLUETOOTH_CONNECT_NAP, ifname), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + bzobj->x_network_server.r_req_data->int_cancellable, + _network_server_register_cb, + bzobj); + return TRUE; +} + +static void +_network_server_unregister_bridge_complete_on_idle_cb(gpointer user_data, GCancellable *cancellable) +{ + gs_free_error GError * error = NULL; + gs_free char * reason = NULL; + NetworkServerRegisterReqData *r_req_data; + + nm_utils_user_data_unpack(user_data, &r_req_data, &reason); + + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "registration was aborted due to %s", + reason); + _network_server_register_req_data_complete(r_req_data, error); +} + +static void +_network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const char *reason) +{ + NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + _nm_unused gs_unref_object NMDevice *device = NULL; + NetworkServerRegisterReqData * r_req_data; + + nm_assert(NM_IS_DEVICE(bzobj->x_network_server.device_br)); + + _LOGD("NAP: [%s]: unregistering \"%s\" (%s)", + bzobj->object_path, + nm_device_get_iface(bzobj->x_network_server.device_br), + reason); + + device = g_steal_pointer(&bzobj->x_network_server.device_br); + + r_req_data = g_steal_pointer(&bzobj->x_network_server.r_req_data); + + if (priv->name_owner) { + gs_unref_object GCancellable *cancellable = NULL; + + cancellable = g_cancellable_new(); + + nm_shutdown_wait_obj_register_cancellable_full( + cancellable, + g_strdup_printf("bt-unregister-nap[%s]", bzobj->object_path), + TRUE); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_SERVER_INTERFACE, + "Unregister", + g_variant_new("(s)", BLUETOOTH_CONNECT_NAP), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + cancellable, + _dbus_call_complete_cb_nop, + NULL); + } + + if (r_req_data) { + nm_clear_g_cancellable(&r_req_data->int_cancellable); + nm_utils_invoke_on_idle(r_req_data->ext_cancellable, + _network_server_unregister_bridge_complete_on_idle_cb, + nm_utils_user_data_pack(r_req_data, g_strdup(reason))); + } + + _nm_device_bridge_notify_unregister_bt_nap(device, reason); +} + +static gboolean +_network_server_vt_unregister_bridge(const NMBtVTableNetworkServer *vtable, NMDevice *device) +{ + NMBluezManager * self = _network_server_get_bluez_manager(vtable); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + BzDBusObj * bzobj; + + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + bzobj = _network_server_find_has_device(priv, device); + if (bzobj) + _network_server_unregister_bridge(self, bzobj, "disconnecting"); + + return TRUE; +} + +static void +_network_server_process_change(BzDBusObj *bzobj, gboolean *out_emit_device_availability_changed) +{ + NMBluezManager * self = bzobj->self; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + gboolean network_server_is_usable; + gboolean emit_device_availability_changed = FALSE; + + network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); + + if (!network_server_is_usable) { + if (!c_list_is_empty(&bzobj->x_network_server.lst)) { + emit_device_availability_changed = TRUE; + c_list_unlink(&bzobj->x_network_server.lst); + } + + nm_clear_g_free(&bzobj->x_network_server.adapter_address); + + if (bzobj->x_network_server.device_br) { + _network_server_unregister_bridge(self, + bzobj, + _bzobjs_network_server_is_usable(bzobj, FALSE) + ? "adapter disabled" + : "adapter disappeared"); + } + + } else { + if (!nm_streq0(bzobj->x_network_server.adapter_address, bzobj->d_adapter.address)) { + emit_device_availability_changed = TRUE; + g_free(bzobj->x_network_server.adapter_address); + bzobj->x_network_server.adapter_address = g_strdup(bzobj->d_adapter.address); + } + + if (c_list_is_empty(&bzobj->x_network_server.lst)) { + emit_device_availability_changed = TRUE; + c_list_link_tail(&priv->network_server_lst_head, &bzobj->x_network_server.lst); + } + } + + if (emit_device_availability_changed) + NM_SET_OUT(out_emit_device_availability_changed, TRUE); +} + +/*****************************************************************************/ + +static void +_conn_create_panu_connection(NMBluezManager *self, BzDBusObj *bzobj) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + gs_unref_object NMConnection *connection = NULL; + NMSettingsConnection * added; + NMSetting * setting; + gs_free char * id = NULL; + char uuid[37]; + gs_free_error GError *error = NULL; + + nm_utils_uuid_generate_buf(uuid); + id = g_strdup_printf(_("%s Network"), bzobj->d_device.name); + + connection = nm_simple_connection_new(); + + setting = nm_setting_connection_new(); + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BLUETOOTH_SETTING_NAME, + NULL); + nm_connection_add_setting(connection, setting); + + setting = nm_setting_bluetooth_new(); + g_object_set(setting, + NM_SETTING_BLUETOOTH_BDADDR, + bzobj->d_device.address, + NM_SETTING_BLUETOOTH_TYPE, + NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + nm_connection_add_setting(connection, setting); + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) { + _LOGE("connection: couldn't generate a connection for NAP device: %s", error->message); + g_return_if_reached(); + } + + nm_assert(_conn_track_is_relevant_connection(connection, NULL, NULL)); + + _LOGT("connection: create in-memory PANU connection %s (%s) for device \"%s\" (%s)", + uuid, + id, + bzobj->d_device.name, + bzobj->d_device.address); + + nm_settings_add_connection(priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, + &added, + &error); + if (!added) { + _LOGW("connection: couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s", + id, + uuid, + error->message); + return; + } + + if (!_conn_track_is_relevant_for_sett_conn(added, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) + || !_conn_track_find_elem(self, added) || bzobj->x_device.panu_connection) { + _LOGE("connection: something went wrong creating PANU connection %s (%s) for device '%s'", + uuid, + id, + bzobj->d_device.address); + g_return_if_reached(); + } + + bzobj->x_device.panu_connection = added; +} + +/*****************************************************************************/ + +static void +_device_state_changed_cb(NMDevice *device, + guint new_state_u, + guint old_state_u, + guint reason_u, + gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + if (!_bzobjs_device_is_usable(bzobj, NULL, NULL)) { + /* the device got unusable? Need to revisit it... */ + _process_change_idle_schedule(bzobj->self, bzobj); + } +} + +static void +_device_process_change(BzDBusObj *bzobj) +{ + NMBluezManager *self = bzobj->self; + gs_unref_object NMDeviceBt *device_added = NULL; + gs_unref_object NMDeviceBt *device_deleted = NULL; + gboolean device_is_usable; + gboolean create_panu_connection = FALSE; + + device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, &create_panu_connection); + + if (create_panu_connection) { + bzobj->x_device_panu_connection_allow_create = FALSE; + _conn_create_panu_connection(self, bzobj); + device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); + } else { + if (device_is_usable && bzobj->x_device_panu_connection_allow_create + && NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP) + && _conn_track_find_head(self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) { + /* We have a useable device and also a panu-connection. We block future attemps + * to generate a connection. */ + bzobj->x_device_panu_connection_allow_create = FALSE; + } + if (bzobj->x_device.panu_connection) { + if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(bzobj->x_device.panu_connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + /* the connection that we generated earlier still exists, but it's not longer the same + * as it was when we created it. Forget about it, so that we don't delete the profile later... */ + bzobj->x_device.panu_connection = NULL; + } else { + if (!device_is_usable + || !_conn_track_is_relevant_for_sett_conn(bzobj->x_device.panu_connection, + NM_BT_CAPABILITY_NAP, + bzobj->d_device.address)) { + _LOGT("connection: delete in-memory PANU connection %s (%s) as device %s", + nm_settings_connection_get_uuid(bzobj->x_device.panu_connection), + nm_settings_connection_get_id(bzobj->x_device.panu_connection), + !device_is_usable ? "is now unusable" : "no longer matches"); + bzobj->x_device_panu_connection_allow_create = TRUE; + nm_settings_connection_delete(g_steal_pointer(&bzobj->x_device.panu_connection), + FALSE); + } + } + } + } + + bzobj->x_device_is_connected = device_is_usable && _bzobjs_device_is_connected(bzobj); + + bzobj->x_device_is_usable = device_is_usable; + + if (bzobj->x_device.device_bt) { + const char *device_to_delete_msg; + + if (!device_is_usable) + device_to_delete_msg = "device became unusable"; + else if (!_nm_device_bt_for_same_device(bzobj->x_device.device_bt, + bzobj->object_path, + bzobj->d_device.address, + NULL, + bzobj->d_device_capabilities)) + device_to_delete_msg = "device is no longer compatible"; + else + device_to_delete_msg = NULL; + + if (device_to_delete_msg) { + nm_clear_g_signal_handler(bzobj->x_device.device_bt, + &bzobj->x_device.device_bt_signal_id); + + device_deleted = g_steal_pointer(&bzobj->x_device.device_bt); + + _LOGD("[%s]: drop device because %s", bzobj->object_path, device_to_delete_msg); + + _connect_disconnect(self, bzobj, device_to_delete_msg); + } + } + + if (device_is_usable) { + if (!bzobj->x_device.device_bt) { + bzobj->x_device.device_bt = nm_device_bt_new(self, + bzobj->object_path, + bzobj->d_device.address, + bzobj->d_device.name, + bzobj->d_device_capabilities); + device_added = g_object_ref(bzobj->x_device.device_bt); + bzobj->x_device.device_bt_signal_id = + g_signal_connect(device_added, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(_device_state_changed_cb), + bzobj); + } else + _nm_device_bt_notify_set_name(bzobj->x_device.device_bt, bzobj->d_device.name); + + _nm_device_bt_notify_set_connected(bzobj->x_device.device_bt, bzobj->x_device_is_connected); + } + + if (bzobj->x_device.c_req_data && !bzobj->x_device.c_req_data->int_cancellable + && bzobj->x_device_is_connected) { + gs_free char *device_name = g_steal_pointer(&bzobj->x_device.c_req_data->device_name); + + _device_connect_req_data_complete(g_steal_pointer(&bzobj->x_device.c_req_data), + self, + device_name, + NULL); + } + + if (device_added) + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device_added); + + if (device_deleted) + _nm_device_bt_notify_removed(device_deleted); +} + +/*****************************************************************************/ + +static void +_process_change_idle_all(NMBluezManager *self, gboolean *out_emit_device_availability_changed) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + BzDBusObj * bzobj; + + while ( + (bzobj = + c_list_first_entry(&priv->process_change_lst_head, BzDBusObj, process_change_lst))) { + c_list_unlink(&bzobj->process_change_lst); + + _LOG_bzobj(bzobj, "before-processing"); + + _device_process_change(bzobj); + + _network_server_process_change(bzobj, out_emit_device_availability_changed); + + _LOG_bzobj(bzobj, "after-processing"); + + _bzobjs_del_if_dead(bzobj); + } + + nm_clear_g_source(&priv->process_change_idle_id); +} + +static gboolean +_process_change_idle_cb(gpointer user_data) +{ + NMBluezManager * self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + gboolean emit_device_availability_changed = FALSE; + + _process_change_idle_all(self, &emit_device_availability_changed); + + if (emit_device_availability_changed) + nm_manager_notify_device_availability_maybe_changed(priv->manager); + + return G_SOURCE_CONTINUE; +} + +static void +_process_change_idle_schedule(NMBluezManager *self, BzDBusObj *bzobj) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj->process_change_lst); + if (priv->process_change_idle_id == 0) + priv->process_change_idle_id = + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE + 1, _process_change_idle_cb, self, NULL); +} + +static void +_dbus_process_changes(NMBluezManager *self, BzDBusObj *bzobj, const char *log_reason) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + gboolean network_server_is_usable; + gboolean adapter_is_usable_for_device; + gboolean device_is_usable; + gboolean changes = FALSE; + gboolean recheck_devices_for_adapter = FALSE; + + nm_assert(bzobj); + + _LOG_bzobj(bzobj, log_reason); + + device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); + + if (bzobj->x_device_is_usable != device_is_usable) + changes = TRUE; + else if (bzobj->x_device.device_bt) { + if (!device_is_usable) + changes = TRUE; + else { + if (bzobj->x_device_is_connected != _bzobjs_device_is_connected(bzobj) + || !_nm_device_bt_for_same_device(bzobj->x_device.device_bt, + bzobj->object_path, + bzobj->d_device.address, + bzobj->d_device.name, + bzobj->d_device_capabilities)) + changes = TRUE; + } + } + + adapter_is_usable_for_device = _bzobjs_adapter_is_usable_for_device(bzobj); + if (adapter_is_usable_for_device != bzobj->was_usable_adapter_for_device_before) { + /* this function does not modify bzobj in any other cases except here. + * Usually changes are processed delayed, in the idle handler. + * + * But the bzobj->was_usable_adapter_for_device_before only exists to know whether + * we need to re-check device availability. It is correct to set the flag + * here, right before we checked. */ + bzobj->was_usable_adapter_for_device_before = adapter_is_usable_for_device; + recheck_devices_for_adapter = TRUE; + changes = TRUE; + } + + if (!changes) { + network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); + + if (network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst))) + changes = TRUE; + else if (bzobj->x_network_server.device_br && !network_server_is_usable) + changes = TRUE; + else if (!nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, + bzobj->x_network_server.adapter_address)) + changes = TRUE; + } + + if (changes) + _process_change_idle_schedule(self, bzobj); + + if (recheck_devices_for_adapter) { + GHashTableIter iter; + BzDBusObj * bzobj2; + + /* we got a change to the availability of an adapter. We might need to recheck + * all devices that use this adapter... */ + g_hash_table_iter_init(&iter, priv->bzobjs); + while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj2, NULL)) { + if (bzobj2 == bzobj) + continue; + if (!nm_streq0(bzobj2->d_device.adapter, bzobj->object_path)) + continue; + if (c_list_is_empty(&bzobj2->process_change_lst)) + _dbus_process_changes(self, bzobj2, "adapter-changed"); + else + nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj2->process_change_lst); + } + } + + _bzobjs_del_if_dead(bzobj); +} + +/*****************************************************************************/ + +#define ALL_RELEVANT_INTERFACE_NAMES \ + NM_MAKE_STRV(NM_BLUEZ5_ADAPTER_INTERFACE, \ + NM_BLUEZ5_DEVICE_INTERFACE, \ + NM_BLUEZ5_NETWORK_INTERFACE, \ + NM_BLUEZ5_NETWORK_SERVER_INTERFACE) + +static gboolean +_dbus_handle_properties_changed(NMBluezManager * self, + const char * object_path, + const char * interface_name, + GVariant * changed_properties, + const char *const *invalidated_properties, + BzDBusObj ** inout_bzobj) +{ + BzDBusObj * bzobj = NULL; + gboolean changed = FALSE; + const char * property_name; + GVariant * property_value; + GVariantIter iter_prop; + gsize i; + + if (!invalidated_properties) + invalidated_properties = NM_PTRARRAY_EMPTY(const char *); + + nm_assert(g_variant_is_of_type(changed_properties, G_VARIANT_TYPE("a{sv}"))); + + if (inout_bzobj) { + bzobj = *inout_bzobj; + nm_assert(!bzobj || nm_streq(object_path, bzobj->object_path)); + } + + if (changed_properties) + g_variant_iter_init(&iter_prop, changed_properties); + + if (nm_streq(interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { + _bzobjs_init(self, &bzobj, object_path); + if (!bzobj->d_has_adapter_iface) { + changed = TRUE; + bzobj->d_has_adapter_iface = TRUE; + } + + while (changed_properties + && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq(property_name, "Address")) { + gs_free char *s = + g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) + ? nm_utils_hwaddr_canonical(g_variant_get_string(property_value, NULL), + ETH_ALEN) + : NULL; + + if (!nm_streq0(bzobj->d_adapter.address, s)) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_adapter.address); + bzobj->d_adapter.address = g_steal_pointer(&s); + } + continue; + } + if (nm_streq(property_name, "Powered")) { + bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(property_value); + + if (bzobj->d_adapter_powered != v) { + changed = TRUE; + bzobj->d_adapter_powered = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq(property_name, "Address")) { + if (bzobj->d_adapter.address) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_adapter.address); + } + continue; + } + if (nm_streq(property_name, "Powered")) { + if (bzobj->d_adapter_powered) { + changed = TRUE; + bzobj->d_adapter_powered = FALSE; + } + continue; + } + } + + } else if (nm_streq(interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { + _bzobjs_init(self, &bzobj, object_path); + if (!bzobj->d_has_device_iface) { + changed = TRUE; + bzobj->d_has_device_iface = TRUE; + } + + while (changed_properties + && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq(property_name, "Address")) { + gs_free char *s = + g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) + ? nm_utils_hwaddr_canonical(g_variant_get_string(property_value, NULL), + ETH_ALEN) + : NULL; + + if (!nm_streq0(bzobj->d_device.address, s)) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.address); + bzobj->d_device.address = g_steal_pointer(&s); + } + continue; + } + if (nm_streq(property_name, "Name")) { + const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) + ? g_variant_get_string(property_value, NULL) + : NULL; + + if (!nm_streq0(bzobj->d_device.name, s)) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.name); + bzobj->d_device.name = g_strdup(s); + } + continue; + } + if (nm_streq(property_name, "Adapter")) { + const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_OBJECT_PATH) + ? g_variant_get_string(property_value, NULL) + : NULL; + + if (!nm_streq0(bzobj->d_device.adapter, s)) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.adapter); + bzobj->d_device.adapter = g_strdup(s); + } + continue; + } + if (nm_streq(property_name, "UUIDs")) { + NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; + + if (g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING_ARRAY)) { + gs_free const char **s = g_variant_get_strv(property_value, NULL); + + capabilities = convert_uuids_to_capabilities(s); + } + if (bzobj->d_device_capabilities != capabilities) { + changed = TRUE; + bzobj->d_device_capabilities = capabilities; + nm_assert(bzobj->d_device_capabilities == capabilities); + } + continue; + } + if (nm_streq(property_name, "Connected")) { + bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(property_value); + + if (bzobj->d_device_connected != v) { + changed = TRUE; + bzobj->d_device_connected = v; + } + continue; + } + if (nm_streq(property_name, "Paired")) { + bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(property_value); + + if (bzobj->d_device_paired != v) { + changed = TRUE; + bzobj->d_device_paired = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq(property_name, "Address")) { + if (bzobj->d_device.address) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.address); + } + continue; + } + if (nm_streq(property_name, "Name")) { + if (bzobj->d_device.name) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.name); + } + continue; + } + if (nm_streq(property_name, "Adapter")) { + if (bzobj->d_device.adapter) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.adapter); + } + continue; + } + if (nm_streq(property_name, "UUIDs")) { + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + changed = TRUE; + bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; + } + continue; + } + if (nm_streq(property_name, "Connected")) { + if (bzobj->d_device_connected) { + changed = TRUE; + bzobj->d_device_connected = FALSE; + } + continue; + } + if (nm_streq(property_name, "Paired")) { + if (bzobj->d_device_paired) { + changed = TRUE; + bzobj->d_device_paired = FALSE; + } + continue; + } + } + + } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { + _bzobjs_init(self, &bzobj, object_path); + if (!bzobj->d_has_network_iface) { + changed = TRUE; + bzobj->d_has_network_iface = TRUE; + } + + while (changed_properties + && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq(property_name, "Interface")) { + const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) + ? g_variant_get_string(property_value, NULL) + : NULL; + + if (!nm_streq0(bzobj->d_network.interface, s)) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_network.interface); + bzobj->d_network.interface = g_strdup(s); + } + continue; + } + if (nm_streq(property_name, "Connected")) { + bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(property_value); + + if (bzobj->d_network_connected != v) { + changed = TRUE; + bzobj->d_network_connected = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq(property_name, "Interface")) { + if (bzobj->d_network.interface) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_network.interface); + } + continue; + } + if (nm_streq(property_name, "Connected")) { + if (bzobj->d_network_connected) { + changed = TRUE; + bzobj->d_network_connected = FALSE; + } + continue; + } + } + + } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { + _bzobjs_init(self, &bzobj, object_path); + if (!bzobj->d_has_network_server_iface) { + changed = TRUE; + bzobj->d_has_network_server_iface = TRUE; + } + } + + nm_assert(!changed || bzobj); + + if (inout_bzobj) + *inout_bzobj = bzobj; + + return changed; +} + +static void +_dbus_handle_interface_added(NMBluezManager *self, + const char * object_path, + GVariant * ifaces, + gboolean initial_get_managed_objects) +{ + BzDBusObj * bzobj = NULL; + gboolean changed = FALSE; + const char * interface_name; + GVariant * changed_properties; + GVariantIter iter_ifaces; + + nm_assert(g_variant_is_of_type(ifaces, G_VARIANT_TYPE("a{sa{sv}}"))); + + g_variant_iter_init(&iter_ifaces, ifaces); + while (g_variant_iter_next(&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) { + _nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties; + + if (_dbus_handle_properties_changed(self, + object_path, + interface_name, + changed_properties, + NULL, + &bzobj)) + changed = TRUE; + } + + if (changed) { + _dbus_process_changes(self, + bzobj, + initial_get_managed_objects ? "dbus-init" : "dbus-iface-added"); + } +} + +static gboolean +_dbus_handle_interface_removed(NMBluezManager * self, + const char * object_path, + BzDBusObj ** inout_bzobj, + const char *const *removed_interfaces) +{ + gboolean changed = FALSE; + BzDBusObj *bzobj; + gsize i; + + if (inout_bzobj && *inout_bzobj) { + bzobj = *inout_bzobj; + nm_assert(bzobj == _bzobjs_get(self, object_path)); + } else { + bzobj = _bzobjs_get(self, object_path); + if (!bzobj) + return FALSE; + NM_SET_OUT(inout_bzobj, bzobj); + } + + for (i = 0; removed_interfaces[i]; i++) { + const char *interface_name = removed_interfaces[i]; + + if (nm_streq(interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { + if (bzobj->d_has_adapter_iface) { + changed = TRUE; + bzobj->d_has_adapter_iface = FALSE; + } + if (bzobj->d_adapter.address) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_adapter.address); + } + if (bzobj->d_adapter_powered) { + changed = TRUE; + bzobj->d_adapter_powered = FALSE; + } + } else if (nm_streq(interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { + if (bzobj->d_has_device_iface) { + changed = TRUE; + bzobj->d_has_device_iface = FALSE; + } + if (bzobj->d_device.address) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.address); + } + if (bzobj->d_device.name) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.name); + } + if (bzobj->d_device.adapter) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_device.adapter); + } + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + changed = TRUE; + bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; + } + if (bzobj->d_device_connected) { + changed = TRUE; + bzobj->d_device_connected = FALSE; + } + if (bzobj->d_device_paired) { + changed = TRUE; + bzobj->d_device_paired = FALSE; + } + } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { + if (bzobj->d_has_network_iface) { + changed = TRUE; + bzobj->d_has_network_iface = FALSE; + } + if (bzobj->d_network.interface) { + changed = TRUE; + nm_clear_g_free(&bzobj->d_network.interface); + } + if (bzobj->d_network_connected) { + changed = TRUE; + bzobj->d_network_connected = FALSE; + } + } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { + if (bzobj->d_has_network_server_iface) { + changed = TRUE; + bzobj->d_has_network_server_iface = FALSE; + } + } + } + + return changed; +} + +static void +_dbus_managed_objects_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * arg_object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMBluezManager * self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + BzDBusObj * bzobj = NULL; + gboolean changed; + + nm_assert(nm_streq0(interface_name, DBUS_INTERFACE_OBJECT_MANAGER)); + + if (priv->get_managed_objects_cancellable) { + /* we still wait for the initial GetManagedObjects(). Ignore the event. */ + return; + } + + if (nm_streq(signal_name, "InterfacesAdded")) { + gs_unref_variant GVariant *interfaces_and_properties = NULL; + const char * object_path; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sa{sv}})"))) + return; + + g_variant_get(parameters, "(&o@a{sa{sv}})", &object_path, &interfaces_and_properties); + + _dbus_handle_interface_added(self, object_path, interfaces_and_properties, FALSE); + return; + } + + if (nm_streq(signal_name, "InterfacesRemoved")) { + gs_free const char **interfaces = NULL; + const char * object_path; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oas)"))) + return; + + g_variant_get(parameters, "(&o^a&s)", &object_path, &interfaces); + + changed = _dbus_handle_interface_removed(self, object_path, &bzobj, interfaces); + if (changed) + _dbus_process_changes(self, bzobj, "dbus-iface-removed"); + return; + } +} + +static void +_dbus_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMBluezManager * self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + const char * interface_name; + gs_unref_variant GVariant *changed_properties = NULL; + gs_free const char ** invalidated_properties = NULL; + BzDBusObj * bzobj = NULL; + + if (priv->get_managed_objects_cancellable) { + /* we still wait for the initial GetManagedObjects(). Ignore the event. */ + return; + } + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + g_variant_get(parameters, + "(&s@a{sv}^a&s)", + &interface_name, + &changed_properties, + &invalidated_properties); + + if (_dbus_handle_properties_changed(self, + object_path, + interface_name, + changed_properties, + invalidated_properties, + &bzobj)) + _dbus_process_changes(self, bzobj, "dbus-property-changed"); +} + +static void +_dbus_get_managed_objects_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMBluezManager * self; + NMBluezManagerPrivate *priv; + GVariantIter iter; + const char * object_path; + GVariant * ifaces; + + if (!result && nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->get_managed_objects_cancellable); + + if (!result) { + _LOGT("initial GetManagedObjects() call failed: %s", error->message); + _cleanup_for_name_owner(self); + return; + } + + _LOGT("initial GetManagedObjects call succeeded"); + + g_variant_iter_init(&iter, result); + while (g_variant_iter_next(&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces)) { + _nm_unused gs_unref_variant GVariant *ifaces_free = ifaces; + + _dbus_handle_interface_added(self, object_path, ifaces, TRUE); + } +} + +/*****************************************************************************/ + +static void +_cleanup_for_name_owner(NMBluezManager *self) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + gboolean emit_device_availability_changed = FALSE; + GHashTableIter iter; + BzDBusObj * bzobj; + gboolean first = TRUE; + + nm_clear_g_cancellable(&priv->get_managed_objects_cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->managed_objects_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id); + + nm_clear_g_free(&priv->name_owner); + + g_hash_table_iter_init(&iter, priv->bzobjs); + while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { + if (first) { + first = FALSE; + _LOGT("drop all objects form D-Bus cache..."); + } + _dbus_handle_interface_removed(self, + bzobj->object_path, + &bzobj, + ALL_RELEVANT_INTERFACE_NAMES); + nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj->process_change_lst); + } + _process_change_idle_all(self, &emit_device_availability_changed); + nm_assert(g_hash_table_size(priv->bzobjs) == 0); + + if (emit_device_availability_changed) + nm_manager_notify_device_availability_maybe_changed(priv->manager); +} + +static void +name_owner_changed(NMBluezManager *self, const char *owner) +{ + _nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref(self); + NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + owner = nm_str_not_empty(owner); + + if (!owner) + _LOGT("D-Bus name for bluez has no owner"); + else + _LOGT("D-Bus name for bluez has owner %s", owner); + + nm_clear_g_cancellable(&priv->name_owner_get_cancellable); + + if (nm_streq0(priv->name_owner, owner)) + return; + + _cleanup_for_name_owner(self); + + if (!owner) + return; + + priv->name_owner = g_strdup(owner); + + priv->get_managed_objects_cancellable = g_cancellable_new(); + + priv->managed_objects_changed_id = + nm_dbus_connection_signal_subscribe_object_manager(priv->dbus_connection, + priv->name_owner, + NM_BLUEZ_MANAGER_PATH, + NULL, + _dbus_managed_objects_changed_cb, + self, + NULL); + + priv->properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner, + NULL, + NULL, + _dbus_properties_changed_cb, + self, + NULL); + + nm_dbus_connection_call_get_managed_objects(priv->dbus_connection, + priv->name_owner, + NM_BLUEZ_MANAGER_PATH, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 20000, + priv->get_managed_objects_cancellable, + _dbus_get_managed_objects_cb, + self); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMBluezManager *self = user_data; + const char * new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(self, new_owner); +} + +static void +name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (name_owner || !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + name_owner_changed(user_data, name_owner); +} + +/*****************************************************************************/ + +static void +_cleanup_all(NMBluezManager *self) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + priv->settings_registered = FALSE; + + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_added, self); + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_updated, self); + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_removed, self); + + g_hash_table_remove_all(priv->conn_data_elems); + g_hash_table_remove_all(priv->conn_data_heads); + + _cleanup_for_name_owner(self); + + nm_clear_g_cancellable(&priv->name_owner_get_cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); +} + +static void +start(NMDeviceFactory *factory) +{ + NMBluezManager * self; + NMBluezManagerPrivate * priv; + NMSettingsConnection *const *sett_conns; + guint n_sett_conns; + guint i; + + g_return_if_fail(NM_IS_BLUEZ_MANAGER(factory)); + + self = NM_BLUEZ_MANAGER(factory); + priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + _cleanup_all(self); + + if (!priv->dbus_connection) { + _LOGI("no D-Bus connection available"); + return; + } + + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + G_CALLBACK(cp_connection_added), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK(cp_connection_updated), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK(cp_connection_removed), + self); + + priv->settings_registered = TRUE; + + sett_conns = nm_settings_get_connections(priv->settings, &n_sett_conns); + for (i = 0; i < n_sett_conns; i++) + _conn_track_update(self, sett_conns[i], TRUE, NULL, NULL, NULL); + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + NM_BLUEZ_SERVICE, + name_owner_changed_cb, + self, + NULL); + + priv->name_owner_get_cancellable = g_cancellable_new(); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + NM_BLUEZ_SERVICE, + 10000, + priv->name_owner_get_cancellable, + name_owner_get_cb, + self); +} + +/*****************************************************************************/ + +static void +_connect_returned(NMBluezManager * self, + BzDBusObj * bzobj, + NMBluetoothCapabilities bt_type, + const char * device_name, + NMBluez5DunContext * dun_context, + GError * error) +{ + char sbuf_cap[100]; + + if (error) { + nm_assert(!device_name); + nm_assert(!dun_context); + + _LOGI("%s [%s]: connect failed: %s", + nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, + sbuf_cap, + sizeof(sbuf_cap)), + bzobj->object_path, + error->message); + + _device_connect_req_data_complete(g_steal_pointer(&bzobj->x_device.c_req_data), + self, + NULL, + error); + _connect_disconnect(self, bzobj, "cleanup after connect failure"); + return; + } + + nm_assert(bzobj->x_device_connect_bt_type == bt_type); + nm_assert(device_name); + nm_assert((bt_type == NM_BT_CAPABILITY_DUN) == (!!dun_context)); + nm_assert(bzobj->x_device.c_req_data); + + g_clear_object(&bzobj->x_device.c_req_data->int_cancellable); + + bzobj->x_device.connect_dun_context = dun_context; + + _LOGD("%s [%s]: connect successful to device %s", + nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, + sbuf_cap, + sizeof(sbuf_cap)), + bzobj->object_path, + device_name); + + /* we already have another over-all timer running. But after we connected the device, + * we still need to wait for bluez to acknowledge the connected state (via D-Bus, for NAP). + * For DUN profiles we likely are already fully connected by now. + * + * Anyway, schedule another timeout that is possibly shorter than the overall, original + * timeout. Now this should go down fast. */ + bzobj->x_device.c_req_data->timeout_wait_connect_id = + g_timeout_add(5000, _connect_timeout_wait_connected_cb, bzobj), + bzobj->x_device.c_req_data->device_name = g_strdup(device_name); + + if (_bzobjs_device_is_usable(bzobj, NULL, NULL) && _bzobjs_device_is_connected(bzobj)) { + /* We are now connected. Schedule the task that completes the state. */ + _process_change_idle_schedule(self, bzobj); + } +} + +#if WITH_BLUEZ5_DUN +static void +_connect_dun_notify_tty_hangup_cb(NMBluez5DunContext *context, gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + _connect_disconnect(bzobj->self, bzobj, "DUN connection hung up"); +} + +static void +_connect_dun_step2_cb(NMBluez5DunContext *context, + const char * rfcomm_dev, + GError * error, + gpointer user_data) +{ + BzDBusObj *bzobj; + + if (nm_utils_error_is_cancelled(error)) + return; + + bzobj = user_data; + + if (rfcomm_dev) { + /* We want to early notify about the rfcomm path. That is because we might still delay + * to signal full activation longer (asynchronously). But the earliest time the callback + * is invoked with the rfcomm path, we just created the device synchronously. + * + * By already notifying the caller about the path early, it avoids a race where ModemManager + * would find the modem before the bluetooth code considers the profile fully activated. */ + + nm_assert(!error); + nm_assert(bzobj->x_device.c_req_data); + + if (!g_cancellable_is_cancelled(bzobj->x_device.c_req_data->ext_cancellable)) + bzobj->x_device.c_req_data->callback(bzobj->self, + FALSE, + rfcomm_dev, + NULL, + bzobj->x_device.c_req_data->callback_user_data); + + if (!context) { + /* No context set. This means, we just got notified about the rfcomm path and need to wait + * longer, for the next callback. */ + return; + } + } + + _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, rfcomm_dev, context, error); +} + +static void +_connect_dun_step1_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + BzDBusObj * bzobj_adapter; + BzDBusObj * bzobj; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); + + if (!ret && nm_utils_error_is_cancelled(error)) + return; + + bzobj = user_data; + + if (error) { + _LOGT("DUN: [%s]: bluetooth device connect failed: %s", bzobj->object_path, error->message); + /* we actually ignore this error. Let's try, maybe we still can connect via DUN. */ + g_clear_error(&error); + } else + _LOGT("DUN: [%s]: bluetooth device connected successfully", bzobj->object_path); + + if (!_bzobjs_device_is_usable(bzobj, &bzobj_adapter, NULL)) { + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "device %s is not usable for DUN after connect", + bzobj->object_path); + _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); + return; + } + + if (!nm_bluez5_dun_connect(bzobj_adapter->d_adapter.address, + bzobj->d_device.address, + bzobj->x_device.c_req_data->int_cancellable, + _connect_dun_step2_cb, + bzobj, + _connect_dun_notify_tty_hangup_cb, + bzobj, + &error)) { + _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); + return; + } +} +#endif + +static void +_connect_nap_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + const char * network_iface_name = NULL; + gs_free_error GError *error = NULL; + BzDBusObj * bzobj; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); + + if (!ret && nm_utils_error_is_cancelled(error)) + return; + + if (ret) + g_variant_get(ret, "(&s)", &network_iface_name); + + bzobj = user_data; + + _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_NAP, network_iface_name, NULL, error); +} + +static void +_connect_cancelled_cb(GCancellable *cancellable, BzDBusObj *bzobj) +{ + _connect_disconnect(bzobj->self, bzobj, "connect cancelled"); +} + +static gboolean +_connect_timeout_wait_connected_cb(gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + bzobj->x_device.c_req_data->timeout_wait_connect_id = 0; + _connect_disconnect(bzobj->self, bzobj, "timeout waiting for connected"); + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_timeout_cb(gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + bzobj->x_device.c_req_data->timeout_id = 0; + _connect_disconnect(bzobj->self, bzobj, "timeout connecting"); + return G_SOURCE_REMOVE; +} + +static void +_connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + DeviceConnectReqData * c_req_data; + char sbuf_cap[100]; + gboolean bt_type; + + if (bzobj->x_device_connect_bt_type == NM_BT_CAPABILITY_NONE) { + nm_assert(!bzobj->x_device.c_req_data); + return; + } + + bt_type = bzobj->x_device_connect_bt_type; + nm_assert(NM_IN_SET(bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP)); + bzobj->x_device_connect_bt_type = NM_BT_CAPABILITY_NONE; + + c_req_data = g_steal_pointer(&bzobj->x_device.c_req_data); + + _LOGD("%s [%s]: disconnect due to %s", + nm_bluetooth_capability_to_string(bt_type, sbuf_cap, sizeof(sbuf_cap)), + bzobj->object_path, + reason); + + if (c_req_data) + nm_clear_g_cancellable(&c_req_data->int_cancellable); + + if (bt_type == NM_BT_CAPABILITY_DUN) { + /* For DUN devices, we also called org.bluez.Device1.Connect() (because in order + * for nm_bluez5_dun_connect() to succeed, we need to be already connected *why??). + * + * But upon disconnect we don't call Disconnect() because we don't know whether somebody + * else also uses the bluetooth device for other purposes. During disconnect we only + * terminate the DUN connection, but don't disconnect entirely. I think that's the + * best we can do. */ +#if WITH_BLUEZ5_DUN + nm_clear_pointer(&bzobj->x_device.connect_dun_context, nm_bluez5_dun_disconnect); +#else + nm_assert_not_reached(); +#endif + } else { + if (priv->name_owner) { + gs_unref_object GCancellable *cancellable = NULL; + + cancellable = g_cancellable_new(); + + nm_shutdown_wait_obj_register_cancellable_full( + cancellable, + g_strdup_printf("bt-disconnect-nap[%s]", bzobj->object_path), + TRUE); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_INTERFACE, + "Disconnect", + g_variant_new("()"), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + cancellable, + _dbus_call_complete_cb_nop, + NULL); + } + } + + if (c_req_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "connect aborted due to %s", reason); + _device_connect_req_data_complete(c_req_data, self, NULL, error); + } +} + +gboolean +nm_bluez_manager_connect(NMBluezManager * self, + const char * object_path, + NMBluetoothCapabilities connection_bt_type, + int timeout_msec, + GCancellable * cancellable, + NMBluezManagerConnectCb callback, + gpointer callback_user_data, + GError ** error) +{ + gs_unref_object GCancellable *int_cancellable = NULL; + DeviceConnectReqData * c_req_data; + NMBluezManagerPrivate * priv; + BzDBusObj * bzobj; + char sbuf_cap[100]; + + g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IN_SET(connection_bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP), + FALSE); + g_return_val_if_fail(callback, FALSE); + + nm_assert(timeout_msec > 0); + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + bzobj = _bzobjs_get(self, object_path); + + if (!bzobj) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "device %s does not exist", object_path); + return FALSE; + } + + if (!_bzobjs_device_is_usable(bzobj, NULL, NULL)) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "device %s is not usable", object_path); + return FALSE; + } + + if (!NM_FLAGS_ALL(bzobj->d_device_capabilities, connection_bt_type)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "device %s has not the required capabilities", + object_path); + return FALSE; + } + +#if !WITH_BLUEZ5_DUN + if (connection_bt_type == NM_BT_CAPABILITY_DUN) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "DUN is not supported"); + return FALSE; + } +#endif + + _connect_disconnect(self, bzobj, "new activation"); + + _LOGD("%s [%s]: connecting...", + nm_bluetooth_capability_to_string(connection_bt_type, sbuf_cap, sizeof(sbuf_cap)), + bzobj->object_path); + + int_cancellable = g_cancellable_new(); + +#if WITH_BLUEZ5_DUN + if (connection_bt_type == NM_BT_CAPABILITY_DUN) { + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_DEVICE_INTERFACE, + "Connect", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + timeout_msec, + int_cancellable, + _connect_dun_step1_cb, + bzobj); + } else +#endif + { + nm_assert(connection_bt_type == NM_BT_CAPABILITY_NAP); + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_INTERFACE, + "Connect", + g_variant_new("(s)", BLUETOOTH_CONNECT_NAP), + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + timeout_msec, + int_cancellable, + _connect_nap_cb, + bzobj); + } + + c_req_data = g_slice_new(DeviceConnectReqData); + *c_req_data = (DeviceConnectReqData){ + .int_cancellable = g_steal_pointer(&int_cancellable), + .ext_cancellable = g_object_ref(cancellable), + .callback = callback, + .callback_user_data = callback_user_data, + .ext_cancelled_id = + g_signal_connect(cancellable, "cancelled", G_CALLBACK(_connect_cancelled_cb), bzobj), + .timeout_id = g_timeout_add(timeout_msec, _connect_timeout_cb, bzobj), + }; + + bzobj->x_device_connect_bt_type = connection_bt_type; + bzobj->x_device.c_req_data = c_req_data; + + return TRUE; +} + +void +nm_bluez_manager_disconnect(NMBluezManager *self, const char *object_path) +{ + BzDBusObj *bzobj; + + g_return_if_fail(NM_IS_BLUEZ_MANAGER(self)); + g_return_if_fail(object_path); + + bzobj = _bzobjs_get(self, object_path); + if (!bzobj) + return; + + _connect_disconnect(self, bzobj, "disconnected by user"); +} + +/*****************************************************************************/ + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + *out_ignore = TRUE; + g_return_val_if_fail(plink->type == NM_LINK_TYPE_BNEP, NULL); + return NULL; +} + +static gboolean +match_connection(NMDeviceFactory *factory, NMConnection *connection) +{ + const char *type = nm_connection_get_connection_type(connection); + + nm_assert(nm_streq(type, NM_SETTING_BLUETOOTH_SETTING_NAME)); + + if (_nm_connection_get_setting_bluetooth_for_nap(connection)) + return FALSE; /* handled by the bridge factory */ + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_bluez_manager_init(NMBluezManager *self) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + priv->vtable_network_server = (NMBtVTableNetworkServer){ + .is_available = _network_server_vt_is_available, + .register_bridge = _network_server_vt_register_bridge, + .unregister_bridge = _network_server_vt_unregister_bridge, + }; + + c_list_init(&priv->network_server_lst_head); + c_list_init(&priv->process_change_lst_head); + + priv->conn_data_heads = + g_hash_table_new_full(_conn_data_head_hash, _conn_data_head_equal, g_free, NULL); + priv->conn_data_elems = g_hash_table_new_full(nm_pdirect_hash, + nm_pdirect_equal, + nm_g_slice_free_fcn(ConnDataElem), + NULL); + + priv->bzobjs = g_hash_table_new_full(nm_pstr_hash, + nm_pstr_equal, + (GDestroyNotify) _bz_dbus_obj_free, + NULL); + + priv->manager = g_object_ref(NM_MANAGER_GET); + priv->settings = g_object_ref(NM_SETTINGS_GET); + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + g_atomic_pointer_compare_and_exchange(&nm_bt_vtable_network_server, + NULL, + &priv->vtable_network_server); +} + +static void +dispose(GObject *object) +{ + NMBluezManager * self = NM_BLUEZ_MANAGER(object); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); + + /* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all + * BzDBusObj instances and do necessary cleanup actions (like disconnecting devices + * or deleting panu_connection). */ + + nm_assert(c_list_is_empty(&priv->network_server_lst_head)); + nm_assert(c_list_is_empty(&priv->process_change_lst_head)); + nm_assert(priv->process_change_idle_id == 0); + + g_atomic_pointer_compare_and_exchange(&nm_bt_vtable_network_server, + &priv->vtable_network_server, + NULL); + + _cleanup_all(self); + + G_OBJECT_CLASS(nm_bluez_manager_parent_class)->dispose(object); + + g_clear_object(&priv->settings); + g_clear_object(&priv->manager); + g_clear_object(&priv->dbus_connection); + + nm_clear_pointer(&priv->bzobjs, g_hash_table_destroy); +} + +static void +nm_bluez_manager_class_init(NMBluezManagerClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + object_class->dispose = dispose; + + factory_class->get_supported_types = get_supported_types; + factory_class->create_device = create_device; + factory_class->match_connection = match_connection; + factory_class->start = start; +} diff --git a/src/core/devices/bluetooth/nm-bluez-manager.h b/src/core/devices/bluetooth/nm-bluez-manager.h new file mode 100644 index 0000000..04bfea7 --- /dev/null +++ b/src/core/devices/bluetooth/nm-bluez-manager.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2009 - 2019 Red Hat, Inc. + */ + +#ifndef __NM_BLUEZ_MANAGER_H__ +#define __NM_BLUEZ_MANAGER_H__ + +#define NM_TYPE_BLUEZ_MANAGER (nm_bluez_manager_get_type()) +#define NM_BLUEZ_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager)) +#define NM_BLUEZ_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) +#define NM_IS_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_BLUEZ_MANAGER)) +#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_BLUEZ_MANAGER)) +#define NM_BLUEZ_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) + +typedef struct _NMBluezManager NMBluezManager; +typedef struct _NMBluezManagerClass NMBluezManagerClass; + +GType nm_bluez_manager_get_type(void); + +typedef void (*NMBluezManagerConnectCb)( + NMBluezManager *self, + gboolean is_completed /* or else is early notification with DUN path */, + const char * device_name, + GError * error, + gpointer user_data); + +gboolean nm_bluez_manager_connect(NMBluezManager * self, + const char * object_path, + NMBluetoothCapabilities connection_bt_type, + int timeout_msec, + GCancellable * cancellable, + NMBluezManagerConnectCb callback, + gpointer callback_user_data, + GError ** error); + +void nm_bluez_manager_disconnect(NMBluezManager *self, const char *object_path); + +#endif /* __NM_BLUEZ_MANAGER_H__ */ diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.c b/src/core/devices/bluetooth/nm-bluez5-dun.c new file mode 100644 index 0000000..d803b74 --- /dev/null +++ b/src/core/devices/bluetooth/nm-bluez5-dun.c @@ -0,0 +1,857 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-bluez5-dun.h" +#include "nm-bt-error.h" +#include "NetworkManagerUtils.h" + +#define RFCOMM_FMT "/dev/rfcomm%d" + +/*****************************************************************************/ + +typedef struct { + GCancellable * cancellable; + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + sdp_session_t *sdp_session; + + GError *rfcomm_sdp_search_error; + + GSource *source; + + gint64 connect_open_tty_started_at; + + gulong cancelled_id; + + guint8 sdp_session_try_count; +} ConnectData; + +struct _NMBluez5DunContext { + const char *dst_str; + + ConnectData *cdat; + + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb; + gpointer notify_tty_hangup_user_data; + + char *rfcomm_tty_path; + + GSource *rfcomm_tty_poll_source; + + int rfcomm_sock_fd; + int rfcomm_tty_fd; + int rfcomm_tty_no; + int rfcomm_channel; + + bdaddr_t src; + bdaddr_t dst; + + char src_str[]; +}; + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG_PREFIX_NAME "bluez" +#define _NMLOG(level, context, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + const NMBluez5DunContext *const _context = (context); \ + \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: DUN[%s] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _context->src_str _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _context_invoke_callback_success(NMBluez5DunContext *context); +static void _context_invoke_callback_fail_and_free(NMBluez5DunContext *context, GError *error); +static void _context_free(NMBluez5DunContext *context); +static int _connect_open_tty(NMBluez5DunContext *context); +static gboolean _connect_sdp_session_start(NMBluez5DunContext *context, GError **error); + +/*****************************************************************************/ + +NM_AUTO_DEFINE_FCN0(NMBluez5DunContext *, _nm_auto_free_context, _context_free); +#define nm_auto_free_context nm_auto(_nm_auto_free_context) + +/*****************************************************************************/ + +const char * +nm_bluez5_dun_context_get_adapter(const NMBluez5DunContext *context) +{ + return context->src_str; +} + +const char * +nm_bluez5_dun_context_get_remote(const NMBluez5DunContext *context) +{ + return context->dst_str; +} + +const char * +nm_bluez5_dun_context_get_rfcomm_dev(const NMBluez5DunContext *context) +{ + return context->rfcomm_tty_path; +} + +/*****************************************************************************/ + +static gboolean +_rfcomm_tty_poll_cb(int fd, GIOCondition condition, gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + + _LOGD(context, + "receive %s%s%s signal on rfcomm file descriptor", + NM_FLAGS_HAS(condition, G_IO_ERR) ? "ERR" : "", + NM_FLAGS_ALL(condition, G_IO_HUP | G_IO_ERR) ? "," : "", + NM_FLAGS_HAS(condition, G_IO_HUP) ? "HUP" : ""); + + nm_clear_g_source_inst(&context->rfcomm_tty_poll_source); + context->notify_tty_hangup_cb(context, context->notify_tty_hangup_user_data); + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_open_tty_retry_cb(gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + int r; + + r = _connect_open_tty(context); + if (r >= 0) + return G_SOURCE_REMOVE; + + if (nm_utils_get_monotonic_timestamp_nsec() + > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NSEC_PER_MSEC)) { + gs_free_error GError *error = NULL; + + nm_clear_g_source_inst(&context->cdat->source); + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "give up waiting to open %s device: %s (%d)", + context->rfcomm_tty_path, + nm_strerror_native(r), + -r); + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static int +_connect_open_tty(NMBluez5DunContext *context) +{ + int fd; + int errsv; + + fd = open(context->rfcomm_tty_path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd < 0) { + errsv = NM_ERRNO_NATIVE(errno); + + if (!context->cdat->source) { + _LOGD(context, + "failed opening tty " RFCOMM_FMT ": %s (%d). Start polling...", + context->rfcomm_tty_no, + nm_strerror_native(errsv), + errsv); + context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_nsec(); + context->cdat->source = nm_g_timeout_source_new(100, + G_PRIORITY_DEFAULT, + _connect_open_tty_retry_cb, + context, + NULL); + g_source_attach(context->cdat->source, NULL); + } + return -errsv; + } + + context->rfcomm_tty_fd = fd; + + context->rfcomm_tty_poll_source = nm_g_unix_fd_source_new(context->rfcomm_tty_fd, + G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + _rfcomm_tty_poll_cb, + context, + NULL); + g_source_attach(context->rfcomm_tty_poll_source, NULL); + + _context_invoke_callback_success(context); + return 0; +} + +static void +_connect_create_rfcomm(NMBluez5DunContext *context) +{ + gs_free_error GError *error = NULL; + struct rfcomm_dev_req req; + int devid; + int errsv; + int r; + + _LOGD(context, "connected to %s on channel %d", context->dst_str, context->rfcomm_channel); + + /* Create an RFCOMM kernel device for the DUN channel */ + memset(&req, 0, sizeof(req)); + req.dev_id = -1; + req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP); + req.channel = context->rfcomm_channel; + memcpy(&req.src, &context->src, ETH_ALEN); + memcpy(&req.dst, &context->dst, ETH_ALEN); + devid = ioctl(context->rfcomm_sock_fd, RFCOMMCREATEDEV, &req); + if (devid < 0) { + errsv = NM_ERRNO_NATIVE(errno); + if (errsv == EBADFD) { + /* hm. We use a non-blocking socket to connect. Above getsockopt(SOL_SOCKET,SO_ERROR) indicated + * success, but still now we fail with EBADFD. I think that is a bug and we should get the + * failure during connect(). + * + * Anyway, craft a less confusing error message than + * "failed to create rfcomm device: File descriptor in bad state (77)". */ + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "unknown failure to connect to DUN device"); + } else { + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create rfcomm device: %s (%d)", + nm_strerror_native(errsv), + errsv); + } + _context_invoke_callback_fail_and_free(context, error); + return; + } + + context->rfcomm_tty_no = devid; + context->rfcomm_tty_path = g_strdup_printf(RFCOMM_FMT, devid); + + r = _connect_open_tty(context); + if (r < 0) { + /* we created the rfcomm device, but cannot yet open it. That means, we are + * not yet fully connected. However, we notify the caller about "what we learned + * so far". Note that this happens synchronously. + * + * The purpose is that once we proceed synchronously, modem-manager races with + * the detection of the modem. We want to notify the caller first about the + * device name. */ + context->cdat->callback(NULL, + context->rfcomm_tty_path, + NULL, + context->cdat->callback_user_data); + } +} + +static gboolean +_connect_socket_connect_cb(int fd, GIOCondition condition, gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + gs_free_error GError *error = NULL; + int errsv = 0; + socklen_t slen = sizeof(errsv); + int r; + + nm_clear_g_source_inst(&context->cdat->source); + + r = getsockopt(context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen); + + if (r < 0) { + errsv = errno; + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to complete connecting RFCOMM socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; + } + + if (errsv != 0) { + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect RFCOMM socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; + } + + _connect_create_rfcomm(context); + return G_SOURCE_REMOVE; +} + +static void +_connect_socket_connect(NMBluez5DunContext *context) +{ + gs_free_error GError *error = NULL; + struct sockaddr_rc sa; + int errsv; + + context->rfcomm_sock_fd = + socket(AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_RFCOMM); + if (context->rfcomm_sock_fd < 0) { + errsv = errno; + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create RFCOMM socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return; + } + + /* Connect to the remote device */ + memset(&sa, 0, sizeof(sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + memcpy(&sa.rc_bdaddr, &context->src, ETH_ALEN); + if (bind(context->rfcomm_sock_fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) { + errsv = errno; + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to bind socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return; + } + + memset(&sa, 0, sizeof(sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = context->rfcomm_channel; + memcpy(&sa.rc_bdaddr, &context->dst, ETH_ALEN); + if (connect(context->rfcomm_sock_fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) { + errsv = errno; + if (errsv != EINPROGRESS) { + g_set_error(&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to remote device: %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return; + } + + _LOGD(context, + "connecting to %s on channel %d...", + context->dst_str, + context->rfcomm_channel); + + context->cdat->source = nm_g_unix_fd_source_new(context->rfcomm_sock_fd, + G_IO_OUT, + G_PRIORITY_DEFAULT, + _connect_socket_connect_cb, + context, + NULL); + g_source_attach(context->cdat->source, NULL); + return; + } + + _connect_create_rfcomm(context); +} + +static void +_connect_sdp_search_cb(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) +{ + NMBluez5DunContext *context = user_data; + int scanned; + int seqlen = 0; + int bytesleft = size; + uint8_t dataType; + int channel = -1; + + if (context->cdat->rfcomm_sdp_search_error || context->rfcomm_channel >= 0) + return; + + _LOGD(context, "SDP search finished with type=%d status=%d", status, type); + + /* SDP response received */ + if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { + g_set_error(&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not get a Service Discovery response"); + return; + } + + scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); + + _LOGD(context, "SDP sequence type scanned=%d length=%d", scanned, seqlen); + + scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); + if (!scanned || !seqlen) { + /* Short read or unknown sequence type */ + g_set_error(&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "improper Service Discovery response"); + return; + } + + rsp += scanned; + bytesleft -= scanned; + do { + sdp_record_t *rec; + int recsize = 0; + sdp_list_t * protos; + + rec = sdp_extract_pdu(rsp, bytesleft, &recsize); + if (!rec) + break; + + if (!recsize) { + sdp_record_free(rec); + break; + } + + if (sdp_get_access_protos(rec, &protos) == 0) { + /* Extract the DUN channel number */ + channel = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_free(protos, NULL); + + _LOGD(context, "SDP channel=%d", channel); + } + sdp_record_free(rec); + + scanned += recsize; + rsp += recsize; + bytesleft -= recsize; + } while (scanned < (ssize_t) size && bytesleft > 0 && channel < 0); + + if (channel == -1) { + g_set_error(&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not receive rfcomm-channel"); + return; + } + + context->rfcomm_channel = channel; +} + +static gboolean +_connect_sdp_search_io_cb(int fd, GIOCondition condition, gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + gs_free_error GError *error = NULL; + int errsv; + + if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + _LOGD(context, "SDP search returned with invalid IO condition 0x%x", (guint) condition); + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery interrupted"); + nm_clear_g_source_inst(&context->cdat->source); + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; + } + + if (sdp_process(context->cdat->sdp_session) == 0) { + _LOGD(context, "SDP search still not finished"); + return G_SOURCE_CONTINUE; + } + + nm_clear_g_source_inst(&context->cdat->source); + + if (context->rfcomm_channel < 0 && !context->cdat->rfcomm_sdp_search_error) { + errsv = sdp_get_error(context->cdat->sdp_session); + _LOGD(context, "SDP search failed: %s (%d)", nm_strerror_native(errsv), errsv); + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery failed with %s (%d)", + nm_strerror_native(errsv), + errsv); + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; + } + + if (context->cdat->rfcomm_sdp_search_error) { + _LOGD(context, + "SDP search failed to complete: %s", + context->cdat->rfcomm_sdp_search_error->message); + _context_invoke_callback_fail_and_free(context, context->cdat->rfcomm_sdp_search_error); + return G_SOURCE_REMOVE; + } + + nm_clear_pointer(&context->cdat->sdp_session, sdp_close); + + _connect_socket_connect(context); + + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_sdp_session_start_on_idle_cb(gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + gs_free_error GError *error = NULL; + + nm_clear_g_source_inst(&context->cdat->source); + + _LOGD(context, "retry starting sdp-session..."); + + if (!_connect_sdp_session_start(context, &error)) + _context_invoke_callback_fail_and_free(context, error); + + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_sdp_io_cb(int fd, GIOCondition condition, gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + sdp_list_t * search; + sdp_list_t * attrs; + uuid_t svclass; + uint16_t attr; + int errsv; + int fd_err = 0; + int r; + socklen_t len = sizeof(fd_err); + gs_free_error GError *error = NULL; + + nm_clear_g_source_inst(&context->cdat->source); + + _LOGD(context, "sdp-session ready to connect with fd=%d", fd); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) { + errsv = NM_ERRNO_NATIVE(errno); + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "error for getsockopt on Service Discovery socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + goto done; + } + + if (fd_err != 0) { + errsv = nm_errno_native(fd_err); + + if (NM_IN_SET(errsv, ECONNREFUSED, EHOSTDOWN) + && --context->cdat->sdp_session_try_count > 0) { + /* *sigh* */ + _LOGD(context, + "sdp-session failed with %s (%d). Retry in a bit", + nm_strerror_native(errsv), + errsv); + nm_clear_g_source_inst(&context->cdat->source); + context->cdat->source = nm_g_timeout_source_new(1000, + G_PRIORITY_DEFAULT, + _connect_sdp_session_start_on_idle_cb, + context, + NULL); + g_source_attach(context->cdat->source, NULL); + return G_SOURCE_REMOVE; + } + + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "error on Service Discovery socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + goto done; + } + + if (sdp_set_notify(context->cdat->sdp_session, _connect_sdp_search_cb, context) < 0) { + /* Should not be reached, only can fail if we passed bad sdp_session. */ + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "could not set Service Discovery notification"); + goto done; + } + + sdp_uuid16_create(&svclass, DIALUP_NET_SVCLASS_ID); + search = sdp_list_append(NULL, &svclass); + attr = SDP_ATTR_PROTO_DESC_LIST; + attrs = sdp_list_append(NULL, &attr); + + r = sdp_service_search_attr_async(context->cdat->sdp_session, + search, + SDP_ATTR_REQ_INDIVIDUAL, + attrs); + + sdp_list_free(attrs, NULL); + sdp_list_free(search, NULL); + + if (r < 0) { + errsv = nm_errno_native(sdp_get_error(context->cdat->sdp_session)); + error = g_error_new(NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "error starting Service Discovery: %s (%d)", + nm_strerror_native(errsv), + errsv); + goto done; + } + + /* Set callback responsible for update the internal SDP transaction */ + context->cdat->source = nm_g_unix_fd_source_new(fd, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + G_PRIORITY_DEFAULT, + _connect_sdp_search_io_cb, + context, + NULL); + g_source_attach(context->cdat->source, NULL); + +done: + if (error) + _context_invoke_callback_fail_and_free(context, error); + return G_SOURCE_REMOVE; +} + +/*****************************************************************************/ + +static void +_connect_cancelled_cb(GCancellable *cancellable, NMBluez5DunContext *context) +{ + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) + g_return_if_reached(); + + _context_invoke_callback_fail_and_free(context, error); +} + +static gboolean +_connect_sdp_session_start(NMBluez5DunContext *context, GError **error) +{ + nm_assert(context->cdat); + + nm_clear_g_source_inst(&context->cdat->source); + nm_clear_pointer(&context->cdat->sdp_session, sdp_close); + + context->cdat->sdp_session = sdp_connect(&context->src, &context->dst, SDP_NON_BLOCKING); + if (!context->cdat->sdp_session) { + int errsv = nm_errno_native(errno); + + g_set_error(error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to the SDP server: %s (%d)", + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + context->cdat->source = nm_g_unix_fd_source_new(sdp_get_socket(context->cdat->sdp_session), + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + G_PRIORITY_DEFAULT, + _connect_sdp_io_cb, + context, + NULL); + g_source_attach(context->cdat->source, NULL); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_bluez5_dun_connect(const char * adapter, + const char * remote, + GCancellable * cancellable, + NMBluez5DunConnectCb callback, + gpointer callback_user_data, + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb, + gpointer notify_tty_hangup_user_data, + GError ** error) +{ + nm_auto_free_context NMBluez5DunContext *context = NULL; + ConnectData * cdat; + gsize src_l; + gsize dst_l; + + g_return_val_if_fail(adapter, FALSE); + g_return_val_if_fail(remote, FALSE); + g_return_val_if_fail(G_IS_CANCELLABLE(cancellable), FALSE); + g_return_val_if_fail(callback, FALSE); + g_return_val_if_fail(notify_tty_hangup_cb, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + nm_assert(!g_cancellable_is_cancelled(cancellable)); + + src_l = strlen(adapter) + 1; + dst_l = strlen(remote) + 1; + + cdat = g_slice_new(ConnectData); + *cdat = (ConnectData){ + .callback = callback, + .callback_user_data = callback_user_data, + .cancellable = g_object_ref(cancellable), + .sdp_session_try_count = 5, + }; + + context = g_malloc(sizeof(NMBluez5DunContext) + src_l + dst_l); + *context = (NMBluez5DunContext){ + .cdat = cdat, + .notify_tty_hangup_cb = notify_tty_hangup_cb, + .notify_tty_hangup_user_data = notify_tty_hangup_user_data, + .rfcomm_tty_no = -1, + .rfcomm_sock_fd = -1, + .rfcomm_tty_fd = -1, + .rfcomm_channel = -1, + }; + memcpy(&context->src_str[0], adapter, src_l); + context->dst_str = &context->src_str[src_l]; + memcpy((char *) context->dst_str, remote, dst_l); + + if (str2ba(adapter, &context->src) < 0) { + g_set_error(error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, "invalid source"); + return FALSE; + } + + if (str2ba(remote, &context->dst) < 0) { + g_set_error(error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, "invalid remote"); + return FALSE; + } + + context->cdat->cancelled_id = g_signal_connect(context->cdat->cancellable, + "cancelled", + G_CALLBACK(_connect_cancelled_cb), + context); + + if (!_connect_sdp_session_start(context, error)) + return FALSE; + + _LOGD(context, "starting channel number discovery for device %s", context->dst_str); + + g_steal_pointer(&context); + return TRUE; +} + +/*****************************************************************************/ + +void +nm_bluez5_dun_disconnect(NMBluez5DunContext *context) +{ + nm_assert(context); + nm_assert(!context->cdat); + + _LOGD(context, "disconnecting DUN connection"); + + _context_free(context); +} + +/*****************************************************************************/ + +static void +_context_cleanup_connect_data(NMBluez5DunContext *context) +{ + ConnectData *cdat; + + cdat = g_steal_pointer(&context->cdat); + if (!cdat) + return; + + nm_clear_g_signal_handler(cdat->cancellable, &cdat->cancelled_id); + + nm_clear_g_source_inst(&cdat->source); + + nm_clear_pointer(&cdat->sdp_session, sdp_close); + + g_clear_object(&cdat->cancellable); + + g_clear_error(&cdat->rfcomm_sdp_search_error); + + nm_g_slice_free(cdat); +} + +static void +_context_invoke_callback(NMBluez5DunContext *context, GError *error) +{ + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + nm_assert(context); + nm_assert(context->cdat); + nm_assert(context->cdat->callback); + nm_assert(error || context->rfcomm_tty_path); + + if (!error) + _LOGD(context, "connected via \"%s\"", context->rfcomm_tty_path); + else if (nm_utils_error_is_cancelled(error)) + _LOGD(context, "cancelled"); + else + _LOGD(context, "failed to connect: %s", error->message); + + callback = context->cdat->callback; + callback_user_data = context->cdat->callback_user_data; + + _context_cleanup_connect_data(context); + + callback(error ? NULL : context, + error ? NULL : context->rfcomm_tty_path, + error, + callback_user_data); +} + +static void +_context_invoke_callback_success(NMBluez5DunContext *context) +{ + nm_assert(context->rfcomm_tty_path); + _context_invoke_callback(context, NULL); +} + +static void +_context_invoke_callback_fail_and_free(NMBluez5DunContext *context, GError *error) +{ + nm_assert(error); + _context_invoke_callback(context, error); + _context_free(context); +} + +static void +_context_free(NMBluez5DunContext *context) +{ + nm_assert(context); + + _context_cleanup_connect_data(context); + + nm_clear_g_source_inst(&context->rfcomm_tty_poll_source); + + if (context->rfcomm_sock_fd >= 0) { + if (context->rfcomm_tty_no >= 0) { + struct rfcomm_dev_req req; + + memset(&req, 0, sizeof(struct rfcomm_dev_req)); + req.dev_id = context->rfcomm_tty_no; + context->rfcomm_tty_no = -1; + (void) ioctl(context->rfcomm_sock_fd, RFCOMMRELEASEDEV, &req); + } + nm_close(nm_steal_fd(&context->rfcomm_sock_fd)); + } + + if (context->rfcomm_tty_fd >= 0) + nm_close(nm_steal_fd(&context->rfcomm_tty_fd)); + nm_clear_g_free(&context->rfcomm_tty_path); + g_free(context); +} diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.h b/src/core/devices/bluetooth/nm-bluez5-dun.h new file mode 100644 index 0000000..020d411 --- /dev/null +++ b/src/core/devices/bluetooth/nm-bluez5-dun.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NM_BLUEZ5_DUN_H__ +#define __NM_BLUEZ5_DUN_H__ + +typedef struct _NMBluez5DunContext NMBluez5DunContext; + +#if WITH_BLUEZ5_DUN + +typedef void (*NMBluez5DunConnectCb)(NMBluez5DunContext *context, + const char * rfcomm_dev, + GError * error, + gpointer user_data); + +typedef void (*NMBluez5DunNotifyTtyHangupCb)(NMBluez5DunContext *context, gpointer user_data); + +gboolean nm_bluez5_dun_connect(const char * adapter, + const char * remote, + GCancellable * cancellable, + NMBluez5DunConnectCb callback, + gpointer callback_user_data, + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb, + gpointer notify_tty_hangup_user_data, + GError ** error); + +void nm_bluez5_dun_disconnect(NMBluez5DunContext *context); + +const char *nm_bluez5_dun_context_get_adapter(const NMBluez5DunContext *context); +const char *nm_bluez5_dun_context_get_remote(const NMBluez5DunContext *context); +const char *nm_bluez5_dun_context_get_rfcomm_dev(const NMBluez5DunContext *context); + +#endif /* WITH_BLUEZ5_DUN */ + +#endif /* __NM_BLUEZ5_DUN_H__ */ diff --git a/src/core/devices/bluetooth/nm-bt-error.c b/src/core/devices/bluetooth/nm-bt-error.c new file mode 100644 index 0000000..ed09147 --- /dev/null +++ b/src/core/devices/bluetooth/nm-bt-error.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-bt-error.h" + +NM_CACHED_QUARK_FCN("nm-bt-error", nm_bt_error_quark); diff --git a/src/core/devices/bluetooth/nm-bt-error.h b/src/core/devices/bluetooth/nm-bt-error.h new file mode 100644 index 0000000..432ae5b --- /dev/null +++ b/src/core/devices/bluetooth/nm-bt-error.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef _NM_BLUEZ5_ERROR_H_ +#define _NM_BLUEZ5_ERROR_H_ + +typedef enum { + NM_BT_ERROR_CONNECTION_NOT_BT = 0, /*< nick=ConnectionNotBt >*/ + NM_BT_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_BT_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ + NM_BT_ERROR_DUN_CONNECT_FAILED, /*< nick=DunConnectFailed >*/ +} NMBtError; + +#define NM_BT_ERROR (nm_bt_error_quark()) +GQuark nm_bt_error_quark(void); + +#endif /* _NM_BT_ERROR_H_ */ diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c new file mode 100644 index 0000000..0515d29 --- /dev/null +++ b/src/core/devices/bluetooth/nm-device-bt.c @@ -0,0 +1,1412 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-bt.h" + +#include +#include + +#include "nm-core-internal.h" +#include "nm-bluez-common.h" +#include "nm-bluez-manager.h" +#include "devices/nm-device-private.h" +#include "ppp/nm-ppp-manager.h" +#include "nm-setting-connection.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-cdma.h" +#include "nm-setting-gsm.h" +#include "nm-setting-serial.h" +#include "nm-setting-ppp.h" +#include "NetworkManagerUtils.h" +#include "settings/nm-settings-connection.h" +#include "nm-utils.h" +#include "nm-bt-error.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" + +#include "devices/wwan/nm-modem-manager.h" +#include "devices/wwan/nm-modem.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceBt +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceBt, + PROP_BT_BDADDR, + PROP_BT_BZ_MGR, + PROP_BT_CAPABILITIES, + PROP_BT_DBUS_PATH, + PROP_BT_NAME, ); + +enum { + PPP_STATS, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + NMModemManager *modem_manager; + + NMBluezManager *bz_mgr; + + char *dbus_path; + + char *bdaddr; + char *name; + + char *connect_rfcomm_iface; + + GSList *connect_modem_candidates; + + NMModem *modem; + + GCancellable *connect_bz_cancellable; + + gulong connect_watch_link_id; + + guint connect_watch_link_idle_id; + + guint connect_wait_modem_id; + + NMBluetoothCapabilities capabilities : 6; + + NMBluetoothCapabilities connect_bt_type : 6; /* BT type of the current connection */ + + NMDeviceStageState stage1_bt_state : 3; + NMDeviceStageState stage1_modem_prepare_state : 3; + + bool is_connected : 1; + + bool mm_running : 1; + +} NMDeviceBtPrivate; + +struct _NMDeviceBt { + NMDevice parent; + NMDeviceBtPrivate _priv; +}; + +struct _NMDeviceBtClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE) + +#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceBt, NM_IS_DEVICE_BT, NMDevice) + +/*****************************************************************************/ + +NMBluetoothCapabilities +nm_device_bt_get_capabilities(NMDeviceBt *self) +{ + g_return_val_if_fail(NM_IS_DEVICE_BT(self), NM_BT_CAPABILITY_NONE); + + return NM_DEVICE_BT_GET_PRIVATE(self)->capabilities; +} + +static NMBluetoothCapabilities +get_connection_bt_type(NMConnection *connection) +{ + NMSettingBluetooth *s_bt; + const char * bt_type; + + s_bt = nm_connection_get_setting_bluetooth(connection); + + if (s_bt) { + bt_type = nm_setting_bluetooth_get_connection_type(s_bt); + if (bt_type) { + if (nm_streq(bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) + return NM_BT_CAPABILITY_DUN; + else if (nm_streq(bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) + return NM_BT_CAPABILITY_NAP; + } + } + + return NM_BT_CAPABILITY_NONE; +} + +static gboolean +get_connection_bt_type_check(NMDeviceBt * self, + NMConnection * connection, + NMBluetoothCapabilities *out_bt_type, + GError ** error) +{ + NMBluetoothCapabilities bt_type; + + bt_type = get_connection_bt_type(connection); + + NM_SET_OUT(out_bt_type, bt_type); + + if (bt_type == NM_BT_CAPABILITY_NONE) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "profile is not a PANU/DUN bluetooth type"); + return FALSE; + } + + if (!NM_FLAGS_ALL(NM_DEVICE_BT_GET_PRIVATE(self)->capabilities, bt_type)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not support bluetooth type"); + return FALSE; + } + + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_NON_KERNEL; +} + +static gboolean +can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object) +{ + NMDeviceBt * self = NM_DEVICE_BT(device); + NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(self); + NMBluetoothCapabilities bt_type; + + nm_assert(!specific_object || !*specific_object); + + if (!NM_DEVICE_CLASS(nm_device_bt_parent_class)->can_auto_connect(device, sett_conn, NULL)) + return FALSE; + + if (!get_connection_bt_type_check(self, + nm_settings_connection_get_connection(sett_conn), + &bt_type, + NULL)) + return FALSE; + + /* Can't auto-activate a DUN connection without ModemManager */ + if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) + return FALSE; + + return TRUE; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceBt * self = NM_DEVICE_BT(device); + NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(self); + NMSettingBluetooth *s_bt; + const char * bdaddr; + + if (!NM_DEVICE_CLASS(nm_device_bt_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (!get_connection_bt_type_check(self, connection, NULL, error)) + return FALSE; + + s_bt = nm_connection_get_setting_bluetooth(connection); + + bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt); + if (!bdaddr) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "profile lacks bdaddr setting"); + return FALSE; + } + if (!nm_utils_hwaddr_matches(priv->bdaddr, -1, bdaddr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "devices bdaddr setting mismatches"); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceBt * self = NM_DEVICE_BT(device); + NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(self); + NMBluetoothCapabilities bt_type; + + if (!get_connection_bt_type_check(self, connection, &bt_type, error)) + return FALSE; + + if (bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "ModemManager missing for DUN profile"); + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(device); + NMSettingBluetooth *s_bt; + const char * setting_bdaddr; + const char * ctype; + gboolean is_dun = FALSE; + gboolean is_pan = FALSE; + NMSettingGsm * s_gsm; + NMSettingCdma * s_cdma; + NMSettingSerial * s_serial; + NMSettingPpp * s_ppp; + const char * fallback_prefix = NULL, *preferred = NULL; + + s_gsm = nm_connection_get_setting_gsm(connection); + s_cdma = nm_connection_get_setting_cdma(connection); + s_serial = nm_connection_get_setting_serial(connection); + s_ppp = nm_connection_get_setting_ppp(connection); + + s_bt = nm_connection_get_setting_bluetooth(connection); + if (!s_bt) { + s_bt = (NMSettingBluetooth *) nm_setting_bluetooth_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bt)); + } + + ctype = nm_setting_bluetooth_get_connection_type(s_bt); + if (ctype) { + if (!strcmp(ctype, NM_SETTING_BLUETOOTH_TYPE_DUN)) + is_dun = TRUE; + else if (!strcmp(ctype, NM_SETTING_BLUETOOTH_TYPE_PANU)) + is_pan = TRUE; + } else { + if (s_gsm || s_cdma) + is_dun = TRUE; + else if (priv->capabilities & NM_BT_CAPABILITY_NAP) + is_pan = TRUE; + } + + if (is_pan) { + /* Make sure the device supports PAN */ + if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("PAN requested, but Bluetooth device does not support NAP")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return FALSE; + } + + /* PAN can't use any DUN-related settings */ + if (s_gsm || s_cdma || s_serial || s_ppp) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("PAN connections cannot specify GSM, CDMA, or serial settings")); + g_prefix_error(error, + "%s: ", + s_gsm ? NM_SETTING_GSM_SETTING_NAME + : s_cdma ? NM_SETTING_CDMA_SETTING_NAME + : s_serial ? NM_SETTING_SERIAL_SETTING_NAME + : NM_SETTING_PPP_SETTING_NAME); + return FALSE; + } + + g_object_set(G_OBJECT(s_bt), + NM_SETTING_BLUETOOTH_TYPE, + NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + + fallback_prefix = _("PAN connection"); + } else if (is_dun) { + /* Make sure the device supports PAN */ + if (!(priv->capabilities & NM_BT_CAPABILITY_DUN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("DUN requested, but Bluetooth device does not support DUN")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return FALSE; + } + + /* Need at least a GSM or a CDMA setting */ + if (!s_gsm && !s_cdma) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("DUN connection must include a GSM or CDMA setting")); + g_prefix_error(error, "%s: ", NM_SETTING_BLUETOOTH_SETTING_NAME); + return FALSE; + } + + g_object_set(G_OBJECT(s_bt), + NM_SETTING_BLUETOOTH_TYPE, + NM_SETTING_BLUETOOTH_TYPE_DUN, + NULL); + + if (s_gsm) { + fallback_prefix = _("GSM connection"); + } else { + fallback_prefix = _("CDMA connection"); + if (!nm_setting_cdma_get_number(s_cdma)) + g_object_set(G_OBJECT(s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL); + } + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Unknown/unhandled Bluetooth connection type")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return FALSE; + } + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_BLUETOOTH_SETTING_NAME, + existing_connections, + preferred, + fallback_prefix, + NULL, + NULL, + is_dun ? FALSE : TRUE); /* No IPv6 yet for DUN */ + + setting_bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt); + if (setting_bdaddr) { + /* Make sure the setting BT Address (if any) matches the device's */ + if (!nm_utils_hwaddr_matches(setting_bdaddr, -1, priv->bdaddr, -1)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("connection does not match device")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_BDADDR); + return FALSE; + } + } else { + /* Lock the connection to this device by default */ + if (!nm_utils_hwaddr_matches(priv->bdaddr, -1, NULL, ETH_ALEN)) + g_object_set(G_OBJECT(s_bt), NM_SETTING_BLUETOOTH_BDADDR, priv->bdaddr, NULL); + } + + return TRUE; +} + +/*****************************************************************************/ +/* IP method PPP */ + +static void +ppp_stats(NMModem *modem, guint i_in_bytes, guint i_out_bytes, gpointer user_data) +{ + guint32 in_bytes = i_in_bytes; + guint32 out_bytes = i_out_bytes; + + g_signal_emit(NM_DEVICE_BT(user_data), + signals[PPP_STATS], + 0, + (guint) in_bytes, + (guint) out_bytes); +} + +static void +ppp_failed(NMModem *modem, guint i_reason, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceBt * self = NM_DEVICE_BT(user_data); + NMDeviceStateReason reason = i_reason; + + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + break; + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_activate_ip4_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET); + else if (nm_device_activate_ip6_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET6); + else if (nm_device_activate_ip4_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else if (nm_device_activate_ip6_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET6, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + _LOGW(LOGD_MB, + "PPP failure in unexpected state %u", + (guint) nm_device_get_state(device)); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + break; + default: + break; + } +} + +static void +modem_auth_requested(NMModem *modem, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + /* Auth requests (PIN, PAP/CHAP passwords, etc) only get handled + * during activation. + */ + if (!nm_device_is_activating(device)) + return; + + nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); +} + +static void +modem_auth_result(NMModem *modem, GError *error, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device); + + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + + if (error) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer user_data) +{ + NMDeviceBt * self = user_data; + NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(self); + NMDeviceStateReason reason = i_reason; + NMDeviceState state; + + state = nm_device_get_state(NM_DEVICE(self)); + + g_return_if_fail(NM_IN_SET(state, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_NEED_AUTH)); + + nm_assert(priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING); + + if (!success) { + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) { + /* If the connect failed because the SIM PIN was wrong don't allow + * the device to be auto-activated anymore, which would risk locking + * the SIM if the incorrect PIN continues to be used. + */ + nm_device_autoconnect_blocked_set(NM_DEVICE(self), + NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN); + } + + nm_device_state_changed(NM_DEVICE(self), NM_DEVICE_STATE_FAILED, reason); + return; + } + + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device); + + if (priv->modem) + nm_modem_device_state_changed(priv->modem, new_state, old_state); + + /* Need to recheck available connections whenever MM appears or disappears, + * since the device could be both DUN and NAP capable and thus may not + * change state (which rechecks available connections) when MM comes and goes. + */ + if (priv->mm_running && NM_FLAGS_HAS(priv->capabilities, NM_BT_CAPABILITY_DUN)) + nm_device_recheck_available_connections(device); +} + +static void +modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpointer user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT(user_data); + NMDevice * device = NM_DEVICE(self); + + g_return_if_fail(nm_device_activate_ip4_state_in_conf(device) == TRUE); + + if (error) { + _LOGW(LOGD_MB | LOGD_IP4 | LOGD_BT, + "retrieving IP4 configuration failed: %s", + error->message); + nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config)); +} + +static void +ip_ifindex_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + if (!nm_device_is_activating(device)) + return; + + if (!nm_device_set_ip_ifindex(device, nm_modem_get_ip_ifindex(modem))) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } +} + +/*****************************************************************************/ + +static void +modem_cleanup(NMDeviceBt *self) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + if (priv->modem) { + g_signal_handlers_disconnect_matched(priv->modem, + G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + NULL, + self); + nm_clear_pointer(&priv->modem, nm_modem_unclaim); + } +} + +static void +modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_data) +{ + NMModemState new_state = new_state_i; + NMModemState old_state = old_state_i; + NMDevice * device = NM_DEVICE(user_data); + NMDeviceState dev_state = nm_device_get_state(device); + + if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) { + /* Will be called whenever something external to NM disables the + * modem directly through ModemManager. + */ + if (nm_device_is_activating(device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + return; + } + } + + if (new_state < NM_MODEM_STATE_CONNECTING && old_state >= NM_MODEM_STATE_CONNECTING + && dev_state >= NM_DEVICE_STATE_NEED_AUTH && dev_state <= NM_DEVICE_STATE_ACTIVATED) { + /* Fail the device if the modem disconnects unexpectedly while the + * device is activating/activated. */ + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } +} + +static void +modem_removed_cb(NMModem *modem, gpointer user_data) +{ + NMDeviceBt * self = NM_DEVICE_BT(user_data); + NMDeviceState state; + + state = nm_device_get_state(NM_DEVICE(self)); + if (nm_device_is_activating(NM_DEVICE(self)) || state == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + modem_cleanup(self); +} + +static gboolean +modem_try_claim(NMDeviceBt *self, NMModem *modem) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + gs_free char * rfcomm_base_name = NULL; + NMDeviceState state; + + if (priv->modem) { + if (priv->modem == modem) + return TRUE; + return FALSE; + } + + if (nm_modem_is_claimed(modem)) + return FALSE; + + if (!priv->connect_rfcomm_iface) + return FALSE; + + rfcomm_base_name = g_path_get_basename(priv->connect_rfcomm_iface); + if (!nm_streq0(rfcomm_base_name, nm_modem_get_control_port(modem))) + return FALSE; + + /* Can only accept the modem in stage1, but since the interface matched + * what we were expecting, don't let anything else claim the modem either. + */ + state = nm_device_get_state(NM_DEVICE(self)); + if (state != NM_DEVICE_STATE_PREPARE) { + _LOGD(LOGD_BT | LOGD_MB, + "modem found but device not in correct state (%d)", + nm_device_get_state(NM_DEVICE(self))); + return FALSE; + } + + priv->modem = nm_modem_claim(modem); + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT; + + g_signal_connect(modem, NM_MODEM_PPP_STATS, G_CALLBACK(ppp_stats), self); + g_signal_connect(modem, NM_MODEM_PPP_FAILED, G_CALLBACK(ppp_failed), self); + g_signal_connect(modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK(modem_prepare_result), self); + g_signal_connect(modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK(modem_ip4_config_result), self); + g_signal_connect(modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK(modem_auth_requested), self); + g_signal_connect(modem, NM_MODEM_AUTH_RESULT, G_CALLBACK(modem_auth_result), self); + g_signal_connect(modem, NM_MODEM_STATE_CHANGED, G_CALLBACK(modem_state_cb), self); + g_signal_connect(modem, NM_MODEM_REMOVED, G_CALLBACK(modem_removed_cb), self); + g_signal_connect(modem, + "notify::" NM_MODEM_IP_IFINDEX, + G_CALLBACK(ip_ifindex_changed_cb), + self); + + _LOGD(LOGD_BT | LOGD_MB, "modem found"); + + return TRUE; +} + +static void +mm_modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data) +{ + NMDeviceBt * self = user_data; + NMDeviceBtPrivate *priv; + + if (!modem_try_claim(user_data, modem)) + return; + + priv = NM_DEVICE_BT_GET_PRIVATE(self); + + if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED) + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); +} + +/*****************************************************************************/ + +void +_nm_device_bt_notify_set_connected(NMDeviceBt *self, gboolean connected) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + connected = !!connected; + if (priv->is_connected == connected) + return; + + priv->is_connected = connected; + + if (connected || priv->stage1_bt_state != NM_DEVICE_STAGE_STATE_COMPLETED + || nm_device_get_state(NM_DEVICE(self)) > NM_DEVICE_STATE_ACTIVATED) { + _LOGT(LOGD_BT, "set-connected: %d", connected); + return; + } + + _LOGT(LOGD_BT, "set-connected: %d (disconnecting device...)", connected); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CARRIER); +} + +static gboolean +connect_watch_link_idle_cb(gpointer user_data) +{ + NMDeviceBt * self = user_data; + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + int ifindex; + + priv->connect_watch_link_idle_id = 0; + + if (nm_device_get_state(NM_DEVICE(self)) <= NM_DEVICE_STATE_ACTIVATED) { + ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self)); + if (ifindex > 0 + && !nm_platform_link_get(nm_device_get_platform(NM_DEVICE(self)), ifindex)) { + _LOGT(LOGD_BT, "device disappeared"); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + } + } + + return G_SOURCE_REMOVE; +} + +static void +connect_watch_link_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *info, + int change_type_i, + NMDevice * self) +{ + const NMPlatformSignalChangeType change_type = change_type_i; + NMDeviceBtPrivate * priv; + + /* bluez doesn't notify us when the connection disconnects. + * Neither does NMManager (or NMDevice) tell us when the ip-ifindex goes away. + * This is horrible, and should be improved. For now, watch the link ourself... */ + + if (NM_IN_SET(change_type, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED)) { + priv = NM_DEVICE_BT_GET_PRIVATE(self); + if (priv->connect_watch_link_idle_id == 0) + priv->connect_watch_link_idle_id = g_idle_add(connect_watch_link_idle_cb, self); + } +} + +static gboolean +connect_wait_modem_timeout(gpointer user_data) +{ + NMDeviceBt * self = NM_DEVICE_BT(user_data); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + /* since this timeout is longer than the connect timeout, we must have already + * hit the connect-timeout first or being connected. */ + nm_assert(priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED); + + priv->connect_wait_modem_id = 0; + nm_clear_g_cancellable(&priv->connect_bz_cancellable); + + if (priv->modem) + _LOGD(LOGD_BT, "timeout connecting modem for DUN connection"); + else + _LOGD(LOGD_BT, "timeout finding modem for DUN connection"); + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND); + return G_SOURCE_REMOVE; +} + +static void +connect_bz_cb(NMBluezManager *bz_mgr, + gboolean is_complete, + const char * device_name, + GError * error, + gpointer user_data) +{ + NMDeviceBt * self; + NMDeviceBtPrivate *priv; + char sbuf[100]; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_DEVICE_BT_GET_PRIVATE(self); + + nm_assert(nm_device_is_activating(NM_DEVICE(self))); + nm_assert(NM_IN_SET((NMBluetoothCapabilities) priv->connect_bt_type, + NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP)); + + if (!is_complete) { + nm_assert(priv->connect_bt_type == NM_BT_CAPABILITY_DUN); + nm_assert(device_name); + nm_assert(!error); + + if (!nm_streq0(priv->connect_rfcomm_iface, device_name)) { + nm_assert(!priv->connect_rfcomm_iface); + _LOGD(LOGD_BT, + "DUN is still connecting but got serial port \"%s\" to claim modem", + device_name); + g_free(priv->connect_rfcomm_iface); + priv->connect_rfcomm_iface = g_strdup(device_name); + } + return; + } + + g_clear_object(&priv->connect_bz_cancellable); + + if (!device_name) { + _LOGW(LOGD_BT, + "%s connect request failed: %s", + nm_bluetooth_capability_to_string(priv->connect_bt_type, sbuf, sizeof(sbuf)), + error->message); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + _LOGD(LOGD_BT, + "%s connect request successful (%s)", + nm_bluetooth_capability_to_string(priv->connect_bt_type, sbuf, sizeof(sbuf)), + device_name); + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (!nm_streq0(priv->connect_rfcomm_iface, device_name)) { + nm_assert_not_reached(); + g_free(priv->connect_rfcomm_iface); + priv->connect_rfcomm_iface = g_strdup(device_name); + } + } else { + nm_assert(priv->connect_bt_type == NM_BT_CAPABILITY_NAP); + if (!nm_device_set_ip_iface(NM_DEVICE(self), device_name)) { + _LOGW(LOGD_BT, "Error connecting with bluez: cannot find device %s", device_name); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + priv->connect_watch_link_id = g_signal_connect(nm_device_get_platform(NM_DEVICE(self)), + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(connect_watch_link_cb), + self); + } + + if (!priv->is_connected) { + /* we got the callback from NMBluezManager with success. We actually should be + * connected and this line shouldn't be reached. */ + nm_assert_not_reached(); + _LOGE(LOGD_BT, "bluetooth is unexpectedly not in connected state"); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBt * self = NM_DEVICE_BT(device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + NMConnection * connection; + + connection = nm_device_get_applied_connection(device); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + priv->connect_bt_type = get_connection_bt_type(connection); + if (priv->connect_bt_type == NM_BT_CAPABILITY_NONE) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_BT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + else if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_INIT) { + gs_unref_object GCancellable *cancellable = NULL; + char sbuf[100]; + + _LOGD(LOGD_BT, + "connecting to %s bluetooth device", + nm_bluetooth_capability_to_string(priv->connect_bt_type, sbuf, sizeof(sbuf))); + + cancellable = g_cancellable_new(); + + if (!nm_bluez_manager_connect(priv->bz_mgr, + priv->dbus_path, + priv->connect_bt_type, + 30000, + cancellable, + connect_bz_cb, + self, + &error)) { + _LOGD(LOGD_BT, "cannot connect to bluetooth device: %s", error->message); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv->connect_bz_cancellable = g_steal_pointer(&cancellable); + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_PENDING; + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (!priv->modem) { + gs_free NMModem **modems = NULL; + guint i, n; + + if (priv->connect_wait_modem_id == 0) + priv->connect_wait_modem_id = + g_timeout_add_seconds(30, connect_wait_modem_timeout, self); + + modems = nm_modem_manager_get_modems(priv->modem_manager, &n); + for (i = 0; i < n; i++) { + if (modem_try_claim(self, modems[i])) + break; + } + if (!priv->modem) + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_INIT) { + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_PENDING; + return nm_modem_act_stage1_prepare(priv->modem, + nm_device_get_act_request(NM_DEVICE(self)), + out_failure_reason); + } + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBt * self = NM_DEVICE_BT(device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) + nm_modem_act_stage2_config(priv->modem); + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device); + + nm_assert_addr_family(addr_family); + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (addr_family == AF_INET) { + return nm_modem_stage3_ip4_config_start(priv->modem, + device, + NM_DEVICE_CLASS(nm_device_bt_parent_class), + out_failure_reason); + } else { + return nm_modem_stage3_ip6_config_start(priv->modem, device, out_failure_reason); + } + } + + return NM_DEVICE_CLASS(nm_device_bt_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device); + + nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->connect_watch_link_id); + nm_clear_g_source(&priv->connect_watch_link_idle_id); + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT; + nm_clear_g_source(&priv->connect_wait_modem_id); + nm_clear_g_cancellable(&priv->connect_bz_cancellable); + + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT; + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (priv->modem) { + nm_modem_deactivate(priv->modem, device); + + /* Since we're killing the Modem object before it'll get the + * state change signal, simulate the state change here. + */ + nm_modem_device_state_changed(priv->modem, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_ACTIVATED); + modem_cleanup(NM_DEVICE_BT(device)); + } + } + + if (priv->connect_bt_type != NM_BT_CAPABILITY_NONE) { + priv->connect_bt_type = NM_BT_CAPABILITY_NONE; + nm_bluez_manager_disconnect(priv->bz_mgr, priv->dbus_path); + } + + nm_clear_g_free(&priv->connect_rfcomm_iface); + + if (NM_DEVICE_CLASS(nm_device_bt_parent_class)->deactivate) + NM_DEVICE_CLASS(nm_device_bt_parent_class)->deactivate(device); +} + +void +_nm_device_bt_notify_removed(NMDeviceBt *self) +{ + g_signal_emit_by_name(self, NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +gboolean +_nm_device_bt_for_same_device(NMDeviceBt * self, + const char * dbus_path, + const char * bdaddr, + const char * name, + NMBluetoothCapabilities capabilities) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + return nm_streq(priv->dbus_path, dbus_path) && nm_streq(priv->bdaddr, bdaddr) + && capabilities == priv->capabilities && (!name || nm_streq(priv->name, name)); +} + +void +_nm_device_bt_notify_set_name(NMDeviceBt *self, const char *name) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + nm_assert(name); + + if (!nm_streq(priv->name, name)) { + _LOGT(LOGD_BT, "set-name: %s", name); + g_free(priv->name); + priv->name = g_strdup(name); + _notify(self, PROP_BT_NAME); + } +} + +/*****************************************************************************/ + +static gboolean +is_available(NMDevice *dev, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceBt * self = NM_DEVICE_BT(dev); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + /* PAN doesn't need ModemManager, so devices that support it are always available */ + if (priv->capabilities & NM_BT_CAPABILITY_NAP) + return TRUE; + + /* DUN requires ModemManager */ + return priv->mm_running; +} + +static void +set_mm_running(NMDeviceBt *self) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + gboolean running; + + running = (nm_modem_manager_name_owner_get(priv->modem_manager) != NULL); + + if (priv->mm_running != running) { + _LOGD(LOGD_BT, "ModemManager now %s", running ? "available" : "unavailable"); + + priv->mm_running = running; + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE); + } +} + +static void +mm_name_owner_changed_cb(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + set_mm_running(user_data); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_BT_NAME: + g_value_set_string(value, priv->name); + break; + case PROP_BT_CAPABILITIES: + g_value_set_uint(value, priv->capabilities); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_BT_BZ_MGR: + /* construct-only */ + priv->bz_mgr = g_object_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_BLUEZ_MANAGER(priv->bz_mgr)); + break; + case PROP_BT_DBUS_PATH: + /* construct-only */ + priv->dbus_path = g_value_dup_string(value); + nm_assert(priv->dbus_path); + break; + case PROP_BT_BDADDR: + /* construct-only */ + priv->bdaddr = g_value_dup_string(value); + nm_assert(priv->bdaddr); + break; + case PROP_BT_NAME: + /* construct-only */ + priv->name = g_value_dup_string(value); + nm_assert(priv->name); + break; + case PROP_BT_CAPABILITIES: + /* construct-only */ + priv->capabilities = g_value_get_uint(value); + nm_assert(NM_IN_SET((NMBluetoothCapabilities) priv->capabilities, + NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP, + NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_bt_init(NMDeviceBt *self) +{} + +static void +constructed(GObject *object) +{ + NMDeviceBt * self = NM_DEVICE_BT(object); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_device_bt_parent_class)->constructed(object); + + priv->modem_manager = g_object_ref(nm_modem_manager_get()); + + nm_modem_manager_name_owner_ref(priv->modem_manager); + + g_signal_connect(priv->modem_manager, + NM_MODEM_MANAGER_MODEM_ADDED, + G_CALLBACK(mm_modem_added_cb), + self); + + g_signal_connect(priv->modem_manager, + "notify::" NM_MODEM_MANAGER_NAME_OWNER, + G_CALLBACK(mm_name_owner_changed_cb), + self); + + set_mm_running(self); +} + +NMDeviceBt * +nm_device_bt_new(NMBluezManager * bz_mgr, + const char * dbus_path, + const char * bdaddr, + const char * name, + NMBluetoothCapabilities capabilities) +{ + g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(bz_mgr), NULL); + g_return_val_if_fail(dbus_path, NULL); + g_return_val_if_fail(bdaddr, NULL); + g_return_val_if_fail(name, NULL); + g_return_val_if_fail(capabilities != NM_BT_CAPABILITY_NONE, NULL); + + return g_object_new(NM_TYPE_DEVICE_BT, + NM_DEVICE_UDI, + dbus_path, + NM_DEVICE_IFACE, + bdaddr, + NM_DEVICE_DRIVER, + "bluez", + NM_DEVICE_PERM_HW_ADDRESS, + bdaddr, + NM_DEVICE_BT_BDADDR, + bdaddr, + NM_DEVICE_BT_BZ_MGR, + bz_mgr, + NM_DEVICE_BT_CAPABILITIES, + (guint) capabilities, + NM_DEVICE_BT_DBUS_PATH, + dbus_path, + NM_DEVICE_BT_NAME, + name, + NM_DEVICE_TYPE_DESC, + "Bluetooth", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_BT, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceBt * self = NM_DEVICE_BT(object); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self); + + nm_clear_g_signal_handler(nm_device_get_platform(NM_DEVICE(self)), + &priv->connect_watch_link_id); + nm_clear_g_source(&priv->connect_watch_link_idle_id); + + nm_clear_g_source(&priv->connect_wait_modem_id); + nm_clear_g_cancellable(&priv->connect_bz_cancellable); + + if (priv->modem_manager) { + g_signal_handlers_disconnect_by_func(priv->modem_manager, + G_CALLBACK(mm_name_owner_changed_cb), + self); + g_signal_handlers_disconnect_by_func(priv->modem_manager, + G_CALLBACK(mm_modem_added_cb), + self); + nm_modem_manager_name_owner_unref(priv->modem_manager); + g_clear_object(&priv->modem_manager); + } + + modem_cleanup(self); + + G_OBJECT_CLASS(nm_device_bt_parent_class)->dispose(object); + + g_clear_object(&priv->bz_mgr); +} + +static void +finalize(GObject *object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(object); + + g_free(priv->connect_rfcomm_iface); + g_free(priv->dbus_path); + g_free(priv->name); + g_free(priv->bdaddr); + + G_OBJECT_CLASS(nm_device_bt_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_bluetooth = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_BLUETOOTH, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Name", "s", NM_DEVICE_BT_NAME), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("BtCapabilities", + "u", + NM_DEVICE_BT_CAPABILITIES), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_bt_class_init(NMDeviceBtClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bluetooth); + + device_class->connection_type_check_compatible = NM_SETTING_BLUETOOTH_SETTING_NAME; + + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->can_auto_connect = can_auto_connect; + device_class->deactivate = deactivate; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->is_available = is_available; + device_class->get_configured_mtu = nm_modem_get_configured_mtu; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_BT_BZ_MGR] = + g_param_spec_pointer(NM_DEVICE_BT_BZ_MGR, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_BDADDR] = + g_param_spec_string(NM_DEVICE_BT_BDADDR, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_DBUS_PATH] = + g_param_spec_string(NM_DEVICE_BT_DBUS_PATH, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_NAME] = + g_param_spec_string(NM_DEVICE_BT_NAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_BT_CAPABILITIES, + "", + "", + NM_BT_CAPABILITY_NONE, + G_MAXUINT, + NM_BT_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[PPP_STATS] = g_signal_new(NM_DEVICE_BT_PPP_STATS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT /*guint32 in_bytes*/, + G_TYPE_UINT /*guint32 out_bytes*/); +} diff --git a/src/core/devices/bluetooth/nm-device-bt.h b/src/core/devices/bluetooth/nm-device-bt.h new file mode 100644 index 0000000..c2d3bc1 --- /dev/null +++ b/src/core/devices/bluetooth/nm-device-bt.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_BT_H__ +#define __NETWORKMANAGER_DEVICE_BT_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_DEVICE_BT (nm_device_bt_get_type()) +#define NM_DEVICE_BT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_BT, NMDeviceBt)) +#define NM_DEVICE_BT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_BT, NMDeviceBtClass)) +#define NM_IS_DEVICE_BT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_BT)) +#define NM_IS_DEVICE_BT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_BT)) +#define NM_DEVICE_BT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_BT, NMDeviceBtClass)) + +#define NM_DEVICE_BT_BDADDR "bt-bdaddr" +#define NM_DEVICE_BT_BZ_MGR "bt-bz-mgr" +#define NM_DEVICE_BT_CAPABILITIES "bt-capabilities" +#define NM_DEVICE_BT_DBUS_PATH "bt-dbus-path" +#define NM_DEVICE_BT_NAME "bt-name" + +#define NM_DEVICE_BT_PPP_STATS "ppp-stats" + +typedef struct _NMDeviceBt NMDeviceBt; +typedef struct _NMDeviceBtClass NMDeviceBtClass; + +GType nm_device_bt_get_type(void); + +struct _NMBluezManager; + +NMDeviceBt *nm_device_bt_new(struct _NMBluezManager *bz_mgr, + const char * dbus_path, + const char * bdaddr, + const char * name, + NMBluetoothCapabilities capabilities); + +gboolean _nm_device_bt_for_same_device(NMDeviceBt * device, + const char * dbus_path, + const char * bdaddr, + const char * name, + NMBluetoothCapabilities capabilities); + +NMBluetoothCapabilities nm_device_bt_get_capabilities(NMDeviceBt *device); + +struct _NMModem; + +gboolean nm_device_bt_modem_added(NMDeviceBt *device, struct _NMModem *modem, const char *driver); + +void _nm_device_bt_notify_removed(NMDeviceBt *self); + +void _nm_device_bt_notify_set_name(NMDeviceBt *self, const char *name); + +void _nm_device_bt_notify_set_connected(NMDeviceBt *self, gboolean connected); + +#endif /* __NETWORKMANAGER_DEVICE_BT_H__ */ diff --git a/src/core/devices/bluetooth/tests/nm-bt-test.c b/src/core/devices/bluetooth/tests/nm-bt-test.c new file mode 100644 index 0000000..546c06a --- /dev/null +++ b/src/core/devices/bluetooth/tests/nm-bt-test.c @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include + +#include "devices/bluetooth/nm-bluez5-dun.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG(level, ...) \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "bt%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_PRINT_FMT_QUOTED(gl.argv_cmd, "[", gl.argv_cmd, "]", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +struct { + int argc; + const char *const *argv; + const char * argv_cmd; + GMainLoop * loop; +} gl; + +typedef struct _MainCmdInfo { + const char *name; + int (*main_func)(const struct _MainCmdInfo *main_cmd_info); +} MainCmdInfo; + +/*****************************************************************************/ + +#if WITH_BLUEZ5_DUN + +typedef struct { + NMBluez5DunContext *dun_context; + GCancellable * cancellable; + guint timeout_id; + guint sig_term_id; + guint sig_int_id; +} DunConnectData; + +static void +_dun_connect_cb(NMBluez5DunContext *context, + const char * rfcomm_dev, + GError * error, + gpointer user_data) +{ + DunConnectData *dun_connect_data = user_data; + + g_assert(dun_connect_data); + g_assert(!dun_connect_data->dun_context); + g_assert((!!error) != (!!rfcomm_dev)); + + if (rfcomm_dev && !context) { + _LOGI("dun-connect notifies path \"%s\". Wait longer...", rfcomm_dev); + return; + } + + if (rfcomm_dev) { + g_assert(context); + _LOGI("dun-connect completed with path \"%s\"", rfcomm_dev); + } else { + g_assert(!context); + _LOGI("dun-connect failed with error: %s", error->message); + } + + dun_connect_data->dun_context = context; + + g_main_loop_quit(gl.loop); +} + +static void +_dun_notify_tty_hangup_cb(NMBluez5DunContext *context, gpointer user_data) +{ + _LOGI("dun-connect: notified TTY hangup"); +} + +static gboolean +_timeout_cb(gpointer user_data) +{ + DunConnectData *dun_connect_data = user_data; + + _LOGI("timeout"); + dun_connect_data->timeout_id = 0; + if (dun_connect_data->cancellable) + g_cancellable_cancel(dun_connect_data->cancellable); + return G_SOURCE_REMOVE; +} + +static gboolean +_sig_xxx_cb(DunConnectData *dun_connect_data, int sigid) +{ + _LOGI("signal %s received", sigid == SIGTERM ? "SIGTERM" : "SIGINT"); + g_main_loop_quit(gl.loop); + return G_SOURCE_CONTINUE; +} + +static gboolean +_sig_term_cb(gpointer user_data) +{ + return _sig_xxx_cb(user_data, SIGTERM); +} + +static gboolean +_sig_int_cb(gpointer user_data) +{ + return _sig_xxx_cb(user_data, SIGINT); +} +#endif + +static int +do_dun_connect(const MainCmdInfo *main_cmd_info) +{ +#if WITH_BLUEZ5_DUN + gs_unref_object GCancellable *cancellable = NULL; + gs_free_error GError *error = NULL; + const char * adapter; + const char * remote; + DunConnectData dun_connect_data = {}; + + if (gl.argc < 4) { + _LOGE("missing arguments \"adapter\" and \"remote\""); + return -1; + } + + adapter = gl.argv[2]; + remote = gl.argv[3]; + + cancellable = g_cancellable_new(); + dun_connect_data.cancellable = cancellable; + + if (!nm_bluez5_dun_connect(adapter, + remote, + cancellable, + _dun_connect_cb, + &dun_connect_data, + _dun_notify_tty_hangup_cb, + &dun_connect_data, + &error)) { + _LOGE("connect failed to start: %s", error->message); + return -1; + } + + dun_connect_data.timeout_id = g_timeout_add(60000, _timeout_cb, &dun_connect_data); + + g_main_loop_run(gl.loop); + + nm_clear_g_source(&dun_connect_data.timeout_id); + + if (dun_connect_data.dun_context) { + dun_connect_data.sig_term_id = g_unix_signal_add(SIGTERM, _sig_term_cb, &dun_connect_data); + dun_connect_data.sig_int_id = g_unix_signal_add(SIGINT, _sig_int_cb, &dun_connect_data); + + g_main_loop_run(gl.loop); + + nm_clear_g_source(&dun_connect_data.sig_term_id); + nm_clear_g_source(&dun_connect_data.sig_int_id); + + nm_bluez5_dun_disconnect(g_steal_pointer(&dun_connect_data.dun_context)); + } + + return 0; +#else + _LOGE("compiled without bluetooth DUN support"); + return 1; +#endif +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + static const MainCmdInfo main_cmd_infos[] = { + { + .name = "dun-connect", + .main_func = do_dun_connect, + }, + }; + int exit_code = 0; + guint i; + + if (!g_getenv("G_MESSAGES_DEBUG")) + g_setenv("G_MESSAGES_DEBUG", "all", TRUE); + + nmtst_init_with_logging(&argc, &argv, "DEBUG", "ALL"); + + nm_logging_init(NULL, TRUE); + + gl.argv = (const char *const *) argv; + gl.argc = argc; + gl.loop = g_main_loop_new(NULL, FALSE); + + _LOGI("bluetooth test util start"); + + gl.argv_cmd = argc >= 2 ? argv[1] : NULL; + + for (i = 0; i < G_N_ELEMENTS(main_cmd_infos); i++) { + if (nm_streq0(main_cmd_infos[i].name, gl.argv_cmd)) { + _LOGD("start \"%s\"", gl.argv_cmd); + exit_code = main_cmd_infos[i].main_func(&main_cmd_infos[i]); + _LOGD("completed with %d", exit_code); + break; + } + } + if (gl.argv_cmd && i >= G_N_ELEMENTS(main_cmd_infos)) { + nm_log_err(LOGD_BT, "invalid command \"%s\"", gl.argv_cmd); + exit_code = -1; + } + + nm_clear_pointer(&gl.loop, g_main_loop_unref); + + return exit_code; +} diff --git a/src/core/devices/meson.build b/src/core/devices/meson.build new file mode 100644 index 0000000..66f7aa9 --- /dev/null +++ b/src/core/devices/meson.build @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +subdir('adsl') + +if enable_modem_manager + subdir('wwan') + subdir('bluetooth') +endif + +if enable_wifi + subdir('wifi') +endif + +if enable_teamdctl + subdir('team') +endif + +if enable_ovs + subdir('ovs') +endif + +if enable_tests + subdir('tests') +endif diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c new file mode 100644 index 0000000..82b7146 --- /dev/null +++ b/src/core/devices/nm-acd-manager.c @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-acd-manager.h" + +#include +#include +#include +#include + +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "n-acd/src/n-acd.h" + +/*****************************************************************************/ + +typedef enum { + STATE_INIT, + STATE_PROBING, + STATE_PROBE_DONE, + STATE_ANNOUNCING, +} State; + +typedef struct { + in_addr_t address; + gboolean duplicate; + NAcdProbe *probe; +} AddressInfo; + +struct _NMAcdManager { + int ifindex; + guint8 hwaddr[ETH_ALEN]; + State state; + GHashTable *addresses; + guint completed; + NAcd * acd; + GSource * event_source; + + NMAcdCallbacks callbacks; + gpointer user_data; +}; + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_IP4 +#define _NMLOG_PREFIX_NAME "acd" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _sbuf[64]; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + self && self->ifindex > 0 \ + ? nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex) \ + : NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + self ? nm_sprintf_buf(_sbuf, "[%p,%d]", self, self->ifindex) \ + : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static const char * +_acd_event_to_string(unsigned int event) +{ + switch (event) { + case N_ACD_EVENT_READY: + return "ready"; + case N_ACD_EVENT_USED: + return "used"; + case N_ACD_EVENT_DEFENDED: + return "defended"; + case N_ACD_EVENT_CONFLICT: + return "conflict"; + case N_ACD_EVENT_DOWN: + return "down"; + } + return NULL; +} + +#define ACD_EVENT_TO_STRING_BUF_SIZE 50 + +static const char * +_acd_event_to_string_buf(unsigned event, char buffer[static ACD_EVENT_TO_STRING_BUF_SIZE]) +{ + const char *s; + + s = _acd_event_to_string(event); + if (s) + return s; + + g_snprintf(buffer, ACD_EVENT_TO_STRING_BUF_SIZE, "(%u)", event); + return buffer; +} + +static const char * +acd_error_to_string(int error) +{ + if (error < 0) + return nm_strerror_native(-error); + + switch (error) { + case _N_ACD_E_SUCCESS: + return "success"; + case N_ACD_E_PREEMPTED: + return "preempted"; + case N_ACD_E_INVALID_ARGUMENT: + return "invalid argument"; + } + + g_return_val_if_reached(NULL); +} + +static int +acd_error_to_nmerr(int error, gboolean always_fail) +{ + if (error < 0) + return -nm_errno_native(error); + + if (always_fail) { + if (NM_IN_SET(error, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT)) + return -NME_UNSPEC; + g_return_val_if_reached(-NME_UNSPEC); + } + + /* so, @error is either zero (indicating success) or one + * of the special status codes like N_ACD_E_*. In both cases, + * return the positive value here. */ + if (NM_IN_SET(error, _N_ACD_E_SUCCESS, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT)) + return error; + + g_return_val_if_reached(error); +} + +/*****************************************************************************/ + +/** + * nm_acd_manager_add_address: + * @self: a #NMAcdManager + * @address: an IP address + * + * Add @address to the list of IP addresses to probe. + + * Returns: %TRUE on success, %FALSE if the address was already in the list + */ +gboolean +nm_acd_manager_add_address(NMAcdManager *self, in_addr_t address) +{ + AddressInfo *info; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(self->state == STATE_INIT, FALSE); + + if (g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address))) + return FALSE; + + info = g_slice_new0(AddressInfo); + info->address = address; + + g_hash_table_insert(self->addresses, GUINT_TO_POINTER(address), info); + + return TRUE; +} + +static gboolean +acd_event(int fd, GIOCondition condition, gpointer data) +{ + NMAcdManager *self = data; + NAcdEvent * event; + AddressInfo * info; + gboolean emit_probe_terminated = FALSE; + char address_str[INET_ADDRSTRLEN]; + int r; + + if (n_acd_dispatch(self->acd)) + return G_SOURCE_CONTINUE; + + while (!n_acd_pop_event(self->acd, &event) && event) { + char to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE]; + gs_free char *hwaddr_str = NULL; + gboolean check_probing_done = FALSE; + + switch (event->event) { + case N_ACD_EVENT_READY: + n_acd_probe_get_userdata(event->ready.probe, (void **) &info); + info->duplicate = FALSE; + if (self->state == STATE_ANNOUNCING) { + /* fake probe ended, start announcing */ + r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE); + if (r) { + _LOGW("couldn't announce address %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, address_str), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + } else { + _LOGD("announcing address %s", + _nm_utils_inet4_ntop(info->address, address_str)); + } + } + check_probing_done = TRUE; + break; + case N_ACD_EVENT_USED: + n_acd_probe_get_userdata(event->used.probe, (void **) &info); + info->duplicate = TRUE; + check_probing_done = TRUE; + break; + case N_ACD_EVENT_DEFENDED: + n_acd_probe_get_userdata(event->defended.probe, (void **) &info); + _LOGD("defended address %s from host %s", + _nm_utils_inet4_ntop(info->address, address_str), + (hwaddr_str = + nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender))); + break; + case N_ACD_EVENT_CONFLICT: + n_acd_probe_get_userdata(event->conflict.probe, (void **) &info); + _LOGW("conflict for address %s detected with host %s on interface '%s'", + _nm_utils_inet4_ntop(info->address, address_str), + (hwaddr_str = + nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender)), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex)); + break; + default: + _LOGD("unhandled event '%s'", _acd_event_to_string_buf(event->event, to_string_buffer)); + break; + } + + if (check_probing_done && self->state == STATE_PROBING + && ++self->completed == g_hash_table_size(self->addresses)) { + self->state = STATE_PROBE_DONE; + emit_probe_terminated = TRUE; + } + } + + if (emit_probe_terminated) { + if (self->callbacks.probe_terminated_callback) { + self->callbacks.probe_terminated_callback(self, self->user_data); + } + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +acd_probe_add(NMAcdManager *self, AddressInfo *info, guint64 timeout) +{ + NAcdProbeConfig *probe_config; + int r; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + r = n_acd_probe_config_new(&probe_config); + if (r) { + _LOGW("could not create probe config for %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return FALSE; + } + + n_acd_probe_config_set_ip(probe_config, (struct in_addr){info->address}); + n_acd_probe_config_set_timeout(probe_config, timeout); + + r = n_acd_probe(self->acd, &info->probe, probe_config); + if (r) { + _LOGW("could not start probe for %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + n_acd_probe_config_free(probe_config); + return FALSE; + } + + n_acd_probe_set_userdata(info->probe, info); + n_acd_probe_config_free(probe_config); + + return TRUE; +} + +static int +acd_init(NMAcdManager *self) +{ + NAcdConfig *config; + int r; + + if (self->acd) + return 0; + + r = n_acd_config_new(&config); + if (r) + return r; + + n_acd_config_set_ifindex(config, self->ifindex); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, self->hwaddr, ETH_ALEN); + + r = n_acd_new(&self->acd, config); + n_acd_config_free(config); + return r; +} + +/** + * nm_acd_manager_start_probe: + * @self: a #NMAcdManager + * @timeout: maximum probe duration in milliseconds + * @error: location to store error, or %NULL + * + * Start probing IP addresses for duplicates; when the probe terminates a + * PROBE_TERMINATED signal is emitted. + * + * Returns: 0 on success or a negative NetworkManager error code (NME_*). + */ +int +nm_acd_manager_start_probe(NMAcdManager *self, guint timeout) +{ + GHashTableIter iter; + AddressInfo * info; + gboolean success = FALSE; + int fd, r; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(self->state == STATE_INIT, FALSE); + + r = acd_init(self); + if (r) { + _LOGW("couldn't init ACD for probing on interface '%s': %s", + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return acd_error_to_nmerr(r, TRUE); + } + + self->completed = 0; + + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) + success |= acd_probe_add(self, info, timeout); + + if (success) + self->state = STATE_PROBING; + + nm_assert(!self->event_source); + n_acd_get_fd(self->acd, &fd); + self->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL); + g_source_attach(self->event_source, NULL); + + return success ? 0 : -NME_UNSPEC; +} + +/** + * nm_acd_manager_check_address: + * @self: a #NMAcdManager + * @address: an IP address + * + * Check if an IP address is duplicate. @address must have been added with + * nm_acd_manager_add_address(). + * + * Returns: %TRUE if the address is not duplicate, %FALSE otherwise + */ +gboolean +nm_acd_manager_check_address(NMAcdManager *self, in_addr_t address) +{ + AddressInfo *info; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(NM_IN_SET(self->state, STATE_INIT, STATE_PROBE_DONE), FALSE); + + info = g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address)); + g_return_val_if_fail(info, FALSE); + + return !info->duplicate; +} + +/** + * nm_acd_manager_announce_addresses: + * @self: a #NMAcdManager + * + * Start announcing addresses. + * + * Returns: a negative NetworkManager error number or zero on success. + */ +int +nm_acd_manager_announce_addresses(NMAcdManager *self) +{ + GHashTableIter iter; + AddressInfo * info; + int r; + int fd; + gboolean success = TRUE; + + r = acd_init(self); + if (r) { + _LOGW("couldn't init ACD for announcing addresses on interface '%s': %s", + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return acd_error_to_nmerr(r, TRUE); + } + + if (self->state == STATE_INIT) { + /* n-acd can't announce without probing, therefore let's + * start a fake probe with zero timeout and then perform + * the announcement. */ + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) { + if (!acd_probe_add(self, info, 0)) + success = FALSE; + } + self->state = STATE_ANNOUNCING; + } else if (self->state == STATE_ANNOUNCING) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) { + if (info->duplicate) + continue; + r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE); + if (r) { + _LOGW("couldn't announce address %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + success = FALSE; + } else + _LOGD("announcing address %s", _nm_utils_inet4_ntop(info->address, sbuf)); + } + } + + if (!self->event_source) { + n_acd_get_fd(self->acd, &fd); + self->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL); + g_source_attach(self->event_source, NULL); + } + + return success ? 0 : -NME_UNSPEC; +} + +static void +destroy_address_info(gpointer data) +{ + AddressInfo *info = (AddressInfo *) data; + + n_acd_probe_free(info->probe); + + g_slice_free(AddressInfo, info); +} + +/*****************************************************************************/ + +NMAcdManager * +nm_acd_manager_new(int ifindex, + const guint8 * hwaddr, + guint hwaddr_len, + const NMAcdCallbacks *callbacks, + gpointer user_data) +{ + NMAcdManager *self; + + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(hwaddr, NULL); + g_return_val_if_fail(hwaddr_len == ETH_ALEN, NULL); + + self = g_slice_new0(NMAcdManager); + + if (callbacks) + self->callbacks = *callbacks; + self->user_data = user_data; + + self->addresses = g_hash_table_new_full(nm_direct_hash, NULL, NULL, destroy_address_info); + self->state = STATE_INIT; + self->ifindex = ifindex; + memcpy(self->hwaddr, hwaddr, ETH_ALEN); + return self; +} + +void +nm_acd_manager_free(NMAcdManager *self) +{ + g_return_if_fail(self); + + if (self->callbacks.user_data_destroy) + self->callbacks.user_data_destroy(self->user_data); + + nm_clear_pointer(&self->addresses, g_hash_table_destroy); + nm_clear_g_source_inst(&self->event_source); + nm_clear_pointer(&self->acd, n_acd_unref); + + g_slice_free(NMAcdManager, self); +} diff --git a/src/core/devices/nm-acd-manager.h b/src/core/devices/nm-acd-manager.h new file mode 100644 index 0000000..e8ef6f2 --- /dev/null +++ b/src/core/devices/nm-acd-manager.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_ACD_MANAGER__ +#define __NM_ACD_MANAGER__ + +#include + +typedef struct _NMAcdManager NMAcdManager; + +typedef struct { + void (*probe_terminated_callback)(NMAcdManager *self, gpointer user_data); + GDestroyNotify user_data_destroy; +} NMAcdCallbacks; + +NMAcdManager *nm_acd_manager_new(int ifindex, + const guint8 * hwaddr, + guint hwaddr_len, + const NMAcdCallbacks *callbacks, + gpointer user_data); + +void nm_acd_manager_free(NMAcdManager *self); + +gboolean nm_acd_manager_add_address(NMAcdManager *self, in_addr_t address); +int nm_acd_manager_start_probe(NMAcdManager *self, guint timeout); +gboolean nm_acd_manager_check_address(NMAcdManager *self, in_addr_t address); +int nm_acd_manager_announce_addresses(NMAcdManager *self); + +NM_AUTO_DEFINE_FCN0(NMAcdManager *, _nm_auto_free_acdmgr, nm_acd_manager_free); +#define nm_auto_free_acdmgr nm_auto(_nm_auto_free_acdmgr) + +#endif /* __NM_ACD_MANAGER__ */ diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c new file mode 100644 index 0000000..7c0be59 --- /dev/null +++ b/src/core/devices/nm-device-6lowpan.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-6lowpan.h" + +#include "nm-device-private.h" +#include "settings/nm-settings.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-6lowpan.h" +#include "nm-utils.h" + +#define _NMLOG_DEVICE_TYPE NMDevice6Lowpan +#include "nm-device-logging.h" + +/*****************************************************************************/ + +typedef struct { + gulong parent_state_id; +} NMDevice6LowpanPrivate; + +struct _NMDevice6Lowpan { + NMDevice parent; + NMDevice6LowpanPrivate _priv; +}; + +struct _NMDevice6LowpanClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDevice6Lowpan, nm_device_6lowpan, NM_TYPE_DEVICE) + +#define NM_DEVICE_6LOWPAN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDevice6Lowpan, NM_IS_DEVICE_6LOWPAN, NMDevice) + +/*****************************************************************************/ + +static void +parent_state_changed(NMDevice * parent, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDevice6Lowpan *self = NM_DEVICE_6LOWPAN(user_data); + + nm_device_set_unmanaged_by_flags(NM_DEVICE(self), + NM_UNMANAGED_PARENT, + !nm_device_get_managed(parent, FALSE), + reason); +} + +static void +parent_changed_notify(NMDevice *device, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + NMDevice6Lowpan * self = NM_DEVICE_6LOWPAN(device); + NMDevice6LowpanPrivate *priv = NM_DEVICE_6LOWPAN_GET_PRIVATE(self); + + NM_DEVICE_CLASS(nm_device_6lowpan_parent_class) + ->parent_changed_notify(device, old_ifindex, old_parent, new_ifindex, new_parent); + + /* note that @self doesn't have to clear @parent_state_id on dispose, + * because NMDevice's dispose() will unset the parent, which in turn calls + * parent_changed_notify(). */ + nm_clear_g_signal_handler(old_parent, &priv->parent_state_id); + + if (new_parent) { + priv->parent_state_id = g_signal_connect(new_parent, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(parent_state_changed), + device); + + /* Set parent-dependent unmanaged flag */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PARENT, + !nm_device_get_managed(new_parent, FALSE), + NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED); + } + + if (new_ifindex > 0) { + /* Recheck availability now that the parent has changed */ + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_PARENT_CHANGED, + NM_DEVICE_STATE_REASON_PARENT_CHANGED); + } +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSetting6Lowpan *s_6lowpan; + int parent_ifindex; + int r; + + s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); + g_return_val_if_fail(s_6lowpan, FALSE); + + parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0; + + if (parent_ifindex <= 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "6LoWPAN devices can not be created without a parent interface"); + g_return_val_if_fail(!parent, FALSE); + return FALSE; + } + + r = nm_platform_link_6lowpan_add(nm_device_get_platform(device), + iface, + parent_ifindex, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create 6lowpan interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + nm_device_parent_set_ifindex(device, parent_ifindex); + + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NMDevice6Lowpan *self = NM_DEVICE_6LOWPAN(device); + int parent = 0; + int ifindex; + + NM_DEVICE_CLASS(nm_device_6lowpan_parent_class)->link_changed(device, pllink); + + ifindex = nm_device_get_ifindex(device); + if (!nm_platform_link_6lowpan_get_properties(nm_device_get_platform(device), + ifindex, + &parent)) { + _LOGW(LOGD_DEVICE, "could not get 6lowpan properties"); + return; + } + + nm_device_parent_set_ifindex(device, parent); +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!nm_device_parent_get_device(device)) + return FALSE; + return NM_DEVICE_CLASS(nm_device_6lowpan_parent_class)->is_available(device, flags); +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSetting6Lowpan *s_6lowpan; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_6LOWPAN_SETTING_NAME, + existing_connections, + NULL, + _("6LOWPAN connection"), + NULL, + NULL, + TRUE); + + s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); + if (!s_6lowpan) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A '6lowpan' setting is required."); + return FALSE; + } + + /* If there's no 6LoWPAN interface, no parent, and no hardware address in the + * settings, then there's not enough information to complete the setting. + */ + if (!nm_setting_6lowpan_get_parent(s_6lowpan) + && !nm_device_match_parent_hwaddr(device, connection, TRUE)) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "The '6lowpan' setting had no interface name, parent, or hardware address."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMSetting6Lowpan *s_6lowpan = + NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); + + if (!s_6lowpan) { + s_6lowpan = (NMSetting6Lowpan *) nm_setting_6lowpan_new(); + nm_connection_add_setting(connection, (NMSetting *) s_6lowpan); + } + + g_object_set( + s_6lowpan, + NM_SETTING_6LOWPAN_PARENT, + nm_device_parent_find_for_connection(device, nm_setting_6lowpan_get_parent(s_6lowpan)), + NULL); +} + +/*****************************************************************************/ + +static void +nm_device_6lowpan_init(NMDevice6Lowpan *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_6lowpan = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_6LOWPAN, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT), ), ), +}; + +static void +nm_device_6lowpan_class_init(NMDevice6LowpanClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_6lowpan); + + device_class->connection_type_supported = NM_SETTING_6LOWPAN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_6LOWPAN_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_6LOWPAN); + + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->complete_connection = complete_connection; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + device_class->link_changed = link_changed; + device_class->is_available = is_available; + device_class->parent_changed_notify = parent_changed_notify; + device_class->update_connection = update_connection; +} + +/*****************************************************************************/ + +#define NM_TYPE_6LOWPAN_DEVICE_FACTORY (nm_6lowpan_device_factory_get_type()) +#define NM_6LOWPAN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_6LOWPAN_DEVICE_FACTORY, NM6LowpanDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_6LOWPAN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "6LoWPAN", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_6LOWPAN, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_6LOWPAN, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSetting6Lowpan *s_6lowpan; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL); + + s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); + g_assert(s_6lowpan); + + return nm_setting_6lowpan_get_parent(s_6lowpan); +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + NMSetting6Lowpan *s_6lowpan; + const char * ifname; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL); + + s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN)); + g_assert(s_6lowpan); + + if (!parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + return g_strdup(ifname); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + 6LOWPAN, + 6Lowpan, + 6lowpan, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_6LOWPAN) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_6LOWPAN_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-6lowpan.h b/src/core/devices/nm-device-6lowpan.h new file mode 100644 index 0000000..34ea3c8 --- /dev/null +++ b/src/core/devices/nm-device-6lowpan.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_6LOWPAN_H__ +#define __NETWORKMANAGER_DEVICE_6LOWPAN_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_6LOWPAN (nm_device_6lowpan_get_type()) +#define NM_DEVICE_6LOWPAN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_6LOWPAN, NMDevice6Lowpan)) +#define NM_DEVICE_6LOWPAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_6LOWPAN, NMDevice6LowpanClass)) +#define NM_IS_DEVICE_6LOWPAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_6LOWPAN)) +#define NM_IS_DEVICE_6LOWPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_6LOWPAN)) +#define NM_DEVICE_6LOWPAN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_6LOWPAN, NMDevice6LowpanClass)) + +typedef struct _NMDevice6Lowpan NMDevice6Lowpan; +typedef struct _NMDevice6LowpanClass NMDevice6LowpanClass; + +GType nm_device_6lowpan_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_6LOWPAN_H__ */ diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c new file mode 100644 index 0000000..5671a42 --- /dev/null +++ b/src/core/devices/nm-device-bond.c @@ -0,0 +1,651 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-bond.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-core-internal.h" +#include "nm-ip4-config.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceBond +#include "nm-device-logging.h" + +/*****************************************************************************/ + +#define OPTIONS_APPLY_SUBSET \ + NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, \ + NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_ARP_INTERVAL, \ + NM_SETTING_BOND_OPTION_ARP_VALIDATE, NM_SETTING_BOND_OPTION_PRIMARY, \ + NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, \ + NM_SETTING_BOND_OPTION_AD_SELECT, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, \ + NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, \ + NM_SETTING_BOND_OPTION_FAIL_OVER_MAC, NM_SETTING_BOND_OPTION_LACP_RATE, \ + NM_SETTING_BOND_OPTION_LP_INTERVAL, NM_SETTING_BOND_OPTION_MIN_LINKS, \ + NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ + NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, \ + NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, \ + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP + +#define OPTIONS_REAPPLY_SUBSET \ + NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, \ + NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_ARP_INTERVAL, \ + NM_SETTING_BOND_OPTION_ARP_VALIDATE, NM_SETTING_BOND_OPTION_PRIMARY, \ + NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, \ + NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, \ + NM_SETTING_BOND_OPTION_FAIL_OVER_MAC, NM_SETTING_BOND_OPTION_LP_INTERVAL, \ + NM_SETTING_BOND_OPTION_MIN_LINKS, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, \ + NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, NM_SETTING_BOND_OPTION_RESEND_IGMP, \ + NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, \ + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP + +#define OPTIONS_REAPPLY_FULL \ + OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \ + NM_SETTING_BOND_OPTION_ARP_IP_TARGET + +/*****************************************************************************/ + +struct _NMDeviceBond { + NMDevice parent; +}; + +struct _NMDeviceBondClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceBond, nm_device_bond, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingBond *s_bond; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_BOND_SETTING_NAME, + existing_connections, + NULL, + _("Bond connection"), + "bond", + NULL, + TRUE); + + s_bond = nm_connection_get_setting_bond(connection); + if (!s_bond) { + s_bond = (NMSettingBond *) nm_setting_bond_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bond)); + } + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +_set_bond_attr(NMDevice *device, const char *attr, const char *value) +{ + NMDeviceBond *self = NM_DEVICE_BOND(device); + int ifindex = nm_device_get_ifindex(device); + gboolean ret; + + ret = + nm_platform_sysctl_master_set_option(nm_device_get_platform(device), ifindex, attr, value); + if (!ret) + _LOGW(LOGD_PLATFORM, "failed to set bonding attribute '%s' to '%s'", attr, value); + return ret; +} + +#define _set_bond_attr_take(device, attr, value) \ + G_STMT_START \ + { \ + gs_free char *_tmp = (value); \ + \ + _set_bond_attr(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, _tmp); \ + } \ + G_STMT_END + +#define _set_bond_attr_printf(device, attr, fmt, ...) \ + _set_bond_attr_take((device), (attr), g_strdup_printf(fmt, __VA_ARGS__)) + +static gboolean +ignore_option(NMSettingBond *s_bond, const char *option, const char *value) +{ + const char *defvalue; + + if (nm_streq0(option, NM_SETTING_BOND_OPTION_MIIMON)) { + /* The default value for miimon, when missing in the setting, is + * 0 if arp_interval is != 0, and 100 otherwise. So, let's ignore + * miimon=0 (which means that miimon is disabled) and accept any + * other value. Adding miimon=100 does not cause any harm. + */ + defvalue = "0"; + } else + defvalue = nm_setting_bond_get_option_default(s_bond, option); + + return nm_streq0(value, defvalue); +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceBond * self = NM_DEVICE_BOND(device); + NMSettingBond *s_bond = nm_connection_get_setting_bond(connection); + int ifindex = nm_device_get_ifindex(device); + NMBondMode mode = NM_BOND_MODE_UNKNOWN; + const char ** options; + + if (!s_bond) { + s_bond = (NMSettingBond *) nm_setting_bond_new(); + nm_connection_add_setting(connection, (NMSetting *) s_bond); + } + + /* Read bond options from sysfs and update the Bond setting to match */ + options = nm_setting_bond_get_valid_options(NULL); + for (; options[0]; options++) { + const char * option = options[0]; + gs_free char *value = NULL; + char * p; + + if (NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE)) + continue; + + value = + nm_platform_sysctl_master_get_option(nm_device_get_platform(device), ifindex, option); + + if (value && _nm_setting_bond_get_option_type(s_bond, option) == NM_BOND_OPTION_TYPE_BOTH) { + p = strchr(value, ' '); + if (p) + *p = '\0'; + } + + if (mode == NM_BOND_MODE_UNKNOWN) { + if (value && nm_streq(option, NM_SETTING_BOND_OPTION_MODE)) + mode = _nm_setting_bond_mode_from_string(value); + if (mode == NM_BOND_MODE_UNKNOWN) + continue; + } + + if (!_nm_setting_bond_option_supported(option, mode)) + continue; + + if (value && value[0] && !ignore_option(s_bond, option, value)) { + /* Replace " " with "," for arp_ip_targets from the kernel */ + if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) { + for (p = value; *p; p++) { + if (*p == ' ') + *p = ','; + } + } + + if (!_nm_setting_bond_validate_option(option, value, NULL)) + _LOGT(LOGD_BOND, "cannot set invalid bond option '%s' = '%s'", option, value); + else + nm_setting_bond_add_option(s_bond, option, value); + } + } +} + +static gboolean +master_update_slave_connection(NMDevice * self, + NMDevice * slave, + NMConnection *connection, + GError ** error) +{ + g_object_set(nm_connection_get_setting_connection(connection), + NM_SETTING_CONNECTION_MASTER, + nm_device_get_iface(self), + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BOND_SETTING_NAME, + NULL); + return TRUE; +} + +static void +set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new_arp_ip_target) +{ + gs_unref_ptrarray GPtrArray *free_list = NULL; + gs_free const char ** cur_strv = NULL; + gs_free const char ** new_strv = NULL; + gsize cur_len; + gsize new_len; + gsize i; + gsize j; + + cur_strv = nm_utils_strsplit_set_full(cur_arp_ip_target, + NM_ASCII_SPACES, + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); + new_strv = nm_utils_bond_option_arp_ip_targets_split(new_arp_ip_target); + + cur_len = NM_PTRARRAY_LEN(cur_strv); + new_len = NM_PTRARRAY_LEN(new_strv); + + if (new_len > 0) { + for (j = 0, i = 0; i < new_len; i++) { + const char *s; + in_addr_t a4; + + s = new_strv[i]; + if (nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &a4)) { + char sbuf[INET_ADDRSTRLEN]; + + _nm_utils_inet4_ntop(a4, sbuf); + if (!nm_streq(s, sbuf)) { + if (!free_list) + free_list = g_ptr_array_new_with_free_func(g_free); + s = g_strdup(sbuf); + g_ptr_array_add(free_list, (gpointer) s); + } + } + + if (nm_utils_strv_find_first((char **) new_strv, i, s) < 0) + new_strv[j++] = s; + } + new_strv[j] = NULL; + new_len = j; + } + + if (cur_len == 0 && new_len == 0) + return; + + if (nm_utils_strv_equal(cur_strv, new_strv)) + return; + + for (i = 0; i < cur_len; i++) + _set_bond_attr_printf(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "-%s", cur_strv[i]); + for (i = 0; i < new_len; i++) + _set_bond_attr_printf(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "+%s", new_strv[i]); +} + +/* + * Sets bond attribute stored in the option hashtable or + * the default value if no value was set. + */ +static void +set_bond_attr_or_default(NMDevice *device, NMSettingBond *s_bond, const char *opt) +{ + NMDeviceBond *self = NM_DEVICE_BOND(device); + const char * value; + + value = nm_setting_bond_get_option_or_default(s_bond, opt); + if (!value) { + if (_LOGT_ENABLED(LOGD_BOND) && nm_setting_bond_get_option_by_name(s_bond, opt)) + _LOGT(LOGD_BOND, "bond option '%s' not set as it conflicts with other options", opt); + return; + } + + _set_bond_attr(device, opt, value); +} + +static void +set_bond_attrs_or_default(NMDevice *device, NMSettingBond *s_bond, const char *const *attr_v) +{ + nm_assert(NM_IS_DEVICE(device)); + nm_assert(s_bond); + nm_assert(attr_v); + + for (; *attr_v; ++attr_v) + set_bond_attr_or_default(device, s_bond, *attr_v); +} + +static void +set_bond_arp_ip_targets(NMDevice *device, NMSettingBond *s_bond) +{ + int ifindex = nm_device_get_ifindex(device); + gs_free char *cur_arp_ip_target = NULL; + + /* ARP targets: clear and initialize the list */ + cur_arp_ip_target = nm_platform_sysctl_master_get_option(nm_device_get_platform(device), + ifindex, + NM_SETTING_BOND_OPTION_ARP_IP_TARGET); + set_arp_targets( + device, + cur_arp_ip_target, + nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)); +} + +static gboolean +apply_bonding_config(NMDeviceBond *self) +{ + NMDevice * device = NM_DEVICE(self); + NMSettingBond *s_bond; + NMBondMode mode; + const char * mode_str; + gs_free char * device_bond_mode = NULL; + + s_bond = nm_device_get_applied_setting(device, NM_TYPE_SETTING_BOND); + g_return_val_if_fail(s_bond, FALSE); + + mode_str = nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_MODE); + mode = _nm_setting_bond_mode_from_string(mode_str); + g_return_val_if_fail(mode != NM_BOND_MODE_UNKNOWN, FALSE); + + /* Set mode first, as some other options (e.g. arp_interval) are valid + * only for certain modes. + */ + device_bond_mode = nm_platform_sysctl_master_get_option(nm_device_get_platform(device), + nm_device_get_ifindex(device), + NM_SETTING_BOND_OPTION_MODE); + /* Need to release all slaves before we can change bond mode */ + if (!nm_streq0(device_bond_mode, mode_str)) + nm_device_master_release_slaves(device); + + set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_MODE); + + set_bond_arp_ip_targets(device, s_bond); + + set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_APPLY_SUBSET)); + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBond * self = NM_DEVICE_BOND(device); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + + /* Interface must be down to set bond options */ + nm_device_take_down(device, TRUE); + if (!apply_bonding_config(self)) + ret = NM_ACT_STAGE_RETURN_FAILURE; + else { + if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + nm_device_bring_up(device, TRUE, NULL); + + return ret; +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceBond *self = NM_DEVICE_BOND(device); + + nm_device_master_check_slave_physical_port(device, slave, LOGD_BOND); + + if (configure) { + gboolean success; + + nm_device_take_down(slave, TRUE); + success = nm_platform_link_enslave(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + nm_device_get_ip_ifindex(slave)); + nm_device_bring_up(slave, TRUE, NULL); + + if (!success) { + _LOGI(LOGD_BOND, "enslaved bond slave %s: failed", nm_device_get_ip_iface(slave)); + return FALSE; + } + + _LOGI(LOGD_BOND, "enslaved bond slave %s", nm_device_get_ip_iface(slave)); + } else + _LOGI(LOGD_BOND, "bond slave %s was enslaved", nm_device_get_ip_iface(slave)); + + return TRUE; +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceBond *self = NM_DEVICE_BOND(device); + gboolean success; + gs_free char *address = NULL; + int ifindex_slave; + int ifindex; + + if (configure) { + ifindex = nm_device_get_ifindex(device); + if (ifindex <= 0 || !nm_platform_link_get(nm_device_get_platform(device), ifindex)) + configure = FALSE; + } + + ifindex_slave = nm_device_get_ip_ifindex(slave); + + if (ifindex_slave <= 0) + _LOGD(LOGD_BOND, "bond slave %s is already released", nm_device_get_ip_iface(slave)); + + if (configure) { + /* When the last slave is released the bond MAC will be set to a random + * value by kernel; remember the current one and restore it afterwards. + */ + address = g_strdup(nm_device_get_hw_address(device)); + + if (ifindex_slave > 0) { + success = nm_platform_link_release(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + ifindex_slave); + + if (success) { + _LOGI(LOGD_BOND, "released bond slave %s", nm_device_get_ip_iface(slave)); + } else { + _LOGW(LOGD_BOND, "failed to release bond slave %s", nm_device_get_ip_iface(slave)); + } + } + + nm_platform_process_events(nm_device_get_platform(device)); + if (nm_device_update_hw_address(device)) + nm_device_hw_addr_set(device, address, "restore", FALSE); + + /* Kernel bonding code "closes" the slave when releasing it, (which clears + * IFF_UP), so we must bring it back up here to ensure carrier changes and + * other state is noticed by the now-released slave. + */ + if (ifindex_slave > 0) { + if (!nm_device_bring_up(slave, TRUE, NULL)) + _LOGW(LOGD_BOND, "released bond slave could not be brought up."); + } + } else { + if (ifindex_slave > 0) { + _LOGI(LOGD_BOND, "bond slave %s was released", nm_device_get_ip_iface(slave)); + } + } +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char *iface = nm_device_get_iface(device); + int r; + + g_assert(iface); + + r = nm_platform_link_bond_add(nm_device_get_platform(device), iface, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create bond interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + return TRUE; +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + /* Only handle bond setting here, delegate other settings to parent class */ + if (nm_streq(setting_name, NM_SETTING_BOND_SETTING_NAME)) { + NMSettingBond *s_a = NM_SETTING_BOND(s_old); + NMSettingBond *s_b = NM_SETTING_BOND(s_new); + const char ** option_list; + + if (!nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_BOND_SETTING_NAME, + error, + NM_SETTING_BOND_OPTIONS)) + return FALSE; + + option_list = nm_setting_bond_get_valid_options(NULL); + + for (; *option_list; ++option_list) { + const char *name = *option_list; + + /* We support changes to these */ + if (NM_IN_STRSET(name, OPTIONS_REAPPLY_FULL)) + continue; + + /* Reject any other changes */ + if (!nm_streq0(nm_setting_bond_get_option_normalized(s_a, name), + nm_setting_bond_get_option_normalized(s_b, name))) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Can't reapply '%s' bond option", + name); + return FALSE; + } + } + + return TRUE; + } + + device_class = NM_DEVICE_CLASS(nm_device_bond_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static void +reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new) +{ + NMDeviceBond * self = NM_DEVICE_BOND(device); + NMSettingBond *s_bond; + const char * value; + NMBondMode mode; + + NM_DEVICE_CLASS(nm_device_bond_parent_class)->reapply_connection(device, con_old, con_new); + + _LOGD(LOGD_BOND, "reapplying bond settings"); + s_bond = nm_connection_get_setting_bond(con_new); + g_return_if_fail(s_bond); + + value = nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_MODE); + mode = _nm_setting_bond_mode_from_string(value); + g_return_if_fail(mode != NM_BOND_MODE_UNKNOWN); + + /* Below we set only the bond options that kernel allows to modify + * while keeping the bond interface up */ + + set_bond_arp_ip_targets(device, s_bond); + + set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_REAPPLY_SUBSET)); +} + +/*****************************************************************************/ + +static void +nm_device_bond_init(NMDeviceBond *self) +{ + nm_assert(nm_device_is_master(NM_DEVICE(self))); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_bond = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_BOND, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves", + "ao", + NM_DEVICE_SLAVES), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_bond_class_init(NMDeviceBondClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bond); + + device_class->connection_type_supported = NM_SETTING_BOND_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_BOND_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_BOND); + + device_class->is_master = TRUE; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->complete_connection = complete_connection; + + device_class->update_connection = update_connection; + device_class->master_update_slave_connection = master_update_slave_connection; + + device_class->create_and_realize = create_and_realize; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + device_class->can_reapply_change = can_reapply_change; + device_class->reapply_connection = reapply_connection; +} + +/*****************************************************************************/ + +#define NM_TYPE_BOND_DEVICE_FACTORY (nm_bond_device_factory_get_type()) +#define NM_BOND_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BOND_DEVICE_FACTORY, NMBondDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_BOND, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_DRIVER, + "bonding", + NM_DEVICE_TYPE_DESC, + "Bond", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_BOND, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_BOND, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + BOND, + Bond, + bond, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_BOND) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BOND_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-bond.h b/src/core/devices/nm-device-bond.h new file mode 100644 index 0000000..1930179 --- /dev/null +++ b/src/core/devices/nm-device-bond.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_BOND_H__ +#define __NETWORKMANAGER_DEVICE_BOND_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_BOND (nm_device_bond_get_type()) +#define NM_DEVICE_BOND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_BOND, NMDeviceBond)) +#define NM_DEVICE_BOND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_BOND, NMDeviceBondClass)) +#define NM_IS_DEVICE_BOND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_BOND)) +#define NM_IS_DEVICE_BOND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_BOND)) +#define NM_DEVICE_BOND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_BOND, NMDeviceBondClass)) + +typedef struct _NMDeviceBond NMDeviceBond; +typedef struct _NMDeviceBondClass NMDeviceBondClass; + +GType nm_device_bond_get_type(void); + +#endif /* NM_DEVICE_BOND_H */ diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c new file mode 100644 index 0000000..6557558 --- /dev/null +++ b/src/core/devices/nm-device-bridge.c @@ -0,0 +1,1246 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-bridge.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceBridge +#include "nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceBridge { + NMDevice parent; + GCancellable *bt_cancellable; + bool vlan_configured : 1; + bool bt_registered : 1; +}; + +struct _NMDeviceBridgeClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceBridge, nm_device_bridge, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +const NMBtVTableNetworkServer *nm_bt_vtable_network_server = NULL; + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + NMSettingBluetooth *s_bt; + + if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class) + ->check_connection_available(device, connection, flags, specific_object, error)) + return FALSE; + + s_bt = _nm_connection_get_setting_bluetooth_for_nap(connection); + if (s_bt) { + const char *bdaddr; + + if (!nm_bt_vtable_network_server) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "bluetooth plugin not available to activate NAP profile"); + return FALSE; + } + + bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt); + if (!nm_bt_vtable_network_server->is_available( + nm_bt_vtable_network_server, + bdaddr, + (self->bt_cancellable || self->bt_registered) ? device : NULL)) { + if (bdaddr) + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "no suitable NAP device \"%s\" available", + bdaddr); + else + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "no suitable NAP device available"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingBridge *s_bridge; + const char * mac_address; + + if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME) + && _nm_connection_get_setting_bluetooth_for_nap(connection)) { + s_bridge = nm_connection_get_setting_bridge(connection); + if (!s_bridge) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "missing bridge setting for bluetooth NAP profile"); + return FALSE; + } + + /* a bluetooth NAP connection is handled by the bridge. + * + * Proceed... */ + } else { + s_bridge = + _nm_connection_check_main_setting(connection, NM_SETTING_BRIDGE_SETTING_NAME, error); + if (!s_bridge) + return FALSE; + } + + mac_address = nm_setting_bridge_get_mac_address(s_bridge); + if (mac_address && nm_device_is_real(device)) { + const char *hw_addr; + + hw_addr = nm_device_get_hw_address(device); + if (!hw_addr || !nm_utils_hwaddr_matches(hw_addr, -1, mac_address, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "mac address mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingBridge *s_bridge; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_BRIDGE_SETTING_NAME, + existing_connections, + NULL, + _("Bridge connection"), + "bridge", + NULL, + TRUE); + + s_bridge = nm_connection_get_setting_bridge(connection); + if (!s_bridge) { + s_bridge = (NMSettingBridge *) nm_setting_bridge_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bridge)); + } + + return TRUE; +} + +static void +to_sysfs_group_address_sys(const char *group_address, NMEtherAddr *out_addr) +{ + if (group_address == NULL) { + *out_addr = NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN); + return; + } + if (!nm_utils_hwaddr_aton(group_address, out_addr, ETH_ALEN)) + nm_assert_not_reached(); +} + +static void +from_sysfs_group_address(const char *value, GValue *out) +{ + if (!nm_utils_hwaddr_matches(value, -1, NM_BRIDGE_GROUP_ADDRESS_DEF_STR, -1)) + g_value_set_string(out, value); +} + +static const char * +to_sysfs_group_address(GValue *value) +{ + return g_value_get_string(value) ?: NM_BRIDGE_GROUP_ADDRESS_DEF_STR; +} + +static int +to_sysfs_vlan_protocol_sys(const char *value) +{ + if (nm_streq0(value, "802.1ad")) + return ETH_P_8021AD; + + return ETH_P_8021Q; +} + +static void +from_sysfs_vlan_protocol(const char *value, GValue *out) +{ + switch (_nm_utils_ascii_str_to_uint64(value, 16, 0, G_MAXUINT, -1)) { + case ETH_P_8021Q: + /* default value */ + break; + case ETH_P_8021AD: + g_value_set_string(out, "802.1ad"); + break; + } +} + +static const char * +to_sysfs_vlan_protocol(GValue *value) +{ + const char *str = g_value_get_string(value); + + if (nm_streq0(str, "802.1ad")) { + G_STATIC_ASSERT_EXPR(ETH_P_8021AD == 0x88A8); + return "0x88A8"; + } + + G_STATIC_ASSERT_EXPR(ETH_P_8021Q == 0x8100); + return "0x8100"; +} + +static int +to_sysfs_multicast_router_sys(const char *value) +{ + if (nm_streq0(value, "disabled")) + return 0; + if (nm_streq0(value, "auto")) + return 1; + if (nm_streq0(value, "enabled")) + return 2; + + return 1; +} + +static const char * +to_sysfs_multicast_router(GValue *value) +{ + const char *str = g_value_get_string(value); + + if (nm_streq0(str, "disabled")) + return "0"; + if (nm_streq0(str, "auto")) + return "1"; + if (nm_streq0(str, "enabled")) + return "2"; + + return "1"; +} + +static void +from_sysfs_multicast_router(const char *value, GValue *out) +{ + switch (_nm_utils_ascii_str_to_uint64(value, 10, 0, G_MAXUINT, -1)) { + case 0: + g_value_set_string(out, "disabled"); + break; + case 2: + g_value_set_string(out, "enabled"); + break; + case 1: + default: + /* default value */ + break; + } +} + +/*****************************************************************************/ +#define _DEFAULT_IF_ZERO(val, def_val) \ + ({ \ + typeof(val) _val = (val); \ + typeof(val) _def_val = (def_val); \ + \ + (_val == 0) ? _def_val : _val; \ + }) + +typedef struct { + const char *name; + const char *sysname; + const char *(*to_sysfs)(GValue *value); + void (*from_sysfs)(const char *value, GValue *out); + guint64 nm_min; + guint64 nm_max; + guint64 nm_default; + bool default_if_zero; + bool user_hz_compensate; + bool only_with_stp; +} Option; + +#define OPTION(_name, _sysname, ...) \ + { \ + .name = ""_name \ + "", \ + .sysname = ""_sysname \ + "", \ + __VA_ARGS__ \ + } + +#define OPTION_TYPE_INT(min, max, def) .nm_min = (min), .nm_max = (max), .nm_default = (def) + +#define OPTION_TYPE_BOOL(def) OPTION_TYPE_INT(FALSE, TRUE, def) + +#define OPTION_TYPE_TOFROM(to, fro) .to_sysfs = (to), .from_sysfs = (fro) + +static const Option master_options[] = { + OPTION(NM_SETTING_BRIDGE_STP, /* this must stay as the first item */ + "stp_state", + OPTION_TYPE_BOOL(NM_BRIDGE_STP_DEF), ), + OPTION(NM_SETTING_BRIDGE_PRIORITY, + "priority", + OPTION_TYPE_INT(NM_BRIDGE_PRIORITY_MIN, NM_BRIDGE_PRIORITY_MAX, NM_BRIDGE_PRIORITY_DEF), + .default_if_zero = TRUE, + .only_with_stp = TRUE, ), + OPTION(NM_SETTING_BRIDGE_FORWARD_DELAY, + "forward_delay", + OPTION_TYPE_INT(NM_BRIDGE_FORWARD_DELAY_MIN, + NM_BRIDGE_FORWARD_DELAY_MAX, + NM_BRIDGE_FORWARD_DELAY_DEF), + .default_if_zero = TRUE, + .user_hz_compensate = TRUE, + .only_with_stp = TRUE, ), + OPTION(NM_SETTING_BRIDGE_HELLO_TIME, + "hello_time", + OPTION_TYPE_INT(NM_BRIDGE_HELLO_TIME_MIN, + NM_BRIDGE_HELLO_TIME_MAX, + NM_BRIDGE_HELLO_TIME_DEF), + .default_if_zero = TRUE, + .user_hz_compensate = TRUE, + .only_with_stp = TRUE, ), + OPTION(NM_SETTING_BRIDGE_MAX_AGE, + "max_age", + OPTION_TYPE_INT(NM_BRIDGE_MAX_AGE_MIN, NM_BRIDGE_MAX_AGE_MAX, NM_BRIDGE_MAX_AGE_DEF), + .default_if_zero = TRUE, + .user_hz_compensate = TRUE, + .only_with_stp = TRUE, ), + OPTION(NM_SETTING_BRIDGE_AGEING_TIME, + "ageing_time", + OPTION_TYPE_INT(NM_BRIDGE_AGEING_TIME_MIN, + NM_BRIDGE_AGEING_TIME_MAX, + NM_BRIDGE_AGEING_TIME_DEF), + .default_if_zero = TRUE, + .user_hz_compensate = TRUE, ), + OPTION(NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask", OPTION_TYPE_INT(0, 0xFFFF, 0), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_HASH_MAX, + "hash_max", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_HASH_MAX_MIN, + NM_BRIDGE_MULTICAST_HASH_MAX_MAX, + NM_BRIDGE_MULTICAST_HASH_MAX_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT, + "multicast_last_member_count", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MIN, + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MAX, + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL, + "multicast_last_member_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL, + "multicast_membership_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_QUERIER, + "multicast_querier", + OPTION_TYPE_BOOL(NM_BRIDGE_MULTICAST_QUERIER_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL, + "multicast_querier_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL, + "multicast_query_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL, + "multicast_query_response_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, + "multicast_query_use_ifaddr", + OPTION_TYPE_BOOL(NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_SNOOPING, + "multicast_snooping", + OPTION_TYPE_BOOL(NM_BRIDGE_MULTICAST_SNOOPING_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_ROUTER, + "multicast_router", + OPTION_TYPE_TOFROM(to_sysfs_multicast_router, from_sysfs_multicast_router), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT, + "multicast_startup_query_count", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MIN, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MAX, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF), ), + OPTION(NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL, + "multicast_startup_query_interval", + OPTION_TYPE_INT(NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF), ), + OPTION(NM_SETTING_BRIDGE_GROUP_ADDRESS, + "group_addr", + OPTION_TYPE_TOFROM(to_sysfs_group_address, from_sysfs_group_address), ), + OPTION(NM_SETTING_BRIDGE_VLAN_PROTOCOL, + "vlan_protocol", + OPTION_TYPE_TOFROM(to_sysfs_vlan_protocol, from_sysfs_vlan_protocol), ), + OPTION(NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, + "vlan_stats_enabled", + OPTION_TYPE_BOOL(NM_BRIDGE_VLAN_STATS_ENABLED_DEF)), + { + 0, + }}; + +static const Option slave_options[] = { + OPTION(NM_SETTING_BRIDGE_PORT_PRIORITY, + "priority", + OPTION_TYPE_INT(NM_BRIDGE_PORT_PRIORITY_MIN, + NM_BRIDGE_PORT_PRIORITY_MAX, + NM_BRIDGE_PORT_PRIORITY_DEF), + .default_if_zero = TRUE, ), + OPTION(NM_SETTING_BRIDGE_PORT_PATH_COST, + "path_cost", + OPTION_TYPE_INT(NM_BRIDGE_PORT_PATH_COST_MIN, + NM_BRIDGE_PORT_PATH_COST_MAX, + NM_BRIDGE_PORT_PATH_COST_DEF), + .default_if_zero = TRUE, ), + OPTION(NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", OPTION_TYPE_BOOL(FALSE), ), + {0}}; + +static void +commit_option(NMDevice *device, NMSetting *setting, const Option *option, gboolean slave) +{ + int ifindex = nm_device_get_ifindex(device); + nm_auto_unset_gvalue GValue val = G_VALUE_INIT; + GParamSpec * pspec; + const char * value; + char value_buf[100]; + + if (slave) + nm_assert(NM_IS_SETTING_BRIDGE_PORT(setting)); + else + nm_assert(NM_IS_SETTING_BRIDGE(setting)); + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), option->name); + nm_assert(pspec); + + g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec)); + g_object_get_property((GObject *) setting, option->name, &val); + + if (option->to_sysfs) { + value = option->to_sysfs(&val); + goto out; + } + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + value = g_value_get_boolean(&val) ? "1" : "0"; + break; + case G_TYPE_UINT64: + case G_TYPE_UINT: + { + guint64 uval; + + if (pspec->value_type == G_TYPE_UINT64) + uval = g_value_get_uint64(&val); + else + uval = (guint) g_value_get_uint(&val); + + /* zero means "unspecified" for some NM properties but isn't in the + * allowed kernel range, so reset the property to the default value. + */ + if (option->default_if_zero && uval == 0) { + if (pspec->value_type == G_TYPE_UINT64) + uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec); + else + uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec); + } + + /* Linux kernel bridge interfaces use 'centiseconds' for time-based values. + * In reality it's not centiseconds, but depends on HZ and USER_HZ, which + * is almost always works out to be a multiplier of 100, so we can assume + * centiseconds. See clock_t_to_jiffies(). + */ + if (option->user_hz_compensate) + uval *= 100; + + if (pspec->value_type == G_TYPE_UINT64) + nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval); + else + nm_sprintf_buf(value_buf, "%u", (guint) uval); + + value = value_buf; + } break; + case G_TYPE_STRING: + value = g_value_get_string(&val); + break; + default: + nm_assert_not_reached(); + value = NULL; + break; + } + +out: + if (!value) + return; + + if (slave) { + nm_platform_sysctl_slave_set_option(nm_device_get_platform(device), + ifindex, + option->sysname, + value); + } else { + nm_platform_sysctl_master_set_option(nm_device_get_platform(device), + ifindex, + option->sysname, + value); + } +} + +static const NMPlatformBridgeVlan ** +setting_vlans_to_platform(GPtrArray *array) +{ + NMPlatformBridgeVlan **arr; + NMPlatformBridgeVlan * p_data; + guint i; + + if (!array || !array->len) + return NULL; + + G_STATIC_ASSERT_EXPR(_nm_alignof(NMPlatformBridgeVlan *) >= _nm_alignof(NMPlatformBridgeVlan)); + arr = g_malloc((sizeof(NMPlatformBridgeVlan *) * (array->len + 1)) + + (sizeof(NMPlatformBridgeVlan) * (array->len))); + p_data = (NMPlatformBridgeVlan *) &arr[array->len + 1]; + + for (i = 0; i < array->len; i++) { + NMBridgeVlan *vlan = array->pdata[i]; + guint16 vid_start, vid_end; + + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + + p_data[i] = (NMPlatformBridgeVlan){ + .vid_start = vid_start, + .vid_end = vid_end, + .pvid = nm_bridge_vlan_is_pvid(vlan), + .untagged = nm_bridge_vlan_is_untagged(vlan), + }; + arr[i] = &p_data[i]; + } + arr[i] = NULL; + return (const NMPlatformBridgeVlan **) arr; +} + +static void +commit_slave_options(NMDevice *device, NMSettingBridgePort *setting) +{ + const Option * option; + NMSetting * s; + gs_unref_object NMSetting *s_clear = NULL; + + if (setting) + s = NM_SETTING(setting); + else + s = s_clear = nm_setting_bridge_port_new(); + + for (option = slave_options; option->name; option++) + commit_option(device, s, option, TRUE); +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + NMSettingBridge *s_bridge = nm_connection_get_setting_bridge(connection); + int ifindex = nm_device_get_ifindex(device); + const Option * option; + gs_free char * stp = NULL; + int stp_value; + + if (!s_bridge) { + s_bridge = (NMSettingBridge *) nm_setting_bridge_new(); + nm_connection_add_setting(connection, (NMSetting *) s_bridge); + } + + option = master_options; + nm_assert(nm_streq(option->sysname, "stp_state")); + + stp = nm_platform_sysctl_master_get_option(nm_device_get_platform(device), + ifindex, + option->sysname); + stp_value = + _nm_utils_ascii_str_to_int64(stp, 10, option->nm_min, option->nm_max, option->nm_default); + g_object_set(s_bridge, option->name, stp_value, NULL); + option++; + + for (; option->name; option++) { + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + gs_free char * str = NULL; + GParamSpec * pspec; + + str = nm_platform_sysctl_master_get_option(nm_device_get_platform(device), + ifindex, + option->sysname); + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(s_bridge), option->name); + + if (!stp_value && option->only_with_stp) + continue; + + if (!str) { + _LOGW(LOGD_BRIDGE, "failed to read bridge setting '%s'", option->sysname); + continue; + } + + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec)); + + if (option->from_sysfs) { + option->from_sysfs(str, &value); + goto out; + } + + switch (pspec->value_type) { + case G_TYPE_UINT64: + case G_TYPE_UINT: + { + guint64 uvalue; + + /* See comments in set_sysfs_uint() about centiseconds. */ + if (option->user_hz_compensate) { + uvalue = _nm_utils_ascii_str_to_int64(str, + 10, + option->nm_min * 100, + option->nm_max * 100, + option->nm_default * 100); + uvalue /= 100; + } else { + uvalue = _nm_utils_ascii_str_to_uint64(str, + 10, + option->nm_min, + option->nm_max, + option->nm_default); + } + + if (pspec->value_type == G_TYPE_UINT64) + g_value_set_uint64(&value, uvalue); + else + g_value_set_uint(&value, (guint) uvalue); + } break; + case G_TYPE_BOOLEAN: + { + gboolean bvalue; + + bvalue = _nm_utils_ascii_str_to_int64(str, + 10, + option->nm_min, + option->nm_max, + option->nm_default); + g_value_set_boolean(&value, bvalue); + } break; + case G_TYPE_STRING: + g_value_set_string(&value, str); + break; + default: + nm_assert_not_reached(); + break; + } + +out: + g_object_set_property(G_OBJECT(s_bridge), option->name, &value); + } +} + +static gboolean +master_update_slave_connection(NMDevice * device, + NMDevice * slave, + NMConnection *connection, + GError ** error) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + NMSettingConnection *s_con; + NMSettingBridgePort *s_port; + int ifindex_slave = nm_device_get_ifindex(slave); + const char * iface = nm_device_get_iface(device); + const Option * option; + + g_return_val_if_fail(ifindex_slave > 0, FALSE); + + s_con = nm_connection_get_setting_connection(connection); + s_port = nm_connection_get_setting_bridge_port(connection); + if (!s_port) { + s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new(); + nm_connection_add_setting(connection, NM_SETTING(s_port)); + } + + for (option = slave_options; option->name; option++) { + gs_free char *str = nm_platform_sysctl_slave_get_option(nm_device_get_platform(device), + ifindex_slave, + option->sysname); + uint value; + + if (str) { + /* See comments in set_sysfs_uint() about centiseconds. */ + if (option->user_hz_compensate) { + value = _nm_utils_ascii_str_to_int64(str, + 10, + option->nm_min * 100, + option->nm_max * 100, + option->nm_default * 100); + value /= 100; + } else { + value = _nm_utils_ascii_str_to_int64(str, + 10, + option->nm_min, + option->nm_max, + option->nm_default); + } + g_object_set(s_port, option->name, value, NULL); + } else + _LOGW(LOGD_BRIDGE, "failed to read bridge port setting '%s'", option->sysname); + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + iface, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + return TRUE; +} + +static gboolean +bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + gconstpointer hwaddr; + size_t length; + gboolean enabled; + guint16 pvid; + NMPlatform * plat; + int ifindex; + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free const NMPlatformBridgeVlan **plat_vlans = NULL; + + if (self->vlan_configured) + return TRUE; + + plat = nm_device_get_platform(device); + ifindex = nm_device_get_ifindex(device); + enabled = nm_setting_bridge_get_vlan_filtering(s_bridge); + + if (!enabled) { + nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0"); + nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "1"); + nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL); + return TRUE; + } + + hwaddr = nm_platform_link_get_address(plat, ifindex, &length); + g_return_val_if_fail(length == ETH_ALEN, FALSE); + if (nm_utils_hwaddr_matches(hwaddr, length, &nm_ether_addr_zero, ETH_ALEN)) { + /* We need a non-zero MAC address to set the default pvid. + * Retry later. */ + return TRUE; + } + + self->vlan_configured = TRUE; + + /* Filtering must be disabled to change the default PVID */ + if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0")) + return FALSE; + + /* Clear the default PVID so that we later can force the re-creation of + * default PVID VLANs by writing the option again. */ + if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "0")) + return FALSE; + + /* Clear all existing VLANs */ + if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL)) + return FALSE; + + /* Now set the default PVID. After this point the kernel creates + * a PVID VLAN on each port, including the bridge itself. */ + pvid = nm_setting_bridge_get_vlan_default_pvid(s_bridge); + if (pvid) { + char value[32]; + + nm_sprintf_buf(value, "%u", pvid); + if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", value)) + return FALSE; + } + + /* Create VLANs only after setting the default PVID, so that + * any PVID VLAN overrides the bridge's default PVID. */ + g_object_get(s_bridge, NM_SETTING_BRIDGE_VLANS, &vlans, NULL); + plat_vlans = setting_vlans_to_platform(vlans); + if (plat_vlans && !nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, plat_vlans)) + return FALSE; + + if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "1")) + return FALSE; + + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMConnection *connection; + NMSetting * s_bridge; + const Option *option; + + connection = nm_device_get_applied_connection(device); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + s_bridge = (NMSetting *) nm_connection_get_setting_bridge(connection); + g_return_val_if_fail(s_bridge, NM_ACT_STAGE_RETURN_FAILURE); + + for (option = master_options; option->name; option++) + commit_option(device, s_bridge, option, FALSE); + + if (!bridge_set_vlan_options(device, (NMSettingBridge *) s_bridge)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +_bt_register_bridge_cb(GError *error, gpointer user_data) +{ + NMDeviceBridge *self; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + + g_clear_object(&self->bt_cancellable); + + if (error) { + _LOGD(LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + nm_device_activate_schedule_stage2_device_config(NM_DEVICE(self), FALSE); +} + +void +_nm_device_bridge_notify_unregister_bt_nap(NMDevice *device, const char *reason) +{ + NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); + + _LOGD(LOGD_DEVICE, + "bluetooth NAP server unregistered from bridge: %s%s", + reason, + self->bt_registered ? "" : " (was no longer registered)"); + + nm_clear_g_cancellable(&self->bt_cancellable); + + if (self->bt_registered) { + self->bt_registered = FALSE; + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_BT_FAILED); + } +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + NMConnection * connection; + NMSettingBluetooth *s_bt; + gs_free_error GError *error = NULL; + + connection = nm_device_get_applied_connection(device); + + s_bt = _nm_connection_get_setting_bluetooth_for_nap(connection); + if (!s_bt) + return NM_ACT_STAGE_RETURN_SUCCESS; + + if (!nm_bt_vtable_network_server) { + _LOGD(LOGD_DEVICE, "bluetooth NAP server failed because bluetooth plugin not available"); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (self->bt_cancellable) + return NM_ACT_STAGE_RETURN_POSTPONE; + + if (self->bt_registered) + return NM_ACT_STAGE_RETURN_POSTPONE; + + self->bt_cancellable = g_cancellable_new(); + if (!nm_bt_vtable_network_server->register_bridge(nm_bt_vtable_network_server, + nm_setting_bluetooth_get_bdaddr(s_bt), + device, + self->bt_cancellable, + _bt_register_bridge_cb, + device, + &error)) { + _LOGD(LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + self->bt_registered = TRUE; + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); + + _LOGD(LOGD_DEVICE, + "deactivate bridge%s", + self->bt_registered ? " (registered as NAP bluetooth device)" : ""); + + self->vlan_configured = FALSE; + + nm_clear_g_cancellable(&self->bt_cancellable); + + if (self->bt_registered) { + self->bt_registered = FALSE; + nm_bt_vtable_network_server->unregister_bridge(nm_bt_vtable_network_server, device); + } +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceBridge * self = NM_DEVICE_BRIDGE(device); + NMConnection * master_connection; + NMSettingBridge * s_bridge; + NMSettingBridgePort *s_port; + + if (configure) { + if (!nm_platform_link_enslave(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + nm_device_get_ip_ifindex(slave))) + return FALSE; + + master_connection = nm_device_get_applied_connection(device); + nm_assert(master_connection); + s_bridge = nm_connection_get_setting_bridge(master_connection); + nm_assert(s_bridge); + s_port = nm_connection_get_setting_bridge_port(connection); + + bridge_set_vlan_options(device, s_bridge); + + if (nm_setting_bridge_get_vlan_filtering(s_bridge)) { + gs_free const NMPlatformBridgeVlan **plat_vlans = NULL; + gs_unref_ptrarray GPtrArray *vlans = NULL; + + if (s_port) + g_object_get(s_port, NM_SETTING_BRIDGE_PORT_VLANS, &vlans, NULL); + + plat_vlans = setting_vlans_to_platform(vlans); + + /* Since the link was just enslaved, there are no existing VLANs + * (except for the default one) and so there's no need to flush. */ + + if (plat_vlans + && !nm_platform_link_set_bridge_vlans(nm_device_get_platform(slave), + nm_device_get_ifindex(slave), + TRUE, + plat_vlans)) + return FALSE; + } + + commit_slave_options(slave, s_port); + + _LOGI(LOGD_BRIDGE, "attached bridge port %s", nm_device_get_ip_iface(slave)); + } else { + _LOGI(LOGD_BRIDGE, "bridge port %s was attached", nm_device_get_ip_iface(slave)); + } + + return TRUE; +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); + gboolean success; + int ifindex_slave; + int ifindex; + + if (configure) { + ifindex = nm_device_get_ifindex(device); + if (ifindex <= 0 || !nm_platform_link_get(nm_device_get_platform(device), ifindex)) + configure = FALSE; + } + + ifindex_slave = nm_device_get_ip_ifindex(slave); + + if (ifindex_slave <= 0) { + _LOGD(LOGD_TEAM, "bond slave %s is already released", nm_device_get_ip_iface(slave)); + return; + } + + if (configure) { + success = nm_platform_link_release(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + ifindex_slave); + + if (success) { + _LOGI(LOGD_BRIDGE, "detached bridge port %s", nm_device_get_ip_iface(slave)); + } else { + _LOGW(LOGD_BRIDGE, "failed to detach bridge port %s", nm_device_get_ip_iface(slave)); + } + } else { + _LOGI(LOGD_BRIDGE, "bridge port %s was detached", nm_device_get_ip_iface(slave)); + } +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + NMSettingWired * s_wired; + NMSettingBridge * s_bridge; + const char * iface = nm_device_get_iface(device); + const char * hwaddr; + gs_free char * hwaddr_cloned = NULL; + guint8 mac_address[NM_UTILS_HWADDR_LEN_MAX]; + NMPlatformLnkBridge props; + int r; + guint32 mtu = 0; + + nm_assert(iface); + + s_bridge = nm_connection_get_setting_bridge(connection); + nm_assert(s_bridge); + + s_wired = nm_connection_get_setting_wired(connection); + if (s_wired) + mtu = nm_setting_wired_get_mtu(s_wired); + + hwaddr = nm_setting_bridge_get_mac_address(s_bridge); + if (!hwaddr + && nm_device_hw_addr_get_cloned(device, connection, FALSE, &hwaddr_cloned, NULL, NULL)) { + /* FIXME: we set the MAC address when creating the interface, while the + * NMDevice is still unrealized. As we afterwards realize the device, it + * forgets the parameters for the cloned MAC address, and in stage 1 + * it might create a different MAC address. That should be fixed by + * better handling device realization. */ + hwaddr = hwaddr_cloned; + } + + if (hwaddr) { + if (!nm_utils_hwaddr_aton(hwaddr, mac_address, ETH_ALEN)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Invalid hardware address '%s'", + hwaddr); + g_return_val_if_reached(FALSE); + } + } + + props = (NMPlatformLnkBridge){ + .forward_delay = _DEFAULT_IF_ZERO(nm_setting_bridge_get_forward_delay(s_bridge) * 100u, + NM_BRIDGE_FORWARD_DELAY_DEF_SYS), + .hello_time = _DEFAULT_IF_ZERO(nm_setting_bridge_get_hello_time(s_bridge) * 100u, + NM_BRIDGE_HELLO_TIME_DEF_SYS), + .max_age = _DEFAULT_IF_ZERO(nm_setting_bridge_get_max_age(s_bridge) * 100u, + NM_BRIDGE_MAX_AGE_DEF_SYS), + .ageing_time = _DEFAULT_IF_ZERO(nm_setting_bridge_get_ageing_time(s_bridge) * 100u, + NM_BRIDGE_AGEING_TIME_DEF_SYS), + .stp_state = nm_setting_bridge_get_stp(s_bridge), + .priority = nm_setting_bridge_get_priority(s_bridge), + .vlan_protocol = to_sysfs_vlan_protocol_sys(nm_setting_bridge_get_vlan_protocol(s_bridge)), + .vlan_stats_enabled = nm_setting_bridge_get_vlan_stats_enabled(s_bridge), + .group_fwd_mask = nm_setting_bridge_get_group_forward_mask(s_bridge), + .mcast_snooping = nm_setting_bridge_get_multicast_snooping(s_bridge), + .mcast_router = + to_sysfs_multicast_router_sys(nm_setting_bridge_get_multicast_router(s_bridge)), + .mcast_query_use_ifaddr = nm_setting_bridge_get_multicast_query_use_ifaddr(s_bridge), + .mcast_querier = nm_setting_bridge_get_multicast_querier(s_bridge), + .mcast_hash_max = nm_setting_bridge_get_multicast_hash_max(s_bridge), + .mcast_last_member_count = nm_setting_bridge_get_multicast_last_member_count(s_bridge), + .mcast_startup_query_count = nm_setting_bridge_get_multicast_startup_query_count(s_bridge), + .mcast_last_member_interval = + nm_setting_bridge_get_multicast_last_member_interval(s_bridge), + .mcast_membership_interval = nm_setting_bridge_get_multicast_membership_interval(s_bridge), + .mcast_querier_interval = nm_setting_bridge_get_multicast_querier_interval(s_bridge), + .mcast_query_interval = nm_setting_bridge_get_multicast_query_interval(s_bridge), + .mcast_query_response_interval = + nm_setting_bridge_get_multicast_query_response_interval(s_bridge), + .mcast_startup_query_interval = + nm_setting_bridge_get_multicast_startup_query_interval(s_bridge), + }; + + to_sysfs_group_address_sys(nm_setting_bridge_get_group_address(s_bridge), &props.group_addr); + + /* If mtu != 0, we set the MTU of the new bridge at creation time. However, kernel will still + * automatically adjust the MTU of the bridge based on the minimum of the slave's MTU. + * We don't want this automatism as the user asked for a fixed MTU. + * + * To workaround this behavior of kernel, we will later toggle the MTU twice. See + * NMDeviceClass.mtu_force_set. */ + r = nm_platform_link_bridge_add(nm_device_get_platform(device), + iface, + hwaddr ? mac_address : NULL, + hwaddr ? ETH_ALEN : 0, + mtu, + &props, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create bridge interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_device_bridge_init(NMDeviceBridge *self) +{ + nm_assert(nm_device_is_master(NM_DEVICE(self))); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_bridge = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_BRIDGE, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves", + "ao", + NM_DEVICE_SLAVES), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_bridge_class_init(NMDeviceBridgeClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bridge); + + device_class->connection_type_supported = NM_SETTING_BRIDGE_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_BRIDGE); + + device_class->is_master = TRUE; + device_class->mtu_force_set = TRUE; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + + device_class->update_connection = update_connection; + device_class->master_update_slave_connection = master_update_slave_connection; + + device_class->create_and_realize = create_and_realize; + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->deactivate = deactivate; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; +} + +/*****************************************************************************/ + +#define NM_TYPE_BRIDGE_DEVICE_FACTORY (nm_bridge_device_factory_get_type()) +#define NM_BRIDGE_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BRIDGE_DEVICE_FACTORY, NMBridgeDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_BRIDGE, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_DRIVER, + "bridge", + NM_DEVICE_TYPE_DESC, + "Bridge", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_BRIDGE, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_BRIDGE, + NULL); +} + +static gboolean +match_connection(NMDeviceFactory *factory, NMConnection *connection) +{ + const char *type = nm_connection_get_connection_type(connection); + + if (nm_streq(type, NM_SETTING_BRIDGE_SETTING_NAME)) + return TRUE; + + nm_assert(nm_streq(type, NM_SETTING_BLUETOOTH_SETTING_NAME)); + + if (!_nm_connection_get_setting_bluetooth_for_nap(connection)) + return FALSE; + + if (!g_type_from_name("NMBluezManager")) { + /* bluetooth NAP connections are handled by bridge factory. However, + * it needs help from the bluetooth plugin, so if the plugin is not loaded, + * we claim not to support it. */ + return FALSE; + } + + return TRUE; +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + BRIDGE, + Bridge, + bridge, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_BRIDGE) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BLUETOOTH_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->match_connection = match_connection;); diff --git a/src/core/devices/nm-device-bridge.h b/src/core/devices/nm-device-bridge.h new file mode 100644 index 0000000..3243794 --- /dev/null +++ b/src/core/devices/nm-device-bridge.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_BRIDGE_H__ +#define __NETWORKMANAGER_DEVICE_BRIDGE_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_BRIDGE (nm_device_bridge_get_type()) +#define NM_DEVICE_BRIDGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridge)) +#define NM_DEVICE_BRIDGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgeClass)) +#define NM_IS_DEVICE_BRIDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_BRIDGE)) +#define NM_IS_DEVICE_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_BRIDGE)) +#define NM_DEVICE_BRIDGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgeClass)) + +typedef struct _NMDeviceBridge NMDeviceBridge; +typedef struct _NMDeviceBridgeClass NMDeviceBridgeClass; + +GType nm_device_bridge_get_type(void); + +extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server; + +void _nm_device_bridge_notify_unregister_bt_nap(NMDevice *device, const char *reason); + +#endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */ diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c new file mode 100644 index 0000000..5ec0b7f --- /dev/null +++ b/src/core/devices/nm-device-dummy.c @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-dummy.h" + +#include +#include + +#include "nm-act-request.h" +#include "nm-device-private.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-dummy.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceDummy +#include "nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceDummy { + NMDevice parent; +}; + +struct _NMDeviceDummyClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceDummy, nm_device_dummy, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingDummy *s_dummy; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_DUMMY_SETTING_NAME, + existing_connections, + NULL, + _("Dummy connection"), + NULL, + NULL, + TRUE); + + s_dummy = nm_connection_get_setting_dummy(connection); + if (!s_dummy) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'dummy' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMSettingDummy *s_dummy = nm_connection_get_setting_dummy(connection); + + if (!s_dummy) { + s_dummy = (NMSettingDummy *) nm_setting_dummy_new(); + nm_connection_add_setting(connection, (NMSetting *) s_dummy); + } +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSettingDummy *s_dummy; + int r; + + s_dummy = nm_connection_get_setting_dummy(connection); + g_assert(s_dummy); + + r = nm_platform_link_dummy_add(nm_device_get_platform(device), iface, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create dummy interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_device_dummy_init(NMDeviceDummy *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_dummy = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_DUMMY, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_dummy_class_init(NMDeviceDummyClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_dummy); + + device_class->connection_type_supported = NM_SETTING_DUMMY_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_DUMMY_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_DUMMY); + + device_class->complete_connection = complete_connection; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->update_connection = update_connection; + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; +} + +/*****************************************************************************/ + +#define NM_TYPE_DUMMY_DEVICE_FACTORY (nm_dummy_device_factory_get_type()) +#define NM_DUMMY_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DUMMY_DEVICE_FACTORY, NMDummyDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_DUMMY, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Dummy", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_DUMMY, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_DUMMY, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + DUMMY, + Dummy, + dummy, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_DUMMY) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_DUMMY_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-dummy.h b/src/core/devices/nm-device-dummy.h new file mode 100644 index 0000000..2845b4e --- /dev/null +++ b/src/core/devices/nm-device-dummy.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_DUMMY_H__ +#define __NETWORKMANAGER_DEVICE_DUMMY_H__ + +#include "nm-device-generic.h" + +#define NM_TYPE_DEVICE_DUMMY (nm_device_dummy_get_type()) +#define NM_DEVICE_DUMMY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_DUMMY, NMDeviceDummy)) +#define NM_DEVICE_DUMMY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_DUMMY, NMDeviceDummyClass)) +#define NM_IS_DEVICE_DUMMY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_DUMMY)) +#define NM_IS_DEVICE_DUMMY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_DUMMY)) +#define NM_DEVICE_DUMMY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_DUMMY, NMDeviceDummyClass)) + +typedef struct _NMDeviceDummy NMDeviceDummy; +typedef struct _NMDeviceDummyClass NMDeviceDummyClass; + +GType nm_device_dummy_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_DUMMY_H__ */ diff --git a/src/core/devices/nm-device-ethernet-utils.c b/src/core/devices/nm-device-ethernet-utils.c new file mode 100644 index 0000000..50da267 --- /dev/null +++ b/src/core/devices/nm-device-ethernet-utils.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ethernet-utils.h" + +#include "settings/nm-settings-connection.h" + +char * +nm_device_ethernet_utils_get_default_wired_name(GHashTable *existing_ids) +{ + char *temp; + int i; + + /* Find the next available unique connection name */ + for (i = 1; i < G_MAXINT; i++) { + temp = g_strdup_printf(_("Wired connection %d"), i); + if (!existing_ids || !g_hash_table_contains(existing_ids, temp)) + return temp; + g_free(temp); + } + return NULL; +} diff --git a/src/core/devices/nm-device-ethernet-utils.h b/src/core/devices/nm-device-ethernet-utils.h new file mode 100644 index 0000000..133340f --- /dev/null +++ b/src/core/devices/nm-device-ethernet-utils.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__ +#define __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__ + +char *nm_device_ethernet_utils_get_default_wired_name(GHashTable *existing_ids); + +#endif /* NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H */ diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c new file mode 100644 index 0000000..1c6c300 --- /dev/null +++ b/src/core/devices/nm-device-ethernet.c @@ -0,0 +1,2128 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2014 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ethernet.h" + +#include +#include +#include +#include +#include + +#include "nm-device-private.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "NetworkManagerUtils.h" +#include "supplicant/nm-supplicant-manager.h" +#include "supplicant/nm-supplicant-interface.h" +#include "supplicant/nm-supplicant-config.h" +#include "ppp/nm-ppp-manager.h" +#include "ppp/nm-ppp-manager-call.h" +#include "ppp/nm-ppp-status.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-dcb.h" +#include "settings/nm-settings-connection.h" +#include "nm-config.h" +#include "nm-device-ethernet-utils.h" +#include "settings/nm-settings.h" +#include "nm-device-factory.h" +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" +#include "nm-udev-aux/nm-udev-utils.h" +#include "nm-device-veth.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceEthernet +#include "nm-device-logging.h" + +/*****************************************************************************/ + +#define PPPOE_RECONNECT_DELAY 7 +#define PPPOE_ENCAP_OVERHEAD 8 /* 2 bytes for PPP, 6 for PPPoE */ + +#define SUPPLICANT_LNK_TIMEOUT_SEC 15 + +/*****************************************************************************/ + +typedef enum { + DCB_WAIT_UNKNOWN = 0, + /* Ensure carrier is up before enabling DCB */ + DCB_WAIT_CARRIER_PREENABLE_UP, + /* Wait for carrier down when device starts enabling */ + DCB_WAIT_CARRIER_PRECONFIG_DOWN, + /* Wait for carrier up when device has finished enabling */ + DCB_WAIT_CARRIER_PRECONFIG_UP, + /* Wait carrier down when device starts configuring */ + DCB_WAIT_CARRIER_POSTCONFIG_DOWN, + /* Wait carrier up when device has finished configuring */ + DCB_WAIT_CARRIER_POSTCONFIG_UP, +} DcbWait; + +typedef struct _NMDeviceEthernetPrivate { + /* s390 */ + char * subchan1; + char * subchan2; + char * subchan3; + char * subchannels; /* Composite used for checking unmanaged specs */ + char ** subchannels_dbus; /* Array exported on D-Bus */ + char * s390_nettype; + GHashTable *s390_options; + + guint32 speed; + gulong carrier_id; + + struct { + NMSupplicantManager * mgr; + NMSupplMgrCreateIfaceHandle *create_handle; + NMSupplicantInterface * iface; + + gulong iface_state_id; + gulong auth_state_id; + + guint con_timeout_id; + + guint lnk_timeout_id; + + bool is_associated : 1; + } supplicant; + + NMActRequestGetSecretsCallId *wired_secrets_id; + + /* PPPoE */ + NMPPPManager *ppp_manager; + gint32 last_pppoe_time; + guint pppoe_wait_id; + + /* DCB */ + DcbWait dcb_wait; + guint dcb_timeout_id; + + guint32 ethtool_prev_speed; + + NMPlatformLinkDuplexType ethtool_prev_duplex : 3; + + bool dcb_handle_carrier_changes : 1; + + bool ethtool_prev_set : 1; + bool ethtool_prev_autoneg : 1; + +} NMDeviceEthernetPrivate; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceEthernet, PROP_SPEED, PROP_S390_SUBCHANNELS, ); + +/*****************************************************************************/ + +G_DEFINE_TYPE(NMDeviceEthernet, nm_device_ethernet, NM_TYPE_DEVICE) + +#define NM_DEVICE_ETHERNET_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMDeviceEthernet, NM_IS_DEVICE_ETHERNET, NMDevice) + +/*****************************************************************************/ + +static void wired_secrets_cancel(NMDeviceEthernet *self); + +/*****************************************************************************/ + +static char * +get_link_basename(const char *parent_path, const char *name, GError **error) +{ + char *link_dest, *path; + char *result = NULL; + + path = g_strdup_printf("%s/%s", parent_path, name); + link_dest = g_file_read_link(path, error); + if (link_dest) { + result = g_path_get_basename(link_dest); + g_free(link_dest); + } + g_free(path); + return result; +} + +static void +_update_s390_subchannels(NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + struct udev_device * dev = NULL; + struct udev_device * parent = NULL; + const char * parent_path, *item; + int ifindex; + GDir * dir; + GError * error = NULL; + + if (priv->subchannels) { + /* only read the subchannels once. For one, we don't expect them to change + * on multiple invocations. Second, we didn't implement proper reloading. + * Proper reloading might also be complicated, because the subchannels are + * used to match on devices based on a device-spec. Thus, it's not clear + * what it means to change afterwards. */ + return; + } + + ifindex = nm_device_get_ifindex((NMDevice *) self); + dev = nm_platform_link_get_udev_device(nm_device_get_platform(NM_DEVICE(self)), ifindex); + if (!dev) + return; + + /* Try for the "ccwgroup" parent */ + parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccwgroup", NULL); + if (!parent) { + /* FIXME: whatever 'lcs' devices' subsystem is here... */ + + /* Not an s390 device */ + return; + } + + parent_path = udev_device_get_syspath(parent); + dir = g_dir_open(parent_path, 0, &error); + if (!dir) { + _LOGW(LOGD_DEVICE | LOGD_PLATFORM, + "update-s390: failed to open directory '%s': %s", + parent_path, + error->message); + g_clear_error(&error); + return; + } + + while ((item = g_dir_read_name(dir))) { + if (!strcmp(item, "cdev0")) { + priv->subchan1 = get_link_basename(parent_path, "cdev0", &error); + } else if (!strcmp(item, "cdev1")) { + priv->subchan2 = get_link_basename(parent_path, "cdev1", &error); + } else if (!strcmp(item, "cdev2")) { + priv->subchan3 = get_link_basename(parent_path, "cdev2", &error); + } else if (!strcmp(item, "driver")) { + priv->s390_nettype = get_link_basename(parent_path, "driver", &error); + } else if (!strcmp(item, "layer2") || !strcmp(item, "portname") + || !strcmp(item, "portno")) { + gs_free char *path = NULL, *value = NULL; + + path = g_strdup_printf("%s/%s", parent_path, item); + value = nm_platform_sysctl_get(nm_device_get_platform(NM_DEVICE(self)), + NMP_SYSCTL_PATHID_ABSOLUTE(path)); + + if (!strcmp(item, "portname") && !g_strcmp0(value, "no portname required")) { + /* Do nothing */ + } else if (value && *value) { + g_hash_table_insert(priv->s390_options, g_strdup(item), value); + value = NULL; + } else + _LOGW(LOGD_DEVICE | LOGD_PLATFORM, "update-s390: error reading %s", path); + } + + if (error) { + _LOGW(LOGD_DEVICE | LOGD_PLATFORM, + "update-s390: failed reading sysfs for %s (%s)", + item, + error->message); + g_clear_error(&error); + } + } + + g_dir_close(dir); + + if (priv->subchan3) { + priv->subchannels = + g_strdup_printf("%s,%s,%s", priv->subchan1, priv->subchan2, priv->subchan3); + } else if (priv->subchan2) { + priv->subchannels = g_strdup_printf("%s,%s", priv->subchan1, priv->subchan2); + } else + priv->subchannels = g_strdup(priv->subchan1); + + priv->subchannels_dbus = g_new(char *, 3 + 1); + priv->subchannels_dbus[0] = g_strdup(priv->subchan1); + priv->subchannels_dbus[1] = g_strdup(priv->subchan2); + priv->subchannels_dbus[2] = g_strdup(priv->subchan3); + priv->subchannels_dbus[3] = NULL; + + _LOGI(LOGD_DEVICE | LOGD_PLATFORM, + "update-s390: found s390 '%s' subchannels [%s]", + nm_device_get_driver((NMDevice *) self) ?: "(unknown driver)", + priv->subchannels); + + _notify(self, PROP_S390_SUBCHANNELS); +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if (new_state > NM_DEVICE_STATE_ACTIVATED) + wired_secrets_cancel(NM_DEVICE_ETHERNET(device)); +} + +static void +nm_device_ethernet_init(NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate); + self->_priv = priv; + + priv->s390_options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device); + int ifindex = nm_device_get_ifindex(device); + + if (ifindex > 0) { + if (nm_platform_link_supports_carrier_detect(nm_device_get_platform(device), ifindex)) + return NM_DEVICE_CAP_CARRIER_DETECT; + else { + _LOGI(LOGD_PLATFORM, + "driver '%s' does not support carrier detection.", + nm_device_get_driver(device)); + } + } + + return NM_DEVICE_CAP_NONE; +} + +static guint32 +_subchannels_count_num(const char *const *array) +{ + int i; + + if (!array) + return 0; + for (i = 0; array[i]; i++) + /* NOP */; + return i; +} + +static gboolean +match_subchans(NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_mac) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + const char *const * subchans; + guint32 num1, num2; + int i; + + *try_mac = TRUE; + + subchans = nm_setting_wired_get_s390_subchannels(s_wired); + num1 = _subchannels_count_num(subchans); + num2 = _subchannels_count_num((const char *const *) priv->subchannels_dbus); + /* connection has no subchannels */ + if (num1 == 0) + return TRUE; + /* connection requires subchannels but the device has none */ + if (num2 == 0) + return FALSE; + /* number of subchannels differ */ + if (num1 != num2) + return FALSE; + + /* Make sure each subchannel in the connection is a subchannel of this device */ + for (i = 0; subchans[i]; i++) { + const char *candidate = subchans[i]; + + if ((priv->subchan1 && !strcmp(priv->subchan1, candidate)) + || (priv->subchan2 && !strcmp(priv->subchan2, candidate)) + || (priv->subchan3 && !strcmp(priv->subchan3, candidate))) + continue; + + return FALSE; /* a subchannel was not found */ + } + + *try_mac = FALSE; + return TRUE; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device); + NMSettingWired * s_wired; + + if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME) + || (nm_connection_is_type(connection, NM_SETTING_VETH_SETTING_NAME) + && NM_IS_DEVICE_VETH(device))) { + s_wired = nm_connection_get_setting_wired(connection); + } else { + s_wired = + _nm_connection_check_main_setting(connection, NM_SETTING_WIRED_SETTING_NAME, error); + if (!s_wired) + return FALSE; + } + + if (s_wired) { + const char * mac, *perm_hw_addr; + gboolean try_mac = TRUE; + const char *const *mac_blacklist; + int i; + + if (!match_subchans(self, s_wired, &try_mac)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "s390 subchannels don't match"); + return FALSE; + } + + perm_hw_addr = nm_device_get_permanent_hw_address(device); + mac = nm_setting_wired_get_mac_address(s_wired); + if (perm_hw_addr) { + if (try_mac && mac && !nm_utils_hwaddr_matches(mac, -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "permanent MAC address doesn't match"); + return FALSE; + } + + /* Check for MAC address blacklist */ + mac_blacklist = nm_setting_wired_get_mac_address_blacklist(s_wired); + for (i = 0; mac_blacklist[i]; i++) { + if (!nm_utils_hwaddr_valid(mac_blacklist[i], ETH_ALEN)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "invalid MAC in blacklist"); + return FALSE; + } + + if (nm_utils_hwaddr_matches(mac_blacklist[i], -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "permanent MAC address of device blacklisted"); + return FALSE; + } + } + } else if (mac) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has no permanent MAC address to match"); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ +/* 802.1X */ + +static void +supplicant_interface_release(NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + nm_clear_pointer(&priv->supplicant.create_handle, + nm_supplicant_manager_create_interface_cancel); + + nm_clear_g_source(&priv->supplicant.lnk_timeout_id); + nm_clear_g_source(&priv->supplicant.con_timeout_id); + nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.iface_state_id); + nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.auth_state_id); + + if (priv->supplicant.iface) { + nm_supplicant_interface_disconnect(priv->supplicant.iface); + g_clear_object(&priv->supplicant.iface); + } +} + +static void +supplicant_auth_state_changed(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceEthernet * self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSupplicantAuthState state; + + state = nm_supplicant_interface_get_auth_state(priv->supplicant.iface); + _LOGD(LOGD_CORE, "supplicant auth state changed to %u", (unsigned) state); + + if (state == NM_SUPPLICANT_AUTH_STATE_SUCCESS) { + nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.iface_state_id); + nm_device_update_dynamic_ip_setup(NM_DEVICE(self)); + } +} + +static gboolean +wired_auth_is_optional(NMDeviceEthernet *self) +{ + NMSetting8021x *s_8021x; + + s_8021x = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_802_1X); + g_return_val_if_fail(s_8021x, FALSE); + return nm_setting_802_1x_get_optional(s_8021x); +} + +static void +wired_auth_cond_fail(NMDeviceEthernet *self, NMDeviceStateReason reason) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + + if (wired_auth_is_optional(self)) { + _LOGI( + LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) 802.1X authentication is optional, continuing after a failure"); + if (NM_IN_SET(nm_device_get_state(device), + NM_DEVICE_STATE_CONFIG, + NM_DEVICE_STATE_NEED_AUTH)) + nm_device_activate_schedule_stage3_ip_config_start(device); + + if (!priv->supplicant.auth_state_id) { + priv->supplicant.auth_state_id = + g_signal_connect(priv->supplicant.iface, + "notify::" NM_SUPPLICANT_INTERFACE_AUTH_STATE, + G_CALLBACK(supplicant_auth_state_changed), + self); + } + return; + } + + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), NM_DEVICE_STATE_FAILED, reason); +} + +static void +wired_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMDeviceEthernet * self = user_data; + NMDevice * device = user_data; + NMDeviceEthernetPrivate *priv; + + g_return_if_fail(NM_IS_DEVICE_ETHERNET(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + g_return_if_fail(priv->wired_secrets_id == call_id); + + priv->wired_secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail(nm_act_request_get_settings_connection(req) == connection); + + if (error) { + _LOGW(LOGD_ETHER, "%s", error->message); + wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + supplicant_interface_release(self); + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +wired_secrets_cancel(NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + if (priv->wired_secrets_id) + nm_act_request_cancel_secrets(NULL, priv->wired_secrets_id); + nm_assert(!priv->wired_secrets_id); +} + +static void +wired_secrets_get_secrets(NMDeviceEthernet * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMActRequest * req; + + wired_secrets_cancel(self); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv->wired_secrets_id = + nm_act_request_get_secrets(req, TRUE, setting_name, flags, NULL, wired_secrets_cb, self); + g_return_if_fail(priv->wired_secrets_id); +} + +static gboolean +supplicant_lnk_timeout_cb(gpointer user_data) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMActRequest * req; + NMConnection * applied_connection; + const char * setting_name; + + priv->supplicant.lnk_timeout_id = 0; + + req = nm_device_get_act_request(device); + + if (nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) { + wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return G_SOURCE_REMOVE; + } + + /* Disconnect event during initial authentication and credentials + * ARE checked - we are likely to have wrong key. Ask the user for + * another one. + */ + if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG) + goto time_out; + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(req)); + + applied_connection = nm_act_request_get_applied_connection(req); + setting_name = nm_connection_need_secrets(applied_connection, NULL); + if (!setting_name) + goto time_out; + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) disconnected during authentication, asking for new key."); + if (!wired_auth_is_optional(self)) + supplicant_interface_release(self); + + nm_device_state_changed(device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + wired_secrets_get_secrets(self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); + + return G_SOURCE_REMOVE; + +time_out: + _LOGW(LOGD_DEVICE | LOGD_ETHER, "link timed out."); + wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + + return G_SOURCE_REMOVE; +} + +static NMSupplicantConfig * +build_supplicant_config(NMDeviceEthernet *self, GError **error) +{ + const char * con_uuid; + NMSupplicantConfig *config = NULL; + NMSetting8021x * security; + NMConnection * connection; + guint32 mtu; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + g_return_val_if_fail(connection, NULL); + + con_uuid = nm_connection_get_uuid(connection); + mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)), + nm_device_get_ifindex(NM_DEVICE(self))); + + config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE); + + security = nm_connection_get_setting_802_1x(connection); + if (!nm_supplicant_config_add_setting_8021x(config, security, con_uuid, mtu, TRUE, error)) { + g_prefix_error(error, "802-1x-setting: "); + g_clear_object(&config); + } + + return config; +} + +static void +supplicant_iface_state_is_completed(NMDeviceEthernet *self, NMSupplicantInterfaceState state) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) { + nm_clear_g_source(&priv->supplicant.lnk_timeout_id); + nm_clear_g_source(&priv->supplicant.con_timeout_id); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) { + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) Stage 2 of 5 (Device Configure) successful."); + nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self)); + } + return; + } + + if (!priv->supplicant.lnk_timeout_id && !priv->supplicant.con_timeout_id) + priv->supplicant.lnk_timeout_id = + g_timeout_add_seconds(SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self); +} + +static void +supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data) +{ + NMDeviceEthernet * self; + NMDeviceEthernetPrivate *priv; + + if (nm_utils_error_is_cancelled_or_disposing(error)) + return; + + self = NM_DEVICE_ETHERNET(user_data); + priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + if (error) { + supplicant_interface_release(self); + nm_device_queue_state(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + return; + } + + nm_assert(!priv->supplicant.lnk_timeout_id); + nm_assert(!priv->supplicant.is_associated); + + priv->supplicant.is_associated = TRUE; + supplicant_iface_state_is_completed(self, + nm_supplicant_interface_get_state(priv->supplicant.iface)); +} + +static gboolean +supplicant_iface_start(NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + gs_unref_object NMSupplicantConfig *config = NULL; + gs_free_error GError *error = NULL; + + config = build_supplicant_config(self, &error); + if (!config) { + _LOGE(LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) couldn't build security configuration: %s", + error->message); + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + return FALSE; + } + + nm_supplicant_interface_disconnect(priv->supplicant.iface); + nm_supplicant_interface_assoc(priv->supplicant.iface, config, supplicant_iface_assoc_cb, self); + return TRUE; +} + +static void +supplicant_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(user_data); + NMDeviceEthernetPrivate * priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSupplicantInterfaceState new_state = new_state_i; + NMSupplicantInterfaceState old_state = old_state_i; + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "supplicant interface state: %s -> %s", + nm_supplicant_interface_state_to_string(old_state), + nm_supplicant_interface_state_to_string(new_state)); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + supplicant_interface_release(self); + wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + if (!supplicant_iface_start(self)) + return; + } + + if (priv->supplicant.is_associated) + supplicant_iface_state_is_completed(self, new_state); +} + +static gboolean +handle_auth_or_fail(NMDeviceEthernet *self, NMActRequest *req, gboolean new_secrets) +{ + const char * setting_name; + NMConnection *applied_connection; + + if (!nm_device_auth_retries_try_next(NM_DEVICE(self))) + return FALSE; + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(req)); + + applied_connection = nm_act_request_get_applied_connection(req); + setting_name = nm_connection_need_secrets(applied_connection, NULL); + if (!setting_name) { + _LOGI(LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + return FALSE; + } + + _LOGI(LOGD_DEVICE | LOGD_ETHER, "Activation: (ethernet) asking for new secrets"); + + /* Don't tear down supplicant if the authentication is optional + * because in case of a failure in getting new secrets we want to + * keep the supplicant alive. + */ + if (!wired_auth_is_optional(self)) + supplicant_interface_release(self); + + wired_secrets_get_secrets( + self, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0)); + return TRUE; +} + +static gboolean +supplicant_connection_timeout_cb(gpointer user_data) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMActRequest * req; + NMSettingsConnection * connection; + guint64 timestamp = 0; + gboolean new_secrets = TRUE; + + priv->supplicant.con_timeout_id = 0; + + /* Authentication failed; either driver problems, the encryption key is + * wrong, the passwords or certificates were wrong or the Ethernet switch's + * port is not configured for 802.1x. */ + _LOGW(LOGD_DEVICE | LOGD_ETHER, "Activation: (ethernet) association took too long."); + + req = nm_device_get_act_request(device); + connection = nm_act_request_get_settings_connection(req); + + /* Ask for new secrets only if we've never activated this connection + * before. If we've connected before, don't bother the user with dialogs, + * just retry or fail, and if we never connect the user can fix the + * password somewhere else. */ + if (nm_settings_connection_get_timestamp(connection, ×tamp)) + new_secrets = !timestamp; + + if (!handle_auth_or_fail(self, req, new_secrets)) { + wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_NO_SECRETS); + return G_SOURCE_REMOVE; + } + + if (!priv->supplicant.lnk_timeout_id && priv->supplicant.iface) { + NMSupplicantInterfaceState state; + + state = nm_supplicant_interface_get_state(priv->supplicant.iface); + if (state != NM_SUPPLICANT_INTERFACE_STATE_COMPLETED + && nm_supplicant_interface_state_is_operational(state)) + priv->supplicant.lnk_timeout_id = + g_timeout_add_seconds(SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self); + } + + return G_SOURCE_REMOVE; +} + +static void +supplicant_interface_create_cb(NMSupplicantManager * supplicant_manager, + NMSupplMgrCreateIfaceHandle *handle, + NMSupplicantInterface * iface, + GError * error, + gpointer user_data) +{ + NMDeviceEthernet * self; + NMDeviceEthernetPrivate *priv; + guint timeout; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + nm_assert(priv->supplicant.create_handle == handle); + priv->supplicant.create_handle = NULL; + + if (error) { + _LOGE(LOGD_DEVICE | LOGD_ETHER, + "Couldn't initialize supplicant interface: %s", + error->message); + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + priv->supplicant.iface = g_object_ref(iface); + priv->supplicant.is_associated = FALSE; + + priv->supplicant.iface_state_id = g_signal_connect(priv->supplicant.iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK(supplicant_iface_state_cb), + self); + + timeout = nm_device_get_supplicant_timeout(NM_DEVICE(self)); + priv->supplicant.con_timeout_id = + g_timeout_add_seconds(timeout, supplicant_connection_timeout_cb, self); + + if (nm_supplicant_interface_state_is_operational(nm_supplicant_interface_get_state(iface))) + supplicant_iface_start(self); +} + +static NMPlatformLinkDuplexType +link_duplex_to_platform(const char *duplex) +{ + if (!duplex) + return NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + if (nm_streq(duplex, "full")) + return NM_PLATFORM_LINK_DUPLEX_FULL; + if (nm_streq(duplex, "half")) + return NM_PLATFORM_LINK_DUPLEX_HALF; + g_return_val_if_reached(NM_PLATFORM_LINK_DUPLEX_UNKNOWN); +} + +static void +link_negotiation_set(NMDevice *device) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSettingWired * s_wired; + gboolean autoneg = TRUE; + gboolean link_autoneg; + NMPlatformLinkDuplexType duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + NMPlatformLinkDuplexType link_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + guint32 speed = 0; + guint32 link_speed; + + s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED); + if (s_wired) { + autoneg = nm_setting_wired_get_auto_negotiate(s_wired); + speed = nm_setting_wired_get_speed(s_wired); + duplex = link_duplex_to_platform(nm_setting_wired_get_duplex(s_wired)); + if (!autoneg && !speed && !duplex) { + _LOGD(LOGD_DEVICE, "set-link: ignore link negotiation"); + return; + } + } + + if (!nm_platform_ethtool_get_link_settings(nm_device_get_platform(device), + nm_device_get_ifindex(device), + &link_autoneg, + &link_speed, + &link_duplex)) { + _LOGW(LOGD_DEVICE, "set-link: unable to retrieve link negotiation"); + return; + } + + /* If link negotiation setting are already in place do nothing and return with success */ + if (!!autoneg == !!link_autoneg && speed == link_speed && duplex == link_duplex) { + _LOGD(LOGD_DEVICE, "set-link: link negotiation is already configured"); + return; + } + + if (autoneg && !speed && !duplex) + _LOGD(LOGD_DEVICE, "set-link: configure auto-negotiation"); + else { + _LOGD(LOGD_DEVICE, + "set-link: configure %snegotiation (%u Mbit%s, %s duplex%s)", + autoneg ? "auto-" : "static ", + speed ?: link_speed, + speed ? "" : "*", + duplex ? nm_platform_link_duplex_type_to_string(duplex) + : nm_platform_link_duplex_type_to_string(link_duplex), + duplex ? "" : "*"); + } + + if (!priv->ethtool_prev_set) { + /* remember the values we had before setting it. */ + priv->ethtool_prev_autoneg = link_autoneg; + priv->ethtool_prev_speed = link_speed; + priv->ethtool_prev_duplex = link_duplex; + priv->ethtool_prev_set = TRUE; + } + + if (!nm_platform_ethtool_set_link_settings(nm_device_get_platform(device), + nm_device_get_ifindex(device), + autoneg, + speed, + duplex)) { + _LOGW(LOGD_DEVICE, "set-link: failure to set link negotiation"); + return; + } +} + +static gboolean +pppoe_reconnect_delay(gpointer user_data) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + priv->pppoe_wait_id = 0; + priv->last_pppoe_time = 0; + _LOGI(LOGD_DEVICE, "PPPoE reconnect delay complete, resuming connection..."); + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); + return G_SOURCE_REMOVE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + if (nm_device_sys_iface_state_is_external_or_assume(device)) { + if (!priv->ethtool_prev_set && !nm_device_sys_iface_state_is_external(device)) { + NMSettingWired *s_wired; + + /* During restart of NetworkManager service we forget the original auto + * negotiation settings. When taking over a device, remember to reset + * the "default" during deactivate. */ + s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED); + if (s_wired + && (nm_setting_wired_get_auto_negotiate(s_wired) + || nm_setting_wired_get_speed(s_wired) + || nm_setting_wired_get_duplex(s_wired))) { + priv->ethtool_prev_set = TRUE; + priv->ethtool_prev_autoneg = TRUE; + priv->ethtool_prev_speed = 0; + priv->ethtool_prev_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + } + } + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + link_negotiation_set(device); + + /* If we're re-activating a PPPoE connection a short while after + * a previous PPPoE connection was torn down, wait a bit to allow the + * remote side to handle the disconnection. Otherwise, the peer may + * get confused and fail to negotiate the new connection. (rh #1023503) + * + * FIXME(shutdown): when exiting, we also need to wait before quitting, + * at least for additional NM_SHUTDOWN_TIMEOUT_MS seconds because + * otherwise after restart the device won't work for the first seconds. + */ + if (priv->last_pppoe_time != 0) { + gint32 delay = nm_utils_get_monotonic_timestamp_sec() - priv->last_pppoe_time; + + if (delay < PPPOE_RECONNECT_DELAY + && nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE)) { + if (priv->pppoe_wait_id == 0) { + _LOGI(LOGD_DEVICE, + "delaying PPPoE reconnect for %d seconds to ensure peer is ready...", + delay); + priv->pppoe_wait_id = g_timeout_add_seconds(delay, pppoe_reconnect_delay, self); + } + return NM_ACT_STAGE_RETURN_POSTPONE; + } + nm_clear_g_source(&priv->pppoe_wait_id); + priv->last_pppoe_time = 0; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +supplicant_check_secrets_needed(NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMConnection * connection; + NMSetting8021x * security; + const char * setting_name; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + security = nm_connection_get_setting_802_1x(connection); + if (!security) { + _LOGE(LOGD_DEVICE, "Invalid or missing 802.1X security"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!priv->supplicant.mgr) + priv->supplicant.mgr = g_object_ref(nm_supplicant_manager_get()); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets(connection, NULL); + if (setting_name) { + NMActRequest *req = nm_device_get_act_request(NM_DEVICE(self)); + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) connection '%s' has security, but secrets are required.", + nm_connection_get_id(connection)); + + if (!handle_auth_or_fail(self, req, FALSE)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + return NM_ACT_STAGE_RETURN_FAILURE; + } + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: (ethernet) connection '%s' requires no security. No secrets needed.", + nm_connection_get_id(connection)); + + supplicant_interface_release(self); + + priv->supplicant.create_handle = + nm_supplicant_manager_create_interface(priv->supplicant.mgr, + nm_device_get_ifindex(NM_DEVICE(self)), + NM_SUPPLICANT_DRIVER_WIRED, + supplicant_interface_create_cb, + self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +carrier_changed(NMSupplicantInterface *iface, GParamSpec *pspec, NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMDeviceStateReason reason; + NMActStageReturn ret; + + if (!nm_device_has_carrier(NM_DEVICE(self))) + return; + + _LOGD(LOGD_DEVICE | LOGD_ETHER, "got carrier, initializing supplicant"); + nm_clear_g_signal_handler(self, &priv->carrier_id); + ret = supplicant_check_secrets_needed(self, &reason); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed(NM_DEVICE(self), NM_DEVICE_STATE_FAILED, reason); + } +} + +/*****************************************************************************/ +/* PPPoE */ + +static void +ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + if (!nm_device_set_ip_ifindex(device, ifindex)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } +} + +static void +ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + /* Ignore PPP IP4 events that come in after initial configuration */ + if (nm_device_activate_ip4_state_in_conf(device)) + nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config)); +} + +static NMActStageReturn +pppoe_stage3_ip4_config_start(NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason) +{ + NMDevice * device = NM_DEVICE(self); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSettingPppoe * s_pppoe; + NMActRequest * req; + GError * err = NULL; + + req = nm_device_get_act_request(device); + + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE); + + g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE); + + priv->ppp_manager = nm_ppp_manager_create(nm_device_get_iface(device), &err); + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + nm_device_get_route_table(device, AF_INET), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_table(device, AF_INET6), + nm_device_get_route_metric(device, AF_INET6)); + } + + if (!priv->ppp_manager + || !nm_ppp_manager_start(priv->ppp_manager, + req, + nm_setting_pppoe_get_username(s_pppoe), + 30, + 0, + &err)) { + _LOGW(LOGD_DEVICE, "PPPoE failed to start: %s", err->message); + g_error_free(err); + + g_clear_object(&priv->ppp_manager); + + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_CALLBACK(ppp_state_changed), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_CALLBACK(ppp_ifindex_set), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_CALLBACK(ppp_ip4_config), + self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void dcb_state(NMDevice *device, gboolean timeout); + +static gboolean +dcb_carrier_timeout(gpointer user_data) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(user_data); + + g_return_val_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG, G_SOURCE_REMOVE); + + priv->dcb_timeout_id = 0; + if (priv->dcb_wait != DCB_WAIT_CARRIER_POSTCONFIG_DOWN) { + _LOGW(LOGD_DCB, "DCB: timed out waiting for carrier (step %d)", priv->dcb_wait); + } + dcb_state(device, TRUE); + return G_SOURCE_REMOVE; +} + +static gboolean +dcb_configure(NMDevice *device) +{ + NMDeviceEthernet * self = (NMDeviceEthernet *) device; + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSettingDcb * s_dcb; + GError * error = NULL; + + nm_clear_g_source(&priv->dcb_timeout_id); + + s_dcb = nm_device_get_applied_setting(device, NM_TYPE_SETTING_DCB); + + g_return_val_if_fail(s_dcb, FALSE); + + if (!nm_dcb_setup(nm_device_get_iface(device), s_dcb, &error)) { + _LOGW(LOGD_DCB, "Activation: (ethernet) failed to enable DCB/FCoE: %s", error->message); + g_clear_error(&error); + return FALSE; + } + + /* Pause again just in case the device takes the carrier down when + * setting specific DCB attributes. + */ + _LOGD(LOGD_DCB, "waiting for carrier (postconfig down)"); + priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_DOWN; + priv->dcb_timeout_id = g_timeout_add_seconds(3, dcb_carrier_timeout, device); + return TRUE; +} + +static gboolean +dcb_enable(NMDevice *device) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + GError * error = NULL; + + nm_clear_g_source(&priv->dcb_timeout_id); + if (!nm_dcb_enable(nm_device_get_iface(device), TRUE, &error)) { + _LOGW(LOGD_DCB, "Activation: (ethernet) failed to enable DCB/FCoE: %s", error->message); + g_clear_error(&error); + return FALSE; + } + + /* Pause for 3 seconds after enabling DCB to let the card reconfigure + * itself. Drivers will often re-initialize internal settings which + * takes the carrier down for 2 or more seconds. During this time, + * lldpad will refuse to do anything else with the card since the carrier + * is down. But NM might get the carrier-down signal long after calling + * "dcbtool dcb on", so we have to first wait for the carrier to go down. + */ + _LOGD(LOGD_DCB, "waiting for carrier (preconfig down)"); + priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_DOWN; + priv->dcb_timeout_id = g_timeout_add_seconds(3, dcb_carrier_timeout, device); + return TRUE; +} + +static void +dcb_state(NMDevice *device, gboolean timeout) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + gboolean carrier; + + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); + + carrier = nm_platform_link_is_connected(nm_device_get_platform(device), + nm_device_get_ifindex(device)); + _LOGD(LOGD_DCB, "dcb_state() wait %d carrier %d timeout %d", priv->dcb_wait, carrier, timeout); + + switch (priv->dcb_wait) { + case DCB_WAIT_CARRIER_PREENABLE_UP: + if (timeout || carrier) { + _LOGD(LOGD_DCB, "dcb_state() enabling DCB"); + nm_clear_g_source(&priv->dcb_timeout_id); + if (!dcb_enable(device)) { + priv->dcb_handle_carrier_changes = FALSE; + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED); + } + } + break; + case DCB_WAIT_CARRIER_PRECONFIG_DOWN: + nm_clear_g_source(&priv->dcb_timeout_id); + priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_UP; + + if (!carrier) { + /* Wait for the carrier to come back up */ + _LOGD(LOGD_DCB, "waiting for carrier (preconfig up)"); + priv->dcb_timeout_id = g_timeout_add_seconds(5, dcb_carrier_timeout, device); + break; + } + _LOGD(LOGD_DCB, "dcb_state() preconfig down falling through"); + /* fall-through */ + case DCB_WAIT_CARRIER_PRECONFIG_UP: + if (timeout || carrier) { + _LOGD(LOGD_DCB, "dcb_state() preconfig up configuring DCB"); + nm_clear_g_source(&priv->dcb_timeout_id); + if (!dcb_configure(device)) { + priv->dcb_handle_carrier_changes = FALSE; + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED); + } + } + break; + case DCB_WAIT_CARRIER_POSTCONFIG_DOWN: + nm_clear_g_source(&priv->dcb_timeout_id); + priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_UP; + + if (!carrier) { + /* Wait for the carrier to come back up */ + _LOGD(LOGD_DCB, "waiting for carrier (postconfig up)"); + priv->dcb_timeout_id = g_timeout_add_seconds(5, dcb_carrier_timeout, device); + break; + } + _LOGD(LOGD_DCB, "dcb_state() postconfig down falling through"); + /* fall-through */ + case DCB_WAIT_CARRIER_POSTCONFIG_UP: + if (timeout || carrier) { + _LOGD(LOGD_DCB, "dcb_state() postconfig up starting IP"); + nm_clear_g_source(&priv->dcb_timeout_id); + priv->dcb_handle_carrier_changes = FALSE; + priv->dcb_wait = DCB_WAIT_UNKNOWN; + nm_device_activate_schedule_stage3_ip_config_start(device); + } + break; + default: + g_assert_not_reached(); + } +} + +/*****************************************************************************/ + +static gboolean +wake_on_lan_enable(NMDevice *device) +{ + NMSettingWiredWakeOnLan wol; + NMSettingWired * s_wired; + const char * password = NULL; + + s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED); + + if (NM_IS_DEVICE_VETH(device)) + return FALSE; + + if (s_wired) { + wol = nm_setting_wired_get_wake_on_lan(s_wired); + password = nm_setting_wired_get_wake_on_lan_password(s_wired); + if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT) + goto found; + } + + wol = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ethernet.wake-on-lan"), + device, + NM_SETTING_WIRED_WAKE_ON_LAN_NONE, + G_MAXINT32, + NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT); + + if (NM_FLAGS_ANY(wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS) + && !nm_utils_is_power_of_two(wol)) { + nm_log_dbg(LOGD_ETHER, "invalid default value %u for wake-on-lan", (guint) wol); + wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT; + } + if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT) + goto found; + wol = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE; +found: + return nm_platform_ethtool_set_wake_on_lan(nm_device_get_platform(device), + nm_device_get_ifindex(device), + _NM_SETTING_WIRED_WAKE_ON_LAN_CAST(wol), + password); +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceEthernet * self = (NMDeviceEthernet *) device; + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSettingConnection * s_con; + const char * connection_type; + gboolean do_postpone = FALSE; + NMSettingDcb * s_dcb; + + s_con = nm_device_get_applied_setting(device, NM_TYPE_SETTING_CONNECTION); + + g_return_val_if_fail(s_con, NM_ACT_STAGE_RETURN_FAILURE); + + nm_clear_g_source(&priv->dcb_timeout_id); + priv->dcb_handle_carrier_changes = FALSE; + + /* 802.1x has to run before any IP configuration since the 802.1x auth + * process opens the port up for normal traffic. + */ + connection_type = nm_setting_connection_get_connection_type(s_con); + if (nm_streq(connection_type, NM_SETTING_WIRED_SETTING_NAME)) { + NMSetting8021x *security; + + security = nm_device_get_applied_setting(device, NM_TYPE_SETTING_802_1X); + + if (security) { + /* FIXME: for now 802.1x is mutually exclusive with DCB */ + if (!nm_device_has_carrier(NM_DEVICE(self))) { + _LOGD(LOGD_DEVICE | LOGD_ETHER, + "delay supplicant initialization until carrier goes up"); + priv->carrier_id = g_signal_connect(self, + "notify::" NM_DEVICE_CARRIER, + G_CALLBACK(carrier_changed), + self); + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + return supplicant_check_secrets_needed(self, out_failure_reason); + } + } + + wake_on_lan_enable(device); + + /* DCB and FCoE setup */ + s_dcb = nm_device_get_applied_setting(device, NM_TYPE_SETTING_DCB); + if (s_dcb) { + /* lldpad really really wants the carrier to be up */ + if (nm_platform_link_is_connected(nm_device_get_platform(device), + nm_device_get_ifindex(device))) { + if (!dcb_enable(device)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } else { + _LOGD(LOGD_DCB, "waiting for carrier (preenable up)"); + priv->dcb_wait = DCB_WAIT_CARRIER_PREENABLE_UP; + priv->dcb_timeout_id = g_timeout_add_seconds(4, dcb_carrier_timeout, device); + } + + priv->dcb_handle_carrier_changes = TRUE; + do_postpone = TRUE; + } + + /* PPPoE setup */ + if (nm_connection_is_type(nm_device_get_applied_connection(device), + NM_SETTING_PPPOE_SETTING_NAME)) { + NMSettingPpp *s_ppp; + + s_ppp = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPP); + if (s_ppp) { + guint32 mtu; + guint32 mru; + guint32 mxu; + + mtu = nm_setting_ppp_get_mtu(s_ppp); + mru = nm_setting_ppp_get_mru(s_ppp); + mxu = MAX(mru, mtu); + if (mxu) { + _LOGD(LOGD_PPP, + "set MTU to %u (PPP interface MRU %u, MTU %u)", + mxu + PPPOE_ENCAP_OVERHEAD, + mru, + mtu); + nm_platform_link_set_mtu(nm_device_get_platform(device), + nm_device_get_ifindex(device), + mxu + PPPOE_ENCAP_OVERHEAD); + } + } + } + + return do_postpone ? NM_ACT_STAGE_RETURN_POSTPONE : NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMSettingConnection *s_con; + const char * connection_type; + int ifindex; + + ifindex = nm_device_get_ifindex(device); + + if (ifindex <= 0) + return NM_ACT_STAGE_RETURN_FAILURE; + + if (addr_family == AF_INET) { + s_con = nm_device_get_applied_setting(device, NM_TYPE_SETTING_CONNECTION); + + g_return_val_if_fail(s_con, NM_ACT_STAGE_RETURN_FAILURE); + + connection_type = nm_setting_connection_get_connection_type(s_con); + if (!strcmp(connection_type, NM_SETTING_PPPOE_SETTING_NAME)) + return pppoe_stage3_ip4_config_start(NM_DEVICE_ETHERNET(device), out_failure_reason); + } + + return NM_DEVICE_CLASS(nm_device_ethernet_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + /* MTU only set for plain ethernet */ + if (NM_DEVICE_ETHERNET_GET_PRIVATE(device)->ppp_manager) + return 0; + + return nm_device_get_configured_mtu_for_wired(device, out_source, out_force); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + NMSettingDcb * s_dcb; + GError * error = NULL; + int ifindex; + + nm_clear_g_source(&priv->pppoe_wait_id); + nm_clear_g_signal_handler(self, &priv->carrier_id); + + if (priv->ppp_manager) { + nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL); + g_clear_object(&priv->ppp_manager); + } + + supplicant_interface_release(self); + + priv->dcb_wait = DCB_WAIT_UNKNOWN; + nm_clear_g_source(&priv->dcb_timeout_id); + priv->dcb_handle_carrier_changes = FALSE; + + /* Tear down DCB/FCoE if it was enabled */ + s_dcb = nm_device_get_applied_setting(device, NM_TYPE_SETTING_DCB); + if (s_dcb) { + if (!nm_dcb_cleanup(nm_device_get_iface(device), &error)) { + _LOGW(LOGD_DEVICE | LOGD_PLATFORM, "failed to disable DCB/FCoE: %s", error->message); + g_clear_error(&error); + } + } + + /* Set last PPPoE connection time */ + if (nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE)) + priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_sec(); + + ifindex = nm_device_get_ifindex(device); + if (ifindex > 0 && priv->ethtool_prev_set) { + priv->ethtool_prev_set = FALSE; + + _LOGD(LOGD_DEVICE, + "set-link: reset %snegotiation (%u Mbit, %s duplex)", + priv->ethtool_prev_autoneg ? "auto-" : "static ", + priv->ethtool_prev_speed, + nm_platform_link_duplex_type_to_string(priv->ethtool_prev_duplex)); + if (!nm_platform_ethtool_set_link_settings(nm_device_get_platform(device), + ifindex, + priv->ethtool_prev_autoneg, + priv->ethtool_prev_speed, + priv->ethtool_prev_duplex)) { + _LOGW(LOGD_DEVICE, "set-link: failure to reset link negotiation"); + return; + } + } +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingWired *s_wired; + NMSettingPppoe *s_pppoe; + + if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_VETH_SETTING_NAME)) { + NMSettingVeth *s_veth; + const char * peer_name = NULL; + const char * con_peer_name = NULL; + int ifindex; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_VETH_SETTING_NAME, + existing_connections, + NULL, + _("Veth connection"), + "veth", + NULL, + TRUE); + + s_veth = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VETH); + if (!s_veth) { + s_veth = (NMSettingVeth *) nm_setting_veth_new(); + nm_connection_add_setting(connection, NM_SETTING(s_veth)); + } + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex > 0) { + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(nm_device_get_platform(device), ifindex); + if (pllink && pllink->type == NM_LINK_TYPE_VETH && pllink->parent > 0) { + pllink = nm_platform_link_get(nm_device_get_platform(device), pllink->parent); + + if (pllink && pllink->type == NM_LINK_TYPE_VETH) { + peer_name = pllink->name; + } + } + } + + if (!peer_name) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "cannot find peer for veth device"); + return FALSE; + } + + con_peer_name = nm_setting_veth_get_peer(s_veth); + if (con_peer_name) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "mismatching veth peer \"%s\"", + con_peer_name); + return FALSE; + } else + g_object_set(s_veth, NM_SETTING_VETH_PEER, peer_name, NULL); + + return TRUE; + } + + s_pppoe = nm_connection_get_setting_pppoe(connection); + + /* We can't telepathically figure out the service name or username, so if + * those weren't given, we can't complete the connection. + */ + if (s_pppoe && !nm_setting_verify(NM_SETTING(s_pppoe), NULL, error)) + return FALSE; + + s_wired = nm_connection_get_setting_wired(connection); + if (!s_wired) { + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + } + + /* Default to an ethernet-only connection, but if a PPPoE setting was given + * then PPPoE should be our connection type. + */ + nm_utils_complete_generic( + nm_device_get_platform(device), + connection, + s_pppoe ? NM_SETTING_PPPOE_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME, + existing_connections, + NULL, + s_pppoe ? _("PPPoE connection") : _("Wired connection"), + NULL, + nm_setting_wired_get_mac_address(s_wired) ? NULL : nm_device_get_iface(device), + s_pppoe ? FALSE : TRUE); /* No IPv6 by default yet for PPPoE */ + + return TRUE; +} + +static NMConnection * +new_default_connection(NMDevice *self) +{ + NMConnection * connection; + NMSettingsConnection *const *connections; + NMSetting * setting; + gs_unref_hashtable GHashTable *existing_ids = NULL; + struct udev_device * dev; + const char * perm_hw_addr; + const char * iface; + const char * uprop = "0"; + gs_free char * defname = NULL; + gs_free char * uuid = NULL; + guint i, n_connections; + + perm_hw_addr = nm_device_get_permanent_hw_address(self); + iface = nm_device_get_iface(self); + + connection = nm_simple_connection_new(); + setting = nm_setting_connection_new(); + nm_connection_add_setting(connection, setting); + + connections = nm_settings_get_connections(nm_device_get_settings(self), &n_connections); + if (n_connections > 0) { + existing_ids = g_hash_table_new(nm_str_hash, g_str_equal); + for (i = 0; i < n_connections; i++) + g_hash_table_add(existing_ids, (char *) nm_settings_connection_get_id(connections[i])); + } + defname = nm_device_ethernet_utils_get_default_wired_name(existing_ids); + if (!defname) + return NULL; + + /* Create a stable UUID. The UUID is also the Network_ID for stable-privacy addr-gen-mode, + * thus when it changes we will also generate different IPv6 addresses. */ + uuid = _nm_utils_uuid_generate_from_strings("default-wired", + nm_utils_machine_id_str(), + defname, + perm_hw_addr ?: iface, + NULL); + + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + defname, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TIMESTAMP, + (guint64) time(NULL), + NM_SETTING_CONNECTION_INTERFACE_NAME, + iface, + NULL); + + /* Check if we should create a Link-Local only connection */ + dev = nm_platform_link_get_udev_device(nm_device_get_platform(NM_DEVICE(self)), + nm_device_get_ip_ifindex(self)); + if (dev) + uprop = udev_device_get_property_value(dev, "NM_AUTO_DEFAULT_LINK_LOCAL_ONLY"); + + if (nm_udev_utils_property_as_boolean(uprop)) { + setting = nm_setting_ip4_config_new(); + g_object_set(setting, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL, + NULL); + nm_connection_add_setting(connection, setting); + + setting = nm_setting_ip6_config_new(); + g_object_set(setting, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + nm_connection_add_setting(connection, setting); + } + + return connection; +} + +static const char * +get_s390_subchannels(NMDevice *device) +{ + nm_assert(NM_IS_DEVICE_ETHERNET(device)); + + return NM_DEVICE_ETHERNET_GET_PRIVATE(device)->subchannels; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(device); + NMSettingWired * s_wired = nm_connection_get_setting_wired(connection); + gboolean perm_hw_addr_is_fake; + const char * perm_hw_addr; + const char * mac = nm_device_get_hw_address(device); + const char * mac_prop = NM_SETTING_WIRED_MAC_ADDRESS; + GHashTableIter iter; + gpointer key, value; + + if (!s_wired) { + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, (NMSetting *) s_wired); + } + + g_object_set(nm_connection_get_setting_connection(connection), + NM_SETTING_CONNECTION_TYPE, + nm_connection_get_setting_pppoe(connection) ? NM_SETTING_PPPOE_SETTING_NAME + : NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* If the device reports a permanent address, use that for the MAC address + * and the current MAC, if different, is the cloned MAC. + */ + perm_hw_addr = nm_device_get_permanent_hw_address_full(device, TRUE, &perm_hw_addr_is_fake); + if (perm_hw_addr && !perm_hw_addr_is_fake) { + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, perm_hw_addr, NULL); + + mac_prop = NULL; + if (mac && !nm_utils_hwaddr_matches(perm_hw_addr, -1, mac, -1)) + mac_prop = NM_SETTING_WIRED_CLONED_MAC_ADDRESS; + } + + if (mac_prop && mac && nm_utils_hwaddr_valid(mac, ETH_ALEN)) + g_object_set(s_wired, mac_prop, mac, NULL); + + /* We don't set the MTU as we don't know whether it was set explicitly */ + + /* s390 */ + if (priv->subchannels_dbus) + g_object_set(s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, priv->subchannels_dbus, NULL); + if (priv->s390_nettype) + g_object_set(s_wired, NM_SETTING_WIRED_S390_NETTYPE, priv->s390_nettype, NULL); + + _nm_setting_wired_clear_s390_options(s_wired); + g_hash_table_iter_init(&iter, priv->s390_options); + while (g_hash_table_iter_next(&iter, &key, &value)) + nm_setting_wired_add_s390_option(s_wired, (const char *) key, (const char *) value); +} + +static void +link_speed_update(NMDevice *device) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + guint32 speed; + + if (!nm_platform_ethtool_get_link_settings(nm_device_get_platform(device), + nm_device_get_ifindex(device), + NULL, + &speed, + NULL)) + return; + if (priv->speed == speed) + return; + + priv->speed = speed; + _LOGD(LOGD_PLATFORM | LOGD_ETHER, "speed is now %d Mb/s", speed); + _notify(self, PROP_SPEED); +} + +static void +carrier_changed_notify(NMDevice *device, gboolean carrier) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + if (priv->dcb_handle_carrier_changes) { + nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); + + if (priv->dcb_timeout_id) { + _LOGD(LOGD_DCB, "carrier_changed() calling dcb_state()"); + dcb_state(device, FALSE); + } + } + + if (carrier) + link_speed_update(device); + + NM_DEVICE_CLASS(nm_device_ethernet_parent_class)->carrier_changed_notify(device, carrier); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_ethernet_parent_class)->link_changed(device, pllink); + if (!NM_IS_DEVICE_VETH(device) && pllink->initialized) + _update_s390_subchannels((NMDeviceEthernet *) device); +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class)->is_available(device, flags)) + return FALSE; + + return !!nm_device_get_initial_hw_address(device); +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + /* Only handle wired setting here, delegate other settings to parent class */ + if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys( + diffs, + NM_SETTING_WIRED_SETTING_NAME, + error, + NM_SETTING_WIRED_MTU, /* reapplied with IP config */ + NM_SETTING_WIRED_SPEED, + NM_SETTING_WIRED_DUPLEX, + NM_SETTING_WIRED_AUTO_NEGOTIATE, + NM_SETTING_WIRED_WAKE_ON_LAN, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD); + } + + device_class = NM_DEVICE_CLASS(nm_device_ethernet_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static void +reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device); + NMDeviceState state = nm_device_get_state(device); + + NM_DEVICE_CLASS(nm_device_ethernet_parent_class)->reapply_connection(device, con_old, con_new); + + _LOGD(LOGD_DEVICE, "reapplying wired settings"); + + if (state >= NM_DEVICE_STATE_PREPARE) + link_negotiation_set(device); + if (state >= NM_DEVICE_STATE_CONFIG) + wake_on_lan_enable(device); +} + +static void +dispose(GObject *object) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + wired_secrets_cancel(self); + + supplicant_interface_release(self); + + nm_clear_g_source(&priv->pppoe_wait_id); + + nm_clear_g_source(&priv->dcb_timeout_id); + + nm_clear_g_signal_handler(self, &priv->carrier_id); + + G_OBJECT_CLASS(nm_device_ethernet_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + g_clear_object(&priv->supplicant.mgr); + g_free(priv->subchan1); + g_free(priv->subchan2); + g_free(priv->subchan3); + g_free(priv->subchannels); + g_strfreev(priv->subchannels_dbus); + g_free(priv->s390_nettype); + g_hash_table_destroy(priv->s390_options); + + G_OBJECT_CLASS(nm_device_ethernet_parent_class)->finalize(object); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceEthernet * self = NM_DEVICE_ETHERNET(object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_SPEED: + g_value_set_uint(value, priv->speed); + break; + case PROP_S390_SUBCHANNELS: + g_value_set_boxed(value, priv->subchannels_dbus); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static const NMDBusInterfaceInfoExtended interface_info_device_wired = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WIRED, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PermHwAddress", + "s", + NM_DEVICE_PERM_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Speed", + "u", + NM_DEVICE_ETHERNET_SPEED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("S390Subchannels", + "as", + NM_DEVICE_ETHERNET_S390_SUBCHANNELS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", + "b", + NM_DEVICE_CARRIER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ethernet_class_init(NMDeviceEthernetClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMDeviceEthernetPrivate)); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wired); + + device_class->connection_type_supported = NM_SETTING_WIRED_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_ETHERNET); + + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->new_default_connection = new_default_connection; + + device_class->act_stage1_prepare_also_for_external_or_assume = TRUE; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->get_configured_mtu = get_configured_mtu; + device_class->deactivate = deactivate; + device_class->get_s390_subchannels = get_s390_subchannels; + device_class->update_connection = update_connection; + device_class->carrier_changed_notify = carrier_changed_notify; + device_class->link_changed = link_changed; + device_class->is_available = is_available; + device_class->can_reapply_change = can_reapply_change; + device_class->reapply_connection = reapply_connection; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_SPEED] = g_param_spec_uint(NM_DEVICE_ETHERNET_SPEED, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_S390_SUBCHANNELS] = + g_param_spec_boxed(NM_DEVICE_ETHERNET_S390_SUBCHANNELS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_ETHERNET_DEVICE_FACTORY (nm_ethernet_device_factory_get_type()) +#define NM_ETHERNET_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_ETHERNET_DEVICE_FACTORY, NMEthernetDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_ETHERNET, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Ethernet", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_ETHERNET, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_ETHERNET, + NULL); +} + +static gboolean +match_connection(NMDeviceFactory *factory, NMConnection *connection) +{ + const char * type = nm_connection_get_connection_type(connection); + NMSettingPppoe *s_pppoe; + + if (nm_streq(type, NM_SETTING_WIRED_SETTING_NAME)) + return TRUE; + + nm_assert(nm_streq(type, NM_SETTING_PPPOE_SETTING_NAME)); + s_pppoe = nm_connection_get_setting_pppoe(connection); + + return !nm_setting_pppoe_get_parent(s_pppoe); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + ETHERNET, + Ethernet, + ethernet, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_ETHERNET) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_PPPOE_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->match_connection = match_connection;); diff --git a/src/core/devices/nm-device-ethernet.h b/src/core/devices/nm-device-ethernet.h new file mode 100644 index 0000000..6e134d7 --- /dev/null +++ b/src/core/devices/nm-device-ethernet.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2010 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_ETHERNET_H__ +#define __NETWORKMANAGER_DEVICE_ETHERNET_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_ETHERNET (nm_device_ethernet_get_type()) +#define NM_DEVICE_ETHERNET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernet)) +#define NM_DEVICE_ETHERNET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass)) +#define NM_IS_DEVICE_ETHERNET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_ETHERNET)) +#define NM_IS_DEVICE_ETHERNET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_ETHERNET)) +#define NM_DEVICE_ETHERNET_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass)) + +#define NM_DEVICE_ETHERNET_SPEED "speed" +#define NM_DEVICE_ETHERNET_S390_SUBCHANNELS "s390-subchannels" + +struct _NMDeviceEthernetPrivate; + +typedef struct { + NMDevice parent; + struct _NMDeviceEthernetPrivate *_priv; +} NMDeviceEthernet; + +typedef struct { + NMDeviceClass parent; +} NMDeviceEthernetClass; + +GType nm_device_ethernet_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_ETHERNET_H__ */ diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c new file mode 100644 index 0000000..7034202 --- /dev/null +++ b/src/core/devices/nm-device-factory.c @@ -0,0 +1,413 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-factory.h" + +#include +#include +#include + +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-setting-bluetooth.h" + +#define PLUGIN_PREFIX "libnm-device-plugin-" + +/*****************************************************************************/ + +enum { DEVICE_ADDED, LAST_SIGNAL }; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_ABSTRACT_TYPE(NMDeviceFactory, nm_device_factory, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static void +nm_device_factory_get_supported_types(NMDeviceFactory * factory, + const NMLinkType ** out_link_types, + const char *const **out_setting_types) +{ + g_return_if_fail(NM_IS_DEVICE_FACTORY(factory)); + + NM_DEVICE_FACTORY_GET_CLASS(factory)->get_supported_types(factory, + out_link_types, + out_setting_types); +} + +void +nm_device_factory_start(NMDeviceFactory *factory) +{ + g_return_if_fail(factory != NULL); + + if (NM_DEVICE_FACTORY_GET_CLASS(factory)->start) + NM_DEVICE_FACTORY_GET_CLASS(factory)->start(factory); +} + +NMDevice * +nm_device_factory_create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore, + GError ** error) +{ + NMDeviceFactoryClass *klass; + NMDevice * device; + gboolean ignore = FALSE; + + g_return_val_if_fail(factory, NULL); + g_return_val_if_fail(iface && *iface, NULL); + if (plink) { + g_return_val_if_fail(!connection, NULL); + g_return_val_if_fail(strcmp(iface, plink->name) == 0, NULL); + nm_assert(factory == nm_device_factory_manager_find_factory_for_link_type(plink->type)); + } else if (connection) + nm_assert(factory == nm_device_factory_manager_find_factory_for_connection(connection)); + else + g_return_val_if_reached(NULL); + + klass = NM_DEVICE_FACTORY_GET_CLASS(factory); + if (!klass->create_device) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Device factory %s cannot manage new devices", + G_OBJECT_TYPE_NAME(factory)); + NM_SET_OUT(out_ignore, FALSE); + return NULL; + } + + device = klass->create_device(factory, iface, plink, connection, &ignore); + NM_SET_OUT(out_ignore, ignore); + if (!device) { + if (ignore) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Device factory %s ignores device %s", + G_OBJECT_TYPE_NAME(factory), + iface); + } else { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Device factory %s failed to create device %s", + G_OBJECT_TYPE_NAME(factory), + iface); + } + } + return device; +} + +const char * +nm_device_factory_get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + g_return_val_if_fail(factory != NULL, NULL); + g_return_val_if_fail(connection != NULL, NULL); + + if (!nm_connection_is_virtual(connection)) + return NULL; + + if (NM_DEVICE_FACTORY_GET_CLASS(factory)->get_connection_parent) + return NM_DEVICE_FACTORY_GET_CLASS(factory)->get_connection_parent(factory, connection); + return NULL; +} + +char * +nm_device_factory_get_connection_iface(NMDeviceFactory *factory, + NMConnection * connection, + const char * parent_iface, + GError ** error) +{ + NMDeviceFactoryClass *klass; + char * ifname; + + g_return_val_if_fail(factory != NULL, NULL); + g_return_val_if_fail(connection != NULL, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + klass = NM_DEVICE_FACTORY_GET_CLASS(factory); + + ifname = g_strdup(nm_connection_get_interface_name(connection)); + if (!ifname && klass->get_connection_iface) + ifname = klass->get_connection_iface(factory, connection, parent_iface); + + if (!ifname) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "failed to determine interface name: error determine name for %s", + nm_connection_get_connection_type(connection)); + return NULL; + } + + return ifname; +} + +/*****************************************************************************/ + +static void +nm_device_factory_init(NMDeviceFactory *self) +{} + +static void +nm_device_factory_class_init(NMDeviceFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + signals[DEVICE_ADDED] = g_signal_new(NM_DEVICE_FACTORY_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); +} + +/*****************************************************************************/ + +static GHashTable *factories_by_link = NULL; +static GHashTable *factories_by_setting = NULL; + +static void __attribute__((destructor)) _cleanup(void) +{ + nm_clear_pointer(&factories_by_link, g_hash_table_unref); + nm_clear_pointer(&factories_by_setting, g_hash_table_unref); +} + +NMDeviceFactory * +nm_device_factory_manager_find_factory_for_link_type(NMLinkType link_type) +{ + g_return_val_if_fail(factories_by_link, NULL); + + return g_hash_table_lookup(factories_by_link, GUINT_TO_POINTER(link_type)); +} + +NMDeviceFactory * +nm_device_factory_manager_find_factory_for_connection(NMConnection *connection) +{ + NMDeviceFactoryClass *klass; + NMDeviceFactory * factory; + const char * type; + GSList * list; + + g_return_val_if_fail(factories_by_setting, NULL); + + type = nm_connection_get_connection_type(connection); + list = g_hash_table_lookup(factories_by_setting, type); + + for (; list; list = g_slist_next(list)) { + factory = list->data; + klass = NM_DEVICE_FACTORY_GET_CLASS(factory); + if (!klass->match_connection || klass->match_connection(factory, connection)) + return factory; + } + + return NULL; +} + +void +nm_device_factory_manager_for_each_factory(NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data) +{ + GHashTableIter iter; + NMDeviceFactory *factory; + GSList * list_iter, *list = NULL; + + if (factories_by_link) { + g_hash_table_iter_init(&iter, factories_by_link); + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &factory)) { + if (!g_slist_find(list, factory)) + list = g_slist_prepend(list, factory); + } + } + + if (factories_by_setting) { + g_hash_table_iter_init(&iter, factories_by_setting); + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &list_iter)) { + for (; list_iter; list_iter = g_slist_next(list_iter)) { + if (!g_slist_find(list, list_iter->data)) + list = g_slist_prepend(list, list_iter->data); + } + } + } + + for (list_iter = list; list_iter; list_iter = list_iter->next) + callback(list_iter->data, user_data); + + g_slist_free(list); +} + +static gboolean +_add_factory(NMDeviceFactory * factory, + const char * path, + NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data) +{ + const NMLinkType * link_types = NULL; + const char *const *setting_types = NULL; + GSList * list, *list2; + int i; + + g_return_val_if_fail(factories_by_link, FALSE); + g_return_val_if_fail(factories_by_setting, FALSE); + + nm_device_factory_get_supported_types(factory, &link_types, &setting_types); + + g_return_val_if_fail((link_types && link_types[0] > NM_LINK_TYPE_UNKNOWN) + || (setting_types && setting_types[0]), + FALSE); + + for (i = 0; link_types && link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) + g_hash_table_insert(factories_by_link, + GUINT_TO_POINTER(link_types[i]), + g_object_ref(factory)); + for (i = 0; setting_types && setting_types[i]; i++) { + list = g_hash_table_lookup(factories_by_setting, (char *) setting_types[i]); + if (list) { + list2 = g_slist_append(list, g_object_ref(factory)); + nm_assert(list == list2); + } else { + list = g_slist_append(list, g_object_ref(factory)); + g_hash_table_insert(factories_by_setting, (char *) setting_types[i], list); + } + } + + callback(factory, user_data); + + nm_log(path ? LOGL_INFO : LOGL_DEBUG, + LOGD_PLATFORM, + NULL, + NULL, + "Loaded device plugin: %s (%s)", + G_OBJECT_TYPE_NAME(factory), + path ?: "internal"); + return TRUE; +} + +static void +_load_internal_factory(GType factory_gtype, + NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data) +{ + gs_unref_object NMDeviceFactory *factory = NULL; + + factory = g_object_new(factory_gtype, NULL); + _add_factory(factory, NULL, callback, user_data); +} + +static void +factories_list_unref(GSList *list) +{ + g_slist_free_full(list, g_object_unref); +} + +static void +load_factories_from_dir(const char * dirname, + NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data) +{ + NMDeviceFactory *factory; + GError * error = NULL; + char ** path, **paths; + + paths = nm_utils_read_plugin_paths(dirname, PLUGIN_PREFIX); + if (!paths) + return; + + for (path = paths; *path; path++) { + GModule * plugin; + NMDeviceFactoryCreateFunc create_func; + const char * item; + + item = strrchr(*path, '/'); + g_assert(item); + + plugin = g_module_open(*path, G_MODULE_BIND_LOCAL); + + if (!plugin) { + nm_log_warn(LOGD_PLATFORM, "(%s): failed to load plugin: %s", item, g_module_error()); + continue; + } + + if (!g_module_symbol(plugin, "nm_device_factory_create", (gpointer) &create_func)) { + nm_log_warn(LOGD_PLATFORM, + "(%s): failed to find device factory creator: %s", + item, + g_module_error()); + g_module_close(plugin); + continue; + } + + /* after loading glib types from the plugin, we cannot unload the library anymore. + * Make it resident. */ + g_module_make_resident(plugin); + + factory = create_func(&error); + if (!factory) { + nm_log_warn(LOGD_PLATFORM, + "(%s): failed to initialize device factory: %s", + item, + NM_G_ERROR_MSG(error)); + g_clear_error(&error); + continue; + } + g_clear_error(&error); + + _add_factory(factory, g_module_name(plugin), callback, user_data); + + g_object_unref(factory); + } + g_strfreev(paths); +} + +void +nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data) +{ + g_return_if_fail(factories_by_link == NULL); + g_return_if_fail(factories_by_setting == NULL); + + factories_by_link = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_object_unref); + factories_by_setting = g_hash_table_new_full(nm_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) factories_list_unref); + +#define _ADD_INTERNAL(get_type_fcn) \ + G_STMT_START \ + { \ + GType get_type_fcn(void); \ + _load_internal_factory(get_type_fcn(), callback, user_data); \ + } \ + G_STMT_END + + _ADD_INTERNAL(nm_6lowpan_device_factory_get_type); + _ADD_INTERNAL(nm_bond_device_factory_get_type); + _ADD_INTERNAL(nm_bridge_device_factory_get_type); + _ADD_INTERNAL(nm_dummy_device_factory_get_type); + _ADD_INTERNAL(nm_ethernet_device_factory_get_type); + _ADD_INTERNAL(nm_infiniband_device_factory_get_type); + _ADD_INTERNAL(nm_ip_tunnel_device_factory_get_type); + _ADD_INTERNAL(nm_macsec_device_factory_get_type); + _ADD_INTERNAL(nm_macvlan_device_factory_get_type); + _ADD_INTERNAL(nm_ppp_device_factory_get_type); + _ADD_INTERNAL(nm_tun_device_factory_get_type); + _ADD_INTERNAL(nm_veth_device_factory_get_type); + _ADD_INTERNAL(nm_vlan_device_factory_get_type); + _ADD_INTERNAL(nm_vrf_device_factory_get_type); + _ADD_INTERNAL(nm_vxlan_device_factory_get_type); + _ADD_INTERNAL(nm_wireguard_device_factory_get_type); + _ADD_INTERNAL(nm_wpan_device_factory_get_type); + + load_factories_from_dir(NMPLUGINDIR, callback, user_data); +} diff --git a/src/core/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h new file mode 100644 index 0000000..d9b5056 --- /dev/null +++ b/src/core/devices/nm-device-factory.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_FACTORY_H__ +#define __NETWORKMANAGER_DEVICE_FACTORY_H__ + +#include "nm-dbus-interface.h" +#include "nm-device.h" + +/* WARNING: this file is private API between NetworkManager and its internal + * device plugins. Its API can change at any time and is not guaranteed to be + * stable. NM and device plugins are distributed together and this API is + * not meant to enable third-party plugins. + */ + +#define NM_TYPE_DEVICE_FACTORY (nm_device_factory_get_type()) +#define NM_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactory)) +#define NM_DEVICE_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_FACTORY, NMDeviceFactoryClass)) +#define NM_IS_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_FACTORY)) +#define NM_IS_DEVICE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_FACTORY)) +#define NM_DEVICE_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactoryClass)) + +#define NM_DEVICE_FACTORY_DEVICE_ADDED "device-added" + +typedef struct { + GObject parent; +} NMDeviceFactory; + +typedef struct { + GObjectClass parent; + + /** + * get_supported_types: + * @factory: the #NMDeviceFactory + * @out_link_types: on return, a %NM_LINK_TYPE_NONE terminated + * list of #NMLinkType that the plugin supports + * @out_setting_types: on return, a %NULL terminated list of + * base-type #NMSetting names that the plugin can create devices for + * + * Returns the #NMLinkType and #NMSetting names that this plugin + * supports. This function MUST be implemented. + */ + void (*get_supported_types)(NMDeviceFactory * factory, + const NMLinkType ** out_link_types, + const char *const **out_setting_types); + + /** + * start: + * @factory: the #NMDeviceFactory + * + * Start the factory and discover any existing devices that the factory + * can manage. + */ + void (*start)(NMDeviceFactory *factory); + + /** + * match_connection: + * @connection: the #NMConnection + * + * Check if the factory supports the given connection. + */ + gboolean (*match_connection)(NMDeviceFactory *factory, NMConnection *connection); + + /** + * get_connection_parent: + * @factory: the #NMDeviceFactory + * @connection: the #NMConnection to return the parent name for, if supported + * + * Given a connection, returns the parent interface name, parent connection + * UUID, or parent device permanent hardware address for @connection. + * + * Returns: the parent interface name, parent connection UUID, parent + * device permanent hardware address, or %NULL + */ + const char *(*get_connection_parent)(NMDeviceFactory *factory, NMConnection *connection); + + /** + * get_connection_iface: + * @factory: the #NMDeviceFactory + * @connection: the #NMConnection to return the interface name for + * @parent_iface: optional parent interface name for virtual devices + * + * Given a connection, returns the interface name that a device activating + * that connection would have. + * + * Returns: the interface name, or %NULL + */ + char *(*get_connection_iface)(NMDeviceFactory *factory, + NMConnection * connection, + const char * parent_iface); + + /** + * create_device: + * @factory: the #NMDeviceFactory + * @iface: the interface name of the device + * @plink: the #NMPlatformLink if backed by a kernel device + * @connection: the #NMConnection if not backed by a kernel device + * @out_ignore: on return, %TRUE if the link should be ignored + * + * The plugin should create a new unrealized device using the details given + * by @iface and @plink or @connection. If both @iface and @plink are given, + * they are guaranteed to match. If both @iface and @connection are given, + * @iface is guaranteed to be the interface name that @connection specifies. + * + * If the plugin cannot create a #NMDevice for the link and wants the + * core to ignore it, set @out_ignore to %TRUE and return %NULL. + * + * Returns: the new unrealized #NMDevice, or %NULL + */ + NMDevice *(*create_device)(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore); + +} NMDeviceFactoryClass; + +GType nm_device_factory_get_type(void); + +/*****************************************************************************/ + +/** + * nm_device_factory_create: + * @error: an error if creation of the factory failed, or %NULL + * + * Creates a #GObject that implements the #NMDeviceFactory interface. This + * function must not emit any signals or perform any actions that would cause + * devices or components to be created immediately. Instead these should be + * deferred to the "start" interface method. + * + * Returns: the #GObject implementing #NMDeviceFactory or %NULL + */ +NMDeviceFactory *nm_device_factory_create(GError **error); + +/* Should match nm_device_factory_create() */ +typedef NMDeviceFactory *(*NMDeviceFactoryCreateFunc)(GError **error); + +/*****************************************************************************/ + +const char *nm_device_factory_get_connection_parent(NMDeviceFactory *factory, + NMConnection * connection); + +char *nm_device_factory_get_connection_iface(NMDeviceFactory *factory, + NMConnection * connection, + const char * parent_iface, + GError ** error); + +void nm_device_factory_start(NMDeviceFactory *factory); + +NMDevice *nm_device_factory_create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore, + GError ** error); + +#define NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(...) \ + { \ + static NMLinkType const _link_types_declared[] = {__VA_ARGS__, NM_LINK_TYPE_NONE}; \ + _link_types = _link_types_declared; \ + } +#define NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(...) \ + { \ + static const char *const _setting_types_declared[] = {__VA_ARGS__, NULL}; \ + _setting_types = _setting_types_declared; \ + } + +#define NM_DEVICE_FACTORY_DECLARE_TYPES(...) \ + static void get_supported_types(NMDeviceFactory * factory, \ + const NMLinkType ** out_link_types, \ + const char *const **out_setting_types) \ + { \ + static NMLinkType const _link_types_null[1] = {NM_LINK_TYPE_NONE}; \ + static const char *const _setting_types_null[1] = {NULL}; \ + \ + const NMLinkType * _link_types = _link_types_null; \ + const char *const *_setting_types = _setting_types_null; \ + \ + { \ + __VA_ARGS__; \ + } \ + \ + NM_SET_OUT(out_link_types, _link_types); \ + NM_SET_OUT(out_setting_types, _setting_types); \ + } + +/************************************************************************** + * INTERNAL DEVICE FACTORY FUNCTIONS - devices provided by plugins should + * not use these functions. + **************************************************************************/ + +#define NM_DEVICE_FACTORY_DEFINE_INTERNAL(upper, mixed, lower, st_code, dfi_code) \ + typedef struct { \ + NMDeviceFactory parent; \ + } NM##mixed##DeviceFactory; \ + typedef struct { \ + NMDeviceFactoryClass parent; \ + } NM##mixed##DeviceFactoryClass; \ + \ + GType nm_##lower##_device_factory_get_type(void); \ + \ + G_DEFINE_TYPE(NM##mixed##DeviceFactory, nm_##lower##_device_factory, NM_TYPE_DEVICE_FACTORY) \ + \ + NM_DEVICE_FACTORY_DECLARE_TYPES(st_code) \ + \ + static void nm_##lower##_device_factory_init(NM##mixed##DeviceFactory *self) {} \ + \ + static void nm_##lower##_device_factory_class_init(NM##mixed##DeviceFactoryClass *klass) \ + { \ + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); \ + \ + factory_class->get_supported_types = get_supported_types; \ + dfi_code \ + } + +/************************************************************************** + * PRIVATE FACTORY FUNCTIONS - for factory consumers (eg, NMManager). + **************************************************************************/ + +typedef void (*NMDeviceFactoryManagerFactoryFunc)(NMDeviceFactory *factory, gpointer user_data); + +void nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data); + +NMDeviceFactory *nm_device_factory_manager_find_factory_for_link_type(NMLinkType link_type); + +NMDeviceFactory *nm_device_factory_manager_find_factory_for_connection(NMConnection *connection); + +void nm_device_factory_manager_for_each_factory(NMDeviceFactoryManagerFactoryFunc callback, + gpointer user_data); + +#endif /* __NETWORKMANAGER_DEVICE_FACTORY_H__ */ diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c new file mode 100644 index 0000000..17ff407 --- /dev/null +++ b/src/core/devices/nm-device-generic.c @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-generic.h" + +#include "nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-core-internal.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_TYPE_DESCRIPTION, ); + +typedef struct { + char *type_description; +} NMDeviceGenericPrivate; + +struct _NMDeviceGeneric { + NMDevice parent; + NMDeviceGenericPrivate _priv; +}; + +struct _NMDeviceGenericClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceGeneric, nm_device_generic, NM_TYPE_DEVICE) + +#define NM_DEVICE_GENERIC_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceGeneric, NM_IS_DEVICE_GENERIC, NMDevice) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + int ifindex = nm_device_get_ifindex(device); + + if (ifindex > 0 + && nm_platform_link_supports_carrier_detect(nm_device_get_platform(device), ifindex)) + return NM_DEVICE_CAP_CARRIER_DETECT; + else + return NM_DEVICE_CAP_NONE; +} + +static const char * +get_type_description(NMDevice *device) +{ + if (NM_DEVICE_GENERIC_GET_PRIVATE(device)->type_description) + return NM_DEVICE_GENERIC_GET_PRIVATE(device)->type_description; + return NM_DEVICE_CLASS(nm_device_generic_parent_class)->get_type_description(device); +} + +static void +realize_start_notify(NMDevice *device, const NMPlatformLink *plink) +{ + NMDeviceGeneric * self = NM_DEVICE_GENERIC(device); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); + int ifindex; + + NM_DEVICE_CLASS(nm_device_generic_parent_class)->realize_start_notify(device, plink); + + nm_clear_g_free(&priv->type_description); + ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self)); + if (ifindex > 0) + priv->type_description = + g_strdup(nm_platform_link_get_type_name(nm_device_get_platform(device), ifindex)); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingConnection *s_con; + + if (!NM_DEVICE_CLASS(nm_device_generic_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_con = nm_connection_get_setting_connection(connection); + if (!nm_setting_connection_get_interface_name(s_con)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "generic profiles need an interface name"); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + + if (!nm_connection_get_setting_generic(connection)) + nm_connection_add_setting(connection, nm_setting_generic_new()); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_INTERFACE_NAME, + nm_device_get_iface(device), + NULL); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceGeneric * self = NM_DEVICE_GENERIC(object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_TYPE_DESCRIPTION: + g_value_set_string(value, priv->type_description); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceGeneric * self = NM_DEVICE_GENERIC(object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_TYPE_DESCRIPTION: + priv->type_description = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_generic_init(NMDeviceGeneric *self) +{} + +static GObject * +constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS(nm_device_generic_parent_class) + ->constructor(type, n_construct_params, construct_params); + + nm_device_set_unmanaged_flags((NMDevice *) object, NM_UNMANAGED_BY_DEFAULT, TRUE); + + return object; +} + +NMDevice * +nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing) +{ + g_return_val_if_fail(plink != NULL, NULL); + + return g_object_new(NM_TYPE_DEVICE_GENERIC, + NM_DEVICE_IFACE, + plink->name, + NM_DEVICE_TYPE_DESC, + "Generic", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_GENERIC, + NM_DEVICE_NM_PLUGIN_MISSING, + nm_plugin_missing, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceGeneric * self = NM_DEVICE_GENERIC(object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self); + + nm_clear_g_free(&priv->type_description); + + G_OBJECT_CLASS(nm_device_generic_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_generic = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_GENERIC, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "TypeDescription", + "s", + NM_DEVICE_GENERIC_TYPE_DESCRIPTION), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_generic_class_init(NMDeviceGenericClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructor = constructor; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_generic); + + device_class->connection_type_supported = NM_SETTING_GENERIC_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_GENERIC_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_ANY); + + device_class->realize_start_notify = realize_start_notify; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_type_description = get_type_description; + device_class->check_connection_compatible = check_connection_compatible; + device_class->update_connection = update_connection; + + obj_properties[PROP_TYPE_DESCRIPTION] = + g_param_spec_string(NM_DEVICE_GENERIC_TYPE_DESCRIPTION, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/nm-device-generic.h b/src/core/devices/nm-device-generic.h new file mode 100644 index 0000000..48c4352 --- /dev/null +++ b/src/core/devices/nm-device-generic.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_GENERIC_H__ +#define __NETWORKMANAGER_DEVICE_GENERIC_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_GENERIC (nm_device_generic_get_type()) +#define NM_DEVICE_GENERIC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_GENERIC, NMDeviceGeneric)) +#define NM_DEVICE_GENERIC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_GENERIC, NMDeviceGenericClass)) +#define NM_IS_DEVICE_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_GENERIC)) +#define NM_IS_DEVICE_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_GENERIC)) +#define NM_DEVICE_GENERIC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_GENERIC, NMDeviceGenericClass)) + +#define NM_DEVICE_GENERIC_TYPE_DESCRIPTION "type-description" + +typedef struct _NMDeviceGeneric NMDeviceGeneric; +typedef struct _NMDeviceGenericClass NMDeviceGenericClass; + +GType nm_device_generic_get_type(void); + +NMDevice *nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing); + +#endif /* __NETWORKMANAGER_DEVICE_GENERIC_H__ */ diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c new file mode 100644 index 0000000..af1a49b --- /dev/null +++ b/src/core/devices/nm-device-infiniband.c @@ -0,0 +1,504 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-infiniband.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-core-internal.h" + +#define NM_DEVICE_INFINIBAND_IS_PARTITION "is-partition" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_IS_PARTITION, ); + +typedef struct { + gboolean is_partition; + int parent_ifindex; + int p_key; +} NMDeviceInfinibandPrivate; + +struct _NMDeviceInfiniband { + NMDevice parent; + NMDeviceInfinibandPrivate _priv; +}; + +struct _NMDeviceInfinibandClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceInfiniband, nm_device_infiniband, NM_TYPE_DEVICE) + +#define NM_DEVICE_INFINIBAND_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceInfiniband, NM_IS_DEVICE_INFINIBAND, NMDevice) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + guint32 caps = NM_DEVICE_CAP_CARRIER_DETECT; + + if (NM_DEVICE_INFINIBAND_GET_PRIVATE(device)->is_partition) + caps |= NM_DEVICE_CAP_IS_SOFTWARE; + + return caps; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + nm_auto_close int dirfd = -1; + NMSettingInfiniband *s_infiniband; + char ifname_verified[IFNAMSIZ]; + const char * transport_mode; + gboolean ok; + + s_infiniband = nm_device_get_applied_setting(device, NM_TYPE_SETTING_INFINIBAND); + + g_return_val_if_fail(s_infiniband, NM_ACT_STAGE_RETURN_FAILURE); + + transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband); + + dirfd = nm_platform_sysctl_open_netdir(nm_device_get_platform(device), + nm_device_get_ifindex(device), + ifname_verified); + if (dirfd < 0) { + if (nm_streq(transport_mode, "datagram")) + return NM_ACT_STAGE_RETURN_SUCCESS; + else { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_INFINIBAND_MODE); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + /* With some drivers the interface must be down to set transport mode */ + nm_device_take_down(device, TRUE); + ok = nm_platform_sysctl_set(nm_device_get_platform(device), + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"), + transport_mode); + nm_device_bring_up(device, TRUE, NULL); + + if (!ok) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + return nm_device_get_configured_mtu_from_connection(device, + NM_TYPE_SETTING_INFINIBAND, + out_source); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingInfiniband *s_infiniband; + + if (!NM_DEVICE_CLASS(nm_device_infiniband_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_device_is_real(device)) { + const char *mac; + const char *hw_addr; + + s_infiniband = nm_connection_get_setting_infiniband(connection); + + mac = nm_setting_infiniband_get_mac_address(s_infiniband); + if (mac) { + hw_addr = nm_device_get_permanent_hw_address(device); + if (!hw_addr || !nm_utils_hwaddr_matches(mac, -1, hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "MAC address mismatches"); + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingInfiniband *s_infiniband; + + s_infiniband = nm_connection_get_setting_infiniband(connection); + if (!s_infiniband) { + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new(); + nm_connection_add_setting(connection, NM_SETTING(s_infiniband)); + } + + nm_utils_complete_generic( + nm_device_get_platform(device), + connection, + NM_SETTING_INFINIBAND_SETTING_NAME, + existing_connections, + NULL, + _("InfiniBand connection"), + NULL, + nm_setting_infiniband_get_mac_address(s_infiniband) ? NULL : nm_device_get_iface(device), + TRUE); + + if (!nm_setting_infiniband_get_transport_mode(s_infiniband)) + g_object_set(G_OBJECT(s_infiniband), + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "datagram", + NULL); + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMSettingInfiniband *s_infiniband = nm_connection_get_setting_infiniband(connection); + const char * mac = nm_device_get_permanent_hw_address(device); + const char * transport_mode = "datagram"; + int ifindex; + + if (!s_infiniband) { + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new(); + nm_connection_add_setting(connection, (NMSetting *) s_infiniband); + } + + if (mac && !nm_utils_hwaddr_matches(mac, -1, NULL, INFINIBAND_ALEN)) + g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL); + + ifindex = nm_device_get_ifindex(device); + if (ifindex > 0) { + if (!nm_platform_link_infiniband_get_properties(nm_device_get_platform(device), + ifindex, + NULL, + NULL, + &transport_mode)) + transport_mode = "datagram"; + } + g_object_set(G_OBJECT(s_infiniband), + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + transport_mode, + NULL); +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + if (nm_streq(setting_name, NM_SETTING_INFINIBAND_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys( + diffs, + NM_SETTING_INFINIBAND_SETTING_NAME, + error, + NM_SETTING_INFINIBAND_MTU); /* reapplied with IP config */ + } + + device_class = NM_DEVICE_CLASS(nm_device_infiniband_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE(device); + NMSettingInfiniband * s_infiniband; + int r; + + s_infiniband = nm_connection_get_setting_infiniband(connection); + g_assert(s_infiniband); + + /* Can only create partitions at this time */ + priv->p_key = nm_setting_infiniband_get_p_key(s_infiniband); + if (priv->p_key < 0) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "only InfiniBand partitions can be created"); + return FALSE; + } + + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "InfiniBand partitions can not be created without a parent interface"); + return FALSE; + } + + if (!NM_IS_DEVICE_INFINIBAND(parent)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "Parent interface %s must be an InfiniBand interface", + nm_device_get_iface(parent)); + return FALSE; + } + + priv->parent_ifindex = nm_device_get_ifindex(parent); + if (priv->parent_ifindex <= 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "failed to get InfiniBand parent %s ifindex", + nm_device_get_iface(parent)); + return FALSE; + } + + r = nm_platform_link_infiniband_add(nm_device_get_platform(device), + priv->parent_ifindex, + priv->p_key, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create InfiniBand P_Key interface '%s' for '%s': %s", + nm_device_get_iface(device), + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + priv->is_partition = TRUE; + return TRUE; +} + +static gboolean +unrealize(NMDevice *device, GError **error) +{ + NMDeviceInfinibandPrivate *priv; + int r; + + g_return_val_if_fail(NM_IS_DEVICE_INFINIBAND(device), FALSE); + + priv = NM_DEVICE_INFINIBAND_GET_PRIVATE(device); + + if (priv->p_key < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Only InfiniBand partitions can be removed"); + return FALSE; + } + + r = nm_platform_link_infiniband_delete(nm_device_get_platform(device), + priv->parent_ifindex, + priv->p_key); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to remove InfiniBand P_Key interface '%s': %s", + nm_device_get_iface(device), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_IS_PARTITION: + g_value_set_boolean(value, NM_DEVICE_INFINIBAND_GET_PRIVATE(object)->is_partition); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_IS_PARTITION: + NM_DEVICE_INFINIBAND_GET_PRIVATE(object)->is_partition = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_infiniband_init(NMDeviceInfiniband *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_infiniband = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_INFINIBAND, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", + "b", + NM_DEVICE_CARRIER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_infiniband_class_init(NMDeviceInfinibandClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_infiniband); + + device_class->connection_type_supported = NM_SETTING_INFINIBAND_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_INFINIBAND_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_INFINIBAND); + + device_class->can_reapply_change = can_reapply_change; + device_class->create_and_realize = create_and_realize; + device_class->unrealize = unrealize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->update_connection = update_connection; + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->get_configured_mtu = get_configured_mtu; + + obj_properties[PROP_IS_PARTITION] = + g_param_spec_boolean(NM_DEVICE_INFINIBAND_IS_PARTITION, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_INFINIBAND_DEVICE_FACTORY (nm_infiniband_device_factory_get_type()) +#define NM_INFINIBAND_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NM_TYPE_INFINIBAND_DEVICE_FACTORY, \ + NMInfinibandDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + gboolean is_partition = FALSE; + + if (plink) + is_partition = (plink->parent > 0 || plink->parent == NM_PLATFORM_LINK_OTHER_NETNS); + else if (connection) { + NMSettingInfiniband *s_infiniband; + + s_infiniband = nm_connection_get_setting_infiniband(connection); + g_return_val_if_fail(s_infiniband, NULL); + is_partition = !!nm_setting_infiniband_get_parent(s_infiniband) + || (nm_setting_infiniband_get_p_key(s_infiniband) >= 0 + && nm_setting_infiniband_get_mac_address(s_infiniband)); + } + + return g_object_new(NM_TYPE_DEVICE_INFINIBAND, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "InfiniBand", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_INFINIBAND, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_INFINIBAND, + /* NOTE: Partition should probably be a different link type! */ + NM_DEVICE_INFINIBAND_IS_PARTITION, + is_partition, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingInfiniband *s_infiniband; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_INFINIBAND_SETTING_NAME), + NULL); + + s_infiniband = nm_connection_get_setting_infiniband(connection); + g_assert(s_infiniband); + + return nm_setting_infiniband_get_parent(s_infiniband); +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + NMSettingInfiniband *s_infiniband; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_INFINIBAND_SETTING_NAME), + NULL); + + s_infiniband = nm_connection_get_setting_infiniband(connection); + g_assert(s_infiniband); + + if (!parent_iface) + return NULL; + + g_return_val_if_fail(g_strcmp0(parent_iface, nm_setting_infiniband_get_parent(s_infiniband)) + == 0, + NULL); + + return g_strdup(nm_setting_infiniband_get_virtual_interface_name(s_infiniband)); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + INFINIBAND, + Infiniband, + infiniband, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_INFINIBAND) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_INFINIBAND_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-infiniband.h b/src/core/devices/nm-device-infiniband.h new file mode 100644 index 0000000..69994a2 --- /dev/null +++ b/src/core/devices/nm-device-infiniband.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_INFINIBAND_H__ +#define __NETWORKMANAGER_DEVICE_INFINIBAND_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_INFINIBAND (nm_device_infiniband_get_type()) +#define NM_DEVICE_INFINIBAND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfiniband)) +#define NM_DEVICE_INFINIBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandClass)) +#define NM_IS_DEVICE_INFINIBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_INFINIBAND)) +#define NM_IS_DEVICE_INFINIBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_INFINIBAND)) +#define NM_DEVICE_INFINIBAND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandClass)) + +typedef struct _NMDeviceInfiniband NMDeviceInfiniband; +typedef struct _NMDeviceInfinibandClass NMDeviceInfinibandClass; + +GType nm_device_infiniband_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_INFINIBAND_H__ */ diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c new file mode 100644 index 0000000..9124c44 --- /dev/null +++ b/src/core/devices/nm-device-ip-tunnel.c @@ -0,0 +1,1299 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ip-tunnel.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-device-private.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-core-internal.h" +#include "settings/nm-settings.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceIPTunnel +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIPTunnel, + PROP_MODE, + PROP_LOCAL, + PROP_REMOTE, + PROP_TTL, + PROP_TOS, + PROP_PATH_MTU_DISCOVERY, + PROP_INPUT_KEY, + PROP_OUTPUT_KEY, + PROP_ENCAPSULATION_LIMIT, + PROP_FLOW_LABEL, + PROP_FLAGS, ); + +typedef struct { + NMIPTunnelMode mode; + char * local; + char * remote; + guint8 ttl; + guint8 tos; + gboolean path_mtu_discovery; + int addr_family; + char * input_key; + char * output_key; + guint8 encap_limit; + guint32 flow_label; + NMIPTunnelFlags flags; +} NMDeviceIPTunnelPrivate; + +struct _NMDeviceIPTunnel { + NMDevice parent; + NMDeviceIPTunnelPrivate _priv; +}; + +struct _NMDeviceIPTunnelClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceIPTunnel, nm_device_ip_tunnel, NM_TYPE_DEVICE) + +#define NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceIPTunnel, NM_IS_DEVICE_IP_TUNNEL, NMDevice) + +/*****************************************************************************/ + +static guint32 +ip6tnl_flags_setting_to_plat(NMIPTunnelFlags flags) +{ + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT == IP6_TNL_F_IGN_ENCAP_LIMIT); + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS == IP6_TNL_F_USE_ORIG_TCLASS); + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL == IP6_TNL_F_USE_ORIG_FLOWLABEL); + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV == IP6_TNL_F_MIP6_DEV); + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY == IP6_TNL_F_RCV_DSCP_COPY); + G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK == IP6_TNL_F_USE_ORIG_FWMARK); + + /* NOTE: "accidentally", the numeric values correspond. + * For flags added in the future, that might no longer + * be the case. */ + return flags & _NM_IP_TUNNEL_FLAG_ALL_IP6TNL; +} + +static NMIPTunnelFlags +ip6tnl_flags_plat_to_setting(guint32 flags) +{ + return flags & ((guint32) _NM_IP_TUNNEL_FLAG_ALL_IP6TNL); +} + +/*****************************************************************************/ + +static gboolean +address_equal_pp(int addr_family, const char *a, const char *b) +{ + const NMIPAddr *addr_a = &nm_ip_addr_zero; + const NMIPAddr *addr_b = &nm_ip_addr_zero; + NMIPAddr addr_a_val; + NMIPAddr addr_b_val; + + nm_assert_addr_family(addr_family); + + if (a) { + if (!nm_utils_parse_inaddr_bin(addr_family, a, NULL, &addr_a_val)) + nm_assert_not_reached(); + addr_a = &addr_a_val; + } + if (b) { + if (!nm_utils_parse_inaddr_bin(addr_family, b, NULL, &addr_b_val)) + nm_assert_not_reached(); + addr_b = &addr_b_val; + } + + return nm_ip_addr_equal(addr_family, addr_a, addr_b); +} + +static gboolean +address_set(int addr_family, char **p_addr, const NMIPAddr *addr_new) +{ + nm_assert_addr_family(addr_family); + nm_assert(p_addr); + nm_assert(!*p_addr || nm_utils_ipaddr_is_normalized(addr_family, *p_addr)); + + if (!addr_new || nm_ip_addr_is_null(addr_family, addr_new)) { + if (nm_clear_g_free(p_addr)) + return TRUE; + return FALSE; + } + + if (*p_addr) { + NMIPAddr addr_val; + + if (!nm_utils_parse_inaddr_bin(addr_family, *p_addr, NULL, &addr_val)) + nm_assert_not_reached(); + + if (nm_ip_addr_equal(addr_family, &addr_val, addr_new)) + return FALSE; + + g_free(*p_addr); + } + + *p_addr = nm_utils_inet_ntop_dup(addr_family, addr_new); + return TRUE; +} + +static void +update_properties_from_ifindex(NMDevice *device, int ifindex) +{ + NMDeviceIPTunnel * self = NM_DEVICE_IP_TUNNEL(device); + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); + int parent_ifindex = 0; + NMIPAddr local = NM_IP_ADDR_INIT; + NMIPAddr remote = NM_IP_ADDR_INIT; + guint8 ttl = 0; + guint8 tos = 0; + guint8 encap_limit = 0; + gboolean pmtud = FALSE; + guint32 flow_label = 0; + NMIPTunnelFlags flags = NM_IP_TUNNEL_FLAG_NONE; + char * key; + + if (ifindex <= 0) { +clear: + nm_device_parent_set_ifindex(device, 0); + if (priv->local) { + nm_clear_g_free(&priv->local); + _notify(self, PROP_LOCAL); + } + if (priv->remote) { + nm_clear_g_free(&priv->remote); + _notify(self, PROP_REMOTE); + } + if (priv->input_key) { + nm_clear_g_free(&priv->input_key); + _notify(self, PROP_INPUT_KEY); + } + if (priv->output_key) { + nm_clear_g_free(&priv->output_key); + _notify(self, PROP_OUTPUT_KEY); + } + + goto out; + } + + if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_GRE, NM_IP_TUNNEL_MODE_GRETAP)) { + const NMPlatformLnkGre *lnk; + + if (priv->mode == NM_IP_TUNNEL_MODE_GRE) + lnk = nm_platform_link_get_lnk_gre(nm_device_get_platform(device), ifindex, NULL); + else + lnk = nm_platform_link_get_lnk_gretap(nm_device_get_platform(device), ifindex, NULL); + if (!lnk) { + _LOGW(LOGD_PLATFORM, "could not read %s properties", "gre"); + goto clear; + } + + parent_ifindex = lnk->parent_ifindex; + local.addr4 = lnk->local; + remote.addr4 = lnk->remote; + ttl = lnk->ttl; + tos = lnk->tos; + pmtud = lnk->path_mtu_discovery; + + if (NM_FLAGS_HAS(lnk->input_flags, NM_GRE_KEY)) { + key = g_strdup_printf("%u", lnk->input_key); + if (g_strcmp0(priv->input_key, key)) { + g_free(priv->input_key); + priv->input_key = key; + _notify(self, PROP_INPUT_KEY); + } else + g_free(key); + } else { + if (priv->input_key) { + nm_clear_g_free(&priv->input_key); + _notify(self, PROP_INPUT_KEY); + } + } + + if (NM_FLAGS_HAS(lnk->output_flags, NM_GRE_KEY)) { + key = g_strdup_printf("%u", lnk->output_key); + if (g_strcmp0(priv->output_key, key)) { + g_free(priv->output_key); + priv->output_key = key; + _notify(self, PROP_OUTPUT_KEY); + } else + g_free(key); + } else { + if (priv->output_key) { + nm_clear_g_free(&priv->output_key); + _notify(self, PROP_OUTPUT_KEY); + } + } + } else if (priv->mode == NM_IP_TUNNEL_MODE_SIT) { + const NMPlatformLnkSit *lnk; + + lnk = nm_platform_link_get_lnk_sit(nm_device_get_platform(device), ifindex, NULL); + if (!lnk) { + _LOGW(LOGD_PLATFORM, "could not read %s properties", "sit"); + goto clear; + } + + parent_ifindex = lnk->parent_ifindex; + local.addr4 = lnk->local; + remote.addr4 = lnk->remote; + ttl = lnk->ttl; + tos = lnk->tos; + pmtud = lnk->path_mtu_discovery; + } else if (priv->mode == NM_IP_TUNNEL_MODE_IPIP) { + const NMPlatformLnkIpIp *lnk; + + lnk = nm_platform_link_get_lnk_ipip(nm_device_get_platform(device), ifindex, NULL); + if (!lnk) { + _LOGW(LOGD_PLATFORM, "could not read %s properties", "ipip"); + goto clear; + } + + parent_ifindex = lnk->parent_ifindex; + local.addr4 = lnk->local; + remote.addr4 = lnk->remote; + ttl = lnk->ttl; + tos = lnk->tos; + pmtud = lnk->path_mtu_discovery; + } else if (NM_IN_SET(priv->mode, + NM_IP_TUNNEL_MODE_IPIP6, + NM_IP_TUNNEL_MODE_IP6IP6, + NM_IP_TUNNEL_MODE_IP6GRE, + NM_IP_TUNNEL_MODE_IP6GRETAP)) { + const NMPlatformLnkIp6Tnl *lnk; + NMPlatform * plat = nm_device_get_platform(device); + + if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRE) + lnk = nm_platform_link_get_lnk_ip6gre(plat, ifindex, NULL); + else if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRETAP) + lnk = nm_platform_link_get_lnk_ip6gretap(plat, ifindex, NULL); + else + lnk = nm_platform_link_get_lnk_ip6tnl(plat, ifindex, NULL); + + if (!lnk) { + _LOGW(LOGD_PLATFORM, "could not read %s properties", "ip6tnl"); + goto clear; + } + + parent_ifindex = lnk->parent_ifindex; + local.addr6 = lnk->local; + remote.addr6 = lnk->remote; + ttl = lnk->ttl; + tos = lnk->tclass; + encap_limit = lnk->encap_limit; + flow_label = lnk->flow_label; + flags = ip6tnl_flags_plat_to_setting(lnk->flags); + + if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_IP6GRE, NM_IP_TUNNEL_MODE_IP6GRETAP)) { + if (NM_FLAGS_HAS(lnk->input_flags, NM_GRE_KEY)) { + key = g_strdup_printf("%u", lnk->input_key); + if (g_strcmp0(priv->input_key, key)) { + g_free(priv->input_key); + priv->input_key = key; + _notify(self, PROP_INPUT_KEY); + } else + g_free(key); + } else { + if (priv->input_key) { + nm_clear_g_free(&priv->input_key); + _notify(self, PROP_INPUT_KEY); + } + } + + if (NM_FLAGS_HAS(lnk->output_flags, NM_GRE_KEY)) { + key = g_strdup_printf("%u", lnk->output_key); + if (g_strcmp0(priv->output_key, key)) { + g_free(priv->output_key); + priv->output_key = key; + _notify(self, PROP_OUTPUT_KEY); + } else + g_free(key); + } else { + if (priv->output_key) { + nm_clear_g_free(&priv->output_key); + _notify(self, PROP_OUTPUT_KEY); + } + } + } + } else + g_return_if_reached(); + + nm_device_parent_set_ifindex(device, parent_ifindex); + + if (address_set(priv->addr_family, &priv->local, &local)) + _notify(self, PROP_LOCAL); + if (address_set(priv->addr_family, &priv->remote, &remote)) + _notify(self, PROP_REMOTE); + +out: + + if (priv->ttl != ttl) { + priv->ttl = ttl; + _notify(self, PROP_TTL); + } + + if (priv->tos != tos) { + priv->tos = tos; + _notify(self, PROP_TOS); + } + + if (priv->path_mtu_discovery != pmtud) { + priv->path_mtu_discovery = pmtud; + _notify(self, PROP_PATH_MTU_DISCOVERY); + } + + if (priv->encap_limit != encap_limit) { + priv->encap_limit = encap_limit; + _notify(self, PROP_ENCAPSULATION_LIMIT); + } + + if (priv->flow_label != flow_label) { + priv->flow_label = flow_label; + _notify(self, PROP_FLOW_LABEL); + } + + if (priv->flags != flags) { + priv->flags = flags; + _notify(self, PROP_FLAGS); + } +} + +static void +update_properties(NMDevice *device) +{ + update_properties_from_ifindex(device, nm_device_get_ifindex(device)); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingIPTunnel *s_ip_tunnel; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_IP_TUNNEL_SETTING_NAME, + existing_connections, + NULL, + _("IP tunnel connection"), + NULL, + NULL, + TRUE); + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + if (!s_ip_tunnel) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'tunnel' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceIPTunnel * self = NM_DEVICE_IP_TUNNEL(device); + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); + NMSettingIPTunnel * s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + + if (!s_ip_tunnel) { + s_ip_tunnel = (NMSettingIPTunnel *) nm_setting_ip_tunnel_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip_tunnel); + } + + if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) != priv->mode) + g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_MODE, priv->mode, NULL); + + g_object_set( + s_ip_tunnel, + NM_SETTING_IP_TUNNEL_PARENT, + nm_device_parent_find_for_connection(device, nm_setting_ip_tunnel_get_parent(s_ip_tunnel)), + NULL); + + if (!address_equal_pp(priv->addr_family, + nm_setting_ip_tunnel_get_local(s_ip_tunnel), + priv->local)) + g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_LOCAL, priv->local, NULL); + + if (!address_equal_pp(priv->addr_family, + nm_setting_ip_tunnel_get_remote(s_ip_tunnel), + priv->remote)) + g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_REMOTE, priv->remote, NULL); + + if (nm_setting_ip_tunnel_get_ttl(s_ip_tunnel) != priv->ttl) + g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_TTL, priv->ttl, NULL); + + if (nm_setting_ip_tunnel_get_tos(s_ip_tunnel) != priv->tos) + g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_TOS, priv->tos, NULL); + + if (nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel) != priv->path_mtu_discovery) { + g_object_set(G_OBJECT(s_ip_tunnel), + NM_SETTING_IP_TUNNEL_PATH_MTU_DISCOVERY, + priv->path_mtu_discovery, + NULL); + } + + if (nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel) != priv->encap_limit) { + g_object_set(G_OBJECT(s_ip_tunnel), + NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT, + priv->encap_limit, + NULL); + } + + if (nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel) != priv->flow_label) { + g_object_set(G_OBJECT(s_ip_tunnel), + NM_SETTING_IP_TUNNEL_FLOW_LABEL, + priv->flow_label, + NULL); + } + + if (NM_IN_SET(priv->mode, + NM_IP_TUNNEL_MODE_GRE, + NM_IP_TUNNEL_MODE_GRETAP, + NM_IP_TUNNEL_MODE_IP6GRE, + NM_IP_TUNNEL_MODE_IP6GRETAP)) { + if (g_strcmp0(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel), priv->input_key)) { + g_object_set(G_OBJECT(s_ip_tunnel), + NM_SETTING_IP_TUNNEL_INPUT_KEY, + priv->input_key, + NULL); + } + if (g_strcmp0(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel), priv->output_key)) { + g_object_set(G_OBJECT(s_ip_tunnel), + NM_SETTING_IP_TUNNEL_OUTPUT_KEY, + priv->output_key, + NULL); + } + } +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceIPTunnel * self = NM_DEVICE_IP_TUNNEL(device); + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); + NMSettingIPTunnel * s_ip_tunnel; + const char * parent; + + if (!NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + + if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) != priv->mode) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "incompatible IP tunnel mode"); + return FALSE; + } + + if (nm_device_is_real(device)) { + /* Check parent interface; could be an interface name or a UUID */ + parent = nm_setting_ip_tunnel_get_parent(s_ip_tunnel); + if (parent && !nm_device_match_parent(device, parent)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "IP tunnel parent mismatches"); + return FALSE; + } + + if (!address_equal_pp(priv->addr_family, + nm_setting_ip_tunnel_get_local(s_ip_tunnel), + priv->local)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "local IP tunnel address mismatches"); + return FALSE; + } + + if (!address_equal_pp(priv->addr_family, + nm_setting_ip_tunnel_get_remote(s_ip_tunnel), + priv->remote)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "remote IP tunnel address mismatches"); + return FALSE; + } + + if (nm_setting_ip_tunnel_get_ttl(s_ip_tunnel) != priv->ttl) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "TTL of IP tunnel mismatches"); + return FALSE; + } + + if (nm_setting_ip_tunnel_get_tos(s_ip_tunnel) != priv->tos) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "TOS of IP tunnel mismatches"); + return FALSE; + } + + if (priv->addr_family == AF_INET) { + if (nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel) + != priv->path_mtu_discovery) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "MTU discovery setting of IP tunnel mismatches"); + return FALSE; + } + } else { + if (nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel) != priv->encap_limit) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "encapsulation limit of IP tunnel mismatches"); + return FALSE; + } + + if (nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel) != priv->flow_label) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "flow-label of IP tunnel mismatches"); + return FALSE; + } + } + } + + return TRUE; +} + +static NMIPTunnelMode +platform_link_to_tunnel_mode(const NMPlatformLink *link) +{ + const NMPlatformLnkIp6Tnl *lnk; + + switch (link->type) { + case NM_LINK_TYPE_GRE: + return NM_IP_TUNNEL_MODE_GRE; + case NM_LINK_TYPE_GRETAP: + return NM_IP_TUNNEL_MODE_GRETAP; + case NM_LINK_TYPE_IP6TNL: + lnk = nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, link->ifindex, NULL); + if (lnk) { + if (lnk->proto == IPPROTO_IPIP) + return NM_IP_TUNNEL_MODE_IPIP6; + if (lnk->proto == IPPROTO_IPV6) + return NM_IP_TUNNEL_MODE_IP6IP6; + } + return NM_IP_TUNNEL_MODE_UNKNOWN; + case NM_LINK_TYPE_IP6GRE: + return NM_IP_TUNNEL_MODE_IP6GRE; + case NM_LINK_TYPE_IP6GRETAP: + return NM_IP_TUNNEL_MODE_IP6GRETAP; + case NM_LINK_TYPE_IPIP: + return NM_IP_TUNNEL_MODE_IPIP; + case NM_LINK_TYPE_SIT: + return NM_IP_TUNNEL_MODE_SIT; + default: + g_return_val_if_reached(NM_IP_TUNNEL_MODE_UNKNOWN); + } +} + +static NMLinkType +tunnel_mode_to_link_type(NMIPTunnelMode tunnel_mode) +{ + switch (tunnel_mode) { + case NM_IP_TUNNEL_MODE_GRE: + return NM_LINK_TYPE_GRE; + case NM_IP_TUNNEL_MODE_GRETAP: + return NM_LINK_TYPE_GRETAP; + case NM_IP_TUNNEL_MODE_IPIP6: + case NM_IP_TUNNEL_MODE_IP6IP6: + return NM_LINK_TYPE_IP6TNL; + case NM_IP_TUNNEL_MODE_IP6GRE: + return NM_LINK_TYPE_IP6GRE; + case NM_IP_TUNNEL_MODE_IP6GRETAP: + return NM_LINK_TYPE_IP6GRETAP; + case NM_IP_TUNNEL_MODE_IPIP: + return NM_LINK_TYPE_IPIP; + case NM_IP_TUNNEL_MODE_SIT: + return NM_LINK_TYPE_SIT; + case NM_IP_TUNNEL_MODE_VTI: + case NM_IP_TUNNEL_MODE_VTI6: + case NM_IP_TUNNEL_MODE_ISATAP: + return NM_LINK_TYPE_UNKNOWN; + case NM_IP_TUNNEL_MODE_UNKNOWN: + break; + } + g_return_val_if_reached(NM_LINK_TYPE_UNKNOWN); +} + +/*****************************************************************************/ + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSettingIPTunnel * s_ip_tunnel; + NMPlatformLnkGre lnk_gre = {}; + NMPlatformLnkSit lnk_sit = {}; + NMPlatformLnkIpIp lnk_ipip = {}; + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + const char * str; + gint64 val; + NMIPTunnelMode mode; + int r; + gs_free char * hwaddr = NULL; + guint8 mac_address[ETH_ALEN]; + gboolean mac_address_valid = FALSE; + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + nm_assert(NM_IS_SETTING_IP_TUNNEL(s_ip_tunnel)); + + mode = nm_setting_ip_tunnel_get_mode(s_ip_tunnel); + + if (_nm_ip_tunnel_mode_is_layer2(mode) + && nm_device_hw_addr_get_cloned(device, connection, FALSE, &hwaddr, NULL, NULL) && hwaddr) { + /* FIXME: we set the MAC address when creating the interface, while the + * NMDevice is still unrealized. As we afterwards realize the device, it + * forgets the parameters for the cloned MAC address, and in stage 1 + * it might create a different MAC address. That should be fixed by + * better handling device realization. */ + if (!nm_utils_hwaddr_aton(hwaddr, mac_address, ETH_ALEN)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Invalid hardware address '%s'", + hwaddr); + g_return_val_if_reached(FALSE); + } + + mac_address_valid = TRUE; + } + + switch (mode) { + case NM_IP_TUNNEL_MODE_GRETAP: + lnk_gre.is_tap = TRUE; + /* fall-through */ + case NM_IP_TUNNEL_MODE_GRE: + if (parent) + lnk_gre.parent_ifindex = nm_device_get_ifindex(parent); + + str = nm_setting_ip_tunnel_get_local(s_ip_tunnel); + if (str) + inet_pton(AF_INET, str, &lnk_gre.local); + + str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel); + g_assert(str); + inet_pton(AF_INET, str, &lnk_gre.remote); + + lnk_gre.ttl = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel); + lnk_gre.tos = nm_setting_ip_tunnel_get_tos(s_ip_tunnel); + lnk_gre.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel); + + val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel), + 10, + 0, + G_MAXUINT32, + -1); + if (val != -1) { + lnk_gre.input_key = val; + lnk_gre.input_flags = NM_GRE_KEY; + } + + val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel), + 10, + 0, + G_MAXUINT32, + -1); + if (val != -1) { + lnk_gre.output_key = val; + lnk_gre.output_flags = NM_GRE_KEY; + } + + r = nm_platform_link_gre_add(nm_device_get_platform(device), + iface, + mac_address_valid ? mac_address : NULL, + mac_address_valid ? ETH_ALEN : 0, + &lnk_gre, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create GRE interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + break; + case NM_IP_TUNNEL_MODE_SIT: + if (parent) + lnk_sit.parent_ifindex = nm_device_get_ifindex(parent); + + str = nm_setting_ip_tunnel_get_local(s_ip_tunnel); + if (str) + inet_pton(AF_INET, str, &lnk_sit.local); + + str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel); + g_assert(str); + inet_pton(AF_INET, str, &lnk_sit.remote); + + lnk_sit.ttl = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel); + lnk_sit.tos = nm_setting_ip_tunnel_get_tos(s_ip_tunnel); + lnk_sit.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel); + + r = nm_platform_link_sit_add(nm_device_get_platform(device), iface, &lnk_sit, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create SIT interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + break; + case NM_IP_TUNNEL_MODE_IPIP: + if (parent) + lnk_ipip.parent_ifindex = nm_device_get_ifindex(parent); + + str = nm_setting_ip_tunnel_get_local(s_ip_tunnel); + if (str) + inet_pton(AF_INET, str, &lnk_ipip.local); + + str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel); + g_assert(str); + inet_pton(AF_INET, str, &lnk_ipip.remote); + + lnk_ipip.ttl = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel); + lnk_ipip.tos = nm_setting_ip_tunnel_get_tos(s_ip_tunnel); + lnk_ipip.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel); + + r = nm_platform_link_ipip_add(nm_device_get_platform(device), iface, &lnk_ipip, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create IPIP interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + break; + case NM_IP_TUNNEL_MODE_IPIP6: + case NM_IP_TUNNEL_MODE_IP6IP6: + case NM_IP_TUNNEL_MODE_IP6GRE: + case NM_IP_TUNNEL_MODE_IP6GRETAP: + if (parent) + lnk_ip6tnl.parent_ifindex = nm_device_get_ifindex(parent); + + str = nm_setting_ip_tunnel_get_local(s_ip_tunnel); + if (str) + inet_pton(AF_INET6, str, &lnk_ip6tnl.local); + + str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel); + g_assert(str); + inet_pton(AF_INET6, str, &lnk_ip6tnl.remote); + + lnk_ip6tnl.ttl = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel); + lnk_ip6tnl.tclass = nm_setting_ip_tunnel_get_tos(s_ip_tunnel); + lnk_ip6tnl.encap_limit = nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel); + lnk_ip6tnl.flow_label = nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel); + lnk_ip6tnl.flags = + ip6tnl_flags_setting_to_plat(nm_setting_ip_tunnel_get_flags(s_ip_tunnel)); + + if (NM_IN_SET(mode, NM_IP_TUNNEL_MODE_IP6GRE, NM_IP_TUNNEL_MODE_IP6GRETAP)) { + val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel), + 10, + 0, + G_MAXUINT32, + -1); + if (val != -1) { + lnk_ip6tnl.input_key = val; + lnk_ip6tnl.input_flags = NM_GRE_KEY; + } + + val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel), + 10, + 0, + G_MAXUINT32, + -1); + if (val != -1) { + lnk_ip6tnl.output_key = val; + lnk_ip6tnl.output_flags = NM_GRE_KEY; + } + + lnk_ip6tnl.is_gre = TRUE; + lnk_ip6tnl.is_tap = (mode == NM_IP_TUNNEL_MODE_IP6GRETAP); + + r = nm_platform_link_ip6gre_add(nm_device_get_platform(device), + iface, + mac_address_valid ? mac_address : NULL, + mac_address_valid ? ETH_ALEN : 0, + &lnk_ip6tnl, + out_plink); + } else { + lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode(s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6 + ? IPPROTO_IPIP + : IPPROTO_IPV6; + r = nm_platform_link_ip6tnl_add(nm_device_get_platform(device), + iface, + &lnk_ip6tnl, + out_plink); + } + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create IPv6 tunnel interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + break; + default: + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create IP tunnel interface '%s' for '%s': mode %d not supported", + iface, + nm_connection_get_id(connection), + (int) nm_setting_ip_tunnel_get_mode(s_ip_tunnel)); + return FALSE; + } + + return TRUE; +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + return nm_device_get_configured_mtu_from_connection(device, + NM_TYPE_SETTING_IP_TUNNEL, + out_source); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +unrealize_notify(NMDevice *device) +{ + NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)->unrealize_notify(device); + + update_properties_from_ifindex(device, 0); +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + /* Only handle ip-tunnel setting here, delegate other settings to parent class */ + if (nm_streq(setting_name, NM_SETTING_IP_TUNNEL_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys( + diffs, + NM_SETTING_IP_TUNNEL_SETTING_NAME, + error, + NM_SETTING_IP_TUNNEL_MTU); /* reapplied with IP config */ + } + + device_class = NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceIPTunnel * self = NM_DEVICE_IP_TUNNEL(device); + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); + + if (_nm_ip_tunnel_mode_is_layer2(priv->mode) + && !nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) { + *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_LOCAL: + g_value_set_string(value, priv->local); + break; + case PROP_REMOTE: + g_value_set_string(value, priv->remote); + break; + case PROP_TTL: + g_value_set_uchar(value, priv->ttl); + break; + case PROP_TOS: + g_value_set_uchar(value, priv->tos); + break; + case PROP_PATH_MTU_DISCOVERY: + g_value_set_boolean(value, priv->path_mtu_discovery); + break; + case PROP_INPUT_KEY: + g_value_set_string(value, priv->input_key); + break; + case PROP_OUTPUT_KEY: + g_value_set_string(value, priv->output_key); + break; + case PROP_ENCAPSULATION_LIMIT: + g_value_set_uchar(value, priv->encap_limit); + break; + case PROP_FLOW_LABEL: + g_value_set_uint(value, priv->flow_label); + break; + case PROP_FLAGS: + g_value_set_uint(value, priv->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODE: + priv->mode = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +/*****************************************************************************/ + +static void +nm_device_ip_tunnel_init(NMDeviceIPTunnel *self) +{} + +static void +constructed(GObject *object) +{ + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object); + + if (NM_IN_SET(priv->mode, + NM_IP_TUNNEL_MODE_IPIP6, + NM_IP_TUNNEL_MODE_IP6IP6, + NM_IP_TUNNEL_MODE_IP6GRE, + NM_IP_TUNNEL_MODE_IP6GRETAP)) + priv->addr_family = AF_INET6; + else + priv->addr_family = AF_INET; + + G_OBJECT_CLASS(nm_device_ip_tunnel_parent_class)->constructed(object); +} + +static void +dispose(GObject *object) +{ + NMDeviceIPTunnel * self = NM_DEVICE_IP_TUNNEL(object); + NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self); + + nm_clear_g_free(&priv->local); + nm_clear_g_free(&priv->remote); + nm_clear_g_free(&priv->input_key); + nm_clear_g_free(&priv->output_key); + + G_OBJECT_CLASS(nm_device_ip_tunnel_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_ip_tunnel = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_DEVICE_IP_TUNNEL_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Local", + "s", + NM_DEVICE_IP_TUNNEL_LOCAL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Remote", + "s", + NM_DEVICE_IP_TUNNEL_REMOTE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ttl", "y", NM_DEVICE_IP_TUNNEL_TTL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tos", "y", NM_DEVICE_IP_TUNNEL_TOS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "PathMtuDiscovery", + "b", + NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("InputKey", + "s", + NM_DEVICE_IP_TUNNEL_INPUT_KEY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("OutputKey", + "s", + NM_DEVICE_IP_TUNNEL_OUTPUT_KEY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "EncapsulationLimit", + "y", + NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FlowLabel", + "u", + NM_DEVICE_IP_TUNNEL_FLOW_LABEL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Flags", + "u", + NM_DEVICE_IP_TUNNEL_FLAGS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ip_tunnel_class_init(NMDeviceIPTunnelClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ip_tunnel); + + device_class->connection_type_supported = NM_SETTING_IP_TUNNEL_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_IP_TUNNEL_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP, + NM_LINK_TYPE_IPIP, + NM_LINK_TYPE_SIT); + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->link_changed = link_changed; + device_class->can_reapply_change = can_reapply_change; + device_class->complete_connection = complete_connection; + device_class->update_connection = update_connection; + device_class->check_connection_compatible = check_connection_compatible; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_configured_mtu = get_configured_mtu; + device_class->unrealize_notify = unrealize_notify; + + obj_properties[PROP_MODE] = + g_param_spec_uint(NM_DEVICE_IP_TUNNEL_MODE, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LOCAL] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_LOCAL, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_REMOTE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TTL] = g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_TTL, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PATH_MTU_DISCOVERY] = + g_param_spec_boolean(NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INPUT_KEY] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_INPUT_KEY, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_OUTPUT_KEY] = + g_param_spec_string(NM_DEVICE_IP_TUNNEL_OUTPUT_KEY, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ENCAPSULATION_LIMIT] = + g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FLOW_LABEL] = g_param_spec_uint(NM_DEVICE_IP_TUNNEL_FLOW_LABEL, + "", + "", + 0, + (1 << 20) - 1, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_DEVICE_IP_TUNNEL_FLAGS, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_IP_TUNNEL_DEVICE_FACTORY (nm_ip_tunnel_device_factory_get_type()) +#define NM_IP_TUNNEL_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP_TUNNEL_DEVICE_FACTORY, NMIPTunnelDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + NMSettingIPTunnel *s_ip_tunnel; + NMIPTunnelMode mode; + NMLinkType link_type; + + if (connection) { + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + mode = nm_setting_ip_tunnel_get_mode(s_ip_tunnel); + link_type = tunnel_mode_to_link_type(mode); + } else { + link_type = plink->type; + mode = platform_link_to_tunnel_mode(plink); + } + + if (mode == NM_IP_TUNNEL_MODE_UNKNOWN || link_type == NM_LINK_TYPE_UNKNOWN) + return NULL; + + return g_object_new(NM_TYPE_DEVICE_IP_TUNNEL, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "IPTunnel", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_IP_TUNNEL, + NM_DEVICE_LINK_TYPE, + link_type, + NM_DEVICE_IP_TUNNEL_MODE, + mode, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingIPTunnel *s_ip_tunnel; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME), + NULL); + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + g_assert(s_ip_tunnel); + + return nm_setting_ip_tunnel_get_parent(s_ip_tunnel); +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + const char * ifname; + NMSettingIPTunnel *s_ip_tunnel; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME), + NULL); + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection); + g_assert(s_ip_tunnel); + + if (nm_setting_ip_tunnel_get_parent(s_ip_tunnel) && !parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + + return g_strdup(ifname); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + IP_TUNNEL, + IPTunnel, + ip_tunnel, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_IPIP, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_IP_TUNNEL_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-ip-tunnel.h b/src/core/devices/nm-device-ip-tunnel.h new file mode 100644 index 0000000..0a941cc --- /dev/null +++ b/src/core/devices/nm-device-ip-tunnel.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_IP_TUNNEL_H__ +#define __NETWORKMANAGER_DEVICE_IP_TUNNEL_H__ + +#include "nm-core-types.h" +#include "nm-device.h" + +#define NM_TYPE_DEVICE_IP_TUNNEL (nm_device_ip_tunnel_get_type()) +#define NM_DEVICE_IP_TUNNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IP_TUNNEL, NMDeviceIPTunnel)) +#define NM_DEVICE_IP_TUNNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IP_TUNNEL, NMDeviceIPTunnelClass)) +#define NM_IS_DEVICE_IP_TUNNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IP_TUNNEL)) +#define NM_IS_DEVICE_IP_TUNNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_IP_TUNNEL)) +#define NM_DEVICE_IP_TUNNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_IP_TUNNEL, NMDeviceIPTunnelClass)) + +#define NM_DEVICE_IP_TUNNEL_MODE "mode" +#define NM_DEVICE_IP_TUNNEL_LOCAL "local" +#define NM_DEVICE_IP_TUNNEL_REMOTE "remote" +#define NM_DEVICE_IP_TUNNEL_TTL "ttl" +#define NM_DEVICE_IP_TUNNEL_TOS "tos" +#define NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY "path-mtu-discovery" +#define NM_DEVICE_IP_TUNNEL_INPUT_KEY "input-key" +#define NM_DEVICE_IP_TUNNEL_OUTPUT_KEY "output-key" +#define NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT "encapsulation-limit" +#define NM_DEVICE_IP_TUNNEL_FLOW_LABEL "flow-label" +#define NM_DEVICE_IP_TUNNEL_FLAGS "flags" + +typedef struct _NMDeviceIPTunnel NMDeviceIPTunnel; +typedef struct _NMDeviceIPTunnelClass NMDeviceIPTunnelClass; + +GType nm_device_ip_tunnel_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_IP_TUNNEL_H__ */ diff --git a/src/core/devices/nm-device-logging.h b/src/core/devices/nm-device-logging.h new file mode 100644 index 0000000..844e994 --- /dev/null +++ b/src/core/devices/nm-device-logging.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_LOGGING_H__ +#define __NETWORKMANAGER_DEVICE_LOGGING_H__ + +#include "nm-device.h" + +#if !_NM_CC_SUPPORT_GENERIC + #define _NM_DEVICE_CAST(self) ((NMDevice *) (self)) +#elif !defined(_NMLOG_DEVICE_TYPE) + #define _NM_DEVICE_CAST(self) \ + _Generic((self), NMDevice * \ + : ((NMDevice *) (self)), NMDevice *const \ + : ((NMDevice *) (self))) +#else + #define _NM_DEVICE_CAST(self) \ + _Generic((self), \ + _NMLOG_DEVICE_TYPE * : ((NMDevice *) (self)), \ + _NMLOG_DEVICE_TYPE * const: ((NMDevice *) (self)), \ + NMDevice * : ((NMDevice *) (self)), \ + NMDevice * const: ((NMDevice *) (self))) +#endif + +#undef _NMLOG_ENABLED +#define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain))) +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + typeof(*self) *const _self = (self); \ + const char *const _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self)); \ + \ + nm_log_obj(_level, \ + _domain, \ + _ifname, \ + NULL, \ + _self, \ + "device", \ + "%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#endif /* __NETWORKMANAGER_DEVICE_LOGGING_H__ */ diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c new file mode 100644 index 0000000..390cc6d --- /dev/null +++ b/src/core/devices/nm-device-macsec.c @@ -0,0 +1,1090 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-macsec.h" + +#include + +#include "nm-act-request.h" +#include "nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-manager.h" +#include "nm-setting-macsec.h" +#include "nm-core-internal.h" +#include "supplicant/nm-supplicant-manager.h" +#include "supplicant/nm-supplicant-interface.h" +#include "supplicant/nm-supplicant-config.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceMacsec +#include "nm-device-logging.h" + +/*****************************************************************************/ + +#define SUPPLICANT_LNK_TIMEOUT_SEC 15 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceMacsec, + PROP_SCI, + PROP_CIPHER_SUITE, + PROP_ICV_LENGTH, + PROP_WINDOW, + PROP_ENCODING_SA, + PROP_ENCRYPT, + PROP_PROTECT, + PROP_INCLUDE_SCI, + PROP_ES, + PROP_SCB, + PROP_REPLAY_PROTECT, + PROP_VALIDATION, ); + +typedef struct { + NMPlatformLnkMacsec props; + gulong parent_state_id; + gulong parent_mtu_id; + + struct { + NMSupplicantManager * mgr; + NMSupplMgrCreateIfaceHandle *create_handle; + NMSupplicantInterface * iface; + + gulong iface_state_id; + + guint con_timeout_id; + guint lnk_timeout_id; + + bool is_associated : 1; + } supplicant; + + NMActRequestGetSecretsCallId *macsec_secrets_id; +} NMDeviceMacsecPrivate; + +struct _NMDeviceMacsec { + NMDevice parent; + NMDeviceMacsecPrivate _priv; +}; + +struct _NMDeviceMacsecClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceMacsec, nm_device_macsec, NM_TYPE_DEVICE) + +#define NM_DEVICE_MACSEC_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceMacsec, NM_IS_DEVICE_MACSEC, NMDevice) + +/******************************************************************/ + +static void macsec_secrets_cancel(NMDeviceMacsec *self); + +/******************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(validation_mode_to_string, + guint8, + NM_UTILS_LOOKUP_DEFAULT_WARN(""), + NM_UTILS_LOOKUP_STR_ITEM(0, "disable"), + NM_UTILS_LOOKUP_STR_ITEM(1, "check"), + NM_UTILS_LOOKUP_STR_ITEM(2, "strict"), ); + +static void +parent_state_changed(NMDevice * parent, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceMacsec *self = NM_DEVICE_MACSEC(user_data); + + /* We'll react to our own carrier state notifications. Ignore the parent's. */ + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CARRIER) + return; + + nm_device_set_unmanaged_by_flags(NM_DEVICE(self), + NM_UNMANAGED_PARENT, + !nm_device_get_managed(parent, FALSE), + reason); +} + +static void +parent_mtu_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data) +{ + /* the MTU of a MACsec device is limited by the parent's MTU. + * + * When the parent's MTU changes, try to re-set the MTU. */ + nm_device_commit_mtu(user_data); +} + +static void +parent_changed_notify(NMDevice *device, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(device); + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + NM_DEVICE_CLASS(nm_device_macsec_parent_class) + ->parent_changed_notify(device, old_ifindex, old_parent, new_ifindex, new_parent); + + /* note that @self doesn't have to clear @parent_state_id on dispose, + * because NMDevice's dispose() will unset the parent, which in turn calls + * parent_changed_notify(). */ + nm_clear_g_signal_handler(old_parent, &priv->parent_state_id); + nm_clear_g_signal_handler(old_parent, &priv->parent_mtu_id); + + if (new_parent) { + priv->parent_state_id = g_signal_connect(new_parent, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(parent_state_changed), + device); + priv->parent_mtu_id = g_signal_connect(new_parent, + "notify::" NM_DEVICE_MTU, + G_CALLBACK(parent_mtu_maybe_changed), + device); + + /* Set parent-dependent unmanaged flag */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PARENT, + !nm_device_get_managed(new_parent, FALSE), + NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED); + } + + /* Recheck availability now that the parent has changed */ + if (new_ifindex > 0) { + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_PARENT_CHANGED, + NM_DEVICE_STATE_REASON_PARENT_CHANGED); + } +} + +static void +update_properties(NMDevice *device) +{ + NMDeviceMacsec * self; + NMDeviceMacsecPrivate * priv; + const NMPlatformLink * plink = NULL; + const NMPlatformLnkMacsec *props = NULL; + int ifindex; + + g_return_if_fail(NM_IS_DEVICE_MACSEC(device)); + self = NM_DEVICE_MACSEC(device); + priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + ifindex = nm_device_get_ifindex(device); + g_return_if_fail(ifindex > 0); + props = nm_platform_link_get_lnk_macsec(nm_device_get_platform(device), ifindex, &plink); + + if (!props) { + _LOGW(LOGD_PLATFORM, "could not get macsec properties"); + return; + } + + g_object_freeze_notify((GObject *) device); + + if (priv->props.parent_ifindex != props->parent_ifindex) + nm_device_parent_set_ifindex(device, props->parent_ifindex); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED(sci, PROP_SCI); + CHECK_PROPERTY_CHANGED(cipher_suite, PROP_CIPHER_SUITE); + CHECK_PROPERTY_CHANGED(window, PROP_WINDOW); + CHECK_PROPERTY_CHANGED(icv_length, PROP_ICV_LENGTH); + CHECK_PROPERTY_CHANGED(encoding_sa, PROP_ENCODING_SA); + CHECK_PROPERTY_CHANGED(validation, PROP_VALIDATION); + CHECK_PROPERTY_CHANGED(encrypt, PROP_ENCRYPT); + CHECK_PROPERTY_CHANGED(protect, PROP_PROTECT); + CHECK_PROPERTY_CHANGED(include_sci, PROP_INCLUDE_SCI); + CHECK_PROPERTY_CHANGED(es, PROP_ES); + CHECK_PROPERTY_CHANGED(scb, PROP_SCB); + CHECK_PROPERTY_CHANGED(replay_protect, PROP_REPLAY_PROTECT); + + g_object_thaw_notify((GObject *) device); +} + +static NMSupplicantConfig * +build_supplicant_config(NMDeviceMacsec *self, GError **error) +{ + gs_unref_object NMSupplicantConfig *config = NULL; + NMSettingMacsec * s_macsec; + NMSetting8021x * s_8021x; + NMConnection * connection; + const char * con_uuid; + guint32 mtu; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + g_return_val_if_fail(connection, NULL); + + con_uuid = nm_connection_get_uuid(connection); + mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)), + nm_device_get_ifindex(NM_DEVICE(self))); + + config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE); + + s_macsec = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_MACSEC); + + g_return_val_if_fail(s_macsec, NULL); + + if (!nm_supplicant_config_add_setting_macsec(config, s_macsec, error)) { + g_prefix_error(error, "macsec-setting: "); + return NULL; + } + + if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_EAP) { + s_8021x = nm_connection_get_setting_802_1x(connection); + if (!nm_supplicant_config_add_setting_8021x(config, s_8021x, con_uuid, mtu, TRUE, error)) { + g_prefix_error(error, "802-1x-setting: "); + return NULL; + } + } + + return g_steal_pointer(&config); +} + +static void +supplicant_interface_release(NMDeviceMacsec *self) +{ + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + nm_clear_pointer(&priv->supplicant.create_handle, + nm_supplicant_manager_create_interface_cancel); + + nm_clear_g_source(&priv->supplicant.lnk_timeout_id); + nm_clear_g_source(&priv->supplicant.con_timeout_id); + nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.iface_state_id); + + if (priv->supplicant.iface) { + nm_supplicant_interface_disconnect(priv->supplicant.iface); + g_clear_object(&priv->supplicant.iface); + } +} + +static void +macsec_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(user_data); + NMDevice * device = NM_DEVICE(self); + NMDeviceMacsecPrivate *priv; + + g_return_if_fail(NM_IS_DEVICE_MACSEC(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + g_return_if_fail(priv->macsec_secrets_id == call_id); + + priv->macsec_secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail(nm_act_request_get_settings_connection(req) == connection); + + if (error) { + _LOGW(LOGD_ETHER, "%s", error->message); + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +macsec_secrets_cancel(NMDeviceMacsec *self) +{ + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + if (priv->macsec_secrets_id) + nm_act_request_cancel_secrets(NULL, priv->macsec_secrets_id); + nm_assert(!priv->macsec_secrets_id); +} + +static void +macsec_secrets_get_secrets(NMDeviceMacsec * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags) +{ + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + NMActRequest * req; + + macsec_secrets_cancel(self); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv->macsec_secrets_id = + nm_act_request_get_secrets(req, TRUE, setting_name, flags, NULL, macsec_secrets_cb, self); + g_return_if_fail(priv->macsec_secrets_id); +} + +static gboolean +supplicant_lnk_timeout_cb(gpointer user_data) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(user_data); + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + NMDevice * dev = NM_DEVICE(self); + NMActRequest * req; + NMConnection * applied_connection; + const char * setting_name; + + priv->supplicant.lnk_timeout_id = 0; + + req = nm_device_get_act_request(dev); + + if (nm_device_get_state(dev) == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed(dev, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return G_SOURCE_REMOVE; + } + + /* Disconnect event during initial authentication and credentials + * ARE checked - we are likely to have wrong key. Ask the user for + * another one. + */ + if (nm_device_get_state(dev) != NM_DEVICE_STATE_CONFIG) + goto time_out; + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(req)); + + applied_connection = nm_act_request_get_applied_connection(req); + setting_name = nm_connection_need_secrets(applied_connection, NULL); + if (!setting_name) + goto time_out; + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: disconnected during authentication, asking for new key."); + supplicant_interface_release(self); + + nm_device_state_changed(dev, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + macsec_secrets_get_secrets(self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); + + return G_SOURCE_REMOVE; + +time_out: + _LOGW(LOGD_DEVICE | LOGD_ETHER, "link timed out."); + nm_device_state_changed(dev, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + + return G_SOURCE_REMOVE; +} + +static void +supplicant_iface_state_is_completed(NMDeviceMacsec *self, NMSupplicantInterfaceState state) +{ + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) { + nm_clear_g_source(&priv->supplicant.lnk_timeout_id); + nm_clear_g_source(&priv->supplicant.con_timeout_id); + + nm_device_bring_up(NM_DEVICE(self), TRUE, NULL); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) { + _LOGI(LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) successful."); + nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self)); + } + return; + } + + if (!priv->supplicant.lnk_timeout_id && !priv->supplicant.con_timeout_id) + priv->supplicant.lnk_timeout_id = + g_timeout_add_seconds(SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self); +} + +static void +supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data) +{ + NMDeviceMacsec * self; + NMDeviceMacsecPrivate *priv; + + if (nm_utils_error_is_cancelled_or_disposing(error)) + return; + + self = user_data; + priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + if (error) { + supplicant_interface_release(self); + nm_device_queue_state(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + return; + } + + nm_assert(!priv->supplicant.lnk_timeout_id); + nm_assert(!priv->supplicant.is_associated); + + priv->supplicant.is_associated = TRUE; + supplicant_iface_state_is_completed(self, + nm_supplicant_interface_get_state(priv->supplicant.iface)); +} + +static gboolean +supplicant_iface_start(NMDeviceMacsec *self) +{ + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + gs_unref_object NMSupplicantConfig *config = NULL; + gs_free_error GError *error = NULL; + + config = build_supplicant_config(self, &error); + if (!config) { + _LOGE(LOGD_DEVICE, "Activation: couldn't build security configuration: %s", error->message); + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + return FALSE; + } + + nm_supplicant_interface_disconnect(priv->supplicant.iface); + nm_supplicant_interface_assoc(priv->supplicant.iface, config, supplicant_iface_assoc_cb, self); + return TRUE; +} + +static void +supplicant_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(user_data); + NMDeviceMacsecPrivate * priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + NMSupplicantInterfaceState new_state = new_state_i; + NMSupplicantInterfaceState old_state = old_state_i; + + _LOGI(LOGD_DEVICE, + "supplicant interface state: %s -> %s", + nm_supplicant_interface_state_to_string(old_state), + nm_supplicant_interface_state_to_string(new_state)); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + if (!supplicant_iface_start(self)) + return; + } + + if (priv->supplicant.is_associated) + supplicant_iface_state_is_completed(self, new_state); +} + +static gboolean +handle_auth_or_fail(NMDeviceMacsec *self, NMActRequest *req, gboolean new_secrets) +{ + const char * setting_name; + NMConnection *applied_connection; + + if (!nm_device_auth_retries_try_next(NM_DEVICE(self))) + return FALSE; + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(req)); + + applied_connection = nm_act_request_get_applied_connection(req); + setting_name = nm_connection_need_secrets(applied_connection, NULL); + if (!setting_name) { + _LOGI(LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + return FALSE; + } + + macsec_secrets_get_secrets( + self, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0)); + return TRUE; +} + +static gboolean +supplicant_connection_timeout_cb(gpointer user_data) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(user_data); + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMActRequest * req; + NMSettingsConnection * connection; + guint64 timestamp = 0; + gboolean new_secrets = TRUE; + + priv->supplicant.con_timeout_id = 0; + + /* Authentication failed; either driver problems, the encryption key is + * wrong, the passwords or certificates were wrong or the Ethernet switch's + * port is not configured for 802.1x. */ + _LOGW(LOGD_DEVICE, "Activation: (macsec) association took too long."); + + supplicant_interface_release(self); + + req = nm_device_get_act_request(device); + connection = nm_act_request_get_settings_connection(req); + g_return_val_if_fail(connection, G_SOURCE_REMOVE); + + /* Ask for new secrets only if we've never activated this connection + * before. If we've connected before, don't bother the user with dialogs, + * just retry or fail, and if we never connect the user can fix the + * password somewhere else. */ + if (nm_settings_connection_get_timestamp(connection, ×tamp)) + new_secrets = !timestamp; + + if (!handle_auth_or_fail(self, req, new_secrets)) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return G_SOURCE_REMOVE; + } + + _LOGW(LOGD_DEVICE, "Activation: (macsec) asking for new secrets"); + + if (!priv->supplicant.lnk_timeout_id && priv->supplicant.iface) { + NMSupplicantInterfaceState state; + + state = nm_supplicant_interface_get_state(priv->supplicant.iface); + if (state != NM_SUPPLICANT_INTERFACE_STATE_COMPLETED + && nm_supplicant_interface_state_is_operational(state)) + priv->supplicant.lnk_timeout_id = + g_timeout_add_seconds(SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self); + } + + return G_SOURCE_REMOVE; +} + +static void +supplicant_interface_create_cb(NMSupplicantManager * supplicant_manager, + NMSupplMgrCreateIfaceHandle *handle, + NMSupplicantInterface * iface, + GError * error, + gpointer user_data) +{ + NMDeviceMacsec * self; + NMDeviceMacsecPrivate *priv; + guint timeout; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + nm_assert(priv->supplicant.create_handle == handle); + + priv->supplicant.create_handle = NULL; + + if (error) { + _LOGE(LOGD_DEVICE, "Couldn't initialize supplicant interface: %s", error->message); + supplicant_interface_release(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + priv->supplicant.iface = g_object_ref(iface); + priv->supplicant.is_associated = FALSE; + + priv->supplicant.iface_state_id = g_signal_connect(priv->supplicant.iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK(supplicant_iface_state_cb), + self); + + timeout = nm_device_get_supplicant_timeout(NM_DEVICE(self)); + priv->supplicant.con_timeout_id = + g_timeout_add_seconds(timeout, supplicant_connection_timeout_cb, self); + + if (nm_supplicant_interface_state_is_operational(nm_supplicant_interface_get_state(iface))) + supplicant_iface_start(self); +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(device); + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + NMConnection * connection; + NMDevice * parent; + const char * setting_name; + int ifindex; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + if (!priv->supplicant.mgr) + priv->supplicant.mgr = g_object_ref(nm_supplicant_manager_get()); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets(connection, NULL); + if (setting_name) { + NMActRequest *req = nm_device_get_act_request(NM_DEVICE(self)); + + _LOGI(LOGD_DEVICE, + "Activation: connection '%s' has security, but secrets are required.", + nm_connection_get_id(connection)); + + if (!handle_auth_or_fail(self, req, FALSE)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + _LOGI(LOGD_DEVICE | LOGD_ETHER, + "Activation: connection '%s' requires no security. No secrets needed.", + nm_connection_get_id(connection)); + + supplicant_interface_release(self); + + parent = nm_device_parent_get_device(NM_DEVICE(self)); + g_return_val_if_fail(parent, NM_ACT_STAGE_RETURN_FAILURE); + ifindex = nm_device_get_ifindex(parent); + g_return_val_if_fail(ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE); + + priv->supplicant.create_handle = + nm_supplicant_manager_create_interface(priv->supplicant.mgr, + ifindex, + NM_SUPPLICANT_DRIVER_MACSEC, + supplicant_interface_create_cb, + self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceMacsec *self = NM_DEVICE_MACSEC(device); + + supplicant_interface_release(self); +} + +/******************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + /* We assume MACsec interfaces always support carrier detect */ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +/******************************************************************/ + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!nm_device_parent_get_device(device)) + return FALSE; + return NM_DEVICE_CLASS(nm_device_macsec_parent_class)->is_available(device, flags); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSettingMacsec * s_macsec; + NMPlatformLnkMacsec lnk = {}; + int parent_ifindex; + const char * hw_addr; + union { + struct { + guint8 mac[6]; + guint16 port; + } s; + guint64 u; + } sci; + int r; + + s_macsec = nm_connection_get_setting_macsec(connection); + g_assert(s_macsec); + + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "MACsec devices can not be created without a parent interface"); + return FALSE; + } + + lnk.encrypt = nm_setting_macsec_get_encrypt(s_macsec); + + hw_addr = nm_device_get_hw_address(parent); + if (!hw_addr) { + g_set_error(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "can't read parent MAC"); + return FALSE; + } + + nm_utils_hwaddr_aton(hw_addr, sci.s.mac, ETH_ALEN); + sci.s.port = htons(nm_setting_macsec_get_port(s_macsec)); + lnk.sci = be64toh(sci.u); + lnk.validation = nm_setting_macsec_get_validation(s_macsec); + lnk.include_sci = nm_setting_macsec_get_send_sci(s_macsec); + + parent_ifindex = nm_device_get_ifindex(parent); + g_warn_if_fail(parent_ifindex > 0); + + r = nm_platform_link_macsec_add(nm_device_get_platform(device), + iface, + parent_ifindex, + &lnk, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create macsec interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + nm_device_parent_set_ifindex(device, parent_ifindex); + + return TRUE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_macsec_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if (new_state > NM_DEVICE_STATE_ACTIVATED) + macsec_secrets_cancel(NM_DEVICE_MACSEC(device)); +} + +/******************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceMacsec * self = NM_DEVICE_MACSEC(object); + NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_SCI: + g_value_set_uint64(value, priv->props.sci); + break; + case PROP_CIPHER_SUITE: + g_value_set_uint64(value, priv->props.cipher_suite); + break; + case PROP_ICV_LENGTH: + g_value_set_uchar(value, priv->props.icv_length); + break; + case PROP_WINDOW: + g_value_set_uint(value, priv->props.window); + break; + case PROP_ENCODING_SA: + g_value_set_uchar(value, priv->props.encoding_sa); + break; + case PROP_ENCRYPT: + g_value_set_boolean(value, priv->props.encrypt); + break; + case PROP_PROTECT: + g_value_set_boolean(value, priv->props.protect); + break; + case PROP_INCLUDE_SCI: + g_value_set_boolean(value, priv->props.include_sci); + break; + case PROP_ES: + g_value_set_boolean(value, priv->props.es); + break; + case PROP_SCB: + g_value_set_boolean(value, priv->props.scb); + break; + case PROP_REPLAY_PROTECT: + g_value_set_boolean(value, priv->props.replay_protect); + break; + case PROP_VALIDATION: + g_value_set_string(value, validation_mode_to_string(priv->props.validation)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_device_macsec_init(NMDeviceMacsec *self) +{} + +static void +dispose(GObject *object) +{ + NMDeviceMacsec *self = NM_DEVICE_MACSEC(object); + + macsec_secrets_cancel(self); + supplicant_interface_release(self); + + G_OBJECT_CLASS(nm_device_macsec_parent_class)->dispose(object); + + nm_assert(NM_DEVICE_MACSEC_GET_PRIVATE(self)->parent_state_id == 0); + nm_assert(NM_DEVICE_MACSEC_GET_PRIVATE(self)->parent_mtu_id == 0); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_macsec = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_MACSEC, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Sci", "t", NM_DEVICE_MACSEC_SCI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IcvLength", + "y", + NM_DEVICE_MACSEC_ICV_LENGTH), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CipherSuite", + "t", + NM_DEVICE_MACSEC_CIPHER_SUITE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Window", + "u", + NM_DEVICE_MACSEC_WINDOW), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("EncodingSa", + "y", + NM_DEVICE_MACSEC_ENCODING_SA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Validation", + "s", + NM_DEVICE_MACSEC_VALIDATION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Encrypt", + "b", + NM_DEVICE_MACSEC_ENCRYPT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Protect", + "b", + NM_DEVICE_MACSEC_PROTECT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IncludeSci", + "b", + NM_DEVICE_MACSEC_INCLUDE_SCI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Es", "b", NM_DEVICE_MACSEC_ES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Scb", "b", NM_DEVICE_MACSEC_SCB), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ReplayProtect", + "b", + NM_DEVICE_MACSEC_REPLAY_PROTECT), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_macsec_class_init(NMDeviceMacsecClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->dispose = dispose; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_macsec); + + device_class->connection_type_supported = NM_SETTING_MACSEC_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_MACSEC_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_MACSEC); + device_class->mtu_parent_delta = 32; + + device_class->act_stage2_config = act_stage2_config; + device_class->create_and_realize = create_and_realize; + device_class->deactivate = deactivate; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->link_changed = link_changed; + device_class->is_available = is_available; + device_class->parent_changed_notify = parent_changed_notify; + device_class->state_changed = device_state_changed; + device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent; + + obj_properties[PROP_SCI] = g_param_spec_uint64(NM_DEVICE_MACSEC_SCI, + "", + "", + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CIPHER_SUITE] = + g_param_spec_uint64(NM_DEVICE_MACSEC_CIPHER_SUITE, + "", + "", + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ICV_LENGTH] = g_param_spec_uchar(NM_DEVICE_MACSEC_ICV_LENGTH, + "", + "", + 0, + G_MAXUINT8, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_WINDOW] = g_param_spec_uint(NM_DEVICE_MACSEC_WINDOW, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ENCODING_SA] = + g_param_spec_uchar(NM_DEVICE_MACSEC_ENCODING_SA, + "", + "", + 0, + 3, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_VALIDATION] = + g_param_spec_string(NM_DEVICE_MACSEC_VALIDATION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ENCRYPT] = g_param_spec_boolean(NM_DEVICE_MACSEC_ENCRYPT, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_PROTECT] = g_param_spec_boolean(NM_DEVICE_MACSEC_PROTECT, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_INCLUDE_SCI] = + g_param_spec_boolean(NM_DEVICE_MACSEC_INCLUDE_SCI, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ES] = g_param_spec_boolean(NM_DEVICE_MACSEC_ES, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SCB] = g_param_spec_boolean(NM_DEVICE_MACSEC_SCB, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_REPLAY_PROTECT] = + g_param_spec_boolean(NM_DEVICE_MACSEC_REPLAY_PROTECT, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*************************************************************/ + +#define NM_TYPE_MACSEC_DEVICE_FACTORY (nm_macsec_device_factory_get_type()) +#define NM_MACSEC_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MACSEC_DEVICE_FACTORY, NMMacsecDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_MACSEC, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Macsec", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_MACSEC, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_MACSEC, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingMacsec *s_macsec; + NMSettingWired * s_wired; + const char * parent = NULL; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL); + + s_macsec = nm_connection_get_setting_macsec(connection); + g_assert(s_macsec); + + parent = nm_setting_macsec_get_parent(s_macsec); + if (parent) + return parent; + + /* Try the hardware address from the MACsec connection's hardware setting */ + s_wired = nm_connection_get_setting_wired(connection); + if (s_wired) + return nm_setting_wired_get_mac_address(s_wired); + + return NULL; +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + NMSettingMacsec *s_macsec; + const char * ifname; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL); + + s_macsec = nm_connection_get_setting_macsec(connection); + g_assert(s_macsec); + + if (!parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + return g_strdup(ifname); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + MACSEC, + Macsec, + macsec, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_MACSEC) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_MACSEC_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;) diff --git a/src/core/devices/nm-device-macsec.h b/src/core/devices/nm-device-macsec.h new file mode 100644 index 0000000..e91fe51 --- /dev/null +++ b/src/core/devices/nm-device-macsec.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_DEVICE_MACSEC_H__ +#define __NM_DEVICE_MACSEC_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_MACSEC (nm_device_macsec_get_type()) +#define NM_DEVICE_MACSEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MACSEC, NMDeviceMacsec)) +#define NM_DEVICE_MACSEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_MACSEC, NMDeviceMacsecClass)) +#define NM_IS_DEVICE_MACSEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_MACSEC)) +#define NM_IS_DEVICE_MACSEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_MACSEC)) +#define NM_DEVICE_MACSEC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_MACSEC, NMDeviceMacsecClass)) + +#define NM_DEVICE_MACSEC_SCI "sci" +#define NM_DEVICE_MACSEC_CIPHER_SUITE "cipher-suite" +#define NM_DEVICE_MACSEC_ICV_LENGTH "icv-length" +#define NM_DEVICE_MACSEC_WINDOW "window" +#define NM_DEVICE_MACSEC_ENCODING_SA "encoding-sa" +#define NM_DEVICE_MACSEC_VALIDATION "validation" +#define NM_DEVICE_MACSEC_ENCRYPT "encrypt" +#define NM_DEVICE_MACSEC_PROTECT "protect" +#define NM_DEVICE_MACSEC_INCLUDE_SCI "include-sci" +#define NM_DEVICE_MACSEC_ES "es" +#define NM_DEVICE_MACSEC_SCB "scb" +#define NM_DEVICE_MACSEC_REPLAY_PROTECT "replay-protect" + +typedef struct _NMDeviceMacsec NMDeviceMacsec; +typedef struct _NMDeviceMacsecClass NMDeviceMacsecClass; + +GType nm_device_macsec_get_type(void); + +#endif /* __NM_DEVICE_MACSEC_H__ */ diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c new file mode 100644 index 0000000..051f9d6 --- /dev/null +++ b/src/core/devices/nm-device-macvlan.c @@ -0,0 +1,665 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-macvlan.h" + +#include + +#include "nm-device-private.h" +#include "settings/nm-settings.h" +#include "nm-act-request.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-macvlan.h" +#include "nm-setting-wired.h" +#include "nm-active-connection.h" +#include "nm-ip4-config.h" +#include "nm-utils.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceMacvlan +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceMacvlan, PROP_MODE, PROP_NO_PROMISC, PROP_TAP, ); + +typedef struct { + gulong parent_state_id; + gulong parent_mtu_id; + NMPlatformLnkMacvlan props; +} NMDeviceMacvlanPrivate; + +struct _NMDeviceMacvlan { + NMDevice parent; + NMDeviceMacvlanPrivate _priv; +}; + +struct _NMDeviceMacvlanClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceMacvlan, nm_device_macvlan, NM_TYPE_DEVICE) + +#define NM_DEVICE_MACVLAN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceMacvlan, NM_IS_DEVICE_MACVLAN, NMDevice) + +/*****************************************************************************/ + +static int modes[][2] = { + {NM_SETTING_MACVLAN_MODE_VEPA, MACVLAN_MODE_VEPA}, + {NM_SETTING_MACVLAN_MODE_BRIDGE, MACVLAN_MODE_BRIDGE}, + {NM_SETTING_MACVLAN_MODE_PRIVATE, MACVLAN_MODE_PRIVATE}, + {NM_SETTING_MACVLAN_MODE_PASSTHRU, MACVLAN_MODE_PASSTHRU}, +}; + +static int +setting_mode_to_platform(int mode) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS(modes); i++) { + if (modes[i][0] == mode) + return modes[i][1]; + } + + return 0; +} + +static int +platform_mode_to_setting(int mode) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS(modes); i++) { + if (modes[i][1] == mode) + return modes[i][0]; + } + + return 0; +} + +static const char * +platform_mode_to_string(guint mode) +{ + switch (mode) { + case MACVLAN_MODE_PRIVATE: + return "private"; + case MACVLAN_MODE_VEPA: + return "vepa"; + case MACVLAN_MODE_BRIDGE: + return "bridge"; + case MACVLAN_MODE_PASSTHRU: + return "passthru"; + default: + return "unknown"; + } +} + +/*****************************************************************************/ + +static void +parent_state_changed(NMDevice * parent, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceMacvlan *self = NM_DEVICE_MACVLAN(user_data); + + /* We'll react to our own carrier state notifications. Ignore the parent's. */ + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CARRIER) + return; + + nm_device_set_unmanaged_by_flags(NM_DEVICE(self), + NM_UNMANAGED_PARENT, + !nm_device_get_managed(parent, FALSE), + reason); +} + +static void +parent_mtu_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data) +{ + /* the MTU of a macvlan/macvtap device is limited by the parent's MTU. + * + * When the parent's MTU changes, try to re-set the MTU. */ + nm_device_commit_mtu(user_data); +} + +static void +parent_changed_notify(NMDevice *device, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + NMDeviceMacvlan * self = NM_DEVICE_MACVLAN(device); + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(self); + + NM_DEVICE_CLASS(nm_device_macvlan_parent_class) + ->parent_changed_notify(device, old_ifindex, old_parent, new_ifindex, new_parent); + + /* note that @self doesn't have to clear @parent_state_id on dispose, + * because NMDevice's dispose() will unset the parent, which in turn calls + * parent_changed_notify(). */ + nm_clear_g_signal_handler(old_parent, &priv->parent_state_id); + nm_clear_g_signal_handler(old_parent, &priv->parent_mtu_id); + + if (new_parent) { + priv->parent_state_id = g_signal_connect(new_parent, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(parent_state_changed), + device); + priv->parent_mtu_id = g_signal_connect(new_parent, + "notify::" NM_DEVICE_MTU, + G_CALLBACK(parent_mtu_maybe_changed), + device); + + /* Set parent-dependent unmanaged flag */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PARENT, + !nm_device_get_managed(new_parent, FALSE), + NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED); + } + + if (new_ifindex > 0) { + /* Recheck availability now that the parent has changed */ + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_PARENT_CHANGED, + NM_DEVICE_STATE_REASON_PARENT_CHANGED); + } +} + +static void +update_properties(NMDevice *device) +{ + NMDeviceMacvlan * self = NM_DEVICE_MACVLAN(device); + NMDeviceMacvlanPrivate * priv = NM_DEVICE_MACVLAN_GET_PRIVATE(self); + GObject * object = G_OBJECT(device); + const NMPlatformLnkMacvlan *props; + const NMPlatformLink * plink; + + if (priv->props.tap) + props = nm_platform_link_get_lnk_macvtap(nm_device_get_platform(device), + nm_device_get_ifindex(device), + &plink); + else + props = nm_platform_link_get_lnk_macvlan(nm_device_get_platform(device), + nm_device_get_ifindex(device), + &plink); + + if (!props) { + _LOGW(LOGD_PLATFORM, + "could not get %s properties", + priv->props.tap ? "macvtap" : "macvlan"); + return; + } + + g_object_freeze_notify(object); + + nm_device_parent_set_ifindex(device, plink->parent); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED(mode, PROP_MODE); + CHECK_PROPERTY_CHANGED(no_promisc, PROP_NO_PROMISC); + + g_object_thaw_notify(object); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_macvlan_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSettingMacvlan * s_macvlan; + NMPlatformLnkMacvlan lnk = {}; + int parent_ifindex; + int r; + + s_macvlan = nm_connection_get_setting_macvlan(connection); + g_return_val_if_fail(s_macvlan, FALSE); + + parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0; + + if (parent_ifindex <= 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "MACVLAN devices can not be created without a parent interface"); + g_return_val_if_fail(!parent, FALSE); + return FALSE; + } + + lnk.mode = setting_mode_to_platform(nm_setting_macvlan_get_mode(s_macvlan)); + if (!lnk.mode) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "unsupported MACVLAN mode %u in connection %s", + nm_setting_macvlan_get_mode(s_macvlan), + nm_connection_get_uuid(connection)); + return FALSE; + } + lnk.no_promisc = !nm_setting_macvlan_get_promiscuous(s_macvlan); + lnk.tap = nm_setting_macvlan_get_tap(s_macvlan); + + r = nm_platform_link_macvlan_add(nm_device_get_platform(device), + iface, + parent_ifindex, + &lnk, + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create %s interface '%s' for '%s': %s", + lnk.tap ? "macvtap" : "macvlan", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + /* We assume MACVLAN interfaces always support carrier detect */ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +/*****************************************************************************/ + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!nm_device_parent_get_device(device)) + return FALSE; + return NM_DEVICE_CLASS(nm_device_macvlan_parent_class)->is_available(device, flags); +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device); + NMSettingMacvlan * s_macvlan; + const char * parent = NULL; + + if (!NM_DEVICE_CLASS(nm_device_macvlan_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_macvlan = nm_connection_get_setting_macvlan(connection); + + if (nm_setting_macvlan_get_tap(s_macvlan) != priv->props.tap) { + if (priv->props.tap) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvtap device does not match macvlan profile"); + } else { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvlan device does not match macvtap profile"); + } + return FALSE; + } + + /* Before the device is realized some properties will not be set */ + if (nm_device_is_real(device)) { + if (setting_mode_to_platform(nm_setting_macvlan_get_mode(s_macvlan)) != priv->props.mode) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvlan mode setting differs"); + return FALSE; + } + + if (nm_setting_macvlan_get_promiscuous(s_macvlan) == priv->props.no_promisc) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvlan promiscuous setting differs"); + return FALSE; + } + + /* Check parent interface; could be an interface name or a UUID */ + parent = nm_setting_macvlan_get_parent(s_macvlan); + if (parent) { + if (!nm_device_match_parent(device, parent)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvlan parent setting differs"); + return FALSE; + } + } else { + /* Parent could be a MAC address in an NMSettingWired */ + if (!nm_device_match_parent_hwaddr(device, connection, TRUE)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "macvlan parent mac setting differs"); + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingMacvlan *s_macvlan; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_MACVLAN_SETTING_NAME, + existing_connections, + NULL, + _("MACVLAN connection"), + NULL, + NULL, + TRUE); + + s_macvlan = nm_connection_get_setting_macvlan(connection); + if (!s_macvlan) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'macvlan' setting is required."); + return FALSE; + } + + /* If there's no MACVLAN interface, no parent, and no hardware address in the + * settings, then there's not enough information to complete the setting. + */ + if (!nm_setting_macvlan_get_parent(s_macvlan) + && !nm_device_match_parent_hwaddr(device, connection, TRUE)) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "The 'macvlan' setting had no interface name, parent, or hardware address."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device); + NMSettingMacvlan * s_macvlan = nm_connection_get_setting_macvlan(connection); + int new_mode; + + if (!s_macvlan) { + s_macvlan = (NMSettingMacvlan *) nm_setting_macvlan_new(); + nm_connection_add_setting(connection, (NMSetting *) s_macvlan); + } + + new_mode = platform_mode_to_setting(priv->props.mode); + if (new_mode != nm_setting_macvlan_get_mode(s_macvlan)) + g_object_set(s_macvlan, NM_SETTING_MACVLAN_MODE, new_mode, NULL); + + if (priv->props.no_promisc == nm_setting_macvlan_get_promiscuous(s_macvlan)) + g_object_set(s_macvlan, NM_SETTING_MACVLAN_PROMISCUOUS, !priv->props.no_promisc, NULL); + + if (priv->props.tap != nm_setting_macvlan_get_tap(s_macvlan)) + g_object_set(s_macvlan, NM_SETTING_MACVLAN_TAP, !!priv->props.tap, NULL); + + g_object_set( + s_macvlan, + NM_SETTING_MACVLAN_PARENT, + nm_device_parent_find_for_connection(device, nm_setting_macvlan_get_parent(s_macvlan)), + NULL); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_string(value, platform_mode_to_string(priv->props.mode)); + break; + case PROP_NO_PROMISC: + g_value_set_boolean(value, priv->props.no_promisc); + break; + case PROP_TAP: + g_value_set_boolean(value, priv->props.tap); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_TAP: + priv->props.tap = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +/*****************************************************************************/ + +static void +nm_device_macvlan_init(NMDeviceMacvlan *self) +{} + +#if NM_MORE_ASSERTS +static void +dispose(GObject *object) +{ + G_OBJECT_CLASS(nm_device_macvlan_parent_class)->dispose(object); + + nm_assert(NM_DEVICE_MACVLAN_GET_PRIVATE(object)->parent_state_id == 0); + nm_assert(NM_DEVICE_MACVLAN_GET_PRIVATE(object)->parent_mtu_id == 0); +} +#endif + +static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_MACVLAN, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "s", NM_DEVICE_MACVLAN_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NoPromisc", + "b", + NM_DEVICE_MACVLAN_NO_PROMISC), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tab", + "b", + NM_DEVICE_MACVLAN_TAP), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_macvlan_class_init(NMDeviceMacvlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + +#if NM_MORE_ASSERTS + object_class->dispose = dispose; +#endif + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_macvlan); + + device_class->connection_type_supported = NM_SETTING_MACVLAN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_MACVLAN_SETTING_NAME; + device_class->link_types = + NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP); + device_class->mtu_parent_delta = 0; + + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent; + device_class->is_available = is_available; + device_class->link_changed = link_changed; + device_class->parent_changed_notify = parent_changed_notify; + device_class->update_connection = update_connection; + + obj_properties[PROP_MODE] = g_param_spec_string(NM_DEVICE_MACVLAN_MODE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NO_PROMISC] = + g_param_spec_boolean(NM_DEVICE_MACVLAN_NO_PROMISC, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TAP] = + g_param_spec_boolean(NM_DEVICE_MACVLAN_TAP, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_MACVLAN_DEVICE_FACTORY (nm_macvlan_device_factory_get_type()) +#define NM_MACVLAN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MACVLAN_DEVICE_FACTORY, NMMacvlanDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + NMSettingMacvlan *s_macvlan; + NMLinkType link_type; + gboolean tap; + + if (connection) { + s_macvlan = nm_connection_get_setting_macvlan(connection); + g_assert(s_macvlan); + tap = nm_setting_macvlan_get_tap(s_macvlan); + } else { + g_assert(plink); + tap = plink->type == NM_LINK_TYPE_MACVTAP; + } + + link_type = tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN; + + return g_object_new(NM_TYPE_DEVICE_MACVLAN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Macvlan", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_MACVLAN, + NM_DEVICE_LINK_TYPE, + link_type, + NM_DEVICE_MACVLAN_TAP, + tap, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingMacvlan *s_macvlan; + NMSettingWired * s_wired; + const char * parent = NULL; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL); + + s_macvlan = nm_connection_get_setting_macvlan(connection); + g_assert(s_macvlan); + + parent = nm_setting_macvlan_get_parent(s_macvlan); + if (parent) + return parent; + + /* Try the hardware address from the MACVLAN connection's hardware setting */ + s_wired = nm_connection_get_setting_wired(connection); + if (s_wired) + return nm_setting_wired_get_mac_address(s_wired); + + return NULL; +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + NMSettingMacvlan *s_macvlan; + const char * ifname; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL); + + s_macvlan = nm_connection_get_setting_macvlan(connection); + g_assert(s_macvlan); + + if (!parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + return g_strdup(ifname); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + MACVLAN, + Macvlan, + macvlan, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_MACVLAN_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-macvlan.h b/src/core/devices/nm-device-macvlan.h new file mode 100644 index 0000000..109a2bc --- /dev/null +++ b/src/core/devices/nm-device-macvlan.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_MACVLAN_H__ +#define __NETWORKMANAGER_DEVICE_MACVLAN_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_MACVLAN (nm_device_macvlan_get_type()) +#define NM_DEVICE_MACVLAN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlan)) +#define NM_DEVICE_MACVLAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanClass)) +#define NM_IS_DEVICE_MACVLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_MACVLAN)) +#define NM_IS_DEVICE_MACVLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_MACVLAN)) +#define NM_DEVICE_MACVLAN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanClass)) + +#define NM_DEVICE_MACVLAN_MODE "mode" +#define NM_DEVICE_MACVLAN_NO_PROMISC "no-promisc" +#define NM_DEVICE_MACVLAN_TAP "tap" + +typedef struct _NMDeviceMacvlan NMDeviceMacvlan; +typedef struct _NMDeviceMacvlanClass NMDeviceMacvlanClass; + +GType nm_device_macvlan_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_MACVLAN_H__ */ diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c new file mode 100644 index 0000000..e8a2cb8 --- /dev/null +++ b/src/core/devices/nm-device-ppp.c @@ -0,0 +1,375 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ppp.h" + +#include "nm-ip4-config.h" +#include "nm-act-request.h" +#include "nm-device-factory.h" +#include "nm-device-private.h" +#include "nm-manager.h" +#include "nm-setting-pppoe.h" +#include "platform/nm-platform.h" +#include "ppp/nm-ppp-manager.h" +#include "ppp/nm-ppp-manager-call.h" +#include "ppp/nm-ppp-status.h" + +#define _NMLOG_DEVICE_TYPE NMDevicePpp +#include "nm-device-logging.h" + +/*****************************************************************************/ + +typedef struct _NMDevicePppPrivate { + NMPPPManager *ppp_manager; + NMIP4Config * ip4_config; +} NMDevicePppPrivate; + +struct _NMDevicePpp { + NMDevice parent; + NMDevicePppPrivate _priv; +}; + +struct _NMDevicePppClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDevicePpp, nm_device_ppp, NM_TYPE_DEVICE) + +#define NM_DEVICE_PPP_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDevicePpp, NM_IS_DEVICE_PPP, NMDevice) + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDevicePpp * self = NM_DEVICE_PPP(device); + gs_free char *old_name = NULL; + gs_free_error GError *error = NULL; + + if (!nm_device_take_over_link(device, ifindex, &old_name, &error)) { + _LOGW(LOGD_DEVICE | LOGD_PPP, + "could not take control of link %d: %s", + ifindex, + error->message); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + if (old_name) + nm_manager_remove_device(NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_PPP); + + nm_device_activate_schedule_stage3_ip_config_start(device); +} + +static void +ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDevicePpp * self = NM_DEVICE_PPP(device); + NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self); + + _LOGT(LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd"); + + if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG) { + if (nm_device_activate_ip4_state_in_conf(device)) { + nm_device_activate_schedule_ip_config_result(device, + AF_INET, + NM_IP_CONFIG_CAST(config)); + return; + } + } else { + if (priv->ip4_config) + g_object_unref(priv->ip4_config); + priv->ip4_config = g_object_ref(config); + } +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingPppoe *s_pppoe; + + if (!NM_DEVICE_CLASS(nm_device_ppp_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_pppoe = nm_connection_get_setting_pppoe(connection); + if (!s_pppoe || !nm_setting_pppoe_get_parent(s_pppoe)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "the connection doesn't specify a PPPoE parent interface"); + return FALSE; + } + + return TRUE; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDevicePpp * self = NM_DEVICE_PPP(device); + NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self); + NMSettingPppoe * s_pppoe; + NMActRequest * req; + GError * error = NULL; + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE); + g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE); + + g_clear_object(&priv->ip4_config); + + priv->ppp_manager = nm_ppp_manager_create(nm_setting_pppoe_get_parent(s_pppoe), &error); + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + nm_device_get_route_table(device, AF_INET), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_table(device, AF_INET6), + nm_device_get_route_metric(device, AF_INET6)); + } + + if (!priv->ppp_manager + || !nm_ppp_manager_start(priv->ppp_manager, + req, + nm_setting_pppoe_get_username(s_pppoe), + 30, + 0, + &error)) { + _LOGW(LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message); + g_error_free(error); + + g_clear_object(&priv->ppp_manager); + + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_CALLBACK(ppp_state_changed), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_CALLBACK(ppp_ifindex_set), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_CALLBACK(ppp_ip4_config), + self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + if (addr_family == AF_INET) { + NMDevicePpp * self = NM_DEVICE_PPP(device); + NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self); + + if (priv->ip4_config) { + if (out_config) + *out_config = g_steal_pointer(&priv->ip4_config); + else + g_clear_object(&priv->ip4_config); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + /* Wait IPCP termination */ + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + return NM_DEVICE_CLASS(nm_device_ppp_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + int parent_ifindex; + + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "PPP devices can not be created without a parent interface"); + return FALSE; + } + + parent_ifindex = nm_device_get_ifindex(parent); + g_warn_if_fail(parent_ifindex > 0); + + nm_device_parent_set_ifindex(device, parent_ifindex); + + /* The interface is created later */ + + return TRUE; +} + +static void +deactivate(NMDevice *device) +{ + NMDevicePpp * self = NM_DEVICE_PPP(device); + NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self); + + if (priv->ppp_manager) { + nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL); + g_clear_object(&priv->ppp_manager); + } +} + +static void +nm_device_ppp_init(NMDevicePpp *self) +{} + +static void +dispose(GObject *object) +{ + NMDevicePpp * self = NM_DEVICE_PPP(object); + NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self); + + g_clear_object(&priv->ip4_config); + + G_OBJECT_CLASS(nm_device_ppp_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_ppp = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_PPP, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ppp_class_init(NMDevicePppClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->dispose = dispose; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ppp); + + device_class->connection_type_supported = NM_SETTING_PPPOE_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_PPPOE_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_PPP); + + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->check_connection_compatible = check_connection_compatible; + device_class->create_and_realize = create_and_realize; + device_class->deactivate = deactivate; + device_class->get_generic_capabilities = get_generic_capabilities; +} + +/*****************************************************************************/ + +#define NM_TYPE_PPP_DEVICE_FACTORY (nm_ppp_device_factory_get_type()) +#define NM_PPP_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PPP_DEVICE_FACTORY, NMPppDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_PPP, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Ppp", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_PPP, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_PPP, + NULL); +} + +static gboolean +match_connection(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingPppoe *s_pppoe; + + s_pppoe = nm_connection_get_setting_pppoe(connection); + nm_assert(s_pppoe); + + return !!nm_setting_pppoe_get_parent(s_pppoe); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingPppoe *s_pppoe; + + nm_assert(nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME)); + + s_pppoe = nm_connection_get_setting_pppoe(connection); + nm_assert(s_pppoe); + + return nm_setting_pppoe_get_parent(s_pppoe); +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + nm_assert(nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME)); + + if (!parent_iface) + return NULL; + + return g_strdup(nm_connection_get_interface_name(connection)); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + PPP, + Ppp, + ppp, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_PPP) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_PPPOE_SETTING_NAME), + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface; + factory_class->create_device = create_device; + factory_class->match_connection = match_connection;); diff --git a/src/core/devices/nm-device-ppp.h b/src/core/devices/nm-device-ppp.h new file mode 100644 index 0000000..24c119a --- /dev/null +++ b/src/core/devices/nm-device-ppp.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_PPP_H__ +#define __NETWORKMANAGER_DEVICE_PPP_H__ + +#define NM_TYPE_DEVICE_PPP (nm_device_ppp_get_type()) +#define NM_DEVICE_PPP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_PPP, NMDevicePpp)) +#define NM_DEVICE_PPP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_PPP, NMDevicePppClass)) +#define NM_IS_DEVICE_PPP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_PPP)) +#define NM_IS_DEVICE_PPP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_PPP)) +#define NM_DEVICE_PPP_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_PPP, NMDevicePppClass)) + +typedef struct _NMDevicePpp NMDevicePpp; +typedef struct _NMDevicePppClass NMDevicePppClass; + +GType nm_device_ppp_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_PPP_H__ */ diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h new file mode 100644 index 0000000..8675a69 --- /dev/null +++ b/src/core/devices/nm-device-private.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_PRIVATE_H__ +#define __NETWORKMANAGER_DEVICE_PRIVATE_H__ + +#include "nm-device.h" + +/* This file should only be used by subclasses of NMDevice */ + +typedef enum { + NM_DEVICE_STAGE_STATE_INIT = 0, + NM_DEVICE_STAGE_STATE_PENDING = 1, + NM_DEVICE_STAGE_STATE_COMPLETED = 2, +} NMDeviceStageState; + +typedef enum { + NM_DEVICE_IP_STATE_NONE, + NM_DEVICE_IP_STATE_WAIT, + NM_DEVICE_IP_STATE_CONF, + NM_DEVICE_IP_STATE_DONE, + NM_DEVICE_IP_STATE_FAIL, +} NMDeviceIPState; + +enum NMActStageReturn { + NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */ + NM_ACT_STAGE_RETURN_SUCCESS, /* Activation stage done */ + NM_ACT_STAGE_RETURN_POSTPONE, /* Long-running operation in progress */ + NM_ACT_STAGE_RETURN_IP_WAIT, /* IP config stage is waiting (state IP_WAIT) */ + NM_ACT_STAGE_RETURN_IP_DONE, /* IP config stage is done (state IP_DONE), + * For the ip-config stage, this is similar to + * NM_ACT_STAGE_RETURN_SUCCESS, except that no + * IP config should be committed. */ + NM_ACT_STAGE_RETURN_IP_FAIL, /* IP config stage failed (state IP_FAIL), activation may proceed */ +}; + +#define NM_DEVICE_CAP_NONSTANDARD_CARRIER 0x80000000 +#define NM_DEVICE_CAP_IS_NON_KERNEL 0x40000000 + +#define NM_DEVICE_CAP_INTERNAL_MASK 0xc0000000 + +void nm_device_arp_announce(NMDevice *self); + +NMSettings *nm_device_get_settings(NMDevice *self); + +NMManager *nm_device_get_manager(NMDevice *self); + +gboolean nm_device_set_ip_ifindex(NMDevice *self, int ifindex); + +gboolean nm_device_set_ip_iface(NMDevice *self, const char *iface); + +void nm_device_activate_schedule_stage3_ip_config_start(NMDevice *device); + +gboolean nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family); + +gboolean nm_device_bring_up(NMDevice *self, gboolean wait, gboolean *no_firmware); + +void nm_device_take_down(NMDevice *self, gboolean block); + +gboolean nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **error); + +gboolean nm_device_hw_addr_set(NMDevice * device, + const char *addr, + const char *detail, + gboolean set_permanent); +gboolean nm_device_hw_addr_set_cloned(NMDevice *device, NMConnection *connection, gboolean is_wifi); +gboolean nm_device_hw_addr_reset(NMDevice *device, const char *detail); + +void nm_device_set_firmware_missing(NMDevice *self, gboolean missing); + +void nm_device_activate_schedule_stage1_device_prepare(NMDevice *device, gboolean do_sync); +void nm_device_activate_schedule_stage2_device_config(NMDevice *device, gboolean do_sync); + +void +nm_device_activate_schedule_ip_config_result(NMDevice *device, int addr_family, NMIPConfig *config); + +void nm_device_activate_schedule_ip_config_timeout(NMDevice *device, int addr_family); + +NMDeviceIPState nm_device_activate_get_ip_state(NMDevice *self, int addr_family); + +static inline gboolean +nm_device_activate_ip4_state_in_conf(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_CONF; +} + +static inline gboolean +nm_device_activate_ip4_state_in_wait(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_WAIT; +} + +static inline gboolean +nm_device_activate_ip4_state_done(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_DONE; +} + +static inline gboolean +nm_device_activate_ip6_state_in_conf(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_CONF; +} + +static inline gboolean +nm_device_activate_ip6_state_in_wait(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_WAIT; +} + +static inline gboolean +nm_device_activate_ip6_state_done(NMDevice *self) +{ + return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_DONE; +} + +void nm_device_set_dhcp_anycast_address(NMDevice *device, const char *addr); + +gboolean nm_device_dhcp4_renew(NMDevice *device, gboolean release); +gboolean nm_device_dhcp6_renew(NMDevice *device, gboolean release); + +void nm_device_recheck_available_connections(NMDevice *device); + +void +nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLogDomain log_domain); + +void nm_device_master_release_slaves(NMDevice *self); + +void nm_device_set_carrier(NMDevice *self, gboolean carrier); + +void nm_device_queue_recheck_assume(NMDevice *device); +void nm_device_queue_recheck_available(NMDevice * device, + NMDeviceStateReason available_reason, + NMDeviceStateReason unavailable_reason); + +void nm_device_set_dev2_ip_config(NMDevice *device, int addr_family, NMIPConfig *config); + +gboolean nm_device_hw_addr_is_explict(NMDevice *device); + +void nm_device_ip_method_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason); + +gboolean nm_device_sysctl_ip_conf_set(NMDevice * self, + int addr_family, + const char *property, + const char *value); + +NMIP4Config *nm_device_ip4_config_new(NMDevice *self); + +NMIP6Config *nm_device_ip6_config_new(NMDevice *self); + +NMIPConfig *nm_device_ip_config_new(NMDevice *self, int addr_family); + +NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self); + +/*****************************************************************************/ + +gint64 nm_device_get_configured_mtu_from_connection_default(NMDevice * self, + const char *property_name, + guint32 max_mtu); + +guint32 nm_device_get_configured_mtu_from_connection(NMDevice * device, + GType setting_type, + NMDeviceMtuSource *out_source); + +guint32 nm_device_get_configured_mtu_for_wired(NMDevice * self, + NMDeviceMtuSource *out_source, + gboolean * out_force); + +guint32 nm_device_get_configured_mtu_wired_parent(NMDevice * self, + NMDeviceMtuSource *out_source, + gboolean * out_force); + +void nm_device_commit_mtu(NMDevice *self); + +/*****************************************************************************/ + +#define NM_DEVICE_DEFINE_LINK_TYPES(...) \ + ((NM_NARG(__VA_ARGS__) == 0) ? NULL : ({ \ + static const NMLinkType _types[NM_NARG(__VA_ARGS__) + 1] = { \ + __VA_ARGS__ _NM_MACRO_COMMA_IF_ARGS(__VA_ARGS__) NM_LINK_TYPE_NONE, \ + }; \ + \ + nm_assert(_types[NM_NARG(__VA_ARGS__)] == NM_LINK_TYPE_NONE); \ + _types; \ + })) + +gboolean _nm_device_hash_check_invalid_keys(GHashTable * hash, + const char * setting_name, + GError ** error, + const char *const *whitelist); +#define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \ + _nm_device_hash_check_invalid_keys(hash, setting_name, error, NM_MAKE_STRV(__VA_ARGS__)) + +gboolean nm_device_match_parent(NMDevice *device, const char *parent); +gboolean nm_device_match_parent_hwaddr(NMDevice * device, + NMConnection *connection, + gboolean fail_if_no_hwaddr); + +/*****************************************************************************/ + +void nm_device_auth_request(NMDevice * self, + GDBusMethodInvocation * context, + NMConnection * connection, + const char * permission, + gboolean allow_interaction, + GCancellable * cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data); + +#endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c new file mode 100644 index 0000000..c4dd4b4 --- /dev/null +++ b/src/core/devices/nm-device-tun.c @@ -0,0 +1,571 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-tun.h" + +#include +#include +#include + +#include "nm-act-request.h" +#include "nm-device-private.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-tun.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceTun +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceTun, + PROP_OWNER, + PROP_GROUP, + PROP_MODE, + PROP_NO_PI, + PROP_VNET_HDR, + PROP_MULTI_QUEUE, ); + +typedef struct { + NMPlatformLnkTun props; +} NMDeviceTunPrivate; + +struct _NMDeviceTun { + NMDevice parent; + NMDeviceTunPrivate _priv; +}; + +struct _NMDeviceTunClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE) + +#define NM_DEVICE_TUN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceTun, NM_IS_DEVICE_TUN, NMDevice) + +/*****************************************************************************/ + +static void +update_properties_from_struct(NMDeviceTun *self, const NMPlatformLnkTun *props) +{ + NMDeviceTunPrivate * priv = NM_DEVICE_TUN_GET_PRIVATE(self); + const NMPlatformLnkTun props0 = {}; + + if (!props) { + /* allow passing %NULL to reset all properties. */ + props = &props0; + } + + g_object_freeze_notify(G_OBJECT(self)); + +#define CHECK_PROPERTY_CHANGED_VALID(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field \ + || priv->props.field##_valid != props->field##_valid) { \ + priv->props.field##_valid = props->field##_valid; \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED_VALID(owner, PROP_OWNER); + CHECK_PROPERTY_CHANGED_VALID(group, PROP_GROUP); + CHECK_PROPERTY_CHANGED(type, PROP_MODE); + CHECK_PROPERTY_CHANGED(pi, PROP_NO_PI); + CHECK_PROPERTY_CHANGED(vnet_hdr, PROP_VNET_HDR); + CHECK_PROPERTY_CHANGED(multi_queue, PROP_MULTI_QUEUE); + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +update_properties(NMDeviceTun *self) +{ + NMPlatformLnkTun props_storage; + const NMPlatformLnkTun *props = NULL; + int ifindex; + + ifindex = nm_device_get_ifindex(NM_DEVICE(self)); + if (ifindex > 0 + && nm_platform_link_tun_get_properties(nm_device_get_platform(NM_DEVICE(self)), + ifindex, + &props_storage)) + props = &props_storage; + + update_properties_from_struct(self, props); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_tun_parent_class)->link_changed(device, pllink); + update_properties(NM_DEVICE_TUN(device)); +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingTun *s_tun; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_TUN_SETTING_NAME, + existing_connections, + NULL, + _("TUN connection"), + NULL, + NULL, + TRUE); + + s_tun = nm_connection_get_setting_tun(connection); + if (!s_tun) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'tun' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceTun * self = NM_DEVICE_TUN(device); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self); + NMSettingTun * s_tun; + NMSettingTunMode mode; + char s_buf[100]; + const char * str; + + /* Note: since we read tun properties from sysctl for older kernels, + * we don't get proper change notifications. Make sure that all our + * tun properties are up to date at this point. We should not do this, + * if we would entirely rely on netlink events. */ + update_properties(NM_DEVICE_TUN(device)); + + switch (priv->props.type) { + case IFF_TUN: + mode = NM_SETTING_TUN_MODE_TUN; + break; + case IFF_TAP: + mode = NM_SETTING_TUN_MODE_TAP; + break; + default: + /* Huh? */ + return; + } + + s_tun = nm_connection_get_setting_tun(connection); + if (!s_tun) { + s_tun = (NMSettingTun *) nm_setting_tun_new(); + nm_connection_add_setting(connection, (NMSetting *) s_tun); + } + + if (mode != nm_setting_tun_get_mode(s_tun)) + g_object_set(G_OBJECT(s_tun), NM_SETTING_TUN_MODE, (guint) mode, NULL); + + str = priv->props.owner_valid ? nm_sprintf_buf(s_buf, "%" G_GINT32_FORMAT, priv->props.owner) + : NULL; + if (!nm_streq0(str, nm_setting_tun_get_owner(s_tun))) + g_object_set(G_OBJECT(s_tun), NM_SETTING_TUN_OWNER, str, NULL); + + str = priv->props.group_valid ? nm_sprintf_buf(s_buf, "%" G_GINT32_FORMAT, priv->props.group) + : NULL; + if (!nm_streq0(str, nm_setting_tun_get_group(s_tun))) + g_object_set(G_OBJECT(s_tun), NM_SETTING_TUN_GROUP, str, NULL); + + if (priv->props.pi != nm_setting_tun_get_pi(s_tun)) + g_object_set(G_OBJECT(s_tun), NM_SETTING_TUN_PI, (gboolean) priv->props.pi, NULL); + if (priv->props.vnet_hdr != nm_setting_tun_get_vnet_hdr(s_tun)) + g_object_set(G_OBJECT(s_tun), + NM_SETTING_TUN_VNET_HDR, + (gboolean) priv->props.vnet_hdr, + NULL); + if (priv->props.multi_queue != nm_setting_tun_get_multi_queue(s_tun)) + g_object_set(G_OBJECT(s_tun), + NM_SETTING_TUN_MULTI_QUEUE, + (gboolean) priv->props.multi_queue, + NULL); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMPlatformLnkTun props = {}; + NMSettingTun * s_tun; + gint64 owner; + gint64 group; + int r; + + s_tun = nm_connection_get_setting_tun(connection); + g_return_val_if_fail(s_tun, FALSE); + + switch (nm_setting_tun_get_mode(s_tun)) { + case NM_SETTING_TUN_MODE_TAP: + props.type = IFF_TAP; + break; + case NM_SETTING_TUN_MODE_TUN: + props.type = IFF_TUN; + break; + default: + g_return_val_if_reached(FALSE); + } + + owner = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_owner(s_tun), 10, 0, G_MAXINT32, -1); + if (owner != -1) { + props.owner_valid = TRUE; + props.owner = owner; + } + group = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_group(s_tun), 10, 0, G_MAXINT32, -1); + if (group != -1) { + props.group_valid = TRUE; + props.group = group; + } + + props.pi = nm_setting_tun_get_pi(s_tun); + props.vnet_hdr = nm_setting_tun_get_vnet_hdr(s_tun); + props.multi_queue = nm_setting_tun_get_multi_queue(s_tun); + props.persist = TRUE; + + r = nm_platform_link_tun_add(nm_device_get_platform(device), iface, &props, out_plink, NULL); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create TUN/TAP interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +static gboolean +_same_og(const char *str, gboolean og_valid, guint32 og_num) +{ + gint64 v; + + v = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXINT32, -1); + return (!og_valid && (v == (gint64) -1)) || (og_valid && (((guint32) v) == og_num)); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceTun * self = NM_DEVICE_TUN(device); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self); + NMSettingTunMode mode; + NMSettingTun * s_tun; + + if (!NM_DEVICE_CLASS(nm_device_tun_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_device_is_real(device)) { + switch (priv->props.type) { + case IFF_TUN: + mode = NM_SETTING_TUN_MODE_TUN; + break; + case IFF_TAP: + mode = NM_SETTING_TUN_MODE_TAP; + break; + default: + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "invalid tun type on device"); + return FALSE; + } + + s_tun = nm_connection_get_setting_tun(connection); + + if (mode != nm_setting_tun_get_mode(s_tun)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun mode setting mismatches"); + return FALSE; + } + if (!_same_og(nm_setting_tun_get_owner(s_tun), + priv->props.owner_valid, + priv->props.owner)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun owner setting mismatches"); + return FALSE; + } + if (!_same_og(nm_setting_tun_get_group(s_tun), + priv->props.group_valid, + priv->props.group)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun group setting mismatches"); + return FALSE; + } + if (nm_setting_tun_get_pi(s_tun) != priv->props.pi) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun pi setting mismatches"); + return FALSE; + } + if (nm_setting_tun_get_vnet_hdr(s_tun) != priv->props.vnet_hdr) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun vnet-hdr setting mismatches"); + return FALSE; + } + if (nm_setting_tun_get_multi_queue(s_tun) != priv->props.multi_queue) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "tun multi-queue setting mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceTun * self = NM_DEVICE_TUN(device); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self); + + if (priv->props.type == IFF_TUN) { + /* Nothing to do for TUN devices */ + } else { + if (!nm_device_hw_addr_set_cloned(device, + nm_device_get_applied_connection(device), + FALSE)) { + *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +unrealize_notify(NMDevice *device) +{ + NM_DEVICE_CLASS(nm_device_tun_parent_class)->unrealize_notify(device); + update_properties_from_struct(NM_DEVICE_TUN(device), NULL); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceTun * self = NM_DEVICE_TUN(object); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self); + const char * s; + + switch (prop_id) { + case PROP_OWNER: + g_value_set_int64(value, + priv->props.owner_valid ? (gint64) priv->props.owner : (gint64) -1); + break; + case PROP_GROUP: + g_value_set_int64(value, + priv->props.group_valid ? (gint64) priv->props.group : (gint64) -1); + break; + case PROP_MODE: + switch (priv->props.type) { + case IFF_TUN: + s = "tun"; + break; + case IFF_TAP: + s = "tap"; + break; + default: + s = NULL; + break; + } + g_value_set_static_string(value, s); + break; + case PROP_NO_PI: + g_value_set_boolean(value, !priv->props.pi); + break; + case PROP_VNET_HDR: + g_value_set_boolean(value, priv->props.vnet_hdr); + break; + case PROP_MULTI_QUEUE: + g_value_set_boolean(value, priv->props.multi_queue); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_tun_init(NMDeviceTun *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_tun = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_TUN, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Owner", "x", NM_DEVICE_TUN_OWNER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Group", "x", NM_DEVICE_TUN_GROUP), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "s", NM_DEVICE_TUN_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NoPi", "b", NM_DEVICE_TUN_NO_PI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("VnetHdr", + "b", + NM_DEVICE_TUN_VNET_HDR), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("MultiQueue", + "b", + NM_DEVICE_TUN_MULTI_QUEUE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_tun_class_init(NMDeviceTunClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_tun); + + device_class->connection_type_supported = NM_SETTING_TUN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_TUN_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_TUN); + + device_class->link_changed = link_changed; + device_class->complete_connection = complete_connection; + device_class->check_connection_compatible = check_connection_compatible; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->unrealize_notify = unrealize_notify; + device_class->update_connection = update_connection; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + + obj_properties[PROP_OWNER] = g_param_spec_int64(NM_DEVICE_TUN_OWNER, + "", + "", + -1, + G_MAXUINT32, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_GROUP] = g_param_spec_int64(NM_DEVICE_TUN_GROUP, + "", + "", + -1, + G_MAXUINT32, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MODE] = g_param_spec_string(NM_DEVICE_TUN_MODE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NO_PI] = g_param_spec_boolean(NM_DEVICE_TUN_NO_PI, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_VNET_HDR] = g_param_spec_boolean(NM_DEVICE_TUN_VNET_HDR, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MULTI_QUEUE] = + g_param_spec_boolean(NM_DEVICE_TUN_MULTI_QUEUE, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_TUN_DEVICE_FACTORY (nm_tun_device_factory_get_type()) +#define NM_TUN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_TUN_DEVICE_FACTORY, NMTunDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + g_return_val_if_fail(!plink || plink->type == NM_LINK_TYPE_TUN, NULL); + g_return_val_if_fail(!connection + || nm_streq0(nm_connection_get_connection_type(connection), + NM_SETTING_TUN_SETTING_NAME), + NULL); + + return g_object_new(NM_TYPE_DEVICE_TUN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Tun", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_TUN, + NM_DEVICE_LINK_TYPE, + (guint) NM_LINK_TYPE_TUN, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + TUN, + Tun, + tun, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_TUN) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_TUN_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-tun.h b/src/core/devices/nm-device-tun.h new file mode 100644 index 0000000..84497ea --- /dev/null +++ b/src/core/devices/nm-device-tun.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_TUN_H__ +#define __NETWORKMANAGER_DEVICE_TUN_H__ + +#include "nm-device-generic.h" + +#define NM_TYPE_DEVICE_TUN (nm_device_tun_get_type()) +#define NM_DEVICE_TUN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_TUN, NMDeviceTun)) +#define NM_DEVICE_TUN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_TUN, NMDeviceTunClass)) +#define NM_IS_DEVICE_TUN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_TUN)) +#define NM_IS_DEVICE_TUN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_TUN)) +#define NM_DEVICE_TUN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_TUN, NMDeviceTunClass)) + +#define NM_DEVICE_TUN_OWNER "owner" +#define NM_DEVICE_TUN_GROUP "group" +#define NM_DEVICE_TUN_MODE "mode" +#define NM_DEVICE_TUN_NO_PI "no-pi" +#define NM_DEVICE_TUN_VNET_HDR "vnet-hdr" +#define NM_DEVICE_TUN_MULTI_QUEUE "multi-queue" + +typedef struct _NMDeviceTun NMDeviceTun; +typedef struct _NMDeviceTunClass NMDeviceTunClass; + +GType nm_device_tun_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_TUN_H__ */ diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c new file mode 100644 index 0000000..0fc76b1 --- /dev/null +++ b/src/core/devices/nm-device-veth.c @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-core-internal.h" +#include "nm-device-veth.h" +#include "nm-device-private.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-veth.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceVeth +#include "nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceVeth { + NMDeviceEthernet parent; +}; + +struct _NMDeviceVethClass { + NMDeviceEthernetClass parent; +}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceVeth, PROP_PEER, ); + +/*****************************************************************************/ + +G_DEFINE_TYPE(NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET) + +/*****************************************************************************/ + +static void +update_properties(NMDevice *device) +{ + NMDevice *peer; + int ifindex, peer_ifindex; + + ifindex = nm_device_get_ifindex(device); + + if (ifindex <= 0 + || !nm_platform_link_veth_get_properties(nm_device_get_platform(device), + ifindex, + &peer_ifindex)) + peer_ifindex = 0; + + nm_device_parent_set_ifindex(device, peer_ifindex); + + peer = nm_device_parent_get_device(device); + if (peer && NM_IS_DEVICE_VETH(peer) && nm_device_parent_get_ifindex(peer) <= 0) + update_properties(peer); +} + +static gboolean +can_unmanaged_external_down(NMDevice *self) +{ + /* Unless running in a container, an udev rule causes these to be + * unmanaged. If there's no udev then we're probably in a container + * and should IFF_UP and configure the veth ourselves even if we + * didn't create it. */ + return FALSE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_veth_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMSettingVeth *s_veth; + int r; + + s_veth = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VETH); + if (!s_veth) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Profile %s (%s) is not a suitable veth profile", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + return FALSE; + } + + r = nm_platform_link_veth_add(nm_device_get_platform(device), + iface, + nm_setting_veth_get_peer(s_veth), + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create veth interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +/*****************************************************************************/ + +static void +nm_device_veth_init(NMDeviceVeth *self) +{} + +static void +parent_changed_notify(NMDevice *device, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + NM_DEVICE_CLASS(nm_device_veth_parent_class) + ->parent_changed_notify(device, old_ifindex, old_parent, new_ifindex, new_parent); + _notify(NM_DEVICE_VETH(device), PROP_PEER); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceVeth *self = NM_DEVICE_VETH(object); + NMDevice * peer; + + switch (prop_id) { + case PROP_PEER: + peer = nm_device_parent_get_device(NM_DEVICE(self)); + if (peer && !NM_IS_DEVICE_VETH(peer)) + peer = NULL; + nm_dbus_utils_g_value_set_object_path(value, peer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static const NMDBusInterfaceInfoExtended interface_info_device_veth = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_VETH, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Peer", + "o", + NM_DEVICE_VETH_PEER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_veth_class_init(NMDeviceVethClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_veth); + + device_class->connection_type_supported = NULL; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_VETH); + + device_class->can_unmanaged_external_down = can_unmanaged_external_down; + device_class->link_changed = link_changed; + device_class->parent_changed_notify = parent_changed_notify; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + + obj_properties[PROP_PEER] = g_param_spec_string(NM_DEVICE_VETH_PEER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_VETH_DEVICE_FACTORY (nm_veth_device_factory_get_type()) +#define NM_VETH_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VETH_DEVICE_FACTORY, NMVethDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_VETH, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Veth", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_VETH, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_VETH, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + VETH, + Veth, + veth, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VETH) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VETH_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-veth.h b/src/core/devices/nm-device-veth.h new file mode 100644 index 0000000..d43a0a4 --- /dev/null +++ b/src/core/devices/nm-device-veth.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_VETH_H__ +#define __NETWORKMANAGER_DEVICE_VETH_H__ + +#include "nm-device-ethernet.h" + +#define NM_TYPE_DEVICE_VETH (nm_device_veth_get_type()) +#define NM_DEVICE_VETH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_VETH, NMDeviceVeth)) +#define NM_DEVICE_VETH_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_VETH, NMDeviceVethClass)) +#define NM_IS_DEVICE_VETH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_VETH)) +#define NM_IS_DEVICE_VETH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_VETH)) +#define NM_DEVICE_VETH_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_VETH, NMDeviceVethClass)) + +#define NM_DEVICE_VETH_PEER "peer" + +typedef struct _NMDeviceVeth NMDeviceVeth; +typedef struct _NMDeviceVethClass NMDeviceVethClass; + +GType nm_device_veth_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_VETH_H__ */ diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c new file mode 100644 index 0000000..edb16ed --- /dev/null +++ b/src/core/devices/nm-device-vlan.c @@ -0,0 +1,688 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-vlan.h" + +#include + +#include "nm-manager.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "settings/nm-settings.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-manager.h" +#include "nm-core-internal.h" +#include "platform/nmp-object.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceVlan +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceVlan, PROP_VLAN_ID, ); + +typedef struct { + gulong parent_state_id; + gulong parent_hwaddr_id; + gulong parent_mtu_id; + guint vlan_id; +} NMDeviceVlanPrivate; + +struct _NMDeviceVlan { + NMDevice parent; + NMDeviceVlanPrivate _priv; +}; + +struct _NMDeviceVlanClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceVlan, nm_device_vlan, NM_TYPE_DEVICE) + +#define NM_DEVICE_VLAN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceVlan, NM_IS_DEVICE_VLAN, NMDevice) + +/*****************************************************************************/ + +static void +parent_state_changed(NMDevice * parent, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceVlan *self = NM_DEVICE_VLAN(user_data); + + /* We'll react to our own carrier state notifications. Ignore the parent's. */ + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CARRIER) + return; + + nm_device_set_unmanaged_by_flags(NM_DEVICE(self), + NM_UNMANAGED_PARENT, + !nm_device_get_managed(parent, FALSE), + reason); +} + +static void +parent_mtu_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data) +{ + /* the MTU of a VLAN device is limited by the parent's MTU. + * + * When the parent's MTU changes, try to re-set the MTU. */ + nm_device_commit_mtu(user_data); +} + +static void +parent_hwaddr_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceVlan * self = NM_DEVICE_VLAN(device); + NMConnection * connection; + const char * new_mac, *old_mac; + NMSettingIPConfig *s_ip6; + + /* Never touch assumed devices */ + if (nm_device_sys_iface_state_is_external_or_assume(device)) + return; + + connection = nm_device_get_applied_connection(device); + if (!connection) + return; + + /* Update the VLAN MAC only if configuration does not specify one */ + if (nm_device_hw_addr_is_explict(device)) + return; + + old_mac = nm_device_get_hw_address(device); + new_mac = nm_device_get_hw_address(parent); + if (nm_streq0(old_mac, new_mac)) + return; + + _LOGD(LOGD_VLAN, + "parent hardware address changed to %s%s%s", + NM_PRINT_FMT_QUOTE_STRING(new_mac)); + if (new_mac) { + nm_device_hw_addr_set(device, new_mac, "vlan-parent", TRUE); + nm_device_arp_announce(device); + /* When changing the hw address the interface is taken down, + * removing the IPv6 configuration; reapply it. + */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (s_ip6) + nm_device_reactivate_ip_config(device, AF_INET6, s_ip6, s_ip6); + } +} + +static void +parent_changed_notify(NMDevice *device, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + NMDeviceVlan * self = NM_DEVICE_VLAN(device); + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(self); + + NM_DEVICE_CLASS(nm_device_vlan_parent_class) + ->parent_changed_notify(device, old_ifindex, old_parent, new_ifindex, new_parent); + + /* note that @self doesn't have to clear @parent_state_id on dispose, + * because NMDevice's dispose() will unset the parent, which in turn calls + * parent_changed_notify(). */ + nm_clear_g_signal_handler(old_parent, &priv->parent_state_id); + nm_clear_g_signal_handler(old_parent, &priv->parent_hwaddr_id); + nm_clear_g_signal_handler(old_parent, &priv->parent_mtu_id); + + if (new_parent) { + priv->parent_state_id = g_signal_connect(new_parent, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(parent_state_changed), + device); + + priv->parent_hwaddr_id = g_signal_connect(new_parent, + "notify::" NM_DEVICE_HW_ADDRESS, + G_CALLBACK(parent_hwaddr_maybe_changed), + device); + parent_hwaddr_maybe_changed(new_parent, NULL, self); + + priv->parent_mtu_id = g_signal_connect(new_parent, + "notify::" NM_DEVICE_MTU, + G_CALLBACK(parent_mtu_maybe_changed), + device); + parent_mtu_maybe_changed(new_parent, NULL, self); + + /* Set parent-dependent unmanaged flag */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PARENT, + !nm_device_get_managed(new_parent, FALSE), + NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED); + } + + /* Recheck availability now that the parent has changed */ + if (new_ifindex > 0) { + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_PARENT_CHANGED, + NM_DEVICE_STATE_REASON_PARENT_CHANGED); + } +} + +static void +update_properties(NMDevice *device) +{ + NMDeviceVlanPrivate * priv; + const NMPlatformLink * plink = NULL; + const NMPlatformLnkVlan *plnk = NULL; + int ifindex; + int parent_ifindex = 0; + guint vlan_id; + + g_return_if_fail(NM_IS_DEVICE_VLAN(device)); + + priv = NM_DEVICE_VLAN_GET_PRIVATE(device); + + ifindex = nm_device_get_ifindex(device); + + if (ifindex > 0) + plnk = nm_platform_link_get_lnk_vlan(nm_device_get_platform(device), ifindex, &plink); + + if (plnk && plink->parent > 0) + parent_ifindex = plink->parent; + + g_object_freeze_notify((GObject *) device); + + nm_device_parent_set_ifindex(device, parent_ifindex); + + vlan_id = plnk ? plnk->id : 0; + if (vlan_id != priv->vlan_id) { + priv->vlan_id = vlan_id; + _notify((NMDeviceVlan *) device, PROP_VLAN_ID); + } + + g_object_thaw_notify((GObject *) device); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_vlan_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(device); + const char * iface = nm_device_get_iface(device); + NMSettingVlan * s_vlan; + int parent_ifindex; + guint vlan_id; + int r; + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + if (!parent) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "VLAN devices can not be created without a parent interface"); + return FALSE; + } + + parent_ifindex = nm_device_get_ifindex(parent); + if (parent_ifindex <= 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_MISSING_DEPENDENCIES, + "cannot retrieve ifindex of interface %s (%s)", + nm_device_get_iface(parent), + nm_device_get_type_desc(parent)); + return FALSE; + } + + if (!nm_device_supports_vlans(parent)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "no support for VLANs on interface %s of type %s", + nm_device_get_iface(parent), + nm_device_get_type_desc(parent)); + return FALSE; + } + + vlan_id = nm_setting_vlan_get_id(s_vlan); + + r = nm_platform_link_vlan_add(nm_device_get_platform(device), + iface, + parent_ifindex, + vlan_id, + nm_setting_vlan_get_flags(s_vlan), + out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create VLAN interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + nm_device_parent_set_ifindex(device, parent_ifindex); + if (vlan_id != priv->vlan_id) { + priv->vlan_id = vlan_id; + _notify((NMDeviceVlan *) device, PROP_VLAN_ID); + } + + return TRUE; +} + +static void +unrealize_notify(NMDevice *device) +{ + NMDeviceVlan * self = NM_DEVICE_VLAN(device); + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(self); + + NM_DEVICE_CLASS(nm_device_vlan_parent_class)->unrealize_notify(device); + + if (priv->vlan_id != 0) { + priv->vlan_id = 0; + _notify(self, PROP_VLAN_ID); + } +} + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + /* We assume VLAN interfaces always support carrier detect */ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +/*****************************************************************************/ + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!nm_device_parent_get_device(device)) + return FALSE; + return NM_DEVICE_CLASS(nm_device_vlan_parent_class)->is_available(device, flags); +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(device); + NMSettingVlan * s_vlan; + const char * parent; + + if (!NM_DEVICE_CLASS(nm_device_vlan_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_device_is_real(device)) { + s_vlan = nm_connection_get_setting_vlan(connection); + + if (nm_setting_vlan_get_id(s_vlan) != priv->vlan_id) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vlan id setting mismatches"); + return FALSE; + } + + /* Check parent interface; could be an interface name or a UUID */ + parent = nm_setting_vlan_get_parent(s_vlan); + if (parent) { + if (!nm_device_match_parent(device, parent)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vlan parent setting differs"); + return FALSE; + } + } else { + /* Parent could be a MAC address in an NMSettingWired */ + if (!nm_device_match_parent_hwaddr(device, connection, TRUE)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vlan parent mac setting differs"); + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + if (!nm_device_is_real(device)) + return TRUE; + + return NM_DEVICE_CLASS(nm_device_vlan_parent_class) + ->check_connection_available(device, connection, flags, specific_object, error); +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingVlan *s_vlan; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_VLAN_SETTING_NAME, + existing_connections, + NULL, + _("VLAN connection"), + NULL, + NULL, + TRUE); + + s_vlan = nm_connection_get_setting_vlan(connection); + if (!s_vlan) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'vlan' setting is required."); + return FALSE; + } + + /* If there's no VLAN interface, no parent, and no hardware address in the + * settings, then there's not enough information to complete the setting. + */ + if (!nm_setting_vlan_get_parent(s_vlan) + && !nm_device_match_parent_hwaddr(device, connection, TRUE)) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "The 'vlan' setting had no interface name, parent, or hardware address."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceVlanPrivate * priv = NM_DEVICE_VLAN_GET_PRIVATE(device); + NMSettingVlan * s_vlan = nm_connection_get_setting_vlan(connection); + int ifindex = nm_device_get_ifindex(device); + const NMPlatformLink *plink; + const NMPObject * polnk; + guint vlan_id; + guint vlan_flags; + + if (!s_vlan) { + s_vlan = (NMSettingVlan *) nm_setting_vlan_new(); + nm_connection_add_setting(connection, (NMSetting *) s_vlan); + } + + polnk = nm_platform_link_get_lnk(nm_device_get_platform(device), + ifindex, + NM_LINK_TYPE_VLAN, + &plink); + + if (polnk) + vlan_id = polnk->lnk_vlan.id; + else + vlan_id = priv->vlan_id; + if (vlan_id != nm_setting_vlan_get_id(s_vlan)) + g_object_set(s_vlan, NM_SETTING_VLAN_ID, vlan_id, NULL); + + g_object_set(s_vlan, + NM_SETTING_VLAN_PARENT, + nm_device_parent_find_for_connection(device, nm_setting_vlan_get_parent(s_vlan)), + NULL); + + if (polnk) + vlan_flags = polnk->lnk_vlan.flags; + else + vlan_flags = NM_VLAN_FLAG_REORDER_HEADERS; + if (vlan_flags != nm_setting_vlan_get_flags(s_vlan)) + g_object_set(s_vlan, NM_SETTING_VLAN_FLAGS, (NMVlanFlags) vlan_flags, NULL); + + if (polnk) { + _nm_setting_vlan_set_priorities(s_vlan, + NM_VLAN_INGRESS_MAP, + polnk->_lnk_vlan.ingress_qos_map, + polnk->_lnk_vlan.n_ingress_qos_map); + _nm_setting_vlan_set_priorities(s_vlan, + NM_VLAN_EGRESS_MAP, + polnk->_lnk_vlan.egress_qos_map, + polnk->_lnk_vlan.n_egress_qos_map); + } else { + _nm_setting_vlan_set_priorities(s_vlan, NM_VLAN_INGRESS_MAP, NULL, 0); + _nm_setting_vlan_set_priorities(s_vlan, NM_VLAN_EGRESS_MAP, NULL, 0); + } +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDevice * parent_device; + NMSettingVlan *s_vlan; + + /* Change MAC address to parent's one if needed */ + parent_device = nm_device_parent_get_device(device); + if (parent_device) { + parent_hwaddr_maybe_changed(parent_device, NULL, device); + parent_mtu_maybe_changed(parent_device, NULL, device); + } + + s_vlan = nm_device_get_applied_setting(device, NM_TYPE_SETTING_VLAN); + if (s_vlan) { + gs_free NMVlanQosMapping *ingress_map = NULL; + gs_free NMVlanQosMapping *egress_map = NULL; + guint n_ingress_map = 0; + guint n_egress_map = 0; + + _nm_setting_vlan_get_priorities(s_vlan, NM_VLAN_INGRESS_MAP, &ingress_map, &n_ingress_map); + _nm_setting_vlan_get_priorities(s_vlan, NM_VLAN_EGRESS_MAP, &egress_map, &n_egress_map); + + nm_platform_link_vlan_change(nm_device_get_platform(device), + nm_device_get_ifindex(device), + NM_VLAN_FLAGS_ALL, + nm_setting_vlan_get_flags(s_vlan), + TRUE, + ingress_map, + n_ingress_map, + TRUE, + egress_map, + n_egress_map); + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_VLAN_ID: + g_value_set_uint(value, priv->vlan_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_vlan_init(NMDeviceVlan *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_vlan = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_VLAN, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("VlanId", + "u", + NM_DEVICE_VLAN_ID), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_vlan_class_init(NMDeviceVlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_vlan); + + device_class->connection_type_supported = NM_SETTING_VLAN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_VLAN_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_VLAN); + device_class->mtu_parent_delta = 0; /* VLANs can have the same MTU of parent */ + + device_class->create_and_realize = create_and_realize; + device_class->link_changed = link_changed; + device_class->unrealize_notify = unrealize_notify; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent; + device_class->is_available = is_available; + device_class->parent_changed_notify = parent_changed_notify; + + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->update_connection = update_connection; + + obj_properties[PROP_VLAN_ID] = g_param_spec_uint(NM_DEVICE_VLAN_ID, + "", + "", + 0, + 4095, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_VLAN_DEVICE_FACTORY (nm_vlan_device_factory_get_type()) +#define NM_VLAN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VLAN_DEVICE_FACTORY, NMVlanDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_VLAN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_DRIVER, + "8021q", + NM_DEVICE_TYPE_DESC, + "VLAN", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_VLAN, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_VLAN, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingVlan * s_vlan; + NMSettingWired *s_wired; + const char * parent = NULL; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + parent = nm_setting_vlan_get_parent(s_vlan); + if (parent) + return parent; + + /* Try the hardware address from the VLAN connection's hardware setting */ + s_wired = nm_connection_get_setting_wired(connection); + if (s_wired) + return nm_setting_wired_get_mac_address(s_wired); + + return NULL; +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + const char * ifname; + NMSettingVlan *s_vlan; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + if (!parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + if (ifname) + return g_strdup(ifname); + + /* If the connection doesn't specify the interface name for the VLAN + * device, we create one for it using the VLAN ID and the parent + * interface's name. + */ + return nm_utils_new_vlan_name(parent_iface, nm_setting_vlan_get_id(s_vlan)); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + VLAN, + Vlan, + vlan, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VLAN) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VLAN_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-vlan.h b/src/core/devices/nm-device-vlan.h new file mode 100644 index 0000000..5270706 --- /dev/null +++ b/src/core/devices/nm-device-vlan.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_VLAN_H__ +#define __NETWORKMANAGER_DEVICE_VLAN_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_VLAN (nm_device_vlan_get_type()) +#define NM_DEVICE_VLAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_VLAN, NMDeviceVlan)) +#define NM_DEVICE_VLAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_VLAN, NMDeviceVlanClass)) +#define NM_IS_DEVICE_VLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_VLAN)) +#define NM_IS_DEVICE_VLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_VLAN)) +#define NM_DEVICE_VLAN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_VLAN, NMDeviceVlanClass)) + +typedef enum { + NM_VLAN_ERROR_CONNECTION_NOT_VLAN = 0, /*< nick=ConnectionNotVlan >*/ + NM_VLAN_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_VLAN_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMVlanError; + +#define NM_DEVICE_VLAN_ID "vlan-id" + +typedef struct _NMDeviceVlan NMDeviceVlan; +typedef struct _NMDeviceVlanClass NMDeviceVlanClass; + +GType nm_device_vlan_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_VLAN_H__ */ diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c new file mode 100644 index 0000000..6e672b6 --- /dev/null +++ b/src/core/devices/nm-device-vrf.c @@ -0,0 +1,375 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "nm-default.h" + +#include "nm-device-vrf.h" + +#include "nm-core-internal.h" +#include "nm-device-factory.h" +#include "nm-device-private.h" +#include "nm-manager.h" +#include "nm-setting-vrf.h" +#include "platform/nm-platform.h" +#include "settings/nm-settings.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceVrf +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceVrf, PROP_TABLE, ); + +typedef struct { + NMPlatformLnkVrf props; +} NMDeviceVrfPrivate; + +struct _NMDeviceVrf { + NMDevice parent; + NMDeviceVrfPrivate _priv; +}; + +struct _NMDeviceVrfClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceVrf, nm_device_vrf, NM_TYPE_DEVICE) + +#define NM_DEVICE_VRF_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceVrf, NM_IS_DEVICE_VRF, NMDevice) + +/*****************************************************************************/ + +static void +do_update_properties(NMDeviceVrf *self, const NMPlatformLnkVrf *props) +{ + NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(self); + GObject * object = G_OBJECT(self); + NMPlatformLnkVrf props_null; + + if (!props) { + props_null = (NMPlatformLnkVrf){}; + props = &props_null; + } + + g_object_freeze_notify(object); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED(table, PROP_TABLE); + + g_object_thaw_notify(object); +} + +static void +update_properties(NMDevice *device) +{ + NMDeviceVrf * self = NM_DEVICE_VRF(device); + const NMPlatformLnkVrf *props; + + props = nm_platform_link_get_lnk_vrf(nm_device_get_platform(device), + nm_device_get_ifindex(device), + NULL); + if (!props) { + _LOGW(LOGD_PLATFORM, "could not get vrf properties"); + return; + } + + do_update_properties(self, props); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_vrf_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static void +unrealize_notify(NMDevice *device) +{ + NMDeviceVrf *self = NM_DEVICE_VRF(device); + + NM_DEVICE_CLASS(nm_device_vrf_parent_class)->unrealize_notify(device); + + do_update_properties(self, NULL); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMPlatformLnkVrf props = {}; + NMSettingVrf * s_vrf; + int r; + + s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF); + nm_assert(s_vrf); + + props.table = nm_setting_vrf_get_table(s_vrf); + + r = nm_platform_link_vrf_add(nm_device_get_platform(device), iface, &props, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create VRF interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(device); + NMSettingVrf * s_vrf; + + if (!NM_DEVICE_CLASS(nm_device_vrf_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_device_is_real(device)) { + s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF); + + if (priv->props.table != nm_setting_vrf_get_table(s_vrf)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vrf table mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingVrf *s_vrf; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_VRF_SETTING_NAME, + existing_connections, + NULL, + _("VRF connection"), + NULL, + NULL, + TRUE); + + s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF); + if (!s_vrf) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'vrf' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(device); + NMSettingVrf * s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF); + + if (!s_vrf) { + s_vrf = (NMSettingVrf *) nm_setting_vrf_new(); + nm_connection_add_setting(connection, (NMSetting *) s_vrf); + } + + if (priv->props.table != nm_setting_vrf_get_table(s_vrf)) + g_object_set(G_OBJECT(s_vrf), NM_SETTING_VRF_TABLE, priv->props.table, NULL); +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceVrf *self = NM_DEVICE_VRF(device); + gboolean success = TRUE; + const char * slave_iface = nm_device_get_ip_iface(slave); + + nm_device_master_check_slave_physical_port(device, slave, LOGD_DEVICE); + + if (configure) { + nm_device_take_down(slave, TRUE); + success = nm_platform_link_enslave(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + nm_device_get_ip_ifindex(slave)); + nm_device_bring_up(slave, TRUE, NULL); + + if (!success) + return FALSE; + + _LOGI(LOGD_DEVICE, "enslaved VRF slave %s", slave_iface); + } else + _LOGI(LOGD_BOND, "VRF slave %s was enslaved", slave_iface); + + return TRUE; +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceVrf *self = NM_DEVICE_VRF(device); + gboolean success; + int ifindex_slave; + int ifindex; + + if (configure) { + ifindex = nm_device_get_ifindex(device); + if (ifindex <= 0 || !nm_platform_link_get(nm_device_get_platform(device), ifindex)) + configure = FALSE; + } + + ifindex_slave = nm_device_get_ip_ifindex(slave); + + if (ifindex_slave <= 0) + _LOGD(LOGD_DEVICE, "VRF slave %s is already released", nm_device_get_ip_iface(slave)); + + if (configure) { + if (ifindex_slave > 0) { + success = nm_platform_link_release(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + ifindex_slave); + + if (success) { + _LOGI(LOGD_DEVICE, "released VRF slave %s", nm_device_get_ip_iface(slave)); + } else { + _LOGW(LOGD_DEVICE, "failed to release VRF slave %s", nm_device_get_ip_iface(slave)); + } + } + } else { + if (ifindex_slave > 0) { + _LOGI(LOGD_DEVICE, "VRF slave %s was released", nm_device_get_ip_iface(slave)); + } + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_TABLE: + g_value_set_uint(value, priv->props.table); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_vrf_init(NMDeviceVrf *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_vrf = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_VRF, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Table", "u", NM_DEVICE_VRF_TABLE), ), ), +}; + +static void +nm_device_vrf_class_init(NMDeviceVrfClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_vrf); + + device_class->connection_type_supported = NM_SETTING_VRF_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_VRF_SETTING_NAME; + device_class->is_master = TRUE; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_VRF); + + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + device_class->link_changed = link_changed; + device_class->unrealize_notify = unrealize_notify; + device_class->create_and_realize = create_and_realize; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->update_connection = update_connection; + + obj_properties[PROP_TABLE] = g_param_spec_uint(NM_DEVICE_VRF_TABLE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_VRF_DEVICE_FACTORY (nm_vrf_device_factory_get_type()) +#define NM_VRF_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VRF_DEVICE_FACTORY, NMVrfDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_VRF, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Vrf", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_VRF, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_VRF, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + VRF, + Vrf, + vrf, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VRF) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VRF_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-vrf.h b/src/core/devices/nm-device-vrf.h new file mode 100644 index 0000000..5169041 --- /dev/null +++ b/src/core/devices/nm-device-vrf.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __NETWORKMANAGER_DEVICE_VRF_H__ +#define __NETWORKMANAGER_DEVICE_VRF_H__ + +#include "nm-device-generic.h" + +#define NM_TYPE_DEVICE_VRF (nm_device_vrf_get_type()) +#define NM_DEVICE_VRF(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_VRF, NMDeviceVrf)) +#define NM_DEVICE_VRF_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_VRF, NMDeviceVrfClass)) +#define NM_IS_DEVICE_VRF(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_VRF)) +#define NM_IS_DEVICE_VRF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_VRF)) +#define NM_DEVICE_VRF_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_VRF, NMDeviceVrfClass)) + +#define NM_DEVICE_VRF_TABLE "table" + +typedef struct _NMDeviceVrf NMDeviceVrf; +typedef struct _NMDeviceVrfClass NMDeviceVrfClass; + +GType nm_device_vrf_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_VRF_H__ */ diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c new file mode 100644 index 0000000..763aee9 --- /dev/null +++ b/src/core/devices/nm-device-vxlan.c @@ -0,0 +1,813 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-vxlan.h" + +#include "nm-device-private.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "nm-device-factory.h" +#include "nm-setting-vxlan.h" +#include "nm-setting-wired.h" +#include "settings/nm-settings.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceVxlan +#include "nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceVxlan, + PROP_ID, + PROP_LOCAL, + PROP_GROUP, + PROP_TOS, + PROP_TTL, + PROP_LEARNING, + PROP_AGEING, + PROP_LIMIT, + PROP_SRC_PORT_MIN, + PROP_SRC_PORT_MAX, + PROP_DST_PORT, + PROP_PROXY, + PROP_RSC, + PROP_L2MISS, + PROP_L3MISS, ); + +typedef struct { + NMPlatformLnkVxlan props; +} NMDeviceVxlanPrivate; + +struct _NMDeviceVxlan { + NMDevice parent; + NMDeviceVxlanPrivate _priv; +}; + +struct _NMDeviceVxlanClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceVxlan, nm_device_vxlan, NM_TYPE_DEVICE) + +#define NM_DEVICE_VXLAN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceVxlan, NM_IS_DEVICE_VXLAN, NMDevice) + +/*****************************************************************************/ + +static void +update_properties(NMDevice *device) +{ + NMDeviceVxlan * self = NM_DEVICE_VXLAN(device); + NMDeviceVxlanPrivate * priv = NM_DEVICE_VXLAN_GET_PRIVATE(self); + GObject * object = G_OBJECT(device); + const NMPlatformLnkVxlan *props; + + props = nm_platform_link_get_lnk_vxlan(nm_device_get_platform(device), + nm_device_get_ifindex(device), + NULL); + if (!props) { + _LOGW(LOGD_PLATFORM, "could not get vxlan properties"); + return; + } + + g_object_freeze_notify(object); + + if (priv->props.parent_ifindex != props->parent_ifindex) + nm_device_parent_set_ifindex(device, props->parent_ifindex); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->props.field != props->field) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + +#define CHECK_PROPERTY_CHANGED_IN6ADDR(field, prop) \ + G_STMT_START \ + { \ + if (memcmp(&priv->props.field, &props->field, sizeof(props->field)) != 0) { \ + priv->props.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED(id, PROP_ID); + CHECK_PROPERTY_CHANGED(local, PROP_LOCAL); + CHECK_PROPERTY_CHANGED_IN6ADDR(local6, PROP_LOCAL); + CHECK_PROPERTY_CHANGED(group, PROP_GROUP); + CHECK_PROPERTY_CHANGED_IN6ADDR(group6, PROP_GROUP); + CHECK_PROPERTY_CHANGED(tos, PROP_TOS); + CHECK_PROPERTY_CHANGED(ttl, PROP_TTL); + CHECK_PROPERTY_CHANGED(learning, PROP_LEARNING); + CHECK_PROPERTY_CHANGED(ageing, PROP_AGEING); + CHECK_PROPERTY_CHANGED(limit, PROP_LIMIT); + CHECK_PROPERTY_CHANGED(src_port_min, PROP_SRC_PORT_MIN); + CHECK_PROPERTY_CHANGED(src_port_max, PROP_SRC_PORT_MAX); + CHECK_PROPERTY_CHANGED(dst_port, PROP_DST_PORT); + CHECK_PROPERTY_CHANGED(proxy, PROP_PROXY); + CHECK_PROPERTY_CHANGED(rsc, PROP_RSC); + CHECK_PROPERTY_CHANGED(l2miss, PROP_L2MISS); + CHECK_PROPERTY_CHANGED(l3miss, PROP_L3MISS); + + g_object_thaw_notify(object); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_vxlan_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static void +unrealize_notify(NMDevice *device) +{ + NMDeviceVxlan * self = NM_DEVICE_VXLAN(device); + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(self); + guint i; + + NM_DEVICE_CLASS(nm_device_vxlan_parent_class)->unrealize_notify(device); + + memset(&priv->props, 0, sizeof(NMPlatformLnkVxlan)); + + for (i = 1; i < _PROPERTY_ENUMS_LAST; i++) + g_object_notify_by_pspec(G_OBJECT(self), obj_properties[i]); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char * iface = nm_device_get_iface(device); + NMPlatformLnkVxlan props = {}; + NMSettingVxlan * s_vxlan; + const char * str; + int r; + + s_vxlan = nm_connection_get_setting_vxlan(connection); + g_return_val_if_fail(s_vxlan, FALSE); + + if (parent) + props.parent_ifindex = nm_device_get_ifindex(parent); + + props.id = nm_setting_vxlan_get_id(s_vxlan); + + str = nm_setting_vxlan_get_local(s_vxlan); + if (str) { + if (!nm_utils_parse_inaddr_bin(AF_INET, str, NULL, &props.local) + && !nm_utils_parse_inaddr_bin(AF_INET6, str, NULL, &props.local6)) + return FALSE; + } + + str = nm_setting_vxlan_get_remote(s_vxlan); + if (str) { + if (!nm_utils_parse_inaddr_bin(AF_INET, str, NULL, &props.group) + && !nm_utils_parse_inaddr_bin(AF_INET6, str, NULL, &props.group6)) + return FALSE; + } + + props.tos = nm_setting_vxlan_get_tos(s_vxlan); + props.ttl = nm_setting_vxlan_get_ttl(s_vxlan); + props.learning = nm_setting_vxlan_get_learning(s_vxlan); + props.ageing = nm_setting_vxlan_get_ageing(s_vxlan); + props.limit = nm_setting_vxlan_get_limit(s_vxlan); + props.src_port_min = nm_setting_vxlan_get_source_port_min(s_vxlan); + props.src_port_max = nm_setting_vxlan_get_source_port_max(s_vxlan); + props.dst_port = nm_setting_vxlan_get_destination_port(s_vxlan); + props.proxy = nm_setting_vxlan_get_proxy(s_vxlan); + props.rsc = nm_setting_vxlan_get_rsc(s_vxlan); + props.l2miss = nm_setting_vxlan_get_l2_miss(s_vxlan); + props.l3miss = nm_setting_vxlan_get_l3_miss(s_vxlan); + + r = nm_platform_link_vxlan_add(nm_device_get_platform(device), iface, &props, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create VXLAN interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +static gboolean +address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6) +{ + NMIPAddr candidate_addr; + int addr_family; + + if (!candidate) + return addr4 == 0u && IN6_IS_ADDR_UNSPECIFIED(addr6); + + if (!nm_utils_parse_inaddr_bin(AF_UNSPEC, candidate, &addr_family, &candidate_addr)) + return FALSE; + + if (!nm_ip_addr_equal(addr_family, + &candidate_addr, + NM_IS_IPv4(addr_family) ? (gpointer) &addr4 : addr6)) + return FALSE; + + if (NM_IS_IPv4(addr_family)) + return IN6_IS_ADDR_UNSPECIFIED(addr6); + else + return addr4 == 0u; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device); + NMSettingVxlan * s_vxlan; + const char * parent; + + if (!NM_DEVICE_CLASS(nm_device_vxlan_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (nm_device_is_real(device)) { + s_vxlan = nm_connection_get_setting_vxlan(connection); + + parent = nm_setting_vxlan_get_parent(s_vxlan); + if (parent && !nm_device_match_parent(device, parent)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan parent mismatches"); + return FALSE; + } + + if (priv->props.id != nm_setting_vxlan_get_id(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan id mismatches"); + return FALSE; + } + + if (!address_matches(nm_setting_vxlan_get_local(s_vxlan), + priv->props.local, + &priv->props.local6)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan local address mismatches"); + return FALSE; + } + + if (!address_matches(nm_setting_vxlan_get_remote(s_vxlan), + priv->props.group, + &priv->props.group6)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan remote address mismatches"); + return FALSE; + } + + if (priv->props.src_port_min != nm_setting_vxlan_get_source_port_min(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan source port min mismatches"); + return FALSE; + } + + if (priv->props.src_port_max != nm_setting_vxlan_get_source_port_max(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan source port max mismatches"); + return FALSE; + } + + if (priv->props.dst_port != nm_setting_vxlan_get_destination_port(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan destination port mismatches"); + return FALSE; + } + + if (priv->props.tos != nm_setting_vxlan_get_tos(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan TOS mismatches"); + return FALSE; + } + + if (priv->props.ttl != nm_setting_vxlan_get_ttl(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan TTL mismatches"); + return FALSE; + } + + if (priv->props.learning != nm_setting_vxlan_get_learning(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan learning mismatches"); + return FALSE; + } + + if (priv->props.ageing != nm_setting_vxlan_get_ageing(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan ageing mismatches"); + return FALSE; + } + + if (priv->props.proxy != nm_setting_vxlan_get_proxy(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan proxy mismatches"); + return FALSE; + } + + if (priv->props.rsc != nm_setting_vxlan_get_rsc(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan rsc mismatches"); + return FALSE; + } + + if (priv->props.l2miss != nm_setting_vxlan_get_l2_miss(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan l2miss mismatches"); + return FALSE; + } + + if (priv->props.l3miss != nm_setting_vxlan_get_l3_miss(s_vxlan)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "vxlan l3miss mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingVxlan *s_vxlan; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_VXLAN_SETTING_NAME, + existing_connections, + NULL, + _("VXLAN connection"), + NULL, + NULL, + TRUE); + + s_vxlan = nm_connection_get_setting_vxlan(connection); + if (!s_vxlan) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'vxlan' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device); + NMSettingVxlan * s_vxlan = nm_connection_get_setting_vxlan(connection); + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (!s_vxlan) { + s_vxlan = (NMSettingVxlan *) nm_setting_vxlan_new(); + nm_connection_add_setting(connection, (NMSetting *) s_vxlan); + } + + if (priv->props.id != nm_setting_vxlan_get_id(s_vxlan)) + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_ID, priv->props.id, NULL); + + g_object_set(s_vxlan, + NM_SETTING_VXLAN_PARENT, + nm_device_parent_find_for_connection(device, nm_setting_vxlan_get_parent(s_vxlan)), + NULL); + + if (!address_matches(nm_setting_vxlan_get_remote(s_vxlan), + priv->props.group, + &priv->props.group6)) { + if (priv->props.group) { + g_object_set(s_vxlan, + NM_SETTING_VXLAN_REMOTE, + _nm_utils_inet4_ntop(priv->props.group, sbuf), + NULL); + } else { + g_object_set(s_vxlan, + NM_SETTING_VXLAN_REMOTE, + _nm_utils_inet6_ntop(&priv->props.group6, sbuf), + NULL); + } + } + + if (!address_matches(nm_setting_vxlan_get_local(s_vxlan), + priv->props.local, + &priv->props.local6)) { + if (priv->props.local) { + g_object_set(s_vxlan, + NM_SETTING_VXLAN_LOCAL, + _nm_utils_inet4_ntop(priv->props.local, sbuf), + NULL); + } else if (memcmp(&priv->props.local6, &in6addr_any, sizeof(in6addr_any))) { + g_object_set(s_vxlan, + NM_SETTING_VXLAN_LOCAL, + _nm_utils_inet6_ntop(&priv->props.local6, sbuf), + NULL); + } + } + + if (priv->props.src_port_min != nm_setting_vxlan_get_source_port_min(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), + NM_SETTING_VXLAN_SOURCE_PORT_MIN, + priv->props.src_port_min, + NULL); + } + + if (priv->props.src_port_max != nm_setting_vxlan_get_source_port_max(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), + NM_SETTING_VXLAN_SOURCE_PORT_MAX, + priv->props.src_port_max, + NULL); + } + + if (priv->props.dst_port != nm_setting_vxlan_get_destination_port(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), + NM_SETTING_VXLAN_DESTINATION_PORT, + priv->props.dst_port, + NULL); + } + + if (priv->props.tos != nm_setting_vxlan_get_tos(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_TOS, priv->props.tos, NULL); + } + + if (priv->props.ttl != nm_setting_vxlan_get_ttl(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_TTL, priv->props.ttl, NULL); + } + + if (priv->props.learning != nm_setting_vxlan_get_learning(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_LEARNING, priv->props.learning, NULL); + } + + if (priv->props.ageing != nm_setting_vxlan_get_ageing(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_AGEING, priv->props.ageing, NULL); + } + + if (priv->props.proxy != nm_setting_vxlan_get_proxy(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_PROXY, priv->props.proxy, NULL); + } + + if (priv->props.rsc != nm_setting_vxlan_get_rsc(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_RSC, priv->props.rsc, NULL); + } + + if (priv->props.l2miss != nm_setting_vxlan_get_l2_miss(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_L2_MISS, priv->props.l2miss, NULL); + } + + if (priv->props.l3miss != nm_setting_vxlan_get_l3_miss(s_vxlan)) { + g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_L3_MISS, priv->props.l3miss, NULL); + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint(value, priv->props.id); + break; + case PROP_GROUP: + if (priv->props.group) + g_value_take_string(value, nm_utils_inet4_ntop_dup(priv->props.group)); + else if (!IN6_IS_ADDR_UNSPECIFIED(&priv->props.group6)) + g_value_take_string(value, nm_utils_inet6_ntop_dup(&priv->props.group6)); + break; + case PROP_LOCAL: + if (priv->props.local) + g_value_take_string(value, nm_utils_inet4_ntop_dup(priv->props.local)); + else if (!IN6_IS_ADDR_UNSPECIFIED(&priv->props.local6)) + g_value_take_string(value, nm_utils_inet6_ntop_dup(&priv->props.local6)); + break; + case PROP_TOS: + g_value_set_uchar(value, priv->props.tos); + break; + case PROP_TTL: + g_value_set_uchar(value, priv->props.ttl); + break; + case PROP_LEARNING: + g_value_set_boolean(value, priv->props.learning); + break; + case PROP_AGEING: + g_value_set_uint(value, priv->props.ageing); + break; + case PROP_LIMIT: + g_value_set_uint(value, priv->props.limit); + break; + case PROP_DST_PORT: + g_value_set_uint(value, priv->props.dst_port); + break; + case PROP_SRC_PORT_MIN: + g_value_set_uint(value, priv->props.src_port_min); + break; + case PROP_SRC_PORT_MAX: + g_value_set_uint(value, priv->props.src_port_max); + break; + case PROP_PROXY: + g_value_set_boolean(value, priv->props.proxy); + break; + case PROP_RSC: + g_value_set_boolean(value, priv->props.rsc); + break; + case PROP_L2MISS: + g_value_set_boolean(value, priv->props.l2miss); + break; + case PROP_L3MISS: + g_value_set_boolean(value, priv->props.l3miss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_vxlan_init(NMDeviceVxlan *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_vxlan = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_VXLAN, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Id", "u", NM_DEVICE_VXLAN_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Group", "s", NM_DEVICE_VXLAN_GROUP), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Local", "s", NM_DEVICE_VXLAN_LOCAL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tos", "y", NM_DEVICE_VXLAN_TOS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ttl", "y", NM_DEVICE_VXLAN_TTL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Learning", + "b", + NM_DEVICE_VXLAN_LEARNING), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ageing", "u", NM_DEVICE_VXLAN_AGEING), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Limit", "u", NM_DEVICE_VXLAN_LIMIT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DstPort", + "q", + NM_DEVICE_VXLAN_DST_PORT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("SrcPortMin", + "q", + NM_DEVICE_VXLAN_SRC_PORT_MIN), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("SrcPortMax", + "q", + NM_DEVICE_VXLAN_SRC_PORT_MAX), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Proxy", "b", NM_DEVICE_VXLAN_PROXY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Rsc", "b", NM_DEVICE_VXLAN_RSC), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("L2miss", "b", NM_DEVICE_VXLAN_L2MISS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("L3miss", + "b", + NM_DEVICE_VXLAN_L3MISS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_vxlan_class_init(NMDeviceVxlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_vxlan); + + device_class->connection_type_supported = NM_SETTING_VXLAN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_VXLAN_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_VXLAN); + + device_class->link_changed = link_changed; + device_class->unrealize_notify = unrealize_notify; + device_class->create_and_realize = create_and_realize; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->update_connection = update_connection; + device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + + obj_properties[PROP_ID] = g_param_spec_uint(NM_DEVICE_VXLAN_ID, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LOCAL] = g_param_spec_string(NM_DEVICE_VXLAN_LOCAL, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_GROUP] = g_param_spec_string(NM_DEVICE_VXLAN_GROUP, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_VXLAN_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TTL] = g_param_spec_uchar(NM_DEVICE_VXLAN_TTL, + "", + "", + 0, + 255, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LEARNING] = g_param_spec_boolean(NM_DEVICE_VXLAN_LEARNING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_AGEING] = g_param_spec_uint(NM_DEVICE_VXLAN_AGEING, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LIMIT] = g_param_spec_uint(NM_DEVICE_VXLAN_LIMIT, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SRC_PORT_MIN] = + g_param_spec_uint(NM_DEVICE_VXLAN_SRC_PORT_MIN, + "", + "", + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SRC_PORT_MAX] = + g_param_spec_uint(NM_DEVICE_VXLAN_SRC_PORT_MAX, + "", + "", + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DST_PORT] = g_param_spec_uint(NM_DEVICE_VXLAN_DST_PORT, + "", + "", + 0, + 65535, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PROXY] = g_param_spec_boolean(NM_DEVICE_VXLAN_PROXY, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_RSC] = g_param_spec_boolean(NM_DEVICE_VXLAN_RSC, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_L2MISS] = g_param_spec_boolean(NM_DEVICE_VXLAN_L2MISS, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_L3MISS] = g_param_spec_boolean(NM_DEVICE_VXLAN_L3MISS, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +#define NM_TYPE_VXLAN_DEVICE_FACTORY (nm_vxlan_device_factory_get_type()) +#define NM_VXLAN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VXLAN_DEVICE_FACTORY, NMVxlanDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_VXLAN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "Vxlan", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_VXLAN, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_VXLAN, + NULL); +} + +static const char * +get_connection_parent(NMDeviceFactory *factory, NMConnection *connection) +{ + NMSettingVxlan *s_vxlan; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VXLAN_SETTING_NAME), NULL); + + s_vxlan = nm_connection_get_setting_vxlan(connection); + g_assert(s_vxlan); + + return nm_setting_vxlan_get_parent(s_vxlan); +} + +static char * +get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface) +{ + const char * ifname; + NMSettingVxlan *s_vxlan; + + g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VXLAN_SETTING_NAME), NULL); + + s_vxlan = nm_connection_get_setting_vxlan(connection); + g_assert(s_vxlan); + + if (nm_setting_vxlan_get_parent(s_vxlan) && !parent_iface) + return NULL; + + ifname = nm_connection_get_interface_name(connection); + return g_strdup(ifname); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + VXLAN, + Vxlan, + vxlan, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VXLAN) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VXLAN_SETTING_NAME), + factory_class->create_device = create_device; + factory_class->get_connection_parent = get_connection_parent; + factory_class->get_connection_iface = get_connection_iface;); diff --git a/src/core/devices/nm-device-vxlan.h b/src/core/devices/nm-device-vxlan.h new file mode 100644 index 0000000..4c4165e --- /dev/null +++ b/src/core/devices/nm-device-vxlan.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013, 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_VXLAN_H__ +#define __NETWORKMANAGER_DEVICE_VXLAN_H__ + +#include "nm-device-generic.h" + +#define NM_TYPE_DEVICE_VXLAN (nm_device_vxlan_get_type()) +#define NM_DEVICE_VXLAN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlan)) +#define NM_DEVICE_VXLAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanClass)) +#define NM_IS_DEVICE_VXLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_VXLAN)) +#define NM_IS_DEVICE_VXLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_VXLAN)) +#define NM_DEVICE_VXLAN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanClass)) + +#define NM_DEVICE_VXLAN_ID "id" +#define NM_DEVICE_VXLAN_GROUP "group" +#define NM_DEVICE_VXLAN_LOCAL "local" +#define NM_DEVICE_VXLAN_TOS "tos" +#define NM_DEVICE_VXLAN_TTL "ttl" +#define NM_DEVICE_VXLAN_LEARNING "learning" +#define NM_DEVICE_VXLAN_AGEING "ageing" +#define NM_DEVICE_VXLAN_LIMIT "limit" +#define NM_DEVICE_VXLAN_DST_PORT "dst-port" +#define NM_DEVICE_VXLAN_SRC_PORT_MIN "src-port-min" +#define NM_DEVICE_VXLAN_SRC_PORT_MAX "src-port-max" +#define NM_DEVICE_VXLAN_PROXY "proxy" +#define NM_DEVICE_VXLAN_RSC "rsc" +#define NM_DEVICE_VXLAN_L2MISS "l2miss" +#define NM_DEVICE_VXLAN_L3MISS "l3miss" + +typedef struct _NMDeviceVxlan NMDeviceVxlan; +typedef struct _NMDeviceVxlanClass NMDeviceVxlanClass; + +GType nm_device_vxlan_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_VXLAN_H__ */ diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c new file mode 100644 index 0000000..1b10996 --- /dev/null +++ b/src/core/devices/nm-device-wireguard.c @@ -0,0 +1,2093 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Javier Arteaga + */ + +#include "nm-default.h" + +#include "nm-device-wireguard.h" + +#include +#include + +#include "nm-setting-wireguard.h" +#include "nm-core-internal.h" +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-device-private.h" +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "platform/nmp-rules-manager.h" +#include "nm-device-factory.h" +#include "nm-active-connection.h" +#include "nm-act-request.h" +#include "dns/nm-dns-manager.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceWireGuard +#include "nm-device-logging.h" + +/*****************************************************************************/ + +/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is + * requested (good). Enter it and connect. Reactivate the profile, now there is no password + * prompt, as the secret is cached (good??). */ + +/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means, + * you might get a routing scenario where the peer (VPN server) is reachable via the VPN. + * How we handle adding routes to external gateway for other peers, has severe issues + * as well. We may use policy-routing like wg-quick does. See also discussions at + * https://www.wireguard.com/netns/#improving-the-classic-solutions */ + +/* TODO: honor the TTL of DNS to determine when to retry resolving endpoints. */ + +/* TODO: when we get multiple IP addresses when resolving a peer endpoint. We currently + * just take the first from GAI. We should only accept AAAA/IPv6 if we also have a suitable + * IPv6 address. The problem is, that we have to recheck that when IP addressing on other + * interfaces changes. This makes it almost too cumbersome to implement. */ + +/*****************************************************************************/ + +G_STATIC_ASSERT(NM_WIREGUARD_PUBLIC_KEY_LEN == NMP_WIREGUARD_PUBLIC_KEY_LEN); +G_STATIC_ASSERT(NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LEN); + +/*****************************************************************************/ + +#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NSEC_PER_MSEC) + +/* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */ +#define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64) + +/* a special @next_try_at_nsec timestamp that is + * - positive (indicating resolve-checks are enabled) + * - already in the past (we use the absolute timestamp of 1nsec for that). */ +#define NEXT_TRY_AT_NSEC_PAST ((gint64) 1) + +/* like %NEXT_TRY_AT_NSEC_ASAP, but used for indicating to retry ASAP for a @retry_in_msec value. + * That is a relative time duration, contrary to @next_try_at_nsec which is an absolute + * timestamp. */ +#define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64) + +#define RETRY_IN_MSEC_MAX ((gint64)(30 * 60 * 1000)) + +typedef enum { + LINK_CONFIG_MODE_FULL, + LINK_CONFIG_MODE_REAPPLY, + LINK_CONFIG_MODE_ASSUME, + LINK_CONFIG_MODE_ENDPOINTS, +} LinkConfigMode; + +typedef struct { + GCancellable *cancellable; + + NMSockAddrUnion sockaddr; + + /* the timestamp (in nm_utils_get_monotonic_timestamp_nsec() scale) when we want + * to retry resolving the endpoint (again). + * + * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible. + * + * A @sockaddr is either fixed or it has + * - @cancellable set to indicate an ongoing request + * - @next_try_at_nsec set to a positive value, indicating when + * we ought to retry. */ + gint64 next_try_at_nsec; + + guint resolv_fail_count; +} PeerEndpointResolveData; + +typedef struct { + NMWireGuardPeer *peer; + + NMDeviceWireGuard *self; + + CList lst_peers; + + PeerEndpointResolveData ep_resolv; + + /* dirty flag used during _peers_update_all(). */ + bool dirty_update_all : 1; +} PeerData; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceWireGuard, PROP_PUBLIC_KEY, PROP_LISTEN_PORT, PROP_FWMARK, ); + +typedef struct { + NMDnsManager *dns_manager; + + NMPlatformLnkWireGuard lnk_curr; + NMActRequestGetSecretsCallId *secrets_call_id; + + CList lst_peers_head; + GHashTable *peers; + + /* counts the numbers of peers that are currently resolving. */ + guint peers_resolving_cnt; + + gint64 resolve_next_try_at; + gint64 link_config_last_at; + + guint resolve_next_try_id; + guint link_config_delayed_id; + + guint32 auto_default_route_fwmark; + + guint32 auto_default_route_priority; + + bool auto_default_route_enabled_4 : 1; + bool auto_default_route_enabled_6 : 1; + bool auto_default_route_initialized : 1; + bool auto_default_route_refresh : 1; + bool auto_default_route_priority_initialized : 1; + +} NMDeviceWireGuardPrivate; + +struct _NMDeviceWireGuard { + NMDevice parent; + NMDeviceWireGuardPrivate _priv; +}; + +struct _NMDeviceWireGuardClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE) + +#define NM_DEVICE_WIREGUARD_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceWireGuard, NM_IS_DEVICE_WIREGUARD, NMDevice) + +/*****************************************************************************/ + +static void _peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data); + +static void _peers_resolve_retry_reschedule(NMDeviceWireGuard *self, gint64 new_next_try_at_nsec); + +static gboolean link_config_delayed_resolver_cb(gpointer user_data); + +static gboolean link_config_delayed_ratelimit_cb(gpointer user_data); + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(_link_config_mode_to_string, + LinkConfigMode, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), + NM_UTILS_LOOKUP_ITEM(LINK_CONFIG_MODE_FULL, "full"), + NM_UTILS_LOOKUP_ITEM(LINK_CONFIG_MODE_REAPPLY, "reapply"), + NM_UTILS_LOOKUP_ITEM(LINK_CONFIG_MODE_ASSUME, "assume"), + NM_UTILS_LOOKUP_ITEM(LINK_CONFIG_MODE_ENDPOINTS, "endpoints"), ); + +/*****************************************************************************/ + +static void +_auto_default_route_get_enabled(NMSettingWireGuard *s_wg, + NMConnection * connection, + gboolean * out_enabled_v4, + gboolean * out_enabled_v6) +{ + NMTernary enabled_v4; + NMTernary enabled_v6; + + enabled_v4 = nm_setting_wireguard_get_ip4_auto_default_route(s_wg); + enabled_v6 = nm_setting_wireguard_get_ip6_auto_default_route(s_wg); + + if (enabled_v4 == NM_TERNARY_DEFAULT) { + if (nm_setting_ip_config_get_never_default( + nm_connection_get_setting_ip_config(connection, AF_INET))) + enabled_v4 = FALSE; + } + if (enabled_v6 == NM_TERNARY_DEFAULT) { + if (nm_setting_ip_config_get_never_default( + nm_connection_get_setting_ip_config(connection, AF_INET6))) + enabled_v6 = FALSE; + } + + if (enabled_v4 == NM_TERNARY_DEFAULT || enabled_v6 == NM_TERNARY_DEFAULT) { + guint i, n_peers; + + n_peers = nm_setting_wireguard_get_peers_len(s_wg); + for (i = 0; i < n_peers; i++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer(s_wg, i); + guint n_aips; + guint j; + + n_aips = nm_wireguard_peer_get_allowed_ips_len(peer); + for (j = 0; j < n_aips; j++) { + const char *aip; + gboolean valid; + int prefix; + int addr_family; + + aip = nm_wireguard_peer_get_allowed_ip(peer, j, &valid); + if (!valid) + continue; + if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, aip, &addr_family, NULL, &prefix)) + continue; + if (prefix != 0) + continue; + + if (addr_family == AF_INET) { + if (enabled_v4 == NM_TERNARY_DEFAULT) { + enabled_v4 = TRUE; + if (enabled_v6 != NM_TERNARY_DEFAULT) + goto done; + } + } else { + if (enabled_v6 == NM_TERNARY_DEFAULT) { + enabled_v6 = TRUE; + if (enabled_v4 != NM_TERNARY_DEFAULT) + goto done; + } + } + } + } +done:; + } + + *out_enabled_v4 = (enabled_v4 == TRUE); + *out_enabled_v6 = (enabled_v6 == TRUE); +} + +#define AUTO_RANDOM_RANGE 500u + +static guint32 +_auto_default_route_get_auto_fwmark(const char *uuid) +{ + guint64 rnd_seed; + + /* we use the generated number as fwmark but also as routing table for + * the default-route. + * + * We pick a number + * + * - based on the connection's UUID (as stable seed). + * - larger than 51820u (arbitrarily) + * - one out of AUTO_RANDOM_RANGE + */ + + rnd_seed = c_siphash_hash(NM_HASH_SEED_16(0xb9, + 0x39, + 0x8e, + 0xed, + 0x15, + 0xb3, + 0xd1, + 0xc4, + 0x5f, + 0x45, + 0x00, + 0x4f, + 0xec, + 0xc2, + 0x2b, + 0x7e), + (const guint8 *) uuid, + uuid ? strlen(uuid) + 1u : 0u); + + return 51820u + (rnd_seed % AUTO_RANDOM_RANGE); +} + +#define PRIO_WIDTH 2u + +static guint32 +_auto_default_route_get_auto_priority(const char *uuid) +{ + const guint32 RANGE_TOP = 32766u - 1000u; + guint64 rnd_seed; + + /* we pick a priority for the routing rules as follows: + * + * - use the connection's UUID as stable seed for the "random" number. + * - have it smaller than RANGE_TOP (32766u - 1000u), where 32766u is the priority of the default + * rules + * - we add 2 rules (PRIO_WIDTH). Hence only pick even priorities. + * - pick one out of AUTO_RANDOM_RANGE. */ + + rnd_seed = c_siphash_hash(NM_HASH_SEED_16(0x99, + 0x22, + 0x4d, + 0x7c, + 0x37, + 0xda, + 0x8e, + 0x7b, + 0x2f, + 0x55, + 0x16, + 0x7b, + 0x75, + 0xda, + 0x42, + 0xdc), + (const guint8 *) uuid, + uuid ? strlen(uuid) + 1u : 0u); + + return RANGE_TOP - (((rnd_seed % (PRIO_WIDTH * AUTO_RANDOM_RANGE)) / PRIO_WIDTH) * PRIO_WIDTH); +} + +static void +_auto_default_route_init(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + NMConnection * connection; + gboolean enabled_v4 = FALSE; + gboolean enabled_v6 = FALSE; + gboolean refreshing_only; + guint32 new_fwmark = 0; + guint32 old_fwmark; + char sbuf1[100]; + + if (G_LIKELY(priv->auto_default_route_initialized && !priv->auto_default_route_refresh)) + return; + + refreshing_only = priv->auto_default_route_initialized && priv->auto_default_route_refresh; + + old_fwmark = priv->auto_default_route_fwmark; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + if (connection) { + NMSettingWireGuard *s_wg; + + s_wg = _nm_connection_get_setting(connection, NM_TYPE_SETTING_WIREGUARD); + + new_fwmark = nm_setting_wireguard_get_fwmark(s_wg); + + _auto_default_route_get_enabled(s_wg, connection, &enabled_v4, &enabled_v6); + } + + if ((enabled_v4 || enabled_v6) && new_fwmark == 0u) { + if (refreshing_only) + new_fwmark = old_fwmark; + else + new_fwmark = _auto_default_route_get_auto_fwmark(nm_connection_get_uuid(connection)); + } + + priv->auto_default_route_refresh = FALSE; + priv->auto_default_route_fwmark = new_fwmark; + priv->auto_default_route_enabled_4 = enabled_v4; + priv->auto_default_route_enabled_6 = enabled_v6; + priv->auto_default_route_initialized = TRUE; + + if (connection) { + _LOGT(LOGD_DEVICE, + "auto-default-route is %s for IPv4 and %s for IPv6%s", + priv->auto_default_route_enabled_4 ? "enabled" : "disabled", + priv->auto_default_route_enabled_6 ? "enabled" : "disabled", + priv->auto_default_route_enabled_4 || priv->auto_default_route_enabled_6 + ? nm_sprintf_buf(sbuf1, " (fwmark 0x%x)", priv->auto_default_route_fwmark) + : ""); + } +} + +static GPtrArray * +get_extra_rules(NMDevice *device) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(device); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *extra_rules = NULL; + guint32 priority = 0; + int is_ipv4; + NMConnection * connection; + + _auto_default_route_init(self); + + connection = nm_device_get_applied_connection(device); + if (!connection) + return NULL; + + for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) { + NMSettingIPConfig *s_ip; + int addr_family = is_ipv4 ? AF_INET : AF_INET6; + guint32 table_main; + guint32 fwmark; + + if (is_ipv4) { + if (!priv->auto_default_route_enabled_4) + continue; + } else { + if (!priv->auto_default_route_enabled_6) + continue; + } + + if (!extra_rules) { + if (priv->auto_default_route_priority_initialized) + priority = priv->auto_default_route_priority; + else { + priority = + _auto_default_route_get_auto_priority(nm_connection_get_uuid(connection)); + priv->auto_default_route_priority = priority; + priv->auto_default_route_priority_initialized = TRUE; + } + extra_rules = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + } + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + table_main = nm_setting_ip_config_get_route_table(s_ip); + if (table_main == 0) + table_main = RT_TABLE_MAIN; + + fwmark = priv->auto_default_route_fwmark; + + G_STATIC_ASSERT_EXPR(PRIO_WIDTH == 2); + + g_ptr_array_add(extra_rules, + nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, + &((const NMPlatformRoutingRule){ + .priority = priority, + .addr_family = addr_family, + .action = FR_ACT_TO_TBL, + .table = table_main, + .suppress_prefixlen_inverse = ~((guint32) 0u), + }))); + + g_ptr_array_add(extra_rules, + nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, + &((const NMPlatformRoutingRule){ + .priority = priority + 1u, + .addr_family = addr_family, + .action = FR_ACT_TO_TBL, + .table = fwmark, + .flags = FIB_RULE_INVERT, + .fwmark = fwmark, + .fwmask = 0xFFFFFFFFu, + }))); + } + + return g_steal_pointer(&extra_rules); +} + +static guint32 +coerce_route_table(NMDevice *device, int addr_family, guint32 route_table, gboolean is_user_config) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(device); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gboolean auto_default_route_enabled; + + if (route_table != 0u) + return route_table; + + _auto_default_route_init(self); + + auto_default_route_enabled = (addr_family == AF_INET) ? priv->auto_default_route_enabled_4 + : priv->auto_default_route_enabled_6; + + if (auto_default_route_enabled) { + /* we need to enable full-sync mode of all routing tables. */ + _LOGT(LOGD_DEVICE, + "coerce ipv%c.route-table setting to \"main\" (table 254) as we enable " + "auto-default-route handling", + nm_utils_addr_family_to_char(addr_family)); + return RT_TABLE_MAIN; + } + + return 0; +} + +/*****************************************************************************/ + +static gboolean +_peer_data_equal(gconstpointer ptr_a, gconstpointer ptr_b) +{ + const PeerData *peer_data_a = ptr_a; + const PeerData *peer_data_b = ptr_b; + + return nm_streq(nm_wireguard_peer_get_public_key(peer_data_a->peer), + nm_wireguard_peer_get_public_key(peer_data_b->peer)); +} + +static guint +_peer_data_hash(gconstpointer ptr) +{ + const PeerData *peer_data = ptr; + + return nm_hash_str(nm_wireguard_peer_get_public_key(peer_data->peer)); +} + +static PeerData * +_peers_find(NMDeviceWireGuardPrivate *priv, NMWireGuardPeer *peer) +{ + nm_assert(peer); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(PeerData, peer) == 0); + + return g_hash_table_lookup(priv->peers, &peer); +} + +static guint +_peers_resolving_cnt(NMDeviceWireGuardPrivate *priv) +{ + nm_assert(priv); +#if NM_MORE_ASSERTS > 3 + { + PeerData *peer_data; + guint cnt = 0; + + c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) { + if (peer_data->ep_resolv.cancellable) + cnt++; + } + nm_assert(cnt == priv->peers_resolving_cnt); + } +#endif + + return priv->peers_resolving_cnt; +} + +static void +_peers_resolving_cnt_decrement(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + nm_assert(priv); + nm_assert(priv->peers_resolving_cnt > 0); + + priv->peers_resolving_cnt--; + + nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt); + + if (priv->peers_resolving_cnt == 0) { + if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) { + _LOGT(LOGD_DEVICE, + "activation delayed to resolve DNS names of peers: completed, proceed now"); + nm_device_activate_schedule_stage2_device_config(NM_DEVICE(self), FALSE); + } + } +} + +static void +_peers_remove(NMDeviceWireGuard *self, PeerData *peer_data) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + nm_assert(peer_data); + nm_assert(g_hash_table_lookup(priv->peers, peer_data) == peer_data); + + if (!g_hash_table_remove(priv->peers, peer_data)) + nm_assert_not_reached(); + + c_list_unlink_stale(&peer_data->lst_peers); + nm_wireguard_peer_unref(peer_data->peer); + if (nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable)) + _peers_resolving_cnt_decrement(self); + g_slice_free(PeerData, peer_data); + + if (c_list_is_empty(&priv->lst_peers_head)) { + nm_clear_g_source(&priv->resolve_next_try_id); + nm_clear_g_source(&priv->link_config_delayed_id); + } + + nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt); +} + +static PeerData * +_peers_add(NMDeviceWireGuard *self, NMWireGuardPeer *peer) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + PeerData * peer_data; + + nm_assert(peer); + nm_assert(nm_wireguard_peer_is_sealed(peer)); + nm_assert(!_peers_find(priv, peer)); + + peer_data = g_slice_new(PeerData); + *peer_data = (PeerData){ + .self = self, + .peer = nm_wireguard_peer_ref(peer), + .ep_resolv = + { + .sockaddr = NM_SOCK_ADDR_UNION_INIT_UNSPEC, + }, + }; + + c_list_link_tail(&priv->lst_peers_head, &peer_data->lst_peers); + if (!nm_g_hash_table_add(priv->peers, peer_data)) + nm_assert_not_reached(); + return peer_data; +} + +static gboolean +_peers_resolve_retry_timeout(gpointer user_data) +{ + NMDeviceWireGuard * self = user_data; + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + PeerData * peer_data; + gint64 now; + gint64 next; + + priv->resolve_next_try_id = 0; + + _LOGT(LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints..."); + + now = nm_utils_get_monotonic_timestamp_nsec(); + next = G_MAXINT64; + c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) { + if (peer_data->ep_resolv.next_try_at_nsec <= 0) + continue; + + if (peer_data->ep_resolv.cancellable) { + /* we are currently resolving a name. We don't need the global + * watchdog to guard this peer. No need to adjust @next for + * this one, when the currently ongoing resolving completes, we + * may reschedule. Skip. */ + continue; + } + + if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP + || now >= peer_data->ep_resolv.next_try_at_nsec) { + _peers_resolve_start(self, peer_data); + /* same here. Now we are resolving. We don't need the global + * watchdog. Skip w.r.t. finding @next. */ + continue; + } + + if (next > peer_data->ep_resolv.next_try_at_nsec) + next = peer_data->ep_resolv.next_try_at_nsec; + } + if (next < G_MAXINT64) + _peers_resolve_retry_reschedule(self, next); + + return G_SOURCE_REMOVE; +} + +static void +_peers_resolve_retry_reschedule(NMDeviceWireGuard *self, gint64 new_next_try_at_nsec) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + guint32 interval_ms; + gint64 now; + + nm_assert(new_next_try_at_nsec > 0); + nm_assert(new_next_try_at_nsec != NEXT_TRY_AT_NSEC_ASAP); + + if (priv->resolve_next_try_id && priv->resolve_next_try_at <= new_next_try_at_nsec) { + /* we already have an earlier timeout scheduled (possibly for + * another peer that expires sooner). Don't reschedule now. + * Even if the scheduled timeout expires too early, we will + * compute the right next-timeout and reschedule then. */ + return; + } + + now = nm_utils_get_monotonic_timestamp_nsec(); + + /* schedule at most one day ahead. No problem if we expire earlier + * than expected. Also, rate-limit to 500 msec. */ + interval_ms = NM_CLAMP((new_next_try_at_nsec - now) / NM_UTILS_NSEC_PER_MSEC, + (gint64) 500, + (gint64)(24 * 60 * 60 * 1000)); + + _LOGT(LOGD_DEVICE, + "wireguard-peers: schedule rechecking peer endpoints in %u msec", + interval_ms); + + nm_clear_g_source(&priv->resolve_next_try_id); + priv->resolve_next_try_at = new_next_try_at_nsec; + priv->resolve_next_try_id = g_timeout_add(interval_ms, _peers_resolve_retry_timeout, self); +} + +static void +_peers_resolve_retry_reschedule_for_peer(NMDeviceWireGuard *self, + PeerData * peer_data, + gint64 retry_in_msec) +{ + nm_assert(retry_in_msec >= 0); + + if (retry_in_msec == RETRY_IN_MSEC_ASAP) { + _peers_resolve_start(self, peer_data); + return; + } + + peer_data->ep_resolv.next_try_at_nsec = + nm_utils_get_monotonic_timestamp_nsec() + (retry_in_msec * NM_UTILS_NSEC_PER_MSEC); + _peers_resolve_retry_reschedule(self, peer_data->ep_resolv.next_try_at_nsec); +} + +static gint64 +_peers_retry_in_msec(PeerData *peer_data, gboolean after_failure) +{ + if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP) { + peer_data->ep_resolv.resolv_fail_count = 0; + return RETRY_IN_MSEC_ASAP; + } + + if (after_failure) { + if (peer_data->ep_resolv.resolv_fail_count < G_MAXUINT) + peer_data->ep_resolv.resolv_fail_count++; + } else + peer_data->ep_resolv.resolv_fail_count = 0; + + if (!after_failure) + return RETRY_IN_MSEC_MAX; + + if (peer_data->ep_resolv.resolv_fail_count > 20) + return RETRY_IN_MSEC_MAX; + + /* double the retry-time, starting with one second. */ + return NM_MIN(RETRY_IN_MSEC_MAX, (1u << peer_data->ep_resolv.resolv_fail_count) * 500); +} + +static void +_peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMDeviceWireGuard * self; + NMDeviceWireGuardPrivate *priv; + PeerData * peer_data; + gs_free_error GError *resolv_error = NULL; + GList * list; + gboolean changed = FALSE; + NMSockAddrUnion sockaddr; + gint64 retry_in_msec; + char s_sockaddr[100]; + char s_retry[100]; + + list = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object), res, &resolv_error); + + if (nm_utils_error_is_cancelled(resolv_error)) + return; + + peer_data = user_data; + self = peer_data->self; + priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + if (nm_clear_g_object(&peer_data->ep_resolv.cancellable)) + _peers_resolving_cnt_decrement(self); + + nm_assert((!resolv_error) != (!list)); + nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt); + +#define _retry_in_msec_to_string(retry_in_msec, s_retry) \ + ({ \ + gint64 _retry_in_msec = (retry_in_msec); \ + \ + _retry_in_msec == RETRY_IN_MSEC_ASAP \ + ? "right away" \ + : nm_sprintf_buf(s_retry, "in %" G_GINT64_FORMAT " msec", _retry_in_msec); \ + }) + + if (resolv_error + && !g_error_matches(resolv_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) { + retry_in_msec = _peers_retry_in_msec(peer_data, TRUE); + + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: failure to resolve endpoint \"%s\": %s (retry %s)", + nm_wireguard_peer_get_public_key(peer_data->peer), + nm_wireguard_peer_get_endpoint(peer_data->peer), + resolv_error->message, + _retry_in_msec_to_string(retry_in_msec, s_retry)); + + _peers_resolve_retry_reschedule_for_peer(self, peer_data, retry_in_msec); + return; + } + + sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + + if (!resolv_error) { + GList *iter; + + for (iter = list; iter; iter = iter->next) { + GInetAddress *a = iter->data; + GSocketFamily f = g_inet_address_get_family(a); + + if (f == G_SOCKET_FAMILY_IPV4) { + nm_assert(g_inet_address_get_native_size(a) == sizeof(struct in_addr)); + sockaddr.in = (struct sockaddr_in){ + .sin_family = AF_INET, + .sin_port = htons(nm_sock_addr_endpoint_get_port( + _nm_wireguard_peer_get_endpoint(peer_data->peer))), + }; + memcpy(&sockaddr.in.sin_addr, g_inet_address_to_bytes(a), sizeof(struct in_addr)); + break; + } + if (f == G_SOCKET_FAMILY_IPV6) { + nm_assert(g_inet_address_get_native_size(a) == sizeof(struct in6_addr)); + sockaddr.in6 = (struct sockaddr_in6){ + .sin6_family = AF_INET6, + .sin6_port = htons(nm_sock_addr_endpoint_get_port( + _nm_wireguard_peer_get_endpoint(peer_data->peer))), + .sin6_scope_id = 0, + .sin6_flowinfo = 0, + }; + memcpy(&sockaddr.in6.sin6_addr, + g_inet_address_to_bytes(a), + sizeof(struct in6_addr)); + break; + } + } + + g_list_free_full(list, g_object_unref); + } + + if (sockaddr.sa.sa_family == AF_UNSPEC) { + /* we failed to resolve the name. There is no need to reset the previous + * sockaddr. Either it was already AF_UNSPEC, or we had a good name + * from resolving before. In that case, we don't want to throw away + * a possibly good IP address, since WireGuard supports automatic roaming + * anyway. Either the IP address is still good (and we would wrongly + * reject it), or it isn't -- in which case it does not hurt much. */ + } else { + if (nm_sock_addr_union_cmp(&peer_data->ep_resolv.sockaddr, &sockaddr) != 0) + changed = TRUE; + peer_data->ep_resolv.sockaddr = sockaddr; + } + + if (resolv_error || peer_data->ep_resolv.sockaddr.sa.sa_family == AF_UNSPEC) { + /* while it technically did not fail, something is probably odd. Retry frequently to + * resolve the name, like we would do for normal failures. */ + retry_in_msec = _peers_retry_in_msec(peer_data, TRUE); + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: no %sresults for endpoint \"%s\" (retry %s)", + nm_wireguard_peer_get_public_key(peer_data->peer), + resolv_error ? "" : "suitable ", + nm_wireguard_peer_get_endpoint(peer_data->peer), + _retry_in_msec_to_string(retry_in_msec, s_retry)); + } else { + retry_in_msec = _peers_retry_in_msec(peer_data, FALSE); + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: endpoint \"%s\" resolved to %s (retry %s)", + nm_wireguard_peer_get_public_key(peer_data->peer), + nm_wireguard_peer_get_endpoint(peer_data->peer), + nm_sock_addr_union_to_string(&peer_data->ep_resolv.sockaddr, + s_sockaddr, + sizeof(s_sockaddr)), + _retry_in_msec_to_string(retry_in_msec, s_retry)); + } + + _peers_resolve_retry_reschedule_for_peer(self, peer_data, retry_in_msec); + + if (changed) { + /* schedule the job in the background, to give multiple resolve events time + * to complete. */ + nm_clear_g_source(&priv->link_config_delayed_id); + priv->link_config_delayed_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE + 1, + link_config_delayed_resolver_cb, + self, + NULL); + } +} + +static void +_peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gs_unref_object GResolver *resolver = NULL; + const char * host; + + resolver = g_resolver_get_default(); + + nm_assert(!peer_data->ep_resolv.cancellable); + + peer_data->ep_resolv.cancellable = g_cancellable_new(); + priv->peers_resolving_cnt++; + + /* set a special next-try timestamp. It is positive, and indicates + * that we are in the process of trying. + * This timestamp however already lies in the past, but that is correct, + * because we are currently in the process of trying. We will determine + * a next-try timestamp once the try completes. */ + peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_PAST; + + host = nm_sock_addr_endpoint_get_host(_nm_wireguard_peer_get_endpoint(peer_data->peer)); + + g_resolver_lookup_by_name_async(resolver, + host, + peer_data->ep_resolv.cancellable, + _peers_resolve_cb, + peer_data); + + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: resolving name \"%s\" for endpoint \"%s\"...", + nm_wireguard_peer_get_public_key(peer_data->peer), + host, + nm_wireguard_peer_get_endpoint(peer_data->peer)); + + nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt); +} + +static void +_peers_resolve_reresolve_all(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + PeerData * peer_data; + + c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) { + if (peer_data->ep_resolv.cancellable) { + /* remember to retry when the currently ongoing request completes. */ + peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_ASAP; + } else if (peer_data->ep_resolv.next_try_at_nsec <= 0) { + /* this peer does not require resolving the name. Skip it. */ + } else { + /* we have a next-try scheduled. Restart right away. */ + peer_data->ep_resolv.resolv_fail_count = 0; + _peers_resolve_start(self, peer_data); + } + } +} + +static gboolean +_peers_update(NMDeviceWireGuard *self, + PeerData * peer_data, + NMWireGuardPeer * peer, + gboolean force_update) +{ + nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL; + NMSockAddrEndpoint * old_endpoint; + NMSockAddrEndpoint * endpoint; + gboolean endpoint_changed = FALSE; + gboolean changed; + NMSockAddrUnion sockaddr; + gboolean sockaddr_fixed; + char sockaddr_sbuf[100]; + + nm_assert(peer); + nm_assert(nm_wireguard_peer_is_sealed(peer)); + + if (peer == peer_data->peer && !force_update) + return FALSE; + + changed = (nm_wireguard_peer_cmp(peer, peer_data->peer, NM_SETTING_COMPARE_FLAG_EXACT) != 0); + + old_peer = peer_data->peer; + peer_data->peer = nm_wireguard_peer_ref(peer); + + old_endpoint = old_peer ? _nm_wireguard_peer_get_endpoint(old_peer) : NULL; + endpoint = peer ? _nm_wireguard_peer_get_endpoint(peer) : NULL; + + endpoint_changed = (endpoint != old_endpoint + && (!old_endpoint || !endpoint + || !nm_streq(nm_sock_addr_endpoint_get_endpoint(old_endpoint), + nm_sock_addr_endpoint_get_endpoint(endpoint)))); + + if (!force_update && !endpoint_changed) { + /* nothing to do. */ + return changed; + } + + sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + sockaddr_fixed = TRUE; + if (endpoint && nm_sock_addr_endpoint_get_host(endpoint)) { + if (!nm_sock_addr_endpoint_get_fixed_sockaddr(endpoint, &sockaddr)) { + /* we have an endpoint, but it's not a static IP address. We need to resolve + * the names. */ + sockaddr_fixed = FALSE; + } + } + + if (nm_sock_addr_union_cmp(&peer_data->ep_resolv.sockaddr, &sockaddr) != 0) + changed = TRUE; + + if (nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable)) + _peers_resolving_cnt_decrement(self); + + peer_data->ep_resolv = (PeerEndpointResolveData){ + .sockaddr = sockaddr, + .resolv_fail_count = 0, + .cancellable = NULL, + .next_try_at_nsec = 0, + }; + + if (!endpoint) { + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: no endpoint configured", + nm_wireguard_peer_get_public_key(peer_data->peer)); + } else if (!nm_sock_addr_endpoint_get_host(endpoint)) { + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: invalid endpoint \"%s\"", + nm_wireguard_peer_get_public_key(peer_data->peer), + nm_sock_addr_endpoint_get_endpoint(endpoint)); + } else if (sockaddr_fixed) { + _LOGT(LOGD_DEVICE, + "wireguard-peer[%s]: fixed endpoint \"%s\" (%s)", + nm_wireguard_peer_get_public_key(peer_data->peer), + nm_sock_addr_endpoint_get_endpoint(endpoint), + nm_sock_addr_union_to_string(&peer_data->ep_resolv.sockaddr, + sockaddr_sbuf, + sizeof(sockaddr_sbuf))); + } else + _peers_resolve_start(self, peer_data); + + return changed; +} + +static void +_peers_remove_all(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + PeerData * peer_data; + + while ((peer_data = c_list_first_entry(&priv->lst_peers_head, PeerData, lst_peers))) + _peers_remove(self, peer_data); +} + +static void +_peers_update_all(NMDeviceWireGuard *self, NMSettingWireGuard *s_wg, gboolean *out_peers_removed) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + PeerData * peer_data_safe; + PeerData * peer_data; + guint i, n; + gboolean peers_removed = FALSE; + + c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) + peer_data->dirty_update_all = TRUE; + + n = nm_setting_wireguard_get_peers_len(s_wg); + for (i = 0; i < n; i++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer(s_wg, i); + gboolean added = FALSE; + + peer_data = _peers_find(priv, peer); + if (!peer_data) { + peer_data = _peers_add(self, peer); + added = TRUE; + } + _peers_update(self, peer_data, peer, added); + peer_data->dirty_update_all = FALSE; + } + + c_list_for_each_entry_safe (peer_data, peer_data_safe, &priv->lst_peers_head, lst_peers) { + if (peer_data->dirty_update_all) { + _peers_remove(self, peer_data); + peers_removed = TRUE; + } + } + + NM_SET_OUT(out_peers_removed, peers_removed); +} + +static void +_peers_get_platform_list(NMDeviceWireGuardPrivate * priv, + LinkConfigMode config_mode, + NMPWireGuardPeer ** out_peers, + NMPlatformWireGuardChangePeerFlags **out_peer_flags, + guint * out_len, + GArray ** out_allowed_ips_data) +{ + gs_free NMPWireGuardPeer *plpeers = NULL; + gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL; + gs_unref_array GArray *allowed_ips = NULL; + PeerData * peer_data; + guint i_good; + guint n_aip; + guint i_aip; + guint len; + guint i; + + nm_assert(out_peers && !*out_peers); + nm_assert(out_peer_flags && !*out_peer_flags); + nm_assert(out_len && *out_len == 0); + nm_assert(out_allowed_ips_data && !*out_allowed_ips_data); + + len = g_hash_table_size(priv->peers); + + nm_assert(len == c_list_length(&priv->lst_peers_head)); + + if (len == 0) + return; + + plpeers = g_new0(NMPWireGuardPeer, len); + plpeer_flags = g_new0(NMPlatformWireGuardChangePeerFlags, len); + + i_good = 0; + c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) { + NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good]; + NMPWireGuardPeer * plp = &plpeers[i_good]; + NMSettingSecretFlags psk_secret_flags; + + if (!nm_utils_base64secret_decode(nm_wireguard_peer_get_public_key(peer_data->peer), + sizeof(plp->public_key), + plp->public_key)) + continue; + + *plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE; + + plp->persistent_keepalive_interval = + nm_wireguard_peer_get_persistent_keepalive(peer_data->peer); + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL, LINK_CONFIG_MODE_REAPPLY)) + *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL; + + /* if the peer has an endpoint but it is not yet resolved (not ready), + * we still configure it and leave the endpoint unspecified. Later, + * when we can resolve the endpoint, we will update. */ + plp->endpoint = peer_data->ep_resolv.sockaddr; + if (plp->endpoint.sa.sa_family == AF_UNSPEC) { + /* we don't actually ever clear endpoints, if we don't have better information. */ + } else + *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT; + + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL, LINK_CONFIG_MODE_REAPPLY)) { + psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags(peer_data->peer); + if (!NM_FLAGS_HAS(psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + if (!nm_utils_base64secret_decode( + nm_wireguard_peer_get_preshared_key(peer_data->peer), + sizeof(plp->preshared_key), + plp->preshared_key) + && config_mode == LINK_CONFIG_MODE_FULL) + goto skip; + } + *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY; + } + + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL, LINK_CONFIG_MODE_REAPPLY) + && ((n_aip = nm_wireguard_peer_get_allowed_ips_len(peer_data->peer)) > 0)) { + if (!allowed_ips) + allowed_ips = g_array_new(FALSE, FALSE, sizeof(NMPWireGuardAllowedIP)); + + *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS; + + plp->_construct_idx_start = allowed_ips->len; + for (i_aip = 0; i_aip < n_aip; i_aip++) { + const char *aip; + NMIPAddr addrbin = {}; + int addr_family; + gboolean valid; + int prefix; + + aip = nm_wireguard_peer_get_allowed_ip(peer_data->peer, i_aip, &valid); + if (!valid + || !nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, + aip, + &addr_family, + &addrbin, + &prefix)) { + /* the address is really not expected to be invalid, because then + * the connection would not verify. Anyway, silently skip it. */ + continue; + } + + if (prefix == -1) + prefix = addr_family == AF_INET ? 32 : 128; + + g_array_append_val(allowed_ips, + ((NMPWireGuardAllowedIP){ + .family = addr_family, + .mask = prefix, + .addr = addrbin, + })); + } + plp->_construct_idx_end = allowed_ips->len; + } + + i_good++; + continue; + +skip: + memset(plp, 0, sizeof(*plp)); + } + + if (i_good == 0) + return; + + for (i = 0; i < i_good; i++) { + NMPWireGuardPeer *plp = &plpeers[i]; + guint l; + + if (plp->_construct_idx_end == 0) { + nm_assert(plp->_construct_idx_start == 0); + plp->allowed_ips = NULL; + plp->allowed_ips_len = 0; + } else { + nm_assert(plp->_construct_idx_start < plp->_construct_idx_end); + l = plp->_construct_idx_end - plp->_construct_idx_start; + plp->allowed_ips = + &g_array_index(allowed_ips, NMPWireGuardAllowedIP, plp->_construct_idx_start); + plp->allowed_ips_len = l; + } + } + *out_peers = g_steal_pointer(&plpeers); + *out_peer_flags = g_steal_pointer(&plpeer_flags); + *out_len = i_good; + *out_allowed_ips_data = g_steal_pointer(&allowed_ips); +} + +/*****************************************************************************/ + +static void +update_properties(NMDevice *device) +{ + NMDeviceWireGuard * self; + NMDeviceWireGuardPrivate * priv; + const NMPlatformLink * plink; + const NMPlatformLnkWireGuard *props = NULL; + int ifindex; + + g_return_if_fail(NM_IS_DEVICE_WIREGUARD(device)); + self = NM_DEVICE_WIREGUARD(device); + priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + ifindex = nm_device_get_ifindex(device); + props = nm_platform_link_get_lnk_wireguard(nm_device_get_platform(device), ifindex, &plink); + if (!props) { + _LOGW(LOGD_PLATFORM, "could not get wireguard properties"); + return; + } + + g_object_freeze_notify(G_OBJECT(device)); + +#define CHECK_PROPERTY_CHANGED(field, prop) \ + G_STMT_START \ + { \ + if (priv->lnk_curr.field != props->field) { \ + priv->lnk_curr.field = props->field; \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + +#define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \ + G_STMT_START \ + { \ + if (memcmp(&priv->lnk_curr.field, &props->field, sizeof(priv->lnk_curr.field)) != 0) { \ + memcpy(&priv->lnk_curr.field, &props->field, sizeof(priv->lnk_curr.field)); \ + _notify(self, prop); \ + } \ + } \ + G_STMT_END + + CHECK_PROPERTY_CHANGED_ARRAY(public_key, PROP_PUBLIC_KEY); + CHECK_PROPERTY_CHANGED(listen_port, PROP_LISTEN_PORT); + CHECK_PROPERTY_CHANGED(fwmark, PROP_FWMARK); + + g_object_thaw_notify(G_OBJECT(device)); +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NM_DEVICE_CLASS(nm_device_wireguard_parent_class)->link_changed(device, pllink); + update_properties(device); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *dev) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +/*****************************************************************************/ + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char *iface = nm_device_get_iface(device); + int r; + + g_return_val_if_fail(iface, FALSE); + + r = nm_platform_link_wireguard_add(nm_device_get_platform(device), iface, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create WireGuard interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +_secrets_cancel(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + if (priv->secrets_call_id) + nm_act_request_cancel_secrets(NULL, priv->secrets_call_id); + nm_assert(!priv->secrets_call_id); +} + +static void +_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(user_data); + NMDevice * device = NM_DEVICE(self); + NMDeviceWireGuardPrivate *priv; + + g_return_if_fail(NM_IS_DEVICE_WIREGUARD(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + g_return_if_fail(priv->secrets_call_id == call_id); + + priv->secrets_call_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail(nm_act_request_get_settings_connection(req) == connection); + + if (error) { + _LOGW(LOGD_ETHER, "%s", error->message); + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +_secrets_get_secrets(NMDeviceWireGuard * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + NMActRequest * req; + + _secrets_cancel(self); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv->secrets_call_id = + nm_act_request_get_secrets(req, TRUE, setting_name, flags, hints, _secrets_cb, self); + g_return_if_fail(priv->secrets_call_id); +} + +static NMActStageReturn +_secrets_handle_auth_or_fail(NMDeviceWireGuard *self, NMActRequest *req, gboolean new_secrets) +{ + NMConnection * applied_connection; + const char * setting_name; + gs_unref_ptrarray GPtrArray *hints = NULL; + + if (!nm_device_auth_retries_try_next(NM_DEVICE(self))) + return NM_ACT_STAGE_RETURN_FAILURE; + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(req)); + + applied_connection = nm_act_request_get_applied_connection(req); + setting_name = nm_connection_need_secrets(applied_connection, &hints); + if (!setting_name) { + _LOGI(LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (hints) + g_ptr_array_add(hints, NULL); + + _secrets_get_secrets(self, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0), + (hints ? (const char *const *) hints->pdata : NULL)); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +_dns_config_changed(NMDnsManager *dns_manager, NMDeviceWireGuard *self) +{ + /* when the DNS configuration changes, we re-resolve the peer addresses. + * + * Possibly, we should also do that when the default-route changes, but it's + * hard to figure out when that happens. */ + _peers_resolve_reresolve_all(self); +} + +/*****************************************************************************/ + +static NMActStageReturn +link_config(NMDeviceWireGuard * self, + const char * reason, + LinkConfigMode config_mode, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWireGuardPrivate * priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT(); + NMSettingWireGuard * s_wg; + NMConnection * connection; + NMActStageReturn ret; + gs_unref_array GArray *allowed_ips_data = NULL; + NMPlatformLnkWireGuard wg_lnk; + gs_free NMPWireGuardPeer *plpeers = NULL; + gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL; + guint plpeers_len = 0; + const char * setting_name; + gboolean peers_removed; + NMPlatformWireGuardChangeFlags wg_change_flags; + int ifindex; + int r; + + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NONE); + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + s_wg = NM_SETTING_WIREGUARD(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIREGUARD)); + g_return_val_if_fail(s_wg, NM_ACT_STAGE_RETURN_FAILURE); + + priv->link_config_last_at = nm_utils_get_monotonic_timestamp_nsec(); + + _LOGT(LOGD_DEVICE, + "wireguard link config (%s, %s)...", + reason, + _link_config_mode_to_string(config_mode)); + + _auto_default_route_init(self); + + if (!priv->dns_manager) { + priv->dns_manager = g_object_ref(nm_dns_manager_get()); + g_signal_connect(priv->dns_manager, + NM_DNS_MANAGER_CONFIG_CHANGED, + G_CALLBACK(_dns_config_changed), + self); + } + + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL) + && (setting_name = nm_connection_need_secrets(connection, NULL))) { + NMActRequest *req = nm_device_get_act_request(NM_DEVICE(self)); + + _LOGD(LOGD_DEVICE, + "Activation: connection '%s' has security, but secrets are required.", + nm_connection_get_id(connection)); + + ret = _secrets_handle_auth_or_fail(self, req, FALSE); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) { + if (ret != NM_ACT_STAGE_RETURN_POSTPONE) { + nm_assert(ret == NM_ACT_STAGE_RETURN_FAILURE); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + } + return ret; + } + } + + ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self)); + if (ifindex <= 0) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + _peers_update_all(self, s_wg, &peers_removed); + + wg_lnk = (NMPlatformLnkWireGuard){}; + + wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE; + + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL) + || (NM_IN_SET(config_mode, LINK_CONFIG_MODE_REAPPLY) && peers_removed)) + wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS; + + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL, LINK_CONFIG_MODE_REAPPLY)) { + wg_lnk.listen_port = nm_setting_wireguard_get_listen_port(s_wg); + wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT; + + wg_lnk.fwmark = priv->auto_default_route_fwmark; + wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK; + + if (nm_utils_base64secret_decode(nm_setting_wireguard_get_private_key(s_wg), + sizeof(wg_lnk.private_key), + wg_lnk.private_key)) { + wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY(wg_lnk.private_key); + wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY; + } else { + if (NM_IN_SET(config_mode, LINK_CONFIG_MODE_FULL)) { + _LOGD(LOGD_DEVICE, "the provided private-key is invalid"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + } + + _peers_get_platform_list(priv, + config_mode, + &plpeers, + &plpeer_flags, + &plpeers_len, + &allowed_ips_data); + + r = nm_platform_link_wireguard_change(nm_device_get_platform(NM_DEVICE(self)), + ifindex, + &wg_lnk, + plpeers, + plpeer_flags, + plpeers_len, + wg_change_flags); + + nm_explicit_bzero(plpeers, sizeof(plpeers[0]) * plpeers_len); + + if (r < 0) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +link_config_delayed(NMDeviceWireGuard *self, const char *reason) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gint64 now; + + priv->link_config_delayed_id = 0; + + if (priv->link_config_last_at != 0) { + now = nm_utils_get_monotonic_timestamp_nsec(); + if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) { + /* we ratelimit calls to link_config(), because we call this whenever a resolver + * completes. */ + _LOGT(LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason); + priv->link_config_delayed_id = + g_timeout_add(NM_MAX((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) + / NM_UTILS_NSEC_PER_MSEC, + (gint64) 1), + link_config_delayed_ratelimit_cb, + self); + return; + } + } + + link_config(self, reason, LINK_CONFIG_MODE_ENDPOINTS, NULL); +} + +static gboolean +link_config_delayed_ratelimit_cb(gpointer user_data) +{ + link_config_delayed(user_data, "after-ratelimiting"); + return G_SOURCE_REMOVE; +} + +static gboolean +link_config_delayed_resolver_cb(gpointer user_data) +{ + link_config_delayed(user_data, "resolver-update"); + return G_SOURCE_REMOVE; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(device); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + NMDeviceSysIfaceState sys_iface_state; + NMDeviceStateReason failure_reason; + NMActStageReturn ret; + + sys_iface_state = nm_device_sys_iface_state_get(device); + + if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NONE); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + ret = + link_config(NM_DEVICE_WIREGUARD(device), + "configure", + (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) ? LINK_CONFIG_MODE_ASSUME + : LINK_CONFIG_MODE_FULL, + &failure_reason); + + if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) { + /* this never fails. */ + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, failure_reason); + NM_SET_OUT(out_failure_reason, failure_reason); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + nm_assert(NM_IN_SET(ret, NM_ACT_STAGE_RETURN_SUCCESS, NM_ACT_STAGE_RETURN_POSTPONE)); + + if (ret == NM_ACT_STAGE_RETURN_SUCCESS && _peers_resolving_cnt(priv) > 0u) { + _LOGT(LOGD_DEVICE, + "activation delayed to resolve DNS names of peers: resolving and waiting..."); + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + return ret; +} + +static NMIPConfig * +_get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gs_unref_object NMIPConfig *ip_config = NULL; + NMConnection * connection; + NMSettingWireGuard * s_wg; + guint n_peers; + guint i; + int ip_ifindex; + guint32 route_metric; + guint32 route_table_coerced; + gboolean auto_default_route_enabled; + + _auto_default_route_init(self); + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + s_wg = NM_SETTING_WIREGUARD(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIREGUARD)); + + /* Differences to `wg-quick`. + * + * `wg-quick` supports the "Table" setting with 3 modes: + * + * a1) "off": this is what we do with "peer-routes" disabled. + * + * a2) an explicit routing table. This is our behavior with "peer-routes" on. In this case + * we honor the "ipv4.route-table" and "ipv6.route-table" settings. One difference is that + * `wg-quick` would resolve table names from /etc/iproute2/rt_tables. Our connection profiles + * only contain table numbers, so that conversion from name to table must have happened + * before already. + * + * a3) "auto" (the default). In this case, `wg-quick` would only add the route to the + * main table, if the AllowedIP range is not yet reachable on the link. With "peer-routes" + * enabled, we don't check for that and always add the routes to the main-table + * (with 'ipv4.route-table' and 'ipv6.route-table' set to zero or RT_TABLE_MAIN (254)). + * + * Also, in "auto" mode, `wg-quick` would add special handling for /0 routes and pick + * an empty table to configure policy routing to avoid routing loops. This handling + * of routing-loops via policy routing is not yet done, and requires a separate solution + * from constructing the peer-routes here. + */ + if (!nm_setting_wireguard_get_peer_routes(s_wg)) + return NULL; + + ip_ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self)); + + if (ip_ifindex <= 0) + return NULL; + + route_metric = nm_device_get_route_metric(NM_DEVICE(self), addr_family); + + route_table_coerced = + nm_platform_route_table_coerce(nm_device_get_route_table(NM_DEVICE(self), addr_family)); + + auto_default_route_enabled = (addr_family == AF_INET) ? priv->auto_default_route_enabled_4 + : priv->auto_default_route_enabled_6; + + n_peers = nm_setting_wireguard_get_peers_len(s_wg); + for (i = 0; i < n_peers; i++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer(s_wg, i); + guint n_aips; + guint j; + + n_aips = nm_wireguard_peer_get_allowed_ips_len(peer); + for (j = 0; j < n_aips; j++) { + NMPlatformIPXRoute rt; + NMIPAddr addrbin; + const char * aip; + gboolean valid; + int prefix; + guint32 rtable_coerced; + + aip = nm_wireguard_peer_get_allowed_ip(peer, j, &valid); + + if (!valid + || !nm_utils_parse_inaddr_prefix_bin(addr_family, aip, NULL, &addrbin, &prefix)) + continue; + + if (prefix < 0) + prefix = (addr_family == AF_INET) ? 32 : 128; + + if (prefix == 0) { + NMSettingIPConfig *s_ip; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (nm_setting_ip_config_get_never_default(s_ip)) + continue; + } + + if (!ip_config) { + ip_config = nm_device_ip_config_new(NM_DEVICE(self), addr_family); + nm_ip_config_set_config_flags(ip_config, + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES, + 0); + } + + nm_utils_ipx_address_clear_host_address(addr_family, &addrbin, NULL, prefix); + + rtable_coerced = route_table_coerced; + + if (prefix == 0 && auto_default_route_enabled) { + /* In auto-default-route mode, we place the default route in a table that + * has the same number as the fwmark. wg-quick does that too. If you don't + * like that, configure the rules and the default-route explicitly in the + * connection profile. */ + rtable_coerced = nm_platform_route_table_coerce(priv->auto_default_route_fwmark); + } + + if (addr_family == AF_INET) { + rt.r4 = (NMPlatformIP4Route){ + .network = addrbin.addr4, + .plen = prefix, + .ifindex = ip_ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .table_coerced = rtable_coerced, + .metric = route_metric, + }; + } else { + rt.r6 = (NMPlatformIP6Route){ + .network = addrbin.addr6, + .plen = prefix, + .ifindex = ip_ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .table_coerced = rtable_coerced, + .metric = route_metric, + }; + } + + nm_ip_config_add_route(ip_config, &rt.rx, NULL); + } + } + + return g_steal_pointer(&ip_config); +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + gs_unref_object NMIPConfig *ip_config = NULL; + + ip_config = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family); + + nm_device_set_dev2_ip_config(device, addr_family, ip_config); + + return NM_DEVICE_CLASS(nm_device_wireguard_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + /* When "MTU" for `wg-quick up` is unset, it calls `ip route get` for + * each configured endpoint, to determine the suitable MTU how to reach + * each endpoint. + * For `wg-quick` this works very well, because whenever the script runs it + * determines the best setting at that point in time. It's simply not concerned + * with what happens later (and it's not around anyway). + * + * NetworkManager sticks around, so the right MTU would need to be re-determined + * whenever anything relevant changes. Which basically means, to re-evaluate whenever + * something related to addresses or routing changes (which happens all the time). + * + * The correct MTU indeed depends on the MTU setting of other interfaces (or routes). + * But it's still odd, that activating/deactivating a seemingly unrelated interface + * would trigger an MTU change. It's odd to explain/document and odd to implemented + * -- despite this being the reality. + * + * For now, only support configuring an explicit MTU, or leave the setting untouched. + * The same limitation also applies to other "ip-tunnel" types, where we could use + * similar smarts for autodetecting the MTU. + */ + return nm_device_get_configured_mtu_from_connection(device, + NM_TYPE_SETTING_WIREGUARD, + out_source); +} + +static void +_device_cleanup(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + _peers_remove_all(self); + + _secrets_cancel(self); + + priv->auto_default_route_initialized = FALSE; + priv->auto_default_route_priority_initialized = FALSE; +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if (new_state <= NM_DEVICE_STATE_ACTIVATED) + return; + + _device_cleanup(NM_DEVICE_WIREGUARD(device)); +} + +/*****************************************************************************/ + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + if (nm_streq(setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) { + /* Most, but not all WireGuard settings can be reapplied. Whitelist. + * + * MTU cannot be reapplied. */ + return nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_WIREGUARD_SETTING_NAME, + error, + NM_SETTING_WIREGUARD_FWMARK, + NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE, + NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, + NM_SETTING_WIREGUARD_LISTEN_PORT, + NM_SETTING_WIREGUARD_PEERS, + NM_SETTING_WIREGUARD_PEER_ROUTES, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS); + } + + return NM_DEVICE_CLASS(nm_device_wireguard_parent_class) + ->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static void +reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(device); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + gs_unref_object NMIPConfig *ip4_config = NULL; + gs_unref_object NMIPConfig *ip6_config = NULL; + NMDeviceState state = nm_device_get_state(device); + + NM_DEVICE_CLASS(nm_device_wireguard_parent_class)->reapply_connection(device, con_old, con_new); + + if (state >= NM_DEVICE_STATE_CONFIG) { + priv->auto_default_route_refresh = TRUE; + link_config(NM_DEVICE_WIREGUARD(device), "reapply", LINK_CONFIG_MODE_REAPPLY, NULL); + } + + if (state >= NM_DEVICE_STATE_IP_CONFIG) { + ip4_config = _get_dev2_ip_config(self, AF_INET); + ip6_config = _get_dev2_ip_config(self, AF_INET6); + + nm_device_set_dev2_ip_config(device, AF_INET, ip4_config); + nm_device_set_dev2_ip_config(device, AF_INET6, ip6_config); + } +} + +/*****************************************************************************/ + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(device); + NMSettingWireGuard * s_wg = + NM_SETTING_WIREGUARD(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIREGUARD)); + const NMPObject * obj_wg; + const NMPObjectLnkWireGuard *olnk_wg; + guint i; + + if (!s_wg) { + s_wg = NM_SETTING_WIREGUARD(nm_setting_wireguard_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wg)); + } + + g_object_set(s_wg, + NM_SETTING_WIREGUARD_FWMARK, + (guint) priv->lnk_curr.fwmark, + NM_SETTING_WIREGUARD_LISTEN_PORT, + (guint) priv->lnk_curr.listen_port, + NULL); + + obj_wg = NMP_OBJECT_UP_CAST(nm_platform_link_get_lnk_wireguard(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + NULL)); + if (!obj_wg) + return; + + olnk_wg = &obj_wg->_lnk_wireguard; + + for (i = 0; i < olnk_wg->peers_len; i++) { + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + const NMPWireGuardPeer * ppeer = &olnk_wg->peers[i]; + + peer = nm_wireguard_peer_new(); + + _nm_wireguard_peer_set_public_key_bin(peer, ppeer->public_key); + + nm_setting_wireguard_append_peer(s_wg, peer); + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(object); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_PUBLIC_KEY: + g_value_take_variant(value, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + priv->lnk_curr.public_key, + sizeof(priv->lnk_curr.public_key), + 1)); + break; + case PROP_LISTEN_PORT: + g_value_set_uint(value, priv->lnk_curr.listen_port); + break; + case PROP_FWMARK: + g_value_set_uint(value, priv->lnk_curr.fwmark); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_wireguard_init(NMDeviceWireGuard *self) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + c_list_init(&priv->lst_peers_head); + priv->peers = g_hash_table_new(_peer_data_hash, _peer_data_equal); +} + +static void +dispose(GObject *object) +{ + NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD(object); + + _device_cleanup(self); + + G_OBJECT_CLASS(nm_device_wireguard_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMDeviceWireGuard * self = NM_DEVICE_WIREGUARD(object); + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + + nm_explicit_bzero(priv->lnk_curr.private_key, sizeof(priv->lnk_curr.private_key)); + + if (priv->dns_manager) { + g_signal_handlers_disconnect_by_func(priv->dns_manager, _dns_config_changed, self); + g_object_unref(priv->dns_manager); + } + + g_hash_table_destroy(priv->peers); + + G_OBJECT_CLASS(nm_device_wireguard_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WIREGUARD, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PublicKey", + "ay", + NM_DEVICE_WIREGUARD_PUBLIC_KEY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ListenPort", + "q", + NM_DEVICE_WIREGUARD_LISTEN_PORT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FwMark", + "u", + NM_DEVICE_WIREGUARD_FWMARK), ), ), +}; + +static void +nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wireguard); + + device_class->connection_type_supported = NM_SETTING_WIREGUARD_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WIREGUARD_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIREGUARD); + + device_class->state_changed = device_state_changed; + device_class->create_and_realize = create_and_realize; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage2_config_also_for_external_or_assume = TRUE; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->link_changed = link_changed; + device_class->update_connection = update_connection; + device_class->can_reapply_change = can_reapply_change; + device_class->reapply_connection = reapply_connection; + device_class->get_configured_mtu = get_configured_mtu; + device_class->get_extra_rules = get_extra_rules; + device_class->coerce_route_table = coerce_route_table; + + obj_properties[PROP_PUBLIC_KEY] = + g_param_spec_variant(NM_DEVICE_WIREGUARD_PUBLIC_KEY, + "", + "", + G_VARIANT_TYPE("ay"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LISTEN_PORT] = g_param_spec_uint(NM_DEVICE_WIREGUARD_LISTEN_PORT, + "", + "", + 0, + G_MAXUINT16, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FWMARK] = g_param_spec_uint(NM_DEVICE_WIREGUARD_FWMARK, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*************************************************************/ + +#define NM_TYPE_WIREGUARD_DEVICE_FACTORY (nm_wireguard_device_factory_get_type()) +#define NM_WIREGUARD_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIREGUARD_DEVICE_FACTORY, NMWireGuardDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_WIREGUARD, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "WireGuard", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WIREGUARD, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WIREGUARD, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + WIREGUARD, + WireGuard, + wireguard, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_WIREGUARD) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_WIREGUARD_SETTING_NAME), + factory_class->create_device = create_device;) diff --git a/src/core/devices/nm-device-wireguard.h b/src/core/devices/nm-device-wireguard.h new file mode 100644 index 0000000..7e18bdb --- /dev/null +++ b/src/core/devices/nm-device-wireguard.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Javier Arteaga + */ + +#ifndef __NM_DEVICE_WIREGUARD_H__ +#define __NM_DEVICE_WIREGUARD_H__ + +#include "nm-device.h" + +#define NM_TYPE_DEVICE_WIREGUARD (nm_device_wireguard_get_type()) +#define NM_DEVICE_WIREGUARD(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuard)) +#define NM_DEVICE_WIREGUARD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuardClass)) +#define NM_IS_DEVICE_WIREGUARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WIREGUARD)) +#define NM_IS_DEVICE_WIREGUARD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_WIREGUARD)) +#define NM_DEVICE_WIREGUARD_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuardClass)) + +#define NM_DEVICE_WIREGUARD_PUBLIC_KEY "public-key" +#define NM_DEVICE_WIREGUARD_LISTEN_PORT "listen-port" +#define NM_DEVICE_WIREGUARD_FWMARK "fwmark" + +typedef struct _NMDeviceWireGuard NMDeviceWireGuard; +typedef struct _NMDeviceWireGuardClass NMDeviceWireGuardClass; + +GType nm_device_wireguard_get_type(void); + +#endif /* __NM_DEVICE_WIREGUARD_H__ */ diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c new file mode 100644 index 0000000..05b48a7 --- /dev/null +++ b/src/core/devices/nm-device-wpan.c @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Lubomir Rintel + */ + +#include "nm-default.h" + +#include "nm-manager.h" +#include "nm-device-wpan.h" + +#include +#include +#include + +#include "nm-act-request.h" +#include "nm-device-private.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" +#include "nm-device-factory.h" +#include "nm-setting-wpan.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceWpan +#include "nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceWpan { + NMDevice parent; +}; + +struct _NMDeviceWpanClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceWpan, nm_device_wpan, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingWpan *s_wpan; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_WPAN_SETTING_NAME, + existing_connections, + NULL, + _("WPAN connection"), + NULL, + NULL, + TRUE); + + s_wpan = NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN)); + if (!s_wpan) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wpan' setting is required."); + return FALSE; + } + + return TRUE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMSettingWpan *s_wpan = + NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN)); + + if (!s_wpan) { + s_wpan = (NMSettingWpan *) nm_setting_wpan_new(); + nm_connection_add_setting(connection, (NMSetting *) s_wpan); + } +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingWpan *s_wpan; + const char * mac, *hw_addr; + + if (!NM_DEVICE_CLASS(nm_device_wpan_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_wpan = NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN)); + + mac = nm_setting_wpan_get_mac_address(s_wpan); + if (mac) { + hw_addr = nm_device_get_hw_address(device); + if (!nm_utils_hwaddr_matches(mac, -1, hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "MAC address mismatches"); + return FALSE; + } + } + + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWpan * self = NM_DEVICE_WPAN(device); + NMSettingWpan * s_wpan; + NMPlatform * platform; + guint16 pan_id; + guint16 short_address; + gint16 page, channel; + int ifindex; + const guint8 * hwaddr; + gsize hwaddr_len = 0; + const NMPlatformLink *lowpan_plink; + NMDevice * lowpan_device = NULL; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + + platform = nm_device_get_platform(device); + nm_assert(NM_IS_PLATFORM(platform)); + + ifindex = nm_device_get_ifindex(device); + + g_return_val_if_fail(ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE); + + s_wpan = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WPAN); + + g_return_val_if_fail(s_wpan, NM_ACT_STAGE_RETURN_FAILURE); + + hwaddr = nm_platform_link_get_address(platform, ifindex, &hwaddr_len); + + if (!hwaddr) { + *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* As of kernel 4.16, the 6LoWPAN devices layered on top of WPANs + * need to be DOWN as well as the WPAN device itself in order to + * modify the WPAN properties. */ + lowpan_plink = + nm_platform_link_get_by_address(platform, NM_LINK_TYPE_6LOWPAN, hwaddr, hwaddr_len); + if (lowpan_plink && NM_FLAGS_HAS(lowpan_plink->n_ifi_flags, IFF_UP)) { + lowpan_device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, lowpan_plink->ifindex); + } + + if (lowpan_device) + nm_device_take_down(lowpan_device, TRUE); + + nm_device_take_down(device, TRUE); + + pan_id = nm_setting_wpan_get_pan_id(s_wpan); + if (pan_id != G_MAXUINT16) { + if (!nm_platform_wpan_set_pan_id(platform, ifindex, pan_id)) { + _LOGW(LOGD_DEVICE, "unable to set the PAN ID"); + goto out; + } + } + + short_address = nm_setting_wpan_get_short_address(s_wpan); + if (short_address != G_MAXUINT16) { + if (!nm_platform_wpan_set_short_addr(platform, ifindex, short_address)) { + _LOGW(LOGD_DEVICE, "unable to set the short address"); + goto out; + } + } + + channel = nm_setting_wpan_get_channel(s_wpan); + if (channel != NM_SETTING_WPAN_CHANNEL_DEFAULT) { + page = nm_setting_wpan_get_page(s_wpan); + if (!nm_platform_wpan_set_channel(platform, ifindex, page, channel)) { + _LOGW(LOGD_DEVICE, "unable to set the channel"); + goto out; + } + } + + ret = NM_ACT_STAGE_RETURN_SUCCESS; + +out: + nm_device_bring_up(device, TRUE, NULL); + + if (lowpan_device) + nm_device_bring_up(lowpan_device, TRUE, NULL); + + return ret; +} + +/*****************************************************************************/ + +static void +nm_device_wpan_init(NMDeviceWpan *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_wpan = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WPAN, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), ), ), +}; + +static void +nm_device_wpan_class_init(NMDeviceWpanClass *klass) +{ + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wpan); + + device_class->connection_type_supported = NM_SETTING_WPAN_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WPAN_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WPAN); + + device_class->complete_connection = complete_connection; + device_class->check_connection_compatible = check_connection_compatible; + device_class->update_connection = update_connection; + device_class->act_stage1_prepare = act_stage1_prepare; +} + +/*****************************************************************************/ + +#define NM_TYPE_WPAN_DEVICE_FACTORY (nm_wpan_device_factory_get_type()) +#define NM_WPAN_DEVICE_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WPAN_DEVICE_FACTORY, NMWpanDeviceFactory)) + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return g_object_new(NM_TYPE_DEVICE_WPAN, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "WPAN", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WPAN, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WPAN, + NULL); +} + +NM_DEVICE_FACTORY_DEFINE_INTERNAL( + WPAN, + Wpan, + wpan, + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_WPAN) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_WPAN_SETTING_NAME), + factory_class->create_device = create_device;); diff --git a/src/core/devices/nm-device-wpan.h b/src/core/devices/nm-device-wpan.h new file mode 100644 index 0000000..969929a --- /dev/null +++ b/src/core/devices/nm-device-wpan.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Lubomir Rintel + */ + +#ifndef __NETWORKMANAGER_DEVICE_WPAN_H__ +#define __NETWORKMANAGER_DEVICE_WPAN_H__ + +#define NM_TYPE_DEVICE_WPAN (nm_device_wpan_get_type()) +#define NM_DEVICE_WPAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WPAN, NMDeviceWpan)) +#define NM_DEVICE_WPAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WPAN, NMDeviceWpanClass)) +#define NM_IS_DEVICE_WPAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WPAN)) +#define NM_IS_DEVICE_WPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_WPAN)) +#define NM_DEVICE_WPAN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_WPAN, NMDeviceWpanClass)) + +typedef struct _NMDeviceWpan NMDeviceWpan; +typedef struct _NMDeviceWpanClass NMDeviceWpanClass; + +GType nm_device_wpan_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_WPAN_H__ */ diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c new file mode 100644 index 0000000..a2d4788 --- /dev/null +++ b/src/core/devices/nm-device.c @@ -0,0 +1,19019 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-device.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-std-aux/unaligned.h" +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-glib-aux/nm-random-utils.h" +#include "systemd/nm-sd-utils-shared.h" + +#include "nm-base/nm-ethtool-base.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-device-private.h" +#include "nm-l3cfg.h" +#include "nm-l3-config-data.h" +#include "NetworkManagerUtils.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "platform/nmp-object.h" +#include "platform/nmp-rules-manager.h" +#include "ndisc/nm-ndisc.h" +#include "ndisc/nm-lndp-ndisc.h" +#include "dhcp/nm-dhcp-manager.h" +#include "dhcp/nm-dhcp-utils.h" +#include "nm-act-request.h" +#include "nm-proxy-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-pacrunner-manager.h" +#include "dnsmasq/nm-dnsmasq-manager.h" +#include "nm-dhcp-config.h" +#include "nm-rfkill-manager.h" +#include "nm-firewall-manager.h" +#include "settings/nm-settings-connection.h" +#include "settings/nm-settings.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-ovs-external-ids.h" +#include "nm-setting-user.h" +#include "nm-auth-utils.h" +#include "nm-keep-alive.h" +#include "nm-netns.h" +#include "nm-dispatcher.h" +#include "nm-config.h" +#include "c-list/src/c-list.h" +#include "dns/nm-dns-manager.h" +#include "nm-acd-manager.h" +#include "nm-core-internal.h" +#include "systemd/nm-sd.h" +#include "nm-lldp-listener.h" +#include "nm-audit-manager.h" +#include "nm-connectivity.h" +#include "nm-dbus-interface.h" + +#include "nm-device-generic.h" +#include "nm-device-vlan.h" +#include "nm-device-vrf.h" +#include "nm-device-wireguard.h" + +#include "nm-device-logging.h" + +/*****************************************************************************/ + +#define DEFAULT_AUTOCONNECT TRUE + +static guint32 +dhcp_grace_period_from_timeout(guint32 timeout) +{ +#define DHCP_GRACE_PERIOD_MULTIPLIER 2U + + nm_assert(timeout > 0); + nm_assert(timeout < G_MAXINT32); + + if (timeout < G_MAXUINT32 / DHCP_GRACE_PERIOD_MULTIPLIER) + return timeout * DHCP_GRACE_PERIOD_MULTIPLIER; + + return G_MAXUINT32; +} + +#define CARRIER_WAIT_TIME_MS 6000 +#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000 + +#define NM_DEVICE_AUTH_RETRIES_UNSET -1 +#define NM_DEVICE_AUTH_RETRIES_INFINITY -2 +#define NM_DEVICE_AUTH_RETRIES_DEFAULT 3 + +/*****************************************************************************/ + +typedef void (*ActivationHandleFunc)(NMDevice *self); + +typedef enum { + CLEANUP_TYPE_KEEP, + CLEANUP_TYPE_REMOVED, + CLEANUP_TYPE_DECONFIGURE, +} CleanupType; + +typedef struct { + CList lst_slave; + NMDevice *slave; + gulong watch_id; + bool slave_is_enslaved; + bool configure; +} SlaveInfo; + +typedef struct { + NMDevice *device; + guint idle_add_id; + int ifindex; +} DeleteOnDeactivateData; + +typedef struct { + NMDevice * device; + GCancellable * cancellable; + NMPlatformAsyncCallback callback; + gpointer callback_data; + guint num_vfs; + NMOptionBool autoprobe; +} SriovOp; + +typedef void (*AcdCallback)(NMDevice *, NMIP4Config **, gboolean); + +typedef enum { + /* The various NML3ConfigData types that we track explicitly. Note that + * their relative order matters: higher numbers in this enum means more + * important (and during merge overwrites other settings). */ + L3_CONFIG_DATA_TYPE_LL_4, + L3_CONFIG_DATA_TYPE_AC_6, + L3_CONFIG_DATA_TYPE_DHCP_4, + L3_CONFIG_DATA_TYPE_DHCP_6, + L3_CONFIG_DATA_TYPE_DEV_4, + L3_CONFIG_DATA_TYPE_DEV_6, + L3_CONFIG_DATA_TYPE_SETTING, + _L3_CONFIG_DATA_TYPE_NUM, + _L3_CONFIG_DATA_TYPE_NONE, +} L3ConfigDataType; + +typedef struct { + AcdCallback callback; + NMDevice * device; + NMIP4Config **configs; +} AcdData; + +typedef enum { + HW_ADDR_TYPE_UNSET = 0, + HW_ADDR_TYPE_PERMANENT, + HW_ADDR_TYPE_EXPLICIT, + HW_ADDR_TYPE_GENERATED, +} HwAddrType; + +typedef enum { + FIREWALL_STATE_UNMANAGED = 0, + FIREWALL_STATE_INITIALIZED, + FIREWALL_STATE_WAIT_STAGE_3, + FIREWALL_STATE_WAIT_IP_CONFIG, +} FirewallState; + +typedef struct { + NMIPConfig *orig; /* the original configuration applied to the device */ + NMIPConfig *current; /* configuration after external changes. NULL means + * that the original configuration didn't change. */ +} AppliedConfig; + +typedef struct { + NMDhcpClient *client; + NMDhcpConfig *config; + gulong state_sigid; + guint grace_id; + bool grace_pending : 1; + bool was_active : 1; +} DhcpData; + +struct _NMDeviceConnectivityHandle { + CList concheck_lst; + NMDevice * self; + NMDeviceConnectivityCallback callback; + gpointer user_data; + NMConnectivityCheckHandle * c_handle; + guint64 seq; + bool is_periodic : 1; + bool is_periodic_bump : 1; + bool is_periodic_bump_on_complete : 1; + int addr_family; +}; + +typedef struct { + int ifindex; + NMEthtoolFeatureStates *features; + NMOptionBool requested[_NM_ETHTOOL_ID_FEATURE_NUM]; + NMEthtoolCoalesceState *coalesce; + NMEthtoolRingState * ring; +} EthtoolState; + +typedef enum { + RESOLVER_WAIT_ADDRESS = 0, + RESOLVER_IN_PROGRESS, + RESOLVER_DONE, +} ResolverState; + +typedef struct { + ResolverState state; + GResolver * resolver; + GInetAddress *address; + GCancellable *cancellable; + char * hostname; + NMDevice * device; + guint timeout_id; /* Used when waiting for the address */ + int addr_family; +} HostnameResolver; + +/*****************************************************************************/ + +enum { + STATE_CHANGED, + AUTOCONNECT_ALLOWED, + IP4_CONFIG_CHANGED, + IP6_CONFIG_CHANGED, + IP6_PREFIX_DELEGATED, + IP6_SUBNET_NEEDED, + REMOVED, + RECHECK_AUTO_ACTIVATE, + RECHECK_ASSUME, + DNS_LOOKUP_DONE, + LAST_SIGNAL, +}; +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDevice, + PROP_UDI, + PROP_PATH, + PROP_IFACE, + PROP_IP_IFACE, + PROP_DRIVER, + PROP_DRIVER_VERSION, + PROP_FIRMWARE_VERSION, + PROP_CAPABILITIES, + PROP_CARRIER, + PROP_MTU, + PROP_IP4_ADDRESS, + PROP_IP4_CONFIG, + PROP_DHCP4_CONFIG, + PROP_IP6_CONFIG, + PROP_DHCP6_CONFIG, + PROP_STATE, + PROP_STATE_REASON, + PROP_ACTIVE_CONNECTION, + PROP_DEVICE_TYPE, + PROP_LINK_TYPE, + PROP_MANAGED, + PROP_AUTOCONNECT, + PROP_FIRMWARE_MISSING, + PROP_NM_PLUGIN_MISSING, + PROP_TYPE_DESC, + PROP_RFKILL_TYPE, + PROP_IFINDEX, + PROP_AVAILABLE_CONNECTIONS, + PROP_PHYSICAL_PORT_ID, + PROP_MASTER, + PROP_PARENT, + PROP_HW_ADDRESS, + PROP_PERM_HW_ADDRESS, + PROP_HAS_PENDING_ACTION, + PROP_METERED, + PROP_LLDP_NEIGHBORS, + PROP_REAL, + PROP_SLAVES, + PROP_STATISTICS_REFRESH_RATE_MS, + PROP_STATISTICS_TX_BYTES, + PROP_STATISTICS_RX_BYTES, + PROP_IP4_CONNECTIVITY, + PROP_IP6_CONNECTIVITY, + PROP_INTERFACE_FLAGS, ); + +typedef struct _NMDevicePrivate { + bool in_state_changed; + + guint device_link_changed_id; + guint device_ip_link_changed_id; + + NMDeviceState state; + NMDeviceStateReason state_reason; + struct { + guint id; + + /* The @state/@reason is only valid, when @id is set. */ + NMDeviceState state; + NMDeviceStateReason reason; + } queued_state; + + union { + struct { + guint queued_ip_config_id_6; + guint queued_ip_config_id_4; + }; + guint queued_ip_config_id_x[2]; + }; + + GSList *pending_actions; + GSList *dad6_failed_addrs; + + NMDBusTrackObjPath parent_device; + + char *udi; + char *path; + + union { + const char *const iface; + char * iface_; + }; + union { + const char *const ip_iface; + char * ip_iface_; + }; + + union { + const int ifindex; + int ifindex_; + }; + union { + const int ip_ifindex; + int ip_ifindex_; + }; + + NMNetnsSharedIPHandle *shared_ip_handle; + + int parent_ifindex; + + int auth_retries; + + union { + struct { + HostnameResolver *hostname_resolver_6; + HostnameResolver *hostname_resolver_4; + }; + HostnameResolver *hostname_resolver_x[2]; + }; + + union { + const guint8 hw_addr_len; /* read-only */ + guint8 hw_addr_len_; + }; + + HwAddrType hw_addr_type : 5; + + bool real : 1; + + bool update_ip_config_completed_v4 : 1; + bool update_ip_config_completed_v6 : 1; + + NMDeviceType type; + char * type_desc; + NMLinkType link_type; + NMDeviceCapabilities capabilities; + char * driver; + char * driver_version; + char * firmware_version; + RfKillType rfkill_type; + bool firmware_missing : 1; + bool nm_plugin_missing : 1; + bool + hw_addr_perm_fake : 1; /* whether the permanent HW address could not be read and is a fake */ + + NMUtilsStableType current_stable_id_type : 3; + + bool nm_owned : 1; /* whether the device is a device owned and created by NM */ + + bool assume_state_guess_assume : 1; + char *assume_state_connection_uuid; + + guint64 udi_id; + + GHashTable *available_connections; + char * hw_addr; + char * hw_addr_perm; + char * hw_addr_initial; + char * physical_port_id; + guint dev_id; + + NMUnmanagedFlags unmanaged_mask; + NMUnmanagedFlags unmanaged_flags; + DeleteOnDeactivateData + *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */ + + GCancellable *deactivating_cancellable; + + NMActRequest * queued_act_request; + bool queued_act_request_is_waiting_for_carrier : 1; + NMDBusTrackObjPath act_request; + + union { + struct { + guint activation_source_id_6; + guint activation_source_id_4; /* for layer2 and IPv4. */ + }; + guint activation_source_id_x[2]; + }; + + union { + struct { + ActivationHandleFunc activation_source_func_6; + ActivationHandleFunc activation_source_func_4; /* for layer2 and IPv4. */ + }; + ActivationHandleFunc activation_source_func_x[2]; + }; + + guint recheck_assume_id; + + struct { + guint call_id; + NMDeviceStateReason available_reason; + NMDeviceStateReason unavailable_reason; + } recheck_available; + + struct { + NMDispatcherCallId *call_id; + NMDeviceState post_state; + NMDeviceStateReason post_state_reason; + } dispatcher; + + /* Link stuff */ + guint link_connected_id; + guint link_disconnected_id; + guint carrier_defer_id; + guint carrier_wait_id; + gulong config_changed_id; + gulong ifindex_changed_id; + guint32 mtu; + guint32 ip6_mtu; + guint32 mtu_initial; + guint32 ip6_mtu_initial; + NMDeviceMtuSource mtu_source; + + guint32 v4_route_table; + guint32 v6_route_table; + + /* when carrier goes away, we give a grace period of _get_carrier_wait_ms() + * until taking action. + * + * When changing MTU, the device might take longer then that. So, whenever + * NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS + * in the future. This is used to extend the grace period in this particular case. */ + gint64 carrier_wait_until_ms; + + union { + const NMDeviceSysIfaceState sys_iface_state; + NMDeviceSysIfaceState sys_iface_state_; + }; + + bool carrier : 1; + bool ignore_carrier : 1; + + bool up : 1; /* IFF_UP */ + + bool v4_commit_first_time : 1; + bool v6_commit_first_time : 1; + + bool default_route_metric_penalty_ip4_has : 1; + bool default_route_metric_penalty_ip6_has : 1; + + bool v4_route_table_initialized : 1; + bool v6_route_table_initialized : 1; + + bool l3config_merge_flags_has : 1; + + bool v4_route_table_all_sync_before : 1; + bool v6_route_table_all_sync_before : 1; + + NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags : 5; + + bool is_enslaved : 1; + + bool ipv6ll_handle : 1; /* TRUE if NM handles the device's IPv6LL address */ + bool ipv6ll_has : 1; + bool ndisc_started : 1; + bool device_link_changed_down : 1; + + bool concheck_rp_filter_checked : 1; + + NMDeviceStageState stage1_sriov_state : 3; + + /* Generic DHCP stuff */ + char *dhcp_anycast_address; + + char *current_stable_id; + + /* Proxy Configuration */ + NMProxyConfig * proxy_config; + NMPacrunnerConfId *pacrunner_conf_id; + + /* IP configuration info. Combined config from VPN, settings, and device */ + union { + struct { + NMIP6Config *ip_config_6; + NMIP4Config *ip_config_4; + }; + NMIPConfig *ip_config_x[2]; + }; + + /* Config from DHCP, PPP, LLv4, etc */ + AppliedConfig dev_ip_config_4; + + /* config from the setting */ + union { + struct { + NMIP6Config *con_ip_config_6; + NMIP4Config *con_ip_config_4; + }; + NMIPConfig *con_ip_config_x[2]; + }; + + /* Stuff added outside NM */ + union { + struct { + NMIP6Config *ext_ip_config_6; + NMIP4Config *ext_ip_config_4; + }; + NMIPConfig *ext_ip_config_x[2]; + }; + + /* VPNs which use this device */ + union { + struct { + GSList *vpn_configs_6; + GSList *vpn_configs_4; + }; + GSList *vpn_configs_x[2]; + }; + + /* Extra device configuration, injected by the subclass of NMDevice. + * This is used for example by NMDeviceModem for WWAN configuration. */ + union { + struct { + AppliedConfig dev2_ip_config_6; + AppliedConfig dev2_ip_config_4; + }; + AppliedConfig dev2_ip_config_x[2]; + }; + + /* DHCPv4 tracking */ + struct { + char *pac_url; + } dhcp4; + + struct { + /* IP6 config from DHCP */ + AppliedConfig ip6_config; + /* Event ID of the current IP6 config from DHCP */ + char * event_id; + gulong prefix_sigid; + NMNDiscDHCPLevel mode; + guint needed_prefixes; + } dhcp6; + + union { + struct { + DhcpData dhcp_data_6; + DhcpData dhcp_data_4; + }; + DhcpData dhcp_data_x[2]; + }; + + struct { + NMLogDomain log_domain; + guint timeout; + guint watch; + GPid pid; + char * binary; + char * address; + guint deadline; + } gw_ping; + + /* dnsmasq stuff for shared connections */ + NMDnsMasqManager *dnsmasq_manager; + gulong dnsmasq_state_id; + + /* Firewall */ + FirewallState fw_state : 4; + NMFirewallManager * fw_mgr; + NMFirewallManagerCallId *fw_call; + + /* IPv4LL stuff */ + sd_ipv4ll *ipv4ll; + guint ipv4ll_timeout; + guint rt6_temporary_not_available_id; + + /* IPv4 DAD stuff */ + struct { + GSList * dad_list; + NMAcdManager *announcing; + } acd; + + union { + struct { + const NMDeviceIPState ip_state_6; + const NMDeviceIPState ip_state_4; + }; + union { + const NMDeviceIPState ip_state_x[2]; + NMDeviceIPState ip_state_x_[2]; + }; + }; + + AppliedConfig ac_ip6_config; /* config from IPv6 autoconfiguration */ + NMIP6Config * ext_ip6_config_captured; /* Configuration captured from platform. */ + NMIP6Config * dad6_ip6_config; + struct in6_addr ipv6ll_addr; + + GHashTable *rt6_temporary_not_available; + + NMNDisc * ndisc; + gulong ndisc_changed_id; + gulong ndisc_timeout_id; + NMSettingIP6ConfigPrivacy ndisc_use_tempaddr; + + guint linklocal6_timeout_id; + guint8 linklocal6_dad_counter; + + GHashTable *ip6_saved_properties; + + EthtoolState *ethtool_state; + + gboolean needs_ip6_subnet; + + /* master interface for bridge/bond/team slave */ + NMDevice *master; + gulong master_ready_id; + int master_ifindex; + + /* slave management */ + CList slaves; /* list of SlaveInfo */ + + NMMetered metered; + + NMSettings *settings; + NMManager * manager; + + NMNetns *netns; + + NMLldpListener *lldp_listener; + + NMConnectivity *concheck_mgr; + CList concheck_lst_head; + struct { + /* if periodic checks are enabled, this is the source id for the next check. */ + guint p_cur_id; + + /* the currently configured max periodic interval. */ + guint p_max_interval; + + /* the current interval. If we are probing, the interval might be lower + * then the configured max interval. */ + guint p_cur_interval; + + /* the timestamp, when we last scheduled the timer p_cur_id with current interval + * p_cur_interval. */ + gint64 p_cur_basetime_ns; + + NMConnectivityState state; + } concheck_x[2]; + + guint check_delete_unrealized_id; + guint32 interface_flags; + + struct { + SriovOp *pending; /* SR-IOV operation currently running */ + SriovOp *next; /* next SR-IOV operation scheduled */ + } sriov; + guint sriov_reset_pending; + + struct { + guint timeout_id; + guint refresh_rate_ms; + guint64 tx_bytes; + guint64 rx_bytes; + } stats; + + bool mtu_force_set_done : 1; +} NMDevicePrivate; + +G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT) + +#define NM_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDevice, NM_IS_DEVICE) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_device; +static const GDBusSignalInfo signal_info_state_changed; + +static void nm_device_set_proxy_config(NMDevice *self, const char *pac_url); + +static gboolean update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs); + +static gboolean nm_device_set_ip_config(NMDevice * self, + int addr_family, + NMIPConfig *config, + gboolean commit, + GPtrArray * ip4_dev_route_blacklist); + +static gboolean ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit); + +static gboolean nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure); +static void nm_device_slave_notify_enslave(NMDevice *self, gboolean success); +static void nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason); + +static void addrconf6_start_with_link_ready(NMDevice *self); +static gboolean linklocal6_start(NMDevice *self); + +static guint32 default_route_metric_penalty_get(NMDevice *self, int addr_family); + +static guint _prop_get_ipv4_dad_timeout(NMDevice *self); + +static NMIP6Config *dad6_get_pending_addresses(NMDevice *self); + +static void _carrier_wait_check_queued_act_request(NMDevice *self); +static gint64 _get_carrier_wait_ms(NMDevice *self); + +static const char *_activation_func_to_string(ActivationHandleFunc func); + +static void +_set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting); +static void queued_state_clear(NMDevice *device); +static gboolean queued_ip4_config_change(gpointer user_data); +static gboolean queued_ip6_config_change(gpointer user_data); +static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data); +static gboolean ip_config_valid(NMDeviceState state); +static NMActStageReturn dhcp4_start(NMDevice *self); +static gboolean dhcp6_start(NMDevice *self, gboolean wait_for_ll); +static void nm_device_start_ip_check(NMDevice *self); +static void realize_start_setup(NMDevice * self, + const NMPlatformLink *plink, + gboolean assume_state_guess_assume, + const char * assume_state_connection_uuid, + gboolean set_nm_owned, + NMUnmanFlagOp unmanaged_user_explicit, + gboolean force_platform_init); +static void _set_mtu(NMDevice *self, guint32 mtu); +static void _commit_mtu(NMDevice *self, const NMIP4Config *config); +static void _cancel_activation(NMDevice *self); + +static void concheck_update_state(NMDevice * self, + int addr_family, + NMConnectivityState state, + gboolean is_periodic); + +static void sriov_op_cb(GError *error, gpointer user_data); + +static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self); +static gboolean device_link_changed(NMDevice *self); + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + queued_state_to_string, + NMDeviceState, + NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNMANAGED, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNAVAILABLE, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unavailable"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DISCONNECTED, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "disconnected"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_PREPARE, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "prepare"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_CONFIG, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "config"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_NEED_AUTH, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "need-auth"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CONFIG, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-config"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CHECK, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-check"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_SECONDARIES, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "secondaries"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_ACTIVATED, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "activated"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DEACTIVATING, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "deactivating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_FAILED, + NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), ); + +const char * +nm_device_state_to_str(NMDeviceState state) +{ + return queued_state_to_string(state) + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE); +} + +NM_UTILS_LOOKUP_STR_DEFINE( + nm_device_state_reason_to_str, + NMDeviceStateReason, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NONE, "none"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, "managed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, "unmanaged"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED, "config-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, "ip-config-unavailable"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED, "ip-config-expired"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS, "no-secrets"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, "supplicant-disconnect"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED, + "supplicant-config-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, "supplicant-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT, "supplicant-timeout"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED, "ppp-start-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, "ppp-disconnect"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, "ppp-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED, "dhcp-start-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, "dhcp-error"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, "dhcp-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED, "sharing-start-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED, "sharing-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED, "autoip-start-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, "autoip-error"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, "autoip-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, "modem-busy"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, "modem-no-dialtone"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, "modem-no-carrier"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT, "modem-dial-timeout"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, "modem-dial-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, "modem-init-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED, "gsm-apn-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING, + "gsm-registration-idle"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED, + "gsm-registration-denied"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT, + "gsm-registration-timeout"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED, + "gsm-registration-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, "gsm-pin-check-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING, "firmware-missing"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_REMOVED, "removed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, "sleeping"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, "connection-removed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED, "user-requested"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CARRIER, "carrier-changed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED, "connection-assumed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, "supplicant-available"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND, "modem-not-found"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED, "bluetooth-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED, "gsm-sim-not-inserted"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED, "gsm-sim-pin-required"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED, "gsm-sim-puk-required"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, "gsm-sim-wrong"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE, "infiniband-mode"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED, "dependency-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED, "br2684-bridge-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE, + "modem-manager-unavailable"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND, "ssid-not-found"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED, + "secondary-connection-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, "dcb-fcoe-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, "teamd-control-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED, "modem-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "modem-available"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "sim-pin-incorrect"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "new-activation"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, "parent-changed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED, + "parent-managed-changed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED, "ovsdb-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE, "ip-address-duplicate"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED, "ip-method-unsupported"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED, + "sriov-configuration-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), ); + +#define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, reason) + +static NM_UTILS_LOOKUP_STR_DEFINE(mtu_source_to_str, + NMDeviceMtuSource, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_PARENT, "parent"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_IP_CONFIG, + "ip-config"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION, + "connection"), ); + +/*****************************************************************************/ + +static void +_hostname_resolver_free(HostnameResolver *resolver) +{ + if (!resolver) + return; + + nm_clear_g_source(&resolver->timeout_id); + nm_clear_g_cancellable(&resolver->cancellable); + nm_g_object_unref(resolver->resolver); + nm_g_object_unref(resolver->address); + g_free(resolver->hostname); + nm_g_slice_free(resolver); +} + +/*****************************************************************************/ + +static NMSettingIP6ConfigPrivacy +_ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr) +{ + switch (use_tempaddr) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return use_tempaddr; + default: + return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + } +} + +/*****************************************************************************/ + +static const char * +_prop_get_connection_stable_id(NMDevice * self, + NMConnection * connection, + NMUtilsStableType *out_stable_type) +{ + NMDevicePrivate *priv; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(out_stable_type); + + priv = NM_DEVICE_GET_PRIVATE(self); + + /* we cache the generated stable ID for the time of an activation. + * + * The reason is, that we don't want the stable-id to change as long + * as the device is active. + * + * Especially with ${RANDOM} stable-id we want to generate *one* configuration + * for each activation. */ + if (G_UNLIKELY(!priv->current_stable_id)) { + gs_free char * default_id = NULL; + gs_free char * generated = NULL; + NMUtilsStableType stable_type; + NMSettingConnection *s_con; + gboolean hwaddr_is_fake; + const char * hwaddr; + const char * stable_id; + const char * uuid; + + s_con = nm_connection_get_setting_connection(connection); + + stable_id = nm_setting_connection_get_stable_id(s_con); + + if (!stable_id) { + default_id = + nm_config_data_get_connection_default(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.stable-id"), + self); + stable_id = default_id; + } + + uuid = nm_connection_get_uuid(connection); + + /* the cloned-mac-address may be generated based on the stable-id. + * Thus, at this point, we can only use the permanent MAC address + * as seed. */ + hwaddr = nm_device_get_permanent_hw_address_full(self, TRUE, &hwaddr_is_fake); + + stable_type = nm_utils_stable_id_parse(stable_id, + nm_device_get_ip_iface(self), + !hwaddr_is_fake ? hwaddr : NULL, + nm_utils_boot_id_str(), + uuid, + &generated); + + /* current_stable_id_type is a bitfield! */ + priv->current_stable_id_type = stable_type; + nm_assert(stable_type <= (NMUtilsStableType) 0x3); + nm_assert(stable_type + (NMUtilsStableType) 1 > (NMUtilsStableType) 0); + nm_assert(priv->current_stable_id_type == stable_type); + + if (stable_type == NM_UTILS_STABLE_TYPE_UUID) + priv->current_stable_id = g_strdup(uuid); + else if (stable_type == NM_UTILS_STABLE_TYPE_STABLE_ID) + priv->current_stable_id = g_strdup(stable_id); + else if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED) + priv->current_stable_id = + nm_str_realloc(nm_utils_stable_id_generated_complete(generated)); + else { + nm_assert(stable_type == NM_UTILS_STABLE_TYPE_RANDOM); + priv->current_stable_id = nm_str_realloc(nm_utils_stable_id_random()); + } + _LOGT(LOGD_DEVICE, + "stable-id: type=%d, \"%s\"" + "%s%s%s", + (int) priv->current_stable_id_type, + priv->current_stable_id, + NM_PRINT_FMT_QUOTED(stable_type == NM_UTILS_STABLE_TYPE_GENERATED, + " from \"", + generated, + "\"", + "")); + } + + nm_assert(priv->current_stable_id); + *out_stable_type = priv->current_stable_id_type; + return priv->current_stable_id; +} + +static GBytes * +_prop_get_ipv6_dhcp_duid(NMDevice * self, + NMConnection *connection, + GBytes * hwaddr, + gboolean * out_enforce) +{ + NMSettingIPConfig *s_ip6; + const char * duid; + gs_free char * duid_default = NULL; + const char * duid_error; + GBytes * duid_out; + gboolean duid_enforce = TRUE; + gs_free char * logstr1 = NULL; + const guint8 * hwaddr_bin; + gsize hwaddr_len; + int arp_type; + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + duid = nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6)); + + if (!duid) { + duid_default = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv6.dhcp-duid"), + self); + duid = duid_default; + if (!duid) + duid = "lease"; + } + + if (nm_streq(duid, "lease")) { + duid_enforce = FALSE; + duid_out = nm_utils_generate_duid_from_machine_id(); + goto out_good; + } + + if (!_nm_utils_dhcp_duid_valid(duid, &duid_out)) { + duid_error = "invalid duid"; + goto out_fail; + } + + if (duid_out) + goto out_good; + + if (NM_IN_STRSET(duid, "ll", "llt")) { + if (!hwaddr) { + duid_error = "missing link-layer address"; + goto out_fail; + } + + hwaddr_bin = g_bytes_get_data(hwaddr, &hwaddr_len); + arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len); + if (arp_type < 0) { + duid_error = "unsupported link-layer address"; + goto out_fail; + } + + if (nm_streq(duid, "ll")) + duid_out = nm_utils_generate_duid_ll(arp_type, hwaddr_bin, hwaddr_len); + else { + duid_out = nm_utils_generate_duid_llt(arp_type, + hwaddr_bin, + hwaddr_len, + nm_utils_host_id_get_timestamp_ns() + / NM_UTILS_NSEC_PER_SEC); + } + + goto out_good; + } + + if (NM_IN_STRSET(duid, "stable-ll", "stable-llt", "stable-uuid")) { + /* preferably, we would salt the checksum differently for each @duid type. We missed + * to do that initially, so most types use the DEFAULT_SALT. + * + * Implementations that are added later, should use a distinct salt instead, + * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */ + const guint32 DEFAULT_SALT = 670531087u; + nm_auto_free_checksum GChecksum *sum = NULL; + NMUtilsStableType stable_type; + const char * stable_id = NULL; + guint32 salted_header; + const guint8 * host_id; + gsize host_id_len; + union { + guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256]; + guint8 hwaddr_eth[ETH_ALEN]; + guint8 hwaddr_infiniband[INFINIBAND_ALEN]; + NMUuid uuid; + struct _nm_packed { + guint8 hwaddr[ETH_ALEN]; + guint32 timestamp; + } llt_eth; + struct _nm_packed { + guint8 hwaddr[INFINIBAND_ALEN]; + guint32 timestamp; + } llt_infiniband; + } digest; + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + + if (NM_IN_STRSET(duid, "stable-ll", "stable-llt")) { + /* for stable LL/LLT DUIDs, we still need a hardware address to detect + * the arp-type. Alternatively, we would be able to detect it based on + * other means (e.g. NMDevice type), but instead require the hardware + * address to be present. This is at least consistent with the "ll"/"llt" + * modes above. */ + if (!hwaddr) { + duid_error = "missing link-layer address"; + goto out_fail; + } + if ((arp_type = nm_utils_arp_type_detect_from_hwaddrlen(g_bytes_get_size(hwaddr))) + < 0) { + duid_error = "unsupported link-layer address"; + goto out_fail; + } + + if (arp_type == ARPHRD_ETHER) + salted_header = DEFAULT_SALT; + else { + nm_assert(arp_type == ARPHRD_INFINIBAND); + salted_header = 0x42492CEFu + ((guint32) arp_type); + } + } else { + salted_header = DEFAULT_SALT; + arp_type = -1; + } + + salted_header = htonl(salted_header + ((guint32) stable_type)); + + nm_utils_host_id_get(&host_id, &host_id_len); + + sum = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header)); + g_checksum_update(sum, (const guchar *) stable_id, -1); + g_checksum_update(sum, (const guchar *) host_id, host_id_len); + nm_utils_checksum_get_digest(sum, digest.sha256); + + G_STATIC_ASSERT_EXPR(sizeof(digest) == sizeof(digest.sha256)); + + if (nm_streq(duid, "stable-ll")) { + switch (arp_type) { + case ARPHRD_ETHER: + duid_out = nm_utils_generate_duid_ll(arp_type, + digest.hwaddr_eth, + sizeof(digest.hwaddr_eth)); + break; + case ARPHRD_INFINIBAND: + duid_out = nm_utils_generate_duid_ll(arp_type, + digest.hwaddr_infiniband, + sizeof(digest.hwaddr_infiniband)); + break; + default: + g_return_val_if_reached(NULL); + } + } else if (nm_streq(duid, "stable-llt")) { + gint64 time; + guint32 timestamp; + +#define EPOCH_DATETIME_THREE_YEARS (356 * 24 * 3600 * 3) + + /* We want a variable time between the host_id timestamp and three years + * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll + * subtract it from the host_id timestamp. + */ + time = nm_utils_host_id_get_timestamp_ns() / NM_UTILS_NSEC_PER_SEC; + + /* don't use too old timestamps. They cannot be expressed in DUID-LLT and + * would all be truncated to zero. */ + time = NM_MAX(time, NM_UTILS_EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS); + + switch (arp_type) { + case ARPHRD_ETHER: + timestamp = unaligned_read_be32(&digest.llt_eth.timestamp); + time -= timestamp % EPOCH_DATETIME_THREE_YEARS; + duid_out = nm_utils_generate_duid_llt(arp_type, + digest.llt_eth.hwaddr, + sizeof(digest.llt_eth.hwaddr), + time); + break; + case ARPHRD_INFINIBAND: + timestamp = unaligned_read_be32(&digest.llt_infiniband.timestamp); + time -= timestamp % EPOCH_DATETIME_THREE_YEARS; + duid_out = nm_utils_generate_duid_llt(arp_type, + digest.llt_infiniband.hwaddr, + sizeof(digest.llt_infiniband.hwaddr), + time); + break; + default: + g_return_val_if_reached(NULL); + } + } else { + nm_assert(nm_streq(duid, "stable-uuid")); + duid_out = nm_utils_generate_duid_uuid(&digest.uuid); + } + + goto out_good; + } + + g_return_val_if_reached(NULL); + +out_fail: + nm_assert(!duid_out && duid_error); + { + NMUuid uuid; + + _LOGW(LOGD_IP6 | LOGD_DHCP6, + "ipv6.dhcp-duid: failure to generate %s DUID: %s. Fallback to random DUID-UUID.", + duid, + duid_error); + + nm_utils_random_bytes(&uuid, sizeof(uuid)); + duid_out = nm_utils_generate_duid_uuid(&uuid); + } + +out_good: + nm_assert(duid_out); + _LOGD(LOGD_IP6 | LOGD_DHCP6, + "ipv6.dhcp-duid: generate %s DUID '%s' (%s)", + duid, + (logstr1 = nm_dhcp_utils_duid_to_string(duid_out)), + duid_enforce ? "enforcing" : "prefer lease"); + + NM_SET_OUT(out_enforce, duid_enforce); + return duid_out; +} + +static guint32 +_prop_get_ipv6_ra_timeout(NMDevice *self) +{ + NMConnection *connection; + gint32 timeout; + + G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_DEFAULT == 0); + G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_INFINITY == G_MAXINT32); + + connection = nm_device_get_applied_connection(self); + + timeout = nm_setting_ip6_config_get_ra_timeout( + NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection))); + if (timeout > 0) + return timeout; + nm_assert(timeout == 0); + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv6.ra-timeout"), + self, + 0, + G_MAXINT32, + 0); +} + +static NMSettingConnectionMdns +_prop_get_connection_mdns(NMDevice *self) +{ + NMConnection * connection; + NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_MDNS_DEFAULT); + + connection = nm_device_get_applied_connection(self); + if (connection) + mdns = nm_setting_connection_get_mdns(nm_connection_get_setting_connection(connection)); + if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) + return mdns; + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.mdns"), + self, + NM_SETTING_CONNECTION_MDNS_NO, + NM_SETTING_CONNECTION_MDNS_YES, + NM_SETTING_CONNECTION_MDNS_DEFAULT); +} + +static NMSettingConnectionLlmnr +_prop_get_connection_llmnr(NMDevice *self) +{ + NMConnection * connection; + NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT; + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_LLMNR_DEFAULT); + + connection = nm_device_get_applied_connection(self); + if (connection) + llmnr = nm_setting_connection_get_llmnr(nm_connection_get_setting_connection(connection)); + if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) + return llmnr; + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.llmnr"), + self, + NM_SETTING_CONNECTION_LLMNR_NO, + NM_SETTING_CONNECTION_LLMNR_YES, + NM_SETTING_CONNECTION_LLMNR_DEFAULT); +} + +static guint32 +_prop_get_ipvx_route_table(NMDevice *self, int addr_family) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceClass * klass; + NMConnection * connection; + NMSettingIPConfig * s_ip; + guint32 route_table = 0; + gboolean is_user_config = TRUE; + NMSettingConnection *s_con; + NMSettingVrf * s_vrf; + + nm_assert_addr_family(addr_family); + + /* the route table setting affects how we sync routes. We shall + * not change it while the device is active, hence, cache it. */ + if (NM_IS_IPv4(addr_family)) { + if (priv->v4_route_table_initialized) + return priv->v4_route_table; + } else { + if (priv->v6_route_table_initialized) + return priv->v6_route_table; + } + + connection = nm_device_get_applied_connection(self); + if (connection) { + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (s_ip) + route_table = nm_setting_ip_config_get_route_table(s_ip); + } + if (route_table == 0u) { + gint64 v; + + v = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_IS_IPv4(addr_family) + ? NM_CON_DEFAULT("ipv4.route-table") + : NM_CON_DEFAULT("ipv6.route-table"), + self, + 0, + G_MAXUINT32, + -1); + if (v != -1) { + route_table = v; + is_user_config = FALSE; + } + } + + if (route_table == 0u && connection + && (s_con = nm_connection_get_setting_connection(connection)) + && (nm_streq0(nm_setting_connection_get_slave_type(s_con), NM_SETTING_VRF_SETTING_NAME) + && priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF)) { + const NMPlatformLnkVrf *lnk; + + lnk = nm_platform_link_get_lnk_vrf(nm_device_get_platform(self), + nm_device_get_ifindex(priv->master), + NULL); + + if (lnk) + route_table = lnk->table; + } + + if (route_table == 0u && connection + && (s_vrf = (NMSettingVrf *) nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF))) { + route_table = nm_setting_vrf_get_table(s_vrf); + } + + klass = NM_DEVICE_GET_CLASS(self); + if (klass->coerce_route_table) + route_table = klass->coerce_route_table(self, addr_family, route_table, is_user_config); + + if (NM_IS_IPv4(addr_family)) { + priv->v4_route_table_initialized = TRUE; + priv->v4_route_table = route_table; + } else { + priv->v6_route_table_initialized = TRUE; + priv->v6_route_table = route_table; + } + + _LOGT(LOGD_DEVICE, + "ipv%c.route-table = %u%s", + nm_utils_addr_family_to_char(addr_family), + (guint)(route_table ?: RT_TABLE_MAIN), + route_table != 0u ? "" : " (policy routing not enabled)"); + + return route_table; +} + +static gboolean +_prop_get_connection_lldp(NMDevice *self) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingConnectionLldp lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT; + + connection = nm_device_get_applied_connection(self); + g_return_val_if_fail(connection, FALSE); + + s_con = nm_connection_get_setting_connection(connection); + g_return_val_if_fail(s_con, FALSE); + + lldp = nm_setting_connection_get_lldp(s_con); + if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) { + lldp = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.lldp"), + self, + NM_SETTING_CONNECTION_LLDP_DEFAULT, + NM_SETTING_CONNECTION_LLDP_ENABLE_RX, + NM_SETTING_CONNECTION_LLDP_DEFAULT); + if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) + lldp = NM_SETTING_CONNECTION_LLDP_DISABLE; + } + return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX; +} + +static guint +_prop_get_ipv4_dad_timeout(NMDevice *self) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4 = NULL; + int timeout = -1; + + connection = nm_device_get_applied_connection(self); + if (connection) + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (s_ip4) + timeout = nm_setting_ip_config_get_dad_timeout(s_ip4); + if (timeout >= 0) + return timeout; + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv4.dad-timeout"), + self, + 0, + NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, + 0); +} + +static guint32 +_prop_get_ipvx_dhcp_timeout(NMDevice *self, int addr_family) +{ + NMDeviceClass *klass; + NMConnection * connection; + int timeout_i; + guint32 timeout; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert_addr_family(addr_family); + + connection = nm_device_get_applied_connection(self); + + timeout_i = nm_setting_ip_config_get_dhcp_timeout( + nm_connection_get_setting_ip_config(connection, addr_family)); + nm_assert(timeout_i >= 0 && timeout_i <= G_MAXINT32); + + timeout = (guint32) timeout_i; + if (timeout) + goto out; + + timeout = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_IS_IPv4(addr_family) + ? NM_CON_DEFAULT("ipv4.dhcp-timeout") + : NM_CON_DEFAULT("ipv6.dhcp-timeout"), + self, + 0, + G_MAXINT32, + 0); + if (timeout) + goto out; + + klass = NM_DEVICE_GET_CLASS(self); + if (klass->get_dhcp_timeout_for_device) { + timeout = klass->get_dhcp_timeout_for_device(self, addr_family); + if (timeout) + goto out; + } + + timeout = NM_DHCP_TIMEOUT_DEFAULT; + +out: + G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_DHCP_TIMEOUT_INFINITY); + nm_assert(timeout > 0); + nm_assert(timeout <= G_MAXINT32); + return timeout; +} + +/** + * _prop_get_ipvx_dhcp_iaid: + * @self: the #NMDevice + * @addr_family: the address family + * @connection: the connection + * @log_silent: whether to log the result. + * @out_is_explicit: on return, %TRUE if the user set a valid IAID in + * the connection or in global configuration; %FALSE if the connection + * property was empty and no valid global configuration was provided. + * + * Returns: a IAID value for this device and the given connection. + */ +static guint32 +_prop_get_ipvx_dhcp_iaid(NMDevice * self, + int addr_family, + NMConnection *connection, + gboolean log_silent, + gboolean * out_is_explicit) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMSettingIPConfig *s_ip; + const char * iaid_str; + gs_free char * iaid_str_free = NULL; + guint32 iaid; + const char * iface; + const char * fail_reason; + gboolean is_explicit = TRUE; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + iaid_str = nm_setting_ip_config_get_dhcp_iaid(s_ip); + if (!iaid_str) { + iaid_str_free = nm_config_data_get_connection_default( + NM_CONFIG_GET_DATA, + IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-iaid") : NM_CON_DEFAULT("ipv6.dhcp-iaid"), + self); + iaid_str = iaid_str_free; + if (!iaid_str) { + iaid_str = NM_IAID_IFNAME; + is_explicit = FALSE; + } else if (!_nm_utils_iaid_verify(iaid_str, NULL)) { + if (!log_silent) { + _LOGW(LOGD_DEVICE, + "invalid global default '%s' for ipv%c.dhcp-iaid", + iaid_str, + nm_utils_addr_family_to_char(addr_family)); + } + iaid_str = NM_IAID_IFNAME; + is_explicit = FALSE; + } + } + + if (nm_streq0(iaid_str, NM_IAID_MAC)) { + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self)); + if (!pllink || pllink->l_address.len < 4) { + fail_reason = "invalid link-layer address"; + goto out_fail; + } + + /* @iaid is in native endianness. Use unaligned_read_be32() + * so that the IAID for a given MAC address is the same on + * BE and LE machines. */ + iaid = unaligned_read_be32(&pllink->l_address.data[pllink->l_address.len - 4]); + goto out_good; + } else if (nm_streq0(iaid_str, NM_IAID_PERM_MAC)) { + guint8 hwaddr_buf[NM_UTILS_HWADDR_LEN_MAX]; + const char *hwaddr_str; + gsize hwaddr_len; + + hwaddr_str = nm_device_get_permanent_hw_address(self); + if (!hwaddr_str) { + fail_reason = "no permanent link-layer address"; + goto out_fail; + } + + if (!_nm_utils_hwaddr_aton(hwaddr_str, hwaddr_buf, sizeof(hwaddr_buf), &hwaddr_len)) + g_return_val_if_reached(0); + + if (hwaddr_len < 4) { + fail_reason = "invalid link-layer address"; + goto out_fail; + } + + iaid = unaligned_read_be32(&hwaddr_buf[hwaddr_len - 4]); + goto out_good; + } else if (nm_streq(iaid_str, "stable")) { + nm_auto_free_checksum GChecksum *sum = NULL; + guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + NMUtilsStableType stable_type; + const char * stable_id; + guint32 salted_header; + const guint8 * host_id; + gsize host_id_len; + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + salted_header = htonl(53390459 + stable_type); + nm_utils_host_id_get(&host_id, &host_id_len); + iface = nm_device_get_ip_iface(self); + + sum = g_checksum_new(G_CHECKSUM_SHA1); + g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header)); + g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1); + g_checksum_update(sum, (const guchar *) iface, strlen(iface) + 1); + g_checksum_update(sum, (const guchar *) host_id, host_id_len); + nm_utils_checksum_get_digest(sum, digest); + + iaid = unaligned_read_be32(digest); + goto out_good; + } else if ((iaid = _nm_utils_ascii_str_to_int64(iaid_str, 10, 0, G_MAXUINT32, -1)) != -1) { + goto out_good; + } else { + iface = nm_device_get_ip_iface(self); + iaid = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface)); + goto out_good; + } + +out_fail: + nm_assert(fail_reason); + if (!log_silent) { + _LOGW(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4), + "ipv%c.dhcp-iaid: failure to generate IAID: %s. Using interface-name based IAID", + nm_utils_addr_family_to_char(addr_family), + fail_reason); + } + is_explicit = FALSE; + iface = nm_device_get_ip_iface(self); + iaid = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface)); +out_good: + if (!log_silent) { + _LOGD(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4), + "ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)", + nm_utils_addr_family_to_char(addr_family), + iaid, + iaid, + iaid_str, + is_explicit); + } + NM_SET_OUT(out_is_explicit, is_explicit); + return iaid; +} + +static NMDhcpHostnameFlags +_prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family) +{ + NMConnection * connection; + NMSettingIPConfig * s_ip; + NMDhcpHostnameFlags flags; + gs_free_error GError *error = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_DHCP_HOSTNAME_FLAG_NONE); + + connection = nm_device_get_applied_connection(self); + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + g_return_val_if_fail(s_ip, NM_DHCP_HOSTNAME_FLAG_NONE); + + if (!nm_setting_ip_config_get_dhcp_send_hostname(s_ip)) + return NM_DHCP_HOSTNAME_FLAG_NONE; + + flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip); + if (flags != NM_DHCP_HOSTNAME_FLAG_NONE) + return flags; + + flags = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.dhcp-hostname-flags") + : NM_CON_DEFAULT("ipv6.dhcp-hostname-flags"), + self, + 0, + NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS, + 0); + + if (!_nm_utils_validate_dhcp_hostname_flags(flags, addr_family, &error)) { + _LOGW(LOGD_DEVICE, + "invalid global default value 0x%x for ipv%c.%s: %s", + (guint) flags, + nm_utils_addr_family_to_char(addr_family), + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, + error->message); + flags = NM_DHCP_HOSTNAME_FLAG_NONE; + } + + if (flags != NM_DHCP_HOSTNAME_FLAG_NONE) + return flags; + + if (NM_IS_IPv4(addr_family)) + return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4; + else + return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6; +} + +static const char * +_prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con, char **out_mud_url) +{ + const char * mud_url; + gs_free char *s = NULL; + + nm_assert(out_mud_url && !*out_mud_url); + + mud_url = nm_setting_connection_get_mud_url(s_con); + + if (mud_url) { + if (nm_streq(mud_url, NM_CONNECTION_MUD_URL_NONE)) + return NULL; + return mud_url; + } + + s = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.mud-url"), + self); + if (s) { + if (nm_streq(s, NM_CONNECTION_MUD_URL_NONE)) + return NULL; + if (nm_sd_http_url_is_valid_https(s)) + return (*out_mud_url = g_steal_pointer(&s)); + } + + return NULL; +} + +static GBytes * +_prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *hwaddr) +{ + NMSettingIPConfig *s_ip4; + const char * client_id; + gs_free char * client_id_default = NULL; + guint8 * client_id_buf; + const char * fail_reason; + guint8 hwaddr_bin_buf[NM_UTILS_HWADDR_LEN_MAX]; + const guint8 * hwaddr_bin; + int arp_type; + gsize hwaddr_len; + GBytes * result; + gs_free char * logstr1 = NULL; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + client_id = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4)); + + if (!client_id) { + client_id_default = + nm_config_data_get_connection_default(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv4.dhcp-client-id"), + self); + if (client_id_default && client_id_default[0]) { + /* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes(). */ + client_id = client_id_default; + } + } + + if (!client_id) { + _LOGD(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: no explicit client-id configured"); + return NULL; + } + + if (nm_streq(client_id, "mac")) { + if (!hwaddr) { + fail_reason = "missing link-layer address"; + goto out_fail; + } + + hwaddr_bin = g_bytes_get_data(hwaddr, &hwaddr_len); + arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len); + if (arp_type < 0) { + fail_reason = "unsupported link-layer address"; + goto out_fail; + } + + result = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_bin, hwaddr_len); + goto out_good; + } + + if (nm_streq(client_id, "perm-mac")) { + const char *hwaddr_str; + + hwaddr_str = nm_device_get_permanent_hw_address(self); + if (!hwaddr_str) { + fail_reason = "missing permanent link-layer address"; + goto out_fail; + } + + if (!_nm_utils_hwaddr_aton(hwaddr_str, hwaddr_bin_buf, sizeof(hwaddr_bin_buf), &hwaddr_len)) + g_return_val_if_reached(NULL); + + arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len); + if (arp_type < 0) { + fail_reason = "unsupported permanent link-layer address"; + goto out_fail; + } + + result = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_bin_buf, hwaddr_len); + goto out_good; + } + + if (nm_streq(client_id, "duid")) { + guint32 iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, NULL); + + result = nm_utils_dhcp_client_id_systemd_node_specific(iaid); + goto out_good; + } + + if (nm_streq(client_id, "ipv6-duid")) { + gs_unref_bytes GBytes *duid = NULL; + gboolean iaid_is_explicit; + guint32 iaid; + const guint8 * duid_arr; + gsize duid_len; + + iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, &iaid_is_explicit); + if (!iaid_is_explicit) + iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_is_explicit); + + duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, NULL); + + nm_assert(duid); + + duid_arr = g_bytes_get_data(duid, &duid_len); + + nm_assert(duid_arr); + nm_assert(duid_len >= 2u + 1u); + nm_assert(duid_len <= 2u + 128u); + + result = nm_utils_dhcp_client_id_duid(iaid, duid_arr, duid_len); + goto out_good; + } + + if (nm_streq(client_id, "stable")) { + nm_auto_free_checksum GChecksum *sum = NULL; + guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + NMUtilsStableType stable_type; + const char * stable_id; + guint32 salted_header; + const guint8 * host_id; + gsize host_id_len; + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + salted_header = htonl(2011610591 + stable_type); + nm_utils_host_id_get(&host_id, &host_id_len); + + sum = g_checksum_new(G_CHECKSUM_SHA1); + g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header)); + g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1); + g_checksum_update(sum, (const guchar *) host_id, host_id_len); + nm_utils_checksum_get_digest(sum, digest); + + client_id_buf = g_malloc(1 + 15); + client_id_buf[0] = 0; + memcpy(&client_id_buf[1], digest, 15); + result = g_bytes_new_take(client_id_buf, 1 + 15); + goto out_good; + } + + result = nm_dhcp_utils_client_id_string_to_bytes(client_id); + goto out_good; + +out_fail: + nm_assert(fail_reason); + _LOGW(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: failure to generate client id (%s). Use random client id", + fail_reason); + client_id_buf = g_malloc(1 + 15); + client_id_buf[0] = 0; + nm_utils_random_bytes(&client_id_buf[1], 15); + result = g_bytes_new_take(client_id_buf, 1 + 15); + +out_good: + nm_assert(result); + _LOGD(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: use \"%s\" client ID: %s", + client_id, + (logstr1 = nm_dhcp_utils_duid_to_string(result))); + return result; +} + +static GBytes * +_prop_get_ipv4_dhcp_vendor_class_identifier(NMDevice *self, NMSettingIP4Config *s_ip4) +{ + gs_free char *config_data_prop = NULL; + gs_free char *to_free = NULL; + const char * conn_prop; + GBytes * bytes = NULL; + const char * bin; + gsize len; + + conn_prop = nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4); + + if (!conn_prop) { + /* set in NetworkManager.conf ? */ + config_data_prop = nm_config_data_get_connection_default( + NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv4.dhcp-vendor-class-identifier"), + self); + + if (config_data_prop && nm_utils_validate_dhcp4_vendor_class_id(config_data_prop, NULL)) + conn_prop = config_data_prop; + } + + if (conn_prop) { + bin = nm_utils_buf_utf8safe_unescape(conn_prop, + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, + &len, + (gpointer *) &to_free); + if (to_free) + bytes = g_bytes_new_take(g_steal_pointer(&to_free), len); + else + bytes = g_bytes_new(bin, len); + } + + return bytes; +} + +static NMSettingIP6ConfigPrivacy +_prop_get_ipv6_ip6_privacy(NMDevice *self) +{ + NMSettingIP6ConfigPrivacy ip6_privacy; + NMConnection * connection; + + g_return_val_if_fail(self, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + + /* 1.) First look at the per-connection setting. If it is not -1 (unknown), + * use it. */ + connection = nm_device_get_applied_connection(self); + if (connection) { + NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config(connection); + + if (s_ip6) { + ip6_privacy = nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)); + ip6_privacy = _ip6_privacy_clamp(ip6_privacy); + if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) + return ip6_privacy; + } + } + + /* 2.) use the default value from the configuration. */ + ip6_privacy = + nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv6.ip6-privacy"), + self, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) + return ip6_privacy; + + if (!nm_device_get_ip_ifindex(self)) + return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* 3.) No valid default-value configured. Fallback to reading sysctl. + * + * Instead of reading static config files in /etc, just read the current sysctl value. + * This works as NM only writes to "/proc/sys/net/ipv6/conf/IFNAME/use_tempaddr", but leaves + * the "default" entry untouched. */ + ip6_privacy = nm_platform_sysctl_get_int32( + nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/default/use_tempaddr"), + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + return _ip6_privacy_clamp(ip6_privacy); +} + +static const char * +_prop_get_x_cloned_mac_address(NMDevice * self, + NMConnection *connection, + gboolean is_wifi, + char ** out_addr) +{ + NMSetting * setting; + const char *addr = NULL; + + nm_assert(out_addr && !*out_addr); + + setting = nm_connection_get_setting(connection, + is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED); + if (setting) { + addr = is_wifi ? nm_setting_wireless_get_cloned_mac_address((NMSettingWireless *) setting) + : nm_setting_wired_get_cloned_mac_address((NMSettingWired *) setting); + } + + if (!addr) { + gs_free char *a = NULL; + + a = nm_config_data_get_connection_default( + NM_CONFIG_GET_DATA, + is_wifi ? NM_CON_DEFAULT("wifi.cloned-mac-address") + : NM_CON_DEFAULT("ethernet.cloned-mac-address"), + self); + + addr = NM_CLONED_MAC_PRESERVE; + + if (!a) { + if (is_wifi) { + NMSettingMacRandomization v; + + /* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */ + a = nm_config_data_get_connection_default( + NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("wifi.mac-address-randomization"), + self); + v = _nm_utils_ascii_str_to_int64(a, + 10, + NM_SETTING_MAC_RANDOMIZATION_DEFAULT, + NM_SETTING_MAC_RANDOMIZATION_ALWAYS, + NM_SETTING_MAC_RANDOMIZATION_DEFAULT); + if (v == NM_SETTING_MAC_RANDOMIZATION_ALWAYS) + addr = NM_CLONED_MAC_RANDOM; + } + } else if (NM_CLONED_MAC_IS_SPECIAL(a) || nm_utils_hwaddr_valid(a, ETH_ALEN)) + addr = *out_addr = g_steal_pointer(&a); + } + + return addr; +} + +static const char * +_prop_get_x_generate_mac_address_mask(NMDevice * self, + NMConnection *connection, + gboolean is_wifi, + char ** out_value) +{ + NMSetting * setting; + const char *value = NULL; + char * a; + + nm_assert(out_value && !*out_value); + + setting = nm_connection_get_setting(connection, + is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED); + if (setting) { + value = + is_wifi + ? nm_setting_wireless_get_generate_mac_address_mask((NMSettingWireless *) setting) + : nm_setting_wired_get_generate_mac_address_mask((NMSettingWired *) setting); + if (value) + return value; + } + + a = nm_config_data_get_connection_default( + NM_CONFIG_GET_DATA, + is_wifi ? NM_CON_DEFAULT("wifi.generate-mac-address-mask") + : NM_CON_DEFAULT("ethernet.generate-mac-address-mask"), + self); + if (!a) + return NULL; + *out_value = a; + return a; +} + +/*****************************************************************************/ + +static void +_ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state) +{ + gs_free NMEthtoolFeatureStates *features; + + features = g_steal_pointer(ðtool_state->features); + + if (!nm_platform_ethtool_set_features(platform, + ethtool_state->ifindex, + features, + ethtool_state->requested, + FALSE)) + _LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more offload features"); + else + _LOGD(LOGD_DEVICE, "ethtool: offload features successfully reset"); +} + +static void +_ethtool_features_set(NMDevice * self, + NMPlatform * platform, + EthtoolState * ethtool_state, + NMSettingEthtool *s_ethtool) +{ + gs_free NMEthtoolFeatureStates *features = NULL; + + if (ethtool_state->features) + _ethtool_features_reset(self, platform, ethtool_state); + + if (nm_setting_ethtool_init_features(s_ethtool, ethtool_state->requested) == 0) + return; + + features = nm_platform_ethtool_get_link_features(platform, ethtool_state->ifindex); + if (!features) { + _LOGW(LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)"); + return; + } + + if (!nm_platform_ethtool_set_features(platform, + ethtool_state->ifindex, + features, + ethtool_state->requested, + TRUE)) + _LOGW(LOGD_DEVICE, "ethtool: failure setting one or more offload features"); + else + _LOGD(LOGD_DEVICE, "ethtool: offload features successfully set"); + + ethtool_state->features = g_steal_pointer(&features); +} + +static void +_ethtool_coalesce_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state) +{ + gs_free NMEthtoolCoalesceState *coalesce = NULL; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(ethtool_state); + + coalesce = g_steal_pointer(ðtool_state->coalesce); + if (!coalesce) + return; + + if (!nm_platform_ethtool_set_coalesce(platform, ethtool_state->ifindex, coalesce)) + _LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more coalesce settings"); + else + _LOGD(LOGD_DEVICE, "ethtool: coalesce settings successfully reset"); +} + +static void +_ethtool_coalesce_set(NMDevice * self, + NMPlatform * platform, + EthtoolState * ethtool_state, + NMSettingEthtool *s_ethtool) +{ + NMEthtoolCoalesceState coalesce_old; + NMEthtoolCoalesceState coalesce_new; + gboolean has_old = FALSE; + GHashTable * hash; + GHashTableIter iter; + const char * name; + GVariant * variant; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool)); + nm_assert(ethtool_state); + nm_assert(!ethtool_state->coalesce); + + hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE); + if (!hash) + return; + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { + NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name); + + if (!nm_ethtool_id_is_coalesce(ethtool_id)) + continue; + + if (!has_old) { + if (!nm_platform_ethtool_get_link_coalesce(platform, + ethtool_state->ifindex, + &coalesce_old)) { + _LOGW(LOGD_DEVICE, "ethtool: failure getting coalesce settings (cannot read)"); + return; + } + has_old = TRUE; + coalesce_new = coalesce_old; + } + + nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); + coalesce_new.s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(ethtool_id)] = g_variant_get_uint32(variant); + } + + if (!has_old) + return; + + ethtool_state->coalesce = nm_memdup(&coalesce_old, sizeof(coalesce_old)); + + if (!nm_platform_ethtool_set_coalesce(platform, ethtool_state->ifindex, &coalesce_new)) { + _LOGW(LOGD_DEVICE, "ethtool: failure setting coalesce settings"); + return; + } + + _LOGD(LOGD_DEVICE, "ethtool: coalesce settings successfully set"); +} + +static void +_ethtool_ring_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state) +{ + gs_free NMEthtoolRingState *ring = NULL; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(ethtool_state); + + ring = g_steal_pointer(ðtool_state->ring); + if (!ring) + return; + + if (!nm_platform_ethtool_set_ring(platform, ethtool_state->ifindex, ring)) + _LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more ring settings"); + else + _LOGD(LOGD_DEVICE, "ethtool: ring settings successfully reset"); +} + +static void +_ethtool_ring_set(NMDevice * self, + NMPlatform * platform, + EthtoolState * ethtool_state, + NMSettingEthtool *s_ethtool) +{ + NMEthtoolRingState ring_old; + NMEthtoolRingState ring_new; + GHashTable * hash; + GHashTableIter iter; + const char * name; + GVariant * variant; + gboolean has_old = FALSE; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool)); + nm_assert(ethtool_state); + nm_assert(!ethtool_state->ring); + + hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE); + if (!hash) + return; + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { + NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name); + guint32 u32; + + if (!nm_ethtool_id_is_ring(ethtool_id)) + continue; + + nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); + + if (!has_old) { + if (!nm_platform_ethtool_get_link_ring(platform, ethtool_state->ifindex, &ring_old)) { + _LOGW(LOGD_DEVICE, + "ethtool: failure setting ring options (cannot read existing setting)"); + return; + } + has_old = TRUE; + ring_new = ring_old; + } + + u32 = g_variant_get_uint32(variant); + + switch (ethtool_id) { + case NM_ETHTOOL_ID_RING_RX: + ring_new.rx_pending = u32; + break; + case NM_ETHTOOL_ID_RING_RX_JUMBO: + ring_new.rx_jumbo_pending = u32; + break; + case NM_ETHTOOL_ID_RING_RX_MINI: + ring_new.rx_mini_pending = u32; + break; + case NM_ETHTOOL_ID_RING_TX: + ring_new.tx_pending = u32; + break; + default: + nm_assert_not_reached(); + } + } + + if (!has_old) + return; + + ethtool_state->ring = nm_memdup(&ring_old, sizeof(ring_old)); + + if (!nm_platform_ethtool_set_ring(platform, ethtool_state->ifindex, &ring_new)) { + _LOGW(LOGD_DEVICE, "ethtool: failure setting ring settings"); + return; + } + + _LOGD(LOGD_DEVICE, "ethtool: ring settings successfully set"); +} + +static void +_ethtool_state_reset(NMDevice *self) +{ + NMPlatform * platform = nm_device_get_platform(self); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free EthtoolState *ethtool_state = g_steal_pointer(&priv->ethtool_state); + + if (!ethtool_state) + return; + + if (ethtool_state->features) + _ethtool_features_reset(self, platform, ethtool_state); + if (ethtool_state->coalesce) + _ethtool_coalesce_reset(self, platform, ethtool_state); + if (ethtool_state->ring) + _ethtool_ring_reset(self, platform, ethtool_state); +} + +static void +_ethtool_state_set(NMDevice *self) +{ + int ifindex; + NMPlatform * platform; + NMConnection * connection; + NMSettingEthtool *s_ethtool; + gs_free EthtoolState *ethtool_state = NULL; + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + return; + + platform = nm_device_get_platform(self); + nm_assert(platform); + + connection = nm_device_get_applied_connection(self); + if (!connection) + return; + + s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(connection, NM_TYPE_SETTING_ETHTOOL)); + if (!s_ethtool) + return; + + ethtool_state = g_new0(EthtoolState, 1); + ethtool_state->ifindex = ifindex; + + _ethtool_features_set(self, platform, ethtool_state, s_ethtool); + _ethtool_coalesce_set(self, platform, ethtool_state, s_ethtool); + _ethtool_ring_set(self, platform, ethtool_state, s_ethtool); + + if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring) + priv->ethtool_state = g_steal_pointer(ðtool_state); +} + +/*****************************************************************************/ + +static gboolean +is_loopback(NMDevice *self) +{ + return NM_IS_DEVICE_GENERIC(self) && NM_DEVICE_GET_PRIVATE(self)->ifindex == 1; +} + +gboolean +nm_device_is_vpn(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + /* NetworkManager currently treats VPN connections (loaded from NetworkManager VPN plugins) + * differently. Those are considered VPNs. + * However, some native device types may also be considered VPNs... + * + * We should avoid distinguishing between is-vpn and "regular" devices. Is an (unencrypted) + * IP tunnel a VPN? Is MACSec on top of an IP tunnel a VPN? + * Sometimes we differentiate, but avoid unless reasonable. */ + + return NM_IS_DEVICE_WIREGUARD(self); +} + +NMSettings * +nm_device_get_settings(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->settings; +} + +NMManager * +nm_device_get_manager(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->manager; +} + +NMNetns * +nm_device_get_netns(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->netns; +} + +NMDedupMultiIndex * +nm_device_get_multi_index(NMDevice *self) +{ + return nm_netns_get_multi_idx(nm_device_get_netns(self)); +} + +NMPlatform * +nm_device_get_platform(NMDevice *self) +{ + return nm_netns_get_platform(nm_device_get_netns(self)); +} + +static NMConnectivity * +concheck_get_mgr(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->concheck_mgr)) + priv->concheck_mgr = g_object_ref(nm_connectivity_get()); + return priv->concheck_mgr; +} + +NMIP4Config * +nm_device_ip4_config_new(NMDevice *self) +{ + return nm_ip4_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self)); +} + +NMIP6Config * +nm_device_ip6_config_new(NMDevice *self) +{ + return nm_ip6_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self)); +} + +NMIPConfig * +nm_device_ip_config_new(NMDevice *self, int addr_family) +{ + nm_assert_addr_family(addr_family); + + return NM_IS_IPv4(addr_family) ? (gpointer) nm_device_ip4_config_new(self) + : (gpointer) nm_device_ip6_config_new(self); +} + +NML3ConfigData * +nm_device_create_l3_config_data(NMDevice *self) +{ + int ifindex; + + nm_assert(NM_IS_DEVICE(self)); + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + g_return_val_if_reached(NULL); + + return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex); +} + +static void +applied_config_clear(AppliedConfig *config) +{ + g_clear_object(&config->current); + g_clear_object(&config->orig); +} + +static void +applied_config_init(AppliedConfig *config, gpointer ip_config) +{ + nm_assert(!ip_config || (!config->orig && !config->current) + || nm_ip_config_get_addr_family(ip_config) + == nm_ip_config_get_addr_family(config->orig ?: config->current)); + nm_assert(!ip_config || NM_IS_IP_CONFIG(ip_config)); + + nm_g_object_ref(ip_config); + applied_config_clear(config); + config->orig = ip_config; +} + +static void +applied_config_init_new(AppliedConfig *config, NMDevice *self, int addr_family) +{ + gs_unref_object NMIPConfig *c = nm_device_ip_config_new(self, addr_family); + + applied_config_init(config, c); +} + +static NMIPConfig * +applied_config_get_current(AppliedConfig *config) +{ + return config->current ?: config->orig; +} + +static void +applied_config_add_address(AppliedConfig *config, const NMPlatformIPAddress *address) +{ + if (config->orig) + nm_ip_config_add_address(config->orig, address); + else + nm_assert(!config->current); + + if (config->current) + nm_ip_config_add_address(config->current, address); +} + +static void +applied_config_add_nameserver(AppliedConfig *config, const NMIPAddr *ns) +{ + if (config->orig) + nm_ip_config_add_nameserver(config->orig, ns); + else + nm_assert(!config->current); + + if (config->current) + nm_ip_config_add_nameserver(config->current, ns); +} + +static void +applied_config_add_search(AppliedConfig *config, const char *new) +{ + if (config->orig) + nm_ip_config_add_search(config->orig, new); + else + nm_assert(!config->current); + + if (config->current) + nm_ip_config_add_search(config->current, new); +} + +static void +applied_config_reset_searches(AppliedConfig *config) +{ + if (config->orig) + nm_ip_config_reset_searches(config->orig); + else + nm_assert(!config->current); + + if (config->current) + nm_ip_config_reset_searches(config->current); +} + +static void +applied_config_reset_nameservers(AppliedConfig *config) +{ + if (config->orig) + nm_ip_config_reset_nameservers(config->orig); + else + nm_assert(!config->current); + + if (config->current) + nm_ip_config_reset_nameservers(config->current); +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _sys_iface_state_to_str, + NMDeviceSysIfaceState, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_ASSUME, "assume"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_MANAGED, "managed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED, "removed"), ); + +NMDeviceSysIfaceState +nm_device_sys_iface_state_get(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + + return NM_DEVICE_GET_PRIVATE(self)->sys_iface_state; +} + +gboolean +nm_device_sys_iface_state_is_external(NMDevice *self) +{ + return NM_IN_SET(nm_device_sys_iface_state_get(self), NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); +} + +gboolean +nm_device_sys_iface_state_is_external_or_assume(NMDevice *self) +{ + return NM_IN_SET(nm_device_sys_iface_state_get(self), + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME); +} + +void +nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_state) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IN_SET(sys_iface_state, + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME, + NM_DEVICE_SYS_IFACE_STATE_MANAGED, + NM_DEVICE_SYS_IFACE_STATE_REMOVED)); + + priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->sys_iface_state != sys_iface_state) { + _LOGT(LOGD_DEVICE, + "sys-iface-state: %s -> %s", + _sys_iface_state_to_str(priv->sys_iface_state), + _sys_iface_state_to_str(sys_iface_state)); + priv->sys_iface_state_ = sys_iface_state; + } + + /* this function only sets a flag, no immediate actions are initiated. + * + * If you change this, make sure that all callers are fine with such actions. */ + + nm_assert(priv->sys_iface_state == sys_iface_state); +} + +static void +_active_connection_set_state_flags_full(NMDevice * self, + NMActivationStateFlags flags, + NMActivationStateFlags mask) +{ + NMActiveConnection *ac; + + ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(self)); + if (ac) + nm_active_connection_set_state_flags_full(ac, flags, mask); +} + +static void +_active_connection_set_state_flags(NMDevice *self, NMActivationStateFlags flags) +{ + _active_connection_set_state_flags_full(self, flags, flags); +} + +/*****************************************************************************/ + +void +nm_device_assume_state_get(NMDevice * self, + gboolean * out_assume_state_guess_assume, + const char **out_assume_state_connection_uuid) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + NM_SET_OUT(out_assume_state_guess_assume, priv->assume_state_guess_assume); + NM_SET_OUT(out_assume_state_connection_uuid, priv->assume_state_connection_uuid); +} + +static void +_assume_state_set(NMDevice * self, + gboolean assume_state_guess_assume, + const char *assume_state_connection_uuid) +{ + NMDevicePrivate *priv; + + nm_assert(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->assume_state_guess_assume == !!assume_state_guess_assume + && nm_streq0(priv->assume_state_connection_uuid, assume_state_connection_uuid)) + return; + + _LOGD(LOGD_DEVICE, + "assume-state: set guess-assume=%c, connection=%s%s%s", + assume_state_guess_assume ? '1' : '0', + NM_PRINT_FMT_QUOTE_STRING(assume_state_connection_uuid)); + priv->assume_state_guess_assume = assume_state_guess_assume; + g_free(priv->assume_state_connection_uuid); + priv->assume_state_connection_uuid = g_strdup(assume_state_connection_uuid); +} + +void +nm_device_assume_state_reset(NMDevice *self) +{ + g_return_if_fail(NM_IS_DEVICE(self)); + + _assume_state_set(self, FALSE, NULL); +} + +/*****************************************************************************/ + +static void +init_ip_config_dns_priority(NMDevice *self, NMIPConfig *config) +{ + const char *property; + int priority; + + property = (nm_ip_config_get_addr_family(config) == AF_INET) + ? NM_CON_DEFAULT("ipv4.dns-priority") + : NM_CON_DEFAULT("ipv6.dns-priority"); + + priority = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + property, + self, + G_MININT, + G_MAXINT, + 0); + + if (priority == 0) { + priority = + nm_device_is_vpn(self) ? NM_DNS_PRIORITY_DEFAULT_VPN : NM_DNS_PRIORITY_DEFAULT_NORMAL; + } + + nm_ip_config_set_dns_priority(config, priority); +} + +/*****************************************************************************/ + +static char * +nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property) +{ + const char *ifname; + + nm_assert_addr_family(addr_family); + + ifname = nm_device_get_ip_iface_from_platform(self); + if (!ifname) + return NULL; + return nm_platform_sysctl_ip_conf_get(nm_device_get_platform(self), + addr_family, + ifname, + property); +} + +static gint64 +nm_device_sysctl_ip_conf_get_int_checked(NMDevice * self, + int addr_family, + const char *property, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + const char *ifname; + + nm_assert_addr_family(addr_family); + + ifname = nm_device_get_ip_iface_from_platform(self); + if (!ifname) { + errno = EINVAL; + return fallback; + } + return nm_platform_sysctl_ip_conf_get_int_checked(nm_device_get_platform(self), + addr_family, + ifname, + property, + base, + min, + max, + fallback); +} + +static void +set_ipv6_token(NMDevice *self, NMUtilsIPv6IfaceId iid, const char *token_str) +{ + NMPlatform * platform; + int ifindex; + const NMPlatformLink *link; + char buf[32]; + gint64 val; + + /* Setting the kernel token is not strictly necessary as the + * IPv6 address is generated in userspace. However it is + * convenient so that users can see the token with iproute + * ('ip token'). */ + platform = nm_device_get_platform(self); + ifindex = nm_device_get_ip_ifindex(self); + link = nm_platform_link_get(platform, ifindex); + + if (link && link->inet6_token.id == iid.id) { + _LOGT(LOGD_DEVICE | LOGD_IP6, "token %s already set", token_str); + return; + } + + /* The kernel allows setting a token only when 'accept_ra' + * is 1: temporarily flip it if necessary; unfortunately + * this will also generate an additional Router Solicitation + * from kernel. */ + val = nm_device_sysctl_ip_conf_get_int_checked(self, + AF_INET6, + "accept_ra", + 10, + G_MININT32, + G_MAXINT32, + 1); + if (val != 1) + nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "1"); + + nm_platform_link_set_ipv6_token(platform, ifindex, iid); + + if (val != 1) { + nm_sprintf_buf(buf, "%d", (int) val); + nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", buf); + } +} + +gboolean +nm_device_sysctl_ip_conf_set(NMDevice * self, + int addr_family, + const char *property, + const char *value) +{ + NMPlatform * platform = nm_device_get_platform(self); + gs_free char *value_to_free = NULL; + const char * ifname; + + nm_assert_addr_family(addr_family); + + ifname = nm_device_get_ip_iface_from_platform(self); + if (!ifname) + return FALSE; + + if (!value) { + /* Set to a default value when we've got a NULL @value. */ + value_to_free = nm_platform_sysctl_ip_conf_get(platform, addr_family, "default", property); + value = value_to_free; + if (!value) + return FALSE; + } + + return nm_platform_sysctl_ip_conf_set(platform, addr_family, ifname, property, value); +} + +/*****************************************************************************/ + +gboolean +nm_device_has_capability(NMDevice *self, NMDeviceCapabilities caps) +{ + return NM_FLAGS_ANY(NM_DEVICE_GET_PRIVATE(self)->capabilities, caps); +} + +static void +_add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!NM_FLAGS_ALL(priv->capabilities, capabilities)) { + priv->capabilities |= capabilities; + _notify(self, PROP_CAPABILITIES); + } +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(_ip_state_to_string, + NMDeviceIPState, + NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_WAIT, "wait"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), ); + +static void +_set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + nm_assert_addr_family(addr_family); + + if (priv->ip_state_x[IS_IPv4] == new_state) + return; + + _LOGT(LOGD_DEVICE, + "ip%c-state: set to %d (%s)", + nm_utils_addr_family_to_char(addr_family), + (int) new_state, + _ip_state_to_string(new_state)); + + priv->ip_state_x_[IS_IPv4] = new_state; + + if (new_state == NM_DEVICE_IP_STATE_DONE) { + /* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't + * ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state. + * + * This is not documented/guaranteed behavior, but seems to make sense for now. */ + _active_connection_set_state_flags(self, + NM_IS_IPv4(addr_family) + ? NM_ACTIVATION_STATE_FLAG_IP4_READY + : NM_ACTIVATION_STATE_FLAG_IP6_READY); + } +} + +/*****************************************************************************/ + +const char * +nm_device_get_udi(NMDevice *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE(self)->udi; +} + +const char * +nm_device_get_iface(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->iface; +} + +static gboolean +_set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + int * p_ifindex; + + if (ifindex < 0) + ifindex = 0; + + p_ifindex = is_ip_ifindex ? &priv->ip_ifindex_ : &priv->ifindex_; + + if (*p_ifindex == ifindex) + return FALSE; + + *p_ifindex = ifindex; + + _LOGD(LOGD_DEVICE, "ifindex: set %sifindex %d", is_ip_ifindex ? "ip-" : "", ifindex); + + if (!is_ip_ifindex) + _notify(self, PROP_IFINDEX); + + if (priv->manager) + nm_manager_emit_device_ifindex_changed(priv->manager, self); + return TRUE; +} + +/** + * nm_device_take_over_link: + * @self: the #NMDevice + * @ifindex: a ifindex + * @old_name: (transfer full): on return, the name of the old link, if + * the link was renamed + * @error: location to store error, or %NULL + * + * Given an existing link, move it under the control of a device. In + * particular, the link will be renamed to match the device name. If the + * link was renamed, the old name is returned in @old_name. + * + * Returns: %TRUE if the device took control of the link, %FALSE otherwise + */ +gboolean +nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **error) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const NMPlatformLink *plink; + NMPlatform * platform; + + nm_assert(ifindex > 0); + NM_SET_OUT(old_name, NULL); + + if (priv->ifindex > 0 && priv->ifindex != ifindex) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "the device already has ifindex %d", + priv->ifindex); + return FALSE; + } + + platform = nm_device_get_platform(self); + plink = nm_platform_link_get(platform, ifindex); + if (!plink) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "link %d not found", ifindex); + return FALSE; + } + + if (!nm_streq(plink->name, nm_device_get_iface(self))) { + gboolean up; + gboolean success; + gs_free char *name = NULL; + + up = NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP); + name = g_strdup(plink->name); + + /* Rename the link to the device ifname */ + if (up) + nm_platform_link_set_down(platform, ifindex); + success = nm_platform_link_set_name(platform, ifindex, nm_device_get_iface(self)); + if (up) + nm_platform_link_set_up(platform, ifindex, NULL); + + if (!success) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "failure renaming link %d", ifindex); + return FALSE; + } + + NM_SET_OUT(old_name, g_steal_pointer(&name)); + } + + _set_ifindex(self, ifindex, FALSE); + + return TRUE; +} + +int +nm_device_get_ifindex(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), 0); + + return NM_DEVICE_GET_PRIVATE(self)->ifindex; +} + +/** + * nm_device_is_software: + * @self: the #NMDevice + * + * Indicates if the device is a software-based virtual device without + * backing hardware, which can be added and removed programmatically. + * + * Returns: %TRUE if the device is a software-based device + */ +gboolean +nm_device_is_software(NMDevice *self) +{ + return NM_FLAGS_HAS(NM_DEVICE_GET_PRIVATE(self)->capabilities, NM_DEVICE_CAP_IS_SOFTWARE); +} + +/** + * nm_device_is_real: + * @self: the #NMDevice + * + * Returns: %TRUE if the device exists, %FALSE if the device is a placeholder + */ +gboolean +nm_device_is_real(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + return NM_DEVICE_GET_PRIVATE(self)->real; +} + +const char * +nm_device_get_ip_iface(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(self != NULL, NULL); + + priv = NM_DEVICE_GET_PRIVATE(self); + /* If it's not set, default to iface */ + return priv->ip_iface ?: priv->iface; +} + +const char * +nm_device_get_ip_iface_from_platform(NMDevice *self) +{ + int ifindex; + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + return NULL; + + return nm_platform_link_get_name(nm_device_get_platform(self), ifindex); +} + +int +nm_device_get_ip_ifindex(const NMDevice *self) +{ + const NMDevicePrivate *priv; + + g_return_val_if_fail(self != NULL, 0); + + priv = NM_DEVICE_GET_PRIVATE(self); + /* If it's not set, default to ifindex */ + return priv->ip_iface ? priv->ip_ifindex : priv->ifindex; +} + +static void +_set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform * platform; + gboolean eq_name; + + /* normalize arguments */ + if (ifindex <= 0) { + ifindex = 0; + ifname = NULL; + } + + eq_name = nm_streq0(priv->ip_iface, ifname); + + if (eq_name && priv->ip_ifindex == ifindex) + return; + + _LOGD(LOGD_DEVICE, + "ip-ifindex: update ip-interface to %s%s%s, ifindex %d", + NM_PRINT_FMT_QUOTE_STRING(ifname), + ifindex); + + _set_ifindex(self, ifindex, TRUE); + + if (!eq_name) { + g_free(priv->ip_iface_); + priv->ip_iface_ = g_strdup(ifname); + _notify(self, PROP_IP_IFACE); + } + + if (priv->ip_ifindex > 0) { + platform = nm_device_get_platform(self); + + nm_platform_process_events_ensure_link(platform, priv->ip_ifindex, priv->ip_iface); + + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) + nm_platform_link_set_user_ipv6ll_enabled(platform, priv->ip_ifindex, TRUE); + + if (!nm_platform_link_is_up(platform, priv->ip_ifindex)) + nm_platform_link_set_up(platform, priv->ip_ifindex, NULL); + } + + /* We don't care about any saved values from the old iface */ + g_hash_table_remove_all(priv->ip6_saved_properties); +} + +gboolean +nm_device_set_ip_ifindex(NMDevice *self, int ifindex) +{ + char ifname_buf[IFNAMSIZ]; + const char *ifname = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(nm_device_is_activating(self), FALSE); + + if (ifindex > 0) { + ifname = nm_platform_if_indextoname(nm_device_get_platform(self), ifindex, ifname_buf); + if (!ifname) + _LOGW(LOGD_DEVICE, "ip-ifindex: ifindex %d not found", ifindex); + } + + _set_ip_ifindex(self, ifindex, ifname); + return ifindex > 0; +} + +/** + * nm_device_set_ip_iface: + * @self: the #NMDevice + * @ifname: the new IP interface name + * + * Updates the IP interface name and possibly the ifindex. + * + * Returns: %TRUE if an interface with name @ifname exists, + * and %FALSE, if @ifname is %NULL or no such interface exists. + */ +gboolean +nm_device_set_ip_iface(NMDevice *self, const char *ifname) +{ + int ifindex = 0; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(nm_device_is_activating(self), FALSE); + + if (ifname) { + ifindex = nm_platform_if_nametoindex(nm_device_get_platform(self), ifname); + if (ifindex <= 0) + _LOGW(LOGD_DEVICE, "ip-ifindex: ifname %s not found", ifname); + } + + _set_ip_ifindex(self, ifindex, ifname); + return ifindex > 0; +} + +/*****************************************************************************/ + +int +nm_device_parent_get_ifindex(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), 0); + + priv = NM_DEVICE_GET_PRIVATE(self); + return priv->parent_ifindex; +} + +NMDevice * +nm_device_parent_get_device(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->parent_device.obj; +} + +static void +parent_changed_notify(NMDevice *self, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent) +{ + /* empty handler to allow subclasses to always chain up the virtual function. */ +} + +static gboolean +_parent_set_ifindex(NMDevice *self, int parent_ifindex, gboolean force_check) +{ + NMDevicePrivate *priv; + NMDevice * parent_device; + gboolean changed = FALSE; + int old_ifindex; + gs_unref_object NMDevice *old_device = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (parent_ifindex <= 0) + parent_ifindex = 0; + + old_ifindex = priv->parent_ifindex; + + if (priv->parent_ifindex == parent_ifindex) { + if (parent_ifindex > 0) { + if (!force_check && priv->parent_device.obj + && nm_device_get_ifindex(priv->parent_device.obj) == parent_ifindex) + return FALSE; + } else { + if (!priv->parent_device.obj) + return FALSE; + } + } else { + priv->parent_ifindex = parent_ifindex; + changed = TRUE; + } + + if (parent_ifindex > 0) { + parent_device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, parent_ifindex); + if (parent_device == self) + parent_device = NULL; + } else + parent_device = NULL; + + if (parent_device != priv->parent_device.obj) { + old_device = nm_g_object_ref(priv->parent_device.obj); + nm_dbus_track_obj_path_set(&priv->parent_device, parent_device, TRUE); + changed = TRUE; + } + + if (changed) { + if (priv->parent_ifindex <= 0) + _LOGD(LOGD_DEVICE, "parent: clear"); + else if (!priv->parent_device.obj) + _LOGD(LOGD_DEVICE, "parent: ifindex %d, no device", priv->parent_ifindex); + else { + _LOGD(LOGD_DEVICE, + "parent: ifindex %d, device %p, %s", + priv->parent_ifindex, + priv->parent_device.obj, + nm_device_get_iface(priv->parent_device.obj)); + } + + NM_DEVICE_GET_CLASS(self)->parent_changed_notify(self, + old_ifindex, + old_device, + priv->parent_ifindex, + priv->parent_device.obj); + } + return changed; +} + +void +nm_device_parent_set_ifindex(NMDevice *self, int parent_ifindex) +{ + _parent_set_ifindex(self, parent_ifindex, FALSE); +} + +gboolean +nm_device_parent_notify_changed(NMDevice *self, NMDevice *change_candidate, gboolean device_removed) +{ + NMDevicePrivate *priv; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(NM_IS_DEVICE(change_candidate)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->parent_ifindex > 0) { + if (priv->parent_device.obj == change_candidate + || priv->parent_ifindex == nm_device_get_ifindex(change_candidate)) + return _parent_set_ifindex(self, priv->parent_ifindex, device_removed); + } + return FALSE; +} + +/*****************************************************************************/ + +const char * +nm_device_parent_find_for_connection(NMDevice *self, const char *current_setting_parent) +{ + const char *new_parent; + NMDevice * parent_device; + + parent_device = nm_device_parent_get_device(self); + if (!parent_device) + return NULL; + + new_parent = nm_device_get_iface(parent_device); + if (!new_parent) + return NULL; + + if (current_setting_parent && !nm_streq(current_setting_parent, new_parent) + && nm_utils_is_uuid(current_setting_parent)) { + NMSettingsConnection *parent_connection; + + /* Don't change a parent specified by UUID if it's still valid */ + parent_connection = nm_settings_get_connection_by_uuid(nm_device_get_settings(self), + current_setting_parent); + if (parent_connection + && nm_device_check_connection_compatible( + parent_device, + nm_settings_connection_get_connection(parent_connection), + NULL)) + return current_setting_parent; + } + + return new_parent; +} + +/*****************************************************************************/ + +static void +_stats_update_counters(NMDevice *self, guint64 tx_bytes, guint64 rx_bytes) +{ + NMDevicePrivate *priv; + gboolean tx_changed = FALSE; + gboolean rx_changed = FALSE; + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->stats.tx_bytes != tx_bytes) { + priv->stats.tx_bytes = tx_bytes; + tx_changed = TRUE; + } + if (priv->stats.rx_bytes != rx_bytes) { + priv->stats.rx_bytes = rx_bytes; + rx_changed = TRUE; + } + + nm_gobject_notify_together(self, + tx_changed ? PROP_STATISTICS_TX_BYTES : PROP_0, + rx_changed ? PROP_STATISTICS_RX_BYTES : PROP_0); +} + +static void +_stats_update_counters_from_pllink(NMDevice *self, const NMPlatformLink *pllink) +{ + _stats_update_counters(self, pllink->tx_bytes, pllink->rx_bytes); +} + +static gboolean +_stats_timeout_cb(gpointer user_data) +{ + NMDevice *self = user_data; + int ifindex; + + ifindex = nm_device_get_ip_ifindex(self); + + _LOGT(LOGD_DEVICE, "stats: refresh %d", ifindex); + + if (ifindex > 0) + nm_platform_link_refresh(nm_device_get_platform(self), ifindex); + + return G_SOURCE_CONTINUE; +} + +static guint +_stats_refresh_rate_real(guint refresh_rate_ms) +{ + const guint STATS_REFRESH_RATE_MS_MIN = 200; + + if (refresh_rate_ms == 0) + return 0; + + if (refresh_rate_ms < STATS_REFRESH_RATE_MS_MIN) { + /* you cannot set the refresh-rate arbitrarily small. E.g. + * setting to 1ms is just killing. Have a lowest number. */ + return STATS_REFRESH_RATE_MS_MIN; + } + + return refresh_rate_ms; +} + +static void +_stats_set_refresh_rate(NMDevice *self, guint refresh_rate_ms) +{ + NMDevicePrivate *priv; + int ifindex; + guint old_rate; + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->stats.refresh_rate_ms == refresh_rate_ms) + return; + + old_rate = priv->stats.refresh_rate_ms; + priv->stats.refresh_rate_ms = refresh_rate_ms; + _notify(self, PROP_STATISTICS_REFRESH_RATE_MS); + + _LOGD(LOGD_DEVICE, "stats: set refresh to %u ms", priv->stats.refresh_rate_ms); + + if (!nm_device_is_real(self)) + return; + + refresh_rate_ms = _stats_refresh_rate_real(refresh_rate_ms); + if (_stats_refresh_rate_real(old_rate) == refresh_rate_ms) + return; + + nm_clear_g_source(&priv->stats.timeout_id); + + if (!refresh_rate_ms) + return; + + /* trigger an initial refresh of the data whenever the refresh-rate changes. + * As we process the result in an idle handler with device_link_changed(), + * we don't get the result right away. */ + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex > 0) + nm_platform_link_refresh(nm_device_get_platform(self), ifindex); + + priv->stats.timeout_id = g_timeout_add(refresh_rate_ms, _stats_timeout_cb, self); +} + +/*****************************************************************************/ + +static gboolean +get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *out_iid) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform * platform = nm_device_get_platform(self); + const NMPlatformLink *pllink; + const guint8 * hwaddr; + guint8 pseudo_hwaddr[ETH_ALEN]; + gsize hwaddr_len; + int ifindex; + gboolean success; + + /* If we get here, we *must* have a kernel netdev, which implies an ifindex */ + ifindex = nm_device_get_ip_ifindex(self); + g_return_val_if_fail(ifindex > 0, FALSE); + + pllink = nm_platform_link_get(platform, ifindex); + if (!pllink || NM_IN_SET(pllink->type, NM_LINK_TYPE_NONE, NM_LINK_TYPE_UNKNOWN)) + return FALSE; + + hwaddr = nmp_link_address_get(&pllink->l_address, &hwaddr_len); + if (hwaddr_len <= 0) + return FALSE; + + if (pllink->type == NM_LINK_TYPE_6LOWPAN) { + /* If the underlying IEEE 802.15.4 device has a short address we generate + * a "pseudo 48-bit address" that's to be used in the same fashion as a + * wired Ethernet address. The mechanism is specified in Section 6. of + * RFC 4944 */ + guint16 pan_id; + guint16 short_addr; + + short_addr = nm_platform_wpan_get_short_addr(platform, pllink->parent); + if (short_addr != G_MAXUINT16) { + pan_id = nm_platform_wpan_get_pan_id(platform, pllink->parent); + pseudo_hwaddr[0] = short_addr & 0xff; + pseudo_hwaddr[1] = (short_addr >> 8) & 0xff; + pseudo_hwaddr[2] = 0; + pseudo_hwaddr[3] = 0; + pseudo_hwaddr[4] = pan_id & 0xff; + pseudo_hwaddr[5] = (pan_id >> 8) & 0xff; + + hwaddr = pseudo_hwaddr; + hwaddr_len = G_N_ELEMENTS(pseudo_hwaddr); + } + } + + success = nm_utils_get_ipv6_interface_identifier(pllink->type, + hwaddr, + hwaddr_len, + priv->dev_id, + out_iid); + if (!success) { + _LOGW(LOGD_PLATFORM, + "failed to generate interface identifier " + "for link type %u hwaddr_len %zu", + pllink->type, + hwaddr_len); + } + return success; +} + +/** + * nm_device_get_ip_iface_identifier: + * @self: an #NMDevice + * @iid: where to place the interface identifier + * @ignore_token: force creation of a non-tokenized address + * + * Return the interface's identifier for the EUI64 address generation mode. + * It's either a manually set token or and identifier generated in a + * hardware-specific way. + * + * Unless @ignore_token is set the token is preferred. That is the case + * for link-local addresses (to mimic kernel behavior). + * + * Returns: #TRUE if the @iid could be set + */ +static gboolean +nm_device_get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *iid, gboolean ignore_token) +{ + NMSettingIP6Config *s_ip6; + const char * token = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + if (!ignore_token) { + s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG); + + g_return_val_if_fail(s_ip6, FALSE); + + token = nm_setting_ip6_config_get_token(s_ip6); + } + if (token) + return nm_utils_ipv6_interface_identifier_get_from_token(iid, token); + else + return NM_DEVICE_GET_CLASS(self)->get_ip_iface_identifier(self, iid); +} + +const char * +nm_device_get_driver(NMDevice *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE(self)->driver; +} + +const char * +nm_device_get_driver_version(NMDevice *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE(self)->driver_version; +} + +NMDeviceType +nm_device_get_device_type(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_TYPE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE(self)->type; +} + +NMLinkType +nm_device_get_link_type(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_LINK_TYPE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE(self)->link_type; +} + +/** + * nm_device_get_metered: + * @setting: the #NMDevice + * + * Returns: the #NMDevice:metered property of the device. + * + * Since: 1.2 + **/ +NMMetered +nm_device_get_metered(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_METERED_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE(self)->metered; +} + +guint32 +nm_device_get_route_metric_default(NMDeviceType device_type) +{ + /* Device 'priority' is used for the default route-metric and is based on + * the device type. The settings ipv4.route-metric and ipv6.route-metric + * can overwrite this default. + * + * For both IPv4 and IPv6 we use the same default values. + * + * The route-metric is used for the metric of the routes of device. + * This also applies to the default route. Therefore it affects also + * which device is the "best". + * + * For comparison, note that iproute2 by default adds IPv4 routes with + * metric 0, and IPv6 routes with metric 1024. The latter is the IPv6 + * "user default" in the kernel (NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6). + * In kernel, the full uint32_t range is available for route + * metrics (except for IPv6, where 0 means 1024). + */ + + switch (device_type) { + /* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT). + * + * Note that returning 50 from this function means that this device-type is + * in some aspects a VPN. */ + case NM_DEVICE_TYPE_WIREGUARD: + return NM_VPN_ROUTE_METRIC_DEFAULT; + + case NM_DEVICE_TYPE_ETHERNET: + case NM_DEVICE_TYPE_VETH: + return 100; + case NM_DEVICE_TYPE_MACSEC: + return 125; + case NM_DEVICE_TYPE_INFINIBAND: + return 150; + case NM_DEVICE_TYPE_ADSL: + return 200; + case NM_DEVICE_TYPE_WIMAX: + return 250; + case NM_DEVICE_TYPE_BOND: + return 300; + case NM_DEVICE_TYPE_TEAM: + return 350; + case NM_DEVICE_TYPE_VLAN: + return 400; + case NM_DEVICE_TYPE_MACVLAN: + return 410; + case NM_DEVICE_TYPE_BRIDGE: + return 425; + case NM_DEVICE_TYPE_TUN: + return 450; + case NM_DEVICE_TYPE_PPP: + return 460; + case NM_DEVICE_TYPE_VRF: + return 470; + case NM_DEVICE_TYPE_VXLAN: + return 500; + case NM_DEVICE_TYPE_DUMMY: + return 550; + case NM_DEVICE_TYPE_WIFI: + return 600; + case NM_DEVICE_TYPE_OLPC_MESH: + return 650; + case NM_DEVICE_TYPE_IP_TUNNEL: + return 675; + case NM_DEVICE_TYPE_MODEM: + return 700; + case NM_DEVICE_TYPE_BT: + return 750; + case NM_DEVICE_TYPE_6LOWPAN: + return 775; + case NM_DEVICE_TYPE_OVS_BRIDGE: + case NM_DEVICE_TYPE_OVS_INTERFACE: + case NM_DEVICE_TYPE_OVS_PORT: + return 800; + case NM_DEVICE_TYPE_WPAN: + return 850; + case NM_DEVICE_TYPE_WIFI_P2P: + case NM_DEVICE_TYPE_GENERIC: + return 950; + case NM_DEVICE_TYPE_UNKNOWN: + return 10000; + case NM_DEVICE_TYPE_UNUSED1: + case NM_DEVICE_TYPE_UNUSED2: + /* omit default: to get compiler warning about missing switch cases */ + break; + } + return 11000; +} + +static gboolean +default_route_metric_penalty_detect(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + /* currently we don't differentiate between IPv4 and IPv6 when detecting + * connectivity. */ + if (priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL + && nm_connectivity_check_enabled(concheck_get_mgr(self))) + return TRUE; + + return FALSE; +} + +static guint32 +default_route_metric_penalty_get(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (NM_IS_IPv4(addr_family) ? priv->default_route_metric_penalty_ip4_has + : priv->default_route_metric_penalty_ip6_has) + return 20000; + return 0; +} + +guint32 +nm_device_get_route_metric(NMDevice *self, int addr_family) +{ + gint64 route_metric; + NMSettingIPConfig *s_ip; + NMConnection * connection; + const char * property; + + g_return_val_if_fail(NM_IS_DEVICE(self), G_MAXUINT32); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), G_MAXUINT32); + + connection = nm_device_get_applied_connection(self); + if (connection) { + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + + /* Slave interfaces don't have IP settings, but we may get here when + * external changes are made or when noticing IP changes when starting + * the slave connection. + */ + if (s_ip) { + route_metric = nm_setting_ip_config_get_route_metric(s_ip); + if (route_metric >= 0) + goto out; + } + } + + /* use the current NMConfigData, which makes this configuration reloadable. + * Note that that means that the route-metric might change between SIGHUP. + * You must cache the returned value if that is a problem. */ + property = NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.route-metric") + : NM_CON_DEFAULT("ipv6.route-metric"); + route_metric = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + property, + self, + 0, + G_MAXUINT32, + -1); + if (route_metric >= 0) + goto out; + + route_metric = nm_manager_device_route_metric_reserve(NM_MANAGER_GET, + nm_device_get_ip_ifindex(self), + nm_device_get_device_type(self)); +out: + return nm_utils_ip_route_metric_normalize(addr_family, route_metric); +} + +guint32 +nm_device_get_route_table(NMDevice *self, int addr_family) +{ + guint32 route_table; + + g_return_val_if_fail(NM_IS_DEVICE(self), RT_TABLE_MAIN); + + route_table = _prop_get_ipvx_route_table(self, addr_family); + return route_table ?: (guint32) RT_TABLE_MAIN; +} + +static NMIPRouteTableSyncMode +_get_route_table_sync_mode_stateful(NMDevice *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDedupMultiIter ipconf_iter; + gboolean all_sync_now; + gboolean all_sync_eff; + + all_sync_now = _prop_get_ipvx_route_table(self, addr_family) != 0u; + + if (!all_sync_now) { + const NMPlatformIPRoute *route; + + /* If there's a local route switch to all-sync in order + * to properly manage the local table */ + nm_ip_config_iter_ip_route_for_each (&ipconf_iter, priv->con_ip_config_x[IS_IPv4], &route) { + if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) { + all_sync_now = TRUE; + break; + } + } + } + + if (all_sync_now) + all_sync_eff = TRUE; + else { + /* When we change from all-sync to no all-sync, we do a last all-sync one + * more time. For that, we determine the effective all-state based on the + * cached/previous all-sync flag. + * + * The purpose of this is to support reapply of route-table (and thus the + * all-sync mode). If reapply toggles from all-sync to no-all-sync, we must + * sync one last time. */ + if (NM_IS_IPv4(addr_family)) + all_sync_eff = priv->v4_route_table_all_sync_before; + else + all_sync_eff = priv->v6_route_table_all_sync_before; + } + + if (NM_IS_IPv4(addr_family)) + priv->v4_route_table_all_sync_before = all_sync_now; + else + priv->v6_route_table_all_sync_before = all_sync_now; + + return all_sync_eff ? NM_IP_ROUTE_TABLE_SYNC_MODE_ALL : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN; +} + +const NMPObject * +nm_device_get_best_default_route(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + switch (addr_family) { + case AF_INET: + return priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL; + case AF_INET6: + return priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6) : NULL; + case AF_UNSPEC: + return (priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL) + ?: (priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6) + : NULL); + default: + g_return_val_if_reached(NULL); + } +} + +const char * +nm_device_get_type_desc(NMDevice *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE(self)->type_desc; +} + +const char * +nm_device_get_type_description(NMDevice *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + /* Beware: this function should return the same + * value as nm_device_get_type_description() in libnm. */ + + return NM_DEVICE_GET_CLASS(self)->get_type_description(self); +} + +static const char * +get_type_description(NMDevice *self) +{ + NMDeviceClass *klass; + + nm_assert(NM_IS_DEVICE(self)); + + /* the default implementation for the description just returns the (modified) + * class name and depends entirely on the type of self. Note that we cache the + * description in the klass itself. + * + * Also note, that as the GObject class gets inited, it inherrits the fields + * of the parent class. That means, if NMDeviceVethClass was initialized after + * NMDeviceEthernetClass already has the description cached in the class + * (because we already fetched the description for an ethernet device), + * then default_type_description will wrongly contain "ethernet". + * To avoid that, and catch the situation, also cache the klass for + * which the description was cached. If that doesn't match, it was + * inherited and we need to reset it. */ + klass = NM_DEVICE_GET_CLASS(self); + if (G_UNLIKELY(klass->default_type_description_klass != klass)) { + const char *typename; + gs_free char *s = NULL; + + typename = G_OBJECT_TYPE_NAME(self); + if (g_str_has_prefix(typename, "NMDevice")) { + typename += 8; + if (nm_streq(typename, "Veth")) + typename = "Ethernet"; + } + s = g_ascii_strdown(typename, -1); + klass->default_type_description = g_intern_string(s); + klass->default_type_description_klass = klass; + } + + nm_assert(klass->default_type_description); + return klass->default_type_description; +} + +gboolean +nm_device_has_carrier(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->carrier; +} + +NMActRequest * +nm_device_get_act_request(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->act_request.obj; +} + +NMActivationStateFlags +nm_device_get_activation_state_flags(NMDevice *self) +{ + NMActRequest *ac; + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_ACTIVATION_STATE_FLAG_NONE); + + ac = NM_DEVICE_GET_PRIVATE(self)->act_request.obj; + if (!ac) + return NM_ACTIVATION_STATE_FLAG_NONE; + return nm_active_connection_get_state_flags(NM_ACTIVE_CONNECTION(ac)); +} + +NMSettingsConnection * +nm_device_get_settings_connection(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + priv = NM_DEVICE_GET_PRIVATE(self); + + return priv->act_request.obj ? nm_act_request_get_settings_connection(priv->act_request.obj) + : NULL; +} + +NMConnection * +nm_device_get_settings_connection_get_connection(NMDevice *self) +{ + NMSettingsConnection *sett_con; + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->act_request.obj) + return NULL; + + sett_con = nm_act_request_get_settings_connection(priv->act_request.obj); + if (!sett_con) + return NULL; + + return nm_settings_connection_get_connection(sett_con); +} + +NMConnection * +nm_device_get_applied_connection(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + priv = NM_DEVICE_GET_PRIVATE(self); + + return priv->act_request.obj ? nm_act_request_get_applied_connection(priv->act_request.obj) + : NULL; +} + +gboolean +nm_device_has_unmodified_applied_connection(NMDevice *self, NMSettingCompareFlags compare_flags) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->act_request.obj) + return FALSE; + + return nm_active_connection_has_unmodified_applied_connection( + (NMActiveConnection *) priv->act_request.obj, + compare_flags); +} + +gpointer +nm_device_get_applied_setting(NMDevice *self, GType setting_type) +{ + NMConnection *connection; + + connection = nm_device_get_applied_connection(self); + return connection ? nm_connection_get_setting(connection, setting_type) : NULL; +} + +RfKillType +nm_device_get_rfkill_type(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + return NM_DEVICE_GET_PRIVATE(self)->rfkill_type; +} + +static const char * +nm_device_get_physical_port_id(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->physical_port_id; +} + +/*****************************************************************************/ + +typedef enum { + CONCHECK_SCHEDULE_UPDATE_INTERVAL, + CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART, + CONCHECK_SCHEDULE_CHECK_EXTERNAL, + CONCHECK_SCHEDULE_CHECK_PERIODIC, + CONCHECK_SCHEDULE_RETURNED_MIN, + CONCHECK_SCHEDULE_RETURNED_BUMP, + CONCHECK_SCHEDULE_RETURNED_MAX, +} ConcheckScheduleMode; + +static NMDeviceConnectivityHandle *concheck_start(NMDevice * self, + int addr_family, + NMDeviceConnectivityCallback callback, + gpointer user_data, + gboolean is_periodic); + +static void +concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckScheduleMode mode); + +static gboolean +_concheck_periodic_timeout_cb(NMDevice *self, int addr_family) +{ + _LOGt(LOGD_CONCHECK, + "connectivity: [IPv%c] periodic timeout", + nm_utils_addr_family_to_char(addr_family)); + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_PERIODIC); + return G_SOURCE_REMOVE; +} + +static gboolean +concheck_ip4_periodic_timeout_cb(gpointer user_data) +{ + return _concheck_periodic_timeout_cb(user_data, AF_INET); +} + +static gboolean +concheck_ip6_periodic_timeout_cb(gpointer user_data) +{ + return _concheck_periodic_timeout_cb(user_data, AF_INET6); +} + +static gboolean +concheck_is_possible(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!nm_device_is_real(self) || is_loopback(self)) + return FALSE; + + /* we enable periodic checks for every device state (except UNKNOWN). Especially with + * unmanaged devices, it is interesting to know whether we have connectivity on that device. */ + if (priv->state == NM_DEVICE_STATE_UNKNOWN) + return FALSE; + + return TRUE; +} + +static gboolean +concheck_periodic_schedule_do(NMDevice *self, int addr_family, gint64 now_ns) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean periodic_check_disabled = FALSE; + gint64 expiry, tdiff; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + /* we always cancel whatever was pending. */ + if (nm_clear_g_source(&priv->concheck_x[IS_IPv4].p_cur_id)) + periodic_check_disabled = TRUE; + + if (priv->concheck_x[IS_IPv4].p_max_interval == 0) { + /* periodic checks are disabled */ + goto out; + } + + if (!concheck_is_possible(self)) + goto out; + + nm_assert(now_ns > 0); + nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0); + + /* we schedule the timeout based on our current settings cur-interval and cur-basetime. + * Before calling concheck_periodic_schedule_do(), make sure that these properties are + * correct. */ + + expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC); + tdiff = expiry - now_ns; + + _LOGT(LOGD_CONCHECK, + "connectivity: [IPv%c] periodic-check: %sscheduled in %lld milliseconds (%u seconds " + "interval)", + nm_utils_addr_family_to_char(addr_family), + periodic_check_disabled ? "re-" : "", + (long long) (tdiff / NM_UTILS_NSEC_PER_MSEC), + priv->concheck_x[IS_IPv4].p_cur_interval); + + priv->concheck_x[IS_IPv4].p_cur_id = + g_timeout_add(NM_MAX((gint64) 0, tdiff) / NM_UTILS_NSEC_PER_MSEC, + IS_IPv4 ? concheck_ip4_periodic_timeout_cb : concheck_ip6_periodic_timeout_cb, + self); + return TRUE; +out: + if (periodic_check_disabled) { + _LOGT(LOGD_CONCHECK, + "connectivity: [IPv%c] periodic-check: unscheduled", + nm_utils_addr_family_to_char(addr_family)); + } + return FALSE; +} + +#define CONCHECK_P_PROBE_INTERVAL 1 + +static void +concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckScheduleMode mode) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gint64 new_expiry, exp_expiry, cur_expiry, tdiff; + gint64 now_ns = 0; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + if (priv->concheck_x[IS_IPv4].p_max_interval == 0) { + /* periodic check is disabled. Nothing to do. */ + return; + } + + if (!priv->concheck_x[IS_IPv4].p_cur_id) { + /* we currently don't have a timeout scheduled. No need to reschedule + * another one... */ + if (NM_IN_SET(mode, + CONCHECK_SCHEDULE_UPDATE_INTERVAL, + CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART)) { + /* ... unless, we are about to start periodic checks after update-interval. + * In this case, fall through and restart the periodic checks below. */ + mode = CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART; + } else + return; + } + + switch (mode) { + case CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART: + priv->concheck_x[IS_IPv4].p_cur_interval = + NM_MIN(priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL); + priv->concheck_x[IS_IPv4].p_cur_basetime_ns = + nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns); + if (concheck_periodic_schedule_do(self, addr_family, now_ns)) + concheck_start(self, addr_family, NULL, NULL, TRUE); + return; + + case CONCHECK_SCHEDULE_UPDATE_INTERVAL: + /* called with "UPDATE_INTERVAL" and already have a p_cur_id scheduled. */ + + nm_assert(priv->concheck_x[IS_IPv4].p_max_interval > 0); + nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0); + + if (priv->concheck_x[IS_IPv4].p_cur_interval <= priv->concheck_x[IS_IPv4].p_max_interval) { + /* we currently have a shorter interval set, than what we now have. Either, + * because we are probing, or because the previous max interval was shorter. + * + * Either way, the current timer is set just fine. Nothing to do, we will + * probe our way up. */ + return; + } + + cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + + (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NSEC_PER_SEC); + nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns); + + priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval; + if (cur_expiry <= now_ns) { + /* Since the last time we scheduled a periodic check, already more than the + * new max_interval passed. We need to start a check right away (and + * schedule a timeout in cur-interval in the future). */ + priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns; + if (concheck_periodic_schedule_do(self, addr_family, now_ns)) + concheck_start(self, addr_family, NULL, NULL, TRUE); + } else { + /* we are reducing the max-interval to a shorter interval that we have currently + * scheduled (with cur_interval). + * + * However, since the last time we scheduled the check, not even the new max-interval + * expired. All we need to do, is reschedule the timer to expire sooner. The cur_basetime + * is unchanged. */ + concheck_periodic_schedule_do(self, addr_family, now_ns); + } + return; + + case CONCHECK_SCHEDULE_CHECK_EXTERNAL: + /* a external connectivity check delays our periodic check. We reset the counter. */ + priv->concheck_x[IS_IPv4].p_cur_basetime_ns = + nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns); + concheck_periodic_schedule_do(self, addr_family, now_ns); + return; + + case CONCHECK_SCHEDULE_CHECK_PERIODIC: + { + gboolean any_periodic_pending; + NMDeviceConnectivityHandle *handle; + guint old_interval = priv->concheck_x[IS_IPv4].p_cur_interval; + + any_periodic_pending = FALSE; + c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) { + if (handle->addr_family != addr_family) + continue; + if (handle->is_periodic_bump) { + handle->is_periodic_bump = FALSE; + handle->is_periodic_bump_on_complete = FALSE; + any_periodic_pending = TRUE; + } + } + if (any_periodic_pending) { + /* we reached a timeout to schedule a new periodic request, however we still + * have period requests pending that didn't complete yet. We need to bump the + * interval already. */ + priv->concheck_x[IS_IPv4].p_cur_interval = + NM_MIN(old_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval); + } + + /* we just reached a timeout. The expected expiry (exp_expiry) should be + * pretty close to now_ns. + * + * We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */ + nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns); + exp_expiry = + priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NSEC_PER_SEC); + new_expiry = + exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC); + tdiff = NM_MAX(new_expiry - now_ns, 0); + priv->concheck_x[IS_IPv4].p_cur_basetime_ns = + (now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC); + if (concheck_periodic_schedule_do(self, addr_family, now_ns)) { + handle = concheck_start(self, addr_family, NULL, NULL, TRUE); + if (old_interval != priv->concheck_x[IS_IPv4].p_cur_interval) { + /* we just bumped the interval already when scheduling this check. + * When the handle returns, don't bump a second time. + * + * But if we reach the timeout again before the handle returns (this + * code here) we will still bump the interval. */ + handle->is_periodic_bump_on_complete = FALSE; + } + } + return; + } + + /* we just got an event that we lost connectivity (that is, concheck returned). We reset + * the interval to min/max or increase the probe interval (bump). */ + case CONCHECK_SCHEDULE_RETURNED_MIN: + priv->concheck_x[IS_IPv4].p_cur_interval = + NM_MIN(priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL); + break; + case CONCHECK_SCHEDULE_RETURNED_MAX: + priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval; + break; + case CONCHECK_SCHEDULE_RETURNED_BUMP: + priv->concheck_x[IS_IPv4].p_cur_interval = + NM_MIN(priv->concheck_x[IS_IPv4].p_cur_interval * 2, + priv->concheck_x[IS_IPv4].p_max_interval); + break; + } + + /* we are here, because we returned from a connectivity check and adjust the current interval. + * + * But note that we calculate the new timeout based on the time when we scheduled the + * last check, instead of counting from now. The reason is that we want that the times + * when we schedule checks be at precise intervals, without including the time it took for + * the connectivity check. */ + new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC); + tdiff = NM_MAX(new_expiry - nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns), 0); + priv->concheck_x[IS_IPv4].p_cur_basetime_ns = + now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC); + concheck_periodic_schedule_do(self, addr_family, now_ns); +} + +static void +concheck_update_interval(NMDevice *self, int addr_family, gboolean check_now) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + guint new_interval; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + new_interval = nm_connectivity_get_interval(concheck_get_mgr(self)); + + new_interval = NM_MIN(new_interval, 7 * 24 * 3600); + + if (new_interval != priv->concheck_x[IS_IPv4].p_max_interval) { + _LOGT(LOGD_CONCHECK, + "connectivity: [IPv%c] periodic-check: set interval to %u seconds", + nm_utils_addr_family_to_char(addr_family), + new_interval); + priv->concheck_x[IS_IPv4].p_max_interval = new_interval; + } + + if (!new_interval) { + /* this will cancel any potentially pending timeout because max-interval is zero. + * But it logs a nice message... */ + concheck_periodic_schedule_do(self, addr_family, 0); + + /* also update the fake connectivity state. */ + concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE); + return; + } + + concheck_periodic_schedule_set(self, + addr_family, + check_now ? CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART + : CONCHECK_SCHEDULE_UPDATE_INTERVAL); +} + +void +nm_device_check_connectivity_update_interval(NMDevice *self) +{ + concheck_update_interval(self, AF_INET, TRUE); + concheck_update_interval(self, AF_INET6, TRUE); +} + +static void +concheck_update_state(NMDevice * self, + int addr_family, + NMConnectivityState state, + gboolean allow_periodic_bump) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + /* @state is a result of the connectivity check. We only expect a precise + * number of possible values. */ + nm_assert(NM_IN_SET(state, + NM_CONNECTIVITY_LIMITED, + NM_CONNECTIVITY_PORTAL, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_FAKE, + NM_CONNECTIVITY_NONE, + NM_CONNECTIVITY_ERROR)); + + if (state == NM_CONNECTIVITY_ERROR) { + /* on error, we don't change the current connectivity state, + * except making UNKNOWN to NONE. */ + state = priv->concheck_x[IS_IPv4].state; + if (state == NM_CONNECTIVITY_UNKNOWN) + state = NM_CONNECTIVITY_NONE; + } else if (state == NM_CONNECTIVITY_FAKE) { + /* If the connectivity check is disabled and we obtain a fake + * result, make an optimistic guess. */ + if (priv->state == NM_DEVICE_STATE_ACTIVATED) { + /* FIXME: the fake connectivity state depends on the availability of + * a default route. However, we have no mechanism that rechecks the + * value if a device route appears/disappears after the device + * was activated. */ + if (nm_device_get_best_default_route(self, AF_UNSPEC)) + state = NM_CONNECTIVITY_FULL; + else + state = NM_CONNECTIVITY_LIMITED; + } else + state = NM_CONNECTIVITY_NONE; + } + + if (priv->concheck_x[IS_IPv4].state == state) { + /* we got a connectivity update, but the state didn't change. If we were probing, + * we bump the probe frequency. */ + if (allow_periodic_bump) + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP); + return; + } + /* we need to update the probe interval before emitting signals. Emitting + * a signal might call back into NMDevice and change the probe settings. + * So, do that first. */ + if (state == NM_CONNECTIVITY_FULL) { + /* we reached full connectivity state. Stop probing by setting the + * interval to the max. */ + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_MAX); + } else if (priv->concheck_x[IS_IPv4].state == NM_CONNECTIVITY_FULL) { + /* we are about to loose connectivity. (re)start probing by setting + * the timeout interval to the min. */ + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_MIN); + } else { + if (allow_periodic_bump) + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP); + } + + _LOGD(LOGD_CONCHECK, + "connectivity state changed from %s to %s", + nm_connectivity_state_to_string(priv->concheck_x[IS_IPv4].state), + nm_connectivity_state_to_string(state)); + priv->concheck_x[IS_IPv4].state = state; + + _notify(self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY); + + if (priv->state == NM_DEVICE_STATE_ACTIVATED && !nm_device_sys_iface_state_is_external(self)) { + if (nm_device_get_best_default_route(self, AF_INET) + && !ip_config_merge_and_apply(self, AF_INET, TRUE)) + _LOGW(LOGD_IP4, "Failed to update IPv4 route metric"); + if (nm_device_get_best_default_route(self, AF_INET6) + && !ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "Failed to update IPv6 route metric"); + } +} + +static const char * +nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family) +{ + NMDeviceClass *klass; + NMConnection * connection = nm_device_get_applied_connection(self); + const char * method; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_val_if_fail(NM_IS_CONNECTION(connection), "" /* bogus */); + + method = nm_utils_get_ip_config_method(connection, addr_family); + + if ((IS_IPv4 && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) + || (!IS_IPv4 && nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))) { + klass = NM_DEVICE_GET_CLASS(self); + if (klass->get_auto_ip_config_method) { + const char *auto_method; + + auto_method = klass->get_auto_ip_config_method(self, addr_family); + if (auto_method) + return auto_method; + } + } + + return method; +} + +static void +concheck_handle_complete(NMDeviceConnectivityHandle *handle, GError *error) +{ + const int IS_IPv4 = NM_IS_IPv4(handle->addr_family); + + /* The moment we invoke the callback, we unlink it. It signals + * that @handle is handled -- as far as the callee of callback + * is concerned. */ + c_list_unlink(&handle->concheck_lst); + + if (handle->c_handle) + nm_connectivity_check_cancel(handle->c_handle); + + if (handle->callback) { + handle->callback(handle->self, + handle, + NM_DEVICE_GET_PRIVATE(handle->self)->concheck_x[IS_IPv4].state, + error, + handle->user_data); + } + + g_slice_free(NMDeviceConnectivityHandle, handle); +} + +static void +concheck_cb(NMConnectivity * connectivity, + NMConnectivityCheckHandle *c_handle, + NMConnectivityState state, + gpointer user_data) +{ + _nm_unused gs_unref_object NMDevice *self_keep_alive = NULL; + NMDevice * self; + NMDevicePrivate * priv; + NMDeviceConnectivityHandle * handle; + NMDeviceConnectivityHandle * other_handle; + gboolean handle_is_alive; + gboolean allow_periodic_bump; + gboolean any_periodic_before; + gboolean any_periodic_after; + guint64 seq; + + handle = user_data; + nm_assert(handle->c_handle == c_handle); + nm_assert(NM_IS_DEVICE(handle->self)); + + handle->c_handle = NULL; + self = handle->self; + + if (state == NM_CONNECTIVITY_CANCELLED) { + /* the only place where we nm_connectivity_check_cancel(@c_handle), is + * from inside concheck_handle_complete(). This is a recursive call, + * nothing to do. */ + _LOGT(LOGD_CONCHECK, + "connectivity: [IPv%c] complete check (seq:%llu, cancelled)", + nm_utils_addr_family_to_char(handle->addr_family), + (long long unsigned) handle->seq); + return; + } + + /* we keep NMConnectivity instance alive. It cannot be disposing. */ + nm_assert(state != NM_CONNECTIVITY_DISPOSING); + + self_keep_alive = g_object_ref(self); + + /* keep @self alive, while we invoke callbacks. */ + priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(handle && c_list_contains(&priv->concheck_lst_head, &handle->concheck_lst)); + + seq = handle->seq; + + _LOGT(LOGD_CONCHECK, + "connectivity: [Ipv%c] complete check (seq:%llu, state:%s)", + nm_utils_addr_family_to_char(handle->addr_family), + (long long unsigned) handle->seq, + nm_connectivity_state_to_string(state)); + + /* find out, if there are any periodic checks pending (either whether they + * were scheduled before or after @handle. */ + any_periodic_before = FALSE; + any_periodic_after = FALSE; + c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) { + if (other_handle->addr_family != handle->addr_family) + continue; + if (other_handle->is_periodic_bump_on_complete) { + if (other_handle->seq < seq) + any_periodic_before = TRUE; + else if (other_handle->seq > seq) + any_periodic_after = TRUE; + } + } + if (NM_IN_SET(state, NM_CONNECTIVITY_ERROR)) { + /* the request failed. We consider this periodic check only as completed if + * this was a periodic check, and there are not checks pending (either + * before or after this one). + * + * We allow_periodic_bump, if the request failed and there are + * still other requests periodic pending. */ + allow_periodic_bump = + handle->is_periodic_bump_on_complete && !any_periodic_before && !any_periodic_after; + } else { + /* the request succeeded. This marks the completion of a periodic check, + * if this handle was periodic, or any previously scheduled one (that + * we are going to complete below). */ + allow_periodic_bump = handle->is_periodic_bump_on_complete || any_periodic_before; + } + + /* first update the new state, and emit signals. */ + concheck_update_state(self, handle->addr_family, state, allow_periodic_bump); + + handle_is_alive = FALSE; + + /* we might have invoked callbacks during concheck_update_state(). The caller might have + * cancelled and thus destroyed @handle. We have to check whether handle is still alive, + * by searching it in the list of alive handles. + * + * Also, we might want to complete all pending callbacks that were started before + * @handle, as they are automatically obsoleted. */ +check_handles: + c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) { + if (other_handle->addr_family != handle->addr_family) + continue; + if (other_handle->seq >= seq) { + /* it's not guaranteed that @handle is still in the list. It might already + * be canceled while invoking callbacks for a previous other_handle. + * If it is already cancelled, @handle is a dangling pointer. + * + * Since @seq is assigned uniquely and increasing, either @other_handle is + * @handle (and thus, handle is alive), or it isn't. */ + if (other_handle == handle) + handle_is_alive = TRUE; + break; + } + + nm_assert(other_handle != handle); + + if (!NM_IN_SET(state, NM_CONNECTIVITY_ERROR)) { + /* we also want to complete handles that were started before the current + * @handle. Their response is out-dated. */ + concheck_handle_complete(other_handle, NULL); + + /* we invoked callbacks, other handles might be cancelled and removed from the list. + * Need to iterate the list from the start. */ + goto check_handles; + } + } + + if (!handle_is_alive) { + /* We didn't find @handle in the list of alive handles. Thus, the handles + * was cancelled while we were invoking events. Nothing to do, and don't + * touch the dangling pointer. */ + return; + } + + concheck_handle_complete(handle, NULL); +} + +static NMDeviceConnectivityHandle * +concheck_start(NMDevice * self, + int addr_family, + NMDeviceConnectivityCallback callback, + gpointer user_data, + gboolean is_periodic) +{ + static guint64 seq_counter = 0; + NMDevicePrivate * priv; + NMDeviceConnectivityHandle *handle; + const char * ifname; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + priv = NM_DEVICE_GET_PRIVATE(self); + + handle = g_slice_new0(NMDeviceConnectivityHandle); + handle->seq = ++seq_counter; + handle->self = self; + handle->callback = callback; + handle->user_data = user_data; + handle->is_periodic = is_periodic; + handle->is_periodic_bump = is_periodic; + handle->is_periodic_bump_on_complete = is_periodic; + handle->addr_family = addr_family; + + c_list_link_tail(&priv->concheck_lst_head, &handle->concheck_lst); + + _LOGT(LOGD_CONCHECK, + "connectivity: [IPv%c] start check (seq:%llu%s)", + nm_utils_addr_family_to_char(addr_family), + (long long unsigned) handle->seq, + is_periodic ? ", periodic-check" : ""); + + if (NM_IS_IPv4(addr_family) && !priv->concheck_rp_filter_checked) { + if ((ifname = nm_device_get_ip_iface_from_platform(self))) { + gboolean due_to_all; + int val; + + val = nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(nm_device_get_platform(self), + ifname, + TRUE, + &due_to_all); + if (val == 1) { + _LOGW(LOGD_CONCHECK, + "connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". " + "This might break connectivity checking for IPv4 on this device", + due_to_all ? "all" : ifname); + } + } + + /* we only check once per device. It's a warning after all. */ + priv->concheck_rp_filter_checked = TRUE; + } + + handle->c_handle = nm_connectivity_check_start(concheck_get_mgr(self), + handle->addr_family, + nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + nm_device_get_ip_iface(self), + concheck_cb, + handle); + return handle; +} + +NMDeviceConnectivityHandle * +nm_device_check_connectivity(NMDevice * self, + int addr_family, + NMDeviceConnectivityCallback callback, + gpointer user_data) +{ + if (!concheck_is_possible(self)) + return NULL; + + concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL); + return concheck_start(self, addr_family, callback, user_data, FALSE); +} + +void +nm_device_check_connectivity_cancel(NMDeviceConnectivityHandle *handle) +{ + gs_free_error GError *cancelled_error = NULL; + + g_return_if_fail(handle); + g_return_if_fail(NM_IS_DEVICE(handle->self)); + g_return_if_fail(!c_list_is_empty(&handle->concheck_lst)); + + /* nobody has access to periodic handles, and cannot cancel + * them externally. */ + nm_assert(!handle->is_periodic); + + nm_utils_error_set_cancelled(&cancelled_error, FALSE, "NMDevice"); + concheck_handle_complete(handle, cancelled_error); +} + +NMConnectivityState +nm_device_get_connectivity_state(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_CONNECTIVITY_UNKNOWN); + + priv = NM_DEVICE_GET_PRIVATE(self); + + switch (addr_family) { + case AF_INET: + case AF_INET6: + return priv->concheck_x[NM_IS_IPv4(addr_family)].state; + default: + nm_assert(addr_family == AF_UNSPEC); + return NM_MAX_WITH_CMP(nm_connectivity_state_cmp, + priv->concheck_x[0].state, + priv->concheck_x[1].state); + } +} + +/*****************************************************************************/ + +static SlaveInfo * +find_slave_info(NMDevice *self, NMDevice *slave) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + CList * iter; + SlaveInfo * info; + + c_list_for_each (iter, &priv->slaves) { + info = c_list_entry(iter, SlaveInfo, lst_slave); + if (info->slave == slave) + return info; + } + return NULL; +} + +/** + * nm_device_master_enslave_slave: + * @self: the master device + * @slave: the slave device to enslave + * @connection: (allow-none): the slave device's connection + * + * If @self is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function enslaves @slave. + * + * Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave + * other devices. + */ +static gboolean +nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *connection) +{ + NMDevicePrivate *priv; + SlaveInfo * info; + gboolean success = FALSE; + gboolean configure; + + g_return_val_if_fail(self != NULL, FALSE); + g_return_val_if_fail(slave != NULL, FALSE); + g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + info = find_slave_info(self, slave); + if (!info) + return FALSE; + + if (info->slave_is_enslaved) + success = TRUE; + else { + configure = (info->configure && connection != NULL); + if (configure) + g_return_val_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + success = NM_DEVICE_GET_CLASS(self)->enslave_slave(self, slave, connection, configure); + info->slave_is_enslaved = success; + } + + nm_device_slave_notify_enslave(info->slave, success); + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + nm_device_update_hw_address(self); + + /* Send ARP announcements if did not yet and have addresses. */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !priv->acd.announcing) + nm_device_arp_announce(self); + + /* Restart IP configuration if we're waiting for slaves. Do this + * after updating the hardware address as IP config may need the + * new address. + */ + if (success) { + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT) + nm_device_activate_stage3_ip_start(self, AF_INET); + + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT) + nm_device_activate_stage3_ip_start(self, AF_INET6); + } + + /* Since slave devices don't have their own IP configuration, + * set the MTU here. + */ + _commit_mtu(slave, NM_DEVICE_GET_PRIVATE(slave)->ip_config_4); + + return success; +} + +/** + * nm_device_master_release_one_slave: + * @self: the master device + * @slave: the slave device to release + * @configure: whether @self needs to actually release @slave + * @force: force the release of @slave even if it wasn't added + * to @master by NetworkManager + * @reason: the state change reason for the @slave + * + * If @self is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function releases the previously enslaved @slave and/or + * updates the state of @self and @slave to reflect its release. + */ +static void +nm_device_master_release_one_slave(NMDevice * self, + NMDevice * slave, + gboolean configure, + gboolean force, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + NMDevicePrivate *slave_priv; + SlaveInfo * info; + gs_unref_object NMDevice *self_free = NULL; + + g_return_if_fail(NM_DEVICE(self)); + g_return_if_fail(NM_DEVICE(slave)); + g_return_if_fail(!force || configure); + g_return_if_fail(NM_DEVICE_GET_CLASS(self)->release_slave != NULL); + + info = find_slave_info(self, slave); + + _LOGT(LOGD_CORE, + "master: release one slave %p/%s %s%s", + slave, + nm_device_get_iface(slave), + !info ? "(not registered)" : (info->slave_is_enslaved ? "(enslaved)" : "(not enslaved)"), + force ? " (force-configure)" : (configure ? " (configure)" : "")); + + if (!info) + g_return_if_reached(); + + priv = NM_DEVICE_GET_PRIVATE(self); + slave_priv = NM_DEVICE_GET_PRIVATE(slave); + + g_return_if_fail(self == slave_priv->master); + nm_assert(slave == info->slave); + + /* first, let subclasses handle the release ... */ + if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave) || force) + NM_DEVICE_GET_CLASS(self)->release_slave(self, slave, configure); + + /* raise notifications about the release, including clearing is_enslaved. */ + nm_device_slave_notify_release(slave, reason); + + /* keep both alive until the end of the function. + * Transfers ownership from slave_priv->master. */ + self_free = self; + + c_list_unlink(&info->lst_slave); + slave_priv->master = NULL; + + g_signal_handler_disconnect(slave, info->watch_id); + g_object_unref(slave); + g_slice_free(SlaveInfo, info); + + if (c_list_is_empty(&priv->slaves)) { + _active_connection_set_state_flags_full(self, + 0, + NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES); + } + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + nm_device_update_hw_address(self); + nm_device_set_unmanaged_by_flags(slave, + NM_UNMANAGED_IS_SLAVE, + NM_UNMAN_FLAG_OP_FORGET, + NM_DEVICE_STATE_REASON_REMOVED); +} + +/** + * can_unmanaged_external_down: + * @self: the device + * + * Check whether the device should stay NM_UNMANAGED_EXTERNAL_DOWN unless + * IFF_UP-ed externally. + */ +static gboolean +can_unmanaged_external_down(NMDevice *self) +{ + return !NM_DEVICE_GET_PRIVATE(self)->nm_owned && nm_device_is_software(self); +} + +static NMUnmanFlagOp +is_unmanaged_external_down(NMDevice *self, gboolean consider_can) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (consider_can && !NM_DEVICE_GET_CLASS(self)->can_unmanaged_external_down(self)) + return NM_UNMAN_FLAG_OP_FORGET; + + /* Manage externally-created software interfaces only when they are IFF_UP */ + if (priv->ifindex <= 0 || !priv->up + || !(!c_list_is_empty(&priv->slaves) + || nm_platform_link_can_assume(nm_device_get_platform(self), priv->ifindex))) + return NM_UNMAN_FLAG_OP_SET_UNMANAGED; + + return NM_UNMAN_FLAG_OP_SET_MANAGED; +} + +static void +set_unmanaged_external_down(NMDevice *self, gboolean only_if_unmanaged) +{ + NMUnmanFlagOp ext_flags; + + if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN)) + return; + + if (only_if_unmanaged) { + if (!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) + return; + } + + ext_flags = is_unmanaged_external_down(self, FALSE); + if (ext_flags != NM_UNMAN_FLAG_OP_SET_UNMANAGED) { + /* Ensure the assume check is queued before any queued state changes + * from the transition to UNAVAILABLE. + */ + nm_device_queue_recheck_assume(self); + } + + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_EXTERNAL_DOWN, + ext_flags, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); +} + +void +nm_device_update_dynamic_ip_setup(NMDevice *self) +{ + NMDevicePrivate *priv; + GError * error = NULL; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->state < NM_DEVICE_STATE_IP_CONFIG || priv->state > NM_DEVICE_STATE_ACTIVATED) + return; + + g_hash_table_remove_all(priv->ip6_saved_properties); + + if (priv->dhcp_data_4.client) { + if (!nm_device_dhcp4_renew(self, FALSE)) { + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->dhcp_data_6.client) { + if (!nm_device_dhcp6_renew(self, FALSE)) { + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->ndisc) { + /* FIXME: todo */ + } + if (priv->dnsmasq_manager) { + /* FIXME: todo */ + } + + if (priv->lldp_listener && nm_lldp_listener_is_running(priv->lldp_listener)) { + nm_lldp_listener_stop(priv->lldp_listener); + if (!nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error)) { + _LOGD(LOGD_DEVICE, + "LLDP listener %p could not be restarted: %s", + priv->lldp_listener, + error->message); + g_clear_error(&error); + } + } +} + +/*****************************************************************************/ + +static void +carrier_changed_notify(NMDevice *self, gboolean carrier) +{ + /* stub */ +} + +static void +carrier_changed(NMDevice *self, gboolean carrier) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->state <= NM_DEVICE_STATE_UNMANAGED) + return; + + nm_device_recheck_available_connections(self); + + /* ignore-carrier devices ignore all carrier-down events */ + if (priv->ignore_carrier && !carrier) + return; + + if (nm_device_is_master(self)) { + if (carrier) { + /* Force master to retry getting ip addresses when carrier + * is restored. */ + if (priv->state == NM_DEVICE_STATE_ACTIVATED) + nm_device_update_dynamic_ip_setup(self); + /* If needed, also resume IP configuration that is + * waiting for carrier. */ + if (nm_device_activate_ip4_state_in_wait(self)) + nm_device_activate_stage3_ip_start(self, AF_INET); + if (nm_device_activate_ip6_state_in_wait(self)) + nm_device_activate_stage3_ip_start(self, AF_INET6); + return; + } + /* fall-through and change state of device */ + } else if (priv->is_enslaved && !carrier) { + /* Slaves don't deactivate when they lose carrier; for + * bonds/teams in particular that would be actively + * counterproductive. + */ + return; + } + + if (carrier) { + if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { + nm_device_queue_state(self, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_CARRIER); + } else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) { + /* If the device is already in DISCONNECTED state without a carrier + * (probably because it is tagged for carrier ignore) ensure that + * when the carrier appears, auto connections are rechecked for + * the device. + */ + nm_device_emit_recheck_auto_activate(self); + } else if (priv->state == NM_DEVICE_STATE_ACTIVATED) { + /* If the device is active without a carrier (probably because it is + * tagged for carrier ignore) ensure that when the carrier appears we + * renew DHCP leases and such. + */ + nm_device_update_dynamic_ip_setup(self); + } + } else { + if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { + if (priv->queued_state.id && priv->queued_state.state >= NM_DEVICE_STATE_DISCONNECTED) + queued_state_clear(self); + } else { + nm_device_queue_state(self, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CARRIER); + } + } +} + +static gboolean +carrier_disconnected_action_cb(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + _LOGD(LOGD_DEVICE, + "carrier: link disconnected (calling deferred action) (id=%u)", + priv->carrier_defer_id); + + priv->carrier_defer_id = 0; + carrier_changed(self, FALSE); + return FALSE; +} + +static void +carrier_disconnected_action_cancel(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + guint id = priv->carrier_defer_id; + + if (nm_clear_g_source(&priv->carrier_defer_id)) { + _LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)", id); + } +} + +void +nm_device_set_carrier(NMDevice *self, gboolean carrier) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceState state = nm_device_get_state(self); + gboolean notify_flags = FALSE; + + if (priv->carrier == carrier) + return; + + if (NM_FLAGS_ALL(priv->capabilities, + NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) { + if (carrier) + priv->interface_flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER; + else + priv->interface_flags &= ~NM_DEVICE_INTERFACE_FLAG_CARRIER; + notify_flags = TRUE; + } + + priv->carrier = carrier; + if (notify_flags) + nm_gobject_notify_together(self, PROP_CARRIER, PROP_INTERFACE_FLAGS); + else + _notify(self, PROP_CARRIER); + + if (priv->carrier) { + _LOGI(LOGD_DEVICE, "carrier: link connected"); + carrier_disconnected_action_cancel(self); + NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier); + carrier_changed(self, TRUE); + + if (priv->carrier_wait_id) { + nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); + _carrier_wait_check_queued_act_request(self); + } + } else { + if (priv->carrier_wait_id) + nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); + NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier); + if (state <= NM_DEVICE_STATE_DISCONNECTED && !priv->queued_act_request) { + _LOGD(LOGD_DEVICE, "carrier: link disconnected"); + carrier_changed(self, FALSE); + } else { + gint64 now_ms, until_ms; + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms); + priv->carrier_defer_id = + g_timeout_add(until_ms - now_ms, carrier_disconnected_action_cb, self); + _LOGD(LOGD_DEVICE, + "carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)", + (long) (until_ms - now_ms), + priv->carrier_defer_id); + } + } +} + +static void +nm_device_set_carrier_from_platform(NMDevice *self) +{ + int ifindex; + + if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)) { + if (!nm_device_has_capability(self, NM_DEVICE_CAP_NONSTANDARD_CARRIER) + && (ifindex = nm_device_get_ip_ifindex(self)) > 0) { + nm_device_set_carrier( + self, + nm_platform_link_is_connected(nm_device_get_platform(self), ifindex)); + } + } else { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + /* Fake online link when carrier detection is not available. */ + if (!priv->carrier) { + priv->carrier = TRUE; + _notify(self, PROP_CARRIER); + } + } +} + +/*****************************************************************************/ + +static void +device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDevice * master; + nm_auto_nmpobj const NMPObject *plink_master_keep_alive = NULL; + const NMPlatformLink * plink_master; + + g_return_if_fail(plink); + + if (plink->master <= 0) + goto out; + + master = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master); + plink_master = nm_platform_link_get(nm_device_get_platform(self), plink->master); + plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master)); + + if (master == NULL && plink_master && nm_streq0(plink_master->name, "ovs-system") + && plink_master->type == NM_LINK_TYPE_OPENVSWITCH) { + _LOGD(LOGD_DEVICE, "the device claimed by openvswitch"); + goto out; + } + + priv->master_ifindex = plink->master; + + if (priv->master) { + if (plink->master > 0 && plink->master == nm_device_get_ifindex(priv->master)) { + /* call add-slave again. We expect @self already to be added to + * the master, but this also triggers a recheck-assume. */ + nm_device_master_add_slave(priv->master, self, FALSE); + goto out; + } + + nm_device_master_release_one_slave(priv->master, + self, + FALSE, + FALSE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + + if (master && NM_DEVICE_GET_CLASS(master)->enslave_slave) { + nm_device_master_add_slave(master, self, FALSE); + goto out; + } + + if (master) { + _LOGD(LOGD_DEVICE, + "enslaved to non-master-type device %s; ignoring", + nm_device_get_iface(master)); + } else { + _LOGD(LOGD_DEVICE, + "enslaved to unknown device %d (%s%s%s)", + plink->master, + NM_PRINT_FMT_QUOTED(plink_master, "\"", plink_master->name, "\"", "??")); + } + if (!priv->ifindex_changed_id) { + priv->ifindex_changed_id = g_signal_connect(nm_device_get_manager(self), + NM_MANAGER_DEVICE_IFINDEX_CHANGED, + G_CALLBACK(device_ifindex_changed_cb), + self); + } + return; + +out: + nm_clear_g_signal_handler(nm_device_get_manager(self), &priv->ifindex_changed_id); +} + +static void +device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->master_ifindex != nm_device_get_ifindex(device_changed)) + return; + + _LOGD(LOGD_DEVICE, + "master %s with ifindex %d appeared", + nm_device_get_iface(device_changed), + nm_device_get_ifindex(device_changed)); + if (!priv->device_link_changed_id) + priv->device_link_changed_id = g_idle_add((GSourceFunc) device_link_changed, self); +} + +static void +ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_unref_array GArray *addresses = NULL; + gs_unref_array GArray *dns_servers = NULL; + gs_unref_array GArray * dns_domains = NULL; + guint len; + guint i; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter ipconf_iter; + + if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER) + return; + + head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6); + addresses = + g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0); + nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) { + const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj); + NMNDiscAddress * ndisc_addr; + guint32 lifetime; + guint32 preferred; + + if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address)) + continue; + + if (addr->n_ifa_flags & IFA_F_TENTATIVE || addr->n_ifa_flags & IFA_F_DADFAILED) + continue; + + if (addr->plen != 64) + continue; + + lifetime = nm_utils_lifetime_get(addr->timestamp, + addr->lifetime, + addr->preferred, + NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000, + &preferred); + if (!lifetime) + continue; + + g_array_set_size(addresses, addresses->len + 1); + ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1); + ndisc_addr->address = addr->address; + ndisc_addr->expiry_msec = + _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime); + ndisc_addr->expiry_preferred_msec = + _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred); + } + + len = nm_ip6_config_get_num_nameservers(priv->ip_config_6); + dns_servers = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSServer), len); + g_array_set_size(dns_servers, len); + for (i = 0; i < len; i++) { + const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i); + NMNDiscDNSServer * ndisc_nameserver; + + ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i); + ndisc_nameserver->address = *nameserver; + ndisc_nameserver->expiry_msec = + _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME); + } + + len = nm_ip6_config_get_num_searches(priv->ip_config_6); + dns_domains = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSDomain), len); + g_array_set_size(dns_domains, len); + for (i = 0; i < len; i++) { + const char * search = nm_ip6_config_get_search(priv->ip_config_6, i); + NMNDiscDNSDomain *ndisc_search; + + ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i); + ndisc_search->domain = (char *) search; + ndisc_search->expiry_msec = + _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME); + } + + nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains); +} + +static void +device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceInterfaceFlags flags = NM_DEVICE_INTERFACE_FLAG_NONE; + + if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP)) + flags |= NM_DEVICE_INTERFACE_FLAG_UP; + if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP)) + flags |= NM_DEVICE_INTERFACE_FLAG_LOWER_UP; + + if (NM_FLAGS_ALL(priv->capabilities, + NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) { + if (priv->carrier) + flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER; + } else { + if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP)) + flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER; + } + + if (flags != priv->interface_flags) { + priv->interface_flags = flags; + _notify(self, PROP_INTERFACE_FLAGS); + } +} + +static gboolean +device_link_changed(NMDevice *self) +{ + NMDeviceClass * klass = NM_DEVICE_GET_CLASS(self); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + gboolean ip_ifname_changed = FALSE; + nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL; + const NMPlatformLink * pllink; + const char * str; + int ifindex; + gboolean was_up; + gboolean update_unmanaged_specs = FALSE; + gboolean got_hw_addr = FALSE, had_hw_addr; + gboolean seen_down = priv->device_link_changed_down; + + priv->device_link_changed_id = 0; + priv->device_link_changed_down = FALSE; + + ifindex = nm_device_get_ifindex(self); + if (ifindex <= 0) + return G_SOURCE_REMOVE; + pllink = nm_platform_link_get(nm_device_get_platform(self), ifindex); + if (!pllink) + return G_SOURCE_REMOVE; + + pllink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(pllink)); + + str = nm_platform_link_get_udi(nm_device_get_platform(self), pllink->ifindex); + if (!nm_streq0(str, priv->udi)) { + g_free(priv->udi); + priv->udi = g_strdup(str); + _notify(self, PROP_UDI); + } + + str = nm_platform_link_get_path(nm_device_get_platform(self), pllink->ifindex); + if (!nm_streq0(str, priv->path)) { + g_free(priv->path); + priv->path = g_strdup(str); + _notify(self, PROP_PATH); + } + + if (!nm_streq0(pllink->driver, priv->driver)) { + g_free(priv->driver); + priv->driver = g_strdup(pllink->driver); + _notify(self, PROP_DRIVER); + } + + _set_mtu(self, pllink->mtu); + + if (ifindex == nm_device_get_ip_ifindex(self)) + _stats_update_counters_from_pllink(self, pllink); + + had_hw_addr = (priv->hw_addr != NULL); + nm_device_update_hw_address(self); + got_hw_addr = (!had_hw_addr && priv->hw_addr); + nm_device_update_permanent_hw_address(self, FALSE); + + if (pllink->name[0] && !nm_streq(priv->iface, pllink->name)) { + _LOGI(LOGD_DEVICE, + "interface index %d renamed iface from '%s' to '%s'", + priv->ifindex, + priv->iface, + pllink->name); + g_free(priv->iface_); + priv->iface_ = g_strdup(pllink->name); + + /* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */ + ip_ifname_changed = !priv->ip_iface; + + if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) + nm_device_set_unmanaged_by_user_settings(self); + else + update_unmanaged_specs = TRUE; + + _notify(self, PROP_IFACE); + if (ip_ifname_changed) + _notify(self, PROP_IP_IFACE); + + /* Re-match available connections against the new interface name */ + nm_device_recheck_available_connections(self); + + /* Let any connections that use the new interface name have a chance + * to auto-activate on the device. + */ + nm_device_emit_recheck_auto_activate(self); + } + + if (priv->ndisc && pllink->inet6_token.id) { + if (nm_ndisc_set_iid(priv->ndisc, pllink->inet6_token)) + _LOGD(LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface); + } + + /* Update carrier from link event if applicable. */ + if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT) + && !nm_device_has_capability(self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) + nm_device_set_carrier(self, pllink->connected); + + device_update_interface_flags(self, pllink); + + klass->link_changed(self, pllink); + + /* Update DHCP, etc, if needed */ + if (ip_ifname_changed) + nm_device_update_dynamic_ip_setup(self); + + was_up = priv->up; + priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP); + + if (pllink->initialized && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { + NMDeviceStateReason reason; + + nm_device_set_unmanaged_by_user_udev(self); + nm_device_set_unmanaged_by_user_conf(self); + + reason = NM_DEVICE_STATE_REASON_NOW_MANAGED; + + /* If the device is a external-down candidated but no longer has external + * down set, we must clear the platform-unmanaged flag with reason + * "assumed". */ + if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN) + && !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) { + /* actually, user-udev overwrites external-down. So we only assume the device, + * when it is a external-down candidate, which is not managed via udev. */ + if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) { + /* Ensure the assume check is queued before any queued state changes + * from the transition to UNAVAILABLE. + */ + nm_device_queue_recheck_assume(self); + reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED; + } + } + + nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason); + } + + set_unmanaged_external_down(self, FALSE); + + device_recheck_slave_status(self, pllink); + + if (priv->up && (!was_up || seen_down)) { + /* the link was down and just came up. That happens for example, while changing MTU. + * We must restore IP configuration. */ + if (NM_IN_SET(priv->ip_state_4, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) { + if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) + _LOGW(LOGD_IP4, "failed applying IP4 config after link comes up again"); + } + + priv->linklocal6_dad_counter = 0; + if (NM_IN_SET(priv->ip_state_6, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) { + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "failed applying IP6 config after link comes up again"); + } + } + + if (update_unmanaged_specs) + nm_device_set_unmanaged_by_user_settings(self); + + if (got_hw_addr && !priv->up && nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE) { + /* + * If the device is UNAVAILABLE, any previous try to + * bring it up probably has failed because of the + * invalid hardware address; try again. + */ + nm_device_bring_up(self, TRUE, NULL); + nm_device_queue_recheck_available(self, + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_NONE); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +device_ip_link_changed(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const NMPlatformLink *pllink; + const char * ip_iface; + + priv->device_ip_link_changed_id = 0; + + if (priv->ip_ifindex <= 0) + return G_SOURCE_REMOVE; + + nm_assert(priv->ip_iface); + + pllink = nm_platform_link_get(nm_device_get_platform(self), priv->ip_ifindex); + if (!pllink) + return G_SOURCE_REMOVE; + + if (priv->ifindex <= 0 && pllink->mtu) + _set_mtu(self, pllink->mtu); + + _stats_update_counters_from_pllink(self, pllink); + + ip_iface = pllink->name; + + if (!ip_iface[0]) + return FALSE; + + if (!nm_streq(priv->ip_iface, ip_iface)) { + _LOGI(LOGD_DEVICE, + "ip-ifname: interface index %d renamed ip_iface (%d) from '%s' to '%s'", + priv->ifindex, + priv->ip_ifindex, + priv->ip_iface, + ip_iface); + g_free(priv->ip_iface_); + priv->ip_iface_ = g_strdup(ip_iface); + _notify(self, PROP_IP_IFACE); + + nm_device_update_dynamic_ip_setup(self); + } + + return G_SOURCE_REMOVE; +} + +static void +link_changed_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *info, + int change_type_i, + NMDevice * self) +{ + const NMPlatformSignalChangeType change_type = change_type_i; + NMDevicePrivate * priv; + + if (change_type != NM_PLATFORM_SIGNAL_CHANGED) + return; + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (ifindex == nm_device_get_ifindex(self)) { + if (!(info->n_ifi_flags & IFF_UP)) + priv->device_link_changed_down = TRUE; + if (!priv->device_link_changed_id) { + priv->device_link_changed_id = g_idle_add((GSourceFunc) device_link_changed, self); + _LOGD(LOGD_DEVICE, "queued link change for ifindex %d", ifindex); + } + } else if (ifindex == nm_device_get_ip_ifindex(self)) { + if (!priv->device_ip_link_changed_id) { + priv->device_ip_link_changed_id = + g_idle_add((GSourceFunc) device_ip_link_changed, self); + _LOGD(LOGD_DEVICE, "queued link change for ip-ifindex %d", ifindex); + } + } +} + +/*****************************************************************************/ + +static void +link_changed(NMDevice *self, const NMPlatformLink *pllink) +{ + /* stub implementation of virtual function to allow subclasses to chain up. */ +} + +static gboolean +link_type_compatible(NMDevice *self, NMLinkType link_type, gboolean *out_compatible, GError **error) +{ + NMDeviceClass *klass; + NMLinkType device_type; + guint i = 0; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + klass = NM_DEVICE_GET_CLASS(self); + + if (!klass->link_types) { + NM_SET_OUT(out_compatible, FALSE); + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Device does not support platform links"); + return FALSE; + } + + device_type = self->_priv->link_type; + if (device_type > NM_LINK_TYPE_UNKNOWN && device_type != link_type) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Needed link type 0x%x does not match the platform link type 0x%X", + device_type, + link_type); + return FALSE; + } + + for (i = 0; klass->link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) { + if (klass->link_types[i] == link_type) + return TRUE; + if (klass->link_types[i] == NM_LINK_TYPE_ANY) + return TRUE; + } + + NM_SET_OUT(out_compatible, FALSE); + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Device does not support platform link type 0x%X", + link_type); + return FALSE; +} + +/** + * nm_device_realize_start(): + * @self: the #NMDevice + * @plink: an existing platform link or %NULL + * @assume_state_guess_assume: set the guess_assume state. + * @assume_state_connection_uuid: set the connection uuid to assume. + * @set_nm_owned: for software device, if TRUE set nm-owned. + * @unmanaged_user_explicit: the user-explicit unmanaged flag to apply + * on the device initially. + * @out_compatible: %TRUE on return if @self is compatible with @plink + * @error: location to store error, or %NULL + * + * Initializes and sets up the device using existing backing resources. Before + * the device is ready for use nm_device_realize_finish() must be called. + * @out_compatible will only be set if @plink is not %NULL, and + * + * Important: if nm_device_realize_start() returns %TRUE, the caller MUST + * also call nm_device_realize_finish() to balance g_object_freeze_notify(). + * + * Returns: %TRUE on success, %FALSE on error + */ +gboolean +nm_device_realize_start(NMDevice * self, + const NMPlatformLink *plink, + gboolean assume_state_guess_assume, + const char * assume_state_connection_uuid, + gboolean set_nm_owned, + NMUnmanFlagOp unmanaged_user_explicit, + gboolean * out_compatible, + GError ** error) +{ + nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL; + + nm_assert(!plink || NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK); + + NM_SET_OUT(out_compatible, TRUE); + + if (plink) { + if (!nm_streq0(nm_device_get_iface(self), plink->name)) { + NM_SET_OUT(out_compatible, FALSE); + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Device interface name does not match platform link"); + return FALSE; + } + + if (!link_type_compatible(self, plink->type, out_compatible, error)) + return FALSE; + + plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink)); + } + + realize_start_setup(self, + plink, + assume_state_guess_assume, + assume_state_connection_uuid, + set_nm_owned, + unmanaged_user_explicit, + FALSE); + return TRUE; +} + +/** + * nm_device_create_and_realize(): + * @self: the #NMDevice + * @connection: the #NMConnection being activated + * @parent: the parent #NMDevice if any + * @error: location to store error, or %NULL + * + * Creates any backing resources needed to realize the device to proceed + * with activating @connection. + * + * Returns: %TRUE on success, %FALSE on error + */ +gboolean +nm_device_create_and_realize(NMDevice * self, + NMConnection *connection, + NMDevice * parent, + GError ** error) +{ + nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL; + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const NMPlatformLink * plink; + gboolean nm_owned; + + /* Must be set before device is realized */ + plink = nm_platform_link_get_by_ifname(nm_device_get_platform(self), priv->iface); + nm_owned = !plink || !link_type_compatible(self, plink->type, NULL, NULL); + _LOGD(LOGD_DEVICE, "create (is %snm-owned)", nm_owned ? "" : "not "); + + plink = NULL; + /* Create any resources the device needs */ + if (NM_DEVICE_GET_CLASS(self)->create_and_realize) { + if (!NM_DEVICE_GET_CLASS(self)->create_and_realize(self, connection, parent, &plink, error)) + return FALSE; + if (plink) { + nm_assert(NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK); + plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink)); + } + } + + priv->nm_owned = nm_owned; + + realize_start_setup(self, + plink, + FALSE, /* assume_state_guess_assume */ + NULL, /* assume_state_connection_uuid */ + FALSE, + NM_UNMAN_FLAG_OP_FORGET, + TRUE); + nm_device_realize_finish(self, plink); + + if (nm_device_get_managed(self, FALSE)) { + nm_device_state_changed(self, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + return TRUE; +} + +static gboolean +can_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink) +{ + return TRUE; +} + +void +nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const char * str; + gboolean ifindex_changed; + guint32 mtu; + + if (!NM_DEVICE_GET_CLASS(self)->can_update_from_platform_link(self, plink)) + return; + + g_return_if_fail(plink == NULL || link_type_compatible(self, plink->type, NULL, NULL)); + + str = plink ? nm_platform_link_get_udi(nm_device_get_platform(self), plink->ifindex) : NULL; + if (!nm_streq0(str, priv->udi)) { + g_free(priv->udi); + priv->udi = g_strdup(str); + _notify(self, PROP_UDI); + } + + str = plink ? nm_platform_link_get_path(nm_device_get_platform(self), plink->ifindex) : NULL; + if (!nm_streq0(str, priv->path)) { + g_free(priv->path); + priv->path = g_strdup(str); + _notify(self, PROP_PATH); + } + + if (plink && !nm_str_is_empty(plink->name) && nm_utils_strdup_reset(&priv->iface_, plink->name)) + _notify(self, PROP_IFACE); + + str = plink ? plink->driver : NULL; + if (!nm_streq0(str, priv->driver)) { + g_free(priv->driver); + priv->driver = g_strdup(str); + _notify(self, PROP_DRIVER); + } + + if (plink) { + priv->up = NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP); + if (plink->ifindex == nm_device_get_ip_ifindex(self)) + _stats_update_counters_from_pllink(self, plink); + } else { + priv->up = FALSE; + } + + mtu = plink ? plink->mtu : 0; + _set_mtu(self, mtu); + + ifindex_changed = _set_ifindex(self, plink ? plink->ifindex : 0, FALSE); + + if (ifindex_changed) + NM_DEVICE_GET_CLASS(self)->link_changed(self, plink); + + device_update_interface_flags(self, plink); +} + +/*****************************************************************************/ + +static void +sriov_op_start(NMDevice *self, SriovOp *op) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(!priv->sriov.pending); + + op->cancellable = g_cancellable_new(); + op->device = g_object_ref(self); + priv->sriov.pending = op; + + nm_platform_link_set_sriov_params_async(nm_device_get_platform(self), + priv->ifindex, + op->num_vfs, + op->autoprobe, + sriov_op_cb, + op, + op->cancellable); +} + +static void +sriov_op_cb(GError *error, gpointer user_data) +{ + SriovOp * op = user_data; + gs_unref_object NMDevice *self = op->device; + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(op == priv->sriov.pending); + + g_clear_object(&op->cancellable); + + if (op->callback) + op->callback(error, op->callback_data); + + priv->sriov.pending = NULL; + nm_g_slice_free(op); + + if (priv->sriov.next) { + sriov_op_start(self, g_steal_pointer(&priv->sriov.next)); + } +} + +static void +sriov_op_queue_op(NMDevice *self, SriovOp *op) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->sriov.next) { + SriovOp *op_next = g_steal_pointer(&priv->sriov.next); + + priv->sriov.next = op; + + /* Cancel the next operation immediately */ + if (op_next->callback) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, FALSE, NULL); + op_next->callback(error, op_next->callback_data); + } + + nm_g_slice_free(op_next); + return; + } + + if (priv->sriov.pending) { + priv->sriov.next = op; + g_cancellable_cancel(priv->sriov.pending->cancellable); + return; + } + + if (op) + sriov_op_start(self, op); +} + +static void +sriov_op_queue(NMDevice * self, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data) +{ + SriovOp *op; + + /* We usually never want to cancel an async write operation, unless it's superseded + * by a newer operation (that resets the state). That is, because we need to ensure + * that we never end up doing two concurrent writes (since we write on a background + * thread, that would be unordered/racy). + * Of course, since we queue requests only per-device, when devices get renamed we + * might end up writing the same sysctl concurrently still. But that's really + * unlikely, and don't rename after udev completes! + * + * The "next" operation is not yet even started. It can be replaced/canceled right away + * when a newer request comes. + * The "pending" operation is currently ongoing, and we may cancel it if + * we have a follow-up operation (queued in "next"). Unless we have a such + * a newer request, we cannot cancel it! + * + * FIXME(shutdown): However, during shutdown we don't have a follow-up write request to cancel + * this operation and we have to give it at least some time to complete. The solution is that + * we register a way to abort the last call during shutdown, and after NM_SHUTDOWN_TIMEOUT_MS + * grace period we pull the plug and cancel it. */ + + op = g_slice_new(SriovOp); + *op = (SriovOp){ + .num_vfs = num_vfs, + .autoprobe = autoprobe, + .callback = callback, + .callback_data = callback_data, + }; + sriov_op_queue_op(self, op); +} + +static void +device_init_static_sriov_num_vfs(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free char * value = NULL; + int num_vfs; + + if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) { + value = nm_config_data_get_device_config(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, + self, + NULL); + num_vfs = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, -1); + if (num_vfs >= 0) + sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL); + } +} + +static void +config_changed(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMDevice * self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state > NM_DEVICE_STATE_ACTIVATED) { + priv->ignore_carrier = nm_config_data_get_ignore_carrier(config_data, self); + if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES)) + device_init_static_sriov_num_vfs(self); + } +} + +static void +realize_start_notify(NMDevice *self, const NMPlatformLink *pllink) +{ + /* the default implementation of realize_start_notify() just calls + * link_changed() -- which by default does nothing. */ + NM_DEVICE_GET_CLASS(self)->link_changed(self, pllink); +} + +/** + * realize_start_setup(): + * @self: the #NMDevice + * @plink: the #NMPlatformLink if backed by a kernel netdevice + * @assume_state_guess_assume: set the guess_assume state. + * @assume_state_connection_uuid: set the connection uuid to assume. + * @set_nm_owned: if TRUE and device is a software-device, set nm-owned. + * TRUE. + * @unmanaged_user_explicit: the user-explict unmanaged flag to set. + * @force_platform_init: if TRUE the platform-init unmanaged flag is + * forcefully cleared. + * + * Update the device from backing resource properties (like hardware + * addresses, carrier states, driver/firmware info, etc). This function + * should only change properties for this device, and should not perform + * any tasks that affect other interfaces (like master/slave or parent/child + * stuff). + */ +static void +realize_start_setup(NMDevice * self, + const NMPlatformLink *plink, + gboolean assume_state_guess_assume, + const char * assume_state_connection_uuid, + gboolean set_nm_owned, + NMUnmanFlagOp unmanaged_user_explicit, + gboolean force_platform_init) +{ + NMDevicePrivate * priv; + NMDeviceClass * klass; + NMPlatform * platform; + NMDeviceCapabilities capabilities = 0; + NMConfig * config; + guint real_rate; + gboolean unmanaged; + + /* plink is a NMPlatformLink type, however, we require it to come from the platform + * cache (where else would it come from?). */ + nm_assert(!plink || NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK); + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + /* The device should not be realized */ + g_return_if_fail(!priv->real); + g_return_if_fail(nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)); + g_return_if_fail(priv->ip_ifindex <= 0); + g_return_if_fail(priv->ip_iface == NULL); + g_return_if_fail(!priv->queued_ip_config_id_4); + g_return_if_fail(!priv->queued_ip_config_id_6); + + _LOGD(LOGD_DEVICE, + "start setup of %s, kernel ifindex %d", + G_OBJECT_TYPE_NAME(self), + plink ? plink->ifindex : 0); + + klass = NM_DEVICE_GET_CLASS(self); + platform = nm_device_get_platform(self); + + /* Balanced by a thaw in nm_device_realize_finish() */ + g_object_freeze_notify(G_OBJECT(self)); + + priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE; + priv->mtu_initial = 0; + priv->ip6_mtu_initial = 0; + priv->ip6_mtu = 0; + _set_mtu(self, 0); + + _assume_state_set(self, assume_state_guess_assume, assume_state_connection_uuid); + + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + + if (plink) + nm_device_update_from_platform_link(self, plink); + + if (priv->ifindex > 0) { + priv->physical_port_id = nm_platform_link_get_physical_port_id(platform, priv->ifindex); + _notify(self, PROP_PHYSICAL_PORT_ID); + + priv->dev_id = nm_platform_link_get_dev_id(platform, priv->ifindex); + + if (nm_platform_link_is_software(platform, priv->ifindex)) + capabilities |= NM_DEVICE_CAP_IS_SOFTWARE; + + _set_mtu(self, nm_platform_link_get_mtu(platform, priv->ifindex)); + + nm_platform_link_get_driver_info(platform, + priv->ifindex, + NULL, + &priv->driver_version, + &priv->firmware_version); + if (priv->driver_version) + _notify(self, PROP_DRIVER_VERSION); + if (priv->firmware_version) + _notify(self, PROP_FIRMWARE_VERSION); + + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) + priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled(platform, priv->ifindex); + + if (nm_platform_link_supports_sriov(platform, priv->ifindex)) + capabilities |= NM_DEVICE_CAP_SRIOV; + } + + if (klass->get_generic_capabilities) + capabilities |= klass->get_generic_capabilities(self); + + _add_capabilities(self, capabilities); + + if (!priv->nm_owned && set_nm_owned && nm_device_is_software(self)) { + priv->nm_owned = TRUE; + _LOGD(LOGD_DEVICE, "set nm-owned from state file"); + } + + if (!priv->udi) { + /* Use a placeholder UDI until we get a real one */ + if (priv->udi_id == 0) { + static guint64 udi_id_counter = 0; + + priv->udi_id = ++udi_id_counter; + } + priv->udi = g_strdup_printf("/virtual/device/placeholder/%" G_GUINT64_FORMAT, priv->udi_id); + _notify(self, PROP_UDI); + } + + nm_device_update_hw_address(self); + nm_device_update_initial_hw_address(self); + nm_device_update_permanent_hw_address(self, FALSE); + + /* Note: initial hardware address must be read before calling get_ignore_carrier() */ + config = nm_config_get(); + priv->ignore_carrier = nm_config_data_get_ignore_carrier(nm_config_get_data(config), self); + if (!priv->config_changed_id) { + priv->config_changed_id = g_signal_connect(config, + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed), + self); + } + + nm_device_set_carrier_from_platform(self); + + nm_assert(!priv->stats.timeout_id); + real_rate = _stats_refresh_rate_real(priv->stats.refresh_rate_ms); + if (real_rate) + priv->stats.timeout_id = g_timeout_add(real_rate, _stats_timeout_cb, self); + + klass->realize_start_notify(self, plink); + + nm_assert(!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_EXPLICIT)); + nm_device_set_unmanaged_flags(self, NM_UNMANAGED_USER_EXPLICIT, unmanaged_user_explicit); + + /* Do not manage externally created software devices until they are IFF_UP + * or have IP addressing */ + nm_device_set_unmanaged_flags(self, + NM_UNMANAGED_EXTERNAL_DOWN, + is_unmanaged_external_down(self, TRUE)); + + /* Unmanaged the loopback device with an explicit NM_UNMANAGED_BY_TYPE flag. + * Later we might want to manage 'lo' too. Currently, that doesn't work because + * NetworkManager might down the interface or remove the 127.0.0.1 address. */ + nm_device_set_unmanaged_flags(self, NM_UNMANAGED_BY_TYPE, is_loopback(self)); + + nm_device_set_unmanaged_by_user_udev(self); + nm_device_set_unmanaged_by_user_conf(self); + + unmanaged = plink && !plink->initialized && !force_platform_init; + + nm_device_set_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT, unmanaged); +} + +/** + * nm_device_realize_finish(): + * @self: the #NMDevice + * @plink: the #NMPlatformLink if backed by a kernel netdevice + * + * Update the device's master/slave or parent/child relationships from + * backing resource properties. After this function finishes, the device + * is ready for network connectivity. + */ +void +nm_device_realize_finish(NMDevice *self, const NMPlatformLink *plink) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(!plink || link_type_compatible(self, plink->type, NULL, NULL)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_if_fail(!priv->real); + + if (plink) + device_recheck_slave_status(self, plink); + + priv->update_ip_config_completed_v4 = FALSE; + priv->update_ip_config_completed_v6 = FALSE; + + priv->real = TRUE; + _notify(self, PROP_REAL); + + nm_device_recheck_available_connections(self); + + /* Balanced by a freeze in realize_start_setup(). */ + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +unrealize_notify(NMDevice *self) +{ + /* Stub implementation for unrealize_notify(). It does nothing, + * but allows derived classes to uniformly invoke the parent + * implementation. */ +} + +static gboolean +available_connections_check_delete_unrealized_on_idle(gpointer user_data) +{ + NMDevice * self = user_data; + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + priv->check_delete_unrealized_id = 0; + + if (g_hash_table_size(priv->available_connections) == 0 && !nm_device_is_real(self)) + g_signal_emit(self, signals[REMOVED], 0); + + return G_SOURCE_REMOVE; +} + +static void +available_connections_check_delete_unrealized(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + /* always rescheadule the remove signal. */ + nm_clear_g_source(&priv->check_delete_unrealized_id); + + if (g_hash_table_size(priv->available_connections) == 0 && !nm_device_is_real(self)) + priv->check_delete_unrealized_id = + g_idle_add(available_connections_check_delete_unrealized_on_idle, self); +} + +/** + * nm_device_unrealize(): + * @self: the #NMDevice + * @remove_resources: if %TRUE, remove backing resources + * @error: location to store error, or %NULL + * + * Clears any properties that depend on backing resources (kernel devices, + * etc) and removes those resources if @remove_resources is %TRUE. + * + * Returns: %TRUE on success, %FALSE on error + */ +gboolean +nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + if (!nm_device_is_software(self) || !nm_device_is_real(self)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_SOFTWARE, + "This device is not a software device or is not realized"); + return FALSE; + } + + priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_val_if_fail(priv->iface != NULL, FALSE); + g_return_val_if_fail(priv->real, FALSE); + + ifindex = nm_device_get_ifindex(self); + + _LOGD(LOGD_DEVICE, "unrealize (ifindex %d)", ifindex > 0 ? ifindex : 0); + + nm_device_assume_state_reset(self); + + if (remove_resources) { + if (NM_DEVICE_GET_CLASS(self)->unrealize) { + if (!NM_DEVICE_GET_CLASS(self)->unrealize(self, error)) + return FALSE; + } else if (ifindex > 0) { + nm_platform_link_delete(nm_device_get_platform(self), ifindex); + } + } + + nm_clear_g_source(&priv->queued_ip_config_id_4); + nm_clear_g_source(&priv->queued_ip_config_id_6); + + g_object_freeze_notify(G_OBJECT(self)); + NM_DEVICE_GET_CLASS(self)->unrealize_notify(self); + + _parent_set_ifindex(self, 0, FALSE); + + _set_ifindex(self, 0, FALSE); + _set_ifindex(self, 0, TRUE); + if (nm_clear_g_free(&priv->ip_iface_)) + _notify(self, PROP_IP_IFACE); + + priv->master_ifindex = 0; + + _set_mtu(self, 0); + + if (priv->driver_version) { + nm_clear_g_free(&priv->driver_version); + _notify(self, PROP_DRIVER_VERSION); + } + if (priv->firmware_version) { + nm_clear_g_free(&priv->firmware_version); + _notify(self, PROP_FIRMWARE_VERSION); + } + if (priv->udi) { + nm_clear_g_free(&priv->udi); + _notify(self, PROP_UDI); + } + if (priv->path) { + nm_clear_g_free(&priv->path); + _notify(self, PROP_PATH); + } + if (priv->physical_port_id) { + nm_clear_g_free(&priv->physical_port_id); + _notify(self, PROP_PHYSICAL_PORT_ID); + } + + nm_clear_g_source(&priv->stats.timeout_id); + _stats_update_counters(self, 0, 0); + + priv->hw_addr_len_ = 0; + if (nm_clear_g_free(&priv->hw_addr)) + _notify(self, PROP_HW_ADDRESS); + priv->hw_addr_type = HW_ADDR_TYPE_UNSET; + if (nm_clear_g_free(&priv->hw_addr_perm)) + _notify(self, PROP_PERM_HW_ADDRESS); + nm_clear_g_free(&priv->hw_addr_initial); + + priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED; + if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities) + priv->capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self); + _notify(self, PROP_CAPABILITIES); + + nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id); + nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id); + + priv->real = FALSE; + _notify(self, PROP_REAL); + + g_object_thaw_notify(G_OBJECT(self)); + + nm_device_set_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT, TRUE); + + nm_device_set_unmanaged_flags(self, + NM_UNMANAGED_PARENT | NM_UNMANAGED_BY_TYPE + | NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_USER_EXPLICIT + | NM_UNMANAGED_EXTERNAL_DOWN | NM_UNMANAGED_IS_SLAVE, + NM_UNMAN_FLAG_OP_FORGET); + + nm_device_state_changed(self, + NM_DEVICE_STATE_UNMANAGED, + remove_resources ? NM_DEVICE_STATE_REASON_USER_REQUESTED + : NM_DEVICE_STATE_REASON_NOW_UNMANAGED); + + /* Garbage-collect unneeded unrealized devices. */ + nm_device_recheck_available_connections(self); + + return TRUE; +} + +void +nm_device_notify_availability_maybe_changed(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->state != NM_DEVICE_STATE_DISCONNECTED) + return; + + /* A device could have stayed disconnected because it would + * want to register with a network server that now become + * available. */ + nm_device_recheck_available_connections(self); + if (g_hash_table_size(priv->available_connections) > 0) + nm_device_emit_recheck_auto_activate(self); +} + +/** + * nm_device_owns_iface(): + * @self: the #NMDevice + * @iface: an interface name + * + * Called by the manager to ask if the device or any of its components owns + * @iface. For example, a WWAN implementation would return %TRUE for an + * ethernet interface name that was owned by the WWAN device's modem component, + * because that ethernet interface is controlled by the WWAN device and cannot + * be used independently of the WWAN device. + * + * Returns: %TRUE if @self or its components own the interface name, + * %FALSE if not + */ +gboolean +nm_device_owns_iface(NMDevice *self, const char *iface) +{ + if (NM_DEVICE_GET_CLASS(self)->owns_iface) + return NM_DEVICE_GET_CLASS(self)->owns_iface(self, iface); + return FALSE; +} + +NMConnection * +nm_device_new_default_connection(NMDevice *self) +{ + NMConnection *connection; + GError * error = NULL; + + if (!NM_DEVICE_GET_CLASS(self)->new_default_connection) + return NULL; + + connection = NM_DEVICE_GET_CLASS(self)->new_default_connection(self); + if (!connection) + return NULL; + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) { + _LOGD(LOGD_DEVICE, "device generated an invalid default connection: %s", error->message); + g_error_free(error); + g_return_val_if_reached(NULL); + } + + return connection; +} + +static void +slave_state_changed(NMDevice * slave, + NMDeviceState slave_new_state, + NMDeviceState slave_old_state, + NMDeviceStateReason reason, + NMDevice * self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean release = FALSE; + gboolean configure; + + _LOGD(LOGD_DEVICE, + "slave %s state change %d (%s) -> %d (%s)", + nm_device_get_iface(slave), + slave_old_state, + nm_device_state_to_str(slave_old_state), + slave_new_state, + nm_device_state_to_str(slave_new_state)); + + /* Don't try to enslave slaves until the master is ready */ + if (priv->state < NM_DEVICE_STATE_CONFIG) + return; + + if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG) + nm_device_master_enslave_slave(self, slave, nm_device_get_applied_connection(slave)); + else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED) + release = TRUE; + else if (slave_new_state <= NM_DEVICE_STATE_DISCONNECTED + && slave_old_state > NM_DEVICE_STATE_DISCONNECTED) { + /* Catch failures due to unavailable or unmanaged */ + release = TRUE; + } + + if (release) { + configure = priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED + && nm_device_sys_iface_state_get(slave) != NM_DEVICE_SYS_IFACE_STATE_EXTERNAL; + + nm_device_master_release_one_slave(self, slave, configure, FALSE, reason); + /* Bridge/bond/team interfaces are left up until manually deactivated */ + if (c_list_is_empty(&priv->slaves) && priv->state == NM_DEVICE_STATE_ACTIVATED) + _LOGD(LOGD_DEVICE, "last slave removed; remaining activated"); + } +} + +/** + * nm_device_master_add_slave: + * @self: the master device + * @slave: the slave device to enslave + * @configure: pass %TRUE if the slave should be configured by the master, or + * %FALSE if it is already configured outside NetworkManager + * + * If @self is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function adds @slave to the slave list for later enslavement. + * + * Returns: %TRUE if the slave was enslaved. %FALSE means, the slave was already + * enslaved and nothing was done. + */ +static gboolean +nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure) +{ + NMDevicePrivate *priv; + NMDevicePrivate *slave_priv; + SlaveInfo * info; + gboolean changed = FALSE; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(slave), FALSE); + g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + slave_priv = NM_DEVICE_GET_PRIVATE(slave); + + info = find_slave_info(self, slave); + + _LOGT(LOGD_CORE, + "master: add one slave %p/%s%s", + slave, + nm_device_get_iface(slave), + info ? " (already registered)" : ""); + + if (configure) + g_return_val_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + if (!info) { + g_return_val_if_fail(!slave_priv->master, FALSE); + g_return_val_if_fail(!slave_priv->is_enslaved, FALSE); + + info = g_slice_new0(SlaveInfo); + info->slave = g_object_ref(slave); + info->configure = configure; + info->watch_id = + g_signal_connect(slave, NM_DEVICE_STATE_CHANGED, G_CALLBACK(slave_state_changed), self); + c_list_link_tail(&priv->slaves, &info->lst_slave); + slave_priv->master = g_object_ref(self); + + _active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES); + + /* no need to emit + * + * _notify (slave, PROP_MASTER); + * + * because slave_priv->is_enslaved is not true, thus the value + * didn't change yet. */ + + g_warn_if_fail(!NM_FLAGS_HAS(slave_priv->unmanaged_mask, NM_UNMANAGED_IS_SLAVE)); + nm_device_set_unmanaged_by_flags(slave, + NM_UNMANAGED_IS_SLAVE, + FALSE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + changed = TRUE; + } else + g_return_val_if_fail(slave_priv->master == self, FALSE); + + nm_device_queue_recheck_assume(self); + nm_device_queue_recheck_assume(slave); + + return changed; +} + +/** + * nm_device_master_check_slave_physical_port: + * @self: the master device + * @slave: a slave device + * @log_domain: domain to log a warning in + * + * Checks if @self already has a slave with the same #NMDevice:physical-port-id + * as @slave, and logs a warning if so. + */ +void +nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLogDomain log_domain) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const char * slave_physical_port_id, *existing_physical_port_id; + SlaveInfo * info; + CList * iter; + + slave_physical_port_id = nm_device_get_physical_port_id(slave); + if (!slave_physical_port_id) + return; + + c_list_for_each (iter, &priv->slaves) { + info = c_list_entry(iter, SlaveInfo, lst_slave); + if (info->slave == slave) + continue; + + existing_physical_port_id = nm_device_get_physical_port_id(info->slave); + if (nm_streq0(slave_physical_port_id, existing_physical_port_id)) { + _LOGW(log_domain, + "slave %s shares a physical port with existing slave %s", + nm_device_get_ip_iface(slave), + nm_device_get_ip_iface(info->slave)); + /* Since this function will get called for every slave, we only have + * to warn about the first match we find; if there are other matches + * later in the list, we will have already warned about them matching + * @existing earlier. + */ + return; + } + } +} + +/* release all slaves */ +void +nm_device_master_release_slaves(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceStateReason reason; + CList * iter, *safe; + + /* Don't release the slaves if this connection doesn't belong to NM. */ + if (nm_device_sys_iface_state_is_external(self)) + return; + + reason = priv->state_reason; + if (priv->state == NM_DEVICE_STATE_FAILED) + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + + c_list_for_each_safe (iter, safe, &priv->slaves) { + SlaveInfo *info = c_list_entry(iter, SlaveInfo, lst_slave); + + nm_device_master_release_one_slave(self, info->slave, TRUE, FALSE, reason); + } +} + +/** + * nm_device_is_master: + * @self: the device + * + * Returns: %TRUE if the device can have slaves + */ +gboolean +nm_device_is_master(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + return NM_DEVICE_GET_CLASS(self)->is_master; +} + +/** + * nm_device_get_master: + * @self: the device + * + * If @self has been enslaved by another device, this returns that + * device. Otherwise, it returns %NULL. (In particular, note that if + * @self is in the process of activating as a slave, but has not yet + * been enslaved by its master, this will return %NULL.) + * + * Returns: (transfer none): @self's master, or %NULL + */ +NMDevice * +nm_device_get_master(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->is_enslaved) { + g_return_val_if_fail(priv->master, NULL); + return priv->master; + } + return NULL; +} + +static gboolean +get_ip_config_may_fail(NMDevice *self, int addr_family) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip; + + connection = nm_device_get_applied_connection(self); + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + + return !s_ip || nm_setting_ip_config_get_may_fail(s_ip); +} + +/* + * check_ip_state + * + * When @full_state_update is TRUE, transition the device from IP_CONFIG to the + * next state according to the outcome of IPv4 and IPv6 configuration. @may_fail + * indicates that we are called just after the initial configuration and thus + * IPv4/IPv6 are allowed to fail if the ipvx.may-fail properties say so, because + * the IP methods couldn't even be started. + * If @full_state_update is FALSE, just check if the connection should be failed + * due to the state of both ip families and the ipvx.may-fail settings. + */ +static void +check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + gboolean ip4_disabled = FALSE, ip6_disabled = FALSE; + NMSettingIPConfig *s_ip4, *s_ip6; + NMDeviceState state; + + if (full_state_update && nm_device_get_state(self) != NM_DEVICE_STATE_IP_CONFIG) + return; + + /* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the + * master to enslave us. */ + if (nm_active_connection_get_master(NM_ACTIVE_CONNECTION(priv->act_request.obj)) + && !priv->is_enslaved) + return; + + s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG); + if (s_ip4 + && nm_streq0(nm_setting_ip_config_get_method(s_ip4), NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + ip4_disabled = TRUE; + + s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG); + if (s_ip6 + && NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6), + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + ip6_disabled = TRUE; + + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE + && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) { + /* Both method completed (or disabled), proceed with activation */ + nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); + return; + } + + if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL + || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE)) + && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL + || (ip6_disabled && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) { + /* Either both methods failed, or only one failed and the other is + * disabled */ + if (nm_device_sys_iface_state_is_external_or_assume(self)) { + /* We have assumed configuration, but couldn't redo it. No problem, + * move to check state. */ + _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE); + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE); + state = NM_DEVICE_STATE_IP_CHECK; + } else if (may_fail && get_ip_config_may_fail(self, AF_INET) + && get_ip_config_may_fail(self, AF_INET6)) { + /* Couldn't start either IPv6 and IPv4 autoconfiguration, + * but both are allowed to fail. */ + state = NM_DEVICE_STATE_SECONDARIES; + } else { + /* Autoconfiguration attempted without success. */ + state = NM_DEVICE_STATE_FAILED; + } + + if (full_state_update || state == NM_DEVICE_STATE_FAILED) { + nm_device_state_changed(self, state, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + return; + } + + /* If a method is still pending but required, wait */ + if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET)) + return; + if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET6)) + return; + + /* If at least a method has completed, proceed with activation */ + if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled) + || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_disabled)) { + if (full_state_update) + nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); + return; + } +} + +/** + * nm_device_slave_notify_enslave: + * @self: the slave device + * @success: whether the enslaving operation succeeded + * + * Notifies a slave that either it has been enslaved, or else its master tried + * to enslave it and failed. + */ +static void +nm_device_slave_notify_enslave(NMDevice *self, gboolean success) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection = nm_device_get_applied_connection(self); + gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG); + + g_return_if_fail(priv->master); + + if (!priv->is_enslaved) { + if (success) { + if (activating) { + _LOGI(LOGD_DEVICE, + "Activation: connection '%s' enslaved, continuing activation", + nm_connection_get_id(connection)); + } else + _LOGI(LOGD_DEVICE, "enslaved to %s", nm_device_get_iface(priv->master)); + + priv->is_enslaved = TRUE; + + _notify(self, PROP_MASTER); + _notify(priv->master, PROP_SLAVES); + } else if (activating) { + _LOGW(LOGD_DEVICE, + "Activation: connection '%s' could not be enslaved", + nm_connection_get_id(connection)); + } + } + + if (activating) { + if (success) + check_ip_state(self, FALSE, TRUE); + else + nm_device_queue_state(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN); + } else + nm_device_queue_recheck_assume(self); +} + +/** + * nm_device_slave_notify_release: + * @self: the slave device + * @reason: the reason associated with the state change + * + * Notifies a slave that it has been released, and why. + */ +static void +nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection = nm_device_get_applied_connection(self); + const char * master_status; + + g_return_if_fail(priv->master); + + if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state <= NM_DEVICE_STATE_ACTIVATED) { + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: + master_status = "failed"; + break; + case NM_DEVICE_STATE_REASON_USER_REQUESTED: + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + master_status = "deactivated by user request"; + break; + case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + master_status = "deactivated because master was removed"; + break; + default: + master_status = "deactivated"; + break; + } + + _LOGD(LOGD_DEVICE, + "Activation: connection '%s' master %s", + nm_connection_get_id(connection), + master_status); + + /* Cancel any pending activation sources */ + _cancel_activation(self); + nm_device_queue_state(self, NM_DEVICE_STATE_DEACTIVATING, reason); + } else + _LOGI(LOGD_DEVICE, "released from master device %s", nm_device_get_iface(priv->master)); + + if (priv->is_enslaved) { + priv->is_enslaved = FALSE; + _notify(self, PROP_MASTER); + _notify(priv->master, PROP_SLAVES); + } +} + +/** + * nm_device_removed: + * @self: the #NMDevice + * @unconfigure_ip_config: whether to clear the IP config objects + * of the device (provided, it is still not cleared at this point). + * + * Called by the manager when the device was removed. Releases the device from + * the master in case it's enslaved. + */ +void +nm_device_removed(NMDevice *self, gboolean unconfigure_ip_config) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->master) { + /* this is called when something externally messes with the slave or during shut-down. + * Release the slave from master, but don't touch the device. */ + nm_device_master_release_one_slave(priv->master, + self, + FALSE, + FALSE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + + if (unconfigure_ip_config) { + nm_device_set_ip_config(self, AF_INET, NULL, FALSE, NULL); + nm_device_set_ip_config(self, AF_INET6, NULL, FALSE, NULL); + } else { + if (priv->dhcp_data_4.client) + nm_dhcp_client_stop(priv->dhcp_data_4.client, FALSE); + if (priv->dhcp_data_6.client) + nm_dhcp_client_stop(priv->dhcp_data_6.client, FALSE); + } +} + +static gboolean +is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->carrier || priv->ignore_carrier) + return TRUE; + + if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER)) + return TRUE; + + /* master types are always available even without carrier. */ + if (nm_device_is_master(self)) + return TRUE; + + return FALSE; +} + +/** + * nm_device_is_available: + * @self: the #NMDevice + * @flags: additional flags to influence the check. Flags have the + * meaning to increase the availability of a device. + * + * Checks if @self would currently be capable of activating a + * connection. In particular, it checks that the device is ready (eg, + * is not missing firmware), that it has carrier (if necessary), and + * that any necessary external software (eg, ModemManager, + * wpa_supplicant) is available. + * + * @self can only be in a state higher than + * %NM_DEVICE_STATE_UNAVAILABLE when nm_device_is_available() returns + * %TRUE. (But note that it can still be %NM_DEVICE_STATE_UNMANAGED + * when it is available.) + * + * Returns: %TRUE or %FALSE + */ +gboolean +nm_device_is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->firmware_missing) + return FALSE; + + return NM_DEVICE_GET_CLASS(self)->is_available(self, flags); +} + +gboolean +nm_device_ignore_carrier_by_default(NMDevice *self) +{ + /* master types ignore-carrier by default. */ + return nm_device_is_master(self); +} + +gboolean +nm_device_get_enabled(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + if (NM_DEVICE_GET_CLASS(self)->get_enabled) + return NM_DEVICE_GET_CLASS(self)->get_enabled(self); + return TRUE; +} + +void +nm_device_set_enabled(NMDevice *self, gboolean enabled) +{ + g_return_if_fail(NM_IS_DEVICE(self)); + + if (NM_DEVICE_GET_CLASS(self)->set_enabled) + NM_DEVICE_GET_CLASS(self)->set_enabled(self, enabled); +} + +static NM_UTILS_FLAGS2STR_DEFINE(_autoconnect_blocked_flags_to_string, + NMDeviceAutoconnectBlockedFlags, + NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_USER, "user"), + NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN, + "wrong-pin"), + NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT, + "manual-disconnect"), ); + +NMDeviceAutoconnectBlockedFlags +nm_device_autoconnect_blocked_get(NMDevice *self, NMDeviceAutoconnectBlockedFlags mask) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + if (mask == 0) + mask = NM_DEVICE_AUTOCONNECT_BLOCKED_ALL; + + priv = NM_DEVICE_GET_PRIVATE(self); + return priv->autoconnect_blocked_flags & mask; +} + +void +nm_device_autoconnect_blocked_set_full(NMDevice * self, + NMDeviceAutoconnectBlockedFlags mask, + NMDeviceAutoconnectBlockedFlags value) +{ + NMDevicePrivate *priv; + gboolean changed; + char buf1[128], buf2[128]; + + g_return_if_fail(NM_IS_DEVICE(self)); + nm_assert(mask); + nm_assert(!NM_FLAGS_ANY(mask, ~NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)); + nm_assert(!NM_FLAGS_ANY(value, ~mask)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + value = (priv->autoconnect_blocked_flags & ~mask) | (mask & value); + if (value == priv->autoconnect_blocked_flags) + return; + + changed = ((!value) != (!priv->autoconnect_blocked_flags)); + + _LOGT( + LOGD_DEVICE, + "autoconnect-blocked: set \"%s\" (was \"%s\")", + _autoconnect_blocked_flags_to_string(value, buf1, sizeof(buf1)), + _autoconnect_blocked_flags_to_string(priv->autoconnect_blocked_flags, buf2, sizeof(buf2))); + + priv->autoconnect_blocked_flags = value; + nm_assert(priv->autoconnect_blocked_flags == value); + if (changed) + _notify(self, PROP_AUTOCONNECT); +} + +static gboolean +autoconnect_allowed_accumulator(GSignalInvocationHint *ihint, + GValue * return_accu, + const GValue * handler_return, + gpointer data) +{ + if (!g_value_get_boolean(handler_return)) + g_value_set_boolean(return_accu, FALSE); + return TRUE; +} + +/** + * nm_device_autoconnect_allowed: + * @self: the #NMDevice + * + * Returns: %TRUE if the device can be auto-connected immediately, taking + * transient conditions into account (like companion devices that may wish to + * block autoconnect for a time). + */ +gboolean +nm_device_autoconnect_allowed(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceClass * klass = NM_DEVICE_GET_CLASS(self); + GValue instance = G_VALUE_INIT; + GValue retval = G_VALUE_INIT; + + if (nm_device_autoconnect_blocked_get(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)) + return FALSE; + + if (klass->get_autoconnect_allowed && !klass->get_autoconnect_allowed(self)) + return FALSE; + + if (!nm_device_get_enabled(self)) + return FALSE; + + if (nm_device_is_real(self)) { + if (priv->state < NM_DEVICE_STATE_DISCONNECTED) + return FALSE; + } else { + if (!nm_device_check_unrealized_device_managed(self)) + return FALSE; + } + + if (priv->delete_on_deactivate_data) + return FALSE; + + /* The 'autoconnect-allowed' signal is emitted on a device to allow + * other listeners to block autoconnect on the device if they wish. + * This is mainly used by the OLPC Mesh devices to block autoconnect + * on their companion Wi-Fi device as they share radio resources and + * cannot be connected at the same time. + */ + + g_value_init(&instance, G_TYPE_OBJECT); + g_value_set_object(&instance, self); + + g_value_init(&retval, G_TYPE_BOOLEAN); + g_value_set_boolean(&retval, TRUE); + + /* Use g_signal_emitv() rather than g_signal_emit() to avoid the return + * value being changed if no handlers are connected */ + g_signal_emitv(&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval); + g_value_unset(&instance); + + return g_value_get_boolean(&retval); +} + +static gboolean +can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object) +{ + nm_assert(!specific_object || !*specific_object); + return TRUE; +} + +/** + * nm_device_can_auto_connect: + * @self: an #NMDevice + * @sett_conn: a #NMSettingsConnection + * @specific_object: (out) (transfer full): on output, the path of an + * object associated with the returned connection, to be passed to + * nm_manager_activate_connection(), or %NULL. + * + * Checks if @sett_conn can be auto-activated on @self right now. + * This requires, at a minimum, that the connection be compatible with + * @self, and that it have the #NMSettingConnection:autoconnect property + * set, and that the device allow auto connections. Some devices impose + * additional requirements. (Eg, a Wi-Fi connection can only be activated + * if its SSID was seen in the last scan.) + * + * Returns: %TRUE, if the @sett_conn can be auto-activated. + **/ +gboolean +nm_device_can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), FALSE); + g_return_val_if_fail(!specific_object || !*specific_object, FALSE); + + /* the caller must ensure that nm_device_autoconnect_allowed() returns + * TRUE as well. This is done, because nm_device_can_auto_connect() + * has only one caller, and it iterates over a list of available + * connections. + * + * Hence, we don't need to re-check nm_device_autoconnect_allowed() + * over and over again. The caller is supposed to do that. */ + nm_assert(nm_device_autoconnect_allowed(self)); + + if (!nm_device_check_connection_available(self, + nm_settings_connection_get_connection(sett_conn), + NM_DEVICE_CHECK_CON_AVAILABLE_NONE, + NULL, + NULL)) + return FALSE; + + if (!NM_DEVICE_GET_CLASS(self)->can_auto_connect(self, sett_conn, specific_object)) + return FALSE; + + return TRUE; +} + +static gboolean +device_has_config(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + /* Check for IP configuration. */ + if (priv->ip_config_4 && nm_ip4_config_get_num_addresses(priv->ip_config_4)) + return TRUE; + if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6)) + return TRUE; + + /* The existence of a software device is good enough. */ + if (nm_device_is_software(self) && nm_device_is_real(self)) + return TRUE; + + /* Master-slave relationship is also a configuration */ + if (!c_list_is_empty(&priv->slaves) + || nm_platform_link_get_master(nm_device_get_platform(self), priv->ifindex) > 0) + return TRUE; + + return FALSE; +} + +/** + * nm_device_master_update_slave_connection: + * @self: the master #NMDevice + * @slave: the slave #NMDevice + * @connection: the #NMConnection to update with the slave settings + * @GError: (out): error description + * + * Reads the slave configuration for @slave and updates @connection with those + * properties. This invokes a virtual function on the master device @self. + * + * Returns: %TRUE if the configuration was read and @connection updated, + * %FALSE on failure. + */ +gboolean +nm_device_master_update_slave_connection(NMDevice * self, + NMDevice * slave, + NMConnection *connection, + GError ** error) +{ + NMDeviceClass *klass; + gboolean success; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(slave, FALSE); + g_return_val_if_fail(connection, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + g_return_val_if_fail(nm_connection_get_setting_connection(connection), FALSE); + + g_return_val_if_fail(nm_device_get_iface(self), FALSE); + + klass = NM_DEVICE_GET_CLASS(self); + if (klass->master_update_slave_connection) { + success = klass->master_update_slave_connection(self, slave, connection, error); + + g_return_val_if_fail(!error || (success && !*error) || *error, success); + return success; + } + + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "master device '%s' cannot update a slave connection for slave device '%s' (master " + "type not supported?)", + nm_device_get_iface(self), + nm_device_get_iface(slave)); + return FALSE; +} + +static gboolean +_get_maybe_ipv6_disabled(NMDevice *self) +{ + NMPlatform *platform; + int ifindex; + const char *path; + char ifname[IFNAMSIZ]; + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + return FALSE; + + platform = nm_device_get_platform(self); + if (!nm_platform_if_indextoname(platform, ifindex, ifname)) + return FALSE; + + path = nm_sprintf_bufa(128, "/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifname); + return (nm_platform_sysctl_get_int32(platform, NMP_SYSCTL_PATHID_ABSOLUTE(path), 0) == 0); +} + +NMConnection * +nm_device_generate_connection(NMDevice *self, + NMDevice *master, + gboolean *out_maybe_later, + GError ** error) +{ + NMDeviceClass * klass = NM_DEVICE_GET_CLASS(self); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const char * ifname = nm_device_get_iface(self); + gs_unref_object NMConnection *connection = NULL; + NMSetting * s_con; + NMSetting * s_ip4; + NMSetting * s_ip6; + char uuid[37]; + const char * ip4_method, *ip6_method; + GError * local = NULL; + const NMPlatformLink * pllink; + + NM_SET_OUT(out_maybe_later, FALSE); + + /* If update_connection() is not implemented, just fail. */ + if (!klass->update_connection) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "device class %s does not support generating a connection", + G_OBJECT_TYPE_NAME(self)); + return NULL; + } + + /* Return NULL if device is unconfigured. */ + if (!device_has_config(self)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "device has no existing configuration"); + return NULL; + } + + connection = nm_simple_connection_new(); + s_con = nm_setting_connection_new(); + + g_object_set(s_con, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_buf(uuid), + NM_SETTING_CONNECTION_ID, + ifname, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + ifname, + NM_SETTING_CONNECTION_TIMESTAMP, + (guint64) time(NULL), + NULL); + + if (klass->connection_type_supported) + g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type_supported, NULL); + + nm_connection_add_setting(connection, s_con); + + /* If the device is a slave, update various slave settings */ + if (master) { + if (!nm_device_master_update_slave_connection(master, self, connection, &local)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "master device '%s' failed to update slave connection: %s", + nm_device_get_iface(master), + local->message); + g_error_free(local); + return NULL; + } + } else { + /* Only regular and master devices get IP configuration; slaves do not */ + s_ip4 = nm_ip4_config_create_setting(priv->ip_config_4); + nm_connection_add_setting(connection, s_ip4); + + s_ip6 = nm_ip6_config_create_setting(priv->ip_config_6, _get_maybe_ipv6_disabled(self)); + nm_connection_add_setting(connection, s_ip6); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + pllink = nm_platform_link_get(nm_device_get_platform(self), priv->ifindex); + if (pllink && pllink->inet6_token.id) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_object_set(s_ip6, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + NM_IN6_ADDR_GEN_MODE_EUI64, + NM_SETTING_IP6_CONFIG_TOKEN, + nm_utils_inet6_interface_identifier_to_token(pllink->inet6_token, sbuf), + NULL); + } + } + + klass->update_connection(self, connection); + + if (!nm_connection_normalize(connection, NULL, NULL, &local)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "generated connection does not verify: %s", + local->message); + g_error_free(local); + return NULL; + } + + /* Ignore the connection if it has no IP configuration, + * no slave configuration, and is not a master interface. + */ + ip4_method = nm_utils_get_ip_config_method(connection, AF_INET); + ip6_method = nm_utils_get_ip_config_method(connection, AF_INET6); + if (nm_streq0(ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) + && NM_IN_STRSET(ip6_method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED) + && !nm_setting_connection_get_master(NM_SETTING_CONNECTION(s_con)) + && c_list_is_empty(&priv->slaves)) { + NM_SET_OUT(out_maybe_later, TRUE); + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "ignoring generated connection (no IP and not in master-slave relationship)"); + return NULL; + } + + /* Ignore any IPv6LL-only, not master connections without slaves, + * unless they are in the assume-ipv6ll-only list. + */ + if (nm_streq0(ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) + && nm_streq0(ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) + && !nm_setting_connection_get_master(NM_SETTING_CONNECTION(s_con)) + && c_list_is_empty(&priv->slaves) + && !nm_config_data_get_assume_ipv6ll_only(NM_CONFIG_GET_DATA, self)) { + _LOGD(LOGD_DEVICE, + "ignoring generated connection (IPv6LL-only and not in master-slave relationship)"); + NM_SET_OUT(out_maybe_later, TRUE); + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "ignoring generated connection (IPv6LL-only and not in master-slave relationship)"); + return NULL; + } + + return g_steal_pointer(&connection); +} + +/** + * nm_device_complete_connection: + * + * Complete the connection. This is solely used for AddAndActivate where the user + * may pass in an incomplete connection and a device, and the device tries to + * make sense of it and complete it for activation. Otherwise, this is not + * used. + * + * Returns: success or failure. + */ +gboolean +nm_device_complete_connection(NMDevice * self, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceClass *klass; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + klass = NM_DEVICE_GET_CLASS(self); + + if (!klass->complete_connection) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "Device class %s had no complete_connection method", + G_OBJECT_TYPE_NAME(self)); + return FALSE; + } + + if (!klass->complete_connection(self, connection, specific_object, existing_connections, error)) + return FALSE; + + if (!nm_connection_normalize(connection, NULL, NULL, error)) + return FALSE; + + return nm_device_check_connection_compatible(self, connection, error); +} + +gboolean +nm_device_match_parent(NMDevice *self, const char *parent) +{ + NMDevice *parent_device; + + g_return_val_if_fail(parent, FALSE); + + parent_device = nm_device_parent_get_device(self); + if (!parent_device) + return FALSE; + + if (nm_utils_is_uuid(parent)) { + NMConnection *connection; + + /* If the parent is a UUID, the connection matches when there is + * no connection active on the device or when a connection with + * that UUID is active. + */ + connection = nm_device_get_applied_connection(parent_device); + if (connection && !nm_streq0(parent, nm_connection_get_uuid(connection))) + return FALSE; + } else { + /* Interface name */ + if (!nm_streq0(parent, nm_device_get_ip_iface(parent_device))) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_device_match_parent_hwaddr(NMDevice * device, + NMConnection *connection, + gboolean fail_if_no_hwaddr) +{ + NMSettingWired *s_wired; + NMDevice * parent_device; + const char * setting_mac; + const char * parent_mac; + + s_wired = nm_connection_get_setting_wired(connection); + if (!s_wired) + return !fail_if_no_hwaddr; + + setting_mac = nm_setting_wired_get_mac_address(s_wired); + if (!setting_mac) + return !fail_if_no_hwaddr; + + parent_device = nm_device_parent_get_device(device); + if (!parent_device) + return !fail_if_no_hwaddr; + + parent_mac = nm_device_get_permanent_hw_address(parent_device); + return parent_mac && nm_utils_hwaddr_matches(setting_mac, -1, parent_mac, -1); +} + +static gboolean +check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const char * device_iface = nm_device_get_iface(self); + gs_free_error GError *local = NULL; + gs_free char * conn_iface = NULL; + NMDeviceClass * klass; + NMSettingMatch * s_match; + + klass = NM_DEVICE_GET_CLASS(self); + if (klass->connection_type_check_compatible) { + if (!_nm_connection_check_main_setting(connection, + klass->connection_type_check_compatible, + error)) + return FALSE; + } else if (klass->check_connection_compatible == check_connection_compatible) { + /* the device class does not implement check_connection_compatible nor set + * connection_type_check_compatible. That means, it is by default not compatible + * with any connection type. */ + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "device does not support any connections"); + return FALSE; + } + + conn_iface = nm_manager_get_connection_iface(NM_MANAGER_GET, connection, NULL, NULL, &local); + + /* We always need a interface name for virtual devices, but for + * physical ones a connection without interface name is fine for + * any device. */ + if (!conn_iface) { + if (nm_connection_is_virtual(connection)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "cannot get interface name due to %s", + local->message); + return FALSE; + } + } else if (!nm_streq0(conn_iface, device_iface)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "mismatching interface name"); + return FALSE; + } + + s_match = (NMSettingMatch *) nm_connection_get_setting(connection, NM_TYPE_SETTING_MATCH); + if (s_match) { + const char *const *patterns; + const char * device_driver; + guint num_patterns = 0; + + patterns = nm_setting_match_get_interface_names(s_match, &num_patterns); + if (!nm_wildcard_match_check(device_iface, patterns, num_patterns)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not satisfy match.interface-name property"); + return FALSE; + } + + patterns = nm_setting_match_get_kernel_command_lines(s_match, &num_patterns); + if (num_patterns > 0 + && !nm_utils_kernel_cmdline_match_check(nm_utils_proc_cmdline_split(), + patterns, + num_patterns, + error)) + return FALSE; + + device_driver = nm_device_get_driver(self); + patterns = nm_setting_match_get_drivers(s_match, &num_patterns); + if (!nm_wildcard_match_check(device_driver, patterns, num_patterns)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not satisfy match.driver property"); + return FALSE; + } + + patterns = nm_setting_match_get_paths(s_match, &num_patterns); + if (!nm_wildcard_match_check(priv->path, patterns, num_patterns)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "device does not satisfy match.path property"); + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_device_check_connection_compatible: + * @self: an #NMDevice + * @connection: an #NMConnection + * @error: optional reason why it is incompatible. Note that the + * error code is set to %NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + * if the profile is fundamentally incompatible with the device + * (most commonly, because the device-type does not support the + * connection-type). + * + * Checks if @connection could potentially be activated on @self. + * This means only that @self has the proper capabilities, and that + * @connection is not locked to some other device. It does not + * necessarily mean that @connection could be activated on @self + * right now. (Eg, it might refer to a Wi-Fi network that is not + * currently available.) + * + * Returns: #TRUE if @connection could potentially be activated on + * @self. + */ +gboolean +nm_device_check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self, connection, error); +} + +gboolean +nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave) +{ + NMSettingConnection *s_con; + const char * connection_type, *slave_type; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(slave), FALSE); + + if (!nm_device_is_master(self)) + return FALSE; + + /* All masters should have connection type set */ + connection_type = NM_DEVICE_GET_CLASS(self)->connection_type_supported; + g_return_val_if_fail(connection_type, FALSE); + + s_con = nm_connection_get_setting_connection(slave); + g_assert(s_con); + slave_type = nm_setting_connection_get_slave_type(s_con); + if (!slave_type) + return FALSE; + + return nm_streq(connection_type, slave_type); +} + +/** + * nm_device_can_assume_connections: + * @self: #NMDevice instance + * + * This is a convenience function to determine whether connection assumption + * is available for this device. + * + * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not + */ +static gboolean +nm_device_can_assume_connections(NMDevice *self) +{ + return !!NM_DEVICE_GET_CLASS(self)->update_connection; +} + +static gboolean +unmanaged_on_quit(NMDevice *self) +{ + NMConnection *connection; + + /* NMDeviceWifi overwrites this function to always unmanage wifi devices. + * + * For all other types, if the device type can assume connections, we leave + * it up on quit. + * + * Originally, we would only keep devices up that can be assumed afterwards. + * However, that meant we unmanged layer-2 only devices. So, this was step + * by step refined to unmanage less (commit 25aaaab3, rh#1311988, rh#1333983). + * But there are more scenarios where we also want to keep the device up + * (rh#1378418, rh#1371126). */ + if (!nm_device_can_assume_connections(self)) + return TRUE; + + /* the only exception are IPv4 shared connections. We unmanage them on quit. */ + connection = nm_device_get_applied_connection(self); + if (connection) { + if (NM_IN_STRSET(nm_utils_get_ip_config_method(connection, AF_INET), + NM_SETTING_IP4_CONFIG_METHOD_SHARED)) { + /* shared connections are to be unmangaed. */ + return TRUE; + } + } + + return FALSE; +} + +gboolean +nm_device_unmanage_on_quit(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + return NM_DEVICE_GET_CLASS(self)->unmanaged_on_quit(self); +} + +static gboolean +nm_device_emit_recheck_assume(gpointer user_data) +{ + NMDevice * self = user_data; + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + priv->recheck_assume_id = 0; + if (!nm_device_get_act_request(self)) + g_signal_emit(self, signals[RECHECK_ASSUME], 0); + + return G_SOURCE_REMOVE; +} + +void +nm_device_queue_recheck_assume(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->recheck_assume_id && nm_device_can_assume_connections(self)) + priv->recheck_assume_id = g_idle_add(nm_device_emit_recheck_assume, self); +} + +static gboolean +recheck_available(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean now_available; + NMDeviceState state = nm_device_get_state(self); + NMDeviceState new_state = NM_DEVICE_STATE_UNKNOWN; + + priv->recheck_available.call_id = 0; + + now_available = nm_device_is_available(self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE); + + if (state == NM_DEVICE_STATE_UNAVAILABLE && now_available) { + new_state = NM_DEVICE_STATE_DISCONNECTED; + nm_device_queue_state(self, new_state, priv->recheck_available.available_reason); + } else if (state >= NM_DEVICE_STATE_DISCONNECTED && !now_available) { + new_state = NM_DEVICE_STATE_UNAVAILABLE; + nm_device_queue_state(self, new_state, priv->recheck_available.unavailable_reason); + } + + if (new_state > NM_DEVICE_STATE_UNKNOWN) { + _LOGD(LOGD_DEVICE, + "is %savailable, %s %s", + now_available ? "" : "not ", + new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for" + : "will transition to", + nm_device_state_to_str(new_state == NM_DEVICE_STATE_UNAVAILABLE ? state : new_state)); + + priv->recheck_available.available_reason = NM_DEVICE_STATE_REASON_NONE; + priv->recheck_available.unavailable_reason = NM_DEVICE_STATE_REASON_NONE; + } + + if (priv->recheck_available.call_id == 0) + nm_device_remove_pending_action(self, NM_PENDING_ACTION_RECHECK_AVAILABLE, TRUE); + + return G_SOURCE_REMOVE; +} + +void +nm_device_queue_recheck_available(NMDevice * self, + NMDeviceStateReason available_reason, + NMDeviceStateReason unavailable_reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->recheck_available.available_reason = available_reason; + priv->recheck_available.unavailable_reason = unavailable_reason; + if (!priv->recheck_available.call_id) { + priv->recheck_available.call_id = g_idle_add(recheck_available, self); + nm_device_add_pending_action (self, NM_PENDING_ACTION_RECHECK_AVAILABLE, + FALSE /* cannot assert, because of how recheck_available() first clears + * the call-id and postpones removing the pending-action. */); + } +} + +void +nm_device_emit_recheck_auto_activate(NMDevice *self) +{ + g_signal_emit(self, signals[RECHECK_AUTO_ACTIVATE], 0); +} + +static void +dnsmasq_state_changed_cb(NMDnsMasqManager *manager, guint32 status, gpointer user_data) +{ + NMDevice *self = NM_DEVICE(user_data); + + switch (status) { + case NM_DNSMASQ_STATUS_DEAD: + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED); + break; + default: + break; + } +} + +void +nm_device_auth_request(NMDevice * self, + GDBusMethodInvocation * context, + NMConnection * connection, + const char * permission, + gboolean allow_interaction, + GCancellable * cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data) +{ + nm_manager_device_auth_request(nm_device_get_manager(self), + self, + context, + connection, + permission, + allow_interaction, + cancellable, + callback, + user_data); +} + +/*****************************************************************************/ + +static void +activation_source_clear(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + if (priv->activation_source_id_x[IS_IPv4] != 0) { + _LOGD(LOGD_DEVICE, + "activation-stage: clear %s,v%c (id %u)", + _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]), + nm_utils_addr_family_to_char(addr_family), + priv->activation_source_id_x[IS_IPv4]); + nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]); + priv->activation_source_func_x[IS_IPv4] = NULL; + } +} + +static gboolean +activation_source_handle_cb(NMDevice *self, int addr_family) +{ + NMDevicePrivate * priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + ActivationHandleFunc activation_source_func; + guint activation_source_id; + + g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + activation_source_func = priv->activation_source_func_x[IS_IPv4]; + activation_source_id = priv->activation_source_id_x[IS_IPv4]; + + g_return_val_if_fail(activation_source_id != 0, G_SOURCE_REMOVE); + nm_assert(activation_source_func); + + priv->activation_source_func_x[IS_IPv4] = NULL; + priv->activation_source_id_x[IS_IPv4] = 0; + + _LOGD(LOGD_DEVICE, + "activation-stage: invoke %s,v%c (id %u)", + _activation_func_to_string(activation_source_func), + nm_utils_addr_family_to_char(addr_family), + activation_source_id); + + activation_source_func(self); + + _LOGT(LOGD_DEVICE, + "activation-stage: complete %s,v%c (id %u)", + _activation_func_to_string(activation_source_func), + nm_utils_addr_family_to_char(addr_family), + activation_source_id); + + return G_SOURCE_REMOVE; +} + +static gboolean +activation_source_handle_cb_4(gpointer user_data) +{ + return activation_source_handle_cb(user_data, AF_INET); +} + +static gboolean +activation_source_handle_cb_6(gpointer user_data) +{ + return activation_source_handle_cb(user_data, AF_INET6); +} + +static void +activation_source_schedule(NMDevice *self, ActivationHandleFunc func, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + guint new_id = 0; + + if (priv->activation_source_id_x[IS_IPv4] != 0 + && priv->activation_source_func_x[IS_IPv4] == func) { + /* Scheduling the same stage multiple times is fine. */ + _LOGT(LOGD_DEVICE, + "activation-stage: already scheduled %s,v%c (id %u)", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family), + priv->activation_source_id_x[IS_IPv4]); + return; + } + + new_id = + g_idle_add(IS_IPv4 ? activation_source_handle_cb_4 : activation_source_handle_cb_6, self); + + if (priv->activation_source_id_x[IS_IPv4] != 0) { + _LOGD(LOGD_DEVICE, + "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family), + _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]), + nm_utils_addr_family_to_char(addr_family), + priv->activation_source_id_x[IS_IPv4], + new_id); + nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]); + } else { + _LOGD(LOGD_DEVICE, + "activation-stage: schedule %s,v%c (id %u)", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family), + new_id); + } + + priv->activation_source_func_x[IS_IPv4] = func; + priv->activation_source_id_x[IS_IPv4] = new_id; +} + +static void +activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + if (priv->activation_source_id_x[IS_IPv4] == 0) { + _LOGD(LOGD_DEVICE, + "activation-stage: synchronously invoke %s,v%c", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family)); + } else if (priv->activation_source_func_x[IS_IPv4] == func) { + _LOGD(LOGD_DEVICE, + "activation-stage: synchronously invoke %s,v%c which was already scheduled (id %u)", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family), + priv->activation_source_id_x[IS_IPv4]); + } else { + _LOGD(LOGD_DEVICE, + "activation-stage: synchronously invoke %s,v%c which replaces %s,v%c (id %u)", + _activation_func_to_string(func), + nm_utils_addr_family_to_char(addr_family), + _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]), + nm_utils_addr_family_to_char(addr_family), + priv->activation_source_id_x[IS_IPv4]); + } + + nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]); + priv->activation_source_func_x[IS_IPv4] = NULL; + + func(self); +} + +/*****************************************************************************/ + +static void +master_ready(NMDevice *self, NMActiveConnection *active) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMActiveConnection *master_connection; + NMDevice * master; + + /* Notify a master device that it has a new slave */ + nm_assert(nm_active_connection_get_master_ready(active)); + + master_connection = nm_active_connection_get_master(active); + + master = nm_active_connection_get_device(master_connection); + + _LOGD(LOGD_DEVICE, "master connection ready; master device %s", nm_device_get_iface(master)); + + if (priv->master && priv->master != master) + nm_device_master_release_one_slave(priv->master, + self, + FALSE, + FALSE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + + /* If the master didn't change, add-slave only rechecks whether to assume a connection. */ + nm_device_master_add_slave(master, + self, + !nm_device_sys_iface_state_is_external_or_assume(self)); +} + +static void +master_ready_cb(NMActiveConnection *active, GParamSpec *pspec, NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(nm_active_connection_get_master_ready(active)); + + if (priv->state == NM_DEVICE_STATE_PREPARE) + nm_device_activate_schedule_stage1_device_prepare(self, FALSE); +} + +static void +lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *self = NM_DEVICE(user_data); + + _notify(self, PROP_LLDP_NEIGHBORS); +} + +static NMPlatformVF * +sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free NMPlatformVF *plat_vf = NULL; + const guint * vlan_ids; + GVariant * variant; + guint i, num_vlans; + gsize length; + + g_return_val_if_fail(!error || !*error, FALSE); + + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans); + plat_vf = g_malloc0(sizeof(NMPlatformVF) + sizeof(NMPlatformVFVlan) * num_vlans); + + plat_vf->index = nm_sriov_vf_get_index(vf); + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK); + if (variant) + plat_vf->spoofchk = g_variant_get_boolean(variant); + else + plat_vf->spoofchk = -1; + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_TRUST); + if (variant) + plat_vf->trust = g_variant_get_boolean(variant); + else + plat_vf->trust = -1; + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAC); + if (variant) { + if (!_nm_utils_hwaddr_aton(g_variant_get_string(variant, NULL), + plat_vf->mac.data, + sizeof(plat_vf->mac.data), + &length)) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "invalid MAC %s", + g_variant_get_string(variant, NULL)); + return NULL; + } + if (length != priv->hw_addr_len) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "wrong MAC length %" G_GSIZE_FORMAT ", should be %u", + length, + priv->hw_addr_len); + return NULL; + } + plat_vf->mac.len = length; + } + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE); + if (variant) + plat_vf->min_tx_rate = g_variant_get_uint32(variant); + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE); + if (variant) + plat_vf->max_tx_rate = g_variant_get_uint32(variant); + + plat_vf->num_vlans = num_vlans; + plat_vf->vlans = (NMPlatformVFVlan *) (&plat_vf[1]); + for (i = 0; i < num_vlans; i++) { + plat_vf->vlans[i].id = vlan_ids[i]; + plat_vf->vlans[i].qos = nm_sriov_vf_get_vlan_qos(vf, vlan_ids[i]); + plat_vf->vlans[i].proto_ad = + nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[i]) == NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD; + } + + return g_steal_pointer(&plat_vf); +} + +static void +sriov_params_cb(GError *error, gpointer user_data) +{ + NMDevice * self; + NMDevicePrivate *priv; + nm_auto_freev NMPlatformVF **plat_vfs = NULL; + + nm_utils_user_data_unpack(user_data, &self, &plat_vfs); + + if (nm_utils_error_is_cancelled_or_disposing(error)) + return; + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (error) { + _LOGE(LOGD_DEVICE, "failed to set SR-IOV parameters: %s", error->message); + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED); + return; + } + + if (!nm_platform_link_set_sriov_vfs(nm_device_get_platform(self), + priv->ifindex, + (const NMPlatformVF *const *) plat_vfs)) { + _LOGE(LOGD_DEVICE, "failed to apply SR-IOV VFs"); + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED); + return; + } + + priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED; + + nm_device_activate_schedule_stage1_device_prepare(self, FALSE); +} + +/* + * activate_stage1_device_prepare + * + * Prepare for device activation + * + */ +static void +activate_stage1_device_prepare(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + NMActiveConnection *active; + NMActiveConnection *master; + NMDeviceClass * klass; + + priv->v4_route_table_initialized = FALSE; + priv->v6_route_table_initialized = FALSE; + + _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_NONE); + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_NONE); + + /* Notify the new ActiveConnection along with the state change */ + nm_dbus_track_obj_path_set(&priv->act_request, priv->act_request.obj, TRUE); + + nm_device_state_changed(self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE); + + if (priv->stage1_sriov_state != NM_DEVICE_STAGE_STATE_COMPLETED) { + NMSettingSriov *s_sriov; + + if (nm_device_sys_iface_state_is_external_or_assume(self)) { + /* pass */ + } else if (priv->stage1_sriov_state == NM_DEVICE_STAGE_STATE_PENDING) + return; + else if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV) + && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) { + nm_auto_freev NMPlatformVF **plat_vfs = NULL; + gs_free_error GError *error = NULL; + NMSriovVF * vf; + NMTernary autoprobe; + guint num; + guint i; + + autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov); + if (autoprobe == NM_TERNARY_DEFAULT) { + autoprobe = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("sriov.autoprobe-drivers"), + self, + NM_OPTION_BOOL_FALSE, + NM_OPTION_BOOL_TRUE, + NM_OPTION_BOOL_TRUE); + } + + num = nm_setting_sriov_get_num_vfs(s_sriov); + plat_vfs = g_new0(NMPlatformVF *, num + 1); + for (i = 0; i < num; i++) { + vf = nm_setting_sriov_get_vf(s_sriov, i); + plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &error); + if (!plat_vfs[i]) { + _LOGE(LOGD_DEVICE, + "failed to apply SR-IOV VF '%s': %s", + nm_utils_sriov_vf_to_str(vf, FALSE, NULL), + error->message); + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED); + return; + } + } + + /* When changing the number of VFs the kernel can block + * for very long time in the write to sysfs, especially + * if autoprobe-drivers is enabled. Do it asynchronously + * to avoid blocking the entire NM process. + */ + sriov_op_queue(self, + nm_setting_sriov_get_total_vfs(s_sriov), + NM_TERNARY_TO_OPTION_BOOL(autoprobe), + sriov_params_cb, + nm_utils_user_data_pack(self, g_steal_pointer(&plat_vfs))); + priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_PENDING; + return; + } + priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED; + } + + /* Assumed connections were already set up outside NetworkManager */ + klass = NM_DEVICE_GET_CLASS(self); + + if (klass->act_stage1_prepare_set_hwaddr_ethernet + && !nm_device_sys_iface_state_is_external_or_assume(self)) { + if (!nm_device_hw_addr_set_cloned(self, nm_device_get_applied_connection(self), FALSE)) { + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + } + + if (klass->act_stage1_prepare_also_for_external_or_assume + || !nm_device_sys_iface_state_is_external_or_assume(self)) { + nm_assert(!klass->act_stage1_prepare_also_for_external_or_assume + || klass->act_stage1_prepare); + if (klass->act_stage1_prepare) { + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + + ret = klass->act_stage1_prepare(self, &failure_reason); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason); + return; + } + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + return; + + nm_assert(ret == NM_ACT_STAGE_RETURN_SUCCESS); + } + } + + active = NM_ACTIVE_CONNECTION(priv->act_request.obj); + master = nm_active_connection_get_master(active); + if (master) { + if (nm_active_connection_get_state(master) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + _LOGD(LOGD_DEVICE, "master connection is deactivating"); + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED); + return; + } + /* If the master connection is ready for slaves, attach ourselves */ + if (!nm_active_connection_get_master_ready(active)) { + if (priv->master_ready_id == 0) { + _LOGD(LOGD_DEVICE, "waiting for master connection to become ready"); + priv->master_ready_id = + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY, + (GCallback) master_ready_cb, + self); + } + return; + } + } + nm_clear_g_signal_handler(priv->act_request.obj, &priv->master_ready_id); + if (master) + master_ready(self, active); + else if (priv->master) { + nm_device_master_release_one_slave(priv->master, + self, + TRUE, + TRUE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + + nm_device_activate_schedule_stage2_device_config(self, TRUE); +} + +void +nm_device_activate_schedule_stage1_device_prepare(NMDevice *self, gboolean do_sync) +{ + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_DEVICE_GET_PRIVATE(self)->act_request.obj); + + if (!do_sync) { + activation_source_schedule(self, activate_stage1_device_prepare, AF_INET); + return; + } + + activation_source_invoke_sync(self, activate_stage1_device_prepare, AF_INET); +} + +static NMActStageReturn +act_stage2_config(NMDevice *self, NMDeviceStateReason *out_failure_reason) +{ + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +lldp_init(NMDevice *self, gboolean restart) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->ifindex > 0 && _prop_get_connection_lldp(self)) { + gs_free_error GError *error = NULL; + + if (priv->lldp_listener) { + if (restart && nm_lldp_listener_is_running(priv->lldp_listener)) + nm_lldp_listener_stop(priv->lldp_listener); + } else { + priv->lldp_listener = nm_lldp_listener_new(); + g_signal_connect(priv->lldp_listener, + "notify::" NM_LLDP_LISTENER_NEIGHBORS, + G_CALLBACK(lldp_neighbors_changed), + self); + } + + if (!nm_lldp_listener_is_running(priv->lldp_listener)) { + if (nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error)) + _LOGD(LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener); + else { + _LOGD(LOGD_DEVICE, + "LLDP listener %p could not be started: %s", + priv->lldp_listener, + error->message); + } + } + } else { + if (priv->lldp_listener) + nm_lldp_listener_stop(priv->lldp_listener); + } +} + +/* set-mode can be: + * - TRUE: sync with new rules. + * - FALSE: sync, but remove all rules (== flush) + * - DEFAULT: forget about all the rules that we previously tracked, + * but don't actually remove them. This is when quitting NM + * we want to keep the rules. + * The problem is, after restart of NM, the rule manager will + * no longer remember that NM added these rules and treat them + * as externally added ones. Don't restart NetworkManager if + * you care about that. + */ +static void +_routing_rules_sync(NMDevice *self, NMTernary set_mode) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPRulesManager *rules_manager = nm_netns_get_rules_manager(nm_device_get_netns(self)); + NMDeviceClass * klass = NM_DEVICE_GET_CLASS(self); + gboolean untrack_only_dirty = FALSE; + gboolean keep_deleted_rules; + gpointer user_tag_1; + gpointer user_tag_2; + + /* take two arbitrary user-tag pointers that belong to @self. */ + user_tag_1 = &priv->v4_route_table; + user_tag_2 = &priv->v6_route_table; + + if (set_mode == NM_TERNARY_TRUE) { + NMConnection * applied_connection; + NMSettingIPConfig *s_ip; + guint i, num; + int is_ipv4; + + untrack_only_dirty = TRUE; + nmp_rules_manager_set_dirty(rules_manager, user_tag_1); + if (klass->get_extra_rules) + nmp_rules_manager_set_dirty(rules_manager, user_tag_2); + + applied_connection = nm_device_get_applied_connection(self); + + for (is_ipv4 = 0; applied_connection && is_ipv4 < 2; is_ipv4++) { + int addr_family = is_ipv4 ? AF_INET : AF_INET6; + + s_ip = nm_connection_get_setting_ip_config(applied_connection, addr_family); + if (!s_ip) + continue; + + num = nm_setting_ip_config_get_num_routing_rules(s_ip); + for (i = 0; i < num; i++) { + NMPlatformRoutingRule plrule; + NMIPRoutingRule * rule; + + rule = nm_setting_ip_config_get_routing_rule(s_ip, i); + nm_ip_routing_rule_to_platform(rule, &plrule); + + /* We track this rule, but we also make it explicitly not weakly-tracked + * (meaning to untrack NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at + * the same time). */ + nmp_rules_manager_track(rules_manager, + &plrule, + 10, + user_tag_1, + NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG); + } + } + + if (klass->get_extra_rules) { + gs_unref_ptrarray GPtrArray *extra_rules = NULL; + + extra_rules = klass->get_extra_rules(self); + if (extra_rules) { + for (i = 0; i < extra_rules->len; i++) { + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]), + 10, + user_tag_2, + NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG); + } + } + } + } + + nmp_rules_manager_untrack_all(rules_manager, user_tag_1, !untrack_only_dirty); + if (klass->get_extra_rules) + nmp_rules_manager_untrack_all(rules_manager, user_tag_2, !untrack_only_dirty); + + keep_deleted_rules = FALSE; + if (set_mode == NM_TERNARY_DEFAULT) { + /* when exiting NM, we leave the device up and the rules configured. + * We just all nmp_rules_manager_sync() to forget about the synced rules, + * but we don't actually delete them. + * + * FIXME: that is a problem after restart of NetworkManager, because these + * rules will look like externally added, and NM will no longer remove + * them. + * To fix that, we could during "assume" mark the rules of the profile + * as owned (and "added" by the device). The problem with that is that it + * wouldn't cover rules that devices add by internal decision (not because + * of a setting in the profile, e.g. WireGuard could setup policy routing). + * Maybe it would be better to remember these orphaned rules at exit in a + * file and track them after restart again. */ + keep_deleted_rules = TRUE; + } + nmp_rules_manager_sync(rules_manager, keep_deleted_rules); +} + +static gboolean +tc_commit(NMDevice *self) +{ + NMConnection * connection = NULL; + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_unref_ptrarray GPtrArray *tfilters = NULL; + NMSettingTCConfig * s_tc = NULL; + NMPlatform * platform; + int ip_ifindex; + + platform = nm_device_get_platform(self); + connection = nm_device_get_applied_connection(self); + if (connection) + s_tc = nm_connection_get_setting_tc_config(connection); + + ip_ifindex = nm_device_get_ip_ifindex(self); + if (!ip_ifindex) + return s_tc == NULL; + + if (s_tc) { + qdiscs = nm_utils_qdiscs_from_tc_setting(platform, s_tc, ip_ifindex); + tfilters = nm_utils_tfilters_from_tc_setting(platform, s_tc, ip_ifindex); + } + + if (!nm_platform_qdisc_sync(platform, ip_ifindex, qdiscs)) + return FALSE; + + if (!nm_platform_tfilter_sync(platform, ip_ifindex, tfilters)) + return FALSE; + + return TRUE; +} + +/* + * activate_stage2_device_config + * + * Determine device parameters and set those on the device, ie + * for wireless devices, set SSID, keys, etc. + * + */ +static void +activate_stage2_device_config(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceClass * klass; + NMActStageReturn ret; + gboolean no_firmware = FALSE; + CList * iter; + + nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + if (!nm_device_sys_iface_state_is_external_or_assume(self)) + _ethtool_state_set(self); + + if (!nm_device_sys_iface_state_is_external_or_assume(self)) { + if (!tc_commit(self)) { + _LOGW(LOGD_IP6, "failed applying traffic control rules"); + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + } + + _routing_rules_sync(self, NM_TERNARY_TRUE); + + if (!nm_device_sys_iface_state_is_external_or_assume(self)) { + if (!nm_device_bring_up(self, FALSE, &no_firmware)) { + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + no_firmware ? NM_DEVICE_STATE_REASON_FIRMWARE_MISSING + : NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + } + + klass = NM_DEVICE_GET_CLASS(self); + if (klass->act_stage2_config_also_for_external_or_assume + || !nm_device_sys_iface_state_is_external_or_assume(self)) { + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + + ret = klass->act_stage2_config(self, &failure_reason); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + return; + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) { + nm_assert(ret == NM_ACT_STAGE_RETURN_FAILURE); + nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason); + return; + } + } + + /* If we have slaves that aren't yet enslaved, do that now */ + c_list_for_each (iter, &priv->slaves) { + SlaveInfo * info = c_list_entry(iter, SlaveInfo, lst_slave); + NMDeviceState slave_state = nm_device_get_state(info->slave); + + if (slave_state == NM_DEVICE_STATE_IP_CONFIG) + nm_device_master_enslave_slave(self, + info->slave, + nm_device_get_applied_connection(info->slave)); + else if (priv->act_request.obj && nm_device_sys_iface_state_is_external(self) + && slave_state <= NM_DEVICE_STATE_DISCONNECTED) + nm_device_queue_recheck_assume(info->slave); + } + + lldp_init(self, TRUE); + + nm_device_activate_schedule_stage3_ip_config_start(self); +} + +void +nm_device_activate_schedule_stage2_device_config(NMDevice *self, gboolean do_sync) +{ + g_return_if_fail(NM_IS_DEVICE(self)); + + if (!do_sync) { + activation_source_schedule(self, activate_stage2_device_config, AF_INET); + return; + } + + activation_source_invoke_sync(self, activate_stage2_device_config, AF_INET); +} + +void +nm_device_ip_method_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason) +{ + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL); + + if (get_ip_config_may_fail(self, addr_family)) + check_ip_state(self, FALSE, (nm_device_get_state(self) == NM_DEVICE_STATE_IP_CONFIG)); + else + nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, reason); +} + +/*****************************************************************************/ + +static void +acd_data_destroy(gpointer ptr) +{ + AcdData *data = ptr; + int i; + + for (i = 0; data->configs && data->configs[i]; i++) + g_object_unref(data->configs[i]); + g_free(data->configs); + g_slice_free(AcdData, data); +} + +static void +ipv4_manual_method_apply(NMDevice *self, NMIP4Config **configs, gboolean success) +{ + NMConnection *connection; + const char * method; + + connection = nm_device_get_applied_connection(self); + nm_assert(connection); + method = nm_utils_get_ip_config_method(connection, AF_INET); + nm_assert(NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP4_CONFIG_METHOD_AUTO)); + + if (!success) { + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE); + return; + } + + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + nm_device_activate_schedule_ip_config_result(self, AF_INET, NULL); + else { + if (NM_DEVICE_GET_PRIVATE(self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE) + ip_config_merge_and_apply(self, AF_INET, TRUE); + } +} + +static void +acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data) +{ + AcdData * data = user_data; + NMDevice * self; + NMDevicePrivate * priv; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *address; + gboolean result, success = TRUE; + int i; + + g_assert(data); + self = data->device; + priv = NM_DEVICE_GET_PRIVATE(self); + + for (i = 0; data->configs && data->configs[i]; i++) { + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + result = nm_acd_manager_check_address(acd_manager, address->address); + success &= result; + + _NMLOG(result ? LOGL_DEBUG : LOGL_WARN, + LOGD_DEVICE, + "IPv4 DAD result: address %s is %s", + _nm_utils_inet4_ntop(address->address, sbuf), + result ? "unique" : "duplicate"); + } + } + + data->callback(self, data->configs, success); + + priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager); + nm_acd_manager_free(acd_manager); +} + +/** + * ipv4_dad_start: + * @self: device instance + * @configs: NULL-terminated array of IPv4 configurations + * @cb: callback function + * + * Start IPv4 DAD on device @self, check addresses in @configs and call @cb + * when the procedure ends. @cb will be called in any case, even if DAD can't + * be started. @configs will be unreferenced after @cb has been called. + */ +static void +ipv4_dad_start(NMDevice *self, NMIP4Config **configs, AcdCallback cb) +{ + static const NMAcdCallbacks acd_callbacks = { + .probe_terminated_callback = acd_manager_probe_terminated, + .user_data_destroy = acd_data_destroy, + }; + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMAcdManager * acd_manager; + const NMPlatformIP4Address *address; + NMDedupMultiIter ipconf_iter; + AcdData * data; + guint timeout; + gboolean addr_found; + int r; + const guint8 * hwaddr_arr; + size_t length; + guint i; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(configs); + g_return_if_fail(cb); + + for (i = 0, addr_found = FALSE; configs[i]; i++) { + if (nm_ip4_config_get_num_addresses(configs[i]) > 0) { + addr_found = TRUE; + break; + } + } + + timeout = _prop_get_ipv4_dad_timeout(self); + hwaddr_arr = nm_platform_link_get_address(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + &length); + + if (!timeout || !hwaddr_arr || !addr_found || length != ETH_ALEN + || nm_device_sys_iface_state_is_external_or_assume(self)) { + /* DAD not needed, signal success */ + cb(self, configs, TRUE); + + for (i = 0; configs[i]; i++) + g_object_unref(configs[i]); + g_free(configs); + + return; + } + + data = g_slice_new0(AcdData); + data->configs = configs; + data->callback = cb; + data->device = self; + + acd_manager = nm_acd_manager_new(nm_device_get_ip_ifindex(self), + hwaddr_arr, + length, + &acd_callbacks, + data); + priv->acd.dad_list = g_slist_append(priv->acd.dad_list, acd_manager); + + for (i = 0; configs[i]; i++) { + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address) + nm_acd_manager_add_address(acd_manager, address->address); + } + + r = nm_acd_manager_start_probe(acd_manager, timeout); + if (r < 0) { + _LOGW(LOGD_DEVICE, "acd probe failed"); + + /* DAD could not be started, signal success */ + cb(self, configs, TRUE); + + priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager); + nm_acd_manager_free(acd_manager); + } +} + +/*****************************************************************************/ +/* IPv4LL stuff */ + +static void +ipv4ll_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->ipv4ll) { + sd_ipv4ll_set_callback(priv->ipv4ll, NULL, NULL); + sd_ipv4ll_stop(priv->ipv4ll); + priv->ipv4ll = sd_ipv4ll_unref(priv->ipv4ll); + } + + nm_clear_g_source(&priv->ipv4ll_timeout); +} + +static NMIP4Config * +ipv4ll_get_ip4_config(NMDevice *self, guint32 lla) +{ + NMIP4Config * config = NULL; + NMPlatformIP4Address address; + NMPlatformIP4Route route; + + config = nm_device_ip4_config_new(self); + g_assert(config); + + memset(&address, 0, sizeof(address)); + nm_platform_ip4_address_set_addr(&address, lla, 16); + address.addr_source = NM_IP_CONFIG_SOURCE_IP4LL; + nm_ip4_config_add_address(config, &address); + + /* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */ + memset(&route, 0, sizeof(route)); + route.network = htonl(0xE0000000L); + route.plen = 4; + route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL; + route.table_coerced = nm_platform_route_table_coerce(nm_device_get_route_table(self, AF_INET)); + route.metric = nm_device_get_route_metric(self, AF_INET); + nm_ip4_config_add_route(config, &route, NULL); + + return config; +} + +static void +nm_device_handle_ipv4ll_event(sd_ipv4ll *ll, int event, void *data) +{ + NMDevice * self = data; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + struct in_addr address; + NMIP4Config * config; + int r; + + if (priv->act_request.obj == NULL) + return; + + nm_assert(nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET), + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)); + + switch (event) { + case SD_IPV4LL_EVENT_BIND: + r = sd_ipv4ll_get_address(ll, &address); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED); + return; + } + + if (!nm_utils_ip4_address_is_link_local(address.s_addr)) { + _LOGE(LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_ERROR); + return; + } + + config = ipv4ll_get_ip4_config(self, address.s_addr); + if (config == NULL) { + _LOGE(LOGD_AUTOIP4, "failed to get IPv4LL config"); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED); + return; + } + + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) { + nm_clear_g_source(&priv->ipv4ll_timeout); + nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(config)); + } else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) { + applied_config_init(&priv->dev_ip_config_4, config); + if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) { + _LOGE(LOGD_AUTOIP4, "failed to update IP4 config for autoip change."); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED); + } + } else + g_assert_not_reached(); + + g_object_unref(config); + break; + default: + _LOGW(LOGD_AUTOIP4, "IPv4LL address no longer valid after event %d.", event); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED); + } +} + +static gboolean +ipv4ll_timeout_cb(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->ipv4ll_timeout) { + _LOGI(LOGD_AUTOIP4, "IPv4LL configuration timed out."); + priv->ipv4ll_timeout = 0; + ipv4ll_cleanup(self); + + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) + nm_device_activate_schedule_ip_config_timeout(self, AF_INET); + } + + return FALSE; +} + +static NMActStageReturn +ipv4ll_start(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const struct ether_addr *addr; + int ifindex, r; + size_t addr_len; + + ipv4ll_cleanup(self); + + r = sd_ipv4ll_new(&priv->ipv4ll); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: new() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + r = sd_ipv4ll_attach_event(priv->ipv4ll, NULL, 0); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: attach_event() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + ifindex = nm_device_get_ip_ifindex(self); + addr = nm_platform_link_get_address(nm_device_get_platform(self), ifindex, &addr_len); + if (!addr || addr_len != ETH_ALEN) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: can't retrieve hardware address"); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + r = sd_ipv4ll_set_mac(priv->ipv4ll, addr); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: set_mac() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + r = sd_ipv4ll_set_ifindex(priv->ipv4ll, ifindex); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: set_ifindex() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + r = sd_ipv4ll_set_callback(priv->ipv4ll, nm_device_handle_ipv4ll_event, self); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: set_callback() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + r = sd_ipv4ll_start(priv->ipv4ll); + if (r < 0) { + _LOGE(LOGD_AUTOIP4, "IPv4LL: start() failed with error %d", r); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + _LOGI(LOGD_DEVICE | LOGD_AUTOIP4, "IPv4LL: started"); + + /* Start a timeout to bound the address attempt */ + priv->ipv4ll_timeout = g_timeout_add_seconds(20, ipv4ll_timeout_cb, self); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +ensure_con_ip_config(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMIPConfig * con_ip_config; + + if (priv->con_ip_config_x[IS_IPv4]) + return; + + connection = nm_device_get_applied_connection(self); + if (!connection) + return; + + con_ip_config = nm_device_ip_config_new(self, addr_family); + + if (IS_IPv4) { + nm_ip4_config_merge_setting(NM_IP4_CONFIG(con_ip_config), + nm_connection_get_setting_ip4_config(connection), + _prop_get_connection_mdns(self), + _prop_get_connection_llmnr(self), + nm_device_get_route_table(self, addr_family), + nm_device_get_route_metric(self, addr_family)); + } else { + nm_ip6_config_merge_setting(NM_IP6_CONFIG(con_ip_config), + nm_connection_get_setting_ip6_config(connection), + nm_device_get_route_table(self, addr_family), + nm_device_get_route_metric(self, addr_family)); + } + + if (nm_device_sys_iface_state_is_external_or_assume(self)) { + /* For assumed connections ignore all addresses and routes. */ + nm_ip_config_reset_addresses(con_ip_config); + nm_ip_config_reset_routes(con_ip_config); + } + + priv->con_ip_config_x[IS_IPv4] = con_ip_config; +} + +/*****************************************************************************/ + +static void +dhcp4_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->dhcp_data_4.was_active = FALSE; + nm_clear_g_source(&priv->dhcp_data_4.grace_id); + priv->dhcp_data_4.grace_pending = FALSE; + nm_clear_g_free(&priv->dhcp4.pac_url); + + if (priv->dhcp_data_4.client) { + /* Stop any ongoing DHCP transaction on this device */ + nm_clear_g_signal_handler(priv->dhcp_data_4.client, &priv->dhcp_data_4.state_sigid); + + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED) + nm_dhcp_client_stop(priv->dhcp_data_4.client, release); + + g_clear_object(&priv->dhcp_data_4.client); + } + + if (priv->dhcp_data_4.config) { + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config); + _notify(self, PROP_DHCP4_CONFIG); + } +} + +static gboolean +ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean success; + gs_unref_object NMIPConfig *composite = NULL; + NMIPConfig * config; + gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL; + NMConnection * connection; + gboolean ignore_auto_routes = FALSE; + gboolean ignore_auto_dns = FALSE; + gboolean ignore_default_routes = FALSE; + GSList * iter; + const char * ip6_addr_gen_token = NULL; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + if (nm_device_sys_iface_state_is_external(self)) + commit = FALSE; + + connection = nm_device_get_applied_connection(self); + + /* Apply ignore-auto-routes and ignore-auto-dns settings */ + if (connection) { + NMSettingIPConfig *s_ip; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (s_ip) { + ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes(s_ip); + ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns(s_ip); + + /* if the connection has an explicit gateway, we also ignore + * the default routes from other sources. */ + ignore_default_routes = nm_setting_ip_config_get_never_default(s_ip) + || nm_setting_ip_config_get_gateway(s_ip); + + if (!IS_IPv4) { + NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG(s_ip); + + if (nm_setting_ip6_config_get_addr_gen_mode(s_ip6) + == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) + ip6_addr_gen_token = nm_setting_ip6_config_get_token(s_ip6); + } + } + } + + composite = nm_device_ip_config_new(self, addr_family); + + if (!IS_IPv4) { + nm_ip6_config_set_privacy(NM_IP6_CONFIG(composite), + priv->ndisc ? priv->ndisc_use_tempaddr + : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + } + + init_ip_config_dns_priority(self, composite); + + if (commit) { + if (priv->queued_ip_config_id_x[IS_IPv4]) + update_ext_ip_config(self, addr_family, FALSE); + ensure_con_ip_config(self, addr_family); + } + + if (!IS_IPv4) { + if (commit && priv->ipv6ll_has) { + const NMPlatformIP6Address ll_a = { + .address = priv->ipv6ll_addr, + .plen = 64, + .addr_source = NM_IP_CONFIG_SOURCE_IP6LL, + }; + const NMPlatformIP6Route ll_r = { + .network.s6_addr16[0] = htons(0xfe80u), + .plen = 64, + .metric = nm_device_get_route_metric(self, addr_family), + .rt_source = NM_IP_CONFIG_SOURCE_IP6LL, + }; + + nm_assert(IN6_IS_ADDR_LINKLOCAL(&priv->ipv6ll_addr)); + + nm_ip6_config_add_address(NM_IP6_CONFIG(composite), &ll_a); + nm_ip6_config_add_route(NM_IP6_CONFIG(composite), &ll_r, NULL); + } + } + + if (commit) { + gboolean v; + + v = default_route_metric_penalty_detect(self, addr_family); + if (IS_IPv4) + priv->default_route_metric_penalty_ip4_has = v; + else + priv->default_route_metric_penalty_ip6_has = v; + } + + /* Merge all the IP configs into the composite config */ + + if (IS_IPv4) { + config = applied_config_get_current(&priv->dev_ip_config_4); + if (config) { + nm_ip4_config_merge( + NM_IP4_CONFIG(composite), + NM_IP4_CONFIG(config), + (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0) + | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0) + | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0), + default_route_metric_penalty_get(self, addr_family)); + } + } + + if (!IS_IPv4) { + config = applied_config_get_current(&priv->ac_ip6_config); + if (config) { + nm_ip6_config_merge( + NM_IP6_CONFIG(composite), + NM_IP6_CONFIG(config), + (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0) + | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0) + | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0), + default_route_metric_penalty_get(self, addr_family)); + } + } + + if (!IS_IPv4) { + config = applied_config_get_current(&priv->dhcp6.ip6_config); + if (config) { + nm_ip6_config_merge( + NM_IP6_CONFIG(composite), + NM_IP6_CONFIG(config), + (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0) + | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0) + | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0), + default_route_metric_penalty_get(self, addr_family)); + } + } + + for (iter = priv->vpn_configs_x[IS_IPv4]; iter; iter = iter->next) + nm_ip_config_merge(composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0); + + if (priv->ext_ip_config_x[IS_IPv4]) + nm_ip_config_merge(composite, + priv->ext_ip_config_x[IS_IPv4], + NM_IP_CONFIG_MERGE_EXTERNAL, + 0); + + /* Merge WWAN config *last* to ensure modem-given settings overwrite + * any external stuff set by pppd or other scripts. + */ + config = applied_config_get_current(&priv->dev2_ip_config_x[IS_IPv4]); + if (config) { + nm_ip_config_merge(composite, + config, + (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0) + | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0) + | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0), + default_route_metric_penalty_get(self, addr_family)); + } + + if (!IS_IPv4) { + if (priv->rt6_temporary_not_available) { + const NMPObject *o; + GHashTableIter hiter; + + g_hash_table_iter_init(&hiter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next(&hiter, (gpointer *) &o, NULL)) { + nm_ip6_config_add_route(NM_IP6_CONFIG(composite), + NMP_OBJECT_CAST_IP6_ROUTE(o), + NULL); + } + } + } + + /* Merge user overrides into the composite config. For assumed connections, + * con_ip_config_x is empty. */ + if (priv->con_ip_config_x[IS_IPv4]) { + nm_ip_config_merge(composite, + priv->con_ip_config_x[IS_IPv4], + NM_IP_CONFIG_MERGE_DEFAULT, + default_route_metric_penalty_get(self, addr_family)); + } + + if (commit) { + gboolean is_vrf; + + is_vrf = priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF; + + if (IS_IPv4) { + nm_ip4_config_add_dependent_routes(NM_IP4_CONFIG(composite), + nm_device_get_route_table(self, addr_family), + nm_device_get_route_metric(self, addr_family), + is_vrf, + &ip4_dev_route_blacklist); + } else { + nm_ip6_config_add_dependent_routes(NM_IP6_CONFIG(composite), + nm_device_get_route_table(self, addr_family), + nm_device_get_route_metric(self, addr_family), + is_vrf); + } + } + + if (IS_IPv4) { + if (commit) { + if (NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit) + NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit(self, NM_IP4_CONFIG(composite)); + } + } + + if (!IS_IPv4) { + NMUtilsIPv6IfaceId iid; + + if (commit && priv->ndisc_started && ip6_addr_gen_token + && nm_utils_ipv6_interface_identifier_get_from_token(&iid, ip6_addr_gen_token)) { + set_ipv6_token(self, iid, ip6_addr_gen_token); + } + } + + success = + nm_device_set_ip_config(self, addr_family, composite, commit, ip4_dev_route_blacklist); + if (commit) { + if (IS_IPv4) + priv->v4_commit_first_time = FALSE; + else + priv->v6_commit_first_time = FALSE; + } + + return success; +} + +static gboolean +dhcp4_lease_change(NMDevice *self, NMIP4Config *config, gboolean bound) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + + g_return_val_if_fail(config, FALSE); + + applied_config_init(&priv->dev_ip_config_4, config); + + if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) { + _LOGW(LOGD_DHCP4, "failed to update IPv4 config for DHCP change."); + return FALSE; + } + + /* TODO: we should perform DAD again whenever we obtain a + * new lease after an expiry. But what should we do if + * a duplicate address is detected? Fail the connection; + * restart DHCP; continue without an address? */ + if (bound && !nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) { + _LOGW(LOGD_DHCP4, "error accepting lease: %s", error->message); + return FALSE; + } + + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE, self, NULL, NULL, NULL, NULL); + + return TRUE; +} + +static gboolean +dhcp_grace_period_expired(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + priv->dhcp_data_x[IS_IPv4].grace_id = 0; + priv->dhcp_data_x[IS_IPv4].grace_pending = FALSE; + + _LOGI(LOGD_DHCPX(IS_IPv4), + "DHCPv%c: grace period expired", + nm_utils_addr_family_to_char(addr_family)); + + nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); + /* If the device didn't fail, the DHCP client will continue */ + + return G_SOURCE_REMOVE; +} + +static gboolean +dhcp_grace_period_expired_4(gpointer user_data) +{ + return dhcp_grace_period_expired(user_data, AF_INET); +} + +static gboolean +dhcp_grace_period_expired_6(gpointer user_data) +{ + return dhcp_grace_period_expired(user_data, AF_INET6); +} + +static gboolean +dhcp_grace_period_start(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + guint32 timeout; + + /* In any other case (expired lease, assumed connection, etc.), + * wait for some time before failing the IP method. + */ + if (priv->dhcp_data_x[IS_IPv4].grace_pending) { + /* already pending. */ + return FALSE; + } + + /* Start a grace period equal to the DHCP timeout multiplied + * by a constant factor. */ + timeout = _prop_get_ipvx_dhcp_timeout(self, addr_family); + if (timeout == NM_DHCP_TIMEOUT_INFINITY) + _LOGI(LOGD_DHCPX(IS_IPv4), + "DHCPv%c: trying to acquire a new lease", + nm_utils_addr_family_to_char(addr_family)); + else { + timeout = dhcp_grace_period_from_timeout(timeout); + _LOGI(LOGD_DHCPX(IS_IPv4), + "DHCPv%c: trying to acquire a new lease within %u seconds", + nm_utils_addr_family_to_char(addr_family), + timeout); + nm_assert(!priv->dhcp_data_x[IS_IPv4].grace_id); + priv->dhcp_data_x[IS_IPv4].grace_id = g_timeout_add_seconds( + timeout, + IS_IPv4 ? dhcp_grace_period_expired_4 : dhcp_grace_period_expired_6, + self); + } + + priv->dhcp_data_x[IS_IPv4].grace_pending = TRUE; + + return TRUE; +} +static void +dhcp4_fail(NMDevice *self, NMDhcpState dhcp_state) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + _LOGD(LOGD_DHCP4, + "DHCPv4 failed (ip_state %s, was_active %d)", + _ip_state_to_string(priv->ip_state_4), + priv->dhcp_data_4.was_active); + + /* The client is always left running after a failure. */ + + /* Nothing to do if we failed before... */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL) + goto clear_config; + + /* ... and also if there are static addresses configured + * on the interface. + */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_4 + && nm_ip4_config_get_num_addresses(priv->con_ip_config_4) > 0) + goto clear_config; + + /* Fail the method when one of the following is true: + * 1) the DHCP client terminated: it does not make sense to start a grace + * period without a client running; + * 2) we failed to get an initial lease AND the client was + * not active before. + */ + if (dhcp_state == NM_DHCP_STATE_TERMINATED + || (!priv->dhcp_data_4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) { + nm_device_activate_schedule_ip_config_timeout(self, AF_INET); + return; + } + + if (dhcp_grace_period_start(self, AF_INET)) + goto clear_config; + + return; + +clear_config: + /* The previous configuration is no longer valid */ + if (priv->dhcp_data_4.config) { + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config); + priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET); + _notify(self, PROP_DHCP4_CONFIG); + } +} + +static void +dhcp4_dad_cb(NMDevice *self, NMIP4Config **configs, gboolean success) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (success) { + nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(configs[1])); + } else { + nm_dhcp_client_decline(priv->dhcp_data_4.client, "Address conflict detected", NULL); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE); + } +} + +static void +dhcp4_state_changed(NMDhcpClient *client, + NMDhcpState state, + NMIP4Config * ip4_config, + GHashTable * options, + gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMIP4Config * manual, **configs; + NMConnection * connection; + + g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET); + g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config)); + + _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state); + + switch (state) { + case NM_DHCP_STATE_BOUND: + case NM_DHCP_STATE_EXTENDED: + if (!ip4_config) { + _LOGW(LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event."); + dhcp4_fail(self, state); + break; + } + + nm_clear_g_source(&priv->dhcp_data_4.grace_id); + priv->dhcp_data_4.grace_pending = FALSE; + + /* After some failures, we have been able to renew the lease: + * update the ip state + */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL) + _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_CONF); + + g_free(priv->dhcp4.pac_url); + priv->dhcp4.pac_url = g_strdup(g_hash_table_lookup(options, "wpad")); + nm_device_set_proxy_config(self, priv->dhcp4.pac_url); + + nm_dhcp_config_set_options(priv->dhcp_data_4.config, options); + _notify(self, PROP_DHCP4_CONFIG); + + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) { + connection = nm_device_get_applied_connection(self); + g_assert(connection); + + manual = nm_device_ip4_config_new(self); + nm_ip4_config_merge_setting(manual, + nm_connection_get_setting_ip4_config(connection), + NM_SETTING_CONNECTION_MDNS_DEFAULT, + NM_SETTING_CONNECTION_LLMNR_DEFAULT, + nm_device_get_route_table(self, AF_INET), + nm_device_get_route_metric(self, AF_INET)); + + configs = g_new0(NMIP4Config *, 3); + configs[0] = manual; + configs[1] = g_object_ref(ip4_config); + + ipv4_dad_start(self, configs, dhcp4_dad_cb); + } else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) { + if (dhcp4_lease_change(self, ip4_config, state == NM_DHCP_STATE_BOUND)) + nm_device_update_metered(self); + else + dhcp4_fail(self, state); + } + break; + case NM_DHCP_STATE_TIMEOUT: + dhcp4_fail(self, state); + break; + case NM_DHCP_STATE_EXPIRE: + /* Ignore expiry before we even have a lease (NAK, old lease, etc) */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) + break; + /* fall-through */ + case NM_DHCP_STATE_DONE: + case NM_DHCP_STATE_FAIL: + case NM_DHCP_STATE_TERMINATED: + dhcp4_fail(self, state); + break; + default: + break; + } +} + +static NMActStageReturn +dhcp4_start(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingIPConfig *s_ip4; + gs_unref_bytes GBytes *vendor_class_identifier = NULL; + gs_unref_bytes GBytes *hwaddr = NULL; + gs_unref_bytes GBytes *bcast_hwaddr = NULL; + gs_unref_bytes GBytes *client_id = NULL; + gs_free char * mud_url_free = NULL; + NMConnection * connection; + NMSettingConnection * s_con; + GError * error = NULL; + const NMPlatformLink * pllink; + const char *const * reject_servers; + + connection = nm_device_get_applied_connection(self); + g_return_val_if_fail(connection, FALSE); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + + s_con = nm_connection_get_setting_connection(connection); + nm_assert(s_con); + + /* Clear old exported DHCP options */ + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config); + priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET); + + pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self)); + if (pllink) { + hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address); + bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast); + } + + client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr); + vendor_class_identifier = + _prop_get_ipv4_dhcp_vendor_class_identifier(self, NM_SETTING_IP4_CONFIG(s_ip4)); + reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, NULL); + + g_warn_if_fail(priv->dhcp_data_4.client == NULL); + priv->dhcp_data_4.client = + nm_dhcp_manager_start_ip4(nm_dhcp_manager_get(), + nm_netns_get_multi_idx(nm_device_get_netns(self)), + nm_device_get_ip_iface(self), + nm_device_get_ip_ifindex(self), + hwaddr, + bcast_hwaddr, + nm_connection_get_uuid(connection), + nm_device_get_route_table(self, AF_INET), + nm_device_get_route_metric(self, AF_INET), + nm_setting_ip_config_get_dhcp_send_hostname(s_ip4), + nm_setting_ip_config_get_dhcp_hostname(s_ip4), + nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)), + _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET), + _prop_get_connection_mud_url(self, s_con, &mud_url_free), + client_id, + _prop_get_ipvx_dhcp_timeout(self, AF_INET), + priv->dhcp_anycast_address, + NULL, + vendor_class_identifier, + reject_servers, + &error); + if (!priv->dhcp_data_4.client) { + _LOGW(LOGD_DHCP4, "failure to start DHCP: %s", error->message); + g_clear_error(&error); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv->dhcp_data_4.state_sigid = g_signal_connect(priv->dhcp_data_4.client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK(dhcp4_state_changed), + self); + + if (nm_device_sys_iface_state_is_external_or_assume(self)) + priv->dhcp_data_4.was_active = TRUE; + + /* DHCP devices will be notified by the DHCP manager when stuff happens */ + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +gboolean +nm_device_dhcp4_renew(NMDevice *self, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_val_if_fail(priv->dhcp_data_4.client != NULL, FALSE); + + _LOGI(LOGD_DHCP4, "DHCPv4 lease renewal requested"); + + /* Terminate old DHCP instance and release the old lease */ + dhcp4_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release); + + /* Start DHCP again on the interface */ + return dhcp4_start(self) != NM_ACT_STAGE_RETURN_FAILURE; +} + +/*****************************************************************************/ + +static NMIP4Config * +shared4_new_config(NMDevice *self, NMConnection *connection) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMIP4Config * config; + NMSettingIPConfig * s_ip4; + NMPlatformIP4Address address = { + .addr_source = NM_IP_CONFIG_SOURCE_SHARED, + }; + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(connection, NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (s_ip4 && nm_setting_ip_config_get_num_addresses(s_ip4) > 0) { + /* Use the first user-supplied address */ + NMIPAddress *user = nm_setting_ip_config_get_address(s_ip4, 0); + in_addr_t a; + + nm_ip_address_get_address_binary(user, &a); + nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user)); + nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release); + } else { + if (!priv->shared_ip_handle) + priv->shared_ip_handle = nm_netns_shared_ip_reserve(nm_device_get_netns(self)); + nm_platform_ip4_address_set_addr(&address, priv->shared_ip_handle->addr, 24); + } + + config = nm_device_ip4_config_new(self); + nm_ip4_config_add_address(config, &address); + + return config; +} + +/*****************************************************************************/ + +static gboolean +connection_ip_method_requires_carrier(NMConnection *connection, + int addr_family, + gboolean * out_ip_enabled) +{ + const char *method; + + method = nm_utils_get_ip_config_method(connection, addr_family); + + if (NM_IS_IPv4(addr_family)) { + NM_SET_OUT(out_ip_enabled, !nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)); + return NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL); + } + + NM_SET_OUT(out_ip_enabled, + !NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)); + return NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_SHARED, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL); +} + +static gboolean +connection_requires_carrier(NMConnection *connection) +{ + NMSettingIPConfig * s_ip4, *s_ip6; + NMSettingConnection *s_con; + gboolean ip4_carrier_wanted, ip6_carrier_wanted; + gboolean ip4_used = FALSE, ip6_used = FALSE; + + /* We can progress to IP_CONFIG now, so that we're enslaved. + * That may actually cause carrier to go up and thus continue activation. */ + s_con = nm_connection_get_setting_connection(connection); + if (nm_setting_connection_get_master(s_con)) + return FALSE; + + ip4_carrier_wanted = connection_ip_method_requires_carrier(connection, AF_INET, &ip4_used); + if (ip4_carrier_wanted) { + /* If IPv4 wants a carrier and cannot fail, the whole connection + * requires a carrier regardless of the IPv6 method. + */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (s_ip4 && !nm_setting_ip_config_get_may_fail(s_ip4)) + return TRUE; + } + + ip6_carrier_wanted = connection_ip_method_requires_carrier(connection, AF_INET6, &ip6_used); + if (ip6_carrier_wanted) { + /* If IPv6 wants a carrier and cannot fail, the whole connection + * requires a carrier regardless of the IPv4 method. + */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (s_ip6 && !nm_setting_ip_config_get_may_fail(s_ip6)) + return TRUE; + } + + /* If an IP version wants a carrier and the other IP version isn't + * used, the connection requires carrier since it will just fail without one. + */ + if (ip4_carrier_wanted && !ip6_used) + return TRUE; + if (ip6_carrier_wanted && !ip4_used) + return TRUE; + + /* If both want a carrier, the whole connection wants a carrier */ + return ip4_carrier_wanted && ip6_carrier_wanted; +} + +static gboolean +have_any_ready_slaves(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + SlaveInfo * info; + CList * iter; + + /* Any enslaved slave is "ready" in the generic case as it's + * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2 + * properties set up. + */ + c_list_for_each (iter, &priv->slaves) { + info = c_list_entry(iter, SlaveInfo, lst_slave); + if (NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ +/* DHCPv6 stuff */ + +static void +dhcp6_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->dhcp_data_6.was_active = FALSE; + priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE; + applied_config_clear(&priv->dhcp6.ip6_config); + nm_clear_g_free(&priv->dhcp6.event_id); + nm_clear_g_source(&priv->dhcp_data_6.grace_id); + priv->dhcp_data_6.grace_pending = FALSE; + + if (priv->dhcp_data_6.client) { + nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp_data_6.state_sigid); + nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp6.prefix_sigid); + + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED) + nm_dhcp_client_stop(priv->dhcp_data_6.client, release); + + g_clear_object(&priv->dhcp_data_6.client); + } + + if (priv->dhcp_data_6.config) { + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config); + _notify(self, PROP_DHCP6_CONFIG); + } +} + +static gboolean +dhcp6_lease_change(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingsConnection *settings_connection; + + if (!applied_config_get_current(&priv->dhcp6.ip6_config)) { + _LOGW(LOGD_DHCP6, "failed to get DHCPv6 config for rebind"); + return FALSE; + } + + g_assert(priv->dhcp_data_6.client); /* sanity check */ + + settings_connection = nm_device_get_settings_connection(self); + g_assert(settings_connection); + + /* Apply the updated config */ + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) { + _LOGW(LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event"); + return FALSE; + } + + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE, self, NULL, NULL, NULL, NULL); + + return TRUE; +} + +static void +dhcp6_fail(NMDevice *self, NMDhcpState dhcp_state) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean is_dhcp_managed; + + _LOGD(LOGD_DHCP6, + "DHCPv6 failed (ip_state %s, was_active %d)", + _ip_state_to_string(priv->ip_state_6), + priv->dhcp_data_6.was_active); + + /* The client is always left running after a failure. */ + + /* Nothing to do if we failed before... */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL) + goto clear_config; + + is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED); + + if (is_dhcp_managed) { + /* ... and also if there are static addresses configured + * on the interface. + */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_6 + && nm_ip6_config_get_num_addresses(priv->con_ip_config_6)) + goto clear_config; + + /* Fail the method when one of the following is true: + * 1) the DHCP client terminated: it does not make sense to start a grace + * period without a client running; + * 2) we failed to get an initial lease AND the client was + * not active before. + */ + if (dhcp_state == NM_DHCP_STATE_TERMINATED + || (!priv->dhcp_data_6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) { + nm_device_activate_schedule_ip_config_timeout(self, AF_INET6); + return; + } + + if (dhcp_grace_period_start(self, AF_INET6)) + goto clear_config; + } else { + /* not a hard failure; just live with the RA info */ + dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE); + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + } + return; + +clear_config: + /* The previous configuration is no longer valid */ + if (priv->dhcp_data_6.config) { + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config); + priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6); + _notify(self, PROP_DHCP6_CONFIG); + } +} + +static void +dhcp6_state_changed(NMDhcpClient *client, + NMDhcpState state, + NMIP6Config * ip6_config, + GHashTable * options, + gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free char * event_id = NULL; + + g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET6); + g_return_if_fail(!ip6_config || NM_IS_IP6_CONFIG(ip6_config)); + + _LOGD(LOGD_DHCP6, "new DHCPv6 client state %d", state); + + switch (state) { + case NM_DHCP_STATE_BOUND: + case NM_DHCP_STATE_EXTENDED: + nm_clear_g_source(&priv->dhcp_data_6.grace_id); + priv->dhcp_data_6.grace_pending = FALSE; + /* If the server sends multiple IPv6 addresses, we receive a state + * changed event for each of them. Use the event ID to merge IPv6 + * addresses from the same transaction into a single configuration. + */ + + event_id = nm_dhcp_utils_get_dhcp6_event_id(options); + + if (ip6_config && event_id && priv->dhcp6.event_id + && nm_streq(event_id, priv->dhcp6.event_id)) { + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *a; + + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a) + applied_config_add_address(&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(a)); + } else { + nm_clear_g_free(&priv->dhcp6.event_id); + if (ip6_config) { + applied_config_init(&priv->dhcp6.ip6_config, ip6_config); + priv->dhcp6.event_id = g_strdup(event_id); + nm_dhcp_config_set_options(priv->dhcp_data_6.config, options); + _notify(self, PROP_DHCP6_CONFIG); + } else + applied_config_clear(&priv->dhcp6.ip6_config); + } + + /* After long time we have been able to renew the lease: + * update the ip state + */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL) + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF); + + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) { + if (!applied_config_get_current(&priv->dhcp6.ip6_config)) { + nm_device_ip_method_failed(self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED); + break; + } + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + } else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) + if (!dhcp6_lease_change(self)) + dhcp6_fail(self, state); + break; + case NM_DHCP_STATE_TIMEOUT: + if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED) + dhcp6_fail(self, state); + else { + /* not a hard failure; just live with the RA info */ + dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE); + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + } + break; + case NM_DHCP_STATE_EXPIRE: + /* Ignore expiry before we even have a lease (NAK, old lease, etc) */ + if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF) + dhcp6_fail(self, state); + break; + case NM_DHCP_STATE_TERMINATED: + /* In IPv6 info-only mode, the client doesn't handle leases so it + * may exit right after getting a response from the server. That's + * normal. In that case we just ignore the exit. + */ + if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) + break; + /* fall-through */ + case NM_DHCP_STATE_DONE: + case NM_DHCP_STATE_FAIL: + dhcp6_fail(self, state); + break; + default: + break; + } +} + +static void +dhcp6_prefix_delegated(NMDhcpClient *client, NMPlatformIP6Address *prefix, gpointer user_data) +{ + NMDevice *self = NM_DEVICE(user_data); + + /* Just re-emit. The device just contributes the prefix to the + * pool in NMPolicy, which decides about subnet allocation + * on the shared devices. */ + g_signal_emit(self, signals[IP6_PREFIX_DELEGATED], 0, prefix); +} + +/*****************************************************************************/ + +static gboolean +dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingIPConfig *s_ip6; + gs_unref_bytes GBytes *hwaddr = NULL; + gs_unref_bytes GBytes * duid = NULL; + gboolean enforce_duid = FALSE; + const NMPlatformLink * pllink; + gs_free char * mud_url_free = NULL; + GError * error = NULL; + guint32 iaid; + gboolean iaid_explicit; + NMSettingConnection * s_con; + const NMPlatformIP6Address *ll_addr = NULL; + + g_return_val_if_fail(connection, FALSE); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + nm_assert(s_ip6); + s_con = nm_connection_get_setting_connection(connection); + nm_assert(s_con); + + if (priv->ext_ip6_config_captured) { + ll_addr = nm_ip6_config_find_first_address(priv->ext_ip6_config_captured, + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL); + } + + if (!ll_addr) { + _LOGW(LOGD_DHCP6, "can't start DHCPv6: no link-local address"); + return FALSE; + } + + pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self)); + if (pllink) + hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address); + + iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, TRUE, &iaid_explicit); + duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid); + + priv->dhcp_data_6.client = nm_dhcp_manager_start_ip6( + nm_dhcp_manager_get(), + nm_device_get_multi_index(self), + nm_device_get_ip_iface(self), + nm_device_get_ip_ifindex(self), + &ll_addr->address, + nm_connection_get_uuid(connection), + nm_device_get_route_table(self, AF_INET6), + nm_device_get_route_metric(self, AF_INET6), + nm_setting_ip_config_get_dhcp_send_hostname(s_ip6), + nm_setting_ip_config_get_dhcp_hostname(s_ip6), + _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6), + _prop_get_connection_mud_url(self, s_con, &mud_url_free), + duid, + enforce_duid, + iaid, + iaid_explicit, + _prop_get_ipvx_dhcp_timeout(self, AF_INET6), + priv->dhcp_anycast_address, + (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE, + nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)), + priv->dhcp6.needed_prefixes, + &error); + if (!priv->dhcp_data_6.client) { + _LOGW(LOGD_DHCP6, "failure to start DHCPv6: %s", error->message); + g_clear_error(&error); + if (nm_device_sys_iface_state_is_external_or_assume(self)) + priv->dhcp_data_6.was_active = TRUE; + return FALSE; + } + + priv->dhcp_data_6.state_sigid = g_signal_connect(priv->dhcp_data_6.client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK(dhcp6_state_changed), + self); + priv->dhcp6.prefix_sigid = g_signal_connect(priv->dhcp_data_6.client, + NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED, + G_CALLBACK(dhcp6_prefix_delegated), + self); + + if (nm_device_sys_iface_state_is_external_or_assume(self)) + priv->dhcp_data_6.was_active = TRUE; + + return TRUE; +} + +static gboolean +dhcp6_start(NMDevice *self, gboolean wait_for_ll) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + + nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config); + priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6); + + nm_assert(!applied_config_get_current(&priv->dhcp6.ip6_config)); + applied_config_clear(&priv->dhcp6.ip6_config); + nm_clear_g_free(&priv->dhcp6.event_id); + + connection = nm_device_get_applied_connection(self); + g_return_val_if_fail(connection, FALSE); + + if (wait_for_ll) { + /* ensure link local is ready... */ + if (!linklocal6_start(self)) { + /* wait for the LL address to show up */ + return TRUE; + } + /* already have the LL address; kick off DHCP */ + } + + if (!dhcp6_start_with_link_ready(self, connection)) + return FALSE; + + return TRUE; +} + +gboolean +nm_device_dhcp6_renew(NMDevice *self, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_val_if_fail(priv->dhcp_data_6.client != NULL, FALSE); + + _LOGI(LOGD_DHCP6, "DHCPv6 lease renewal requested"); + + /* Terminate old DHCP instance and release the old lease */ + dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release); + + /* Start DHCP again on the interface */ + return dhcp6_start(self, FALSE); +} + +/*****************************************************************************/ + +/* + * Called on the requesting interface when a subnet can't be obtained + * from known prefixes for a newly active shared connection. + */ +void +nm_device_request_ip6_prefixes(NMDevice *self, int needed_prefixes) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->dhcp6.needed_prefixes = needed_prefixes; + + if (priv->dhcp_data_6.client) { + _LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %d prefixes", needed_prefixes); + nm_device_dhcp6_renew(self, FALSE); + } else { + _LOGI(LOGD_IP6, "ipv6-pd: device doesn't use DHCPv6, can't request prefixes"); + } +} + +gboolean +nm_device_needs_ip6_subnet(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->needs_ip6_subnet; +} + +/* + * Called on the ipv6.method=shared interface when a new subnet is allocated + * or the prefix from which it is allocated is renewed. + */ +void +nm_device_use_ip6_subnet(NMDevice *self, const NMPlatformIP6Address *subnet) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatformIP6Address address = *subnet; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (!applied_config_get_current(&priv->ac_ip6_config)) + applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6); + + /* Assign a ::1 address in the subnet for us. */ + address.address.s6_addr32[3] |= htonl(1); + applied_config_add_address(&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(&address)); + + _LOGD(LOGD_IP6, + "ipv6-pd: using %s address (preferred for %u seconds)", + _nm_utils_inet6_ntop(&address.address, sbuf), + subnet->preferred); + + /* This also updates the ndisc if there are actual changes. */ + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "ipv6-pd: failed applying IP6 config for connection sharing"); +} + +/* + * Called whenever the policy picks a default IPv6 device. + * The ipv6.method=shared devices just reuse its DNS configuration. + */ +void +nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMIP6Config * from_config = NULL; + guint i, len; + + if (applied_config_get_current(&priv->ac_ip6_config)) { + applied_config_reset_nameservers(&priv->ac_ip6_config); + applied_config_reset_searches(&priv->ac_ip6_config); + } else + applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6); + + if (from_device) + from_config = nm_device_get_ip6_config(from_device); + if (!from_config) + return; + + len = nm_ip6_config_get_num_nameservers(from_config); + for (i = 0; i < len; i++) { + applied_config_add_nameserver( + &priv->ac_ip6_config, + (const NMIPAddr *) nm_ip6_config_get_nameserver(from_config, i)); + } + + len = nm_ip6_config_get_num_searches(from_config); + for (i = 0; i < len; i++) { + applied_config_add_search(&priv->ac_ip6_config, nm_ip6_config_get_search(from_config, i)); + } + + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "ipv6-pd: failed applying DNS config for connection sharing"); +} + +/*****************************************************************************/ + +static void +linklocal6_failed(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_clear_g_source(&priv->linklocal6_timeout_id); + nm_device_activate_schedule_ip_config_timeout(self, AF_INET6); +} + +static gboolean +linklocal6_timeout_cb(gpointer user_data) +{ + NMDevice *self = user_data; + + _LOGD(LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout"); + linklocal6_failed(self); + return G_SOURCE_REMOVE; +} + +static void +linklocal6_check_complete(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + const char * method; + + if (!priv->linklocal6_timeout_id) { + /* we are not waiting for linklocal to complete. Nothing to do. */ + return; + } + + if (!priv->ext_ip6_config_captured + || !nm_ip6_config_find_first_address(priv->ext_ip6_config_captured, + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) { + /* we don't have a non-tentative link local address yet. Wait longer. */ + return; + } + + nm_clear_g_source(&priv->linklocal6_timeout_id); + + connection = nm_device_get_applied_connection(self); + g_assert(connection); + + method = nm_device_get_effective_ip_config_method(self, AF_INET6); + + _LOGD(LOGD_DEVICE, + "linklocal6: waiting for link-local addresses successful, continue with method %s", + method); + + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_SHARED)) + addrconf6_start_with_link_ready(self); + else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) { + if (!dhcp6_start_with_link_ready(self, connection)) { + /* Time out IPv6 instead of failing the entire activation */ + nm_device_activate_schedule_ip_config_timeout(self, AF_INET6); + } + } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + else + g_return_if_fail(FALSE); +} + +static void +check_and_add_ipv6ll_addr(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + struct in6_addr lladdr; + NMConnection * connection; + NMSettingIP6Config *s_ip6 = NULL; + GError * error = NULL; + const char * addr_type; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (!priv->ipv6ll_handle) + return; + + if (priv->ext_ip6_config_captured + && nm_ip6_config_find_first_address(priv->ext_ip6_config_captured, + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) { + /* Already have an LL address, nothing to do */ + return; + } + + priv->ipv6ll_has = FALSE; + memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr)); + + memset(&lladdr, 0, sizeof(lladdr)); + lladdr.s6_addr16[0] = htons(0xfe80); + + connection = nm_device_get_applied_connection(self); + if (connection) + s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection)); + + if (s_ip6 + && nm_setting_ip6_config_get_addr_gen_mode(s_ip6) + == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) { + NMUtilsStableType stable_type; + const char * stable_id; + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + if (!nm_utils_ipv6_addr_set_stable_privacy(stable_type, + &lladdr, + nm_device_get_iface(self), + stable_id, + priv->linklocal6_dad_counter++, + &error)) { + _LOGW(LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message); + g_clear_error(&error); + linklocal6_failed(self); + return; + } + addr_type = "stable-privacy"; + } else { + NMUtilsIPv6IfaceId iid; + + if (priv->linklocal6_timeout_id) { + /* We already started and attempt to add a LL address. For the EUI-64 + * mode we can't pick a new one, we'll just fail. */ + _LOGW(LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address"); + linklocal6_failed(self); + return; + } + + if (!nm_device_get_ip_iface_identifier(self, &iid, TRUE)) { + _LOGW(LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue"); + return; + } + nm_utils_ipv6_addr_set_interface_identifier(&lladdr, iid); + addr_type = "EUI-64"; + } + + _LOGD(LOGD_IP6, + "linklocal6: generated %s IPv6LL address %s", + addr_type, + _nm_utils_inet6_ntop(&lladdr, sbuf)); + priv->ipv6ll_has = TRUE; + priv->ipv6ll_addr = lladdr; + ip_config_merge_and_apply(self, AF_INET6, TRUE); +} + +static gboolean +linklocal6_start(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_clear_g_source(&priv->linklocal6_timeout_id); + + if (priv->ext_ip6_config_captured + && nm_ip6_config_find_first_address(priv->ext_ip6_config_captured, + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) + return TRUE; + + _LOGD(LOGD_DEVICE, + "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses " + "configured. Wait.", + nm_device_get_effective_ip_config_method(self, AF_INET6)); + + check_and_add_ipv6ll_addr(self); + + /* Depending on the network and what the 'dad_transmits' and 'retrans_time_ms' + * sysctl values are, DAD for the IPv6LL address may take quite a while. + * FIXME: use dad/retrans sysctl values if they are higher than a minimum time. + * (rh #1101809) + */ + priv->linklocal6_timeout_id = g_timeout_add_seconds(15, linklocal6_timeout_cb, self); + return FALSE; +} + +/*****************************************************************************/ + +gint64 +nm_device_get_configured_mtu_from_connection_default(NMDevice * self, + const char *property_name, + guint32 max_mtu) +{ + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + property_name, + self, + 0, + max_mtu, + -1); +} + +guint32 +nm_device_get_configured_mtu_from_connection(NMDevice * self, + GType setting_type, + NMDeviceMtuSource *out_source) +{ + const char * global_property_name; + NMConnection *connection; + NMSetting * setting; + gint64 mtu_default; + guint32 mtu = 0; + guint32 max_mtu = G_MAXUINT32; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(out_source); + + connection = nm_device_get_applied_connection(self); + if (!connection) + g_return_val_if_reached(0); + + setting = nm_connection_get_setting(connection, setting_type); + + if (setting_type == NM_TYPE_SETTING_WIRED) { + if (setting) + mtu = nm_setting_wired_get_mtu(NM_SETTING_WIRED(setting)); + global_property_name = NM_CON_DEFAULT("ethernet.mtu"); + } else if (setting_type == NM_TYPE_SETTING_WIRELESS) { + if (setting) + mtu = nm_setting_wireless_get_mtu(NM_SETTING_WIRELESS(setting)); + global_property_name = NM_CON_DEFAULT("wifi.mtu"); + } else if (setting_type == NM_TYPE_SETTING_INFINIBAND) { + if (setting) + mtu = nm_setting_infiniband_get_mtu(NM_SETTING_INFINIBAND(setting)); + global_property_name = NM_CON_DEFAULT("infiniband.mtu"); + max_mtu = NM_INFINIBAND_MAX_MTU; + } else if (setting_type == NM_TYPE_SETTING_IP_TUNNEL) { + if (setting) + mtu = nm_setting_ip_tunnel_get_mtu(NM_SETTING_IP_TUNNEL(setting)); + global_property_name = NM_CON_DEFAULT("ip-tunnel.mtu"); + } else if (setting_type == NM_TYPE_SETTING_WIREGUARD) { + if (setting) + mtu = nm_setting_wireguard_get_mtu(NM_SETTING_WIREGUARD(setting)); + global_property_name = NM_CON_DEFAULT("wireguard.mtu"); + } else + g_return_val_if_reached(0); + + if (mtu) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return mtu; + } + + mtu_default = + nm_device_get_configured_mtu_from_connection_default(self, global_property_name, max_mtu); + if (mtu_default >= 0) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return (guint32) mtu_default; + } + + *out_source = NM_DEVICE_MTU_SOURCE_NONE; + return 0; +} + +guint32 +nm_device_get_configured_mtu_for_wired(NMDevice * self, + NMDeviceMtuSource *out_source, + gboolean * out_force) +{ + return nm_device_get_configured_mtu_from_connection(self, NM_TYPE_SETTING_WIRED, out_source); +} + +guint32 +nm_device_get_configured_mtu_wired_parent(NMDevice * self, + NMDeviceMtuSource *out_source, + gboolean * out_force) +{ + guint32 mtu = 0; + guint32 parent_mtu = 0; + int ifindex; + + ifindex = nm_device_parent_get_ifindex(self); + if (ifindex > 0) { + parent_mtu = nm_platform_link_get_mtu(nm_device_get_platform(self), ifindex); + if (parent_mtu >= NM_DEVICE_GET_CLASS(self)->mtu_parent_delta) + parent_mtu -= NM_DEVICE_GET_CLASS(self)->mtu_parent_delta; + else + parent_mtu = 0; + } + + mtu = nm_device_get_configured_mtu_for_wired(self, out_source, NULL); + + if (parent_mtu && mtu > parent_mtu) { + /* Trying to set a MTU that is out of range from configuration: + * fall back to the parent MTU and set force flag so that it + * overrides an MTU with higher priority already configured. + */ + *out_source = NM_DEVICE_MTU_SOURCE_PARENT; + *out_force = TRUE; + return parent_mtu; + } + + if (*out_source != NM_DEVICE_MTU_SOURCE_NONE) { + nm_assert(mtu > 0); + return mtu; + } + + /* Inherit the MTU from parent device, if any */ + if (parent_mtu) { + mtu = parent_mtu; + *out_source = NM_DEVICE_MTU_SOURCE_PARENT; + } + + return mtu; +} + +/*****************************************************************************/ + +static void +_set_mtu(NMDevice *self, guint32 mtu) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->mtu == mtu) + return; + + priv->mtu = mtu; + _notify(self, PROP_MTU); + + if (priv->master) { + /* changing the MTU of a slave, might require the master to reset + * its MTU. Note that the master usually cannot set a MTU larger + * then the slave's. Hence, when the slave increases the MTU, + * master might want to retry setting the MTU. */ + nm_device_commit_mtu(priv->master); + } +} + +static gboolean +set_platform_mtu(NMDevice *self, guint32 mtu) +{ + int r; + + r = nm_platform_link_set_mtu(nm_device_get_platform(self), nm_device_get_ip_ifindex(self), mtu); + return (r != -NME_PL_CANT_SET_MTU); +} + +static void +_commit_mtu(NMDevice *self, const NMIP4Config *config) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceMtuSource source = NM_DEVICE_MTU_SOURCE_NONE; + guint32 ip6_mtu, ip6_mtu_orig; + guint32 mtu_desired, mtu_desired_orig; + guint32 mtu_plat; + struct { + gboolean initialized; + guint32 value; + } ip6_mtu_sysctl = { + 0, + }; + int ifindex; + char sbuf[64], sbuf1[64], sbuf2[64]; + gboolean success = TRUE; + + ifindex = nm_device_get_ip_ifindex(self); + if (ifindex <= 0) + return; + + if (!nm_device_get_applied_connection(self) + || nm_device_sys_iface_state_is_external_or_assume(self)) { + /* we don't tamper with the MTU of disconnected and + * external/assumed devices. */ + return; + } + + { + guint32 mtu = 0; + gboolean force = FALSE; + + /* We take the MTU from various sources: (in order of increasing + * priority) parent link, IP configuration (which contains the + * MTU from DHCP/PPP), connection profile. + * + * We could just compare it with the platform MTU and apply it + * when different, but this would revert at random times manual + * changes done by the user with the MTU from the connection. + * + * Instead, we remember the source of the currently configured + * MTU and apply the new one only when the new source has a + * higher priority, so that we don't set a MTU from same source + * multiple times. An exception to this is for the PARENT + * source, since we need to keep tracking the parent MTU when it + * changes. + * + * The subclass can set the @force argument to TRUE to signal that the + * returned MTU should be applied even if it has a lower priority. This + * is useful when the value from a lower source should + * preempt the one from higher ones. + */ + + if (NM_DEVICE_GET_CLASS(self)->get_configured_mtu) + mtu = NM_DEVICE_GET_CLASS(self)->get_configured_mtu(self, &source, &force); + + if (config && !force && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG + && nm_ip4_config_get_mtu(config)) { + mtu = nm_ip4_config_get_mtu(config); + source = NM_DEVICE_MTU_SOURCE_IP_CONFIG; + } + + if (mtu != 0) { + _LOGT(LOGD_DEVICE, + "mtu: value %u from source '%s' (%u), current source '%s' (%u)%s", + (guint) mtu, + mtu_source_to_str(source), + (guint) source, + mtu_source_to_str(priv->mtu_source), + (guint) priv->mtu_source, + force ? " (forced)" : ""); + } + + if (mtu != 0 + && (force || source > priv->mtu_source + || (priv->mtu_source == NM_DEVICE_MTU_SOURCE_PARENT && source == priv->mtu_source))) + mtu_desired = mtu; + else { + mtu_desired = 0; + source = NM_DEVICE_MTU_SOURCE_NONE; + } + } + + if (mtu_desired && mtu_desired < 1280) { + NMSettingIPConfig *s_ip6; + + s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG); + if (s_ip6 + && !NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6), + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + /* the interface has IPv6 enabled. The MTU with IPv6 cannot be smaller + * then 1280. + * + * For slave-devices (that don't have @s_ip6 we) don't do this fixup because + * it's anyway an unsolved problem when the slave configures a conflicting + * MTU. */ + mtu_desired = 1280; + } + } + + ip6_mtu = priv->ip6_mtu; + if (!ip6_mtu && priv->mtu_source == NM_DEVICE_MTU_SOURCE_NONE) { + /* initially, if the IPv6 MTU is not specified, grow it as large as the + * link MTU @mtu_desired. Only exception is, if @mtu_desired is so small + * to disable IPv6. */ + if (mtu_desired >= 1280) + ip6_mtu = mtu_desired; + } + + if (!ip6_mtu && !mtu_desired) + return; + + mtu_desired_orig = mtu_desired; + ip6_mtu_orig = ip6_mtu; + + mtu_plat = nm_platform_link_get_mtu(nm_device_get_platform(self), ifindex); + + if (ip6_mtu) { + ip6_mtu = NM_MAX(1280, ip6_mtu); + + if (!mtu_desired) + mtu_desired = mtu_plat; + + if (mtu_desired) { + mtu_desired = NM_MAX(1280, mtu_desired); + + if (mtu_desired < ip6_mtu) + ip6_mtu = mtu_desired; + } + } + + if (mtu_desired && NM_DEVICE_GET_CLASS(self)->mtu_force_set && !priv->mtu_force_set_done) { + priv->mtu_force_set_done = TRUE; + + if (mtu_desired == mtu_plat) { + mtu_plat--; + if (NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired - 1)) { + _LOGD(LOGD_DEVICE, "mtu: force-set MTU to %u", mtu_desired - 1); + } else + _LOGW(LOGD_DEVICE, "mtu: failure to force-set MTU to %u", mtu_desired - 1); + } + } + + _LOGT(LOGD_DEVICE, + "mtu: device-mtu: %u%s, ipv6-mtu: %u%s, ifindex: %d", + (guint) mtu_desired, + mtu_desired == mtu_desired_orig + ? "" + : nm_sprintf_buf(sbuf1, " (was %u)", (guint) mtu_desired_orig), + (guint) ip6_mtu, + ip6_mtu == ip6_mtu_orig ? "" : nm_sprintf_buf(sbuf2, " (was %u)", (guint) ip6_mtu_orig), + ifindex); + +#define _IP6_MTU_SYS() \ + ({ \ + if (!ip6_mtu_sysctl.initialized) { \ + ip6_mtu_sysctl.value = nm_device_sysctl_ip_conf_get_int_checked(self, \ + AF_INET6, \ + "mtu", \ + 10, \ + 0, \ + G_MAXUINT32, \ + 0); \ + ip6_mtu_sysctl.initialized = TRUE; \ + } \ + ip6_mtu_sysctl.value; \ + }) + if ((mtu_desired && mtu_desired != mtu_plat) || (ip6_mtu && ip6_mtu != _IP6_MTU_SYS())) { + gboolean anticipated_failure = FALSE; + + if (!priv->mtu_initial && !priv->ip6_mtu_initial) { + /* before touching any of the MTU parameters, record the + * original setting to restore on deactivation. */ + priv->mtu_initial = mtu_plat; + priv->ip6_mtu_initial = _IP6_MTU_SYS(); + } + + if (mtu_desired && mtu_desired != mtu_plat) { + if (!NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired)) { + anticipated_failure = TRUE; + success = FALSE; + _LOGW(LOGD_DEVICE, + "mtu: failure to set MTU. %s", + NM_IS_DEVICE_VLAN(self) + ? "Is the parent's MTU size large enough?" + : (!c_list_is_empty(&priv->slaves) + ? "Are the MTU sizes of the slaves large enough?" + : "Did you configure the MTU correctly?")); + } + priv->carrier_wait_until_ms = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + } + + if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS()) { + if (!nm_device_sysctl_ip_conf_set(self, + AF_INET6, + "mtu", + nm_sprintf_buf(sbuf, "%u", (unsigned) ip6_mtu))) { + int errsv = errno; + NMLogLevel level = LOGL_WARN; + const char *msg = NULL; + + success = FALSE; + + if (anticipated_failure && errsv == EINVAL) { + level = LOGL_DEBUG; + msg = "Is the underlying MTU value successfully set?"; + } else if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) { + level = LOGL_DEBUG; + msg = "IPv6 is disabled"; + success = TRUE; + } + + _NMLOG(level, + LOGD_DEVICE, + "mtu: failure to set IPv6 MTU%s%s", + msg ? ": " : "", + msg ?: ""); + } + priv->carrier_wait_until_ms = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + } + } + + if (success && source != NM_DEVICE_MTU_SOURCE_NONE) + priv->mtu_source = source; + +#undef _IP6_MTU_SYS +} + +void +nm_device_commit_mtu(NMDevice *self) +{ + NMDeviceState state; + + g_return_if_fail(NM_IS_DEVICE(self)); + + state = nm_device_get_state(self); + if (state >= NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) { + _LOGT(LOGD_DEVICE, "mtu: commit-mtu..."); + _commit_mtu(self, NM_DEVICE_GET_PRIVATE(self)->ip_config_4); + } else + _LOGT(LOGD_DEVICE, + "mtu: commit-mtu... skip due to state %s", + nm_device_state_to_str(state)); +} + +static void +ndisc_config_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, NMDevice *self) +{ + NMNDiscConfigMap changed = changed_int; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + guint i; + + g_return_if_fail(priv->act_request.obj); + + if (!applied_config_get_current(&priv->ac_ip6_config)) + applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6); + + if (changed & NM_NDISC_CONFIG_ADDRESSES) { + guint8 plen; + guint32 ifa_flags; + + /* Check, whether kernel is recent enough to help user space handling RA. + * If it's not supported, we have no ipv6-privacy and must add autoconf + * addresses as /128. The reason for the /128 is to prevent the kernel + * from adding a prefix route for this address. */ + ifa_flags = 0; + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) { + ifa_flags |= IFA_F_NOPREFIXROUTE; + if (NM_IN_SET(priv->ndisc_use_tempaddr, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)) + ifa_flags |= IFA_F_MANAGETEMPADDR; + plen = 64; + } else + plen = 128; + + nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.orig, + rdata->addresses, + rdata->addresses_n, + plen, + ifa_flags); + if (priv->ac_ip6_config.current) { + nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.current, + rdata->addresses, + rdata->addresses_n, + plen, + ifa_flags); + } + } + + if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) { + nm_ip6_config_reset_routes_ndisc( + (NMIP6Config *) priv->ac_ip6_config.orig, + rdata->gateways, + rdata->gateways_n, + rdata->routes, + rdata->routes_n, + nm_device_get_route_table(self, AF_INET6), + nm_device_get_route_metric(self, AF_INET6), + nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)); + if (priv->ac_ip6_config.current) { + nm_ip6_config_reset_routes_ndisc( + (NMIP6Config *) priv->ac_ip6_config.current, + rdata->gateways, + rdata->gateways_n, + rdata->routes, + rdata->routes_n, + nm_device_get_route_table(self, AF_INET6), + nm_device_get_route_metric(self, AF_INET6), + nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)); + } + } + + if (changed & NM_NDISC_CONFIG_DNS_SERVERS) { + /* Rebuild DNS server list from neighbor discovery cache. */ + applied_config_reset_nameservers(&priv->ac_ip6_config); + + for (i = 0; i < rdata->dns_servers_n; i++) + applied_config_add_nameserver(&priv->ac_ip6_config, + (const NMIPAddr *) &rdata->dns_servers[i].address); + } + + if (changed & NM_NDISC_CONFIG_DNS_DOMAINS) { + /* Rebuild domain list from neighbor discovery cache. */ + applied_config_reset_searches(&priv->ac_ip6_config); + + for (i = 0; i < rdata->dns_domains_n; i++) + applied_config_add_search(&priv->ac_ip6_config, rdata->dns_domains[i].domain); + } + + if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) { + dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, TRUE); + + priv->dhcp6.mode = rdata->dhcp_level; + if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE) { + _LOGD(LOGD_DEVICE | LOGD_DHCP6, + "Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6" + " as requested by IPv6 router..."); + if (!dhcp6_start(self, FALSE)) { + if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED) { + nm_device_state_changed(self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_START_FAILED); + return; + } + } + } + } + + if (changed & NM_NDISC_CONFIG_HOP_LIMIT) + nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(nm_device_get_platform(self), + nm_device_get_ip_iface(self), + rdata->hop_limit); + + if (changed & NM_NDISC_CONFIG_REACHABLE_TIME) { + nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(nm_device_get_platform(self), + nm_device_get_ip_iface(self), + rdata->reachable_time_ms); + } + + if (changed & NM_NDISC_CONFIG_RETRANS_TIMER) { + nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(nm_device_get_platform(self), + nm_device_get_ip_iface(self), + rdata->retrans_timer_ms); + } + + if (changed & NM_NDISC_CONFIG_MTU) { + if (priv->ip6_mtu != rdata->mtu) { + _LOGD(LOGD_DEVICE, "mtu: set IPv6 MTU to %u", (guint) rdata->mtu); + priv->ip6_mtu = rdata->mtu; + } + } + + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); +} + +static void +ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + /* We don't want to stop listening for router advertisements completely, + * but instead let device activation continue activating. If an RA + * shows up later, we'll use it as long as the device is not disconnected. + */ + + _LOGD(LOGD_IP6, "timed out waiting for IPv6 router advertisement"); + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) { + /* If RA is our only source of addressing information and we don't + * ever receive one, then time out IPv6. But if there is other + * IPv6 configuration, like manual IPv6 addresses or external IPv6 + * config, consider that sufficient for IPv6 success. + * + * FIXME: it doesn't seem correct to determine this based on which + * addresses we find inside priv->ip_config_6. + */ + if (priv->ip_config_6 + && nm_ip6_config_find_first_address(priv->ip_config_6, + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)) + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + else + nm_device_activate_schedule_ip_config_timeout(self, AF_INET6); + } +} + +static void +addrconf6_start_with_link_ready(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMUtilsIPv6IfaceId iid; + + g_assert(priv->ndisc); + + if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) { + _LOGD(LOGD_IP6, "addrconf6: using the device EUI-64 identifier"); + nm_ndisc_set_iid(priv->ndisc, iid); + } else { + /* Don't abort the addrconf at this point -- if ndisc needs the iid + * it will notice this itself. */ + _LOGI(LOGD_IP6, "addrconf6: no interface identifier; IPv6 address creation may fail"); + } + + /* Apply any manual configuration before starting RA */ + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "failed to apply manual IPv6 configuration"); + + if (nm_ndisc_get_node_type(priv->ndisc) == NM_NDISC_NODE_TYPE_ROUTER) { + nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "1"); + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + priv->needs_ip6_subnet = TRUE; + g_signal_emit(self, signals[IP6_SUBNET_NEEDED], 0); + } + + priv->ndisc_changed_id = g_signal_connect(priv->ndisc, + NM_NDISC_CONFIG_RECEIVED, + G_CALLBACK(ndisc_config_changed), + self); + priv->ndisc_timeout_id = g_signal_connect(priv->ndisc, + NM_NDISC_RA_TIMEOUT_SIGNAL, + G_CALLBACK(ndisc_ra_timeout), + self); + + ndisc_set_router_config(priv->ndisc, self); + nm_ndisc_start(priv->ndisc); + priv->ndisc_started = TRUE; + return; +} + +static gboolean +addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + NMSettingIP6Config *s_ip6 = NULL; + GError * error = NULL; + NMUtilsStableType stable_type; + const char * stable_id; + NMNDiscNodeType node_type; + int max_addresses; + int router_solicitations; + int router_solicitation_interval; + guint32 ra_timeout; + guint32 default_ra_timeout; + + connection = nm_device_get_applied_connection(self); + g_assert(connection); + + nm_assert(!applied_config_get_current(&priv->ac_ip6_config)); + applied_config_clear(&priv->ac_ip6_config); + + nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source(&priv->rt6_temporary_not_available_id); + + s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection)); + g_assert(s_ip6); + + if (nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET6), + NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + node_type = NM_NDISC_NODE_TYPE_ROUTER; + else + node_type = NM_NDISC_NODE_TYPE_HOST; + + nm_lndp_ndisc_get_sysctl(nm_device_get_platform(self), + nm_device_get_ip_iface(self), + &max_addresses, + &router_solicitations, + &router_solicitation_interval, + &default_ra_timeout); + + if (node_type == NM_NDISC_NODE_TYPE_ROUTER) + ra_timeout = 0u; + else { + ra_timeout = _prop_get_ipv6_ra_timeout(self); + if (ra_timeout == 0u) + ra_timeout = default_ra_timeout; + } + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + priv->ndisc = nm_lndp_ndisc_new(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + nm_device_get_ip_iface(self), + stable_type, + stable_id, + nm_setting_ip6_config_get_addr_gen_mode(s_ip6), + node_type, + max_addresses, + router_solicitations, + router_solicitation_interval, + ra_timeout, + &error); + if (!priv->ndisc) { + _LOGE(LOGD_IP6, "addrconf6: failed to start neighbor discovery: %s", error->message); + g_error_free(error); + return FALSE; + } + + priv->ndisc_use_tempaddr = use_tempaddr; + + if (NM_IN_SET(use_tempaddr, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) + && !nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) { + _LOGW(LOGD_IP6, + "The kernel does not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } + + /* ensure link local is ready... */ + if (!linklocal6_start(self)) { + /* wait for the LL address to show up */ + return TRUE; + } + + /* already have the LL address; kick off neighbor discovery */ + addrconf6_start_with_link_ready(self); + return TRUE; +} + +static void +addrconf6_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->ndisc_started = FALSE; + nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_changed_id); + nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_timeout_id); + + applied_config_clear(&priv->ac_ip6_config); + nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source(&priv->rt6_temporary_not_available_id); + if (priv->ndisc) { + nm_ndisc_stop(priv->ndisc); + g_clear_object(&priv->ndisc); + } +} + +/*****************************************************************************/ + +static void +save_ip6_properties(NMDevice *self) +{ + static const char *const ip6_properties_to_save[] = { + "accept_ra", + "forwarding", + "disable_ipv6", + "hop_limit", + "use_tempaddr", + }; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform * platform = nm_device_get_platform(self); + const char * ifname; + char * value; + int i; + + g_hash_table_remove_all(priv->ip6_saved_properties); + + ifname = nm_device_get_ip_iface_from_platform(self); + if (!ifname) + return; + + for (i = 0; i < G_N_ELEMENTS(ip6_properties_to_save); i++) { + value = + nm_platform_sysctl_ip_conf_get(platform, AF_INET6, ifname, ip6_properties_to_save[i]); + if (value) { + g_hash_table_insert(priv->ip6_saved_properties, + (char *) ip6_properties_to_save[i], + value); + } + } +} + +static void +restore_ip6_properties(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init(&iter, priv->ip6_saved_properties); + while (g_hash_table_iter_next(&iter, &key, &value)) { + /* Don't touch "disable_ipv6" if we're doing userland IPv6LL */ + if (priv->ipv6ll_handle && nm_streq(key, "disable_ipv6")) + continue; + nm_device_sysctl_ip_conf_set(self, AF_INET6, key, value); + } +} + +static void +set_disable_ipv6(NMDevice *self, const char *value) +{ + /* We only touch disable_ipv6 when NM is not managing the IPv6LL address */ + if (!NM_DEVICE_GET_PRIVATE(self)->ipv6ll_handle) + nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", value); +} + +static void +set_nm_ipv6ll(NMDevice *self, gboolean enable) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + int ifindex = nm_device_get_ip_ifindex(self); + + if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) + return; + + priv->ipv6ll_handle = enable; + if (ifindex > 0) { + const char *detail = enable ? "enable" : "disable"; + int r; + + _LOGD(LOGD_IP6, "will %s userland IPv6LL", detail); + r = nm_platform_link_set_user_ipv6ll_enabled(nm_device_get_platform(self), ifindex, enable); + if (r < 0) { + _NMLOG(NM_IN_SET(r, -NME_PL_NOT_FOUND, -NME_PL_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN, + LOGD_IP6, + "failed to %s userspace IPv6LL address handling (%s)", + detail, + nm_strerror(r)); + } + + if (enable) { + gs_free char *value = NULL; + + /* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */ + value = nm_device_sysctl_ip_conf_get(self, AF_INET6, "disable_ipv6"); + if (nm_streq0(value, "0")) + nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1"); + + /* Ensure IPv6 is enabled */ + nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "0"); + } + } +} + +/*****************************************************************************/ + +static gboolean +ip_requires_slaves(NMDevice *self, int addr_family) +{ + const char *method; + + method = nm_device_get_effective_ip_config_method(self, addr_family); + + if (NM_IS_IPv4(addr_family)) + return nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves) + * to complete addressing. SLAAC and DHCP need a peer to provide a prefix. + */ + return NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP); +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * self, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + const char * method; + + nm_assert_addr_family(addr_family); + + connection = nm_device_get_applied_connection(self); + + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + if (connection_ip_method_requires_carrier(connection, addr_family, NULL) + && nm_device_is_master(self) && !priv->carrier) { + _LOGI(LOGD_IP | LOGD_DEVICE, + "IPv%c config waiting until carrier is on", + nm_utils_addr_family_to_char(addr_family)); + return NM_ACT_STAGE_RETURN_IP_WAIT; + } + + if (nm_device_is_master(self) && ip_requires_slaves(self, addr_family)) { + /* If the master has no ready slaves, and depends on slaves for + * a successful IP configuration attempt, then postpone IP addressing. + */ + if (!have_any_ready_slaves(self)) { + _LOGI(LOGD_DEVICE | LOGD_IP, + "IPv%c config waiting until slaves are ready", + nm_utils_addr_family_to_char(addr_family)); + return NM_ACT_STAGE_RETURN_IP_WAIT; + } + } + + if (!IS_IPv4) + priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE; + + method = nm_device_get_effective_ip_config_method(self, addr_family); + + _LOGD(LOGD_IP | LOGD_DEVICE, + "IPv%c config method is %s", + nm_utils_addr_family_to_char(addr_family), + method); + + if (IS_IPv4) { + if (NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + NMSettingIPConfig *s_ip4; + NMIP4Config ** configs, *config; + guint num_addresses; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_return_val_if_fail(s_ip4, NM_ACT_STAGE_RETURN_FAILURE); + num_addresses = nm_setting_ip_config_get_num_addresses(s_ip4); + + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + ret = dhcp4_start(self); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED); + return ret; + } + } else { + g_return_val_if_fail(num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (num_addresses) { + config = nm_device_ip4_config_new(self); + nm_ip4_config_merge_setting(config, + nm_connection_get_setting_ip4_config(connection), + NM_SETTING_CONNECTION_MDNS_DEFAULT, + NM_SETTING_CONNECTION_LLMNR_DEFAULT, + nm_device_get_route_table(self, AF_INET), + nm_device_get_route_metric(self, AF_INET)); + configs = g_new0(NMIP4Config *, 2); + configs[0] = config; + ipv4_dad_start(self, configs, ipv4_manual_method_apply); + } + } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) { + ret = ipv4ll_start(self); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED); + } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) { + if (out_config) { + *out_config = shared4_new_config(self, connection); + if (*out_config) { + priv->dnsmasq_manager = nm_dnsmasq_manager_new(nm_device_get_ip_iface(self)); + ret = NM_ACT_STAGE_RETURN_SUCCESS; + } else { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + } else + g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE); + } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + ret = NM_ACT_STAGE_RETURN_SUCCESS; + else + _LOGW(LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method); + + return ret; + } else { + NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + const char * ip6_privacy_str = "0"; + NMPlatform * platform; + int ifindex; + + if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1"); + return NM_ACT_STAGE_RETURN_IP_DONE; + } + + if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { + if (!nm_device_sys_iface_state_is_external(self)) { + if (priv->master) { + /* If a device only has an IPv6 link-local address, + * we don't generate an assumed connection. Therefore, + * when a new slave connection (without IP configuration) + * is activated on the device, the link-local address + * remains configured. The IP configuration of an activated + * slave should not depend on the previous state. Flush + * addresses and routes on activation. + */ + ifindex = nm_device_get_ip_ifindex(self); + platform = nm_device_get_platform(self); + + if (ifindex > 0) { + gs_unref_object NMIP6Config *config = nm_device_ip6_config_new(self); + + nm_platform_ip_route_flush(platform, AF_INET6, ifindex); + nm_platform_ip_address_flush(platform, AF_INET6, ifindex); + nm_device_set_ip_config(self, AF_INET6, (NMIPConfig *) config, FALSE, NULL); + } + } else { + gboolean ipv6ll_handle_old = priv->ipv6ll_handle; + + /* When activating an IPv6 'ignore' connection we need to revert back + * to kernel IPv6LL, but the kernel won't actually assign an address + * to the interface until disable_ipv6 is bounced. + */ + set_nm_ipv6ll(self, FALSE); + if (ipv6ll_handle_old) + nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1"); + restore_ip6_properties(self); + } + } + return NM_ACT_STAGE_RETURN_IP_DONE; + } + + /* Ensure the MTU makes sense. If it was below 1280 the kernel would not + * expose any ipv6 sysctls or allow presence of any addresses on the interface, + * including LL, which * would make it impossible to autoconfigure MTU to a + * correct value. */ + _commit_mtu(self, priv->ip_config_4); + + /* Any method past this point requires an IPv6LL address. Use NM-controlled + * IPv6LL if this is not an assumed connection, since assumed connections + * will already have IPv6 set up. + */ + if (!nm_device_sys_iface_state_is_external_or_assume(self)) + set_nm_ipv6ll(self, TRUE); + + /* Re-enable IPv6 on the interface */ + nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0"); + set_disable_ipv6(self, "0"); + + /* Synchronize external IPv6 configuration with kernel, since + * linklocal6_start() uses the information there to determine if we can + * proceed with the selected method (SLAAC, DHCP, link-local). + */ + nm_platform_process_events(nm_device_get_platform(self)); + g_clear_object(&priv->ext_ip6_config_captured); + priv->ext_ip6_config_captured = + nm_ip6_config_capture(nm_device_get_multi_index(self), + nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + + ip6_privacy = _prop_get_ipv6_ip6_privacy(self); + + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_SHARED)) { + if (!addrconf6_start(self, ip6_privacy)) { + /* IPv6 might be disabled; allow IPv4 to proceed */ + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + } else + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) { + ret = + linklocal6_start(self) ? NM_ACT_STAGE_RETURN_SUCCESS : NM_ACT_STAGE_RETURN_POSTPONE; + } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) { + priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED; + if (!dhcp6_start(self, TRUE)) { + /* IPv6 might be disabled; allow IPv4 to proceed */ + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + } else + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + ret = NM_ACT_STAGE_RETURN_SUCCESS; + else + _LOGW(LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method); + + if (ret != NM_ACT_STAGE_RETURN_FAILURE + && !nm_device_sys_iface_state_is_external_or_assume(self)) { + switch (ip6_privacy) { + case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN: + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + ip6_privacy_str = "0"; + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + ip6_privacy_str = "1"; + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + ip6_privacy_str = "2"; + break; + } + nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", ip6_privacy_str); + } + + return ret; + } +} + +gboolean +nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMActStageReturn ret; + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + gs_unref_object NMIPConfig *ip_config = NULL; + + g_assert(priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_WAIT); + + if (nm_device_sys_iface_state_is_external(self)) { + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE); + check_ip_state(self, FALSE, TRUE); + return TRUE; + } + + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_CONF); + + ret = NM_DEVICE_GET_CLASS(self)->act_stage3_ip_config_start(self, + addr_family, + (gpointer *) &ip_config, + &failure_reason); + + switch (ret) { + case NM_ACT_STAGE_RETURN_SUCCESS: + if (!IS_IPv4) { + /* Here we get a static IPv6 config, like for Shared where it's + * autogenerated or from modems where it comes from ModemManager. + */ + if (!ip_config) + ip_config = nm_device_ip_config_new(self, addr_family); + nm_assert(!applied_config_get_current(&priv->ac_ip6_config)); + applied_config_init(&priv->ac_ip6_config, ip_config); + ip_config = NULL; + } + nm_device_activate_schedule_ip_config_result(self, addr_family, ip_config); + break; + case NM_ACT_STAGE_RETURN_IP_DONE: + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE); + check_ip_state(self, FALSE, TRUE); + break; + case NM_ACT_STAGE_RETURN_FAILURE: + nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason); + return FALSE; + case NM_ACT_STAGE_RETURN_IP_FAIL: + /* Activation not wanted */ + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL); + break; + case NM_ACT_STAGE_RETURN_IP_WAIT: + /* Wait for something to try IP config again */ + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT); + break; + default: + g_assert(ret == NM_ACT_STAGE_RETURN_POSTPONE); + } + + return TRUE; +} + +/* + * activate_stage3_ip_config_start + * + * Begin automatic/manual IP configuration + * + */ +static void +activate_stage3_ip_config_start(NMDevice *self) +{ + int ifindex; + + _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_WAIT); + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_WAIT); + + _active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_LAYER2_READY); + + nm_device_state_changed(self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + /* Device should be up before we can do anything with it */ + if ((ifindex = nm_device_get_ip_ifindex(self)) > 0 + && !nm_platform_link_is_up(nm_device_get_platform(self), ifindex)) + _LOGW(LOGD_DEVICE, + "interface %s not up for IP configuration", + nm_device_get_ip_iface(self)); + + if (nm_device_activate_ip4_state_in_wait(self) + && !nm_device_activate_stage3_ip_start(self, AF_INET)) + return; + + if (nm_device_activate_ip6_state_in_wait(self) + && !nm_device_activate_stage3_ip_start(self, AF_INET6)) + return; + + /* Proxy */ + nm_device_set_proxy_config(self, NULL); + + check_ip_state(self, TRUE, TRUE); +} + +static void +fw_change_zone_cb(NMFirewallManager * firewall_manager, + NMFirewallManagerCallId *call_id, + GError * error, + gpointer user_data) +{ + NMDevice * self = user_data; + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->fw_call != call_id) + g_return_if_reached(); + + priv->fw_call = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + switch (priv->fw_state) { + case FIREWALL_STATE_WAIT_STAGE_3: + priv->fw_state = FIREWALL_STATE_INITIALIZED; + nm_device_activate_schedule_stage3_ip_config_start(self); + break; + case FIREWALL_STATE_WAIT_IP_CONFIG: + priv->fw_state = FIREWALL_STATE_INITIALIZED; + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE + || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) + nm_device_start_ip_check(self); + break; + case FIREWALL_STATE_INITIALIZED: + break; + default: + g_return_if_reached(); + } +} + +static void +fw_change_zone(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * applied_connection; + NMSettingConnection *s_con; + const char * zone; + + nm_assert(priv->fw_state >= FIREWALL_STATE_INITIALIZED); + + applied_connection = nm_device_get_applied_connection(self); + nm_assert(applied_connection); + + s_con = nm_connection_get_setting_connection(applied_connection); + nm_assert(s_con); + + if (priv->fw_call) { + nm_firewall_manager_cancel_call(priv->fw_call); + nm_assert(!priv->fw_call); + } + + if (G_UNLIKELY(!priv->fw_mgr)) + priv->fw_mgr = g_object_ref(nm_firewall_manager_get()); + + zone = nm_setting_connection_get_zone(s_con); +#if WITH_FIREWALLD_ZONE + if (!zone || zone[0] == '\0') { + if (nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET), + NM_SETTING_IP4_CONFIG_METHOD_SHARED) + || nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET6), + NM_SETTING_IP6_CONFIG_METHOD_SHARED)) + zone = "nm-shared"; + } +#endif + priv->fw_call = nm_firewall_manager_add_or_change_zone(priv->fw_mgr, + nm_device_get_ip_iface(self), + zone, + FALSE, /* change zone */ + fw_change_zone_cb, + self); +} + +/* + * nm_device_activate_schedule_stage3_ip_config_start + * + * Schedule IP configuration start + */ +void +nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + g_return_if_fail(priv->act_request.obj); + ifindex = nm_device_get_ip_ifindex(self); + + /* Add the interface to the specified firewall zone */ + if (priv->fw_state == FIREWALL_STATE_UNMANAGED) { + if (nm_device_sys_iface_state_is_external(self)) { + /* fake success */ + priv->fw_state = FIREWALL_STATE_INITIALIZED; + } else if (ifindex > 0) { + priv->fw_state = FIREWALL_STATE_WAIT_STAGE_3; + fw_change_zone(self); + return; + } + /* no ifindex, nothing to do for now */ + } else if (priv->fw_state == FIREWALL_STATE_WAIT_STAGE_3) { + /* a firewall call for stage3 is pending. Return and wait. */ + return; + } + + nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED); + + activation_source_schedule(self, activate_stage3_ip_config_start, AF_INET); +} + +static NMActStageReturn +act_stage4_ip_config_timeout(NMDevice * self, + int addr_family, + NMDeviceStateReason *out_failure_reason) +{ + nm_assert_addr_family(addr_family); + + if (!get_ip_config_may_fail(self, addr_family)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +activate_stage4_ip_config_timeout_x(NMDevice *self, int addr_family) +{ + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + NMActStageReturn ret; + + ret = + NM_DEVICE_GET_CLASS(self)->act_stage4_ip_config_timeout(self, addr_family, &failure_reason); + + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + return; + + if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason); + return; + } + g_assert(ret == NM_ACT_STAGE_RETURN_SUCCESS); + + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL); + check_ip_state(self, FALSE, TRUE); +} + +static void +activate_stage4_ip_config_timeout_4(NMDevice *self) +{ + activate_stage4_ip_config_timeout_x(self, AF_INET); +} + +static void +activate_stage4_ip_config_timeout_6(NMDevice *self) +{ + activate_stage4_ip_config_timeout_x(self, AF_INET6); +} + +void +nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_if_fail(priv->act_request.obj); + + activation_source_schedule(self, + IS_IPv4 ? activate_stage4_ip_config_timeout_4 + : activate_stage4_ip_config_timeout_6, + addr_family); +} + +static gboolean +share_init(NMDevice *self, GError **error) +{ + const char *const modules[] = {"ip_tables", + "iptable_nat", + "nf_nat_ftp", + "nf_nat_irc", + "nf_nat_sip", + "nf_nat_tftp", + "nf_nat_pptp", + "nf_nat_h323"}; + guint i; + int errsv; + + if (nm_platform_sysctl_get_int32(nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"), + -1) + == 1) { + /* nothing to do. */ + } else if (!nm_platform_sysctl_set(nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"), + "1")) { + errsv = errno; + _LOGD(LOGD_SHARING, + "share: error enabling IPv4 forwarding: (%d) %s", + errsv, + nm_strerror_native(errsv)); + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot set ipv4/ip_forward: %s", + nm_strerror_native(errsv)); + return FALSE; + } + + if (nm_platform_sysctl_get_int32(nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"), + -1) + == 1) { + /* nothing to do. */ + } else if (!nm_platform_sysctl_set(nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"), + "1")) { + errsv = errno; + _LOGD(LOGD_SHARING, + "share: error enabling dynamic addresses: (%d) %s", + errsv, + nm_strerror_native(errsv)); + } + + for (i = 0; i < G_N_ELEMENTS(modules); i++) + nm_utils_modprobe(NULL, FALSE, modules[i], NULL); + + return TRUE; +} + +static gboolean +start_sharing(NMDevice *self, NMIP4Config *config, GError **error) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMActRequest * req; + const NMPlatformIP4Address *ip4_addr = NULL; + const char * ip_iface; + GError * local = NULL; + NMConnection * conn; + NMSettingConnection * s_con; + gboolean announce_android_metered; + NMUtilsShareRules * share_rules; + + g_return_val_if_fail(config, FALSE); + + ip_iface = nm_device_get_ip_iface(self); + if (!ip_iface) { + g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "device has no ip interface"); + return FALSE; + } + + ip4_addr = nm_ip4_config_get_first_address(config); + if (!ip4_addr || !ip4_addr->address) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "could not determine IPv4 address"); + return FALSE; + } + + if (!share_init(self, error)) + return FALSE; + + req = nm_device_get_act_request(self); + g_return_val_if_fail(req, FALSE); + + share_rules = nm_utils_share_rules_new(); + + nm_utils_share_rules_add_all_rules(share_rules, ip_iface, ip4_addr->address, ip4_addr->plen); + + nm_utils_share_rules_apply(share_rules, TRUE); + + nm_act_request_set_shared(req, share_rules); + + conn = nm_act_request_get_applied_connection(req); + s_con = nm_connection_get_setting_connection(conn); + + switch (nm_setting_connection_get_metered(s_con)) { + case NM_METERED_YES: + /* honor the metered flag. Note that reapply on the device does not affect + * the metered setting. This is different from other profiles, where the + * metered flag of an activated profile can be changed (reapplied). */ + announce_android_metered = TRUE; + break; + case NM_METERED_UNKNOWN: + /* we pick up the current value and announce it. But again, we cannot update + * the announced setting without restarting dnsmasq. That means, if the default + * route changes w.r.t. being metered, then the shared connection does not get + * updated before reactivating. */ + announce_android_metered = + NM_IN_SET(nm_manager_get_metered(NM_MANAGER_GET), NM_METERED_YES, NM_METERED_GUESS_YES); + break; + default: + announce_android_metered = FALSE; + break; + } + + if (!nm_dnsmasq_manager_start(priv->dnsmasq_manager, + config, + announce_android_metered, + &local)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "could not start dnsmasq due to %s", + local->message); + g_error_free(local); + nm_act_request_set_shared(req, NULL); + return FALSE; + } + + priv->dnsmasq_state_id = g_signal_connect(priv->dnsmasq_manager, + NM_DNS_MASQ_MANAGER_STATE_CHANGED, + G_CALLBACK(dnsmasq_state_changed_cb), + self); + return TRUE; +} + +static void +arp_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_clear_pointer(&priv->acd.announcing, nm_acd_manager_free); +} + +void +nm_device_arp_announce(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + NMSettingIPConfig *s_ip4; + guint num, i; + const guint8 * hw_addr; + size_t hw_addr_len = 0; + + arp_cleanup(self); + + hw_addr = nm_platform_link_get_address(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + &hw_addr_len); + + if (!hw_addr || hw_addr_len != ETH_ALEN) + return; + + /* We only care about manually-configured addresses; DHCP- and autoip-configured + * ones should already have been seen on the network at this point. + */ + connection = nm_device_get_applied_connection(self); + if (!connection) + return; + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (!s_ip4) + return; + num = nm_setting_ip_config_get_num_addresses(s_ip4); + if (num == 0) + return; + + priv->acd.announcing = + nm_acd_manager_new(nm_device_get_ip_ifindex(self), hw_addr, hw_addr_len, NULL, NULL); + + for (i = 0; i < num; i++) { + NMIPAddress *ip = nm_setting_ip_config_get_address(s_ip4, i); + in_addr_t addr; + + if (inet_pton(AF_INET, nm_ip_address_get_address(ip), &addr) == 1) + nm_acd_manager_add_address(priv->acd.announcing, addr); + else + g_warn_if_reached(); + } + + nm_acd_manager_announce_addresses(priv->acd.announcing); +} + +static void +activate_stage5_ip_config_result_x(NMDevice *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMActRequest * req; + const char * method; + int ip_ifindex; + int errsv; + gboolean do_announce = FALSE; + + req = nm_device_get_act_request(self); + g_assert(req); + + /* Interface must be IFF_UP before IP config can be applied */ + ip_ifindex = nm_device_get_ip_ifindex(self); + g_return_if_fail(ip_ifindex); + + if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex) + && !nm_device_sys_iface_state_is_external_or_assume(self)) { + nm_platform_link_set_up(nm_device_get_platform(self), ip_ifindex, NULL); + if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)) + _LOGW(LOGD_DEVICE, + "interface %s not up for IP configuration", + nm_device_get_ip_iface(self)); + } + + if (!ip_config_merge_and_apply(self, addr_family, TRUE)) { + _LOGD(LOGD_DEVICE | LOGD_IPX(IS_IPv4), + "Activation: Stage 5 of 5 (IPv%c Commit) failed", + nm_utils_addr_family_to_char(addr_family)); + nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + + if (!IS_IPv4) { + if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE + && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) { + if (applied_config_get_current(&priv->dhcp6.ip6_config)) { + /* If IPv6 wasn't the first IP to complete, and DHCP was used, + * then ensure dispatcher scripts get the DHCP lease information. + */ + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE, + self, + NULL, + NULL, + NULL, + NULL); + } else { + /* still waiting for first dhcp6 lease. */ + return; + } + } + } + + /* Start IPv4 sharing/IPv6 forwarding if we need it */ + method = nm_device_get_effective_ip_config_method(self, addr_family); + if (IS_IPv4) { + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) { + gs_free_error GError *error = NULL; + + if (!start_sharing(self, priv->ip_config_4, &error)) { + _LOGW(LOGD_SHARING, + "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s", + error->message); + nm_device_ip_method_failed(self, + AF_INET, + NM_DEVICE_STATE_REASON_SHARED_START_FAILED); + return; + } + } + } else { + if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) { + if (!nm_platform_sysctl_set( + nm_device_get_platform(self), + NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/all/forwarding"), + "1")) { + errsv = errno; + _LOGE(LOGD_SHARING, + "share: error enabling IPv6 forwarding: (%d) %s", + errsv, + nm_strerror_native(errsv)); + nm_device_ip_method_failed(self, + AF_INET6, + NM_DEVICE_STATE_REASON_SHARED_START_FAILED); + return; + } + } + } + + if (IS_IPv4) { + if (priv->dhcp_data_4.client) { + gs_free_error GError *error = NULL; + + if (!nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) { + _LOGW(LOGD_DHCP4, + "Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s", + error->message); + nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_DHCP_ERROR); + return; + } + } + + /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure + * dispatcher scripts get the DHCP lease information. + */ + if (priv->dhcp_data_4.client && nm_device_activate_ip4_state_in_conf(self) + && (nm_device_get_state(self) > NM_DEVICE_STATE_IP_CONFIG)) { + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE, + self, + NULL, + NULL, + NULL, + NULL); + } + } + + if (!IS_IPv4) { + /* Check if we have to wait for DAD */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) { + if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail(self, AF_INET6)) + _LOGI(LOGD_DEVICE | LOGD_IP6, + "IPv6 DAD: carrier missing and ignored, not delaying activation"); + else + priv->dad6_ip6_config = dad6_get_pending_addresses(self); + + if (priv->dad6_ip6_config) { + _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination"); + } else { + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE); + check_ip_state(self, FALSE, TRUE); + } + } + } + + if (IS_IPv4) { + /* Send ARP announcements */ + + if (nm_device_is_master(self)) { + CList * iter; + SlaveInfo *info; + + /* Skip announcement if there are no device enslaved, for two reasons: + * 1) the master has a temporary MAC address until the first slave comes + * 2) announcements are going to be dropped anyway without slaves + */ + do_announce = FALSE; + + c_list_for_each (iter, &priv->slaves) { + info = c_list_entry(iter, SlaveInfo, lst_slave); + if (info->slave_is_enslaved) { + do_announce = TRUE; + break; + } + } + } else + do_announce = TRUE; + + if (do_announce) + nm_device_arp_announce(self); + } + + if (IS_IPv4) { + /* Enter the IP_CHECK state if this is the first method to complete */ + _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE); + check_ip_state(self, FALSE, TRUE); + } +} + +static void +activate_stage5_ip_config_result_4(NMDevice *self) +{ + activate_stage5_ip_config_result_x(self, AF_INET); +} + +static void +activate_stage5_ip_config_result_6(NMDevice *self) +{ + activate_stage5_ip_config_result_x(self, AF_INET6); +} + +#define activate_stage5_ip_config_result_x_fcn(addr_family) \ + (NM_IS_IPv4(addr_family) ? activate_stage5_ip_config_result_4 \ + : activate_stage5_ip_config_result_6) + +void +nm_device_activate_schedule_ip_config_result(NMDevice *self, int addr_family, NMIPConfig *config) +{ + NMDevicePrivate *priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(!config || (IS_IPv4 && nm_ip_config_get_addr_family(config) == AF_INET)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (IS_IPv4) { + applied_config_init(&priv->dev_ip_config_4, config); + } else { + /* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we + * clearly now have configuration. + */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL) + _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF); + } + + activation_source_schedule(self, + activate_stage5_ip_config_result_x_fcn(addr_family), + addr_family); +} + +NMDeviceIPState +nm_device_activate_get_ip_state(NMDevice *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_IP_STATE_NONE); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE); + + return NM_DEVICE_GET_PRIVATE(self)->ip_state_x[IS_IPv4]; +} + +static void +dad6_add_pending_address(NMDevice * self, + NMPlatform * platform, + int ifindex, + const struct in6_addr *address, + NMIP6Config ** dad6_config) +{ + const NMPlatformIP6Address *pl_addr; + + pl_addr = nm_platform_ip6_address_get(platform, ifindex, address); + if (pl_addr && NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_DADFAILED) + && !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) { + _LOGt(LOGD_DEVICE, + "IPv6 DAD: pending address %s", + nm_platform_ip6_address_to_string(pl_addr, NULL, 0)); + + if (!*dad6_config) + *dad6_config = nm_device_ip6_config_new(self); + + nm_ip6_config_add_address(*dad6_config, pl_addr); + } +} + +/* + * Returns a NMIP6Config containing NM-configured addresses which + * have the tentative flag, or NULL if none is present. + */ +static NMIP6Config * +dad6_get_pending_addresses(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMIP6Config * confs[] = {(NMIP6Config *) applied_config_get_current(&priv->ac_ip6_config), + (NMIP6Config *) applied_config_get_current(&priv->dhcp6.ip6_config), + priv->con_ip_config_6, + (NMIP6Config *) applied_config_get_current(&priv->dev2_ip_config_6)}; + const NMPlatformIP6Address *addr; + NMIP6Config * dad6_config = NULL; + NMDedupMultiIter ipconf_iter; + guint i; + int ifindex; + NMPlatform * platform; + + ifindex = nm_device_get_ip_ifindex(self); + g_return_val_if_fail(ifindex > 0, NULL); + + platform = nm_device_get_platform(self); + + if (priv->ipv6ll_has) { + dad6_add_pending_address(self, platform, ifindex, &priv->ipv6ll_addr, &dad6_config); + } + + /* We are interested only in addresses that we have explicitly configured, + * not in externally added ones. + */ + for (i = 0; i < G_N_ELEMENTS(confs); i++) { + if (!confs[i]) + continue; + + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) { + dad6_add_pending_address(self, platform, ifindex, &addr->address, &dad6_config); + } + } + + return dad6_config; +} + +/*****************************************************************************/ + +static void +act_request_set(NMDevice *self, NMActRequest *act_request) +{ + NMDevicePrivate *priv; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(!act_request || NM_IS_ACT_REQUEST(act_request)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->act_request.visible && priv->act_request.obj == act_request) + return; + + /* always clear the public flag. The few callers that set a new @act_request + * don't want that the property is public yet. */ + nm_dbus_track_obj_path_set(&priv->act_request, act_request, FALSE); + + if (act_request) { + switch (nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(act_request))) { + case NM_ACTIVATION_TYPE_EXTERNAL: + break; + case NM_ACTIVATION_TYPE_ASSUME: + if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_ASSUME); + break; + case NM_ACTIVATION_TYPE_MANAGED: + if (NM_IN_SET_TYPED(NMDeviceSysIfaceState, + priv->sys_iface_state, + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME)) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + break; + } + } +} + +static void +dnsmasq_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->dnsmasq_manager) + return; + + nm_clear_g_signal_handler(priv->dnsmasq_manager, &priv->dnsmasq_state_id); + + nm_dnsmasq_manager_stop(priv->dnsmasq_manager); + g_object_unref(priv->dnsmasq_manager); + priv->dnsmasq_manager = NULL; +} + +gboolean +nm_device_is_nm_owned(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->nm_owned; +} + +/* + * delete_on_deactivate_link_delete + * + * Function will be queued with g_idle_add to call + * nm_platform_link_delete for the underlying resources + * of the device. + */ +static gboolean +delete_on_deactivate_link_delete(gpointer user_data) +{ + DeleteOnDeactivateData *data = user_data; + NMDevice * self = data->device; + + _LOGD(LOGD_DEVICE, + "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)", + data->ifindex, + data->idle_add_id); + + if (data->device) { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(data->device); + gs_free_error GError *error = NULL; + + g_object_remove_weak_pointer(G_OBJECT(data->device), (void **) &data->device); + priv->delete_on_deactivate_data = NULL; + + if (!nm_device_unrealize(data->device, TRUE, &error)) + _LOGD(LOGD_DEVICE, + "delete_on_deactivate: unrealizing %d failed (%s)", + data->ifindex, + error->message); + } else if (data->ifindex > 0) + nm_platform_link_delete(nm_device_get_platform(self), data->ifindex); + + nm_device_emit_recheck_auto_activate(self); + + g_free(data); + return FALSE; +} + +static void +delete_on_deactivate_unschedule(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->delete_on_deactivate_data) { + DeleteOnDeactivateData *data = priv->delete_on_deactivate_data; + + priv->delete_on_deactivate_data = NULL; + + g_source_remove(data->idle_add_id); + g_object_remove_weak_pointer(G_OBJECT(self), (void **) &data->device); + _LOGD(LOGD_DEVICE, + "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)", + data->ifindex, + data->idle_add_id); + g_free(data); + } +} + +static void +delete_on_deactivate_check_and_schedule(NMDevice *self, int ifindex) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + DeleteOnDeactivateData *data; + + if (!priv->nm_owned) + return; + if (priv->queued_act_request) + return; + if (!nm_device_is_software(self) || !nm_device_is_real(self)) + return; + if (nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED) + return; + if (nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE) + return; + delete_on_deactivate_unschedule(self); /* always cancel and reschedule */ + + data = g_new(DeleteOnDeactivateData, 1); + g_object_add_weak_pointer(G_OBJECT(self), (void **) &data->device); + data->device = self; + data->ifindex = ifindex; + data->idle_add_id = g_idle_add(delete_on_deactivate_link_delete, data); + priv->delete_on_deactivate_data = data; + + _LOGD(LOGD_DEVICE, + "delete_on_deactivate: schedule cleanup and delete virtual link #%d (id=%u)", + ifindex, + data->idle_add_id); +} + +static void +_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_NONE); + + if (nm_clear_g_source(&priv->queued_ip_config_id_x[IS_IPv4])) { + _LOGD(LOGD_DEVICE, + "clearing queued IP%c config change", + nm_utils_addr_family_to_char(addr_family)); + } + + if (IS_IPv4) { + dhcp4_cleanup(self, cleanup_type, FALSE); + arp_cleanup(self); + dnsmasq_cleanup(self); + ipv4ll_cleanup(self); + g_slist_free_full(priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_free); + priv->acd.dad_list = NULL; + } else { + g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); + priv->dad6_failed_addrs = NULL; + g_clear_object(&priv->dad6_ip6_config); + dhcp6_cleanup(self, cleanup_type, FALSE); + nm_clear_g_source(&priv->linklocal6_timeout_id); + addrconf6_cleanup(self); + } +} + +gboolean +_nm_device_hash_check_invalid_keys(GHashTable * hash, + const char * setting_name, + GError ** error, + const char *const *whitelist) +{ + guint found_whitelisted_keys = 0; + guint i; + + nm_assert(hash && g_hash_table_size(hash) > 0); + nm_assert(whitelist && whitelist[0]); + +#if NM_MORE_ASSERTS > 10 + /* Require whitelist to only contain unique keys. */ + { + gs_unref_hashtable GHashTable *check_dups = + g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL); + + for (i = 0; whitelist[i]; i++) { + if (!g_hash_table_add(check_dups, (char *) whitelist[i])) + nm_assert(FALSE); + } + nm_assert(g_hash_table_size(check_dups) > 0); + } +#endif + + for (i = 0; whitelist[i]; i++) { + if (g_hash_table_contains(hash, whitelist[i])) + found_whitelisted_keys++; + } + + if (found_whitelisted_keys == g_hash_table_size(hash)) { + /* Good, there are only whitelisted keys in the hash. */ + return TRUE; + } + + if (error) { + GHashTableIter iter; + const char * k = NULL; + const char * first_invalid_key = NULL; + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &k, NULL)) { + if (nm_utils_strv_find_first((char **) whitelist, -1, k) < 0) { + first_invalid_key = k; + break; + } + } + if (setting_name) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Can't reapply changes to '%s.%s' setting", + setting_name, + first_invalid_key); + } else { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Can't reapply any changes to '%s' setting", + first_invalid_key); + } + g_return_val_if_fail(first_invalid_key, FALSE); + } + + return FALSE; +} + +void +nm_device_reactivate_ip_config(NMDevice * self, + int addr_family, + NMSettingIPConfig *s_ip_old, + NMSettingIPConfig *s_ip_new) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDevicePrivate *priv; + const char * method_old; + const char * method_new; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_NONE) + return; + + g_clear_object(&priv->con_ip_config_x[IS_IPv4]); + g_clear_object(&priv->ext_ip_config_x[IS_IPv4]); + if (IS_IPv4) { + g_clear_object(&priv->dev_ip_config_4.current); + } else { + g_clear_object(&priv->ac_ip6_config.current); + g_clear_object(&priv->dhcp6.ip6_config.current); + } + g_clear_object(&priv->dev2_ip_config_x[IS_IPv4].current); + + if (!IS_IPv4) { + if (priv->ipv6ll_handle && !IN6_IS_ADDR_UNSPECIFIED(&priv->ipv6ll_addr)) + priv->ipv6ll_has = TRUE; + } + + priv->con_ip_config_x[IS_IPv4] = nm_device_ip_config_new(self, addr_family); + + if (IS_IPv4) { + nm_ip4_config_merge_setting(priv->con_ip_config_4, + s_ip_new, + _prop_get_connection_mdns(self), + _prop_get_connection_llmnr(self), + nm_device_get_route_table(self, AF_INET), + nm_device_get_route_metric(self, AF_INET)); + } else { + nm_ip6_config_merge_setting(priv->con_ip_config_6, + s_ip_new, + nm_device_get_route_table(self, AF_INET6), + nm_device_get_route_metric(self, AF_INET6)); + } + + method_old = (s_ip_old ? nm_setting_ip_config_get_method(s_ip_old) : NULL) + ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED + : NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + method_new = (s_ip_new ? nm_setting_ip_config_get_method(s_ip_new) : NULL) + ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED + : NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + + if (!nm_streq0(method_old, method_new)) { + _cleanup_ip_pre(self, addr_family, CLEANUP_TYPE_DECONFIGURE); + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT); + if (!nm_device_activate_stage3_ip_start(self, addr_family)) { + _LOGW(LOGD_IP4, + "Failed to apply IPv%c configuration", + nm_utils_addr_family_to_char(addr_family)); + } + return; + } + + if (s_ip_old && s_ip_new) { + gint64 metric_old, metric_new; + + /* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is + * set at activation/renewal time using the value from static + * configuration. To support runtime change we need to update the + * dynamic configuration in place and tell the DHCP client the new + * value to use for future renewals. + */ + metric_old = nm_setting_ip_config_get_route_metric(s_ip_old); + metric_new = nm_setting_ip_config_get_route_metric(s_ip_new); + + if (metric_old != metric_new) { + if (IS_IPv4) { + if (priv->dev_ip_config_4.orig) { + nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev_ip_config_4.orig, + nm_device_get_route_metric(self, AF_INET)); + } + if (priv->dev2_ip_config_4.orig) { + nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev2_ip_config_4.orig, + nm_device_get_route_metric(self, AF_INET)); + } + if (priv->dhcp_data_4.client) { + nm_dhcp_client_set_route_metric(priv->dhcp_data_4.client, + nm_device_get_route_metric(self, AF_INET)); + } + } else { + if (priv->ac_ip6_config.orig) { + nm_ip6_config_update_routes_metric((NMIP6Config *) priv->ac_ip6_config.orig, + nm_device_get_route_metric(self, AF_INET6)); + } + if (priv->dhcp6.ip6_config.orig) { + nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dhcp6.ip6_config.orig, + nm_device_get_route_metric(self, AF_INET6)); + } + if (priv->dev2_ip_config_6.orig) { + nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dev2_ip_config_6.orig, + nm_device_get_route_metric(self, AF_INET6)); + } + if (priv->dhcp_data_6.client) { + nm_dhcp_client_set_route_metric(priv->dhcp_data_6.client, + nm_device_get_route_metric(self, AF_INET6)); + } + } + } + } + + if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, addr_family, TRUE)) { + _LOGW(LOGD_IPX(IS_IPv4), + "Failed to reapply IPv%c configuration", + nm_utils_addr_family_to_char(addr_family)); + } +} + +static void +_pacrunner_manager_add(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + + priv->pacrunner_conf_id = nm_pacrunner_manager_add(nm_pacrunner_manager_get(), + priv->proxy_config, + nm_device_get_ip_iface(self), + NULL, + NULL); +} + +static void +reactivate_proxy_config(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->pacrunner_conf_id) + return; + nm_device_set_proxy_config(self, priv->dhcp4.pac_url); + _pacrunner_manager_add(self); +} + +static gboolean +can_reapply_change(NMDevice * self, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + if (nm_streq(setting_name, NM_SETTING_CONNECTION_SETTING_NAME)) { + /* Whitelist allowed properties from "connection" setting which are + * allowed to differ. + * + * This includes UUID, there is no principal problem with reapplying a + * connection and changing its UUID. In fact, disallowing it makes it + * cumbersome for the user to reapply any connection but the original + * settings-connection. */ + return nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_CONNECTION_SETTING_NAME, + error, + NM_SETTING_CONNECTION_ID, + NM_SETTING_CONNECTION_UUID, + NM_SETTING_CONNECTION_STABLE_ID, + NM_SETTING_CONNECTION_AUTOCONNECT, + NM_SETTING_CONNECTION_ZONE, + NM_SETTING_CONNECTION_METERED, + NM_SETTING_CONNECTION_LLDP, + NM_SETTING_CONNECTION_MDNS, + NM_SETTING_CONNECTION_LLMNR); + } + + if (NM_IN_STRSET(setting_name, + NM_SETTING_USER_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME)) + return TRUE; + + if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) { + if (NM_IN_SET(NM_DEVICE_GET_CLASS(self)->get_configured_mtu, + nm_device_get_configured_mtu_wired_parent, + nm_device_get_configured_mtu_for_wired)) { + return nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_WIRED_SETTING_NAME, + error, + NM_SETTING_WIRED_MTU); + } + goto out_fail; + } + + if (nm_streq(setting_name, NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME) + && NM_DEVICE_GET_CLASS(self)->can_reapply_change_ovs_external_ids) { + /* TODO: this means, you cannot reapply changes to the external-ids for + * OVS system interfaces. */ + return TRUE; + } + +out_fail: + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Can't reapply any changes to '%s' setting", + setting_name); + return FALSE; +} + +static void +reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new) +{} + +/* check_and_reapply_connection: + * @connection: the new connection settings to be applied or %NULL to reapply + * the current settings connection + * @version_id: either zero, or the current version id for the applied + * connection. + * @audit_args: on return, a string representing the changes + * @error: the error if %FALSE is returned + * + * Change configuration of an already configured device if possible. + * Updates the device's applied connection upon success. + * + * Return: %FALSE if the new configuration can not be reapplied. + */ +static gboolean +check_and_reapply_connection(NMDevice * self, + NMConnection *connection, + guint64 version_id, + char ** audit_args, + GError ** error) +{ + NMDeviceClass * klass = NM_DEVICE_GET_CLASS(self); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * applied = nm_device_get_applied_connection(self); + gs_unref_object NMConnection *applied_clone = NULL; + gs_unref_hashtable GHashTable *diffs = NULL; + NMConnection * con_old, *con_new; + NMSettingIPConfig * s_ip4_old, *s_ip4_new; + NMSettingIPConfig * s_ip6_old, *s_ip6_new; + GHashTableIter iter; + + if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Device is not activated"); + return FALSE; + } + + nm_connection_diff(connection, + applied, + NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP + | NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS, + &diffs); + + if (audit_args) { + if (diffs && nm_audit_manager_audit_enabled(nm_audit_manager_get())) + *audit_args = nm_utils_format_con_diff_for_audit(diffs); + else + *audit_args = NULL; + } + + /************************************************************************** + * check for unsupported changes and reject to reapply + *************************************************************************/ + if (diffs) { + char * setting_name; + GHashTable *setting_diff; + + g_hash_table_iter_init(&iter, diffs); + while ( + g_hash_table_iter_next(&iter, (gpointer *) &setting_name, (gpointer *) &setting_diff)) { + if (!klass->can_reapply_change( + self, + setting_name, + nm_connection_get_setting_by_name(applied, setting_name), + nm_connection_get_setting_by_name(connection, setting_name), + setting_diff, + error)) + return FALSE; + } + } + + if (version_id != 0 + && version_id + != nm_active_connection_version_id_get( + (NMActiveConnection *) priv->act_request.obj)) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_VERSION_ID_MISMATCH, + "Reapply failed because device changed in the meantime and the version-id mismatches"); + return FALSE; + } + + /************************************************************************** + * Update applied connection + *************************************************************************/ + + if (diffs) + nm_active_connection_version_id_bump((NMActiveConnection *) priv->act_request.obj); + + _LOGD(LOGD_DEVICE, + "reapply (version-id %llu%s)", + (unsigned long long) nm_active_connection_version_id_get( + ((NMActiveConnection *) priv->act_request.obj)), + diffs ? "" : " (unmodified)"); + + if (diffs) { + NMConnection * connection_clean = connection; + gs_unref_object NMConnection *connection_clean_free = NULL; + + { + NMSettingConnection *s_con_a, *s_con_n; + + /* we allow re-applying a connection with differing ID, UUID, STABLE_ID and AUTOCONNECT. + * This is for convenience but these values are not actually changeable. So, check + * if they changed, and if the did revert to the original values. */ + s_con_a = nm_connection_get_setting_connection(applied); + s_con_n = nm_connection_get_setting_connection(connection); + + if (!nm_streq(nm_setting_connection_get_id(s_con_a), + nm_setting_connection_get_id(s_con_n)) + || !nm_streq(nm_setting_connection_get_uuid(s_con_a), + nm_setting_connection_get_uuid(s_con_n)) + || nm_setting_connection_get_autoconnect(s_con_a) + != nm_setting_connection_get_autoconnect(s_con_n) + || !nm_streq0(nm_setting_connection_get_stable_id(s_con_a), + nm_setting_connection_get_stable_id(s_con_n))) { + connection_clean_free = nm_simple_connection_new_clone(connection); + connection_clean = connection_clean_free; + s_con_n = nm_connection_get_setting_connection(connection_clean); + g_object_set(s_con_n, + NM_SETTING_CONNECTION_ID, + nm_setting_connection_get_id(s_con_a), + NM_SETTING_CONNECTION_UUID, + nm_setting_connection_get_uuid(s_con_a), + NM_SETTING_CONNECTION_AUTOCONNECT, + nm_setting_connection_get_autoconnect(s_con_a), + NM_SETTING_CONNECTION_STABLE_ID, + nm_setting_connection_get_stable_id(s_con_a), + NULL); + } + } + + con_old = applied_clone = nm_simple_connection_new_clone(applied); + con_new = applied; + /* FIXME(applied-connection-immutable): we should not modify the applied + * connection but replace it with a new (immutable) instance. */ + nm_connection_replace_settings_from_connection(applied, connection_clean); + nm_connection_clear_secrets(applied); + } else + con_old = con_new = applied; + + priv->v4_commit_first_time = TRUE; + priv->v6_commit_first_time = TRUE; + + priv->v4_route_table_initialized = FALSE; + priv->v6_route_table_initialized = FALSE; + + /************************************************************************** + * Reapply changes + * + * Note that reapply_connection() is called as very first. This is for example + * important for NMDeviceWireGuard, which implements coerce_route_table() + * and get_extra_rules(). + * That is because NMDeviceWireGuard caches settings, so during reapply that + * cache must be updated *first*. + *************************************************************************/ + klass->reapply_connection(self, con_old, con_new); + + if (priv->state >= NM_DEVICE_STATE_CONFIG) + lldp_init(self, FALSE); + + if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) { + s_ip4_old = nm_connection_get_setting_ip4_config(con_old); + s_ip4_new = nm_connection_get_setting_ip4_config(con_new); + s_ip6_old = nm_connection_get_setting_ip6_config(con_old); + s_ip6_new = nm_connection_get_setting_ip6_config(con_new); + + /* Allow reapply of MTU */ + priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE; + + nm_device_reactivate_ip_config(self, AF_INET, s_ip4_old, s_ip4_new); + nm_device_reactivate_ip_config(self, AF_INET6, s_ip6_old, s_ip6_new); + + _routing_rules_sync(self, NM_TERNARY_TRUE); + + reactivate_proxy_config(self); + } + + if (priv->state >= NM_DEVICE_STATE_IP_CHECK) + nm_device_update_firewall_zone(self); + + if (priv->state >= NM_DEVICE_STATE_ACTIVATED) + nm_device_update_metered(self); + + return TRUE; +} + +gboolean +nm_device_reapply(NMDevice *self, NMConnection *connection, GError **error) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + return check_and_reapply_connection(self, connection, 0, NULL, error); +} + +typedef struct { + NMConnection *connection; + guint64 version_id; +} ReapplyData; + +static void +reapply_cb(NMDevice * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + ReapplyData * reapply_data = user_data; + guint64 version_id = 0; + gs_unref_object NMConnection *connection = NULL; + GError * local = NULL; + gs_free char * audit_args = NULL; + + if (reapply_data) { + connection = reapply_data->connection; + version_id = reapply_data->version_id; + g_slice_free(ReapplyData, reapply_data); + } + + if (error) { + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, + self, + FALSE, + NULL, + subject, + error->message); + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + if (nm_device_sys_iface_state_is_external(self)) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + + if (!check_and_reapply_connection(self, + connection + ?: nm_device_get_settings_connection_get_connection(self), + version_id, + &audit_args, + &local)) { + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, + self, + FALSE, + audit_args, + subject, + local->message); + g_dbus_method_invocation_take_error(context, local); + local = NULL; + } else { + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, self, TRUE, audit_args, subject, NULL); + g_dbus_method_invocation_return_value(context, NULL); + } +} + +static void +impl_device_reapply(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDevice * self = NM_DEVICE(obj); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingsConnection *settings_connection; + NMConnection * connection = NULL; + GError * error = NULL; + ReapplyData * reapply_data; + gs_unref_variant GVariant *settings = NULL; + guint64 version_id; + guint32 flags; + + g_variant_get(parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &flags); + + /* No flags supported as of now. */ + if (flags != 0) { + error = + g_error_new_literal(NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "Invalid flags specified"); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, + self, + FALSE, + NULL, + invocation, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) { + error = g_error_new_literal(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Device is not activated"); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, + self, + FALSE, + NULL, + invocation, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + settings_connection = nm_device_get_settings_connection(self); + g_return_if_fail(settings_connection); + + if (settings && g_variant_n_children(settings)) { + /* New settings specified inline. */ + connection = _nm_simple_connection_new_from_dbus(settings, + NM_SETTING_PARSE_FLAGS_STRICT + | NM_SETTING_PARSE_FLAGS_NORMALIZE, + &error); + if (!connection) { + g_prefix_error(&error, "The settings specified are invalid: "); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, + self, + FALSE, + NULL, + invocation, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + nm_connection_clear_secrets(connection); + } + + if (connection || version_id) { + reapply_data = g_slice_new(ReapplyData); + reapply_data->connection = connection; + reapply_data->version_id = version_id; + } else + reapply_data = NULL; + + nm_device_auth_request(self, + invocation, + nm_device_get_applied_connection(self), + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + reapply_cb, + reapply_data); +} + +/*****************************************************************************/ + +static void +impl_device_get_applied_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDevice * self = NM_DEVICE(obj); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + NMConnection * applied_connection; + guint32 flags; + GVariant * var_settings; + + g_variant_get(parameters, "(u)", &flags); + + /* No flags supported as of now. */ + if (flags != 0) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Invalid flags specified"); + return; + } + + applied_connection = nm_device_get_applied_connection(self); + if (!applied_connection) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Device is not activated"); + return; + } + + if (!nm_auth_is_invocation_in_acl_set_error(applied_connection, + invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NULL, + &error)) { + g_dbus_method_invocation_take_error(invocation, g_steal_pointer(&error)); + return; + } + + var_settings = nm_connection_to_dbus(applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS); + if (!var_settings) + var_settings = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); + + g_dbus_method_invocation_return_value( + invocation, + g_variant_new( + "(@a{sa{sv}}t)", + var_settings, + nm_active_connection_version_id_get((NMActiveConnection *) priv->act_request.obj))); +} + +/*****************************************************************************/ + +typedef struct { + gint64 timestamp_ms; + bool dirty; +} IP6RoutesTemporaryNotAvailableData; + +static gboolean +_rt6_temporary_not_available_timeout(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->rt6_temporary_not_available_id = 0; + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + + return G_SOURCE_REMOVE; +} + +static gboolean +_rt6_temporary_not_available_set(NMDevice *self, GPtrArray *temporary_not_available) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + IP6RoutesTemporaryNotAvailableData *data; + GHashTableIter iter; + gint64 now_ms, oldest_ms; + const gint64 MAX_AGE_MS = 20000; + guint i; + gboolean success = TRUE; + + if (!temporary_not_available || !temporary_not_available->len) { + /* nothing outstanding. Clear tracking the routes. */ + nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source(&priv->rt6_temporary_not_available_id); + return success; + } + + if (priv->rt6_temporary_not_available) { + g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data)) + data->dirty = TRUE; + } else { + priv->rt6_temporary_not_available = + g_hash_table_new_full((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + nm_g_slice_free_fcn(IP6RoutesTemporaryNotAvailableData)); + } + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + oldest_ms = now_ms; + + for (i = 0; i < temporary_not_available->len; i++) { + const NMPObject *o = temporary_not_available->pdata[i]; + + data = g_hash_table_lookup(priv->rt6_temporary_not_available, o); + if (data) { + if (!data->dirty) + continue; + data->dirty = FALSE; + nm_assert(data->timestamp_ms > 0 && data->timestamp_ms <= now_ms); + if (now_ms > data->timestamp_ms + MAX_AGE_MS) { + /* timeout. Could not add this address. */ + _LOGW(LOGD_DEVICE, + "failure to add IPv6 route: %s", + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + success = FALSE; + } else + oldest_ms = MIN(data->timestamp_ms, oldest_ms); + continue; + } + + data = g_slice_new0(IP6RoutesTemporaryNotAvailableData); + data->timestamp_ms = now_ms; + g_hash_table_insert(priv->rt6_temporary_not_available, (gpointer) nmp_object_ref(o), data); + } + + g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data)) { + if (data->dirty) + g_hash_table_iter_remove(&iter); + } + + nm_clear_g_source(&priv->rt6_temporary_not_available_id); + priv->rt6_temporary_not_available_id = + g_timeout_add(oldest_ms + MAX_AGE_MS - now_ms, _rt6_temporary_not_available_timeout, self); + + return success; +} + +/*****************************************************************************/ + +static void +disconnect_cb(NMDevice * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + GError * local = NULL; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT, + self, + FALSE, + NULL, + subject, + error->message); + return; + } + + /* Authorized */ + if (priv->state <= NM_DEVICE_STATE_DISCONNECTED) { + local = g_error_new_literal(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Device is not active"); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT, + self, + FALSE, + NULL, + subject, + local->message); + g_dbus_method_invocation_take_error(context, local); + } else { + nm_device_autoconnect_blocked_set(self, NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT); + + nm_device_state_changed(self, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + g_dbus_method_invocation_return_value(context, NULL); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT, self, TRUE, NULL, subject, NULL); + } +} + +static void +_clear_queued_act_request(NMDevicePrivate *priv, NMActiveConnectionStateReason active_reason) +{ + if (priv->queued_act_request) { + gs_unref_object NMActRequest *ac = NULL; + + ac = g_steal_pointer(&priv->queued_act_request); + nm_active_connection_set_state_fail((NMActiveConnection *) ac, active_reason, NULL); + } +} + +static void +impl_device_disconnect(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDevice * self = NM_DEVICE(obj); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + + if (!priv->act_request.obj) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "This device is not active"); + return; + } + + connection = nm_device_get_applied_connection(self); + nm_assert(connection); + + nm_device_auth_request(self, + invocation, + connection, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + disconnect_cb, + NULL); +} + +static void +delete_cb(NMDevice * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + GError *local = NULL; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE, + self, + FALSE, + NULL, + subject, + error->message); + return; + } + + /* Authorized */ + nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE, self, TRUE, NULL, subject, NULL); + if (nm_device_unrealize(self, TRUE, &local)) + g_dbus_method_invocation_return_value(context, NULL); + else + g_dbus_method_invocation_take_error(context, local); +} + +static void +impl_device_delete(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDevice *self = NM_DEVICE(obj); + + if (!nm_device_is_software(self) || !nm_device_is_real(self)) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_SOFTWARE, + "This device is not a software device or is not realized"); + return; + } + + nm_device_auth_request(self, + invocation, + NULL, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + delete_cb, + NULL); +} + +static void +_device_activate(NMDevice *self, NMActRequest *req) +{ + NMConnection *connection; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + nm_assert(nm_device_is_real(self)); + + /* Ensure the activation request is still valid; the master may have + * already failed in which case activation of this device should not proceed. + */ + if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req)) + >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) + return; + + if (!nm_device_get_managed(self, FALSE)) { + /* It's unclear why the device would be unmanaged at this point. + * Just to be sure, handle it and error out. */ + _LOGE(LOGD_DEVICE, + "Activation: failed activating connection '%s' because device is still unmanaged", + nm_active_connection_get_settings_connection_id((NMActiveConnection *) req)); + nm_active_connection_set_state_fail((NMActiveConnection *) req, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + NULL); + return; + } + + connection = nm_act_request_get_applied_connection(req); + nm_assert(connection); + + _LOGI(LOGD_DEVICE, + "Activation: starting connection '%s' (%s)", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + delete_on_deactivate_unschedule(self); + + act_request_set(self, req); + + nm_device_activate_schedule_stage1_device_prepare(self, FALSE); +} + +static void +_carrier_wait_check_queued_act_request(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->queued_act_request || !priv->queued_act_request_is_waiting_for_carrier) + return; + + priv->queued_act_request_is_waiting_for_carrier = FALSE; + if (!priv->carrier) { + _LOGD(LOGD_DEVICE, "Cancel queued activation request as we have no carrier after timeout"); + _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + } else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) { + gs_unref_object NMActRequest *queued_req = NULL; + + _LOGD(LOGD_DEVICE, "Activate queued activation request as we now have carrier"); + queued_req = g_steal_pointer(&priv->queued_act_request); + _device_activate(self, queued_req); + } +} + +static gboolean +_carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + + /* If we have carrier or if we are not waiting for it, the activation + * request is not blocked waiting for carrier. */ + if (priv->carrier) + return FALSE; + if (priv->carrier_wait_id == 0) + return FALSE; + + connection = nm_act_request_get_applied_connection(req); + if (!connection_requires_carrier(connection)) + return FALSE; + + if (!nm_device_check_connection_available(self, + connection, + NM_DEVICE_CHECK_CON_AVAILABLE_ALL, + NULL, + NULL)) { + /* We passed all @flags we have, and no @specific_object. + * This equals maximal availability, if a connection is not available + * in this case, it is not waiting for carrier. + * + * Actually, why are we even trying to activate it? Strange, but whatever + * the reason, don't wait for carrier. + */ + return FALSE; + } + + if (nm_device_check_connection_available( + self, + connection, + NM_DEVICE_CHECK_CON_AVAILABLE_ALL + & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER, + NULL, + NULL)) { + /* The connection was available with flags ALL, and it is still available + * if we pretend not to wait for carrier. That means that the + * connection is available now, and does not wait for carrier. + * + * Since the flags increase the availability of a connection, when checking + * ALL&~WAITING_CARRIER, it means that we certainly would wait for carrier. */ + return FALSE; + } + + /* The activation request must wait for carrier. */ + return TRUE; +} + +void +nm_device_disconnect_active_connection(NMActiveConnection * active, + NMDeviceStateReason device_reason, + NMActiveConnectionStateReason active_reason) +{ + NMDevice * self; + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(active)); + + self = nm_active_connection_get_device(active); + if (!self) { + /* hm, no device? Just fail the active connection. */ + goto do_fail; + } + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (NM_ACTIVE_CONNECTION(priv->queued_act_request) == active) { + _clear_queued_act_request(priv, active_reason); + return; + } + + if (NM_ACTIVE_CONNECTION(priv->act_request.obj) == active) { + if (priv->state < NM_DEVICE_STATE_DEACTIVATING) { + /* When the user actively deactivates a profile, we set + * the sys-iface-state to managed so that we deconfigure/cleanup the interface. + * But for external connections that go down otherwise, we don't want to touch the interface. */ + if (nm_device_sys_iface_state_is_external(self)) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + + nm_device_state_changed(self, NM_DEVICE_STATE_DEACTIVATING, device_reason); + } else { + /* @active is the current ac of @self, but it's going down already. + * Nothing to do. */ + } + return; + } + + /* the active connection references this device, but it's neither the + * queued_act_request nor the current act_request. Just set it to fail... */ +do_fail: + nm_active_connection_set_state_fail(active, active_reason, NULL); +} + +void +nm_device_queue_activation(NMDevice *self, NMActRequest *req) +{ + NMDevicePrivate *priv; + gboolean must_queue; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + nm_keep_alive_arm(nm_active_connection_get_keep_alive(NM_ACTIVE_CONNECTION(req))); + + if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req)) + >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + /* it's already deactivating. Nothing to do. */ + nm_assert( + NM_IN_SET(nm_active_connection_get_device(NM_ACTIVE_CONNECTION(req)), NULL, self)); + return; + } + + nm_assert(self == nm_active_connection_get_device(NM_ACTIVE_CONNECTION(req))); + + priv = NM_DEVICE_GET_PRIVATE(self); + + must_queue = _carrier_wait_check_act_request_must_queue(self, req); + + if (!priv->act_request.obj && !must_queue && nm_device_is_real(self)) { + _device_activate(self, req); + return; + } + + /* supersede any already-queued request */ + _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + priv->queued_act_request = g_object_ref(req); + priv->queued_act_request_is_waiting_for_carrier = must_queue; + + _LOGD(LOGD_DEVICE, + "queue activation request waiting for %s", + must_queue ? "carrier" : "currently active connection to disconnect"); + + /* Deactivate existing activation request first */ + if (priv->act_request.obj) { + _LOGI(LOGD_DEVICE, "disconnecting for new activation request."); + nm_device_state_changed(self, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_NEW_ACTIVATION); + } +} + +/* + * nm_device_is_activating + * + * Return whether or not the device is currently activating itself. + * + */ +gboolean +nm_device_is_activating(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceState state; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + state = nm_device_get_state(self); + if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_SECONDARIES) + return TRUE; + + /* There's a small race between the time when stage 1 is scheduled + * and when the device actually sets STATE_PREPARE when the activation + * handler is actually run. If there's an activation handler scheduled + * we're activating anyway. + */ + return priv->activation_source_id_4 != 0; +} + +NMProxyConfig * +nm_device_get_proxy_config(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->proxy_config; +} + +static void +nm_device_set_proxy_config(NMDevice *self, const char *pac_url) +{ + NMDevicePrivate *priv; + NMConnection * connection; + NMSettingProxy * s_proxy = NULL; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + g_clear_object(&priv->proxy_config); + priv->proxy_config = nm_proxy_config_new(); + + if (pac_url) { + nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_AUTO); + nm_proxy_config_set_pac_url(priv->proxy_config, pac_url); + _LOGD(LOGD_PROXY, "proxy: PAC url \"%s\"", pac_url); + } else + nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_NONE); + + connection = nm_device_get_applied_connection(self); + if (connection) + s_proxy = nm_connection_get_setting_proxy(connection); + + if (s_proxy) + nm_proxy_config_merge_setting(priv->proxy_config, s_proxy); +} + +/* IP Configuration stuff */ +NMDhcpConfig * +nm_device_get_dhcp_config(NMDevice *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + nm_assert_addr_family(addr_family); + + return NM_DEVICE_GET_PRIVATE(self)->dhcp_data_x[IS_IPv4].config; +} + +NMIP4Config * +nm_device_get_ip4_config(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->ip_config_4; +} + +static gboolean +nm_device_set_ip_config(NMDevice * self, + int addr_family, + NMIPConfig *new_config, + gboolean commit, + GPtrArray * ip4_dev_route_blacklist) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMIPConfig * old_config; + gboolean has_changes = FALSE; + gboolean success = TRUE; + NMSettingsConnection *settings_connection; + + nm_assert_addr_family(addr_family); + nm_assert(!new_config || nm_ip_config_get_addr_family(new_config) == addr_family); + nm_assert(!new_config + || (new_config && ({ + int ip_ifindex = nm_device_get_ip_ifindex(self); + + (ip_ifindex > 0 && ip_ifindex == nm_ip_config_get_ifindex(new_config)); + }))); + nm_assert(IS_IPv4 || !ip4_dev_route_blacklist); + + _LOGD(LOGD_IPX(IS_IPv4), + "ip%c-config: update (commit=%d, new-config=%p)", + nm_utils_addr_family_to_char(addr_family), + commit, + new_config); + + /* Always commit to nm-platform to update lifetimes */ + if (commit && new_config) { + _commit_mtu(self, IS_IPv4 ? NM_IP4_CONFIG(new_config) : priv->ip_config_4); + + if (IS_IPv4) { + success = nm_ip4_config_commit(NM_IP4_CONFIG(new_config), + nm_device_get_platform(self), + _get_route_table_sync_mode_stateful(self, AF_INET)); + nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self), + nm_ip_config_get_ifindex(new_config), + ip4_dev_route_blacklist); + } else { + gs_unref_ptrarray GPtrArray *temporary_not_available = NULL; + + success = nm_ip6_config_commit(NM_IP6_CONFIG(new_config), + nm_device_get_platform(self), + _get_route_table_sync_mode_stateful(self, AF_INET6), + &temporary_not_available); + + if (!_rt6_temporary_not_available_set(self, temporary_not_available)) + success = FALSE; + } + } + + old_config = priv->ip_config_x[IS_IPv4]; + + if (new_config && old_config) { + /* has_changes is set only on relevant changes, because when the configuration changes, + * this causes a re-read and reset. This should only happen for relevant changes */ + nm_ip_config_replace(old_config, new_config, &has_changes); + if (has_changes) { + _LOGD(LOGD_IPX(IS_IPv4), + "ip%c-config: update IP Config instance (%s)", + nm_utils_addr_family_to_char(addr_family), + nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config))); + } + } else if (new_config /*&& !old_config*/) { + has_changes = TRUE; + priv->ip_config_x[IS_IPv4] = g_object_ref(new_config); + if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(new_config))) + nm_dbus_object_export(NM_DBUS_OBJECT(new_config)); + + _LOGD(LOGD_IPX(IS_IPv4), + "ip%c-config: set IP Config instance (%s)", + nm_utils_addr_family_to_char(addr_family), + nm_dbus_object_get_path(NM_DBUS_OBJECT(new_config))); + } else if (old_config /*&& !new_config*/) { + has_changes = TRUE; + priv->ip_config_x[IS_IPv4] = NULL; + _LOGD(LOGD_IPX(IS_IPv4), + "ip%c-config: clear IP Config instance (%s)", + nm_utils_addr_family_to_char(addr_family), + nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config))); + if (IS_IPv4) { + /* Device config is invalid if combined config is invalid */ + applied_config_clear(&priv->dev_ip_config_4); + } else + priv->needs_ip6_subnet = FALSE; + } + + if (has_changes) { + if (old_config != priv->ip_config_x[IS_IPv4]) + _notify(self, IS_IPv4 ? PROP_IP4_CONFIG : PROP_IP6_CONFIG); + + g_signal_emit(self, + signals[IS_IPv4 ? IP4_CONFIG_CHANGED : IP6_CONFIG_CHANGED], + 0, + priv->ip_config_x[IS_IPv4], + old_config); + + if (old_config != priv->ip_config_x[IS_IPv4]) + nm_dbus_object_clear_and_unexport(&old_config); + + if (nm_device_sys_iface_state_is_external(self) + && (settings_connection = nm_device_get_settings_connection(self)) + && NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL) + && nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(priv->act_request.obj)) + == NM_ACTIVATION_TYPE_EXTERNAL) { + gs_unref_object NMConnection *new_connection = NULL; + + new_connection = nm_simple_connection_new_clone( + nm_settings_connection_get_connection(settings_connection)); + + nm_connection_add_setting( + new_connection, + IS_IPv4 ? nm_ip4_config_create_setting(priv->ip_config_4) + : nm_ip6_config_create_setting(priv->ip_config_6, + _get_maybe_ipv6_disabled(self))); + + nm_settings_connection_update(settings_connection, + new_connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, + "update-external", + NULL); + } + + nm_device_queue_recheck_assume(self); + + if (!IS_IPv4) { + if (priv->ndisc) + ndisc_set_router_config(priv->ndisc, self); + } + } + + nm_assert(!old_config || old_config == priv->ip_config_x[IS_IPv4]); + + return success; +} + +static gboolean +_replace_vpn_config_in_list(GSList **plist, GObject *old, GObject *new) +{ + GSList *old_link; + + /* Below, assert that @new is not yet tracked, but still behave + * correctly in any case. Don't complain for missing @old since + * it could have been removed when the parent device became + * unmanaged. */ + + if (old && (old_link = g_slist_find(*plist, old))) { + if (old != new) { + if (new) + old_link->data = g_object_ref(new); + else + *plist = g_slist_delete_link(*plist, old_link); + g_object_unref(old); + } + return TRUE; + } + + if (new) { + if (!g_slist_find(*plist, new)) + *plist = g_slist_append(*plist, g_object_ref(new)); + else + g_return_val_if_reached(TRUE); + return TRUE; + } + + return FALSE; +} + +void +nm_device_replace_vpn4_config(NMDevice *self, NMIP4Config *old, NMIP4Config *config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(!old || NM_IS_IP4_CONFIG(old)); + nm_assert(!config || NM_IS_IP4_CONFIG(config)); + nm_assert(!old || nm_ip4_config_get_ifindex(old) == nm_device_get_ip_ifindex(self)); + nm_assert(!config || nm_ip4_config_get_ifindex(config) == nm_device_get_ip_ifindex(self)); + + if (!_replace_vpn_config_in_list(&priv->vpn_configs_4, (GObject *) old, (GObject *) config)) + return; + + /* NULL to use existing configs */ + if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) + _LOGW(LOGD_IP4, "failed to set VPN routes for device"); +} + +void +nm_device_set_dev2_ip_config(NMDevice *self, int addr_family, NMIPConfig *config) +{ + NMDevicePrivate *priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + g_return_if_fail(!config || nm_ip_config_get_addr_family(config) == addr_family); + + priv = NM_DEVICE_GET_PRIVATE(self); + + applied_config_init(&priv->dev2_ip_config_x[IS_IPv4], config); + if (!ip_config_merge_and_apply(self, addr_family, TRUE)) { + _LOGW(LOGD_IP, + "failed to set extra device IPv%c configuration", + nm_utils_addr_family_to_char(addr_family)); + } +} + +void +nm_device_replace_vpn6_config(NMDevice *self, NMIP6Config *old, NMIP6Config *config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(!old || NM_IS_IP6_CONFIG(old)); + nm_assert(!config || NM_IS_IP6_CONFIG(config)); + nm_assert(!old || nm_ip6_config_get_ifindex(old) == nm_device_get_ip_ifindex(self)); + nm_assert(!config || nm_ip6_config_get_ifindex(config) == nm_device_get_ip_ifindex(self)); + + if (!_replace_vpn_config_in_list(&priv->vpn_configs_6, (GObject *) old, (GObject *) config)) + return; + + /* NULL to use existing configs */ + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "failed to set VPN routes for device"); +} + +NMIP6Config * +nm_device_get_ip6_config(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->ip_config_6; +} + +/*****************************************************************************/ + +static gboolean +dispatcher_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->dispatcher.call_id) + return FALSE; + + nm_dispatcher_call_cancel(g_steal_pointer(&priv->dispatcher.call_id)); + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + return TRUE; +} + +static void +dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_if_fail(call_id == priv->dispatcher.call_id); + + priv->dispatcher.call_id = NULL; + nm_device_queue_state(self, priv->dispatcher.post_state, priv->dispatcher.post_state_reason); + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; +} + +/*****************************************************************************/ + +static void +ip_check_pre_up(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (dispatcher_cleanup(self)) + nm_assert_not_reached(); + + priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + if (!nm_dispatcher_call_device(NM_DISPATCHER_ACTION_PRE_UP, + self, + NULL, + dispatcher_complete_proceed_state, + self, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + dispatcher_complete_proceed_state(0, self); + } +} + +static void +ip_check_gw_ping_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_clear_g_source(&priv->gw_ping.watch); + nm_clear_g_source(&priv->gw_ping.timeout); + + if (priv->gw_ping.pid) { + nm_utils_kill_child_async(priv->gw_ping.pid, + SIGTERM, + priv->gw_ping.log_domain, + "ping", + 1000, + NULL, + NULL); + priv->gw_ping.pid = 0; + } + + nm_clear_g_free(&priv->gw_ping.binary); + nm_clear_g_free(&priv->gw_ping.address); +} + +static gboolean +spawn_ping(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gs_free char * str_timeout = NULL; + gs_free char * tmp_str = NULL; + const char * args[] = {priv->gw_ping.binary, + "-I", + nm_device_get_ip_iface(self), + "-c", + "1", + "-w", + NULL, + priv->gw_ping.address, + NULL}; + gs_free_error GError *error = NULL; + gboolean ret; + + args[6] = str_timeout = g_strdup_printf("%u", priv->gw_ping.deadline); + tmp_str = g_strjoinv(" ", (char **) args); + _LOGD(priv->gw_ping.log_domain, "ping: running '%s'", tmp_str); + + ret = g_spawn_async("/", + (char **) args, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &priv->gw_ping.pid, + &error); + + if (!ret) { + _LOGW(priv->gw_ping.log_domain, + "ping: could not spawn %s: %s", + priv->gw_ping.binary, + error->message); + } + + return ret; +} + +static gboolean +respawn_ping_cb(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->gw_ping.watch = 0; + + if (spawn_ping(self)) { + priv->gw_ping.watch = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self); + } else { + ip_check_gw_ping_cleanup(self); + ip_check_pre_up(self); + } + + return FALSE; +} + +static void +ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMLogDomain log_domain = priv->gw_ping.log_domain; + gboolean success = FALSE; + + if (!priv->gw_ping.watch) + return; + priv->gw_ping.watch = 0; + priv->gw_ping.pid = 0; + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) { + _LOGD(log_domain, "ping: gateway ping succeeded"); + success = TRUE; + } else { + _LOGW(log_domain, "ping: gateway ping failed with error code %d", WEXITSTATUS(status)); + } + } else + _LOGW(log_domain, "ping: stopped unexpectedly with status %d", status); + + if (success) { + /* We've got connectivity, proceed to pre_up */ + ip_check_gw_ping_cleanup(self); + ip_check_pre_up(self); + } else { + /* If ping exited with an error it may have returned early, + * wait 1 second and restart it */ + priv->gw_ping.watch = g_timeout_add_seconds(1, respawn_ping_cb, self); + } +} + +static gboolean +ip_check_ping_timeout_cb(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->gw_ping.timeout = 0; + + _LOGW(priv->gw_ping.log_domain, "ping: gateway ping timed out"); + + ip_check_gw_ping_cleanup(self); + ip_check_pre_up(self); + return FALSE; +} + +static gboolean +start_ping(NMDevice * self, + NMLogDomain log_domain, + const char *binary, + const char *address, + guint timeout) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + g_return_val_if_fail(priv->gw_ping.watch == 0, FALSE); + g_return_val_if_fail(priv->gw_ping.timeout == 0, FALSE); + + priv->gw_ping.log_domain = log_domain; + priv->gw_ping.address = g_strdup(address); + priv->gw_ping.binary = g_strdup(binary); + priv->gw_ping.deadline = timeout + 10; /* the proper termination is enforced by a timer */ + + if (spawn_ping(self)) { + priv->gw_ping.watch = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self); + priv->gw_ping.timeout = g_timeout_add_seconds(timeout, ip_check_ping_timeout_cb, self); + return TRUE; + } + + ip_check_gw_ping_cleanup(self); + return FALSE; +} + +static void +nm_device_start_ip_check(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMConnection * connection; + NMSettingConnection *s_con; + guint timeout = 0; + const char * ping_binary = NULL; + char buf[NM_UTILS_INET_ADDRSTRLEN]; + NMLogDomain log_domain = LOGD_IP4; + + /* Shouldn't be any active ping here, since IP_CHECK happens after the + * first IP method completes. Any subsequently completing IP method doesn't + * get checked. + */ + g_return_if_fail(!priv->gw_ping.watch); + g_return_if_fail(!priv->gw_ping.timeout); + g_return_if_fail(!priv->gw_ping.pid); + g_return_if_fail(priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE + || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE); + + connection = nm_device_get_applied_connection(self); + g_assert(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + timeout = nm_setting_connection_get_gateway_ping_timeout(s_con); + + buf[0] = '\0'; + if (timeout) { + const NMPObject *gw; + + if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) { + gw = nm_ip4_config_best_default_route_get(priv->ip_config_4); + if (gw) { + _nm_utils_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway, buf); + ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL); + log_domain = LOGD_IP4; + } + } else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) { + gw = nm_ip6_config_best_default_route_get(priv->ip_config_6); + if (gw) { + _nm_utils_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway, buf); + ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL); + log_domain = LOGD_IP6; + } + } + } + + if (buf[0]) + start_ping(self, log_domain, ping_binary, buf, timeout); + + /* If no ping was started, just advance to pre_up */ + if (!priv->gw_ping.pid) + ip_check_pre_up(self); +} + +/*****************************************************************************/ + +static gboolean +carrier_wait_timeout(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->carrier_wait_id = 0; + nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); + if (!priv->carrier) + _carrier_wait_check_queued_act_request(self); + return G_SOURCE_REMOVE; +} + +static gboolean +nm_device_is_up(NMDevice *self) +{ + int ifindex; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + ifindex = nm_device_get_ip_ifindex(self); + return ifindex > 0 ? nm_platform_link_is_up(nm_device_get_platform(self), ifindex) : TRUE; +} + +static gint64 +_get_carrier_wait_ms(NMDevice *self) +{ + gs_free char *value = NULL; + + value = nm_config_data_get_device_config(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, + self, + NULL); + return _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, CARRIER_WAIT_TIME_MS); +} + +gboolean +nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + gboolean device_is_up = FALSE; + NMDeviceCapabilities capabilities; + int ifindex; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + NM_SET_OUT(no_firmware, FALSE); + + if (!nm_device_get_enabled(self)) { + _LOGD(LOGD_PLATFORM, "bringing up device ignored due to disabled"); + return FALSE; + } + + ifindex = nm_device_get_ip_ifindex(self); + _LOGD(LOGD_PLATFORM, "bringing up device %d", ifindex); + if (ifindex <= 0) { + /* assume success. */ + } else { + if (!nm_platform_link_set_up(nm_device_get_platform(self), ifindex, no_firmware)) + return FALSE; + } + + /* Store carrier immediately. */ + nm_device_set_carrier_from_platform(self); + + device_is_up = nm_device_is_up(self); + if (block && !device_is_up) { + gint64 wait_until = nm_utils_get_monotonic_timestamp_usec() + 10000 /* microseconds */; + + do { + g_usleep(200); + if (!nm_platform_link_refresh(nm_device_get_platform(self), ifindex)) + return FALSE; + device_is_up = nm_device_is_up(self); + } while (!device_is_up && nm_utils_get_monotonic_timestamp_usec() < wait_until); + } + + if (!device_is_up) { + if (block) + _LOGW(LOGD_PLATFORM, "device not up after timeout!"); + else + _LOGD(LOGD_PLATFORM, "device not up immediately"); + return FALSE; + } + + /* some ethernet devices fail to report capabilities unless the device + * is up. Re-read the capabilities. */ + capabilities = 0; + if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities) + capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self); + _add_capabilities(self, capabilities); + + /* Devices that support carrier detect must be IFF_UP to report carrier + * changes; so after setting the device IFF_UP we must suppress startup + * complete (via a pending action) until either the carrier turns on, or + * a timeout is reached. + */ + if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)) { + gint64 now_ms, until_ms; + + /* we start a grace period of 5 seconds during which we will schedule + * a pending action whenever we have no carrier. + * + * If during that time carrier goes away, we declare the interface + * as not ready. */ + nm_clear_g_source(&priv->carrier_wait_id); + if (!priv->carrier) + nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms); + priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self); + } + + /* Can only get HW address of some devices when they are up */ + nm_device_update_hw_address(self); + + /* when the link comes up, we must restore IP configuration if necessary. */ + if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) { + if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) + _LOGW(LOGD_IP4, "failed applying IP4 config after bringing link up"); + } + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) { + if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) + _LOGW(LOGD_IP6, "failed applying IP6 config after bringing link up"); + } + + return TRUE; +} + +void +nm_device_take_down(NMDevice *self, gboolean block) +{ + int ifindex; + gboolean device_is_up; + + g_return_if_fail(NM_IS_DEVICE(self)); + + ifindex = nm_device_get_ip_ifindex(self); + _LOGD(LOGD_PLATFORM, "taking down device %d", ifindex); + if (ifindex <= 0) { + /* devices without ifindex are always up. */ + return; + } + + if (!nm_platform_link_set_down(nm_device_get_platform(self), ifindex)) + return; + + device_is_up = nm_device_is_up(self); + if (block && device_is_up) { + gint64 wait_until = nm_utils_get_monotonic_timestamp_usec() + 10000 /* microseconds */; + + do { + g_usleep(200); + if (!nm_platform_link_refresh(nm_device_get_platform(self), ifindex)) + return; + device_is_up = nm_device_is_up(self); + } while (device_is_up && nm_utils_get_monotonic_timestamp_usec() < wait_until); + } + + if (device_is_up) { + if (block) + _LOGW(LOGD_PLATFORM, "device not down after timeout!"); + else + _LOGD(LOGD_PLATFORM, "device not down immediately"); + } +} + +void +nm_device_set_firmware_missing(NMDevice *self, gboolean new_missing) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->firmware_missing != new_missing) { + priv->firmware_missing = new_missing; + _notify(self, PROP_FIRMWARE_MISSING); + } +} + +gboolean +nm_device_get_firmware_missing(NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE(self)->firmware_missing; +} + +static void +intersect_ext_config(NMDevice * self, + AppliedConfig *config, + gboolean intersect_addresses, + gboolean intersect_routes) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMIPConfig * ext; + guint32 penalty; + int family; + + if (!config->orig) + return; + + family = nm_ip_config_get_addr_family(config->orig); + penalty = default_route_metric_penalty_get(self, family); + ext = family == AF_INET ? (NMIPConfig *) priv->ext_ip_config_4 + : (NMIPConfig *) priv->ext_ip_config_6; + + if (config->current) { + nm_ip_config_intersect(config->current, + ext, + intersect_addresses, + intersect_routes, + penalty); + } else { + config->current = nm_ip_config_intersect_alloc(config->orig, + ext, + intersect_addresses, + intersect_routes, + penalty); + } +} + +static gboolean +update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + int ifindex; + GSList * iter; + gboolean is_up; + + nm_assert_addr_family(addr_family); + + ifindex = nm_device_get_ip_ifindex(self); + if (!ifindex) + return FALSE; + + is_up = nm_platform_link_is_up(nm_device_get_platform(self), ifindex); + + if (NM_IS_IPv4(addr_family)) { + g_clear_object(&priv->ext_ip_config_4); + priv->ext_ip_config_4 = nm_ip4_config_capture(nm_device_get_multi_index(self), + nm_device_get_platform(self), + ifindex); + if (priv->ext_ip_config_4) { + if (intersect_configs) { + /* This function was called upon external changes. Remove the configuration + * (addresses,routes) that is no longer present externally from the internal + * config. This way, we don't re-add addresses that were manually removed + * by the user. */ + if (priv->con_ip_config_4) { + nm_ip4_config_intersect(priv->con_ip_config_4, + priv->ext_ip_config_4, + TRUE, + is_up, + default_route_metric_penalty_get(self, AF_INET)); + } + + intersect_ext_config(self, &priv->dev_ip_config_4, TRUE, is_up); + intersect_ext_config(self, &priv->dev2_ip_config_4, TRUE, is_up); + + for (iter = priv->vpn_configs_4; iter; iter = iter->next) + nm_ip4_config_intersect(iter->data, priv->ext_ip_config_4, TRUE, is_up, 0); + } + + /* Remove parts from ext_ip_config_4 to only contain the information that + * was configured externally -- we already have the same configuration from + * internal origins. */ + if (priv->con_ip_config_4) { + nm_ip4_config_subtract(priv->ext_ip_config_4, + priv->con_ip_config_4, + default_route_metric_penalty_get(self, AF_INET)); + } + if (applied_config_get_current(&priv->dev_ip_config_4)) { + nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4, + applied_config_get_current(&priv->dev_ip_config_4), + default_route_metric_penalty_get(self, AF_INET)); + } + if (applied_config_get_current(&priv->dev2_ip_config_4)) { + nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4, + applied_config_get_current(&priv->dev2_ip_config_4), + default_route_metric_penalty_get(self, AF_INET)); + } + for (iter = priv->vpn_configs_4; iter; iter = iter->next) + nm_ip4_config_subtract(priv->ext_ip_config_4, iter->data, 0); + } + + } else { + nm_assert(!NM_IS_IPv4(addr_family)); + + g_clear_object(&priv->ext_ip_config_6); + g_clear_object(&priv->ext_ip6_config_captured); + priv->ext_ip6_config_captured = + nm_ip6_config_capture(nm_device_get_multi_index(self), + nm_device_get_platform(self), + ifindex, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + if (priv->ext_ip6_config_captured) { + priv->ext_ip_config_6 = nm_ip6_config_new_cloned(priv->ext_ip6_config_captured); + + if (intersect_configs) { + /* This function was called upon external changes. Remove the configuration + * (addresses,routes) that is no longer present externally from the internal + * config. This way, we don't re-add addresses that were manually removed + * by the user. */ + if (priv->con_ip_config_6) { + nm_ip6_config_intersect(priv->con_ip_config_6, + priv->ext_ip_config_6, + is_up, + is_up, + default_route_metric_penalty_get(self, AF_INET6)); + } + + intersect_ext_config(self, &priv->ac_ip6_config, is_up, is_up); + intersect_ext_config(self, &priv->dhcp6.ip6_config, is_up, is_up); + intersect_ext_config(self, &priv->dev2_ip_config_6, is_up, is_up); + + for (iter = priv->vpn_configs_6; iter; iter = iter->next) + nm_ip6_config_intersect(iter->data, priv->ext_ip_config_6, is_up, is_up, 0); + + if (is_up && priv->ipv6ll_has + && !nm_ip6_config_lookup_address(priv->ext_ip_config_6, &priv->ipv6ll_addr)) + priv->ipv6ll_has = FALSE; + } + + /* Remove parts from ext_ip_config_6 to only contain the information that + * was configured externally -- we already have the same configuration from + * internal origins. */ + if (priv->con_ip_config_6) { + nm_ip6_config_subtract(priv->ext_ip_config_6, + priv->con_ip_config_6, + default_route_metric_penalty_get(self, AF_INET6)); + } + if (applied_config_get_current(&priv->ac_ip6_config)) { + nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6, + applied_config_get_current(&priv->ac_ip6_config), + default_route_metric_penalty_get(self, AF_INET6)); + } + if (applied_config_get_current(&priv->dhcp6.ip6_config)) { + nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6, + applied_config_get_current(&priv->dhcp6.ip6_config), + default_route_metric_penalty_get(self, AF_INET6)); + } + if (applied_config_get_current(&priv->dev2_ip_config_6)) { + nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6, + applied_config_get_current(&priv->dev2_ip_config_6), + default_route_metric_penalty_get(self, AF_INET6)); + } + for (iter = priv->vpn_configs_6; iter; iter = iter->next) + nm_ip6_config_subtract(priv->ext_ip_config_6, iter->data, 0); + } + } + + return TRUE; +} + +static void +update_ip_config(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert_addr_family(addr_family); + + if (NM_IS_IPv4(addr_family)) + priv->update_ip_config_completed_v4 = TRUE; + else + priv->update_ip_config_completed_v6 = TRUE; + + if (update_ext_ip_config(self, addr_family, TRUE)) { + if (NM_IS_IPv4(addr_family)) { + if (priv->ext_ip_config_4) + ip_config_merge_and_apply(self, AF_INET, FALSE); + } else { + if (priv->ext_ip6_config_captured) + ip_config_merge_and_apply(self, AF_INET6, FALSE); + } + } +} + +void +nm_device_capture_initial_config(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->update_ip_config_completed_v4) + update_ip_config(self, AF_INET); + if (!priv->update_ip_config_completed_v6) + update_ip_config(self, AF_INET6); +} + +static gboolean +queued_ip_config_change(NMDevice *self, int addr_family) +{ + NMDevicePrivate *priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + /* Wait for any queued state changes */ + if (priv->queued_state.id) + return G_SOURCE_CONTINUE; + + /* If a commit is scheduled, this function would potentially interfere with + * it changing IP configurations before they are applied. Postpone the + * update in such case. + */ + if (priv->activation_source_id_x[IS_IPv4] != 0 + && priv->activation_source_func_x[IS_IPv4] + == activate_stage5_ip_config_result_x_fcn(addr_family)) + return G_SOURCE_CONTINUE; + + priv->queued_ip_config_id_x[IS_IPv4] = 0; + + update_ip_config(self, addr_family); + + if (!IS_IPv4) { + /* Check whether we need to complete waiting for link-local. + * We are also called from an idle handler, so no problem doing state transitions + * now. */ + linklocal6_check_complete(self); + } + + if (!IS_IPv4) { + NMPlatform * platform; + GSList * dad6_failed_addrs, *iter; + const NMPlatformLink *pllink; + + dad6_failed_addrs = g_steal_pointer(&priv->dad6_failed_addrs); + + if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING + && priv->ifindex > 0 && !nm_device_sys_iface_state_is_external(self) + && (platform = nm_device_get_platform(self)) + && (pllink = nm_platform_link_get(platform, priv->ifindex)) + && (pllink->n_ifi_flags & IFF_UP)) { + gboolean need_ipv6ll = FALSE; + NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE; + + /* Handle DAD failures */ + for (iter = dad6_failed_addrs; iter; iter = iter->next) { + const NMPObject * obj = iter->data; + const NMPlatformIP6Address *addr; + + if (!nm_ndisc_dad_addr_is_fail_candidate(platform, obj)) + continue; + + addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj); + + _LOGI(LOGD_IP6, + "ipv6: duplicate address check failed for the %s address", + nm_platform_ip6_address_to_string(addr, NULL, 0)); + + if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) + need_ipv6ll = TRUE; + else if (priv->ndisc) + ndisc_config_changed |= nm_ndisc_dad_failed(priv->ndisc, &addr->address, FALSE); + } + + if (ndisc_config_changed != NM_NDISC_CONFIG_NONE) + nm_ndisc_emit_config_change(priv->ndisc, ndisc_config_changed); + + /* If no IPv6 link-local address exists but other addresses do then we + * must add the LL address to remain conformant with RFC 3513 chapter 2.1 + * ("Addressing Model"): "All interfaces are required to have at least + * one link-local unicast address". + */ + if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6)) + need_ipv6ll = TRUE; + if (need_ipv6ll) + check_and_add_ipv6ll_addr(self); + } + + g_slist_free_full(dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); + } + + if (!IS_IPv4) { + /* Check if DAD is still pending */ + if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && priv->dad6_ip6_config + && priv->ext_ip6_config_captured + && !nm_ip6_config_has_any_dad_pending(priv->ext_ip6_config_captured, + priv->dad6_ip6_config)) { + _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated"); + g_clear_object(&priv->dad6_ip6_config); + _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE); + check_ip_state(self, FALSE, TRUE); + if (priv->rt6_temporary_not_available) + nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL); + } + } + + set_unmanaged_external_down(self, TRUE); + + return G_SOURCE_REMOVE; +} + +static gboolean +queued_ip4_config_change(gpointer user_data) +{ + return queued_ip_config_change(user_data, AF_INET); +} + +static gboolean +queued_ip6_config_change(gpointer user_data) +{ + return queued_ip_config_change(user_data, AF_INET6); +} + +static void +device_ipx_changed(NMPlatform * platform, + int obj_type_i, + int ifindex, + gconstpointer platform_object, + int change_type_i, + NMDevice * self) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMDevicePrivate * priv; + const NMPlatformIP6Address * addr; + + if (nm_device_get_ip_ifindex(self) != ifindex) + return; + + if (!nm_device_is_real(self)) + return; + + if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { + /* ignore all platform signals until the link is initialized in platform. */ + return; + } + + priv = NM_DEVICE_GET_PRIVATE(self); + + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + if (!priv->queued_ip_config_id_4) { + priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self); + _LOGD(LOGD_DEVICE, "queued IP4 config change"); + } + break; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + addr = platform_object; + + if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING + && nm_ndisc_dad_addr_is_fail_candidate_event(change_type, addr)) { + priv->dad6_failed_addrs = + g_slist_prepend(priv->dad6_failed_addrs, + (gpointer) nmp_object_ref(NMP_OBJECT_UP_CAST(addr))); + } + + /* fall-through */ + case NMP_OBJECT_TYPE_IP6_ROUTE: + if (!priv->queued_ip_config_id_6) { + priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self); + _LOGD(LOGD_DEVICE, "queued IP6 config change"); + } + break; + default: + g_return_if_reached(); + } +} + +/*****************************************************************************/ + +NM_UTILS_FLAGS2STR_DEFINE(nm_unmanaged_flags2str, + NMUnmanagedFlags, + NM_UTILS_FLAGS2STR(NM_UNMANAGED_SLEEPING, "sleeping"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_QUITTING, "quitting"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_PARENT, "parent"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_BY_TYPE, "by-type"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_PLATFORM_INIT, "platform-init"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_EXPLICIT, "user-explicit"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_BY_DEFAULT, "by-default"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_SETTINGS, "user-settings"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_CONF, "user-conf"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_UDEV, "user-udev"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_EXTERNAL_DOWN, "external-down"), + NM_UTILS_FLAGS2STR(NM_UNMANAGED_IS_SLAVE, "is-slave"), ); + +static const char * +_unmanaged_flags2str(NMUnmanagedFlags flags, NMUnmanagedFlags mask, char *buf, gsize len) +{ + char buf2[512]; + char *b; + char *tmp, *tmp2; + gsize l; + + nm_utils_to_string_buffer_init(&buf, &len); + if (!len) + return buf; + + b = buf; + + mask |= flags; + + nm_unmanaged_flags2str(flags, b, len); + l = strlen(b); + b += l; + len -= l; + + nm_unmanaged_flags2str(mask & ~flags, buf2, sizeof(buf2)); + if (buf2[0]) { + gboolean add_separator = l > 0; + + tmp = buf2; + while (TRUE) { + if (add_separator) + nm_utils_strbuf_append_c(&b, &len, ','); + add_separator = TRUE; + + tmp2 = strchr(tmp, ','); + if (tmp2) + tmp2[0] = '\0'; + + nm_utils_strbuf_append_c(&b, &len, '!'); + nm_utils_strbuf_append_str(&b, &len, tmp); + if (!tmp2) + break; + + tmp = &tmp2[1]; + } + } + + return buf; +} + +static gboolean +_get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean for_user_request) +{ + /* Evaluate the managed state based on the unmanaged flags. + * + * Some flags are authoritative, meaning they always cause + * the device to be unmanaged (e.g. @NM_UNMANAGED_PLATFORM_INIT). + * + * OTOH, some flags can be overwritten. For example NM_UNMANAGED_USER_UDEV + * is ignored once NM_UNMANAGED_USER_EXPLICIT is set. The idea is that + * the flag from the configuration has no effect once the user explicitly + * touches the unmanaged flags. */ + + if (for_user_request) { + /* @for_user_request can make the result only ~more~ managed. + * If the flags already indicate a managed state for a non-user-request, + * then it is also managed for an explicit user-request. + * + * Effectively, this check is redundant, as the code below already + * already ensures that. Still, express this invariant explicitly here. */ + if (_get_managed_by_flags(flags, mask, FALSE)) + return TRUE; + + /* A for-user-request, is effectively the same as pretending + * that user-explicit flag is cleared. */ + mask |= NM_UNMANAGED_USER_EXPLICIT; + flags &= ~NM_UNMANAGED_USER_EXPLICIT; + } + + if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_SETTINGS) + && !NM_FLAGS_ANY(flags, NM_UNMANAGED_USER_SETTINGS)) { + /* NM_UNMANAGED_USER_SETTINGS can only explicitly unmanage a device. It cannot + * *manage* it. Having NM_UNMANAGED_USER_SETTINGS explicitly not set, is the + * same as having it not set at all. */ + mask &= ~NM_UNMANAGED_USER_SETTINGS; + } + + if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_UDEV)) { + /* configuration from udev or nm-config overwrites the by-default flag + * which is based on the device type. + * configuration from udev overwrites external-down */ + flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_EXTERNAL_DOWN); + } + + if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_CONF)) { + /* configuration from NetworkManager.conf overwrites the by-default flag + * which is based on the device type. + * It also overwrites the udev configuration and external-down */ + flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_EXTERNAL_DOWN); + } + + if (NM_FLAGS_HAS(mask, NM_UNMANAGED_IS_SLAVE) && !NM_FLAGS_HAS(flags, NM_UNMANAGED_IS_SLAVE)) { + /* for an enslaved device, by-default doesn't matter */ + flags &= ~NM_UNMANAGED_BY_DEFAULT; + } + + if (NM_FLAGS_HAS(mask, NM_UNMANAGED_USER_EXPLICIT)) { + /* if the device is managed by user-decision, certain other flags + * are ignored. */ + flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_USER_CONF + | NM_UNMANAGED_EXTERNAL_DOWN); + } + + return flags == NM_UNMANAGED_NONE; +} + +/** + * nm_device_get_managed: + * @self: the #NMDevice + * @for_user_request: whether to check the flags for an explicit user-request + * Setting this to %TRUE has the same effect as if %NM_UNMANAGED_USER_EXPLICIT + * unmanaged flag would be unset (meaning: explicitly not-unmanaged). + * If this parameter is %TRUE, the device can only appear more managed. + * + * Whether the device is unmanaged according to the unmanaged flags. + * + * Returns: %TRUE if the device is unmanaged because of the flags. + */ +gboolean +nm_device_get_managed(NMDevice *self, gboolean for_user_request) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + if (!nm_device_is_real(self)) { + /* a unrealized device is always considered unmanaged. */ + return FALSE; + } + + priv = NM_DEVICE_GET_PRIVATE(self); + + return _get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, for_user_request); +} + +/** + * nm_device_get_unmanaged_mask: + * @self: the #NMDevice + * @flag: the unmanaged flags to check. + * + * Return the unmanaged flags mask set on this device. + * + * Returns: the flags of the device ( & @flag) + */ +NMUnmanagedFlags +nm_device_get_unmanaged_mask(NMDevice *self, NMUnmanagedFlags flag) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_UNMANAGED_NONE); + g_return_val_if_fail(flag != NM_UNMANAGED_NONE, NM_UNMANAGED_NONE); + + return NM_DEVICE_GET_PRIVATE(self)->unmanaged_mask & flag; +} + +/** + * nm_device_get_unmanaged_flags: + * @self: the #NMDevice + * @flag: the unmanaged flags to check. + * + * Return the unmanaged flags of the device. + * + * Returns: the flags of the device ( & @flag) + */ +NMUnmanagedFlags +nm_device_get_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flag) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_UNMANAGED_NONE); + g_return_val_if_fail(flag != NM_UNMANAGED_NONE, NM_UNMANAGED_NONE); + + return NM_DEVICE_GET_PRIVATE(self)->unmanaged_flags & flag; +} + +/** + * _set_unmanaged_flags: + * @self: the #NMDevice instance + * @flags: which #NMUnmanagedFlags to set. + * @set_op: whether to set/clear/forget the flags. You can also pass + * boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED + * and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively. + * @allow_state_transition: if %FALSE, setting flags never triggers a device + * state change. If %TRUE, the device can change state, if it is real and + * switches from managed to unmanaged (or vice versa). + * @now: whether the state change should be immediate or delayed + * @reason: the device state reason passed to nm_device_state_changed() if + * the device becomes managed/unmanaged. This is only relevant if the + * device switches state and if @allow_state_transition is %TRUE. + * + * Set the unmanaged flags of the device. + **/ +static void +_set_unmanaged_flags(NMDevice * self, + NMUnmanagedFlags flags, + NMUnmanFlagOp set_op, + gboolean allow_state_transition, + gboolean now, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + gboolean was_managed, transition_state; + NMUnmanagedFlags old_flags, old_mask; + NMDeviceState new_state; + const char * operation = NULL; + char str1[512]; + char str2[512]; + gboolean do_notify_has_pending_actions = FALSE; + gboolean had_pending_actions = FALSE; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(flags); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->real) + allow_state_transition = FALSE; + was_managed = allow_state_transition && nm_device_get_managed(self, FALSE); + + if (NM_FLAGS_HAS(priv->unmanaged_flags, NM_UNMANAGED_PLATFORM_INIT) + && NM_FLAGS_HAS(flags, NM_UNMANAGED_PLATFORM_INIT) + && NM_IN_SET(set_op, NM_UNMAN_FLAG_OP_SET_MANAGED)) { + /* we are clearing the platform-init flags. This triggers additional actions. */ + if (!NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_SETTINGS)) { + gboolean unmanaged; + + unmanaged = nm_device_spec_match_list( + self, + nm_settings_get_unmanaged_specs(NM_DEVICE_GET_PRIVATE(self)->settings)); + nm_device_set_unmanaged_flags(self, NM_UNMANAGED_USER_SETTINGS, !!unmanaged); + } + + /* trigger an initial update of IP configuration. */ + nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_4)); + nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_6)); + priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self); + priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self); + + if (!priv->pending_actions) { + do_notify_has_pending_actions = TRUE; + had_pending_actions = nm_device_has_pending_action(self); + } + } + + old_flags = priv->unmanaged_flags; + old_mask = priv->unmanaged_mask; + + switch (set_op) { + case NM_UNMAN_FLAG_OP_FORGET: + priv->unmanaged_mask &= ~flags; + priv->unmanaged_flags &= ~flags; + operation = "forget"; + break; + case NM_UNMAN_FLAG_OP_SET_UNMANAGED: + priv->unmanaged_mask |= flags; + priv->unmanaged_flags |= flags; + operation = "set-unmanaged"; + break; + case NM_UNMAN_FLAG_OP_SET_MANAGED: + priv->unmanaged_mask |= flags; + priv->unmanaged_flags &= ~flags; + operation = "set-managed"; + break; + default: + g_return_if_reached(); + } + + if (old_flags == priv->unmanaged_flags && old_mask == priv->unmanaged_mask) + return; + + transition_state = + allow_state_transition && was_managed != nm_device_get_managed(self, FALSE) + && (was_managed + || (!was_managed && nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED)); + + _LOGD(LOGD_DEVICE, + "unmanaged: flags set to [%s%s0x%0x/0x%x/%s%s], %s [%s=0x%0x]%s%s%s)", + _unmanaged_flags2str(priv->unmanaged_flags, priv->unmanaged_mask, str1, sizeof(str1)), + (priv->unmanaged_flags | priv->unmanaged_mask) ? "=" : "", + (guint) priv->unmanaged_flags, + (guint) priv->unmanaged_mask, + (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE) + ? "managed" + : (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, TRUE) + ? "manageable" + : "unmanaged")), + priv->real ? "" : "/unrealized", + operation, + nm_unmanaged_flags2str(flags, str2, sizeof(str2)), + flags, + NM_PRINT_FMT_QUOTED(allow_state_transition, + ", reason ", + reason_to_string_a(reason), + transition_state ? ", transition-state" : "", + "")); + + if (do_notify_has_pending_actions && had_pending_actions != nm_device_has_pending_action(self)) + _notify(self, PROP_HAS_PENDING_ACTION); + + if (transition_state) { + new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE; + if (now) + nm_device_state_changed(self, new_state, reason); + else + nm_device_queue_state(self, new_state, reason); + } +} + +/** + * @self: the #NMDevice instance + * @flags: which #NMUnmanagedFlags to set. + * @set_op: whether to set/clear/forget the flags. You can also pass + * boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED + * and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively. + * + * Set the unmanaged flags of the device (does not trigger a state change). + **/ +void +nm_device_set_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flags, NMUnmanFlagOp set_op) +{ + _set_unmanaged_flags(self, flags, set_op, FALSE, FALSE, NM_DEVICE_STATE_REASON_NONE); +} + +/** + * nm_device_set_unmanaged_by_flags: + * @self: the #NMDevice instance + * @flags: which #NMUnmanagedFlags to set. + * @set_op: whether to set/clear/forget the flags. You can also pass + * boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED + * and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively. + * @reason: the device state reason passed to nm_device_state_changed() if + * the device becomes managed/unmanaged. + * + * Set the unmanaged flags of the device and possibly trigger a state change. + **/ +void +nm_device_set_unmanaged_by_flags(NMDevice * self, + NMUnmanagedFlags flags, + NMUnmanFlagOp set_op, + NMDeviceStateReason reason) +{ + _set_unmanaged_flags(self, flags, set_op, TRUE, TRUE, reason); +} + +void +nm_device_set_unmanaged_by_flags_queue(NMDevice * self, + NMUnmanagedFlags flags, + NMUnmanFlagOp set_op, + NMDeviceStateReason reason) +{ + _set_unmanaged_flags(self, flags, set_op, TRUE, FALSE, reason); +} + +/** + * nm_device_check_unrealized_device_managed: + * + * Checks if a unrealized device is managed from user settings + * or user configuration. + */ +gboolean +nm_device_check_unrealized_device_managed(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(!nm_device_is_real(self)); + + if (!nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + self, + TRUE, + TRUE)) + return FALSE; + + if (nm_device_spec_match_list(self, nm_settings_get_unmanaged_specs(priv->settings))) + return FALSE; + + return TRUE; +} + +void +nm_device_set_unmanaged_by_user_settings(NMDevice *self) +{ + gboolean unmanaged; + + g_return_if_fail(NM_IS_DEVICE(self)); + + if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { + /* the device is already unmanaged due to platform-init. + * + * We want to delay evaluating the device spec, because it will freeze + * the permanent MAC address. That should not be done, before the platform + * link is fully initialized (via UDEV). + * + * Note that when clearing NM_UNMANAGED_PLATFORM_INIT, we will re-evaluate + * whether the device is unmanaged by user-settings. */ + return; + } + + unmanaged = nm_device_spec_match_list( + self, + nm_settings_get_unmanaged_specs(NM_DEVICE_GET_PRIVATE(self)->settings)); + + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_USER_SETTINGS, + !!unmanaged, + unmanaged ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED + : NM_DEVICE_STATE_REASON_NOW_MANAGED); +} + +void +nm_device_set_unmanaged_by_user_udev(NMDevice *self) +{ + int ifindex; + gboolean platform_unmanaged = FALSE; + + ifindex = self->_priv->ifindex; + + if (ifindex <= 0 + || !nm_platform_link_get_unmanaged(nm_device_get_platform(self), + ifindex, + &platform_unmanaged)) + return; + + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_USER_UDEV, + platform_unmanaged, + NM_DEVICE_STATE_REASON_USER_REQUESTED); +} + +void +nm_device_set_unmanaged_by_user_conf(NMDevice *self) +{ + gboolean value; + NMUnmanFlagOp set_op; + + value = nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + self, + -1, + TRUE); + switch (value) { + case TRUE: + set_op = NM_UNMAN_FLAG_OP_SET_MANAGED; + break; + case FALSE: + set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED; + break; + default: + set_op = NM_UNMAN_FLAG_OP_FORGET; + break; + } + + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_USER_CONF, + set_op, + NM_DEVICE_STATE_REASON_USER_REQUESTED); +} + +void +nm_device_set_unmanaged_by_quitting(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + gboolean need_deactivate = + nm_device_is_activating(self) || priv->state == NM_DEVICE_STATE_ACTIVATED; + + /* It's OK to block here because we're quitting */ + if (need_deactivate) + _set_state_full(self, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_NOW_UNMANAGED, + TRUE); + + nm_device_set_unmanaged_by_flags(self, + NM_UNMANAGED_QUITTING, + TRUE, + need_deactivate ? NM_DEVICE_STATE_REASON_REMOVED + : NM_DEVICE_STATE_REASON_NOW_UNMANAGED); +} + +/*****************************************************************************/ + +void +nm_device_set_dhcp_anycast_address(NMDevice *self, const char *addr) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(!addr || nm_utils_hwaddr_valid(addr, ETH_ALEN)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + g_free(priv->dhcp_anycast_address); + priv->dhcp_anycast_address = g_strdup(addr); +} + +void +nm_device_reapply_settings_immediately(NMDevice *self) +{ + NMConnection * applied_connection; + NMSettingsConnection *settings_connection; + NMDeviceState state; + NMSettingConnection * s_con_settings; + NMSettingConnection * s_con_applied; + const char * zone; + NMMetered metered; + guint64 version_id; + + g_return_if_fail(NM_IS_DEVICE(self)); + + state = nm_device_get_state(self); + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED) + return; + + applied_connection = nm_device_get_applied_connection(self); + settings_connection = nm_device_get_settings_connection(self); + + if (!nm_settings_connection_has_unmodified_applied_connection( + settings_connection, + applied_connection, + NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY)) + return; + + s_con_settings = nm_connection_get_setting_connection( + nm_settings_connection_get_connection(settings_connection)); + s_con_applied = nm_connection_get_setting_connection(applied_connection); + + if (!nm_streq0((zone = nm_setting_connection_get_zone(s_con_settings)), + nm_setting_connection_get_zone(s_con_applied))) { + version_id = nm_active_connection_version_id_bump( + (NMActiveConnection *) self->_priv->act_request.obj); + _LOGD(LOGD_DEVICE, + "reapply setting: zone = %s%s%s (version-id %llu)", + NM_PRINT_FMT_QUOTE_STRING(zone), + (unsigned long long) version_id); + + g_object_set(G_OBJECT(s_con_applied), NM_SETTING_CONNECTION_ZONE, zone, NULL); + + nm_device_update_firewall_zone(self); + } + + if ((metered = nm_setting_connection_get_metered(s_con_settings)) + != nm_setting_connection_get_metered(s_con_applied)) { + version_id = nm_active_connection_version_id_bump( + (NMActiveConnection *) self->_priv->act_request.obj); + _LOGD(LOGD_DEVICE, + "reapply setting: metered = %d (version-id %llu)", + (int) metered, + (unsigned long long) version_id); + + g_object_set(G_OBJECT(s_con_applied), NM_SETTING_CONNECTION_METERED, metered, NULL); + + nm_device_update_metered(self); + } +} + +void +nm_device_update_firewall_zone(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->fw_state >= FIREWALL_STATE_INITIALIZED + && !nm_device_sys_iface_state_is_external(self)) + fw_change_zone(self); +} + +void +nm_device_update_metered(NMDevice *self) +{ +#define NM_METERED_INVALID ((NMMetered) -1) + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingConnection *setting; + NMMetered conn_value, value = NM_METERED_INVALID; + NMConnection * connection = NULL; + NMDeviceState state; + + g_return_if_fail(NM_IS_DEVICE(self)); + + state = nm_device_get_state(self); + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED) + value = NM_METERED_UNKNOWN; + + if (value == NM_METERED_INVALID) { + connection = nm_device_get_applied_connection(self); + if (connection) { + setting = nm_connection_get_setting_connection(connection); + if (setting) { + conn_value = nm_setting_connection_get_metered(setting); + if (conn_value != NM_METERED_UNKNOWN) + value = conn_value; + } + } + } + + if (value == NM_METERED_INVALID && NM_DEVICE_GET_CLASS(self)->get_guessed_metered + && NM_DEVICE_GET_CLASS(self)->get_guessed_metered(self)) + value = NM_METERED_GUESS_YES; + + /* Try to guess a value using the metered flag in IP configuration */ + if (value == NM_METERED_INVALID) { + if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE + && nm_ip4_config_get_metered(priv->ip_config_4)) + value = NM_METERED_GUESS_YES; + } + + /* Otherwise, look at connection type. For Bluetooth, we look at the type of + * Bluetooth sharing: for PANU/DUN (where we are receiving internet from + * another device) we set GUESS_YES; for NAP (where we are sharing internet + * to another device) we set GUESS_NO. We ignore WiMAX here as it’s no + * longer supported by NetworkManager. */ + if (value == NM_METERED_INVALID + && nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) { + if (_nm_connection_get_setting_bluetooth_for_nap(connection)) { + /* NAP types are not metered, but other types are. */ + value = NM_METERED_GUESS_NO; + } else + value = NM_METERED_GUESS_YES; + } + + if (value == NM_METERED_INVALID) { + if (nm_connection_is_type(connection, NM_SETTING_GSM_SETTING_NAME) + || nm_connection_is_type(connection, NM_SETTING_CDMA_SETTING_NAME)) + value = NM_METERED_GUESS_YES; + else + value = NM_METERED_GUESS_NO; + } + + if (value != priv->metered) { + _LOGD(LOGD_DEVICE, "set metered value %d", value); + priv->metered = value; + _notify(self, PROP_METERED); + } +} + +static NMDeviceCheckDevAvailableFlags +_device_check_dev_available_flags_from_con(NMDeviceCheckConAvailableFlags con_flags) +{ + NMDeviceCheckDevAvailableFlags dev_flags; + + dev_flags = NM_DEVICE_CHECK_DEV_AVAILABLE_NONE; + + if (NM_FLAGS_HAS(con_flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER)) + dev_flags |= _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER; + + return dev_flags; +} + +static gboolean +_nm_device_check_connection_available(NMDevice * self, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceState state; + GError * local = NULL; + + /* an unrealized software device is always available, hardware devices never. */ + if (!nm_device_is_real(self)) { + if (nm_device_is_software(self)) { + if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) { + if (error) { + g_return_val_if_fail(local, FALSE); + nm_utils_error_set(error, + local->domain == NM_UTILS_ERROR ? local->code + : NM_UTILS_ERROR_UNKNOWN, + "profile is not compatible with software device (%s)", + local->message); + g_error_free(local); + } + return FALSE; + } + return TRUE; + } + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + "hardware device is not realized"); + return FALSE; + } + + state = nm_device_get_state(self); + if (state < NM_DEVICE_STATE_UNMANAGED) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + "device is in unknown state"); + return FALSE; + } + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + if (nm_device_get_managed(self, FALSE)) { + /* device is managed, both for user-requests and non-user-requests alike. */ + } else { + if (!nm_device_get_managed(self, TRUE)) { + /* device is strictly unmanaged by authoritative unmanaged reasons. */ + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + "device is strictly unmanaged"); + return FALSE; + } + if (!NM_FLAGS_HAS(flags, + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED)) { + /* device could be managed for an explict user-request, but this is not such a request. */ + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + "device is currently unmanaged"); + return FALSE; + } + } + } + if (state < NM_DEVICE_STATE_DISCONNECTED && !nm_device_is_software(self)) { + if (!nm_device_is_available(self, _device_check_dev_available_flags_from_con(flags))) { + if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device is not available"); + } else { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device is not available for internal request"); + } + return FALSE; + } + } + + if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) { + if (error) { + nm_utils_error_set(error, + local->domain == NM_UTILS_ERROR ? local->code + : NM_UTILS_ERROR_UNKNOWN, + "profile is not compatible with device (%s)", + local->message); + g_error_free(local); + } + return FALSE; + } + + return NM_DEVICE_GET_CLASS(self)->check_connection_available(self, + connection, + flags, + specific_object, + error); +} + +/** + * nm_device_check_connection_available(): + * @self: the #NMDevice + * @connection: the #NMConnection to check for availability + * @flags: flags to affect the decision making of whether a connection + * is available. Adding a flag can only make a connection more available, + * not less. + * @specific_object: a device type dependent argument to further + * filter the result. Passing a non %NULL specific object can only reduce + * the availability of a connection. + * @error: optionally give reason why not available. + * + * Check if @connection is available to be activated on @self. + * + * Returns: %TRUE if @connection can be activated on @self + */ +gboolean +nm_device_check_connection_available(NMDevice * self, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + gboolean available; + + available = + _nm_device_check_connection_available(self, connection, flags, specific_object, error); + +#if NM_MORE_ASSERTS >= 2 + { + /* The meaning of the flags is so that *adding* a flag relaxes a condition, thus making + * the device *more* available. Assert against that requirement by testing all the flags. */ + NMDeviceCheckConAvailableFlags i, j, k; + gboolean available_all[NM_DEVICE_CHECK_CON_AVAILABLE_ALL + 1] = {FALSE}; + + for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) + available_all[i] = + _nm_device_check_connection_available(self, connection, i, specific_object, NULL); + + for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) { + for (j = 1; j <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; j <<= 1) { + if (NM_FLAGS_ANY(i, j)) { + k = i & ~j; + nm_assert(available_all[i] == available_all[k] || available_all[i]); + } + } + } + } +#endif + + return available; +} + +static gboolean +available_connections_del_all(NMDevice *self) +{ + if (g_hash_table_size(self->_priv->available_connections) == 0) + return FALSE; + g_hash_table_remove_all(self->_priv->available_connections); + return TRUE; +} + +static gboolean +available_connections_add(NMDevice *self, NMSettingsConnection *sett_conn) +{ + return g_hash_table_add(self->_priv->available_connections, g_object_ref(sett_conn)); +} + +static gboolean +available_connections_del(NMDevice *self, NMSettingsConnection *sett_conn) +{ + return g_hash_table_remove(self->_priv->available_connections, sett_conn); +} + +static gboolean +check_connection_available(NMDevice * self, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + /* Connections which require a network connection are not available when + * the device has no carrier, even with ignore-carrer=TRUE. + */ + if (priv->carrier || !connection_requires_carrier(connection)) + return TRUE; + + if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER) + && priv->carrier_wait_id != 0) { + /* The device has no carrier though the connection requires it. + * + * If we are still waiting for carrier, the connection is available + * for an explicit user-request. */ + return TRUE; + } + + /* master types are always available even without carrier. + * Making connection non-available would un-enslave slaves which + * is not desired. */ + if (nm_device_is_master(self)) + return TRUE; + + if (!priv->up) { + /* If the device is !IFF_UP it also has no carrier. But we assume that if we + * would start activating the device (and thereby set the device IFF_UP), + * that we would get a carrier. We only know after we set the device up, + * and we only set it up after we start activating it. So presumably, this + * profile would be available (but we just don't know). */ + return TRUE; + } + + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has no carrier"); + return FALSE; +} + +void +nm_device_recheck_available_connections(NMDevice *self) +{ + NMDevicePrivate * priv; + NMSettingsConnection *const *connections; + gboolean changed = FALSE; + GHashTableIter h_iter; + NMSettingsConnection * sett_conn; + guint i; + gs_unref_hashtable GHashTable *prune_list = NULL; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (g_hash_table_size(priv->available_connections) > 0) { + prune_list = g_hash_table_new(nm_direct_hash, NULL); + g_hash_table_iter_init(&h_iter, priv->available_connections); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &sett_conn, NULL)) + g_hash_table_add(prune_list, sett_conn); + } + + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + sett_conn = connections[i]; + + if (nm_device_check_connection_available(self, + nm_settings_connection_get_connection(sett_conn), + NM_DEVICE_CHECK_CON_AVAILABLE_NONE, + NULL, + NULL)) { + if (available_connections_add(self, sett_conn)) + changed = TRUE; + if (prune_list) + g_hash_table_remove(prune_list, sett_conn); + } + } + + if (prune_list) { + g_hash_table_iter_init(&h_iter, prune_list); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &sett_conn, NULL)) { + if (available_connections_del(self, sett_conn)) + changed = TRUE; + } + } + + if (changed) + _notify(self, PROP_AVAILABLE_CONNECTIONS); + available_connections_check_delete_unrealized(self); +} + +/** + * nm_device_get_best_connection: + * @self: the #NMDevice + * @specific_object: a specific object path if any + * @error: reason why no connection was returned + * + * Returns a connection that's most suitable for user-initiated activation + * of a device, optionally with a given specific object. + * + * Returns: the #NMSettingsConnection or %NULL (setting an @error) + */ +NMSettingsConnection * +nm_device_get_best_connection(NMDevice *self, const char *specific_object, GError **error) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMSettingsConnection *sett_conn = NULL; + NMSettingsConnection *candidate; + guint64 best_timestamp = 0; + GHashTableIter iter; + + g_hash_table_iter_init(&iter, priv->available_connections); + while (g_hash_table_iter_next(&iter, (gpointer) &candidate, NULL)) { + guint64 candidate_timestamp = 0; + + /* If a specific object is given, only include connections that are + * compatible with it. + */ + if (specific_object /* << Optimization: we know that the connection is available without @specific_object. */ + && !nm_device_check_connection_available( + self, + nm_settings_connection_get_connection(candidate), + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + specific_object, + NULL)) + continue; + + nm_settings_connection_get_timestamp(candidate, &candidate_timestamp); + if (!sett_conn || (candidate_timestamp > best_timestamp)) { + sett_conn = candidate; + best_timestamp = candidate_timestamp; + } + } + + if (!sett_conn) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "The device '%s' has no connections available for activation.", + nm_device_get_iface(self)); + } + + return sett_conn; +} + +static void +cp_connection_added_or_updated(NMDevice *self, NMSettingsConnection *sett_conn) +{ + gboolean changed; + + g_return_if_fail(NM_IS_DEVICE(self)); + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn)); + + if (nm_device_check_connection_available(self, + nm_settings_connection_get_connection(sett_conn), + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + NULL)) + changed = available_connections_add(self, sett_conn); + else + changed = available_connections_del(self, sett_conn); + + if (changed) { + _notify(self, PROP_AVAILABLE_CONNECTIONS); + available_connections_check_delete_unrealized(self); + } +} + +static void +cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data) +{ + cp_connection_added_or_updated(user_data, sett_conn); +} + +static void +cp_connection_updated(NMSettings * settings, + NMSettingsConnection *sett_conn, + guint update_reason_u, + gpointer user_data) +{ + cp_connection_added_or_updated(user_data, sett_conn); +} + +static void +cp_connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data) +{ + NMDevice *self = user_data; + + g_return_if_fail(NM_IS_DEVICE(self)); + + if (available_connections_del(self, sett_conn)) { + _notify(self, PROP_AVAILABLE_CONNECTIONS); + available_connections_check_delete_unrealized(self); + } +} + +gboolean +nm_device_supports_vlans(NMDevice *self) +{ + return nm_platform_link_supports_vlans(nm_device_get_platform(self), + nm_device_get_ifindex(self)); +} + +/** + * nm_device_add_pending_action(): + * @self: the #NMDevice to add the pending action to + * @action: a static string that identifies the action. The string instance must + * stay valid until the pending action is removed (that is, the string is + * not cloned, but ownership stays with the caller). + * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending. + * Otherwise, ignore duplicate scheduling of the same action silently. + * + * Adds a pending action to the device. + * + * Returns: %TRUE if the action was added (and not already added before). %FALSE + * if the same action is already scheduled. In the latter case, the action was not scheduled + * a second time. + */ +gboolean +nm_device_add_pending_action(NMDevice *self, const char *action, gboolean assert_not_yet_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + GSList * iter; + guint count = 0; + + g_return_val_if_fail(action, FALSE); + + /* Check if the action is already pending. Cannot add duplicate actions */ + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (nm_streq(action, iter->data)) { + if (assert_not_yet_pending) { + _LOGW(LOGD_DEVICE, + "add_pending_action (%d): '%s' already pending", + count + g_slist_length(iter), + action); + g_return_val_if_reached(FALSE); + } else { + _LOGT(LOGD_DEVICE, + "add_pending_action (%d): '%s' already pending (expected)", + count + g_slist_length(iter), + action); + } + return FALSE; + } + count++; + } + + priv->pending_actions = g_slist_prepend(priv->pending_actions, (char *) action); + count++; + + _LOGD(LOGD_DEVICE, "add_pending_action (%d): '%s'", count, action); + + if (count == 1) + _notify(self, PROP_HAS_PENDING_ACTION); + + return TRUE; +} + +/** + * nm_device_remove_pending_action(): + * @self: the #NMDevice to remove the pending action from + * @action: a string that identifies the action. + * @assert_is_pending: if %TRUE, assert that the @action is pending. + * If %FALSE, don't do anything if the current action is not pending and + * return %FALSE. + * + * Removes a pending action previously added by nm_device_add_pending_action(). + * + * Returns: whether the @action was pending and is now removed. + */ +gboolean +nm_device_remove_pending_action(NMDevice *self, const char *action, gboolean assert_is_pending) +{ + NMDevicePrivate *priv; + GSList * iter, *next; + guint count = 0; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(action, FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + for (iter = priv->pending_actions; iter; iter = next) { + next = iter->next; + if (nm_streq(action, iter->data)) { + _LOGD(LOGD_DEVICE, + "remove_pending_action (%d): '%s'", + count + g_slist_length(iter->next), /* length excluding 'iter' */ + action); + priv->pending_actions = g_slist_delete_link(priv->pending_actions, iter); + if (priv->pending_actions == NULL) + _notify(self, PROP_HAS_PENDING_ACTION); + return TRUE; + } + count++; + } + + if (assert_is_pending) { + _LOGW(LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending", count, action); + g_return_val_if_reached(FALSE); + } else + _LOGT(LOGD_DEVICE, + "remove_pending_action (%d): '%s' not pending (expected)", + count, + action); + + return FALSE; +} + +const char * +nm_device_has_pending_action_reason(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->pending_actions) { + if (!priv->pending_actions->next && nm_device_get_state(self) == NM_DEVICE_STATE_ACTIVATED + && nm_streq(priv->pending_actions->data, NM_PENDING_ACTION_CARRIER_WAIT)) { + /* if the device is already in activated state, and the only reason + * why it appears still busy is "carrier-wait", then we are already complete. */ + return NULL; + } + + return priv->pending_actions->data; + } + + if (nm_device_is_real(self) + && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { + /* as long as the platform link is not yet initialized, we have a pending + * action. */ + return NM_PENDING_ACTION_LINK_INIT; + } + + return NULL; +} + +/*****************************************************************************/ + +static void +_cancel_activation(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->fw_call) { + nm_firewall_manager_cancel_call(priv->fw_call); + nm_assert(!priv->fw_call); + priv->fw_call = NULL; + priv->fw_state = FIREWALL_STATE_INITIALIZED; + } + + dispatcher_cleanup(self); + ip_check_gw_ping_cleanup(self); + + /* Break the activation chain */ + activation_source_clear(self, AF_INET); + activation_source_clear(self, AF_INET6); +} + +static void +_cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + guint i; + + _cancel_activation(self); + + priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_INIT; + + if (cleanup_type != CLEANUP_TYPE_KEEP) { + nm_manager_device_route_metric_clear(NM_MANAGER_GET, nm_device_get_ip_ifindex(self)); + } + + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE && priv->fw_state >= FIREWALL_STATE_INITIALIZED + && priv->fw_mgr && !nm_device_sys_iface_state_is_external(self)) { + nm_firewall_manager_remove_from_zone(priv->fw_mgr, + nm_device_get_ip_iface(self), + NULL, + NULL, + NULL); + } + priv->fw_state = FIREWALL_STATE_UNMANAGED; + g_clear_object(&priv->fw_mgr); + + queued_state_clear(self); + + nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release); + + for (i = 0; i < 2; i++) + nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free); + + _cleanup_ip_pre(self, AF_INET, cleanup_type); + _cleanup_ip_pre(self, AF_INET6, cleanup_type); +} + +static void +_cleanup_generic_post(NMDevice *self, CleanupType cleanup_type) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + priv->v4_commit_first_time = TRUE; + priv->v6_commit_first_time = TRUE; + + priv->v4_route_table_initialized = FALSE; + priv->v6_route_table_initialized = FALSE; + + priv->v4_route_table_all_sync_before = FALSE; + priv->v6_route_table_all_sync_before = FALSE; + + priv->default_route_metric_penalty_ip4_has = FALSE; + priv->default_route_metric_penalty_ip6_has = FALSE; + + priv->linklocal6_dad_counter = 0; + + priv->mtu_force_set_done = FALSE; + + /* Clean up IP configs; this does not actually deconfigure the + * interface; the caller must flush routes and addresses explicitly. + */ + nm_device_set_ip_config(self, AF_INET, NULL, TRUE, NULL); + nm_device_set_ip_config(self, AF_INET6, NULL, TRUE, NULL); + g_clear_object(&priv->proxy_config); + g_clear_object(&priv->con_ip_config_4); + applied_config_clear(&priv->dev_ip_config_4); + applied_config_clear(&priv->dev2_ip_config_4); + g_clear_object(&priv->ext_ip_config_4); + g_clear_object(&priv->ip_config_4); + g_clear_object(&priv->con_ip_config_6); + applied_config_clear(&priv->ac_ip6_config); + g_clear_object(&priv->ext_ip_config_6); + g_clear_object(&priv->ext_ip6_config_captured); + applied_config_clear(&priv->dev2_ip_config_6); + g_clear_object(&priv->ip_config_6); + g_clear_object(&priv->dad6_ip6_config); + priv->ipv6ll_has = FALSE; + memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr)); + + nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source(&priv->rt6_temporary_not_available_id); + + g_slist_free_full(priv->vpn_configs_4, g_object_unref); + priv->vpn_configs_4 = NULL; + g_slist_free_full(priv->vpn_configs_6, g_object_unref); + priv->vpn_configs_6 = NULL; + + /* We no longer accept the delegations. nm_device_set_ip_config(NULL) + * above disables them. */ + nm_assert(priv->needs_ip6_subnet == FALSE); + + if (priv->act_request.obj) { + nm_active_connection_set_default(NM_ACTIVE_CONNECTION(priv->act_request.obj), + AF_INET, + FALSE); + nm_clear_g_signal_handler(priv->act_request.obj, &priv->master_ready_id); + act_request_set(self, NULL); + } + + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) { + /* Check if the device was deactivated, and if so, delete_link. + * Don't call delete_link synchronously because we are currently + * handling a state change -- which is not reentrant. */ + delete_on_deactivate_check_and_schedule(self, nm_device_get_ip_ifindex(self)); + } + + /* ip_iface should be cleared after flushing all routes and addresses, since + * those are identified by ip_iface, not by iface (which might be a tty + * or ATM device). + */ + _set_ip_ifindex(self, 0, NULL); +} + +/* + * nm_device_cleanup + * + * Remove a device's routing table entries and IP addresses. + * + */ +static void +nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanup_type) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_if_fail(NM_IS_DEVICE(self)); + + if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) + _LOGD(LOGD_DEVICE, "preparing device"); + else + _LOGD(LOGD_DEVICE, + "deactivating device (reason '%s') [%d]", + reason_to_string_a(reason), + reason); + + /* Save whether or not we tried IPv6 for later */ + priv = NM_DEVICE_GET_PRIVATE(self); + + _cleanup_generic_pre(self, cleanup_type); + + /* Turn off kernel IPv6 */ + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) { + set_disable_ipv6(self, "1"); + nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0"); + } + + /* Call device type-specific deactivation */ + if (NM_DEVICE_GET_CLASS(self)->deactivate) + NM_DEVICE_GET_CLASS(self)->deactivate(self); + + ifindex = nm_device_get_ip_ifindex(self); + + if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) { + /* master: release slaves */ + nm_device_master_release_slaves(self); + + /* Take out any entries in the routing table and any IP address the device had. */ + if (ifindex > 0) { + NMPlatform * platform = nm_device_get_platform(self); + NMUtilsIPv6IfaceId iid = {}; + + nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex); + nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex); + nm_platform_tfilter_sync(platform, ifindex, NULL); + nm_platform_qdisc_sync(platform, ifindex, NULL); + set_ipv6_token(self, iid, "::"); + } + } + + _routing_rules_sync(self, + cleanup_type == CLEANUP_TYPE_KEEP ? NM_TERNARY_DEFAULT : NM_TERNARY_FALSE); + + if (ifindex > 0) + nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self), ifindex, NULL); + + /* slave: mark no longer enslaved */ + if (priv->master && priv->ifindex > 0 + && nm_platform_link_get_master(nm_device_get_platform(self), priv->ifindex) <= 0) + nm_device_master_release_one_slave(priv->master, + self, + FALSE, + FALSE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + + if (priv->lldp_listener) + nm_lldp_listener_stop(priv->lldp_listener); + + nm_device_update_metered(self); + + if (ifindex > 0) { + /* during device cleanup, we want to reset the MAC address of the device + * to the initial state. + * + * We certainly want to do that when reaching the UNMANAGED state... */ + if (nm_device_get_state(self) <= NM_DEVICE_STATE_UNMANAGED) + nm_device_hw_addr_reset(self, "unmanage"); + else { + /* for other device states (UNAVAILABLE, DISCONNECTED), allow the + * device to overwrite the reset behavior, so that Wi-Fi can set + * a randomized MAC address used during scanning. */ + NM_DEVICE_GET_CLASS(self)->deactivate_reset_hw_addr(self); + } + } + + priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE; + priv->ip6_mtu = 0; + if (priv->mtu_initial || priv->ip6_mtu_initial) { + ifindex = nm_device_get_ip_ifindex(self); + + if (ifindex > 0 && cleanup_type == CLEANUP_TYPE_DECONFIGURE) { + _LOGT(LOGD_DEVICE, + "mtu: reset device-mtu: %u, ipv6-mtu: %u, ifindex: %d", + (guint) priv->mtu_initial, + (guint) priv->ip6_mtu_initial, + ifindex); + if (priv->mtu_initial) { + nm_platform_link_set_mtu(nm_device_get_platform(self), ifindex, priv->mtu_initial); + priv->carrier_wait_until_ms = + nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS; + } + if (priv->ip6_mtu_initial) { + char sbuf[64]; + + nm_device_sysctl_ip_conf_set( + self, + AF_INET6, + "mtu", + nm_sprintf_buf(sbuf, "%u", (unsigned) priv->ip6_mtu_initial)); + } + } + priv->mtu_initial = 0; + priv->ip6_mtu_initial = 0; + } + + _ethtool_state_reset(self); + + _cleanup_generic_post(self, cleanup_type); +} + +static void +deactivate_reset_hw_addr(NMDevice *self) +{ + nm_device_hw_addr_reset(self, "deactivate"); +} + +static char * +find_dhcp4_address(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + const NMPlatformIP4Address *a; + NMDedupMultiIter ipconf_iter; + + if (!priv->ip_config_4) + return NULL; + + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) { + if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP) + return nm_utils_inet4_ntop_dup(a->address); + } + return NULL; +} + +void +nm_device_spawn_iface_helper(NMDevice *self) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + gboolean configured = FALSE; + NMConnection * connection; + GError * error = NULL; + const char * method; + GPtrArray * argv; + gs_free char * dhcp4_address = NULL; + char * logging_backend; + NMUtilsStableType stable_type; + const char * stable_id; + + if (priv->state != NM_DEVICE_STATE_ACTIVATED) + return; + if (!nm_device_can_assume_connections(self)) + return; + + connection = nm_device_get_applied_connection(self); + + g_return_if_fail(connection); + + argv = g_ptr_array_sized_new(10); + g_ptr_array_set_free_func(argv, g_free); + + g_ptr_array_add(argv, g_strdup(LIBEXECDIR "/nm-iface-helper")); + g_ptr_array_add(argv, g_strdup("--ifname")); + g_ptr_array_add(argv, g_strdup(nm_device_get_ip_iface(self))); + g_ptr_array_add(argv, g_strdup("--uuid")); + g_ptr_array_add(argv, g_strdup(nm_connection_get_uuid(connection))); + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + if (stable_type != NM_UTILS_STABLE_TYPE_UUID) { + g_ptr_array_add(argv, g_strdup("--stable-id")); + g_ptr_array_add(argv, g_strdup_printf("%d %s", (int) stable_type, stable_id)); + } + + logging_backend = + nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_LOGGING, + NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + if (logging_backend) { + g_ptr_array_add(argv, g_strdup("--logging-backend")); + g_ptr_array_add(argv, logging_backend); + } + + g_ptr_array_add(argv, g_strdup("--log-level")); + g_ptr_array_add(argv, g_strdup(nm_logging_level_to_string())); + + g_ptr_array_add(argv, g_strdup("--log-domains")); + g_ptr_array_add(argv, g_strdup(nm_logging_domains_to_string())); + + dhcp4_address = find_dhcp4_address(self); + + method = nm_device_get_effective_ip_config_method(self, AF_INET); + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + NMSettingIPConfig *s_ip4; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + nm_assert(s_ip4); + + g_ptr_array_add(argv, g_strdup("--priority4")); + g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET))); + + g_ptr_array_add(argv, g_strdup("--dhcp4")); + g_ptr_array_add(argv, g_strdup(dhcp4_address)); + if (nm_setting_ip_config_get_may_fail(s_ip4) == FALSE) + g_ptr_array_add(argv, g_strdup("--dhcp4-required")); + + if (priv->dhcp_data_4.client) { + const char *hostname; + GBytes * client_id; + + client_id = nm_dhcp_client_get_client_id(priv->dhcp_data_4.client); + if (client_id) { + g_ptr_array_add(argv, g_strdup("--dhcp4-clientid")); + g_ptr_array_add(argv, + nm_utils_bin2hexstr_full(g_bytes_get_data(client_id, NULL), + g_bytes_get_size(client_id), + ':', + FALSE, + NULL)); + } + + hostname = nm_dhcp_client_get_hostname(priv->dhcp_data_4.client); + if (hostname) { + if (nm_dhcp_client_get_use_fqdn(priv->dhcp_data_4.client)) + g_ptr_array_add(argv, g_strdup("--dhcp4-fqdn")); + else + g_ptr_array_add(argv, g_strdup("--dhcp4-hostname")); + g_ptr_array_add(argv, g_strdup(hostname)); + } + } + + configured = TRUE; + } + + method = nm_utils_get_ip_config_method(connection, AF_INET6); + if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + NMSettingIPConfig *s_ip6; + NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT; + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + + g_ptr_array_add(argv, g_strdup("--priority6")); + g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET6))); + + g_ptr_array_add(argv, g_strdup("--slaac")); + + if (nm_setting_ip_config_get_may_fail(s_ip6) == FALSE) + g_ptr_array_add(argv, g_strdup("--slaac-required")); + + g_ptr_array_add(argv, g_strdup("--slaac-tempaddr")); + g_ptr_array_add(argv, g_strdup_printf("%d", priv->ndisc_use_tempaddr)); + + if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) { + g_ptr_array_add(argv, g_strdup("--iid")); + g_ptr_array_add( + argv, + nm_utils_bin2hexstr_full(iid.id_u8, sizeof(NMUtilsIPv6IfaceId), ':', FALSE, NULL)); + } + + g_ptr_array_add(argv, g_strdup("--addr-gen-mode")); + g_ptr_array_add( + argv, + g_strdup_printf("%d", + nm_setting_ip6_config_get_addr_gen_mode(NM_SETTING_IP6_CONFIG(s_ip6)))); + + configured = TRUE; + } + + if (configured) { + GPid pid; + + g_ptr_array_add(argv, NULL); + + if (nm_logging_enabled(LOGL_DEBUG, LOGD_DEVICE)) { + char *tmp; + + tmp = g_strjoinv(" ", (char **) argv->pdata); + _LOGD(LOGD_DEVICE, "running '%s'", tmp); + g_free(tmp); + } + + if (g_spawn_async(NULL, + (char **) argv->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &pid, + &error)) { + _LOGI(LOGD_DEVICE, "spawned helper PID %u", (guint) pid); + } else { + _LOGW(LOGD_DEVICE, "failed to spawn helper: %s", error->message); + g_error_free(error); + } + } + + g_ptr_array_unref(argv); +} + +/*****************************************************************************/ + +static gboolean +ip_config_valid(NMDeviceState state) +{ + return (state == NM_DEVICE_STATE_UNMANAGED) + || (state >= NM_DEVICE_STATE_IP_CHECK && state <= NM_DEVICE_STATE_DEACTIVATING); +} + +static void +notify_ip_properties(NMDevice *self) +{ + _notify(self, PROP_IP_IFACE); + _notify(self, PROP_IP4_CONFIG); + _notify(self, PROP_DHCP4_CONFIG); + _notify(self, PROP_IP6_CONFIG); + _notify(self, PROP_DHCP6_CONFIG); +} + +static void +ip6_managed_setup(NMDevice *self) +{ + set_nm_ipv6ll(self, TRUE); + set_disable_ipv6(self, "1"); + nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0"); + nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0"); + nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "0"); +} + +static void +deactivate_ready(NMDevice *self, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->dispatcher.call_id) + return; + + if (priv->sriov_reset_pending > 0) + return; + + if (priv->state == NM_DEVICE_STATE_DEACTIVATING) + nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, reason); +} + +static void +sriov_reset_on_deactivate_cb(GError *error, gpointer user_data) +{ + NMDevice * self; + NMDevicePrivate *priv; + gpointer reason; + + nm_utils_user_data_unpack(user_data, &self, &reason); + priv = NM_DEVICE_GET_PRIVATE(self); + nm_assert(priv->sriov_reset_pending > 0); + priv->sriov_reset_pending--; + + if (nm_utils_error_is_cancelled(error)) + return; + + deactivate_ready(self, GPOINTER_TO_INT(reason)); +} + +static void +sriov_reset_on_failure_cb(GError *error, gpointer user_data) +{ + NMDevice * self = user_data; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert(priv->sriov_reset_pending > 0); + priv->sriov_reset_pending--; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (priv->state == NM_DEVICE_STATE_FAILED) { + nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + } +} + +static void +deactivate_async_ready(NMDevice *self, GError *error, gpointer user_data) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceStateReason reason = GPOINTER_TO_UINT(user_data); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + _LOGD(LOGD_DEVICE, "Deactivation cancelled"); + return; + } + + g_clear_object(&priv->deactivating_cancellable); + + /* In every other case, transition to the DISCONNECTED state */ + if (error) { + _LOGW(LOGD_DEVICE, "Deactivation failed: %s", error->message); + } + + deactivate_ready(self, reason); +} + +static void +deactivate_dispatcher_complete(NMDispatcherCallId *call_id, gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceStateReason reason; + + g_return_if_fail(call_id == priv->dispatcher.call_id); + g_return_if_fail(priv->dispatcher.post_state == NM_DEVICE_STATE_DISCONNECTED); + + reason = priv->state_reason; + + priv->dispatcher.call_id = NULL; + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + + if (nm_clear_g_cancellable(&priv->deactivating_cancellable)) + nm_assert_not_reached(); + + if (NM_DEVICE_GET_CLASS(self)->deactivate_async) { + /* FIXME: the virtual function deactivate_async() has only this caller here. + * And the NMDevice subtypes are well aware of the circumstances when they + * are called. We shall make the function less generic and thus (as the scope + * is narrower) more convenient. + * + * - Drop the callback argument. Instead, when deactivate_async() completes, the + * subtype shall call a method _nm_device_deactivate_async_done(). Because as + * it is currently, subtypes need to pretend this callback and the user-data + * would be opaque, and carry it around. When it's in fact very clear what this + * is. + * + * - Also drop the GCancellable argument. Upon cancellation, NMDevice shall + * call another virtual function deactivate_async_abort(). As it is currently, + * callers need to register to the cancelled signal of the cancellable. It + * seems simpler to just implement the deactivate_async_abort() function. + * On the other hand, some implementations actually use the GCancellable. + * So, NMDevice shall do both: it shall both pass a cancellable, but also + * invoke deactivate_async_abort(). It allow the implementation to honor + * whatever is simpler for their purpose. + * + * - sometimes, the subclass can complete right away. Scheduling the completion + * in an idle handler is cumbersome. Allow the function to return FALSE to + * indicate that the device is already deactivated and the callback (or + * _nm_device_deactivate_async_done()) won't be invoked. + */ + priv->deactivating_cancellable = g_cancellable_new(); + NM_DEVICE_GET_CLASS(self)->deactivate_async(self, + priv->deactivating_cancellable, + deactivate_async_ready, + GUINT_TO_POINTER(reason)); + } else + deactivate_ready(self, reason); +} + +static void +_set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting) +{ + NMDevicePrivate *priv; + NMDeviceState old_state; + gs_unref_object NMActRequest *req = NULL; + gboolean no_firmware = FALSE; + NMSettingsConnection * sett_conn; + NMSettingSriov * s_sriov; + gboolean concheck_now; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + /* Track re-entry */ + g_warn_if_fail(priv->in_state_changed == FALSE); + + old_state = priv->state; + + if (state == NM_DEVICE_STATE_FAILED && nm_device_sys_iface_state_is_external_or_assume(self)) { + /* Avoid tearing down assumed connection, assume it's connected */ + state = NM_DEVICE_STATE_ACTIVATED; + reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED; + } + + /* Do nothing if state isn't changing, but as a special case allow + * re-setting UNAVAILABLE if the device is missing firmware so that we + * can retry device initialization. + */ + if ((priv->state == state) + && (state != NM_DEVICE_STATE_UNAVAILABLE || !priv->firmware_missing)) { + _LOGD(LOGD_DEVICE, + "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)", + nm_device_state_to_str(old_state), + nm_device_state_to_str(state), + reason_to_string_a(reason), + _sys_iface_state_to_str(priv->sys_iface_state), + priv->firmware_missing ? ", missing firmware" : ""); + return; + } + + _LOGI(LOGD_DEVICE, + "state change: %s -> %s (reason '%s', sys-iface-state: '%s')", + nm_device_state_to_str(old_state), + nm_device_state_to_str(state), + reason_to_string_a(reason), + _sys_iface_state_to_str(priv->sys_iface_state)); + + /* in order to prevent triggering any callback caused + * by the device not having any pending action anymore + * we add one here that gets removed at the end of the function */ + nm_device_add_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE); + priv->in_state_changed = TRUE; + + priv->state = state; + priv->state_reason = reason; + + queued_state_clear(self); + + dispatcher_cleanup(self); + + nm_clear_g_cancellable(&priv->deactivating_cancellable); + + /* Cache the activation request for the dispatcher */ + req = nm_g_object_ref(priv->act_request.obj); + + if (state > NM_DEVICE_STATE_UNMANAGED && state <= NM_DEVICE_STATE_ACTIVATED + && nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NOW_MANAGED + && NM_IN_SET_TYPED(NMDeviceSysIfaceState, + priv->sys_iface_state, + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME)) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + + if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_ACTIVATED) + priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET; + + if (state > NM_DEVICE_STATE_DISCONNECTED) + nm_device_assume_state_reset(self); + + if (state <= NM_DEVICE_STATE_UNAVAILABLE) { + if (available_connections_del_all(self)) + _notify(self, PROP_AVAILABLE_CONNECTIONS); + if (old_state > NM_DEVICE_STATE_UNAVAILABLE) { + _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + } + } + + /* Update the available connections list when a device first becomes available */ + if (state >= NM_DEVICE_STATE_DISCONNECTED && old_state < NM_DEVICE_STATE_DISCONNECTED) + nm_device_recheck_available_connections(self); + + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_DEACTIVATING) { + if (nm_clear_g_free(&priv->current_stable_id)) + _LOGT(LOGD_DEVICE, "stable-id: clear"); + } + + /* Handle the new state here; but anything that could trigger + * another state change should be done below. + */ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + nm_device_set_firmware_missing(self, FALSE); + if (old_state > NM_DEVICE_STATE_UNMANAGED) { + if (priv->sys_iface_state != NM_DEVICE_SYS_IFACE_STATE_MANAGED) { + nm_device_cleanup(self, + reason, + priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_REMOVED + ? CLEANUP_TYPE_REMOVED + : CLEANUP_TYPE_KEEP); + } else { + /* Clean up if the device is now unmanaged but was activated */ + if (nm_device_get_act_request(self)) + nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); + nm_device_take_down(self, TRUE); + nm_device_hw_addr_reset(self, "unmanage"); + set_nm_ipv6ll(self, FALSE); + restore_ip6_properties(self); + } + } + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + break; + case NM_DEVICE_STATE_UNAVAILABLE: + if (old_state == NM_DEVICE_STATE_UNMANAGED) { + save_ip6_properties(self); + if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) + ip6_managed_setup(self); + device_init_static_sriov_num_vfs(self); + } + + if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) { + if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { + if (!nm_device_bring_up(self, TRUE, &no_firmware) && no_firmware) + _LOGW(LOGD_PLATFORM, "firmware may be missing."); + nm_device_set_firmware_missing(self, no_firmware ? TRUE : FALSE); + } + + /* Ensure the device gets deactivated in response to stuff like + * carrier changes or rfkill. But don't deactivate devices that are + * about to assume a connection since that defeats the purpose of + * assuming the device's existing connection. + * + * Note that we "deactivate" the device even when coming from + * UNMANAGED, to ensure that it's in a clean state. + */ + nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); + } + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (old_state > NM_DEVICE_STATE_DISCONNECTED) { + /* Ensure devices that previously assumed a connection now have + * userspace IPv6LL enabled. + */ + set_nm_ipv6ll(self, TRUE); + + nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); + } else if (old_state < NM_DEVICE_STATE_DISCONNECTED) { + if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) { + /* Ensure IPv6 is set up as it may not have been done when + * entering the UNAVAILABLE state depending on the reason. + */ + ip6_managed_setup(self); + } + } + break; + case NM_DEVICE_STATE_PREPARE: + nm_device_update_initial_hw_address(self); + break; + case NM_DEVICE_STATE_NEED_AUTH: + if (old_state > NM_DEVICE_STATE_NEED_AUTH) { + /* Clean up any half-done IP operations if the device's layer2 + * finds out it needs authentication during IP config. + */ + _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE); + _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE); + } + break; + default: + break; + } + + /* Reset intern autoconnect flags when the device is activating or connected. */ + if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_ACTIVATED) + nm_device_autoconnect_blocked_unset(self, NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL); + + _notify(self, PROP_STATE); + _notify(self, PROP_STATE_REASON); + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_device, + &signal_info_state_changed, + "(uuu)", + (guint32) state, + (guint32) old_state, + (guint32) reason); + g_signal_emit(self, + signals[STATE_CHANGED], + 0, + (guint) state, + (guint) old_state, + (guint) reason); + + /* Post-process the event after internal notification */ + + switch (state) { + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device can activate now (ie, it's got a carrier, the supplicant + * is active, or whatever) schedule a delayed transition to DISCONNECTED + * to get things rolling. The device can't transition immediately because + * we can't change states again from the state handler for a variety of + * reasons. + */ + if (nm_device_is_available(self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)) { + nm_device_queue_recheck_available(self, + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_NONE); + } else { + _LOGD(LOGD_DEVICE, "device not yet available for transition to DISCONNECTED"); + } + break; + case NM_DEVICE_STATE_DEACTIVATING: + _cancel_activation(self); + + /* We cache the ignore_carrier state to not react on config-reloads while the connection + * is active. But on deactivating, reset the ignore-carrier flag to the current state. */ + priv->ignore_carrier = nm_config_data_get_ignore_carrier(NM_CONFIG_GET_DATA, self); + + if (quitting) { + nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_PRE_DOWN, self, req); + } else { + priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + priv->dispatcher.post_state_reason = reason; + if (!nm_dispatcher_call_device(NM_DISPATCHER_ACTION_PRE_DOWN, + self, + req, + deactivate_dispatcher_complete, + self, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + deactivate_dispatcher_complete(0, self); + } + + if (priv->ifindex > 0 + && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) { + priv->sriov_reset_pending++; + sriov_op_queue(self, + 0, + NM_OPTION_BOOL_TRUE, + sriov_reset_on_deactivate_cb, + nm_utils_user_data_pack(self, GINT_TO_POINTER(reason))); + } + } + + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->queued_act_request && !priv->queued_act_request_is_waiting_for_carrier) { + gs_unref_object NMActRequest *queued_req = NULL; + + queued_req = g_steal_pointer(&priv->queued_act_request); + _device_activate(self, queued_req); + } + break; + case NM_DEVICE_STATE_ACTIVATED: + _LOGI(LOGD_DEVICE, "Activation: successful, device activated."); + nm_device_update_metered(self); + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_UP, self, req, NULL, NULL, NULL); + + if (priv->proxy_config) + _pacrunner_manager_add(self); + break; + case NM_DEVICE_STATE_FAILED: + /* Usually upon failure the activation chain is interrupted in + * one of the stages; but in some cases the device fails for + * external events (as a failure of master connection) while + * the activation sequence is running and so we need to ensure + * that the chain is terminated here. + */ + _cancel_activation(self); + + sett_conn = nm_device_get_settings_connection(self); + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: failed for connection '%s'", + sett_conn ? nm_settings_connection_get_id(sett_conn) : ""); + + /* Notify any slaves of the unexpected failure */ + nm_device_master_release_slaves(self); + + /* If the connection doesn't yet have a timestamp, set it to zero so that + * we can distinguish between connections we've tried to activate and have + * failed (zero timestamp), connections that succeeded (non-zero timestamp), + * and those we haven't tried yet (no timestamp). + */ + if (sett_conn && !nm_settings_connection_get_timestamp(sett_conn, NULL)) + nm_settings_connection_update_timestamp(sett_conn, (guint64) 0); + + if (priv->ifindex > 0 + && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) { + priv->sriov_reset_pending++; + sriov_op_queue(self, 0, NM_OPTION_BOOL_TRUE, sriov_reset_on_failure_cb, self); + break; + } + /* Schedule the transition to DISCONNECTED. The device can't transition + * immediately because we can't change states again from the state + * handler for a variety of reasons. + */ + nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + break; + case NM_DEVICE_STATE_IP_CHECK: + { + gboolean change_zone = FALSE; + + if (!nm_device_sys_iface_state_is_external(self)) { + if (priv->ip_iface) { + /* The device now has a @ip_iface different from the + * @iface on which we previously set the zone. */ + change_zone = TRUE; + } else if (priv->fw_state == FIREWALL_STATE_UNMANAGED && priv->ifindex > 0) { + /* We didn't set the zone earlier because there was + * no ifindex. */ + change_zone = TRUE; + } + } + + if (change_zone) { + priv->fw_state = FIREWALL_STATE_WAIT_IP_CONFIG; + fw_change_zone(self); + } else + nm_device_start_ip_check(self); + + /* IP-related properties are only valid when the device has IP configuration; + * now that it does, ensure their change notifications are emitted. + */ + notify_ip_properties(self); + break; + } + case NM_DEVICE_STATE_SECONDARIES: + ip_check_gw_ping_cleanup(self); + _LOGD(LOGD_DEVICE, "device entered SECONDARIES state"); + break; + default: + break; + } + + if (state > NM_DEVICE_STATE_DISCONNECTED) + delete_on_deactivate_unschedule(self); + + if ((old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING) + && (state != NM_DEVICE_STATE_DEACTIVATING)) { + if (quitting) { + nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_DOWN, self, req); + } else { + nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DOWN, self, req, NULL, NULL, NULL); + } + } + + /* IP-related properties are only valid when the device has IP configuration. + * If it no longer does, ensure their change notifications are emitted. + */ + if (ip_config_valid(old_state) && !ip_config_valid(state)) + notify_ip_properties(self); + + concheck_now = NM_IN_SET(state, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_DISCONNECTED) + || old_state >= NM_DEVICE_STATE_ACTIVATED; + concheck_update_interval(self, AF_INET, concheck_now); + concheck_update_interval(self, AF_INET6, concheck_now); + + priv->in_state_changed = FALSE; + nm_device_remove_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE); + + if ((old_state > NM_DEVICE_STATE_UNMANAGED) != (state > NM_DEVICE_STATE_UNMANAGED)) + _notify(self, PROP_MANAGED); +} + +void +nm_device_state_changed(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason) +{ + _set_state_full(self, state, reason, FALSE); +} + +static gboolean +queued_state_set(gpointer user_data) +{ + NMDevice * self = NM_DEVICE(user_data); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMDeviceState new_state; + NMDeviceStateReason new_reason; + + nm_assert(priv->queued_state.id); + + _LOGD(LOGD_DEVICE, + "queue-state[%s, reason:%s, id:%u]: %s", + nm_device_state_to_str(priv->queued_state.state), + reason_to_string_a(priv->queued_state.reason), + priv->queued_state.id, + "change state"); + + /* Clear queued state struct before triggering state change, since + * the state change may queue another state. + */ + priv->queued_state.id = 0; + new_state = priv->queued_state.state; + new_reason = priv->queued_state.reason; + + nm_device_state_changed(self, new_state, new_reason); + nm_device_remove_pending_action(self, queued_state_to_string(new_state), TRUE); + + return G_SOURCE_REMOVE; +} + +void +nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + + g_return_if_fail(NM_IS_DEVICE(self)); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->queued_state.id && priv->queued_state.state == state) { + _LOGD(LOGD_DEVICE, + "queue-state[%s, reason:%s, id:%u]: %s%s%s%s", + nm_device_state_to_str(priv->queued_state.state), + reason_to_string_a(priv->queued_state.reason), + priv->queued_state.id, + "ignore queuing same state change", + NM_PRINT_FMT_QUOTED(priv->queued_state.reason != reason, + " (reason differs: ", + reason_to_string_a(reason), + ")", + "")); + return; + } + + /* Add pending action for the new state before clearing the queued states, so + * that we don't accidentally pop all pending states and reach 'startup complete' */ + nm_device_add_pending_action(self, queued_state_to_string(state), TRUE); + + /* We should only ever have one delayed state transition at a time */ + if (priv->queued_state.id) { + _LOGW(LOGD_DEVICE, + "queue-state[%s, reason:%s, id:%u]: %s", + nm_device_state_to_str(priv->queued_state.state), + reason_to_string_a(priv->queued_state.reason), + priv->queued_state.id, + "replace previously queued state change"); + nm_clear_g_source(&priv->queued_state.id); + nm_device_remove_pending_action(self, + queued_state_to_string(priv->queued_state.state), + TRUE); + } + + priv->queued_state.state = state; + priv->queued_state.reason = reason; + priv->queued_state.id = g_idle_add(queued_state_set, self); + + _LOGD(LOGD_DEVICE, + "queue-state[%s, reason:%s, id:%u]: %s", + nm_device_state_to_str(state), + reason_to_string_a(reason), + priv->queued_state.id, + "queue state change"); +} + +static void +queued_state_clear(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->queued_state.id) + return; + + _LOGD(LOGD_DEVICE, + "queue-state[%s, reason:%s, id:%u]: %s", + nm_device_state_to_str(priv->queued_state.state), + reason_to_string_a(priv->queued_state.reason), + priv->queued_state.id, + "clear queued state change"); + nm_clear_g_source(&priv->queued_state.id); + nm_device_remove_pending_action(self, queued_state_to_string(priv->queued_state.state), TRUE); +} + +NMDeviceState +nm_device_get_state(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_STATE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE(self)->state; +} + +/*****************************************************************************/ +/* NMConfigDevice interface related stuff */ + +const char * +nm_device_get_hw_address(NMDevice *self) +{ + NMDevicePrivate *priv; + char buf[NM_UTILS_HWADDR_LEN_MAX]; + gsize l; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + priv = NM_DEVICE_GET_PRIVATE(self); + + nm_assert((!priv->hw_addr && priv->hw_addr_len == 0) + || (priv->hw_addr && _nm_utils_hwaddr_aton(priv->hw_addr, buf, sizeof(buf), &l) + && l == priv->hw_addr_len)); + + return priv->hw_addr; +} + +gboolean +nm_device_update_hw_address(NMDevice *self) +{ + NMDevicePrivate *priv; + const guint8 * hwaddr; + gsize hwaddrlen = 0; + + priv = NM_DEVICE_GET_PRIVATE(self); + if (priv->ifindex <= 0) + return FALSE; + + hwaddr = nm_platform_link_get_address(nm_device_get_platform(self), priv->ifindex, &hwaddrlen); + + if (priv->type == NM_DEVICE_TYPE_ETHERNET && hwaddr + && nm_utils_hwaddr_matches(hwaddr, + hwaddrlen, + &nm_ether_addr_zero, + sizeof(nm_ether_addr_zero))) + hwaddrlen = 0; + + if (!hwaddrlen) + return FALSE; + + if (priv->hw_addr_len && priv->hw_addr_len != hwaddrlen) { + char s_buf[NM_UTILS_HWADDR_LEN_MAX_STR]; + + /* we cannot change the address length of a device once it is set (except + * unrealizing the device). + * + * The reason is that the permanent and initial MAC addresses also must have the + * same address length, so it's unclear what it would mean that the length changes. */ + _LOGD(LOGD_PLATFORM | LOGD_DEVICE, + "hw-addr: read a MAC address with differing length (%s vs. %s)", + priv->hw_addr, + _nm_utils_hwaddr_ntoa(hwaddr, hwaddrlen, TRUE, s_buf, sizeof(s_buf))); + return FALSE; + } + + if (priv->hw_addr && nm_utils_hwaddr_matches(priv->hw_addr, -1, hwaddr, hwaddrlen)) + return FALSE; + + g_free(priv->hw_addr); + priv->hw_addr_len_ = hwaddrlen; + priv->hw_addr = nm_utils_hwaddr_ntoa(hwaddr, hwaddrlen); + + _LOGD(LOGD_PLATFORM | LOGD_DEVICE, "hw-addr: hardware address now %s", priv->hw_addr); + _notify(self, PROP_HW_ADDRESS); + + if (!priv->hw_addr_initial + || (priv->hw_addr_type == HW_ADDR_TYPE_UNSET && priv->state < NM_DEVICE_STATE_PREPARE + && !nm_device_is_activating(self))) { + /* when we get a hw_addr the first time or while the device + * is not activated (with no explicit hw address set), always + * update our initial hw-address as well. */ + nm_device_update_initial_hw_address(self); + } + return TRUE; +} + +void +nm_device_update_initial_hw_address(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->hw_addr && !nm_streq0(priv->hw_addr_initial, priv->hw_addr)) { + if (priv->hw_addr_initial && priv->hw_addr_type != HW_ADDR_TYPE_UNSET) { + /* once we have the initial hw address set, we only allow + * update if the currently type is "unset". */ + return; + } + g_free(priv->hw_addr_initial); + priv->hw_addr_initial = g_strdup(priv->hw_addr); + _LOGD(LOGD_DEVICE, "hw-addr: update initial MAC address %s", priv->hw_addr_initial); + } +} + +void +nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze) +{ + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + guint8 buf[NM_UTILS_HWADDR_LEN_MAX]; + size_t len = 0; + gboolean success_read; + int ifindex; + const NMPlatformLink * pllink; + const NMConfigDeviceStateData *dev_state; + + if (priv->hw_addr_perm) { + /* the permanent hardware address is only read once and not + * re-read later. + * + * Except during unrealize/realize cycles, where we clear the permanent + * hardware address during unrealization. */ + return; + } + + ifindex = priv->ifindex; + if (ifindex <= 0) + return; + + /* the user is advised to configure stable MAC addresses for software devices via + * UDEV. Thus, check whether the link is fully initialized. */ + pllink = nm_platform_link_get(nm_device_get_platform(self), ifindex); + if (!pllink || !pllink->initialized) { + if (!force_freeze) { + /* we can afford to wait. Back off and leave the permanent MAC address + * undecided for now. */ + return; + } + /* try to refresh the link just to give UDEV a bit more time... */ + nm_platform_link_refresh(nm_device_get_platform(self), ifindex); + /* maybe the MAC address changed... */ + nm_device_update_hw_address(self); + } else if (!priv->hw_addr_len) + nm_device_update_hw_address(self); + + if (!priv->hw_addr_len) { + /* we need the current MAC address because we require the permanent MAC address + * to have the same length as the current address. + * + * Abort if there is no current MAC address. */ + return; + } + + success_read = + nm_platform_link_get_permanent_address(nm_device_get_platform(self), ifindex, buf, &len); + if (success_read && priv->hw_addr_len == len) { + priv->hw_addr_perm_fake = FALSE; + priv->hw_addr_perm = nm_utils_hwaddr_ntoa(buf, len); + _LOGD(LOGD_DEVICE, "hw-addr: read permanent MAC address '%s'", priv->hw_addr_perm); + goto notify_and_out; + } + + /* we failed to read a permanent MAC address, thus we use a fake address, + * that is the current MAC address of the device. + * + * Note that the permanet MAC address of a NMDevice instance does not change + * after being set once. Thus, we use now a fake address and stick to that + * (until we unrealize the device). */ + priv->hw_addr_perm_fake = TRUE; + + /* We also persist our choice of the fake address to the device state + * file to use the same address on restart of NetworkManager. + * First, try to reload the address from the state file. */ + dev_state = nm_config_device_state_get(nm_config_get(), ifindex); + if (dev_state && dev_state->perm_hw_addr_fake + && nm_utils_hwaddr_aton(dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len) + && !nm_utils_hwaddr_matches(buf, priv->hw_addr_len, priv->hw_addr, -1)) { + _LOGD(LOGD_PLATFORM | LOGD_ETHER, + "hw-addr: %s (use from statefile: %s, current: %s)", + success_read ? "read HW addr length of permanent MAC address differs" + : "unable to read permanent MAC address", + dev_state->perm_hw_addr_fake, + priv->hw_addr); + priv->hw_addr_perm = nm_utils_hwaddr_ntoa(buf, priv->hw_addr_len); + goto notify_and_out; + } + + _LOGD(LOGD_PLATFORM | LOGD_ETHER, + "hw-addr: %s (use current: %s)", + success_read ? "read HW addr length of permanent MAC address differs" + : "unable to read permanent MAC address", + priv->hw_addr); + priv->hw_addr_perm = g_strdup(priv->hw_addr); + +notify_and_out: + _notify(self, PROP_PERM_HW_ADDRESS); +} + +gboolean +nm_device_hw_addr_is_explict(NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + return !NM_IN_SET((HwAddrType) priv->hw_addr_type, HW_ADDR_TYPE_PERMANENT, HW_ADDR_TYPE_UNSET); +} + +static gboolean +_hw_addr_matches(NMDevice *self, const guint8 *addr, gsize addr_len) +{ + const char *cur_addr; + + cur_addr = nm_device_get_hw_address(self); + return cur_addr && nm_utils_hwaddr_matches(addr, addr_len, cur_addr, -1); +} + +static gboolean +_hw_addr_set(NMDevice * self, + const char *const addr, + const char *const operation, + const char *const detail) +{ + NMDevicePrivate *priv; + gboolean success = FALSE; + int r; + guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX]; + gsize addr_len; + gboolean was_taken_down = FALSE; + gboolean retry_down; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(addr); + nm_assert(operation); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!_nm_utils_hwaddr_aton(addr, addr_bytes, sizeof(addr_bytes), &addr_len)) + g_return_val_if_reached(FALSE); + + /* Do nothing if current MAC is same */ + if (_hw_addr_matches(self, addr_bytes, addr_len)) { + _LOGT(LOGD_DEVICE, "set-hw-addr: no MAC address change needed (%s)", addr); + return TRUE; + } + + if (priv->hw_addr_len && priv->hw_addr_len != addr_len) { + _LOGT(LOGD_DEVICE, + "set-hw-addr: setting MAC address to '%s' (%s, %s) failed because of wrong address " + "length (should be %u bytes)", + addr, + operation, + detail, + priv->hw_addr_len); + return FALSE; + } + + _LOGT(LOGD_DEVICE, + "set-hw-addr: setting MAC address to '%s' (%s, %s)...", + addr, + operation, + detail); + + if (nm_device_get_device_type(self) == NM_DEVICE_TYPE_WIFI) { + /* Always take the device down for Wi-Fi because + * wpa_supplicant needs it to properly detect the MAC + * change. */ + retry_down = FALSE; + was_taken_down = TRUE; + nm_device_take_down(self, FALSE); + } + +again: + r = nm_platform_link_set_address(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self), + addr_bytes, + addr_len); + success = (r >= 0); + if (!success) { + retry_down = + !was_taken_down && r != -NME_PL_NOT_FOUND + && nm_platform_link_is_up(nm_device_get_platform(self), nm_device_get_ip_ifindex(self)); + _NMLOG((retry_down || r == -NME_PL_NOT_FOUND) ? LOGL_DEBUG : LOGL_WARN, + LOGD_DEVICE, + "set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s", + operation, + addr, + detail, + nm_strerror(r), + retry_down ? " (retry with taking down)" : ""); + } else { + /* MAC address successfully changed; update the current MAC to match */ + nm_device_update_hw_address(self); + + if (!_hw_addr_matches(self, addr_bytes, addr_len)) { + gint64 poll_end, now; + + _LOGD(LOGD_DEVICE, + "set-hw-addr: new MAC address %s not successfully %s (%s) (refresh link)", + addr, + operation, + detail); + + /* The platform call indicated success, however the address is not + * as expected. That is either due to a driver issue (brcmfmac, bgo#770456, + * rh#1374023) or a race where externally the MAC address was reset. + * The race is rather unlikely. + * + * The alternative would be to postpone the activation in case the + * MAC address is not yet ready and poll without blocking. However, + * that is rather complicated and it is not expected that this case + * happens for regular drivers. + * Note that brcmfmac can block NetworkManager for 500 msec while + * taking down the device. Let's add another 100 msec to that. + * + * wait/poll up to 100 msec until it changes. */ + + poll_end = nm_utils_get_monotonic_timestamp_usec() + (100 * 1000); + for (;;) { + if (!nm_platform_link_refresh(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self))) + goto handle_fail; + if (!nm_device_update_hw_address(self)) + goto handle_wait; + if (!_hw_addr_matches(self, addr_bytes, addr_len)) + goto handle_fail; + + break; +handle_wait: + now = nm_utils_get_monotonic_timestamp_usec(); + if (now < poll_end) { + g_usleep(NM_MIN(poll_end - now, 500)); + continue; + } +handle_fail: + success = FALSE; + break; + } + } + + if (success) { + retry_down = FALSE; + _LOGI(LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)", operation, addr, detail); + } else { + retry_down = !was_taken_down + && nm_platform_link_is_up(nm_device_get_platform(self), + nm_device_get_ip_ifindex(self)); + + _NMLOG(retry_down ? LOGL_DEBUG : LOGL_WARN, + LOGD_DEVICE, + "set-hw-addr: new MAC address %s not successfully %s (%s)%s", + addr, + operation, + detail, + retry_down ? " (retry with taking down)" : ""); + } + } + + if (retry_down) { + /* changing the MAC address failed, but also the device was up (and we did not yet try to take + * it down). Optimally, we change the MAC address without taking the device down, but some + * devices don't like that. So, retry with taking the device down. */ + retry_down = FALSE; + was_taken_down = TRUE; + nm_device_take_down(self, FALSE); + goto again; + } + + if (was_taken_down) { + if (!nm_device_bring_up(self, TRUE, NULL)) + return FALSE; + } + + return success; +} + +gboolean +nm_device_hw_addr_set(NMDevice *self, const char *addr, const char *detail, gboolean set_permanent) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!addr) + g_return_val_if_reached(FALSE); + + if (set_permanent) { + /* The type is set to PERMANENT by NMDeviceVlan when taking the MAC + * address from the parent and by NMDeviceWifi when setting a random MAC + * address during scanning. + */ + priv->hw_addr_type = HW_ADDR_TYPE_PERMANENT; + } + + return _hw_addr_set(self, addr, "set", detail); +} + +/* + * _hw_addr_get_cloned: + * @self: a #NMDevice + * @connection: a #NMConnection + * @is_wifi: whether the device is Wi-Fi + * @preserve: (out): whether the address must be reset to initial one + * @hwaddr: (out): the cloned MAC address to set on interface + * @hwaddr_type: (out): the type of address to set + * @hwaddr_detail: (out): the detail (origin) of address to set + * @error: (out): on return, an error or %NULL + * + * Computes the MAC to be set on a interface. On success, one of the + * following exclusive conditions are verified: + * + * - @preserve is %TRUE: the address must be reset to the initial one + * - @hwaddr is not %NULL: the given address must be set on the device + * - @hwaddr is %NULL and @preserve is %FALSE: no action needed + * + * Returns: %FALSE in case of error in determining the cloned MAC address, + * %TRUE otherwise + */ +static gboolean +_hw_addr_get_cloned(NMDevice * self, + NMConnection *connection, + gboolean is_wifi, + gboolean * preserve, + char ** hwaddr, + HwAddrType * hwaddr_type, + char ** hwaddr_detail, + GError ** error) +{ + NMDevicePrivate *priv; + gs_free char * addr_setting_free = NULL; + gs_free char * hw_addr_generated = NULL; + gs_free char * generate_mac_address_mask_tmp = NULL; + const char * addr, *addr_setting; + char * addr_out; + HwAddrType type_out; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!connection) + g_return_val_if_reached(FALSE); + + addr = addr_setting = + _prop_get_x_cloned_mac_address(self, connection, is_wifi, &addr_setting_free); + + if (nm_streq(addr, NM_CLONED_MAC_PRESERVE)) { + /* "preserve" means to reset the initial MAC address. */ + NM_SET_OUT(preserve, TRUE); + NM_SET_OUT(hwaddr, NULL); + NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET); + NM_SET_OUT(hwaddr_detail, g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting)); + return TRUE; + } + + if (nm_streq(addr, NM_CLONED_MAC_PERMANENT)) { + gboolean is_fake; + + addr = nm_device_get_permanent_hw_address_full(self, TRUE, &is_fake); + if (is_fake) { + /* Preserve the current address if the permanent address if fake */ + NM_SET_OUT(preserve, TRUE); + NM_SET_OUT(hwaddr, NULL); + NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET); + NM_SET_OUT(hwaddr_detail, + g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting)); + return TRUE; + } else if (!addr) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "failed to retrieve permanent address"); + return FALSE; + } + addr_out = g_strdup(addr); + type_out = HW_ADDR_TYPE_PERMANENT; + } else if (NM_IN_STRSET(addr, NM_CLONED_MAC_RANDOM)) { + if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) { + /* hm, we already use a generate MAC address. Most certainly, that is from the same + * activation request, so we should not create a new random address, instead keep + * the current. */ + goto out_no_action; + } + hw_addr_generated = nm_utils_hw_addr_gen_random_eth( + nm_device_get_initial_hw_address(self), + _prop_get_x_generate_mac_address_mask(self, + connection, + is_wifi, + &generate_mac_address_mask_tmp)); + if (!hw_addr_generated) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "failed to generate %s MAC address", + "random"); + return FALSE; + } + + addr_out = g_steal_pointer(&hw_addr_generated); + type_out = HW_ADDR_TYPE_GENERATED; + } else if (NM_IN_STRSET(addr, NM_CLONED_MAC_STABLE)) { + NMUtilsStableType stable_type; + const char * stable_id; + + if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) { + /* hm, we already use a generate MAC address. Most certainly, that is from the same + * activation request, so let's skip creating the stable address anew. */ + goto out_no_action; + } + + stable_id = _prop_get_connection_stable_id(self, connection, &stable_type); + hw_addr_generated = nm_utils_hw_addr_gen_stable_eth( + stable_type, + stable_id, + nm_device_get_ip_iface(self), + nm_device_get_initial_hw_address(self), + _prop_get_x_generate_mac_address_mask(self, + connection, + is_wifi, + &generate_mac_address_mask_tmp)); + if (!hw_addr_generated) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "failed to generate %s MAC address", + "stable"); + return FALSE; + } + + addr_out = g_steal_pointer(&hw_addr_generated); + type_out = HW_ADDR_TYPE_GENERATED; + } else { + /* this must be a valid address. Otherwise, we shouldn't come here. */ + if (!nm_utils_hwaddr_valid(addr, -1)) + g_return_val_if_reached(FALSE); + + addr_out = g_strdup(addr); + type_out = HW_ADDR_TYPE_EXPLICIT; + } + + NM_SET_OUT(preserve, FALSE); + NM_SET_OUT(hwaddr, addr_out); + NM_SET_OUT(hwaddr_type, type_out); + NM_SET_OUT(hwaddr_detail, g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting)); + return TRUE; +out_no_action: + NM_SET_OUT(preserve, FALSE); + NM_SET_OUT(hwaddr, NULL); + NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET); + NM_SET_OUT(hwaddr_detail, NULL); + return TRUE; +} + +gboolean +nm_device_hw_addr_get_cloned(NMDevice * self, + NMConnection *connection, + gboolean is_wifi, + char ** hwaddr, + gboolean * preserve, + GError ** error) +{ + if (!_hw_addr_get_cloned(self, connection, is_wifi, preserve, hwaddr, NULL, NULL, error)) + return FALSE; + + return TRUE; +} + +gboolean +nm_device_hw_addr_set_cloned(NMDevice *self, NMConnection *connection, gboolean is_wifi) +{ + NMDevicePrivate *priv; + gboolean preserve = FALSE; + gs_free char * hwaddr = NULL; + gs_free char * detail = NULL; + HwAddrType type = HW_ADDR_TYPE_UNSET; + gs_free_error GError *error = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!_hw_addr_get_cloned(self, + connection, + is_wifi, + &preserve, + &hwaddr, + &type, + &detail, + &error)) { + _LOGW(LOGD_DEVICE, "set-hw-addr: %s", error->message); + return FALSE; + } + + if (preserve) + return nm_device_hw_addr_reset(self, detail); + + if (hwaddr) { + priv->hw_addr_type = type; + return _hw_addr_set(self, hwaddr, "set-cloned", detail); + } + + return TRUE; +} + +gboolean +nm_device_hw_addr_reset(NMDevice *self, const char *detail) +{ + NMDevicePrivate *priv; + const char * addr; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->hw_addr_type == HW_ADDR_TYPE_UNSET) + return TRUE; + + priv->hw_addr_type = HW_ADDR_TYPE_UNSET; + addr = nm_device_get_initial_hw_address(self); + if (!addr) { + /* as hw_addr_type is not UNSET, we expect that we can get an + * initial address to which to reset. */ + g_return_val_if_reached(FALSE); + } + + return _hw_addr_set(self, addr, "reset", detail); +} + +const char * +nm_device_get_permanent_hw_address_full(NMDevice *self, + gboolean force_freeze, + gboolean *out_is_fake) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail(NM_IS_DEVICE(self), ({ + NM_SET_OUT(out_is_fake, FALSE); + NULL; + })); + + priv = NM_DEVICE_GET_PRIVATE(self); + + if (!priv->hw_addr_perm && force_freeze) { + /* somebody requests a permanent MAC address, but we don't have it set + * yet. We cannot delay it any longer and try to get it without waiting + * for UDEV. */ + nm_device_update_permanent_hw_address(self, TRUE); + } + + NM_SET_OUT(out_is_fake, priv->hw_addr_perm && priv->hw_addr_perm_fake); + return priv->hw_addr_perm; +} + +const char * +nm_device_get_permanent_hw_address(NMDevice *self) +{ + return nm_device_get_permanent_hw_address_full(self, TRUE, NULL); +} + +const char * +nm_device_get_initial_hw_address(NMDevice *self) +{ + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + + return NM_DEVICE_GET_PRIVATE(self)->hw_addr_initial; +} + +/** + * nm_device_spec_match_list: + * @self: an #NMDevice + * @specs: (element-type utf8): a list of device specs + * + * Checks if @self matches any of the specifications in @specs. The + * currently-supported spec types are: + * + * "mac:00:11:22:33:44:55" - matches a device with the given + * hardware address + * + * "interface-name:foo0" - matches a device with the given + * interface name + * + * "s390-subchannels:00.11.22" - matches a device with the given + * z/VM / s390 subchannels. + * + * "*" - matches any device + * + * Returns: #TRUE if @self matches one of the specs in @specs + */ +gboolean +nm_device_spec_match_list(NMDevice *self, const GSList *specs) +{ + return nm_device_spec_match_list_full(self, specs, FALSE); +} + +int +nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value) +{ + NMDeviceClass * klass; + NMMatchSpecMatchType m; + const char * hw_address = NULL; + gboolean is_fake; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + klass = NM_DEVICE_GET_CLASS(self); + hw_address = nm_device_get_permanent_hw_address_full( + self, + !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT), + &is_fake); + + m = nm_match_spec_device(specs, + nm_device_get_iface(self), + nm_device_get_type_description(self), + nm_device_get_driver(self), + nm_device_get_driver_version(self), + is_fake ? NULL : hw_address, + klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL, + nm_dhcp_manager_get_config(nm_dhcp_manager_get())); + + switch (m) { + case NM_MATCH_SPEC_MATCH: + return TRUE; + case NM_MATCH_SPEC_NEG_MATCH: + return FALSE; + case NM_MATCH_SPEC_NO_MATCH: + return no_match_value; + } + nm_assert_not_reached(); + return no_match_value; +} + +guint +nm_device_get_supplicant_timeout(NMDevice *self) +{ + NMConnection * connection; + NMSetting8021x *s_8021x; + int timeout; +#define SUPPLICANT_DEFAULT_TIMEOUT 25 + + g_return_val_if_fail(NM_IS_DEVICE(self), SUPPLICANT_DEFAULT_TIMEOUT); + + connection = nm_device_get_applied_connection(self); + + g_return_val_if_fail(connection, SUPPLICANT_DEFAULT_TIMEOUT); + + s_8021x = nm_connection_get_setting_802_1x(connection); + if (s_8021x) { + timeout = nm_setting_802_1x_get_auth_timeout(s_8021x); + if (timeout > 0) + return timeout; + } + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("802-1x.auth-timeout"), + self, + 1, + G_MAXINT32, + SUPPLICANT_DEFAULT_TIMEOUT); +} + +gboolean +nm_device_auth_retries_try_next(NMDevice *self) +{ + NMDevicePrivate * priv; + NMSettingConnection *s_con; + int auth_retries; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + auth_retries = priv->auth_retries; + + if (G_UNLIKELY(auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) { + auth_retries = -1; + + s_con = nm_device_get_applied_setting(self, NM_TYPE_SETTING_CONNECTION); + if (s_con) + auth_retries = nm_setting_connection_get_auth_retries(s_con); + + if (auth_retries == -1) { + auth_retries = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.auth-retries"), + self, + -1, + G_MAXINT32, + -1); + } + + if (auth_retries == 0) + auth_retries = NM_DEVICE_AUTH_RETRIES_INFINITY; + else if (auth_retries == -1) + auth_retries = NM_DEVICE_AUTH_RETRIES_DEFAULT; + else + nm_assert(auth_retries > 0); + + priv->auth_retries = auth_retries; + } + + if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY) + return TRUE; + if (auth_retries <= 0) { + nm_assert(auth_retries == 0); + return FALSE; + } + priv->auth_retries--; + return TRUE; +} + +static void +hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data) +{ + HostnameResolver *resolver; + NMDevice * self; + gs_free char * hostname = NULL; + gs_free char * addr_str = NULL; + gs_free_error GError *error = NULL; + + hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + resolver = user_data; + self = resolver->device; + resolver->state = RESOLVER_DONE; + resolver->hostname = g_strdup(hostname); + + _LOGD(LOGD_DNS, + "hostname-from-dns: lookup done for %s, result %s%s%s", + (addr_str = g_inet_address_to_string(resolver->address)), + NM_PRINT_FMT_QUOTE_STRING(hostname)); + + nm_clear_g_cancellable(&resolver->cancellable); + g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0); +} + +static gboolean +hostname_dns_address_timeout(gpointer user_data) +{ + HostnameResolver *resolver = user_data; + NMDevice * self = resolver->device; + + g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE); + + nm_assert(resolver->state == RESOLVER_WAIT_ADDRESS); + nm_assert(!resolver->address); + nm_assert(!resolver->cancellable); + + _LOGT(LOGD_DNS, + "hostname-from-dns: timed out while waiting IPv%c address", + nm_utils_addr_family_to_char(resolver->addr_family)); + + resolver->timeout_id = 0; + resolver->state = RESOLVER_DONE; + g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0); + + return G_SOURCE_REMOVE; +} + +static const char * +_resolver_state_to_string(ResolverState state) +{ + switch (state) { + case RESOLVER_WAIT_ADDRESS: + return "wait-address"; + case RESOLVER_IN_PROGRESS: + return "in-progress"; + case RESOLVER_DONE: + return "done"; + default: + nm_assert_not_reached(); + return "unknown"; + } +} + +void +nm_device_clear_dns_lookup_data(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + guint i; + + for (i = 0; i < 2; i++) + nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free); +} + +/* return value is valid only immediately */ +const char * +nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait) +{ + NMDevicePrivate * priv; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + HostnameResolver *resolver; + NMIPConfig * ip_config; + const char * method; + gboolean address_changed = FALSE; + gs_unref_object GInetAddress *new_address = NULL; + + g_return_val_if_fail(NM_IS_DEVICE(self), NULL); + priv = NM_DEVICE_GET_PRIVATE(self); + + /* If the device is not supposed to have addresses, + * return an immediate empty result.*/ + if (!nm_device_get_applied_connection(self)) { + NM_SET_OUT(out_wait, FALSE); + return NULL; + } + + method = nm_device_get_effective_ip_config_method(self, addr_family); + if (IS_IPv4) { + if (NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) { + nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free); + NM_SET_OUT(out_wait, FALSE); + return NULL; + } + } else { + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { + nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free); + NM_SET_OUT(out_wait, FALSE); + return NULL; + } + } + + resolver = priv->hostname_resolver_x[IS_IPv4]; + if (!resolver) { + resolver = g_slice_new(HostnameResolver); + *resolver = (HostnameResolver){ + .resolver = g_resolver_get_default(), + .device = self, + .addr_family = addr_family, + .state = RESOLVER_WAIT_ADDRESS, + }; + priv->hostname_resolver_x[IS_IPv4] = resolver; + } + + /* Determine the first address of the interface and + * whether it changed from the previous lookup */ + ip_config = priv->ip_config_x[IS_IPv4]; + if (ip_config) { + const NMPlatformIPAddress *addr; + + addr = nm_ip_config_get_first_address(ip_config); + if (addr) { + new_address = g_inet_address_new_from_bytes(addr->address_ptr, + IS_IPv4 ? G_SOCKET_FAMILY_IPV4 + : G_SOCKET_FAMILY_IPV6); + } + } + + if (new_address && resolver->address) { + if (!g_inet_address_equal(new_address, resolver->address)) + address_changed = TRUE; + } else if (new_address != resolver->address) + address_changed = TRUE; + + { + gs_free char *old_str = NULL; + gs_free char *new_str = NULL; + + _LOGT(LOGD_DNS, + "hostname-from-dns: ipv%c resolver state %s, old address %s, new address %s", + nm_utils_addr_family_to_char(resolver->addr_family), + _resolver_state_to_string(resolver->state), + resolver->address ? (old_str = g_inet_address_to_string(resolver->address)) + : "(null)", + new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)"); + } + + /* In every state, if the address changed, we restart + * the resolution with the new address */ + if (address_changed) { + nm_clear_g_cancellable(&resolver->cancellable); + g_clear_object(&resolver->address); + resolver->state = RESOLVER_WAIT_ADDRESS; + } + + if (address_changed && new_address) { + gs_free char *str = NULL; + + _LOGT(LOGD_DNS, + "hostname-from-dns: starting lookup for address %s", + (str = g_inet_address_to_string(new_address))); + + resolver->state = RESOLVER_IN_PROGRESS; + resolver->cancellable = g_cancellable_new(); + resolver->address = g_steal_pointer(&new_address); + g_resolver_lookup_by_address_async(resolver->resolver, + resolver->address, + resolver->cancellable, + hostname_dns_lookup_callback, + resolver); + nm_clear_g_source(&resolver->timeout_id); + } + + switch (resolver->state) { + case RESOLVER_WAIT_ADDRESS: + if (!resolver->timeout_id) + resolver->timeout_id = g_timeout_add(30000, hostname_dns_address_timeout, resolver); + NM_SET_OUT(out_wait, TRUE); + return NULL; + case RESOLVER_IN_PROGRESS: + NM_SET_OUT(out_wait, TRUE); + return NULL; + case RESOLVER_DONE: + NM_SET_OUT(out_wait, FALSE); + return resolver->hostname; + } + + return nm_assert_unreachable_val(NULL); +} + +/*****************************************************************************/ + +static const char * +_activation_func_to_string(ActivationHandleFunc func) +{ +#define FUNC_TO_STRING_CHECK_AND_RETURN(func, f) \ + G_STMT_START \ + { \ + if ((func) == (f)) \ + return #f; \ + } \ + G_STMT_END + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage1_device_prepare); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage2_device_config); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage3_ip_config_start); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_4); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_6); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_4); + FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_6); + g_return_val_if_reached("unknown"); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDevice * self = NM_DEVICE(object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + GVariantBuilder array_builder; + + switch (prop_id) { + case PROP_UDI: + /* UDI is (depending on the device type) a path to sysfs and can contain + * non-UTF-8. + * ip link add name $'d\xccf\\c' type dummy */ + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->udi, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE)); + break; + case PROP_PATH: + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->path, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE)); + break; + case PROP_IFACE: + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->iface, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + break; + case PROP_IP_IFACE: + if (ip_config_valid(priv->state)) { + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(nm_device_get_ip_iface(self), + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + } else + g_value_set_string(value, NULL); + break; + case PROP_IFINDEX: + g_value_set_int(value, priv->ifindex); + break; + case PROP_DRIVER: + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->driver, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + break; + case PROP_DRIVER_VERSION: + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->driver_version, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + break; + case PROP_FIRMWARE_VERSION: + g_value_take_string( + value, + nm_utils_str_utf8safe_escape_cp(priv->firmware_version, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + break; + case PROP_CAPABILITIES: + g_value_set_uint(value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK)); + break; + case PROP_IP4_ADDRESS: + g_value_set_variant(value, nm_g_variant_singleton_u_0()); + break; + case PROP_CARRIER: + g_value_set_boolean(value, priv->carrier); + break; + case PROP_MTU: + g_value_set_uint(value, priv->mtu); + break; + case PROP_IP4_CONFIG: + nm_dbus_utils_g_value_set_object_path(value, + ip_config_valid(priv->state) ? priv->ip_config_4 + : NULL); + break; + case PROP_DHCP4_CONFIG: + nm_dbus_utils_g_value_set_object_path( + value, + ip_config_valid(priv->state) ? priv->dhcp_data_4.config : NULL); + break; + case PROP_IP6_CONFIG: + nm_dbus_utils_g_value_set_object_path(value, + ip_config_valid(priv->state) ? priv->ip_config_6 + : NULL); + break; + case PROP_DHCP6_CONFIG: + nm_dbus_utils_g_value_set_object_path( + value, + ip_config_valid(priv->state) ? priv->dhcp_data_6.config : NULL); + break; + case PROP_STATE: + g_value_set_uint(value, priv->state); + break; + case PROP_STATE_REASON: + g_value_take_variant(value, g_variant_new("(uu)", priv->state, priv->state_reason)); + break; + case PROP_ACTIVE_CONNECTION: + g_value_set_string(value, nm_dbus_track_obj_path_get(&priv->act_request)); + break; + case PROP_DEVICE_TYPE: + g_value_set_uint(value, priv->type); + break; + case PROP_LINK_TYPE: + g_value_set_uint(value, priv->link_type); + break; + case PROP_MANAGED: + /* The managed state exposed on D-Bus only depends on the current device state alone. */ + g_value_set_boolean(value, nm_device_get_state(self) > NM_DEVICE_STATE_UNMANAGED); + break; + case PROP_AUTOCONNECT: + g_value_set_boolean( + value, + nm_device_autoconnect_blocked_get(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL) ? FALSE + : TRUE); + break; + case PROP_FIRMWARE_MISSING: + g_value_set_boolean(value, priv->firmware_missing); + break; + case PROP_NM_PLUGIN_MISSING: + g_value_set_boolean(value, priv->nm_plugin_missing); + break; + case PROP_TYPE_DESC: + g_value_set_string(value, priv->type_desc); + break; + case PROP_RFKILL_TYPE: + g_value_set_uint(value, priv->rfkill_type); + break; + case PROP_AVAILABLE_CONNECTIONS: + nm_dbus_utils_g_value_set_object_path_from_hash(value, priv->available_connections, TRUE); + break; + case PROP_PHYSICAL_PORT_ID: + g_value_set_string(value, priv->physical_port_id); + break; + case PROP_MASTER: + g_value_set_object(value, nm_device_get_master(self)); + break; + case PROP_PARENT: + g_value_set_string(value, nm_dbus_track_obj_path_get(&priv->parent_device)); + break; + case PROP_HW_ADDRESS: + g_value_set_string(value, priv->hw_addr); + break; + case PROP_PERM_HW_ADDRESS: + { + const char *perm_hw_addr; + gboolean perm_hw_addr_is_fake; + + perm_hw_addr = nm_device_get_permanent_hw_address_full(self, FALSE, &perm_hw_addr_is_fake); + /* this property is exposed on D-Bus for NMDeviceEthernet and NMDeviceWifi. */ + g_value_set_string(value, perm_hw_addr && !perm_hw_addr_is_fake ? perm_hw_addr : NULL); + break; + } + case PROP_HAS_PENDING_ACTION: + g_value_set_boolean(value, nm_device_has_pending_action(self)); + break; + case PROP_METERED: + g_value_set_uint(value, priv->metered); + break; + case PROP_LLDP_NEIGHBORS: + if (priv->lldp_listener) + g_value_set_variant(value, nm_lldp_listener_get_neighbors(priv->lldp_listener)); + else { + g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}")); + g_value_take_variant(value, g_variant_builder_end(&array_builder)); + } + break; + case PROP_REAL: + g_value_set_boolean(value, nm_device_is_real(self)); + break; + case PROP_SLAVES: + { + CList *slave_iter; + char **slave_list; + gsize i, n; + + n = c_list_length(&priv->slaves); + slave_list = g_new(char *, n + 1); + i = 0; + c_list_for_each (slave_iter, &priv->slaves) { + SlaveInfo * info = c_list_entry(slave_iter, SlaveInfo, lst_slave); + const char *path; + + if (!NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved) + continue; + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(info->slave)); + if (path) + slave_list[i++] = g_strdup(path); + } + nm_assert(i <= n); + slave_list[i] = NULL; + g_value_take_boxed(value, slave_list); + break; + } + case PROP_STATISTICS_REFRESH_RATE_MS: + g_value_set_uint(value, priv->stats.refresh_rate_ms); + break; + case PROP_STATISTICS_TX_BYTES: + g_value_set_uint64(value, priv->stats.tx_bytes); + break; + case PROP_STATISTICS_RX_BYTES: + g_value_set_uint64(value, priv->stats.rx_bytes); + break; + case PROP_IP4_CONNECTIVITY: + g_value_set_uint(value, priv->concheck_x[1].state); + break; + case PROP_IP6_CONNECTIVITY: + g_value_set_uint(value, priv->concheck_x[0].state); + break; + case PROP_INTERFACE_FLAGS: + g_value_set_uint(value, priv->interface_flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDevice * self = (NMDevice *) object; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_UDI: + /* construct-only */ + priv->udi = g_value_dup_string(value); + break; + case PROP_IFACE: + /* construct-only */ + priv->iface_ = g_value_dup_string(value); + break; + case PROP_DRIVER: + /* construct-only */ + priv->driver = g_value_dup_string(value); + break; + case PROP_MANAGED: + /* via D-Bus */ + if (nm_device_is_real(self)) { + gboolean managed; + NMDeviceStateReason reason; + + managed = g_value_get_boolean(value); + if (managed) { + reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED; + if (NM_IN_SET_TYPED(NMDeviceSysIfaceState, + priv->sys_iface_state, + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_REMOVED)) + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_ASSUME); + } else { + reason = NM_DEVICE_STATE_REASON_REMOVED; + nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_REMOVED); + } + nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_USER_EXPLICIT, !managed, reason); + } + break; + case PROP_AUTOCONNECT: + /* via D-Bus */ + if (g_value_get_boolean(value)) + nm_device_autoconnect_blocked_unset(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL); + else + nm_device_autoconnect_blocked_set(self, NM_DEVICE_AUTOCONNECT_BLOCKED_USER); + break; + case PROP_NM_PLUGIN_MISSING: + /* construct-only */ + priv->nm_plugin_missing = g_value_get_boolean(value); + break; + case PROP_DEVICE_TYPE: + /* construct-only */ + nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN); + priv->type = g_value_get_uint(value); + nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN); + nm_assert(priv->type <= NM_DEVICE_TYPE_VRF); + break; + case PROP_LINK_TYPE: + /* construct-only */ + nm_assert(priv->link_type == NM_LINK_TYPE_NONE); + priv->link_type = g_value_get_uint(value); + break; + case PROP_TYPE_DESC: + /* construct-only */ + priv->type_desc = g_value_dup_string(value); + break; + case PROP_RFKILL_TYPE: + /* construct-only */ + priv->rfkill_type = g_value_get_uint(value); + break; + case PROP_PERM_HW_ADDRESS: + /* construct-only */ + priv->hw_addr_perm = g_value_dup_string(value); + break; + case PROP_STATISTICS_REFRESH_RATE_MS: + /* via D-Bus */ + _stats_set_refresh_rate(self, g_value_get_uint(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_init(NMDevice *self) +{ + NMDevicePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DEVICE, NMDevicePrivate); + + self->_priv = priv; + + c_list_init(&priv->concheck_lst_head); + c_list_init(&self->devices_lst); + c_list_init(&priv->slaves); + + priv->concheck_x[0].state = NM_CONNECTIVITY_UNKNOWN; + priv->concheck_x[1].state = NM_CONNECTIVITY_UNKNOWN; + + nm_dbus_track_obj_path_init(&priv->parent_device, G_OBJECT(self), obj_properties[PROP_PARENT]); + nm_dbus_track_obj_path_init(&priv->act_request, + G_OBJECT(self), + obj_properties[PROP_ACTIVE_CONNECTION]); + + priv->netns = g_object_ref(NM_NETNS_GET); + + priv->autoconnect_blocked_flags = DEFAULT_AUTOCONNECT ? NM_DEVICE_AUTOCONNECT_BLOCKED_NONE + : NM_DEVICE_AUTOCONNECT_BLOCKED_USER; + + priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET; + priv->type = NM_DEVICE_TYPE_UNKNOWN; + priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED; + priv->state = NM_DEVICE_STATE_UNMANAGED; + priv->state_reason = NM_DEVICE_STATE_REASON_NONE; + priv->rfkill_type = RFKILL_TYPE_UNKNOWN; + priv->unmanaged_flags = NM_UNMANAGED_PLATFORM_INIT; + priv->unmanaged_mask = priv->unmanaged_flags; + priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); + priv->ip6_saved_properties = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); + priv->sys_iface_state_ = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL; + + priv->v4_commit_first_time = TRUE; + priv->v6_commit_first_time = TRUE; +} + +static GObject * +constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params) +{ + GObject * object; + GObjectClass * klass; + NMDevice * self; + NMDevicePrivate * priv; + const NMPlatformLink *pllink; + + klass = G_OBJECT_CLASS(nm_device_parent_class); + object = klass->constructor(type, n_construct_params, construct_params); + if (!object) + return NULL; + + self = NM_DEVICE(object); + priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->iface && G_LIKELY(!nm_utils_get_testing())) { + pllink = nm_platform_link_get_by_ifname(nm_device_get_platform(self), priv->iface); + + if (pllink && link_type_compatible(self, pllink->type, NULL, NULL)) { + _set_ifindex(self, pllink->ifindex, FALSE); + priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP); + } + } + + if (priv->hw_addr_perm) { + guint8 buf[NM_UTILS_HWADDR_LEN_MAX]; + gsize l; + + if (!_nm_utils_hwaddr_aton(priv->hw_addr_perm, buf, sizeof(buf), &l)) { + nm_clear_g_free(&priv->hw_addr_perm); + g_return_val_if_reached(object); + } + + priv->hw_addr_len_ = l; + priv->hw_addr = nm_utils_hwaddr_ntoa(buf, l); + _LOGT(LOGD_DEVICE, "hw-addr: has permanent hw-address '%s'", priv->hw_addr_perm); + } + + return object; +} + +static void +constructed(GObject *object) +{ + NMDevice * self = NM_DEVICE(object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform * platform; + + if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities) + priv->capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self); + + /* Watch for external IP config changes */ + platform = nm_device_get_platform(self); + g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + G_CALLBACK(device_ipx_changed), + self); + g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK(device_ipx_changed), + self); + g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + G_CALLBACK(device_ipx_changed), + self); + g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + G_CALLBACK(device_ipx_changed), + self); + g_signal_connect(platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK(link_changed_cb), self); + + priv->manager = g_object_ref(NM_MANAGER_GET); + priv->settings = g_object_ref(NM_SETTINGS_GET); + + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + G_CALLBACK(cp_connection_added), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK(cp_connection_updated), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK(cp_connection_removed), + self); + + G_OBJECT_CLASS(nm_device_parent_class)->constructed(object); + + _LOGD(LOGD_DEVICE, "constructed (%s)", G_OBJECT_TYPE_NAME(self)); +} + +static void +dispose(GObject *object) +{ + NMDevice * self = NM_DEVICE(object); + NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); + NMPlatform * platform; + NMDeviceConnectivityHandle *con_handle; + gs_free_error GError *cancelled_error = NULL; + + _LOGD(LOGD_DEVICE, "disposing"); + + nm_assert(c_list_is_empty(&self->devices_lst)); + + while ((con_handle = c_list_first_entry(&priv->concheck_lst_head, + NMDeviceConnectivityHandle, + concheck_lst))) { + if (!cancelled_error) + nm_utils_error_set_cancelled(&cancelled_error, FALSE, "NMDevice"); + concheck_handle_complete(con_handle, cancelled_error); + } + + nm_clear_g_cancellable(&priv->deactivating_cancellable); + + nm_device_assume_state_reset(self); + + _parent_set_ifindex(self, 0, FALSE); + + platform = nm_device_get_platform(self); + g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(device_ipx_changed), self); + g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(link_changed_cb), self); + + arp_cleanup(self); + + nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id); + nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id); + + dispatcher_cleanup(self); + + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + + _cleanup_generic_pre(self, CLEANUP_TYPE_KEEP); + + nm_assert(c_list_is_empty(&priv->slaves)); + + /* Let the kernel manage IPv6LL again */ + set_nm_ipv6ll(self, FALSE); + + _cleanup_generic_post(self, CLEANUP_TYPE_KEEP); + + nm_assert(priv->master_ready_id == 0); + + g_hash_table_remove_all(priv->ip6_saved_properties); + + nm_clear_g_source(&priv->recheck_assume_id); + nm_clear_g_source(&priv->recheck_available.call_id); + + nm_clear_g_source(&priv->check_delete_unrealized_id); + + nm_clear_g_source(&priv->stats.timeout_id); + + carrier_disconnected_action_cancel(self); + + _set_ifindex(self, 0, FALSE); + _set_ifindex(self, 0, TRUE); + + if (priv->settings) { + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_added, self); + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_updated, self); + g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_removed, self); + } + + available_connections_del_all(self); + + if (nm_clear_g_source(&priv->carrier_wait_id)) + nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE); + + _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + + nm_clear_g_source(&priv->device_link_changed_id); + nm_clear_g_source(&priv->device_ip_link_changed_id); + + if (priv->lldp_listener) { + g_signal_handlers_disconnect_by_func(priv->lldp_listener, + G_CALLBACK(lldp_neighbors_changed), + self); + nm_lldp_listener_stop(priv->lldp_listener); + g_clear_object(&priv->lldp_listener); + } + + nm_clear_g_source(&priv->concheck_x[0].p_cur_id); + nm_clear_g_source(&priv->concheck_x[1].p_cur_id); + + nm_assert(!priv->sriov.pending); + if (priv->sriov.next) { + nm_g_slice_free(priv->sriov.next); + priv->sriov.next = NULL; + } + + G_OBJECT_CLASS(nm_device_parent_class)->dispose(object); + + if (nm_clear_g_source(&priv->queued_state.id)) { + /* FIXME: we'd expect the queud_state to be already cleared and this statement + * not being necessary. Add this check here to hopefully investigate crash + * rh#1270247. */ + g_return_if_reached(); + } +} + +static void +finalize(GObject *object) +{ + NMDevice * self = NM_DEVICE(object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + _LOGD(LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME(self)); + + g_free(priv->hw_addr); + g_free(priv->hw_addr_perm); + g_free(priv->hw_addr_initial); + g_slist_free(priv->pending_actions); + g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); + nm_clear_g_free(&priv->physical_port_id); + g_free(priv->udi); + g_free(priv->path); + g_free(priv->iface_); + g_free(priv->ip_iface_); + g_free(priv->driver); + g_free(priv->driver_version); + g_free(priv->firmware_version); + g_free(priv->type_desc); + g_free(priv->dhcp_anycast_address); + g_free(priv->current_stable_id); + + g_hash_table_unref(priv->ip6_saved_properties); + g_hash_table_unref(priv->available_connections); + + nm_dbus_track_obj_path_deinit(&priv->parent_device); + nm_dbus_track_obj_path_deinit(&priv->act_request); + + G_OBJECT_CLASS(nm_device_parent_class)->finalize(object); + + /* for testing, NMDeviceTest does not invoke NMDevice::constructed, + * and thus @settings might be unset. */ + nm_g_object_unref(priv->settings); + nm_g_object_unref(priv->manager); + + nm_g_object_unref(priv->concheck_mgr); + + g_object_unref(priv->netns); +} + +/*****************************************************************************/ + +static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "StateChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("new_state", "u"), + NM_DEFINE_GDBUS_ARG_INFO("old_state", "u"), + NM_DEFINE_GDBUS_ARG_INFO("reason", "u"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_device = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "Reapply", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ), + .handle = impl_device_reapply, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetAppliedConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"), ), ), + .handle = impl_device_get_applied_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Disconnect", ), + .handle = impl_device_disconnect, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ), + .handle = impl_device_delete, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_state_changed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Udi", "s", NM_DEVICE_UDI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Path", "s", NM_DEVICE_PATH), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Interface", "s", NM_DEVICE_IFACE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IpInterface", + "s", + NM_DEVICE_IP_IFACE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Driver", "s", NM_DEVICE_DRIVER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DriverVersion", + "s", + NM_DEVICE_DRIVER_VERSION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareVersion", + "s", + NM_DEVICE_FIRMWARE_VERSION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Capabilities", + "u", + NM_DEVICE_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Address", + "u", + NM_DEVICE_IP4_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", "u", NM_DEVICE_STATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("StateReason", + "(uu)", + NM_DEVICE_STATE_REASON), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveConnection", + "o", + NM_DEVICE_ACTIVE_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Config", + "o", + NM_DEVICE_IP4_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp4Config", + "o", + NM_DEVICE_DHCP4_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip6Config", + "o", + NM_DEVICE_IP6_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp6Config", + "o", + NM_DEVICE_DHCP6_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Managed", + "b", + NM_DEVICE_MANAGED, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + NM_AUDIT_OP_DEVICE_MANAGED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Autoconnect", + "b", + NM_DEVICE_AUTOCONNECT, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + NM_AUDIT_OP_DEVICE_AUTOCONNECT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareMissing", + "b", + NM_DEVICE_FIRMWARE_MISSING), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NmPluginMissing", + "b", + NM_DEVICE_NM_PLUGIN_MISSING), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DeviceType", + "u", + NM_DEVICE_DEVICE_TYPE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AvailableConnections", + "ao", + NM_DEVICE_AVAILABLE_CONNECTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PhysicalPortId", + "s", + NM_DEVICE_PHYSICAL_PORT_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mtu", "u", NM_DEVICE_MTU), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Metered", "u", NM_DEVICE_METERED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("LldpNeighbors", + "aa{sv}", + NM_DEVICE_LLDP_NEIGHBORS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Real", "b", NM_DEVICE_REAL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Connectivity", + "u", + NM_DEVICE_IP4_CONNECTIVITY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip6Connectivity", + "u", + NM_DEVICE_IP6_CONNECTIVITY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("InterfaceFlags", + "u", + NM_DEVICE_INTERFACE_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), ), ), +}; + +const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_STATISTICS, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE( + "RefreshRateMs", + "u", + NM_DEVICE_STATISTICS_REFRESH_RATE_MS, + NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, + NM_AUDIT_OP_STATISTICS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("TxBytes", + "t", + NM_DEVICE_STATISTICS_TX_BYTES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("RxBytes", + "t", + NM_DEVICE_STATISTICS_RX_BYTES), ), ), +}; + +static void +nm_device_class_init(NMDeviceClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMDevicePrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/Devices"); + dbus_object_class->interface_infos = + NM_DBUS_INTERFACE_INFOS(&interface_info_device, &nm_interface_info_device_statistics); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->constructor = constructor; + object_class->constructed = constructed; + + klass->link_changed = link_changed; + + klass->is_available = is_available; + klass->act_stage2_config = act_stage2_config; + klass->act_stage3_ip_config_start = act_stage3_ip_config_start; + klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout; + + klass->get_type_description = get_type_description; + klass->can_auto_connect = can_auto_connect; + klass->can_update_from_platform_link = can_update_from_platform_link; + klass->check_connection_compatible = check_connection_compatible; + klass->check_connection_available = check_connection_available; + klass->can_unmanaged_external_down = can_unmanaged_external_down; + klass->realize_start_notify = realize_start_notify; + klass->unrealize_notify = unrealize_notify; + klass->carrier_changed_notify = carrier_changed_notify; + klass->get_ip_iface_identifier = get_ip_iface_identifier; + klass->unmanaged_on_quit = unmanaged_on_quit; + klass->deactivate_reset_hw_addr = deactivate_reset_hw_addr; + klass->parent_changed_notify = parent_changed_notify; + klass->can_reapply_change = can_reapply_change; + klass->reapply_connection = reapply_connection; + klass->set_platform_mtu = set_platform_mtu; + + obj_properties[PROP_UDI] = + g_param_spec_string(NM_DEVICE_UDI, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_PATH] = g_param_spec_string(NM_DEVICE_PATH, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFACE] = + g_param_spec_string(NM_DEVICE_IFACE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IP_IFACE] = g_param_spec_string(NM_DEVICE_IP_IFACE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DRIVER] = + g_param_spec_string(NM_DEVICE_DRIVER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DRIVER_VERSION] = + g_param_spec_string(NM_DEVICE_DRIVER_VERSION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FIRMWARE_VERSION] = + g_param_spec_string(NM_DEVICE_FIRMWARE_VERSION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_CAP_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CARRIER] = g_param_spec_boolean(NM_DEVICE_CARRIER, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_MTU] = g_param_spec_uint(NM_DEVICE_MTU, + "", + "", + 0, + G_MAXUINT32, + 1500, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IP4_ADDRESS] = + g_param_spec_variant(NM_DEVICE_IP4_ADDRESS, + "", + "", + G_VARIANT_TYPE_UINT32, + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IP4_CONFIG] = + g_param_spec_string(NM_DEVICE_IP4_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DHCP4_CONFIG] = + g_param_spec_string(NM_DEVICE_DHCP4_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IP6_CONFIG] = + g_param_spec_string(NM_DEVICE_IP6_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DHCP6_CONFIG] = + g_param_spec_string(NM_DEVICE_DHCP6_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_STATE] = g_param_spec_uint(NM_DEVICE_STATE, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_STATE_REASON] = + g_param_spec_variant(NM_DEVICE_STATE_REASON, + "", + "", + G_VARIANT_TYPE("(uu)"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ACTIVE_CONNECTION] = + g_param_spec_string(NM_DEVICE_ACTIVE_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DEVICE_TYPE] = + g_param_spec_uint(NM_DEVICE_DEVICE_TYPE, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_LINK_TYPE] = + g_param_spec_uint(NM_DEVICE_LINK_TYPE, + "", + "", + 0, + G_MAXUINT32, + NM_LINK_TYPE_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_MANAGED] = g_param_spec_boolean(NM_DEVICE_MANAGED, + "", + "", + FALSE, + G_PARAM_READWRITE | /* via D-Bus */ + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_AUTOCONNECT] = g_param_spec_boolean(NM_DEVICE_AUTOCONNECT, + "", + "", + DEFAULT_AUTOCONNECT, + G_PARAM_READWRITE | /* via D-Bus */ + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FIRMWARE_MISSING] = + g_param_spec_boolean(NM_DEVICE_FIRMWARE_MISSING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NM_PLUGIN_MISSING] = + g_param_spec_boolean(NM_DEVICE_NM_PLUGIN_MISSING, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_TYPE_DESC] = + g_param_spec_string(NM_DEVICE_TYPE_DESC, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_RFKILL_TYPE] = + g_param_spec_uint(NM_DEVICE_RFKILL_TYPE, + "", + "", + RFKILL_TYPE_WLAN, + RFKILL_TYPE_MAX, + RFKILL_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = g_param_spec_int(NM_DEVICE_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_AVAILABLE_CONNECTIONS] = + g_param_spec_boxed(NM_DEVICE_AVAILABLE_CONNECTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_PHYSICAL_PORT_ID] = + g_param_spec_string(NM_DEVICE_PHYSICAL_PORT_ID, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_MASTER] = g_param_spec_object(NM_DEVICE_MASTER, + "", + "", + NM_TYPE_DEVICE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_PARENT] = g_param_spec_string(NM_DEVICE_PARENT, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_HW_ADDRESS] = + g_param_spec_string(NM_DEVICE_HW_ADDRESS, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_PERM_HW_ADDRESS] = + g_param_spec_string(NM_DEVICE_PERM_HW_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_HAS_PENDING_ACTION] = + g_param_spec_boolean(NM_DEVICE_HAS_PENDING_ACTION, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_METERED] = g_param_spec_uint(NM_DEVICE_METERED, + "", + "", + 0, + G_MAXUINT32, + NM_METERED_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_LLDP_NEIGHBORS] = + g_param_spec_variant(NM_DEVICE_LLDP_NEIGHBORS, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_REAL] = g_param_spec_boolean(NM_DEVICE_REAL, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SLAVES] = g_param_spec_boxed(NM_DEVICE_SLAVES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATISTICS_REFRESH_RATE_MS] = + g_param_spec_uint(NM_DEVICE_STATISTICS_REFRESH_RATE_MS, + "", + "", + 0, + UINT32_MAX, + 0, + G_PARAM_READWRITE | /* via D-Bus */ + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_STATISTICS_TX_BYTES] = + g_param_spec_uint64(NM_DEVICE_STATISTICS_TX_BYTES, + "", + "", + 0, + UINT64_MAX, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_STATISTICS_RX_BYTES] = + g_param_spec_uint64(NM_DEVICE_STATISTICS_RX_BYTES, + "", + "", + 0, + UINT64_MAX, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP4_CONNECTIVITY] = + g_param_spec_uint(NM_DEVICE_IP4_CONNECTIVITY, + "", + "", + NM_CONNECTIVITY_UNKNOWN, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IP6_CONNECTIVITY] = + g_param_spec_uint(NM_DEVICE_IP6_CONNECTIVITY, + "", + "", + NM_CONNECTIVITY_UNKNOWN, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_INTERFACE_FLAGS] = + g_param_spec_uint(NM_DEVICE_INTERFACE_FLAGS, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[STATE_CHANGED] = g_signal_new(NM_DEVICE_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(NMDeviceClass, state_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); + + signals[AUTOCONNECT_ALLOWED] = g_signal_new(NM_DEVICE_AUTOCONNECT_ALLOWED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + autoconnect_allowed_accumulator, + NULL, + NULL, + G_TYPE_BOOLEAN, + 0); + + signals[IP4_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP4_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_OBJECT, + G_TYPE_OBJECT); + + signals[IP6_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP6_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_OBJECT, + G_TYPE_OBJECT); + + signals[IP6_PREFIX_DELEGATED] = g_signal_new(NM_DEVICE_IP6_PREFIX_DELEGATED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[IP6_SUBNET_NEEDED] = g_signal_new(NM_DEVICE_IP6_SUBNET_NEEDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[REMOVED] = g_signal_new(NM_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[RECHECK_AUTO_ACTIVATE] = g_signal_new(NM_DEVICE_RECHECK_AUTO_ACTIVATE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[RECHECK_ASSUME] = g_signal_new(NM_DEVICE_RECHECK_ASSUME, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[DNS_LOOKUP_DONE] = g_signal_new(NM_DEVICE_DNS_LOOKUP_DONE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} + +/* Connection defaults from plugins */ +NM_CON_DEFAULT_NOP("cdma.mtu"); +NM_CON_DEFAULT_NOP("gsm.mtu"); +NM_CON_DEFAULT_NOP("wifi.ap-isolation"); +NM_CON_DEFAULT_NOP("wifi.powersave"); +NM_CON_DEFAULT_NOP("wifi.wake-on-wlan"); +NM_CON_DEFAULT_NOP("wifi-sec.pmf"); +NM_CON_DEFAULT_NOP("wifi-sec.fils"); diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h new file mode 100644 index 0000000..72777d0 --- /dev/null +++ b/src/core/devices/nm-device.h @@ -0,0 +1,874 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_H__ +#define __NETWORKMANAGER_DEVICE_H__ + +#include + +#include "nm-setting-connection.h" +#include "nm-dbus-object.h" +#include "nm-dbus-interface.h" +#include "nm-connection.h" +#include "nm-rfkill-manager.h" +#include "NetworkManagerUtils.h" + +typedef enum _nm_packed { + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME, + NM_DEVICE_SYS_IFACE_STATE_MANAGED, + + /* the REMOVED state applies when the device is manually set to unmanaged + * or the link was externally removed. In both cases, we move the device + * to UNMANAGED state, without touching the link -- be it, because the link + * is already gone or because we want to release it (give it up). + */ + NM_DEVICE_SYS_IFACE_STATE_REMOVED, +} NMDeviceSysIfaceState; + +typedef enum { + NM_DEVICE_MTU_SOURCE_NONE, + NM_DEVICE_MTU_SOURCE_PARENT, + NM_DEVICE_MTU_SOURCE_IP_CONFIG, + NM_DEVICE_MTU_SOURCE_CONNECTION, +} NMDeviceMtuSource; + +static inline NMDeviceStateReason +nm_device_state_reason_check(NMDeviceStateReason reason) +{ + /* the device-state-reason serves mostly informational purpose during a state + * change. In some cases however, decisions are made based on the reason. + * I tend to think that interpreting the state reason to derive some behaviors + * is confusing, because the cause and effect are so far apart. + * + * This function is here to mark source that inspects the reason to make + * a decision -- contrary to places that set the reason. Thus, by grepping + * for nm_device_state_reason_check() you can find the "effect" to a certain + * reason. + */ + return reason; +} + +#define NM_PENDING_ACTION_AUTOACTIVATE "autoactivate" +#define NM_PENDING_ACTION_IN_STATE_CHANGE "in-state-change" +#define NM_PENDING_ACTION_RECHECK_AVAILABLE "recheck-available" +#define NM_PENDING_ACTION_CARRIER_WAIT "carrier-wait" +#define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT "waiting-for-supplicant" +#define NM_PENDING_ACTION_WIFI_SCAN "wifi-scan" +#define NM_PENDING_ACTION_WAITING_FOR_COMPANION "waiting-for-companion" +#define NM_PENDING_ACTION_LINK_INIT "link-init" + +#define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-" +#define NM_PENDING_ACTIONPREFIX_ACTIVATION "activation-" + +/* Properties */ +#define NM_DEVICE_UDI "udi" +#define NM_DEVICE_PATH "path" +#define NM_DEVICE_IFACE "interface" +#define NM_DEVICE_IP_IFACE "ip-interface" +#define NM_DEVICE_DRIVER "driver" +#define NM_DEVICE_DRIVER_VERSION "driver-version" +#define NM_DEVICE_FIRMWARE_VERSION "firmware-version" +#define NM_DEVICE_CAPABILITIES "capabilities" +#define NM_DEVICE_CARRIER "carrier" +#define NM_DEVICE_IP4_ADDRESS "ip4-address" +#define NM_DEVICE_IP4_CONFIG "ip4-config" +#define NM_DEVICE_DHCP4_CONFIG "dhcp4-config" +#define NM_DEVICE_IP6_CONFIG "ip6-config" +#define NM_DEVICE_DHCP6_CONFIG "dhcp6-config" +#define NM_DEVICE_STATE "state" +#define NM_DEVICE_STATE_REASON "state-reason" +#define NM_DEVICE_ACTIVE_CONNECTION "active-connection" +#define NM_DEVICE_DEVICE_TYPE "device-type" /* ugh */ +#define NM_DEVICE_LINK_TYPE "link-type" +#define NM_DEVICE_MANAGED "managed" +#define NM_DEVICE_AUTOCONNECT "autoconnect" +#define NM_DEVICE_FIRMWARE_MISSING "firmware-missing" +#define NM_DEVICE_NM_PLUGIN_MISSING "nm-plugin-missing" +#define NM_DEVICE_AVAILABLE_CONNECTIONS "available-connections" +#define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id" +#define NM_DEVICE_MTU "mtu" +#define NM_DEVICE_HW_ADDRESS "hw-address" + +/* "perm-hw-address" is exposed on D-Bus both for NMDeviceEthernet + * and NMDeviceWifi. */ +#define NM_DEVICE_PERM_HW_ADDRESS "perm-hw-address" + +#define NM_DEVICE_METERED "metered" +#define NM_DEVICE_LLDP_NEIGHBORS "lldp-neighbors" +#define NM_DEVICE_REAL "real" + +/* "parent" is exposed on D-Bus by subclasses like NMDeviceIPTunnel */ +#define NM_DEVICE_PARENT "parent" + +/* the "slaves" property is internal in the parent class, but exposed + * by the derived classes NMDeviceBond, NMDeviceBridge, NMDeviceTeam, + * NMDeviceOvsBridge and NMDeviceOvsPort. */ +#define NM_DEVICE_SLAVES "slaves" /* partially internal */ + +#define NM_DEVICE_TYPE_DESC "type-desc" /* Internal only */ +#define NM_DEVICE_RFKILL_TYPE "rfkill-type" /* Internal only */ +#define NM_DEVICE_IFINDEX "ifindex" /* Internal only */ +#define NM_DEVICE_MASTER "master" /* Internal only */ +#define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */ + +/* Internal signals */ +#define NM_DEVICE_DNS_LOOKUP_DONE "dns-lookup-done" +#define NM_DEVICE_IP4_CONFIG_CHANGED "ip4-config-changed" +#define NM_DEVICE_IP6_CONFIG_CHANGED "ip6-config-changed" +#define NM_DEVICE_IP6_PREFIX_DELEGATED "ip6-prefix-delegated" +#define NM_DEVICE_IP6_SUBNET_NEEDED "ip6-subnet-needed" +#define NM_DEVICE_REMOVED "removed" +#define NM_DEVICE_RECHECK_AUTO_ACTIVATE "recheck-auto-activate" +#define NM_DEVICE_RECHECK_ASSUME "recheck-assume" +#define NM_DEVICE_STATE_CHANGED "state-changed" +#define NM_DEVICE_LINK_INITIALIZED "link-initialized" +#define NM_DEVICE_AUTOCONNECT_ALLOWED "autoconnect-allowed" + +#define NM_DEVICE_STATISTICS_REFRESH_RATE_MS "refresh-rate-ms" +#define NM_DEVICE_STATISTICS_TX_BYTES "tx-bytes" +#define NM_DEVICE_STATISTICS_RX_BYTES "rx-bytes" + +#define NM_DEVICE_IP4_CONNECTIVITY "ip4-connectivity" +#define NM_DEVICE_IP6_CONNECTIVITY "ip6-connectivity" +#define NM_DEVICE_INTERFACE_FLAGS "interface-flags" + +#define NM_TYPE_DEVICE (nm_device_get_type()) +#define NM_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE, NMDevice)) +#define NM_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE, NMDeviceClass)) +#define NM_IS_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE)) +#define NM_IS_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE)) +#define NM_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE, NMDeviceClass)) + +typedef enum NMActStageReturn NMActStageReturn; + +/* These flags affect whether a connection is considered available on a device + * (check_connection_available()). The flags should have the meaning of relaxing + * a condition, so that adding a flag might make a connection available that would + * not be available otherwise. Adding a flag should never make a connection + * not available if it would be available otherwise. */ +typedef enum { /*< skip >*/ + NM_DEVICE_CHECK_CON_AVAILABLE_NONE = 0, + + /* since NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST is a collection of flags with more fine grained + * parts, this flag in general indicates that this is a user-request. */ + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST = (1L << 0), + + /* we also consider devices which have no carrier but are still waiting for the driver + * to detect carrier. Usually, such devices are not yet available, however for a user-request + * they are. They might fail later if carrier doesn't come. */ + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER = (1L << 1), + + /* usually, a profile is only available if the Wi-Fi AP is in range. For an + * explicit user request, we also consider profiles for APs that are not (yet) + * visible. */ + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP = (1L << 2), + + /* a device can be marked as unmanaged for various reasons. Some of these reasons + * are authoritative, others not. Non-authoritative reasons can be overruled by + * `nmcli device set $DEVICE managed yes`. Also, for an explicit user activation + * request we may want to consider the device as managed. This flag makes devices + * that are unmanaged appear available. */ + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED = (1L << 3), + + /* a collection of flags, that are commonly set for an explicit user-request. */ + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST = + _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST + | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER + | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP + | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED, + + NM_DEVICE_CHECK_CON_AVAILABLE_ALL = (1L << 4) - 1, +} NMDeviceCheckConAvailableFlags; + +struct _NMDevicePrivate; + +struct _NMDevice { + NMDBusObject parent; + struct _NMDevicePrivate *_priv; + CList devices_lst; +}; + +/* The flags have an relaxing meaning, that means, specifying more flags, can make + * a device appear more available. It can never make a device less available. */ +typedef enum { /*< skip >*/ + NM_DEVICE_CHECK_DEV_AVAILABLE_NONE = 0, + + /* the device is considered available, even if it has no carrier. + * + * For various device types (software devices) we ignore carrier based + * on the type. So, for them, this flag has no effect anyway. */ + _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER = (1L << 0), + + NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST = + _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER, + + NM_DEVICE_CHECK_DEV_AVAILABLE_ALL = (1L << 1) - 1, +} NMDeviceCheckDevAvailableFlags; + +typedef void (*NMDeviceDeactivateCallback)(NMDevice *self, GError *error, gpointer user_data); + +typedef struct _NMDeviceClass { + NMDBusObjectClass parent; + + struct _NMDeviceClass *default_type_description_klass; + const char * default_type_description; + + const char *connection_type_supported; + + /* most device types, can only handle profiles of a particular type. This + * is the connection.type setting, as checked by nm_device_check_connection_compatible() */ + const char *connection_type_check_compatible; + + const NMLinkType *link_types; + + /* if the device MTU is set based on parent's one, this specifies + * a delta in the MTU allowed value due the encapsulation overhead */ + guint16 mtu_parent_delta; + + /* Whether the device type is a master-type. This depends purely on the + * type (NMDeviceClass), not the actual device instance. */ + bool is_master : 1; + + /* Force setting the MTU actually means first setting the MTU + * to (desired_MTU-1) and then setting the desired_MTU + * so that kernel actually applies the MTU, otherwise + * kernel will ignore the request if the link's MTU is the + * same as the desired one. + * + * This is just a workaround made for bridges (ATM) that employ + * a auto-MTU adjust mechanism if no MTU is manually set. + */ + bool mtu_force_set : 1; + + /* Control whether to call stage1 and stage2 callbacks also for assuming + * a device or for external activations. In this case, the callback must + * take care not to touch the device's configuration. */ + bool act_stage1_prepare_also_for_external_or_assume : 1; + bool act_stage2_config_also_for_external_or_assume : 1; + + bool act_stage1_prepare_set_hwaddr_ethernet : 1; + + bool can_reapply_change_ovs_external_ids : 1; + + void (*state_changed)(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason); + + void (*link_changed)(NMDevice *self, const NMPlatformLink *pllink); + + /** + * create_and_realize(): + * @self: the #NMDevice + * @connection: the #NMConnection being activated + * @parent: the parent #NMDevice, if any + * @out_plink: on success, a backing kernel network device if one exists. + * The returned pointer is owned by platform and only valid until the + * next platform operation. + * @error: location to store error, or %NULL + * + * Create any backing resources (kernel devices, etc) required for this + * device to activate @connection. If the device is backed by a kernel + * network device, that device should be returned in @out_plink after + * being created. + * + * Returns: %TRUE on success, %FALSE on error + */ + gboolean (*create_and_realize)(NMDevice * self, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error); + + /** + * realize_start_notify(): + * @self: the #NMDevice + * @pllink: the #NMPlatformLink if backed by a kernel netdevice + * + * Hook for derived classes to be notfied during realize_start_setup() + * and perform additional setup. + * + * The default implementation of NMDevice calls link_changed(). + */ + void (*realize_start_notify)(NMDevice *self, const NMPlatformLink *pllink); + + /** + * unrealize(): + * @self: the #NMDevice + * + * Remove the device backing resources. + */ + gboolean (*unrealize)(NMDevice *self, GError **error); + + /** + * unrealize_notify(): + * @self: the #NMDevice + * + * Hook for derived classes to clear any properties that depend on backing resources + * (kernel devices, etc). This is called by nm_device_unrealize() during unrealization. + */ + void (*unrealize_notify)(NMDevice *self); + + /* Hardware state (IFF_UP) */ + gboolean (*can_unmanaged_external_down)(NMDevice *self); + + /* Carrier state (IFF_LOWER_UP) */ + void (*carrier_changed_notify)(NMDevice *, gboolean carrier); + + gboolean (*get_ip_iface_identifier)(NMDevice *self, NMUtilsIPv6IfaceId *out_iid); + + NMDeviceCapabilities (*get_generic_capabilities)(NMDevice *self); + + gboolean (*is_available)(NMDevice *self, NMDeviceCheckDevAvailableFlags flags); + + gboolean (*get_enabled)(NMDevice *self); + + void (*set_enabled)(NMDevice *self, gboolean enabled); + + /* let the subclass return additional NMPlatformRoutingRule (in form of NMPObject + * pointers) that shall be added to the rules provided by this device. + * The returned GPtrArray will be g_ptr_array_unref()'ed. The subclass may or + * may not keep an additional reference and return this array again and again. */ + GPtrArray *(*get_extra_rules)(NMDevice *self); + + /* allow derived classes to override the result of nm_device_autoconnect_allowed(). + * If the value changes, the class should call nm_device_emit_recheck_auto_activate(), + * which emits NM_DEVICE_RECHECK_AUTO_ACTIVATE signal. */ + gboolean (*get_autoconnect_allowed)(NMDevice *self); + + gboolean (*can_auto_connect)(NMDevice * self, + NMSettingsConnection *sett_conn, + char ** specific_object); + + guint32 (*get_configured_mtu)(NMDevice * self, + NMDeviceMtuSource *out_source, + gboolean * out_force); + + /* allow the subclass to overwrite the routing table. This is mainly useful + * to change from partial mode (route-table=0) to full-sync mode (route-table=254). */ + guint32 (*coerce_route_table)(NMDevice *self, + int addr_family, + guint32 route_table, + gboolean is_user_config); + + const char *(*get_auto_ip_config_method)(NMDevice *self, int addr_family); + + /* Checks whether the connection is compatible with the device using + * only the devices type and characteristics. Does not use any live + * network information like Wi-Fi scan lists etc. + */ + gboolean (*check_connection_compatible)(NMDevice * self, + NMConnection *connection, + GError ** error); + + /* Checks whether the connection is likely available to be activated, + * including any live network information like scan lists. The connection + * is checked against the object defined by @specific_object, if given. + * Returns TRUE if the connection is available; FALSE if not. + * + * The passed @flags affect whether a connection is considered + * available or not. Adding more flags, means the connection is + * *more* available. + * + * Specifying @specific_object can only reduce the availability of a connection. + */ + gboolean (*check_connection_available)(NMDevice * self, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error); + + gboolean (*complete_connection)(NMDevice * self, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error); + + NMActStageReturn (*act_stage1_prepare)(NMDevice *self, NMDeviceStateReason *out_failure_reason); + NMActStageReturn (*act_stage2_config)(NMDevice *self, NMDeviceStateReason *out_failure_reason); + NMActStageReturn (*act_stage3_ip_config_start)(NMDevice * self, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason); + NMActStageReturn (*act_stage4_ip_config_timeout)(NMDevice * self, + int addr_family, + NMDeviceStateReason *out_failure_reason); + + void (*ip4_config_pre_commit)(NMDevice *self, NMIP4Config *config); + + /* Async deactivating (in the DEACTIVATING phase) */ + void (*deactivate_async)(NMDevice * self, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer user_data); + + void (*deactivate_reset_hw_addr)(NMDevice *self); + + /* Sync deactivating (in the DISCONNECTED phase) */ + void (*deactivate)(NMDevice *self); + + const char *(*get_type_description)(NMDevice *self); + + const char *(*get_s390_subchannels)(NMDevice *self); + + /* Update the connection with currently configured L2 settings */ + void (*update_connection)(NMDevice *device, NMConnection *connection); + + gboolean (*master_update_slave_connection)(NMDevice * self, + NMDevice * slave, + NMConnection *connection, + GError ** error); + + gboolean (*enslave_slave)(NMDevice * self, + NMDevice * slave, + NMConnection *connection, + gboolean configure); + + void (*release_slave)(NMDevice *self, NMDevice *slave, gboolean configure); + + void (*parent_changed_notify)(NMDevice *self, + int old_ifindex, + NMDevice *old_parent, + int new_ifindex, + NMDevice *new_parent); + + gboolean (*owns_iface)(NMDevice *self, const char *iface); + + NMConnection *(*new_default_connection)(NMDevice *self); + + gboolean (*unmanaged_on_quit)(NMDevice *self); + + gboolean (*can_reapply_change)(NMDevice * self, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error); + + void (*reapply_connection)(NMDevice *self, NMConnection *con_old, NMConnection *con_new); + + guint32 (*get_dhcp_timeout_for_device)(NMDevice *self, int addr_family); + + gboolean (*get_guessed_metered)(NMDevice *self); + + gboolean (*can_update_from_platform_link)(NMDevice *self, const NMPlatformLink *plink); + + gboolean (*set_platform_mtu)(NMDevice *self, guint32 mtu); + +} NMDeviceClass; + +GType nm_device_get_type(void); + +struct _NMDedupMultiIndex *nm_device_get_multi_index(NMDevice *self); +NMNetns * nm_device_get_netns(NMDevice *self); +NMPlatform * nm_device_get_platform(NMDevice *self); + +const char *nm_device_get_udi(NMDevice *dev); +const char *nm_device_get_iface(NMDevice *dev); + +static inline const char * +_nm_device_get_iface(NMDevice *device) +{ + /* like nm_device_get_iface(), but gracefully accept NULL without + * asserting. */ + return device ? nm_device_get_iface(device) : NULL; +} + +int nm_device_get_ifindex(NMDevice *dev); +gboolean nm_device_is_software(NMDevice *dev); +gboolean nm_device_is_real(NMDevice *dev); +const char * nm_device_get_ip_iface(NMDevice *dev); +const char * nm_device_get_ip_iface_from_platform(NMDevice *dev); +int nm_device_get_ip_ifindex(const NMDevice *dev); +const char * nm_device_get_driver(NMDevice *dev); +const char * nm_device_get_driver_version(NMDevice *dev); +const char * nm_device_get_type_desc(NMDevice *dev); +const char * nm_device_get_type_description(NMDevice *dev); +NMDeviceType nm_device_get_device_type(NMDevice *dev); +NMLinkType nm_device_get_link_type(NMDevice *dev); +NMMetered nm_device_get_metered(NMDevice *dev); + +guint32 nm_device_get_route_table(NMDevice *self, int addr_family); +guint32 nm_device_get_route_metric(NMDevice *dev, int addr_family); + +guint32 nm_device_get_route_metric_default(NMDeviceType device_type); + +const char *nm_device_get_hw_address(NMDevice *dev); +const char *nm_device_get_permanent_hw_address(NMDevice *self); +const char *nm_device_get_permanent_hw_address_full(NMDevice *self, + gboolean force_freeze, + gboolean *out_is_fake); +const char *nm_device_get_initial_hw_address(NMDevice *dev); + +NMProxyConfig *nm_device_get_proxy_config(NMDevice *dev); + +NMDhcpConfig *nm_device_get_dhcp_config(NMDevice *dev, int addr_family); +NMIP4Config * nm_device_get_ip4_config(NMDevice *dev); +void nm_device_replace_vpn4_config(NMDevice *dev, NMIP4Config *old, NMIP4Config *config); + +NMIP6Config *nm_device_get_ip6_config(NMDevice *dev); +void nm_device_replace_vpn6_config(NMDevice *dev, NMIP6Config *old, NMIP6Config *config); + +void nm_device_capture_initial_config(NMDevice *dev); + +int nm_device_parent_get_ifindex(NMDevice *dev); +NMDevice *nm_device_parent_get_device(NMDevice *dev); +void nm_device_parent_set_ifindex(NMDevice *self, int parent_ifindex); +gboolean nm_device_parent_notify_changed(NMDevice *self, + NMDevice *change_candidate, + gboolean device_removed); + +const char *nm_device_parent_find_for_connection(NMDevice * self, + const char *current_setting_parent); + +/* Master */ +gboolean nm_device_is_master(NMDevice *dev); + +/* Slave */ +NMDevice *nm_device_get_master(NMDevice *dev); + +NMActRequest * nm_device_get_act_request(NMDevice *dev); +NMSettingsConnection * nm_device_get_settings_connection(NMDevice *dev); +NMConnection * nm_device_get_settings_connection_get_connection(NMDevice *self); +NMConnection * nm_device_get_applied_connection(NMDevice *dev); +gboolean nm_device_has_unmodified_applied_connection(NMDevice * self, + NMSettingCompareFlags compare_flags); +NMActivationStateFlags nm_device_get_activation_state_flags(NMDevice *self); + +gpointer /* (NMSetting *) */ nm_device_get_applied_setting(NMDevice *dev, GType setting_type); + +void nm_device_removed(NMDevice *self, gboolean unconfigure_ip_config); + +gboolean nm_device_ignore_carrier_by_default(NMDevice *self); + +gboolean nm_device_is_available(NMDevice *dev, NMDeviceCheckDevAvailableFlags flags); +gboolean nm_device_has_carrier(NMDevice *dev); + +NMConnection *nm_device_generate_connection(NMDevice *self, + NMDevice *master, + gboolean *out_maybe_later, + GError ** error); + +gboolean nm_device_master_update_slave_connection(NMDevice * master, + NMDevice * slave, + NMConnection *connection, + GError ** error); + +gboolean +nm_device_can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object); + +gboolean nm_device_complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error); + +gboolean +nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error); + +gboolean nm_device_check_slave_connection_compatible(NMDevice *device, NMConnection *connection); + +gboolean nm_device_unmanage_on_quit(NMDevice *self); + +gboolean nm_device_spec_match_list(NMDevice *device, const GSList *specs); +int nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value); + +gboolean nm_device_is_activating(NMDevice *dev); +gboolean nm_device_autoconnect_allowed(NMDevice *self); + +NMDeviceState nm_device_get_state(NMDevice *device); + +gboolean nm_device_get_enabled(NMDevice *device); + +void nm_device_set_enabled(NMDevice *device, gboolean enabled); + +RfKillType nm_device_get_rfkill_type(NMDevice *device); + +/* IPv6 prefix delegation */ + +void nm_device_request_ip6_prefixes(NMDevice *self, int needed_prefixes); + +gboolean nm_device_needs_ip6_subnet(NMDevice *self); + +void nm_device_use_ip6_subnet(NMDevice *self, const NMPlatformIP6Address *subnet); + +void nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device); + +/** + * NMUnmanagedFlags: + * @NM_UNMANAGED_NONE: placeholder value + * @NM_UNMANAGED_SLEEPING: %TRUE when unmanaged because NM is sleeping. + * @NM_UNMANAGED_QUITTING: %TRUE when unmanaged because NM is shutting down. + * @NM_UNMANAGED_PARENT: %TRUE when unmanaged due to parent device being unmanaged + * @NM_UNMANAGED_BY_TYPE: %TRUE for unmanaging device by type, like loopback. + * @NM_UNMANAGED_PLATFORM_INIT: %TRUE when unmanaged because platform link not + * yet initialized. Unrealized device are also unmanaged for this reason. + * @NM_UNMANAGED_USER_EXPLICIT: %TRUE when unmanaged by explicit user decision + * (e.g. via a D-Bus command) + * @NM_UNMANAGED_USER_SETTINGS: %TRUE when unmanaged by user decision via + * the settings plugin (for example keyfile.unmanaged-devices or ifcfg-rh's + * NM_CONTROLLED=no). Although this is user-configuration (provided from + * the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot + * be overruled and is authoritative. That is because users may depend on + * dropping a ifcfg-rh file to ensure the device is unmanaged. + * @NM_UNMANAGED_USER_CONF: %TRUE when unmanaged by user decision via + * the NetworkManager.conf ("unmanaged" in the [device] section). + * Contray to @NM_UNMANAGED_USER_SETTINGS, this can be overwritten via + * D-Bus. + * @NM_UNMANAGED_BY_DEFAULT: %TRUE for certain device types where we unmanage + * them by default + * @NM_UNMANAGED_USER_UDEV: %TRUE when unmanaged by user decision (via UDev rule) + * @NM_UNMANAGED_EXTERNAL_DOWN: %TRUE when unmanaged because !IFF_UP and not created by NM + * @NM_UNMANAGED_IS_SLAVE: indicates that the device is enslaved. Note that + * setting the NM_UNMANAGED_IS_SLAVE to %TRUE makes no sense, this flag has only + * meaning to set a slave device as managed if the parent is managed too. + */ +typedef enum { /*< skip >*/ + NM_UNMANAGED_NONE = 0, + + /* these flags are authoritative. If one of them is set, + * the device cannot be managed. */ + NM_UNMANAGED_SLEEPING = (1LL << 0), + NM_UNMANAGED_QUITTING = (1LL << 1), + NM_UNMANAGED_PARENT = (1LL << 2), + NM_UNMANAGED_BY_TYPE = (1LL << 3), + NM_UNMANAGED_PLATFORM_INIT = (1LL << 4), + NM_UNMANAGED_USER_EXPLICIT = (1LL << 5), + NM_UNMANAGED_USER_SETTINGS = (1LL << 6), + + /* These flags can be non-effective and be overwritten + * by other flags. */ + NM_UNMANAGED_BY_DEFAULT = (1LL << 8), + NM_UNMANAGED_USER_CONF = (1LL << 9), + NM_UNMANAGED_USER_UDEV = (1LL << 10), + NM_UNMANAGED_EXTERNAL_DOWN = (1LL << 11), + NM_UNMANAGED_IS_SLAVE = (1LL << 12), + +} NMUnmanagedFlags; + +typedef enum { + NM_UNMAN_FLAG_OP_SET_MANAGED = FALSE, + NM_UNMAN_FLAG_OP_SET_UNMANAGED = TRUE, + NM_UNMAN_FLAG_OP_FORGET = 2, +} NMUnmanFlagOp; + +const char *nm_unmanaged_flags2str(NMUnmanagedFlags flags, char *buf, gsize len); + +gboolean nm_device_get_managed(NMDevice *device, gboolean for_user_request); +NMUnmanagedFlags nm_device_get_unmanaged_mask(NMDevice *device, NMUnmanagedFlags flag); +NMUnmanagedFlags nm_device_get_unmanaged_flags(NMDevice *device, NMUnmanagedFlags flag); +void nm_device_set_unmanaged_flags(NMDevice *device, NMUnmanagedFlags flags, NMUnmanFlagOp set_op); +void nm_device_set_unmanaged_by_flags(NMDevice * device, + NMUnmanagedFlags flags, + NMUnmanFlagOp set_op, + NMDeviceStateReason reason); +void nm_device_set_unmanaged_by_flags_queue(NMDevice * self, + NMUnmanagedFlags flags, + NMUnmanFlagOp set_op, + NMDeviceStateReason reason); +void nm_device_set_unmanaged_by_user_settings(NMDevice *self); +void nm_device_set_unmanaged_by_user_udev(NMDevice *self); +void nm_device_set_unmanaged_by_user_conf(NMDevice *self); +void nm_device_set_unmanaged_by_quitting(NMDevice *device); + +gboolean nm_device_check_unrealized_device_managed(NMDevice *self); + +gboolean nm_device_is_nm_owned(NMDevice *device); + +gboolean nm_device_has_capability(NMDevice *self, NMDeviceCapabilities caps); + +/*****************************************************************************/ + +void nm_device_assume_state_get(NMDevice * self, + gboolean * out_assume_state_guess_assume, + const char **out_assume_state_connection_uuid); +void nm_device_assume_state_reset(NMDevice *self); + +/*****************************************************************************/ + +gboolean nm_device_realize_start(NMDevice * device, + const NMPlatformLink *plink, + gboolean assume_state_guess_assume, + const char * assume_state_connection_uuid, + gboolean set_nm_owned, + NMUnmanFlagOp unmanaged_user_explicit, + gboolean * out_compatible, + GError ** error); +void nm_device_realize_finish(NMDevice *self, const NMPlatformLink *plink); +gboolean nm_device_create_and_realize(NMDevice * self, + NMConnection *connection, + NMDevice * parent, + GError ** error); +gboolean nm_device_unrealize(NMDevice *device, gboolean remove_resources, GError **error); + +void nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink); + +typedef enum { + NM_DEVICE_AUTOCONNECT_BLOCKED_NONE = 0, + + NM_DEVICE_AUTOCONNECT_BLOCKED_USER = (1LL << 0), + + NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN = (1LL << 1), + NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT = (1LL << 2), + NM_DEVICE_AUTOCONNECT_BLOCKED_SIM_MISSING = (1LL << 3), + NM_DEVICE_AUTOCONNECT_BLOCKED_INIT_FAILED = (1LL << 4), + + _NM_DEVICE_AUTOCONNECT_BLOCKED_LAST, + + NM_DEVICE_AUTOCONNECT_BLOCKED_ALL = (((_NM_DEVICE_AUTOCONNECT_BLOCKED_LAST - 1) << 1) - 1), + + NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL = + NM_DEVICE_AUTOCONNECT_BLOCKED_ALL & ~NM_DEVICE_AUTOCONNECT_BLOCKED_USER, +} NMDeviceAutoconnectBlockedFlags; + +NMDeviceAutoconnectBlockedFlags +nm_device_autoconnect_blocked_get(NMDevice *device, NMDeviceAutoconnectBlockedFlags mask); + +void nm_device_autoconnect_blocked_set_full(NMDevice * device, + NMDeviceAutoconnectBlockedFlags mask, + NMDeviceAutoconnectBlockedFlags values); + +static inline void +nm_device_autoconnect_blocked_set(NMDevice *device, NMDeviceAutoconnectBlockedFlags mask) +{ + nm_device_autoconnect_blocked_set_full(device, mask, mask); +} + +static inline void +nm_device_autoconnect_blocked_unset(NMDevice *device, NMDeviceAutoconnectBlockedFlags mask) +{ + nm_device_autoconnect_blocked_set_full(device, mask, NM_DEVICE_AUTOCONNECT_BLOCKED_NONE); +} + +void nm_device_emit_recheck_auto_activate(NMDevice *device); + +NMDeviceSysIfaceState nm_device_sys_iface_state_get(NMDevice *device); + +gboolean nm_device_sys_iface_state_is_external(NMDevice *self); +gboolean nm_device_sys_iface_state_is_external_or_assume(NMDevice *self); + +void nm_device_sys_iface_state_set(NMDevice *device, NMDeviceSysIfaceState sys_iface_state); + +void nm_device_state_changed(NMDevice *device, NMDeviceState state, NMDeviceStateReason reason); + +void nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason); + +gboolean nm_device_get_firmware_missing(NMDevice *self); + +void nm_device_disconnect_active_connection(NMActiveConnection * active, + NMDeviceStateReason device_reason, + NMActiveConnectionStateReason active_reason); + +void nm_device_queue_activation(NMDevice *device, NMActRequest *req); + +gboolean nm_device_supports_vlans(NMDevice *device); + +gboolean +nm_device_add_pending_action(NMDevice *device, const char *action, gboolean assert_not_yet_pending); +gboolean +nm_device_remove_pending_action(NMDevice *device, const char *action, gboolean assert_is_pending); +const char *nm_device_has_pending_action_reason(NMDevice *device); + +static inline gboolean +nm_device_has_pending_action(NMDevice *device) +{ + return !!nm_device_has_pending_action_reason(device); +} + +NMSettingsConnection * +nm_device_get_best_connection(NMDevice *device, const char *specific_object, GError **error); + +gboolean nm_device_check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error); + +void nm_device_notify_availability_maybe_changed(NMDevice *self); + +gboolean nm_device_owns_iface(NMDevice *device, const char *iface); + +NMConnection *nm_device_new_default_connection(NMDevice *self); + +const NMPObject *nm_device_get_best_default_route(NMDevice *self, int addr_family); + +void nm_device_spawn_iface_helper(NMDevice *self); + +gboolean nm_device_reapply(NMDevice *self, NMConnection *connection, GError **error); +void nm_device_reapply_settings_immediately(NMDevice *self); + +void nm_device_update_firewall_zone(NMDevice *self); +void nm_device_update_metered(NMDevice *self); +void nm_device_reactivate_ip_config(NMDevice * device, + int addr_family, + NMSettingIPConfig *s_ip_old, + NMSettingIPConfig *s_ip_new); + +gboolean nm_device_update_hw_address(NMDevice *self); +void nm_device_update_initial_hw_address(NMDevice *self); +void nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze); +void nm_device_update_dynamic_ip_setup(NMDevice *self); +guint nm_device_get_supplicant_timeout(NMDevice *self); + +gboolean nm_device_auth_retries_try_next(NMDevice *self); + +gboolean nm_device_hw_addr_get_cloned(NMDevice * self, + NMConnection *connection, + gboolean is_wifi, + char ** hwaddr, + gboolean * preserve, + GError ** error); + +typedef struct _NMDeviceConnectivityHandle NMDeviceConnectivityHandle; + +typedef void (*NMDeviceConnectivityCallback)(NMDevice * self, + NMDeviceConnectivityHandle *handle, + NMConnectivityState state, + GError * error, + gpointer user_data); + +void nm_device_check_connectivity_update_interval(NMDevice *self); + +NMDeviceConnectivityHandle *nm_device_check_connectivity(NMDevice * self, + int addr_family, + NMDeviceConnectivityCallback callback, + gpointer user_data); + +void nm_device_check_connectivity_cancel(NMDeviceConnectivityHandle *handle); + +NMConnectivityState nm_device_get_connectivity_state(NMDevice *self, int addr_family); + +typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer; + +typedef void (*NMBtVTableRegisterCallback)(GError *error, gpointer user_data); + +struct _NMBtVTableNetworkServer { + gboolean (*is_available)(const NMBtVTableNetworkServer *vtable, + const char * addr, + NMDevice * device_accept_busy); + + gboolean (*register_bridge)(const NMBtVTableNetworkServer *vtable, + const char * addr, + NMDevice * device, + GCancellable * cancellable, + NMBtVTableRegisterCallback callback, + gpointer callback_user_data, + GError ** error); + gboolean (*unregister_bridge)(const NMBtVTableNetworkServer *vtable, NMDevice *device); +}; + +const char *nm_device_state_to_str(NMDeviceState state); +const char *nm_device_state_reason_to_str(NMDeviceStateReason reason); + +gboolean nm_device_is_vpn(NMDevice *self); + +const char * +nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_pending); + +void nm_device_clear_dns_lookup_data(NMDevice *self); + +#endif /* __NETWORKMANAGER_DEVICE_H__ */ diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c new file mode 100644 index 0000000..e645b9d --- /dev/null +++ b/src/core/devices/nm-lldp-listener.c @@ -0,0 +1,1122 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-lldp-listener.h" + +#include + +#include "nm-std-aux/unaligned.h" +#include "platform/nm-platform.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-utils.h" + +#include "systemd/nm-sd.h" + +#define MAX_NEIGHBORS 128 +#define MIN_UPDATE_INTERVAL_NSEC (2 * NM_UTILS_NSEC_PER_SEC) + +#define LLDP_MAC_NEAREST_BRIDGE \ + (&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e}})) +#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE \ + (&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x03}})) +#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE \ + (&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}})) + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMLldpListener, PROP_NEIGHBORS, ); + +typedef struct { + sd_lldp * lldp_handle; + GHashTable *lldp_neighbors; + GVariant * variant; + + /* the timestamp in nsec until which we delay updates. */ + gint64 ratelimit_next_nsec; + guint ratelimit_id; + + int ifindex; +} NMLldpListenerPrivate; + +struct _NMLldpListener { + GObject parent; + NMLldpListenerPrivate _priv; +}; + +struct _NMLldpListenerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT) + +#define NM_LLDP_LISTENER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMLldpListener, NM_IS_LLDP_LISTENER) + +/*****************************************************************************/ + +typedef struct { + GVariant * variant; + sd_lldp_neighbor *neighbor_sd; + char * chassis_id; + char * port_id; + guint8 chassis_id_type; + guint8 port_id_type; +} LldpNeighbor; + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "lldp" +#define _NMLOG_DOMAIN LOGD_DEVICE +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ + char _sbuf[64]; \ + int _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE(self)->ifindex : 0; \ + \ + _nm_log(_level, \ + _NMLOG_DOMAIN, \ + 0, \ + _ifindex > 0 ? nm_platform_link_get_name(NM_PLATFORM_GET, _ifindex) : NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_ifindex > 0) ? nm_sprintf_buf(_sbuf, "[%p,%d]", (self), _ifindex) \ + : ((self) ? nm_sprintf_buf(_sbuf, "[%p]", (self)) : "")) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define LOG_NEIGH_FMT "CHASSIS=%u/%s PORT=%u/%s" +#define LOG_NEIGH_ARG(neigh) \ + (neigh)->chassis_id_type, (neigh)->chassis_id, (neigh)->port_id_type, (neigh)->port_id + +/*****************************************************************************/ + +static void +lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *out_raw_len) +{ + gconstpointer raw_data = NULL; + gsize raw_len = 0; + int r; + + nm_assert(neigh); + + r = sd_lldp_neighbor_get_raw(neigh->neighbor_sd, &raw_data, &raw_len); + + nm_assert(r >= 0); + nm_assert(raw_data); + nm_assert(raw_len > 0); + + *out_raw_data = raw_data; + *out_raw_len = raw_len; +} + +static gboolean +lldp_neighbor_id_get(struct sd_lldp_neighbor *neighbor_sd, + guint8 * out_chassis_id_type, + const guint8 ** out_chassis_id, + gsize * out_chassis_id_len, + guint8 * out_port_id_type, + const guint8 ** out_port_id, + gsize * out_port_id_len) +{ + int r; + + r = sd_lldp_neighbor_get_chassis_id(neighbor_sd, + out_chassis_id_type, + (gconstpointer *) out_chassis_id, + out_chassis_id_len); + if (r < 0) + return FALSE; + + r = sd_lldp_neighbor_get_port_id(neighbor_sd, + out_port_id_type, + (gconstpointer *) out_port_id, + out_port_id_len); + if (r < 0) + return FALSE; + + return TRUE; +} + +static guint +lldp_neighbor_id_hash(gconstpointer ptr) +{ + const LldpNeighbor *neigh = ptr; + guint8 chassis_id_type; + guint8 port_id_type; + const guint8 * chassis_id; + const guint8 * port_id; + gsize chassis_id_len; + gsize port_id_len; + NMHashState h; + + if (!lldp_neighbor_id_get(neigh->neighbor_sd, + &chassis_id_type, + &chassis_id, + &chassis_id_len, + &port_id_type, + &port_id, + &port_id_len)) { + nm_assert_not_reached(); + return 0; + } + + nm_hash_init(&h, 23423423u); + nm_hash_update_vals(&h, chassis_id_len, port_id_len, chassis_id_type, port_id_type); + nm_hash_update(&h, chassis_id, chassis_id_len); + nm_hash_update(&h, port_id, port_id_len); + return nm_hash_complete(&h); +} + +static int +lldp_neighbor_id_cmp(const LldpNeighbor *a, const LldpNeighbor *b) +{ + guint8 a_chassis_id_type; + guint8 b_chassis_id_type; + guint8 a_port_id_type; + guint8 b_port_id_type; + const guint8 *a_chassis_id; + const guint8 *b_chassis_id; + const guint8 *a_port_id; + const guint8 *b_port_id; + gsize a_chassis_id_len; + gsize b_chassis_id_len; + gsize a_port_id_len; + gsize b_port_id_len; + + NM_CMP_SELF(a, b); + + if (!lldp_neighbor_id_get(a->neighbor_sd, + &a_chassis_id_type, + &a_chassis_id, + &a_chassis_id_len, + &a_port_id_type, + &a_port_id, + &a_port_id_len)) { + nm_assert_not_reached(); + return FALSE; + } + + if (!lldp_neighbor_id_get(b->neighbor_sd, + &b_chassis_id_type, + &b_chassis_id, + &b_chassis_id_len, + &b_port_id_type, + &b_port_id, + &b_port_id_len)) { + nm_assert_not_reached(); + return FALSE; + } + + NM_CMP_DIRECT(a_chassis_id_type, b_chassis_id_type); + NM_CMP_DIRECT(a_port_id_type, b_port_id_type); + NM_CMP_DIRECT(a_chassis_id_len, b_chassis_id_len); + NM_CMP_DIRECT(a_port_id_len, b_port_id_len); + NM_CMP_DIRECT_MEMCMP(a_chassis_id, b_chassis_id, a_chassis_id_len); + NM_CMP_DIRECT_MEMCMP(a_port_id, b_port_id, a_port_id_len); + return 0; +} + +static int +lldp_neighbor_id_cmp_p(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return lldp_neighbor_id_cmp(*((const LldpNeighbor *const *) a), + *((const LldpNeighbor *const *) b)); +} + +static gboolean +lldp_neighbor_id_equal(gconstpointer a, gconstpointer b) +{ + return lldp_neighbor_id_cmp(a, b) == 0; +} + +static void +lldp_neighbor_free(LldpNeighbor *neighbor) +{ + if (!neighbor) + return; + + g_free(neighbor->chassis_id); + g_free(neighbor->port_id); + nm_g_variant_unref(neighbor->variant); + sd_lldp_neighbor_unref(neighbor->neighbor_sd); + nm_g_slice_free(neighbor); +} + +static void +lldp_neighbor_freep(LldpNeighbor **ptr) +{ + lldp_neighbor_free(*ptr); +} + +static gboolean +lldp_neighbor_equal(LldpNeighbor *a, LldpNeighbor *b) +{ + const guint8 *raw_data_a; + const guint8 *raw_data_b; + gsize raw_len_a; + gsize raw_len_b; + + if (a->neighbor_sd == b->neighbor_sd) + return TRUE; + + lldp_neighbor_get_raw(a, &raw_data_a, &raw_len_a); + lldp_neighbor_get_raw(b, &raw_data_b, &raw_len_b); + return raw_len_a == raw_len_b && (memcmp(raw_data_a, raw_data_b, raw_len_a) == 0); +} + +static GVariant * +parse_management_address_tlv(const uint8_t *data, gsize len) +{ + GVariantBuilder builder; + gsize addr_len; + const guint8 * v_object_id_arr; + gsize v_object_id_len; + const guint8 * v_address_arr; + gsize v_address_len; + guint32 v_interface_number; + guint32 v_interface_number_subtype; + guint32 v_address_subtype; + + /* 802.1AB-2009 - Figure 8-11 + * + * - TLV type / length (2 bytes) + * - address string length (1 byte) + * - address subtype (1 byte) + * - address (1 to 31 bytes) + * - interface number subtype (1 byte) + * - interface number (4 bytes) + * - OID string length (1 byte) + * - OID (0 to 128 bytes) + */ + + if (len < 11) + return NULL; + + nm_assert((data[0] >> 1) == SD_LLDP_TYPE_MGMT_ADDRESS); + nm_assert((((data[0] & 1) << 8) + data[1]) + 2 == len); + + data += 2; + len -= 2; + addr_len = *data; /* length of (address subtype + address) */ + + if (addr_len < 2 || addr_len > 32) + return NULL; + if (len < (1 /* address stringth length */ + + addr_len /* address subtype + address */ + + 5 /* interface */ + + 1)) /* oid */ + return NULL; + + data++; + len--; + v_address_subtype = *data; + v_address_arr = &data[1]; + v_address_len = addr_len - 1; + + data += addr_len; + len -= addr_len; + v_interface_number_subtype = *data; + + data++; + len--; + v_interface_number = unaligned_read_be32(data); + + data += 4; + len -= 4; + v_object_id_len = *data; + if (len < (1 + v_object_id_len)) + return NULL; + data++; + v_object_id_arr = data; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + nm_g_variant_builder_add_sv_uint32(&builder, "address-subtype", v_address_subtype); + nm_g_variant_builder_add_sv_bytearray(&builder, "address", v_address_arr, v_address_len); + nm_g_variant_builder_add_sv_uint32(&builder, + "interface-number-subtype", + v_interface_number_subtype); + nm_g_variant_builder_add_sv_uint32(&builder, "interface-number", v_interface_number); + if (v_object_id_len > 0) + nm_g_variant_builder_add_sv_bytearray(&builder, + "object-id", + v_object_id_arr, + v_object_id_len); + return g_variant_builder_end(&builder); +} + +static char * +format_network_address(const guint8 *data, gsize sz) +{ + NMIPAddr a; + int family; + + if (sz == 5 && data[0] == 1 /* LLDP_MGMT_ADDR_IP4 */) { + memcpy(&a, &data[1], sizeof(a.addr4)); + family = AF_INET; + } else if (sz == 17 && data[0] == 2 /* LLDP_MGMT_ADDR_IP6 */) { + memcpy(&a, &data[1], sizeof(a.addr6)); + family = AF_INET6; + } else + return NULL; + + return nm_utils_inet_ntop_dup(family, &a); +} + +static const char * +format_string(const guint8 *data, gsize len, gboolean allow_trim, char **out_to_free) +{ + gboolean is_null_terminated = FALSE; + + nm_assert(out_to_free && !*out_to_free); + + if (allow_trim) { + while (len > 0 && data[len - 1] == '\0') { + is_null_terminated = TRUE; + len--; + } + } + + if (len == 0) + return NULL; + + if (memchr(data, len, '\0')) + return NULL; + + return nm_utils_buf_utf8safe_escape(data, + is_null_terminated ? -1 : (gssize) len, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL + | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII, + out_to_free); +} + +static char * +format_string_cp(const guint8 *data, gsize len, gboolean allow_trim) +{ + char * s_free = NULL; + const char *s; + + s = format_string(data, len, allow_trim, &s_free); + nm_assert(!s_free || s == s_free); + return s ? (s_free ?: g_strdup(s)) : NULL; +} + +static LldpNeighbor * +lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd) +{ + LldpNeighbor *neigh; + guint8 chassis_id_type; + guint8 port_id_type; + const guint8 *chassis_id; + const guint8 *port_id; + gsize chassis_id_len; + gsize port_id_len; + gs_free char *s_chassis_id = NULL; + gs_free char *s_port_id = NULL; + + if (!lldp_neighbor_id_get(neighbor_sd, + &chassis_id_type, + &chassis_id, + &chassis_id_len, + &port_id_type, + &port_id, + &port_id_len)) + return NULL; + + switch (chassis_id_type) { + case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: + s_chassis_id = format_string_cp(chassis_id, chassis_id_len, FALSE); + break; + case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + s_chassis_id = nm_utils_hwaddr_ntoa(chassis_id, chassis_id_len); + break; + case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: + s_chassis_id = format_network_address(chassis_id, chassis_id_len); + break; + } + if (!s_chassis_id) { + /* Invalid/unsupported chassis_id? Expose as hex string. This format is not stable, and + * in the future we may add a better string representation for these case (thus + * changing the API). */ + s_chassis_id = nm_utils_bin2hexstr_full(chassis_id, chassis_id_len, '\0', FALSE, NULL); + } + + switch (port_id_type) { + case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: + s_port_id = format_string_cp(port_id, port_id_len, FALSE); + break; + case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: + s_port_id = nm_utils_hwaddr_ntoa(port_id, port_id_len); + break; + case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: + s_port_id = format_network_address(port_id, port_id_len); + break; + } + if (!s_port_id) { + /* Invalid/unsupported port_id? Expose as hex string. This format is not stable, and + * in the future we may add a better string representation for these case (thus + * changing the API). */ + s_port_id = nm_utils_bin2hexstr_full(port_id, port_id_len, '\0', FALSE, NULL); + } + + neigh = g_slice_new(LldpNeighbor); + *neigh = (LldpNeighbor){ + .neighbor_sd = sd_lldp_neighbor_ref(neighbor_sd), + .chassis_id_type = chassis_id_type, + .chassis_id = g_steal_pointer(&s_chassis_id), + .port_id_type = port_id_type, + .port_id = g_steal_pointer(&s_port_id), + }; + return neigh; +} + +static GVariant * +lldp_neighbor_to_variant(LldpNeighbor *neigh) +{ + struct ether_addr destination_address; + GVariantBuilder builder; + const char * str; + const guint8 * raw_data; + gsize raw_len; + uint16_t u16; + uint8_t * data8; + gsize len; + int r; + + if (neigh->variant) + return neigh->variant; + + lldp_neighbor_get_raw(neigh, &raw_data, &raw_len); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + nm_g_variant_builder_add_sv_bytearray(&builder, NM_LLDP_ATTR_RAW, raw_data, raw_len); + nm_g_variant_builder_add_sv_uint32(&builder, + NM_LLDP_ATTR_CHASSIS_ID_TYPE, + neigh->chassis_id_type); + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_CHASSIS_ID, neigh->chassis_id); + nm_g_variant_builder_add_sv_uint32(&builder, NM_LLDP_ATTR_PORT_ID_TYPE, neigh->port_id_type); + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_PORT_ID, neigh->port_id); + + r = sd_lldp_neighbor_get_destination_address(neigh->neighbor_sd, &destination_address); + if (r < 0) + str = NULL; + else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_BRIDGE)) + str = NM_LLDP_DEST_NEAREST_BRIDGE; + else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE)) + str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE; + else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE)) + str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE; + else + str = NULL; + if (str) + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_DESTINATION, str); + + if (sd_lldp_neighbor_get_port_description(neigh->neighbor_sd, &str) == 0) + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_PORT_DESCRIPTION, str); + + if (sd_lldp_neighbor_get_system_name(neigh->neighbor_sd, &str) == 0) + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_SYSTEM_NAME, str); + + if (sd_lldp_neighbor_get_system_description(neigh->neighbor_sd, &str) == 0) + nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, str); + + if (sd_lldp_neighbor_get_system_capabilities(neigh->neighbor_sd, &u16) == 0) + nm_g_variant_builder_add_sv_uint32(&builder, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, u16); + + r = sd_lldp_neighbor_tlv_rewind(neigh->neighbor_sd); + if (r < 0) + nm_assert_not_reached(); + else { + gboolean v_management_addresses_has = FALSE; + GVariantBuilder v_management_addresses; + GVariant * v_ieee_802_1_pvid = NULL; + GVariant * v_ieee_802_1_ppvid = NULL; + GVariant * v_ieee_802_1_ppvid_flags = NULL; + GVariantBuilder v_ieee_802_1_ppvids; + GVariant * v_ieee_802_1_vid = NULL; + GVariant * v_ieee_802_1_vlan_name = NULL; + GVariantBuilder v_ieee_802_1_vlans; + GVariant * v_ieee_802_3_mac_phy_conf = NULL; + GVariant * v_ieee_802_3_power_via_mdi = NULL; + GVariant * v_ieee_802_3_max_frame_size = NULL; + GVariant * v_mud_url = NULL; + GVariantBuilder tmp_builder; + GVariant * tmp_variant; + + do { + guint8 oui[3]; + guint8 type; + guint8 subtype; + + if (sd_lldp_neighbor_tlv_get_type(neigh->neighbor_sd, &type) < 0) + continue; + + if (sd_lldp_neighbor_tlv_get_raw(neigh->neighbor_sd, (void *) &data8, &len) < 0) + continue; + + switch (type) { + case SD_LLDP_TYPE_MGMT_ADDRESS: + tmp_variant = parse_management_address_tlv(data8, len); + if (tmp_variant) { + if (!v_management_addresses_has) { + v_management_addresses_has = TRUE; + g_variant_builder_init(&v_management_addresses, G_VARIANT_TYPE("aa{sv}")); + } + g_variant_builder_add_value(&v_management_addresses, tmp_variant); + } + continue; + case SD_LLDP_TYPE_PRIVATE: + break; + default: + continue; + } + + r = sd_lldp_neighbor_tlv_get_oui(neigh->neighbor_sd, oui, &subtype); + if (r < 0) { + if (r == -ENXIO) + continue; + + /* in other cases, something is seriously wrong. Abort, but + * keep what we parsed so far. */ + break; + } + + if (len <= 6) + continue; + + /* skip over leading TLV, OUI and subtype */ +#if NM_MORE_ASSERTS > 5 + { + guint8 check_hdr[] = {0xfe | (((len - 2) >> 8) & 0x01), + ((len - 2) & 0xFF), + oui[0], + oui[1], + oui[2], + subtype}; + + nm_assert(len > 2 + 3 + 1); + nm_assert(memcmp(data8, check_hdr, sizeof check_hdr) == 0); + } +#endif + data8 += 6; + len -= 6; + + if (memcmp(oui, SD_LLDP_OUI_802_1, sizeof(oui)) == 0) { + switch (subtype) { + case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID: + if (len != 2) + continue; + if (!v_ieee_802_1_pvid) + v_ieee_802_1_pvid = g_variant_new_uint32(unaligned_read_be16(data8)); + break; + case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID: + if (len != 3) + continue; + if (!v_ieee_802_1_ppvid) { + v_ieee_802_1_ppvid_flags = g_variant_new_uint32(data8[0]); + v_ieee_802_1_ppvid = g_variant_new_uint32(unaligned_read_be16(&data8[1])); + g_variant_builder_init(&v_ieee_802_1_ppvids, G_VARIANT_TYPE("aa{sv}")); + } + g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}")); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, + "ppvid", + unaligned_read_be16(&data8[1])); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, "flags", data8[0]); + g_variant_builder_add_value(&v_ieee_802_1_ppvids, + g_variant_builder_end(&tmp_builder)); + break; + case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: + { + gs_free char *name_to_free = NULL; + const char * name; + guint32 vid; + gsize l; + + if (len <= 3) + continue; + + l = data8[2]; + if (len != 3 + l) + continue; + if (l > 32) + continue; + + name = format_string(&data8[3], l, TRUE, &name_to_free); + if (!name) + continue; + + vid = unaligned_read_be16(&data8[0]); + if (!v_ieee_802_1_vid) { + v_ieee_802_1_vid = g_variant_new_uint32(vid); + v_ieee_802_1_vlan_name = g_variant_new_string(name); + g_variant_builder_init(&v_ieee_802_1_vlans, G_VARIANT_TYPE("aa{sv}")); + } + g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}")); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, "vid", vid); + nm_g_variant_builder_add_sv_str(&tmp_builder, "name", name); + g_variant_builder_add_value(&v_ieee_802_1_vlans, + g_variant_builder_end(&tmp_builder)); + break; + } + default: + continue; + } + } else if (memcmp(oui, SD_LLDP_OUI_802_3, sizeof(oui)) == 0) { + switch (subtype) { + case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS: + if (len != 5) + continue; + + if (!v_ieee_802_3_mac_phy_conf) { + g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}")); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, "autoneg", data8[0]); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, + "pmd-autoneg-cap", + unaligned_read_be16(&data8[1])); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, + "operational-mau-type", + unaligned_read_be16(&data8[3])); + v_ieee_802_3_mac_phy_conf = g_variant_builder_end(&tmp_builder); + } + break; + case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI: + if (len != 3) + continue; + + if (!v_ieee_802_3_power_via_mdi) { + g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}")); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, + "mdi-power-support", + data8[0]); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, + "pse-power-pair", + data8[1]); + nm_g_variant_builder_add_sv_uint32(&tmp_builder, "power-class", data8[2]); + v_ieee_802_3_power_via_mdi = g_variant_builder_end(&tmp_builder); + } + break; + case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE: + if (len != 2) + continue; + if (!v_ieee_802_3_max_frame_size) + v_ieee_802_3_max_frame_size = + g_variant_new_uint32(unaligned_read_be16(data8)); + break; + } + } else if (memcmp(oui, SD_LLDP_OUI_MUD, sizeof(oui)) == 0) { + switch (subtype) { + case SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION: + if (!v_mud_url) { + gs_free char *s_free = NULL; + const char * s; + + s = format_string(data8, len, TRUE, &s_free); + if (s) + v_mud_url = g_variant_new_string(s); + } + break; + } + } + } while (sd_lldp_neighbor_tlv_next(neigh->neighbor_sd) > 0); + + if (v_management_addresses_has) + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_MANAGEMENT_ADDRESSES, + g_variant_builder_end(&v_management_addresses)); + if (v_ieee_802_1_pvid) + nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_IEEE_802_1_PVID, v_ieee_802_1_pvid); + if (v_ieee_802_1_ppvid) { + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_1_PPVID, + v_ieee_802_1_ppvid); + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, + v_ieee_802_1_ppvid_flags); + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_1_PPVIDS, + g_variant_builder_end(&v_ieee_802_1_ppvids)); + } + if (v_ieee_802_1_vid) { + nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_IEEE_802_1_VID, v_ieee_802_1_vid); + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, + v_ieee_802_1_vlan_name); + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_1_VLANS, + g_variant_builder_end(&v_ieee_802_1_vlans)); + } + if (v_ieee_802_3_mac_phy_conf) + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF, + v_ieee_802_3_mac_phy_conf); + if (v_ieee_802_3_power_via_mdi) + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI, + v_ieee_802_3_power_via_mdi); + if (v_ieee_802_3_max_frame_size) + nm_g_variant_builder_add_sv(&builder, + NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE, + v_ieee_802_3_max_frame_size); + if (v_mud_url) + nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_MUD_URL, v_mud_url); + } + + return (neigh->variant = g_variant_ref_sink(g_variant_builder_end(&builder))); +} + +/*****************************************************************************/ + +GVariant * +nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len) +{ + nm_auto(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *neighbor_sd = NULL; + nm_auto(lldp_neighbor_freep) LldpNeighbor * neigh = NULL; + GVariant * variant; + int r; + + g_assert(raw_data); + g_assert(raw_len > 0); + + r = sd_lldp_neighbor_from_raw(&neighbor_sd, raw_data, raw_len); + g_assert(r >= 0); + + neigh = lldp_neighbor_new(neighbor_sd); + g_assert(neigh); + + variant = lldp_neighbor_to_variant(neigh); + g_assert(variant); + + return g_variant_ref(variant); +} + +/*****************************************************************************/ + +static void +data_changed_notify(NMLldpListener *self, NMLldpListenerPrivate *priv) +{ + nm_clear_g_variant(&priv->variant); + _notify(self, PROP_NEIGHBORS); +} + +static gboolean +data_changed_timeout(gpointer user_data) +{ + NMLldpListener * self = user_data; + NMLldpListenerPrivate *priv; + + g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), G_SOURCE_REMOVE); + + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + priv->ratelimit_id = 0; + priv->ratelimit_next_nsec = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NSEC; + data_changed_notify(self, priv); + return G_SOURCE_REMOVE; +} + +static void +data_changed_schedule(NMLldpListener *self) +{ + NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + gint64 now_nsec; + + if (priv->ratelimit_id != 0) + return; + + now_nsec = nm_utils_get_monotonic_timestamp_nsec(); + if (now_nsec < priv->ratelimit_next_nsec) { + priv->ratelimit_id = + g_timeout_add_full(G_PRIORITY_LOW, + NM_UTILS_NSEC_TO_MSEC_CEIL(priv->ratelimit_next_nsec - now_nsec), + data_changed_timeout, + self, + NULL); + return; + } + + priv->ratelimit_id = g_idle_add_full(G_PRIORITY_LOW, data_changed_timeout, self, NULL); +} + +static void +process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove) +{ + NMLldpListenerPrivate * priv; + nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL; + LldpNeighbor * neigh_old; + + g_return_if_fail(NM_IS_LLDP_LISTENER(self)); + + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + g_return_if_fail(priv->lldp_handle); + g_return_if_fail(neighbor_sd); + + nm_assert(priv->lldp_neighbors); + + neigh = lldp_neighbor_new(neighbor_sd); + if (!neigh) { + _LOGT("process: failed to parse neighbor"); + return; + } + + neigh_old = g_hash_table_lookup(priv->lldp_neighbors, neigh); + + if (remove) { + if (neigh_old) { + _LOGT("process: %s neigh: " LOG_NEIGH_FMT, "remove", LOG_NEIGH_ARG(neigh)); + + g_hash_table_remove(priv->lldp_neighbors, neigh_old); + goto handle_changed; + } + return; + } + + if (neigh_old && lldp_neighbor_equal(neigh_old, neigh)) + return; + + _LOGD("process: %s neigh: " LOG_NEIGH_FMT, neigh_old ? "update" : "new", LOG_NEIGH_ARG(neigh)); + + g_hash_table_add(priv->lldp_neighbors, g_steal_pointer(&neigh)); + +handle_changed: + data_changed_schedule(self); +} + +static void +lldp_event_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) +{ + process_lldp_neighbor( + userdata, + n, + !NM_IN_SET(event, SD_LLDP_EVENT_ADDED, SD_LLDP_EVENT_UPDATED, SD_LLDP_EVENT_REFRESHED)); +} + +gboolean +nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error) +{ + NMLldpListenerPrivate *priv; + int ret; + + g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE); + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + if (priv->lldp_handle) { + g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "already running"); + return FALSE; + } + + ret = sd_lldp_new(&priv->lldp_handle); + if (ret < 0) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "initialization failed"); + return FALSE; + } + + ret = sd_lldp_set_ifindex(priv->lldp_handle, ifindex); + if (ret < 0) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "failed setting ifindex"); + goto err; + } + + ret = sd_lldp_set_callback(priv->lldp_handle, lldp_event_handler, self); + if (ret < 0) { + g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed"); + goto err; + } + + ret = sd_lldp_set_neighbors_max(priv->lldp_handle, MAX_NEIGHBORS); + nm_assert(ret == 0); + + priv->ifindex = ifindex; + + ret = sd_lldp_attach_event(priv->lldp_handle, NULL, 0); + if (ret < 0) { + g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "attach event failed"); + goto err_free; + } + + ret = sd_lldp_start(priv->lldp_handle); + if (ret < 0) { + g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "start failed"); + goto err; + } + + priv->lldp_neighbors = g_hash_table_new_full(lldp_neighbor_id_hash, + lldp_neighbor_id_equal, + (GDestroyNotify) lldp_neighbor_free, + NULL); + + _LOGD("start"); + + return TRUE; + +err: + sd_lldp_detach_event(priv->lldp_handle); +err_free: + sd_lldp_unref(priv->lldp_handle); + priv->lldp_handle = NULL; + priv->ifindex = 0; + return FALSE; +} + +void +nm_lldp_listener_stop(NMLldpListener *self) +{ + NMLldpListenerPrivate *priv; + guint size; + gboolean changed = FALSE; + + g_return_if_fail(NM_IS_LLDP_LISTENER(self)); + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + if (priv->lldp_handle) { + _LOGD("stop"); + sd_lldp_stop(priv->lldp_handle); + sd_lldp_detach_event(priv->lldp_handle); + sd_lldp_unref(priv->lldp_handle); + priv->lldp_handle = NULL; + + size = g_hash_table_size(priv->lldp_neighbors); + g_hash_table_remove_all(priv->lldp_neighbors); + nm_clear_pointer(&priv->lldp_neighbors, g_hash_table_unref); + if (size > 0 || priv->ratelimit_id != 0) + changed = TRUE; + } + + nm_clear_g_source(&priv->ratelimit_id); + priv->ratelimit_next_nsec = 0; + priv->ifindex = 0; + + if (changed) + data_changed_notify(self, priv); +} + +gboolean +nm_lldp_listener_is_running(NMLldpListener *self) +{ + NMLldpListenerPrivate *priv; + + g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE); + + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + return !!priv->lldp_handle; +} + +GVariant * +nm_lldp_listener_get_neighbors(NMLldpListener *self) +{ + NMLldpListenerPrivate *priv; + + g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE); + + priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->variant)) { + gs_free LldpNeighbor **neighbors = NULL; + GVariantBuilder array_builder; + guint i, n; + + g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}")); + neighbors = (LldpNeighbor **) + nm_utils_hash_keys_to_array(priv->lldp_neighbors, lldp_neighbor_id_cmp_p, NULL, &n); + for (i = 0; i < n; i++) + g_variant_builder_add_value(&array_builder, lldp_neighbor_to_variant(neighbors[i])); + priv->variant = g_variant_ref_sink(g_variant_builder_end(&array_builder)); + } + return priv->variant; +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMLldpListener *self = NM_LLDP_LISTENER(object); + + switch (prop_id) { + case PROP_NEIGHBORS: + g_value_set_variant(value, nm_lldp_listener_get_neighbors(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_lldp_listener_init(NMLldpListener *self) +{ + _LOGT("lldp listener created"); +} + +NMLldpListener * +nm_lldp_listener_new(void) +{ + return g_object_new(NM_TYPE_LLDP_LISTENER, NULL); +} + +static void +dispose(GObject *object) +{ + nm_lldp_listener_stop(NM_LLDP_LISTENER(object)); + + G_OBJECT_CLASS(nm_lldp_listener_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMLldpListener * self = NM_LLDP_LISTENER(object); + NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self); + + nm_lldp_listener_stop(self); + + nm_clear_g_variant(&priv->variant); + + _LOGT("lldp listener destroyed"); + + G_OBJECT_CLASS(nm_lldp_listener_parent_class)->finalize(object); +} + +static void +nm_lldp_listener_class_init(NMLldpListenerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + + obj_properties[PROP_NEIGHBORS] = + g_param_spec_variant(NM_LLDP_LISTENER_NEIGHBORS, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/nm-lldp-listener.h b/src/core/devices/nm-lldp-listener.h new file mode 100644 index 0000000..9d3e243 --- /dev/null +++ b/src/core/devices/nm-lldp-listener.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_LLDP_LISTENER__ +#define __NM_LLDP_LISTENER__ + +#define NM_TYPE_LLDP_LISTENER (nm_lldp_listener_get_type()) +#define NM_LLDP_LISTENER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LLDP_LISTENER, NMLldpListener)) +#define NM_LLDP_LISTENER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LLDP_LISTENER, NMLldpListenerClass)) +#define NM_IS_LLDP_LISTENER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LLDP_LISTENER)) +#define NM_IS_LLDP_LISTENER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LLDP_LISTENER)) +#define NM_LLDP_LISTENER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LLDP_LISTENER, NMLldpListenerClass)) + +#define NM_LLDP_LISTENER_NEIGHBORS "neighbors" + +typedef struct _NMLldpListenerClass NMLldpListenerClass; + +GType nm_lldp_listener_get_type(void); +NMLldpListener *nm_lldp_listener_new(void); +gboolean nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error); +void nm_lldp_listener_stop(NMLldpListener *self); +gboolean nm_lldp_listener_is_running(NMLldpListener *self); + +GVariant *nm_lldp_listener_get_neighbors(NMLldpListener *self); + +GVariant *nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len); + +#endif /* __NM_LLDP_LISTENER__ */ diff --git a/src/core/devices/ovs/meson.build b/src/core/devices/ovs/meson.build new file mode 100644 index 0000000..81c29bd --- /dev/null +++ b/src/core/devices/ovs/meson.build @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_device_plugin_ovs = shared_module( + 'nm-device-plugin-ovs', + sources: files( + 'nm-device-ovs-bridge.c', + 'nm-device-ovs-interface.c', + 'nm-device-ovs-port.c', + 'nm-ovsdb.c', + 'nm-ovs-factory.c', + ), + dependencies: [ + core_plugin_dep, + jansson_dep, + ], + c_args: daemon_c_flags, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_device_plugin_ovs + +test( + 'check-local-devices-ovs', + check_exports, + args: [ + libnm_device_plugin_ovs.full_path(), + linker_script_devices, + ], +) diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c new file mode 100644 index 0000000..1b7035b --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-bridge.c @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ovs-bridge.h" + +#include "nm-device-ovs-interface.h" +#include "nm-device-ovs-port.h" +#include "nm-ovsdb.h" + +#include "devices/nm-device-private.h" +#include "nm-active-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-ovs-bridge.h" +#include "nm-setting-ovs-external-ids.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceOvsBridge +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceOvsBridge { + NMDevice parent; +}; + +struct _NMDeviceOvsBridgeClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceOvsBridge, nm_device_ovs_bridge, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + return "ovs-bridge"; +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + /* The actual backing resources will be created on enslavement by the port + * when it can identify the port and the bridge. */ + + return TRUE; +} + +static gboolean +unrealize(NMDevice *device, GError **error) +{ + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + return NM_ACT_STAGE_RETURN_IP_FAIL; +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + if (!configure) + return TRUE; + + if (!NM_IS_DEVICE_OVS_PORT(slave)) + return FALSE; + + return TRUE; +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{} + +void +nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new) +{ + NMDeviceType device_type; + GType type; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(g_type_parent(G_TYPE_FROM_INSTANCE(self)) == NM_TYPE_DEVICE); + + /* NMDevice's reapply_connection() doesn't do anything. No need to call the parent + * implementation. */ + + _LOGD(LOGD_DEVICE, "reapplying settings for OVS device"); + + type = G_OBJECT_TYPE(self); + if (type == NM_TYPE_DEVICE_OVS_INTERFACE) + device_type = NM_DEVICE_TYPE_OVS_INTERFACE; + else if (type == NM_TYPE_DEVICE_OVS_PORT) + device_type = NM_DEVICE_TYPE_OVS_PORT; + else { + nm_assert(type == NM_TYPE_DEVICE_OVS_BRIDGE); + device_type = NM_DEVICE_TYPE_OVS_BRIDGE; + } + + nm_ovsdb_set_external_ids( + nm_ovsdb_get(), + device_type, + nm_device_get_ip_iface(self), + nm_connection_get_uuid(con_new), + _nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_EXTERNAL_IDS), + _nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_EXTERNAL_IDS)); +} + +/*****************************************************************************/ + +static void +nm_device_ovs_bridge_init(NMDeviceOvsBridge *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_ovs_bridge = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_bridge); + + device_class->connection_type_supported = NM_SETTING_OVS_BRIDGE_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_OVS_BRIDGE_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(); + + device_class->is_master = TRUE; + device_class->get_type_description = get_type_description; + device_class->create_and_realize = create_and_realize; + device_class->unrealize = unrealize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + device_class->can_reapply_change_ovs_external_ids = TRUE; + device_class->reapply_connection = nm_device_ovs_reapply_connection; +} diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.h b/src/core/devices/ovs/nm-device-ovs-bridge.h new file mode 100644 index 0000000..2b89334 --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-bridge.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ +#define __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ + +#define NM_TYPE_DEVICE_OVS_BRIDGE (nm_device_ovs_bridge_get_type()) +#define NM_DEVICE_OVS_BRIDGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridge)) +#define NM_DEVICE_OVS_BRIDGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass)) +#define NM_IS_DEVICE_OVS_BRIDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_BRIDGE)) +#define NM_IS_DEVICE_OVS_BRIDGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_BRIDGE)) +#define NM_DEVICE_OVS_BRIDGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass)) + +typedef struct _NMDeviceOvsBridge NMDeviceOvsBridge; +typedef struct _NMDeviceOvsBridgeClass NMDeviceOvsBridgeClass; + +GType nm_device_ovs_bridge_get_type(void); + +/*****************************************************************************/ + +void +nm_device_ovs_reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new); + +#endif /* __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ */ diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c new file mode 100644 index 0000000..b9d3d50 --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-interface.c @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ovs-interface.h" + +#include "nm-device-ovs-bridge.h" +#include "nm-ovsdb.h" + +#include "devices/nm-device-private.h" +#include "nm-active-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-ovs-interface.h" +#include "nm-setting-ovs-port.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceOvsInterface +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +typedef struct { + NMOvsdb *ovsdb; + bool waiting_for_interface : 1; +} NMDeviceOvsInterfacePrivate; + +struct _NMDeviceOvsInterface { + NMDevice parent; + NMDeviceOvsInterfacePrivate _priv; +}; + +struct _NMDeviceOvsInterfaceClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE) + +#define NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceOvsInterface, NM_IS_DEVICE_OVS_INTERFACE, NMDevice) + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + return "ovs-interface"; +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + /* The actual backing resources will be created once an interface is + * added to a port of ours, since there can be neither an empty port nor + * an empty bridge. */ + + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(device); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + + return nm_ovsdb_is_ready(priv->ovsdb); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingOvsInterface *s_ovs_iface; + + if (!NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_ovs_iface = nm_connection_get_setting_ovs_interface(connection); + + if (!NM_IN_STRSET(nm_setting_ovs_interface_get_interface_type(s_ovs_iface), + "dpdk", + "internal", + "patch")) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "unsupported OVS interface type in profile"); + return FALSE; + } + + return TRUE; +} + +static void +link_changed(NMDevice *device, const NMPlatformLink *pllink) +{ + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device); + + if (!pllink || !priv->waiting_for_interface) + return; + + priv->waiting_for_interface = FALSE; + + if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG) { + if (!nm_device_hw_addr_set_cloned(device, + nm_device_get_applied_connection(device), + FALSE)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + nm_device_bring_up(device, TRUE, NULL); + nm_device_activate_schedule_stage3_ip_config_start(device); + } +} + +static gboolean +_is_internal_interface(NMDevice *device) +{ + NMSettingOvsInterface *s_ovs_iface; + + s_ovs_iface = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OVS_INTERFACE); + + g_return_val_if_fail(s_ovs_iface, FALSE); + + return nm_streq(nm_setting_ovs_interface_get_interface_type(s_ovs_iface), "internal"); +} + +static void +set_platform_mtu_cb(GError *error, gpointer user_data) +{ + NMDevice * device = user_data; + NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE(device); + + if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) { + _LOGW(LOGD_DEVICE, + "could not change mtu of '%s': %s", + nm_device_get_iface(device), + error->message); + } + + g_object_unref(device); +} + +static gboolean +set_platform_mtu(NMDevice *device, guint32 mtu) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(device); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + + /* + * If the MTU is not set in ovsdb, Open vSwitch will change + * the MTU of an internal interface to match the minimum of + * the other interfaces in the bridge. + */ + /* FIXME(shutdown): the function should become cancellable so + * that it doesn't need to hold a reference to the device, and + * it can be stopped during shutdown. + */ + if (_is_internal_interface(device)) { + nm_ovsdb_set_interface_mtu(priv->ovsdb, + nm_device_get_ip_iface(device), + mtu, + set_platform_mtu_cb, + g_object_ref(device)); + } + + return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)->set_platform_mtu(device, mtu); +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(device); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device); + + if (!_is_internal_interface(device)) + return NM_ACT_STAGE_RETURN_IP_FAIL; + + if (nm_device_get_ip_ifindex(device) <= 0) { + _LOGT(LOGD_DEVICE, "waiting for link to appear"); + priv->waiting_for_interface = TRUE; + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) { + *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static gboolean +can_unmanaged_external_down(NMDevice *self) +{ + return FALSE; +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(device); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + + priv->waiting_for_interface = FALSE; +} + +typedef struct { + NMDeviceOvsInterface * self; + GCancellable * cancellable; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + gulong link_changed_id; + gulong cancelled_id; + guint link_timeout_id; +} DeactivateData; + +static void +deactivate_invoke_cb(DeactivateData *data, GError *error) +{ + NMDeviceOvsInterface *self = data->self; + + _LOGT(LOGD_CORE, "deactivate: async callback (%s)", error ? error->message : "success"); + data->callback(NM_DEVICE(data->self), error, data->callback_user_data); + + nm_clear_g_signal_handler(nm_device_get_platform(NM_DEVICE(data->self)), + &data->link_changed_id); + nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id); + nm_clear_g_source(&data->link_timeout_id); + g_object_unref(data->self); + g_object_unref(data->cancellable); + nm_g_slice_free(data); +} + +static void +deactivate_link_changed_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *info, + int change_type_i, + DeactivateData *data) +{ + NMDeviceOvsInterface * self = data->self; + const NMPlatformSignalChangeType change_type = change_type_i; + + if (change_type == NM_PLATFORM_SIGNAL_REMOVED + && nm_streq0(info->name, nm_device_get_iface(NM_DEVICE(self)))) { + _LOGT(LOGD_DEVICE, "deactivate: link removed, proceeding"); + nm_device_update_from_platform_link(NM_DEVICE(self), NULL); + deactivate_invoke_cb(data, NULL); + return; + } +} + +static gboolean +deactivate_link_timeout(gpointer user_data) +{ + DeactivateData * data = user_data; + NMDeviceOvsInterface *self = data->self; + + _LOGT(LOGD_DEVICE, "deactivate: timeout waiting link removal"); + deactivate_invoke_cb(data, NULL); + return G_SOURCE_REMOVE; +} + +static void +deactivate_cancelled_cb(GCancellable *cancellable, gpointer user_data) +{ + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, FALSE, NULL); + deactivate_invoke_cb((DeactivateData *) user_data, error); +} + +static void +deactivate_cb_on_idle(gpointer user_data, GCancellable *cancellable) +{ + DeactivateData *data = user_data; + gs_free_error GError *cancelled_error = NULL; + + g_cancellable_set_error_if_cancelled(data->cancellable, &cancelled_error); + deactivate_invoke_cb(data, cancelled_error); +} + +static void +deactivate_async(NMDevice * device, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer callback_user_data) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(device); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + DeactivateData * data; + + _LOGT(LOGD_CORE, "deactivate: start async"); + + /* We want to ensure that the kernel link for this device is + * removed upon disconnection so that it will not interfere with + * later activations of the same device. Unfortunately there is + * no synchronization mechanism with vswitchd, we only update + * ovsdb and wait that changes are picked up. + */ + + data = g_slice_new(DeactivateData); + *data = (DeactivateData){ + .self = g_object_ref(self), + .cancellable = g_object_ref(cancellable), + .callback = callback, + .callback_user_data = callback_user_data, + }; + + if (!priv->waiting_for_interface + && !nm_platform_link_get_by_ifname(nm_device_get_platform(device), + nm_device_get_iface(device))) { + _LOGT(LOGD_CORE, "deactivate: link not present, proceeding"); + nm_device_update_from_platform_link(NM_DEVICE(self), NULL); + nm_utils_invoke_on_idle(cancellable, deactivate_cb_on_idle, data); + return; + } + + if (priv->waiting_for_interface) { + /* At this point we have issued an INSERT and a DELETE + * command for the interface to ovsdb. We don't know if + * vswitchd will see the two updates or only one. We + * must add a timeout to avoid waiting forever in case + * the link doesn't appear. + */ + data->link_timeout_id = g_timeout_add(6000, deactivate_link_timeout, data); + _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear in 6 seconds"); + } else + _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear"); + + data->cancelled_id = + g_cancellable_connect(cancellable, G_CALLBACK(deactivate_cancelled_cb), data, NULL); + data->link_changed_id = g_signal_connect(nm_device_get_platform(device), + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(deactivate_link_changed_cb), + data); +} + +static gboolean +can_update_from_platform_link(NMDevice *device, const NMPlatformLink *plink) +{ + /* If the device is deactivating, we already sent the + * deletion command to ovsdb and we don't want to deal + * with any new link appearing from the previous + * activation. + */ + return !plink || nm_device_get_state(device) != NM_DEVICE_STATE_DEACTIVATING; +} + +/*****************************************************************************/ + +static void +ovsdb_ready(NMOvsdb *ovsdb, NMDeviceOvsInterface *self) +{ + NMDevice *device = NM_DEVICE(self); + + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_recheck_available_connections(device); + nm_device_emit_recheck_auto_activate(device); +} + +static void +nm_device_ovs_interface_init(NMDeviceOvsInterface *self) +{ + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + + priv->ovsdb = g_object_ref(nm_ovsdb_get()); + + if (!nm_ovsdb_is_ready(priv->ovsdb)) + g_signal_connect(priv->ovsdb, NM_OVSDB_READY, G_CALLBACK(ovsdb_ready), self); +} + +static void +dispose(GObject *object) +{ + NMDeviceOvsInterface * self = NM_DEVICE_OVS_INTERFACE(object); + NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); + + if (priv->ovsdb) { + g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self); + g_clear_object(&priv->ovsdb); + } + + G_OBJECT_CLASS(nm_device_ovs_interface_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->dispose = dispose; + + dbus_object_class->interface_infos = + NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_interface); + + device_class->connection_type_supported = NM_SETTING_OVS_INTERFACE_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH); + + device_class->can_update_from_platform_link = can_update_from_platform_link; + device_class->deactivate = deactivate; + device_class->deactivate_async = deactivate_async; + device_class->get_type_description = get_type_description; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->is_available = is_available; + device_class->check_connection_compatible = check_connection_compatible; + device_class->link_changed = link_changed; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->can_unmanaged_external_down = can_unmanaged_external_down; + device_class->set_platform_mtu = set_platform_mtu; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + device_class->can_reapply_change_ovs_external_ids = TRUE; + device_class->reapply_connection = nm_device_ovs_reapply_connection; +} diff --git a/src/core/devices/ovs/nm-device-ovs-interface.h b/src/core/devices/ovs/nm-device-ovs-interface.h new file mode 100644 index 0000000..03b0e9a --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-interface.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ +#define __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ + +#define NM_TYPE_DEVICE_OVS_INTERFACE (nm_device_ovs_interface_get_type()) +#define NM_DEVICE_OVS_INTERFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterface)) +#define NM_DEVICE_OVS_INTERFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass)) +#define NM_IS_DEVICE_OVS_INTERFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_INTERFACE)) +#define NM_IS_DEVICE_OVS_INTERFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_INTERFACE)) +#define NM_DEVICE_OVS_INTERFACE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass)) + +typedef struct _NMDeviceOvsInterface NMDeviceOvsInterface; +typedef struct _NMDeviceOvsInterfaceClass NMDeviceOvsInterfaceClass; + +GType nm_device_ovs_interface_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ */ diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c new file mode 100644 index 0000000..5f7a697 --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-port.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-ovs-port.h" + +#include "nm-device-ovs-interface.h" +#include "nm-device-ovs-bridge.h" +#include "nm-ovsdb.h" + +#include "devices/nm-device-private.h" +#include "nm-active-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-ovs-port.h" +#include "nm-setting-ovs-port.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceOvsPort +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +struct _NMDeviceOvsPort { + NMDevice parent; +}; + +struct _NMDeviceOvsPortClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + return "ovs-port"; +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + /* The port will be added to ovsdb when an interface is enslaved, + * because there's no such thing like an empty port. */ + + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_SOFTWARE; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + return NM_ACT_STAGE_RETURN_IP_FAIL; +} + +static void +add_iface_cb(GError *error, gpointer user_data) +{ + NMDevice *slave = user_data; + + if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) { + nm_log_warn(LOGD_DEVICE, + "device %s could not be added to a ovs port: %s", + nm_device_get_iface(slave), + error->message); + nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); + } + + g_object_unref(slave); +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceOvsPort * self = NM_DEVICE_OVS_PORT(device); + NMActiveConnection *ac_port = NULL; + NMActiveConnection *ac_bridge = NULL; + NMDevice * bridge_device; + + if (!configure) + return TRUE; + + ac_port = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device)); + ac_bridge = nm_active_connection_get_master(ac_port); + if (!ac_bridge) { + _LOGW(LOGD_DEVICE, + "can't enslave %s: bridge active-connection not found", + nm_device_get_iface(slave)); + return FALSE; + } + + bridge_device = nm_active_connection_get_device(ac_bridge); + if (!bridge_device) { + _LOGW(LOGD_DEVICE, "can't enslave %s: bridge device not found", nm_device_get_iface(slave)); + return FALSE; + } + + nm_ovsdb_add_interface(nm_ovsdb_get(), + nm_active_connection_get_applied_connection(ac_bridge), + nm_device_get_applied_connection(device), + nm_device_get_applied_connection(slave), + bridge_device, + slave, + add_iface_cb, + g_object_ref(slave)); + + return TRUE; +} + +static void +del_iface_cb(GError *error, gpointer user_data) +{ + NMDevice *slave = user_data; + + if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) { + nm_log_warn(LOGD_DEVICE, + "device %s could not be removed from a ovs port: %s", + nm_device_get_iface(slave), + error->message); + nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); + } + + g_object_unref(slave); +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device); + + if (configure) { + _LOGI(LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface(slave)); + nm_ovsdb_del_interface(nm_ovsdb_get(), + nm_device_get_iface(slave), + del_iface_cb, + g_object_ref(slave)); + /* Open VSwitch is going to delete this one. We must ignore what happens + * next with the interface. */ + if (NM_IS_DEVICE_OVS_INTERFACE(slave)) + nm_device_update_from_platform_link(slave, NULL); + } else + _LOGI(LOGD_DEVICE, "ovs interface %s was released", nm_device_get_ip_iface(slave)); +} + +/*****************************************************************************/ + +static void +nm_device_ovs_port_init(NMDeviceOvsPort *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_OVS_PORT, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_port); + + device_class->connection_type_supported = NM_SETTING_OVS_PORT_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(); + + device_class->is_master = TRUE; + device_class->get_type_description = get_type_description; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + device_class->can_reapply_change_ovs_external_ids = TRUE; + device_class->reapply_connection = nm_device_ovs_reapply_connection; +} diff --git a/src/core/devices/ovs/nm-device-ovs-port.h b/src/core/devices/ovs/nm-device-ovs-port.h new file mode 100644 index 0000000..f4516c0 --- /dev/null +++ b/src/core/devices/ovs/nm-device-ovs-port.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_OVS_PORT_H__ +#define __NETWORKMANAGER_DEVICE_OVS_PORT_H__ + +#define NM_TYPE_DEVICE_OVS_PORT (nm_device_ovs_port_get_type()) +#define NM_DEVICE_OVS_PORT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPort)) +#define NM_DEVICE_OVS_PORT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass)) +#define NM_IS_DEVICE_OVS_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_PORT)) +#define NM_IS_DEVICE_OVS_PORT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_PORT)) +#define NM_DEVICE_OVS_PORT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass)) + +typedef struct _NMDeviceOvsPort NMDeviceOvsPort; +typedef struct _NMDeviceOvsPortClass NMDeviceOvsPortClass; + +GType nm_device_ovs_port_get_type(void); + +#endif /* __NETWORKMANAGER_DEVICE_OVS_PORT_H__ */ diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c new file mode 100644 index 0000000..d5875bc --- /dev/null +++ b/src/core/devices/ovs/nm-ovs-factory.c @@ -0,0 +1,324 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-manager.h" +#include "nm-ovsdb.h" +#include "nm-device-ovs-interface.h" +#include "nm-device-ovs-port.h" +#include "nm-device-ovs-bridge.h" +#include "platform/nm-platform.h" +#include "nm-core-internal.h" +#include "settings/nm-settings.h" +#include "devices/nm-device-factory.h" +#include "devices/nm-device-private.h" + +/*****************************************************************************/ + +typedef struct { + NMDeviceFactory parent; +} NMOvsFactory; + +typedef struct { + NMDeviceFactoryClass parent; +} NMOvsFactoryClass; + +#define NM_TYPE_OVS_FACTORY (nm_ovs_factory_get_type()) +#define NM_OVS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_OVS_FACTORY, NMOvsFactory)) +#define NM_OVS_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass)) +#define NM_IS_OVS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_OVS_FACTORY)) +#define NM_IS_OVS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_OVS_FACTORY)) +#define NM_OVS_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass)) + +static GType nm_ovs_factory_get_type(void); +G_DEFINE_TYPE(NMOvsFactory, nm_ovs_factory, NM_TYPE_DEVICE_FACTORY) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_DEVICE +#define _NMLOG(level, ifname, con_uuid, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + (ifname), \ + (con_uuid), \ + "ovs: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES( + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_OVS); + return g_object_new(NM_TYPE_OVS_FACTORY, NULL); +} + +static NMDevice * +new_device_from_type(const char *name, NMDeviceType device_type) +{ + GType type; + const char *type_desc; + NMLinkType link_type = NM_LINK_TYPE_NONE; + + if (nm_manager_get_device(NM_MANAGER_GET, name, device_type)) + return NULL; + + if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) { + type = NM_TYPE_DEVICE_OVS_INTERFACE; + type_desc = "Open vSwitch Interface"; + link_type = NM_LINK_TYPE_OPENVSWITCH; + } else if (device_type == NM_DEVICE_TYPE_OVS_PORT) { + type = NM_TYPE_DEVICE_OVS_PORT; + type_desc = "Open vSwitch Port"; + } else if (device_type == NM_DEVICE_TYPE_OVS_BRIDGE) { + type = NM_TYPE_DEVICE_OVS_BRIDGE; + type_desc = "Open vSwitch Bridge"; + } else { + return NULL; + } + + return g_object_new(type, + NM_DEVICE_IFACE, + name, + NM_DEVICE_DRIVER, + "openvswitch", + NM_DEVICE_DEVICE_TYPE, + device_type, + NM_DEVICE_TYPE_DESC, + type_desc, + NM_DEVICE_LINK_TYPE, + link_type, + NULL); +} + +static void +ovsdb_device_added(NMOvsdb * ovsdb, + const char * name, + guint device_type_i, + const char * subtype, + NMDeviceFactory *self) +{ + const NMDeviceType device_type = device_type_i; + NMDevice * device; + + if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE + && !NM_IN_STRSET(subtype, "internal", "patch")) { + /* system interfaces refer to kernel devices and + * don't need to be created by this factory. Ignore + * anything that is not an internal or patch + * interface. */ + return; + } + + device = new_device_from_type(name, device_type); + if (!device) + return; + + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); + g_object_unref(device); +} + +static void +ovsdb_device_removed(NMOvsdb * ovsdb, + const char * name, + guint device_type_i, + const char * subtype, + NMDeviceFactory *self) +{ + const NMDeviceType device_type = device_type_i; + NMDevice * device = NULL; + NMDeviceState device_state; + gboolean is_system_interface = FALSE; + + if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE + && !NM_IN_STRSET(subtype, "internal", "patch", "system")) + return; + + if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_streq0(subtype, "system")) { + NMDevice * d; + const CList * list; + NMSettingOvsInterface *s_ovs_int; + + /* The device associated to an OVS system interface can be of + * any kind. Find an interface with the same name and which has + * the OVS-interface setting. */ + is_system_interface = TRUE; + nm_manager_for_each_device (NM_MANAGER_GET, d, list) { + if (!nm_streq0(nm_device_get_iface(d), name)) + continue; + s_ovs_int = nm_device_get_applied_setting(d, NM_TYPE_SETTING_OVS_INTERFACE); + if (!s_ovs_int) + continue; + if (!nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "system")) + continue; + /* Failing the system interface device is almost always the right + * thing to do when the ovsdb entry is removed. However, to avoid + * that a late device-removed signal tears down a different, + * newly-activated connection, let's also check that we have a master. + * Or in alternative, that the device is assumed/external: in such + * case it's always fine to fail the device. + */ + if (!nm_device_get_master(d) && !nm_device_sys_iface_state_is_external_or_assume(d)) + continue; + + device = d; + } + } else { + device = nm_manager_get_device(NM_MANAGER_GET, name, device_type); + } + + if (!device) + return; + + device_state = nm_device_get_state(device); + + if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_device_get_act_request(device) + && device_state < NM_DEVICE_STATE_DEACTIVATING) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_REMOVED); + return; + } + + /* OVS system interfaces still exist even without the ovsdb entry */ + if (!is_system_interface && device_state == NM_DEVICE_STATE_UNMANAGED) { + nm_device_unrealize(device, TRUE, NULL); + } +} + +static void +ovsdb_interface_failed(NMOvsdb * ovsdb, + const char * name, + const char * connection_uuid, + const char * error, + NMDeviceFactory *self) +{ + NMDevice * device = NULL; + NMSettingsConnection * connection = NULL; + NMConnection * c; + const char * type; + NMSettingOvsInterface *s_ovs_int; + gboolean is_patch = FALSE; + gboolean ignore; + + device = nm_manager_get_device(NM_MANAGER_GET, name, NM_DEVICE_TYPE_OVS_INTERFACE); + if (device && connection_uuid) { + connection = + nm_settings_get_connection_by_uuid(nm_device_get_settings(device), connection_uuid); + } + + /* The patch interface which gets created first is expected to + * fail because the second patch doesn't exist yet. Ignore all + * failures of patch interfaces. */ + if (connection && (c = nm_settings_connection_get_connection(connection)) + && (type = nm_connection_get_connection_type(c)) + && nm_streq0(type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) + && (s_ovs_int = nm_connection_get_setting_ovs_interface(c)) + && nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "patch")) + is_patch = TRUE; + + ignore = !device || is_patch; + + _NMLOG(ignore ? LOGL_DEBUG : LOGL_INFO, + name, + connection_uuid, + "ovs interface \"%s\" (%s) failed%s: %s", + name, + connection_uuid, + ignore ? " (ignored)" : "", + error); + + if (ignore) + return; + + if (connection) { + nm_settings_connection_autoconnect_blocked_reason_set( + connection, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + TRUE); + } + + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); +} + +static void +start(NMDeviceFactory *self) +{ + NMOvsdb *ovsdb; + + ovsdb = nm_ovsdb_get(); + + g_signal_connect_object(ovsdb, + NM_OVSDB_DEVICE_ADDED, + G_CALLBACK(ovsdb_device_added), + self, + (GConnectFlags) 0); + g_signal_connect_object(ovsdb, + NM_OVSDB_DEVICE_REMOVED, + G_CALLBACK(ovsdb_device_removed), + self, + (GConnectFlags) 0); + g_signal_connect_object(ovsdb, + NM_OVSDB_INTERFACE_FAILED, + G_CALLBACK(ovsdb_interface_failed), + self, + (GConnectFlags) 0); +} + +static NMDevice * +create_device(NMDeviceFactory * self, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + NMDeviceType device_type = NM_DEVICE_TYPE_UNKNOWN; + const char * connection_type = NULL; + + if (g_strcmp0(iface, "ovs-system") == 0) { + *out_ignore = TRUE; + return NULL; + } + + if (connection) + connection_type = nm_connection_get_connection_type(connection); + + if (plink) + device_type = NM_DEVICE_TYPE_OVS_INTERFACE; + else if (g_strcmp0(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0) + device_type = NM_DEVICE_TYPE_OVS_INTERFACE; + else if (g_strcmp0(connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0) + device_type = NM_DEVICE_TYPE_OVS_PORT; + else if (g_strcmp0(connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME) == 0) + device_type = NM_DEVICE_TYPE_OVS_BRIDGE; + + return new_device_from_type(iface, device_type); +} + +static void +nm_ovs_factory_init(NMOvsFactory *self) +{} + +static void +nm_ovs_factory_class_init(NMOvsFactoryClass *klass) +{ + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + factory_class->get_supported_types = get_supported_types; + factory_class->start = start; + factory_class->create_device = create_device; +} diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c new file mode 100644 index 0000000..de54a2e --- /dev/null +++ b/src/core/devices/ovs/nm-ovsdb.c @@ -0,0 +1,2658 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-ovsdb.h" + +#include +#include + +#include "nm-glib-aux/nm-jansson.h" +#include "nm-glib-aux/nm-str-buf.h" +#include "nm-core-utils.h" +#include "nm-core-internal.h" +#include "devices/nm-device.h" +#include "nm-manager.h" +#include "nm-setting-ovs-external-ids.h" + +/*****************************************************************************/ + +#define OVSDB_MAX_FAILURES 3 + +/*****************************************************************************/ + +#if JANSSON_VERSION_HEX < 0x020400 + #warning "requires at least libjansson 2.4" +#endif + +typedef struct { + char * port_uuid; + char * name; + char * connection_uuid; + GPtrArray *interfaces; /* interface uuids */ + GArray * external_ids; +} OpenvswitchPort; + +typedef struct { + char * bridge_uuid; + char * name; + char * connection_uuid; + GPtrArray *ports; /* port uuids */ + GArray * external_ids; +} OpenvswitchBridge; + +typedef struct { + char * interface_uuid; + char * name; + char * type; + char * connection_uuid; + GArray *external_ids; +} OpenvswitchInterface; + +/*****************************************************************************/ + +typedef void (*OvsdbMethodCallback)(NMOvsdb *self, + json_t * response, + GError * error, + gpointer user_data); + +typedef enum { + OVSDB_MONITOR, + OVSDB_ADD_INTERFACE, + OVSDB_DEL_INTERFACE, + OVSDB_SET_INTERFACE_MTU, + OVSDB_SET_EXTERNAL_IDS, +} OvsdbCommand; + +#define CALL_ID_UNSPEC G_MAXUINT64 + +typedef union { + struct { + } monitor; + struct { + NMConnection *bridge; + NMConnection *port; + NMConnection *interface; + NMDevice * bridge_device; + NMDevice * interface_device; + } add_interface; + struct { + char *ifname; + } del_interface; + struct { + char * ifname; + guint32 mtu; + } set_interface_mtu; + struct { + NMDeviceType device_type; + char * ifname; + char * connection_uuid; + GHashTable * exid_old; + GHashTable * exid_new; + } set_external_ids; +} OvsdbMethodPayload; + +typedef struct { + NMOvsdb * self; + CList calls_lst; + guint64 call_id; + OvsdbCommand command; + OvsdbMethodCallback callback; + gpointer user_data; + OvsdbMethodPayload payload; +} OvsdbMethodCall; + +/*****************************************************************************/ + +enum { + DEVICE_ADDED, + DEVICE_REMOVED, + INTERFACE_FAILED, + READY, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GSocketClient * client; + GSocketConnection *conn; + GCancellable * cancellable; + char buf[4096]; /* Input buffer */ + size_t bufp; /* Last decoded byte in the input buffer. */ + GString * input; /* JSON stream waiting for decoding. */ + GString * output; /* JSON stream to be sent. */ + guint64 call_id_counter; + + CList calls_lst_head; + + GHashTable *interfaces; /* interface uuid => OpenvswitchInterface */ + GHashTable *ports; /* port uuid => OpenvswitchPort */ + GHashTable *bridges; /* bridge uuid => OpenvswitchBridge */ + char * db_uuid; + guint num_failures; + guint num_pending_deletions; + bool ready : 1; +} NMOvsdbPrivate; + +struct _NMOvsdb { + GObject parent; + NMOvsdbPrivate _priv; +}; + +struct _NMOvsdbClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMOvsdb, nm_ovsdb, G_TYPE_OBJECT) + +#define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMOvsdb, NM_IS_OVSDB) + +NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB); + +/*****************************************************************************/ + +static void ovsdb_try_connect(NMOvsdb *self); +static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing); +static void ovsdb_read(NMOvsdb *self); +static void ovsdb_write(NMOvsdb *self); +static void ovsdb_next_command(NMOvsdb *self); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_DEVICE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__) + +#define _NMLOG_call(level, call, ...) \ + _NMLOG((level), \ + "call[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR((call)) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +#define _LOGT_call(call, ...) _NMLOG_call(LOGL_TRACE, (call), __VA_ARGS__) + +/*****************************************************************************/ + +#define OVSDB_METHOD_PAYLOAD_MONITOR() \ + (&((const OvsdbMethodPayload){ \ + .monitor = {}, \ + })) + +#define OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(xbridge, \ + xport, \ + xinterface, \ + xbridge_device, \ + xinterface_device) \ + (&((const OvsdbMethodPayload){ \ + .add_interface = \ + { \ + .bridge = (xbridge), \ + .port = (xport), \ + .interface = (xinterface), \ + .bridge_device = (xbridge_device), \ + .interface_device = (xinterface_device), \ + }, \ + })) + +#define OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(xifname) \ + (&((const OvsdbMethodPayload){ \ + .del_interface = \ + { \ + .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ + }, \ + })) + +#define OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(xifname, xmtu) \ + (&((const OvsdbMethodPayload){ \ + .set_interface_mtu = \ + { \ + .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ + .mtu = (xmtu), \ + }, \ + })) + +#define OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(xdevice_type, \ + xifname, \ + xconnection_uuid, \ + xexid_old, \ + xexid_new) \ + (&((const OvsdbMethodPayload){ \ + .set_external_ids = \ + { \ + .device_type = xdevice_type, \ + .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ + .connection_uuid = (char *) NM_CONSTCAST(char, (xconnection_uuid)), \ + .exid_old = (xexid_old), \ + .exid_new = (xexid_new), \ + }, \ + })) + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(_device_type_to_table, + NMDeviceType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_BRIDGE, "Bridge"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_PORT, "Port"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_INTERFACE, + "Interface"), + NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); + +/*****************************************************************************/ + +static void +_call_complete(OvsdbMethodCall *call, json_t *response, GError *error) +{ + if (response) { + gs_free char *str = NULL; + + str = json_dumps(response, 0); + if (error) + _LOGT_call(call, "completed: %s ; error: %s", str, error->message); + else + _LOGT_call(call, "completed: %s", str); + } else { + nm_assert(error); + _LOGT_call(call, "completed: error: %s", error->message); + } + + c_list_unlink_stale(&call->calls_lst); + + if (call->callback) + call->callback(call->self, response, error, call->user_data); + + switch (call->command) { + case OVSDB_MONITOR: + break; + case OVSDB_ADD_INTERFACE: + g_clear_object(&call->payload.add_interface.bridge); + g_clear_object(&call->payload.add_interface.port); + g_clear_object(&call->payload.add_interface.interface); + g_clear_object(&call->payload.add_interface.bridge_device); + g_clear_object(&call->payload.add_interface.interface_device); + break; + case OVSDB_DEL_INTERFACE: + nm_clear_g_free(&call->payload.del_interface.ifname); + break; + case OVSDB_SET_INTERFACE_MTU: + nm_clear_g_free(&call->payload.set_interface_mtu.ifname); + break; + case OVSDB_SET_EXTERNAL_IDS: + nm_clear_g_free(&call->payload.set_external_ids.ifname); + nm_clear_g_free(&call->payload.set_external_ids.connection_uuid); + nm_clear_pointer(&call->payload.set_external_ids.exid_old, g_hash_table_destroy); + nm_clear_pointer(&call->payload.set_external_ids.exid_new, g_hash_table_destroy); + break; + } + + nm_g_slice_free(call); +} + +/*****************************************************************************/ + +static void +_free_bridge(OpenvswitchBridge *ovs_bridge) +{ + g_free(ovs_bridge->bridge_uuid); + g_free(ovs_bridge->name); + g_free(ovs_bridge->connection_uuid); + g_ptr_array_free(ovs_bridge->ports, TRUE); + nm_g_array_unref(ovs_bridge->external_ids); + nm_g_slice_free(ovs_bridge); +} + +static void +_free_port(OpenvswitchPort *ovs_port) +{ + g_free(ovs_port->port_uuid); + g_free(ovs_port->name); + g_free(ovs_port->connection_uuid); + g_ptr_array_free(ovs_port->interfaces, TRUE); + nm_g_array_unref(ovs_port->external_ids); + nm_g_slice_free(ovs_port); +} + +static void +_free_interface(OpenvswitchInterface *ovs_interface) +{ + g_free(ovs_interface->interface_uuid); + g_free(ovs_interface->name); + g_free(ovs_interface->connection_uuid); + g_free(ovs_interface->type); + nm_g_array_unref(ovs_interface->external_ids); + nm_g_slice_free(ovs_interface); +} + +/*****************************************************************************/ + +static void +_signal_emit_device_added(NMOvsdb * self, + const char * name, + NMDeviceType device_type, + const char * device_subtype) +{ + g_signal_emit(self, signals[DEVICE_ADDED], 0, name, (guint) device_type, device_subtype); +} + +static void +_signal_emit_device_removed(NMOvsdb * self, + const char * name, + NMDeviceType device_type, + const char * device_subtype) +{ + g_signal_emit(self, signals[DEVICE_REMOVED], 0, name, (guint) device_type, device_subtype); +} + +static void +_signal_emit_interface_failed(NMOvsdb * self, + const char *name, + const char *connection_uuid, + const char *error) +{ + g_signal_emit(self, signals[INTERFACE_FAILED], 0, name, connection_uuid, error); +} + +/*****************************************************************************/ + +/** + * ovsdb_call_method: + * + * Queues the ovsdb command. Eventually fires the command right away if + * there's no command pending completion. + */ +static void +ovsdb_call_method(NMOvsdb * self, + OvsdbMethodCallback callback, + gpointer user_data, + gboolean add_first, + OvsdbCommand command, + const OvsdbMethodPayload *payload) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + OvsdbMethodCall *call; + + /* Ensure we're not unsynchronized before we queue the method call. */ + ovsdb_try_connect(self); + + call = g_slice_new(OvsdbMethodCall); + *call = (OvsdbMethodCall){ + .self = self, + .call_id = CALL_ID_UNSPEC, + .command = command, + .callback = callback, + .user_data = user_data, + }; + + if (add_first) + c_list_link_front(&priv->calls_lst_head, &call->calls_lst); + else + c_list_link_tail(&priv->calls_lst_head, &call->calls_lst); + + /* Migrate the arguments from @payload to @call->payload. Technically, + * this is not a plain copy, because + * - call->payload is not initialized (thus no need to free the previous data). + * - payload does not own the data. It is merely initialized using the + * OVSDB_METHOD_PAYLOAD_*() macros. */ + switch (command) { + case OVSDB_MONITOR: + _LOGT_call(call, "new: monitor"); + break; + case OVSDB_ADD_INTERFACE: + /* FIXME(applied-connection-immutable): we should not modify the applied + * connection, consequently there is no need to clone the connections. */ + call->payload.add_interface.bridge = + nm_simple_connection_new_clone(payload->add_interface.bridge); + call->payload.add_interface.port = + nm_simple_connection_new_clone(payload->add_interface.port); + call->payload.add_interface.interface = + nm_simple_connection_new_clone(payload->add_interface.interface); + call->payload.add_interface.bridge_device = + g_object_ref(payload->add_interface.bridge_device); + call->payload.add_interface.interface_device = + g_object_ref(payload->add_interface.interface_device); + _LOGT_call(call, + "new: add-interface bridge=%s port=%s interface=%s", + nm_connection_get_interface_name(call->payload.add_interface.bridge), + nm_connection_get_interface_name(call->payload.add_interface.port), + nm_connection_get_interface_name(call->payload.add_interface.interface)); + break; + case OVSDB_DEL_INTERFACE: + call->payload.del_interface.ifname = g_strdup(payload->del_interface.ifname); + _LOGT_call(call, "new: del-interface interface=%s", call->payload.del_interface.ifname); + break; + case OVSDB_SET_INTERFACE_MTU: + call->payload.set_interface_mtu.ifname = g_strdup(payload->set_interface_mtu.ifname); + call->payload.set_interface_mtu.mtu = payload->set_interface_mtu.mtu; + _LOGT_call(call, + "new: set-interface-mtu interface=%s mtu=%u", + call->payload.set_interface_mtu.ifname, + call->payload.set_interface_mtu.mtu); + break; + case OVSDB_SET_EXTERNAL_IDS: + call->payload.set_external_ids.device_type = payload->set_external_ids.device_type; + call->payload.set_external_ids.ifname = g_strdup(payload->set_external_ids.ifname); + call->payload.set_external_ids.connection_uuid = + g_strdup(payload->set_external_ids.connection_uuid); + call->payload.set_external_ids.exid_old = + nm_g_hash_table_ref(payload->set_external_ids.exid_old); + call->payload.set_external_ids.exid_new = + nm_g_hash_table_ref(payload->set_external_ids.exid_new); + _LOGT_call(call, + "new: set-external-ids con-uuid=%s, interface=%s", + call->payload.set_external_ids.connection_uuid, + call->payload.set_external_ids.ifname); + break; + } + + ovsdb_next_command(self); +} + +/*****************************************************************************/ + +/* Create and process the JSON-RPC messages from ovsdb. */ + +/** + * _expect_ovs_bridges: + * + * Return a command that will fail the transaction if the actual set of + * bridges doesn't match @bridges. This is a way of detecting race conditions + * with other ovsdb clients that might be adding or removing bridges + * at the same time. + */ +static void +_expect_ovs_bridges(json_t *params, const char *db_uuid, json_t *bridges) +{ + json_array_append_new( + params, + json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, [s, s]]]}", + "op", + "wait", + "table", + "Open_vSwitch", + "timeout", + 0, + "columns", + "bridges", + "until", + "==", + "rows", + "bridges", + "set", + bridges, + "where", + "_uuid", + "==", + "uuid", + db_uuid)); +} + +/** + * _set_ovs_bridges: + * + * Return a command that will update the list of bridges in @db_uuid + * database to @new_bridges. + */ +static void +_set_ovs_bridges(json_t *params, const char *db_uuid, json_t *new_bridges) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, [s, s]]]}", + "op", + "update", + "table", + "Open_vSwitch", + "row", + "bridges", + "set", + new_bridges, + "where", + "_uuid", + "==", + "uuid", + db_uuid)); +} + +/** + * _expect_bridge_ports: + * + * Return a command that will fail the transaction if the actual set of + * ports in bridge @ifname doesn't match @ports. This is a way of detecting + * race conditions with other ovsdb clients that might be adding or removing + * bridge ports at the same time. + */ +static void +_expect_bridge_ports(json_t *params, const char *ifname, json_t *ports) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}", + "op", + "wait", + "table", + "Bridge", + "timeout", + 0, + "columns", + "ports", + "until", + "==", + "rows", + "ports", + "set", + ports, + "where", + "name", + "==", + ifname)); +} + +/** + * _set_bridge_ports: + * + * Return a command that will update the list of ports of bridge + * @ifname to @new_ports. + */ +static void +_set_bridge_ports(json_t *params, const char *ifname, json_t *new_ports) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}", + "op", + "update", + "table", + "Bridge", + "row", + "ports", + "set", + new_ports, + "where", + "name", + "==", + ifname)); +} + +static void +_set_bridge_mac(json_t *params, const char *ifname, const char *mac) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:{s:[s, [[s, s]]]}, s:[[s, s, s]]}", + "op", + "update", + "table", + "Bridge", + "row", + "other_config", + "map", + "hwaddr", + mac, + "where", + "name", + "==", + ifname)); +} + +/** + * _expect_port_interfaces: + * + * Return a command that will fail the transaction if the actual set of + * interfaces in port @ifname doesn't match @interfaces. This is a way of + * detecting race conditions with other ovsdb clients that might be adding + * or removing port interfaces at the same time. + */ +static void +_expect_port_interfaces(json_t *params, const char *ifname, json_t *interfaces) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}", + "op", + "wait", + "table", + "Port", + "timeout", + 0, + "columns", + "interfaces", + "until", + "==", + "rows", + "interfaces", + "set", + interfaces, + "where", + "name", + "==", + ifname)); +} + +/** + * _set_port_interfaces: + * + * Return a command that will update the list of interfaces of port @ifname + * to @new_interfaces. + */ +static void +_set_port_interfaces(json_t *params, const char *ifname, json_t *new_interfaces) +{ + json_array_append_new(params, + json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}", + "op", + "update", + "table", + "Port", + "row", + "interfaces", + "set", + new_interfaces, + "where", + "name", + "==", + ifname)); +} + +static json_t * +_j_create_external_ids_array_new(NMConnection *connection) +{ + json_t * array; + const char *const * external_ids = NULL; + guint n_external_ids = 0; + guint i; + const char * uuid; + NMSettingOvsExternalIDs *s_exid; + + nm_assert(NM_IS_CONNECTION(connection)); + + array = json_array(); + + uuid = nm_connection_get_uuid(connection); + nm_assert(uuid); + json_array_append_new(array, json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, uuid)); + + s_exid = _nm_connection_get_setting(connection, NM_TYPE_SETTING_OVS_EXTERNAL_IDS); + if (s_exid) + external_ids = nm_setting_ovs_external_ids_get_data_keys(s_exid, &n_external_ids); + for (i = 0; i < n_external_ids; i++) { + const char *k = external_ids[i]; + + json_array_append_new( + array, + json_pack("[s, s]", k, nm_setting_ovs_external_ids_get_data(s_exid, k))); + } + + return json_pack("[s, o]", "map", array); +} + +static json_t * +_j_create_external_ids_array_update(const char *connection_uuid, + GHashTable *exid_old, + GHashTable *exid_new) +{ + GHashTableIter iter; + json_t * mutations; + json_t * array; + const char * key; + const char * val; + + nm_assert(connection_uuid); + + mutations = json_array(); + + if (exid_old) { + array = NULL; + g_hash_table_iter_init(&iter, exid_old); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { + if (nm_g_hash_table_contains(exid_new, key)) + continue; + if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX)) + continue; + + if (!array) + array = json_array(); + + json_array_append_new(array, json_string(key)); + } + if (array) { + json_array_append_new( + mutations, + json_pack("[s, s, [s, o]]", "external_ids", "delete", "set", array)); + } + } + + array = json_array(); + + json_array_append_new( + array, + json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, connection_uuid)); + + if (exid_new) { + g_hash_table_iter_init(&iter, exid_new); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX)) + continue; + json_array_append_new(array, json_pack("[s, s]", key, val)); + } + } + + json_array_append_new(mutations, + json_pack("[s, s, [s, o]]", "external_ids", "insert", "map", array)); + return mutations; +} + +/** + * _insert_interface: + * + * Returns an commands that adds new interface from a given connection. + */ +static void +_insert_interface(json_t * params, + NMConnection *interface, + NMDevice * interface_device, + const char * cloned_mac) +{ + const char * type = NULL; + NMSettingOvsInterface *s_ovs_iface; + NMSettingOvsDpdk * s_ovs_dpdk; + NMSettingOvsPatch * s_ovs_patch; + json_t * options = json_array(); + json_t * row; + guint32 mtu = 0; + + s_ovs_iface = nm_connection_get_setting_ovs_interface(interface); + if (s_ovs_iface) + type = nm_setting_ovs_interface_get_interface_type(s_ovs_iface); + + if (nm_streq0(type, "internal")) { + NMSettingWired *s_wired; + + s_wired = _nm_connection_get_setting(interface, NM_TYPE_SETTING_WIRED); + if (s_wired) + mtu = nm_setting_wired_get_mtu(s_wired); + } + + json_array_append_new(options, json_string("map")); + + s_ovs_dpdk = + (NMSettingOvsDpdk *) nm_connection_get_setting(interface, NM_TYPE_SETTING_OVS_DPDK); + if (!s_ovs_dpdk) + s_ovs_patch = nm_connection_get_setting_ovs_patch(interface); + + if (s_ovs_dpdk) { + json_array_append_new( + options, + json_pack("[[s, s]]", "dpdk-devargs", nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk))); + } else if (s_ovs_patch) { + json_array_append_new( + options, + json_pack("[[s, s]]", "peer", nm_setting_ovs_patch_get_peer(s_ovs_patch))); + } else { + json_array_append_new(options, json_array()); + } + + row = json_pack("{s:s, s:s, s:o, s:o}", + "name", + nm_connection_get_interface_name(interface), + "type", + type ?: "", + "options", + options, + "external_ids", + _j_create_external_ids_array_new(interface)); + + if (cloned_mac) + json_object_set_new(row, "mac", json_string(cloned_mac)); + + if (mtu != 0) + json_object_set_new(row, "mtu_request", json_integer(mtu)); + + json_array_append_new(params, + json_pack("{s:s, s:s, s:o, s:s}", + "op", + "insert", + "table", + "Interface", + "row", + row, + "uuid-name", + "rowInterface")); +} + +/** + * _insert_port: + * + * Returns an commands that adds new port from a given connection. + */ +static void +_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces) +{ + NMSettingOvsPort *s_ovs_port; + const char * vlan_mode = NULL; + guint tag = 0; + const char * lacp = NULL; + const char * bond_mode = NULL; + guint bond_updelay = 0; + guint bond_downdelay = 0; + json_t * row; + + s_ovs_port = nm_connection_get_setting_ovs_port(port); + + row = json_object(); + + if (s_ovs_port) { + vlan_mode = nm_setting_ovs_port_get_vlan_mode(s_ovs_port); + tag = nm_setting_ovs_port_get_tag(s_ovs_port); + lacp = nm_setting_ovs_port_get_lacp(s_ovs_port); + bond_mode = nm_setting_ovs_port_get_bond_mode(s_ovs_port); + bond_updelay = nm_setting_ovs_port_get_bond_updelay(s_ovs_port); + bond_downdelay = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port); + } + + if (vlan_mode) + json_object_set_new(row, "vlan_mode", json_string(vlan_mode)); + if (tag) + json_object_set_new(row, "tag", json_integer(tag)); + if (lacp) + json_object_set_new(row, "lacp", json_string(lacp)); + if (bond_mode) + json_object_set_new(row, "bond_mode", json_string(bond_mode)); + if (bond_updelay) + json_object_set_new(row, "bond_updelay", json_integer(bond_updelay)); + if (bond_downdelay) + json_object_set_new(row, "bond_downdelay", json_integer(bond_downdelay)); + + json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port))); + json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces)); + json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(port)); + + /* Create a new one. */ + json_array_append_new(params, + json_pack("{s:s, s:s, s:o, s:s}", + "op", + "insert", + "table", + "Port", + "row", + row, + "uuid-name", + "rowPort")); +} + +/** + * _insert_bridge: + * + * Returns an commands that adds new bridge from a given connection. + */ +static void +_insert_bridge(json_t * params, + NMConnection *bridge, + NMDevice * bridge_device, + json_t * new_ports, + const char * cloned_mac) +{ + NMSettingOvsBridge *s_ovs_bridge; + const char * fail_mode = NULL; + gboolean mcast_snooping_enable = FALSE; + gboolean rstp_enable = FALSE; + gboolean stp_enable = FALSE; + const char * datapath_type = NULL; + json_t * row; + + s_ovs_bridge = nm_connection_get_setting_ovs_bridge(bridge); + + row = json_object(); + + if (s_ovs_bridge) { + fail_mode = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge); + mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge); + rstp_enable = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge); + stp_enable = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge); + datapath_type = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge); + } + + if (fail_mode) + json_object_set_new(row, "fail_mode", json_string(fail_mode)); + if (mcast_snooping_enable) + json_object_set_new(row, "mcast_snooping_enable", json_boolean(mcast_snooping_enable)); + if (rstp_enable) + json_object_set_new(row, "rstp_enable", json_boolean(rstp_enable)); + if (stp_enable) + json_object_set_new(row, "stp_enable", json_boolean(stp_enable)); + if (datapath_type) + json_object_set_new(row, "datapath_type", json_string(datapath_type)); + + json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge))); + json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports)); + json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(bridge)); + + if (cloned_mac) { + json_object_set_new(row, + "other_config", + json_pack("[s, [[s, s]]]", "map", "hwaddr", cloned_mac)); + } + + /* Create a new one. */ + json_array_append_new(params, + json_pack("{s:s, s:s, s:o, s:s}", + "op", + "insert", + "table", + "Bridge", + "row", + row, + "uuid-name", + "rowBridge")); +} + +/** + * _inc_next_cfg: + * + * Returns an mutate command that bumps next_cfg upon successful completion + * of the transaction it is in. + */ +static json_t * +_inc_next_cfg(const char *db_uuid) +{ + return json_pack("{s:s, s:s, s:[[s, s, i]], s:[[s, s, [s, s]]]}", + "op", + "mutate", + "table", + "Open_vSwitch", + "mutations", + "next_cfg", + "+=", + 1, + "where", + "_uuid", + "==", + "uuid", + db_uuid); +} + +/** + * _add_interface: + * + * Adds an interface as specified by @interface connection, optionally creating + * a parent @port and @bridge if needed. + */ +static void +_add_interface(NMOvsdb * self, + json_t * params, + NMConnection *bridge, + NMConnection *port, + NMConnection *interface, + NMDevice * bridge_device, + NMDevice * interface_device) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + GHashTableIter iter; + const char * port_uuid; + const char * interface_uuid; + const char * bridge_name; + const char * port_name; + const char * interface_name; + OpenvswitchBridge * ovs_bridge = NULL; + OpenvswitchPort * ovs_port = NULL; + OpenvswitchInterface *ovs_interface = NULL; + nm_auto_decref_json json_t *bridges = NULL; + nm_auto_decref_json json_t *new_bridges = NULL; + nm_auto_decref_json json_t *ports = NULL; + nm_auto_decref_json json_t *new_ports = NULL; + nm_auto_decref_json json_t *interfaces = NULL; + nm_auto_decref_json json_t *new_interfaces = NULL; + gboolean has_interface = FALSE; + gboolean interface_is_local; + gs_free char * bridge_cloned_mac = NULL; + gs_free char * interface_cloned_mac = NULL; + GError * error = NULL; + int pi; + int ii; + + bridges = json_array(); + ports = json_array(); + interfaces = json_array(); + new_bridges = json_array(); + new_ports = json_array(); + new_interfaces = json_array(); + + bridge_name = nm_connection_get_interface_name(bridge); + port_name = nm_connection_get_interface_name(port); + interface_name = nm_connection_get_interface_name(interface); + interface_is_local = nm_streq0(bridge_name, interface_name); + + /* Determine cloned MAC addresses */ + if (!nm_device_hw_addr_get_cloned(bridge_device, + bridge, + FALSE, + &bridge_cloned_mac, + NULL, + &error)) { + _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s", + "bridge", + bridge_name, + error->message); + g_clear_error(&error); + } + + if (!nm_device_hw_addr_get_cloned(interface_device, + interface, + FALSE, + &interface_cloned_mac, + NULL, + &error)) { + _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s", + "interface", + interface_name, + error->message); + g_clear_error(&error); + } + + /* For local interfaces, ovs complains if it finds a + * MAC address in the Interface table because it only takes + * the MAC from the Bridge table. + * Set any cloned MAC present in a local interface connection + * into the Bridge table, unless conflicting with the bridge MAC. */ + if (interface_is_local && interface_cloned_mac) { + if (bridge_cloned_mac && !nm_streq(interface_cloned_mac, bridge_cloned_mac)) { + _LOGW("Cloned MAC '%s' of local ovs-interface '%s' conflicts with MAC '%s' of bridge " + "'%s'", + interface_cloned_mac, + interface_name, + bridge_cloned_mac, + bridge_name); + nm_clear_g_free(&interface_cloned_mac); + } else { + nm_clear_g_free(&bridge_cloned_mac); + bridge_cloned_mac = g_steal_pointer(&interface_cloned_mac); + _LOGT("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'", + interface_name, + bridge_name); + } + } + + g_hash_table_iter_init(&iter, priv->bridges); + while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) { + json_array_append_new(bridges, json_pack("[s, s]", "uuid", ovs_bridge->bridge_uuid)); + + if (!nm_streq0(ovs_bridge->name, bridge_name) + || !nm_streq0(ovs_bridge->connection_uuid, nm_connection_get_uuid(bridge))) + continue; + + for (pi = 0; pi < ovs_bridge->ports->len; pi++) { + port_uuid = g_ptr_array_index(ovs_bridge->ports, pi); + ovs_port = g_hash_table_lookup(priv->ports, &port_uuid); + + json_array_append_new(ports, json_pack("[s, s]", "uuid", port_uuid)); + + if (!ovs_port) { + /* This would be a violation of ovsdb's reference integrity (a bug). */ + _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid); + continue; + } + + if (!nm_streq(ovs_port->name, port_name) + || !nm_streq0(ovs_port->connection_uuid, nm_connection_get_uuid(port))) + continue; + + for (ii = 0; ii < ovs_port->interfaces->len; ii++) { + interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii); + ovs_interface = g_hash_table_lookup(priv->interfaces, &interface_uuid); + + json_array_append_new(interfaces, json_pack("[s, s]", "uuid", interface_uuid)); + + if (!ovs_interface) { + /* This would be a violation of ovsdb's reference integrity (a bug). */ + _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid); + continue; + } + if (nm_streq(ovs_interface->name, interface_name) + && nm_streq0(ovs_interface->connection_uuid, nm_connection_get_uuid(interface))) + has_interface = TRUE; + } + + break; + } + + break; + } + + json_array_extend(new_bridges, bridges); + json_array_extend(new_ports, ports); + json_array_extend(new_interfaces, interfaces); + + if (json_array_size(interfaces) == 0) { + /* Need to create a port. */ + if (json_array_size(ports) == 0) { + /* Need to create a bridge. */ + _expect_ovs_bridges(params, priv->db_uuid, bridges); + json_array_append_new(new_bridges, json_pack("[s, s]", "named-uuid", "rowBridge")); + _set_ovs_bridges(params, priv->db_uuid, new_bridges); + _insert_bridge(params, bridge, bridge_device, new_ports, bridge_cloned_mac); + } else { + /* Bridge already exists. */ + g_return_if_fail(ovs_bridge); + _expect_bridge_ports(params, ovs_bridge->name, ports); + _set_bridge_ports(params, bridge_name, new_ports); + if (bridge_cloned_mac && interface_is_local) + _set_bridge_mac(params, bridge_name, bridge_cloned_mac); + } + + json_array_append_new(new_ports, json_pack("[s, s]", "named-uuid", "rowPort")); + _insert_port(params, port, new_interfaces); + } else { + /* Port already exists */ + g_return_if_fail(ovs_port); + _expect_port_interfaces(params, ovs_port->name, interfaces); + _set_port_interfaces(params, port_name, new_interfaces); + } + + if (!has_interface) { + _insert_interface(params, interface, interface_device, interface_cloned_mac); + json_array_append_new(new_interfaces, json_pack("[s, s]", "named-uuid", "rowInterface")); + } +} + +/** + * _delete_interface: + * + * Removes an interface of @ifname name, collecting empty ports and bridge + * if last item is removed from them. + */ +static void +_delete_interface(NMOvsdb *self, json_t *params, const char *ifname) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + GHashTableIter iter; + char * port_uuid; + char * interface_uuid; + OpenvswitchBridge * ovs_bridge; + OpenvswitchPort * ovs_port; + OpenvswitchInterface *ovs_interface; + nm_auto_decref_json json_t *bridges = NULL; + nm_auto_decref_json json_t *new_bridges = NULL; + gboolean bridges_changed; + gboolean ports_changed; + gboolean interfaces_changed; + int pi; + int ii; + + bridges = json_array(); + new_bridges = json_array(); + bridges_changed = FALSE; + + g_hash_table_iter_init(&iter, priv->bridges); + while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) { + nm_auto_decref_json json_t *ports = NULL; + nm_auto_decref_json json_t *new_ports = NULL; + + ports = json_array(); + new_ports = json_array(); + ports_changed = FALSE; + + json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid)); + + for (pi = 0; pi < ovs_bridge->ports->len; pi++) { + nm_auto_decref_json json_t *interfaces = NULL; + nm_auto_decref_json json_t *new_interfaces = NULL; + + interfaces = json_array(); + new_interfaces = json_array(); + port_uuid = g_ptr_array_index(ovs_bridge->ports, pi); + ovs_port = g_hash_table_lookup(priv->ports, &port_uuid); + + json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid)); + + interfaces_changed = FALSE; + + if (!ovs_port) { + /* This would be a violation of ovsdb's reference integrity (a bug). */ + _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid); + continue; + } + + for (ii = 0; ii < ovs_port->interfaces->len; ii++) { + interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii); + ovs_interface = g_hash_table_lookup(priv->interfaces, &interface_uuid); + + json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid)); + + if (ovs_interface) { + if (nm_streq(ovs_interface->name, ifname)) { + /* skip the interface */ + interfaces_changed = TRUE; + continue; + } + } else { + /* This would be a violation of ovsdb's reference integrity (a bug). */ + _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid); + } + + json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid)); + } + + if (json_array_size(new_interfaces) == 0) { + ports_changed = TRUE; + } else { + if (interfaces_changed) { + _expect_port_interfaces(params, ovs_port->name, interfaces); + _set_port_interfaces(params, ovs_port->name, new_interfaces); + } + json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid)); + } + } + + if (json_array_size(new_ports) == 0) { + bridges_changed = TRUE; + } else { + if (ports_changed) { + _expect_bridge_ports(params, ovs_bridge->name, ports); + _set_bridge_ports(params, ovs_bridge->name, new_ports); + } + json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid)); + } + } + + if (bridges_changed) { + _expect_ovs_bridges(params, priv->db_uuid, bridges); + _set_ovs_bridges(params, priv->db_uuid, new_bridges); + } +} + +/** + * ovsdb_next_command: + * + * Translates a higher level operation (add/remove bridge/port) to a RFC 7047 + * command serialized into JSON ands sends it over to the database. + + * Only called when no command is waiting for a response, since the serialized + * command might depend on result of a previous one (add and remove need to + * include an up to date bridge list in their transactions to rule out races). + */ +static void +ovsdb_next_command(NMOvsdb *self) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + OvsdbMethodCall * call; + char * cmd; + nm_auto_decref_json json_t *msg = NULL; + + if (!priv->conn) + return; + + if (c_list_is_empty(&priv->calls_lst_head)) + return; + + call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); + if (call->call_id != CALL_ID_UNSPEC) + return; + + call->call_id = ++priv->call_id_counter; + + switch (call->command) { + case OVSDB_MONITOR: + msg = json_pack("{s:I, s:s, s:[s, n, {" + " s:[{s:[s, s, s]}]," + " s:[{s:[s, s, s]}]," + " s:[{s:[s, s, s, s]}]," + " s:[{s:[]}]" + "}]}", + "id", + (json_int_t) call->call_id, + "method", + "monitor", + "params", + "Open_vSwitch", + "Bridge", + "columns", + "name", + "ports", + "external_ids", + "Port", + "columns", + "name", + "interfaces", + "external_ids", + "Interface", + "columns", + "name", + "type", + "external_ids", + "error", + "Open_vSwitch", + "columns"); + break; + default: + { + json_t *params = NULL; + + params = json_array(); + json_array_append_new(params, json_string("Open_vSwitch")); + json_array_append_new(params, _inc_next_cfg(priv->db_uuid)); + + switch (call->command) { + case OVSDB_ADD_INTERFACE: + _add_interface(self, + params, + call->payload.add_interface.bridge, + call->payload.add_interface.port, + call->payload.add_interface.interface, + call->payload.add_interface.bridge_device, + call->payload.add_interface.interface_device); + break; + case OVSDB_DEL_INTERFACE: + _delete_interface(self, params, call->payload.del_interface.ifname); + break; + case OVSDB_SET_INTERFACE_MTU: + json_array_append_new(params, + json_pack("{s:s, s:s, s:{s: I}, s:[[s, s, s]]}", + "op", + "update", + "table", + "Interface", + "row", + "mtu_request", + (json_int_t) call->payload.set_interface_mtu.mtu, + "where", + "name", + "==", + call->payload.set_interface_mtu.ifname)); + break; + case OVSDB_SET_EXTERNAL_IDS: + json_array_append_new( + params, + json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}", + "op", + "mutate", + "table", + _device_type_to_table(call->payload.set_external_ids.device_type), + "mutations", + _j_create_external_ids_array_update( + call->payload.set_external_ids.connection_uuid, + call->payload.set_external_ids.exid_old, + call->payload.set_external_ids.exid_new), + "where", + "name", + "==", + call->payload.set_external_ids.ifname)); + break; + + default: + nm_assert_not_reached(); + break; + } + + msg = json_pack("{s:I, s:s, s:o}", + "id", + (json_int_t) call->call_id, + "method", + "transact", + "params", + params); + break; + } + } + + g_return_if_fail(msg); + + cmd = json_dumps(msg, 0); + _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd); + g_string_append(priv->output, cmd); + free(cmd); + + ovsdb_write(self); +} + +/** + * _uuids_to_array: + * + * This tidies up the somewhat non-straightforward way ovsdb represents an array + * of UUID elements. The single element is a tuple (called in RFC7047), + * + * [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ] + * + * while the list of multiple UUIDs are turned into a set of such tuples ("atoms"): + * + * [ "set", [ [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ], + * [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ] + */ +static void +_uuids_to_array_inplace(GPtrArray *array, const json_t *items) +{ + const char *key; + json_t * value; + size_t index = 0; + json_t * set_value; + size_t set_index; + + while (index < json_array_size(items)) { + key = json_string_value(json_array_get(items, index)); + index++; + value = json_array_get(items, index); + index++; + + if (!value || !key) + return; + + if (nm_streq(key, "uuid")) { + if (json_is_string(value)) + g_ptr_array_add(array, g_strdup(json_string_value(value))); + continue; + } + if (nm_streq(key, "set")) { + if (json_is_array(value)) { + json_array_foreach (value, set_index, set_value) + _uuids_to_array_inplace(array, set_value); + } + continue; + } + } +} + +static GPtrArray * +_uuids_to_array(const json_t *items) +{ + GPtrArray *array; + + array = g_ptr_array_new_with_free_func(g_free); + _uuids_to_array_inplace(array, items); + return array; +} + +static void +_external_ids_extract(json_t *external_ids, GArray **out_array, const char **out_connection_uuid) +{ + json_t *array; + json_t *value; + gsize index; + + nm_assert(out_array && !*out_array); + nm_assert(!out_connection_uuid || !*out_connection_uuid); + + if (!nm_streq0("map", json_string_value(json_array_get(external_ids, 0)))) + return; + + array = json_array_get(external_ids, 1); + + json_array_foreach (array, index, value) { + const char * key = json_string_value(json_array_get(value, 0)); + const char * val = json_string_value(json_array_get(value, 1)); + NMUtilsNamedValue *v; + + if (!key || !val) + continue; + + if (!*out_array) { + *out_array = g_array_new(FALSE, FALSE, sizeof(NMUtilsNamedValue)); + g_array_set_clear_func(*out_array, + (GDestroyNotify) nm_utils_named_value_clear_with_g_free); + } + + v = nm_g_array_append_new(*out_array, NMUtilsNamedValue); + *v = (NMUtilsNamedValue){ + .name = g_strdup(key), + .value_str = g_strdup(val), + }; + + if (out_connection_uuid && nm_streq(v->name, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID)) { + *out_connection_uuid = v->value_str; + out_connection_uuid = NULL; + } + } +} + +static gboolean +_external_ids_equal(const GArray *arr1, const GArray *arr2) +{ + guint n; + guint i; + + n = nm_g_array_len(arr1); + + if (n != nm_g_array_len(arr2)) + return FALSE; + for (i = 0; i < n; i++) { + const NMUtilsNamedValue *n1 = &g_array_index(arr1, NMUtilsNamedValue, i); + const NMUtilsNamedValue *n2 = &g_array_index(arr2, NMUtilsNamedValue, i); + + if (!nm_streq0(n1->name, n2->name)) + return FALSE; + if (!nm_streq0(n1->value_str, n2->value_str)) + return FALSE; + } + return TRUE; +} + +static char * +_external_ids_to_string(const GArray *arr) +{ + NMStrBuf strbuf; + guint i; + + if (!arr) + return g_strdup("empty"); + + nm_str_buf_init(&strbuf, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); + nm_str_buf_append(&strbuf, "["); + for (i = 0; i < arr->len; i++) { + const NMUtilsNamedValue *n = &g_array_index(arr, NMUtilsNamedValue, i); + + if (i > 0) + nm_str_buf_append_c(&strbuf, ','); + nm_str_buf_append_printf(&strbuf, " \"%s\" = \"%s\"]", n->name, n->value_str); + } + nm_str_buf_append(&strbuf, " ]"); + + return nm_str_buf_finalize(&strbuf, NULL); +} + +/*****************************************************************************/ + +/** + * ovsdb_got_update: + * + * Called when we've got an "update" method call (we asked for it with the monitor + * command). We use it to maintain a consistent view of bridge list regardless of + * whether the changes are done by us or externally. + */ +static void +ovsdb_got_update(NMOvsdb *self, json_t *msg) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + json_t * ovs = NULL; + json_t * bridge = NULL; + json_t * port = NULL; + json_t * interface = NULL; + json_t * items; + json_t * external_ids; + json_error_t json_error = { + 0, + }; + void * iter; + const char *name; + const char *key; + const char *type; + json_t * value; + + if (json_unpack_ex(msg, + &json_error, + 0, + "{s?:o, s?:o, s?:o, s?:o}", + "Open_vSwitch", + &ovs, + "Bridge", + &bridge, + "Port", + &port, + "Interface", + &interface) + == -1) { + /* This doesn't really have to be an error; the key might + * be missing if there really are no bridges present. */ + _LOGD("Bad update: %s", json_error.text); + } + + if (ovs) { + const char *s; + + iter = json_object_iter(ovs); + s = json_object_iter_key(iter); + if (s) + nm_utils_strdup_reset(&priv->db_uuid, s); + } + + json_object_foreach (interface, key, value) { + OpenvswitchInterface *ovs_interface; + gs_unref_array GArray *external_ids_arr = NULL; + const char * connection_uuid = NULL; + json_t * error = NULL; + int r; + + r = json_unpack(value, + "{s:{s:s, s:s, s?:o, s:o}}", + "new", + "name", + &name, + "type", + &type, + "error", + &error, + "external_ids", + &external_ids); + if (r != 0) { + gpointer unused; + + r = json_unpack(value, "{s:{}}", "old"); + if (r != 0) + continue; + + if (!g_hash_table_steal_extended(priv->interfaces, + &key, + (gpointer *) &ovs_interface, + &unused)) + continue; + + _LOGT("obj[iface:%s]: removed an '%s' interface: %s%s%s", + key, + ovs_interface->type, + ovs_interface->name, + NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, + ", ", + ovs_interface->connection_uuid, + "")); + _signal_emit_device_removed(self, + ovs_interface->name, + NM_DEVICE_TYPE_OVS_INTERFACE, + ovs_interface->type); + _free_interface(ovs_interface); + continue; + } + + ovs_interface = g_hash_table_lookup(priv->interfaces, &key); + + if (ovs_interface + && (!nm_streq0(ovs_interface->name, name) || !nm_streq0(ovs_interface->type, type))) { + if (!g_hash_table_steal(priv->interfaces, ovs_interface)) + nm_assert_not_reached(); + _signal_emit_device_removed(self, + ovs_interface->name, + NM_DEVICE_TYPE_OVS_INTERFACE, + ovs_interface->type); + nm_clear_pointer(&ovs_interface, _free_interface); + } + + _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid); + + if (ovs_interface) { + gboolean changed = FALSE; + + nm_assert(nm_streq0(ovs_interface->name, name)); + + changed |= nm_utils_strdup_reset(&ovs_interface->type, type); + changed |= nm_utils_strdup_reset(&ovs_interface->connection_uuid, connection_uuid); + if (!_external_ids_equal(ovs_interface->external_ids, external_ids_arr)) { + NM_SWAP(&ovs_interface->external_ids, &external_ids_arr); + changed = TRUE; + } + if (changed) { + gs_free char *strtmp = NULL; + + _LOGT("obj[iface:%s]: changed an '%s' interface: %s%s%s, external-ids=%s", + key, + type, + ovs_interface->name, + NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, + ", ", + ovs_interface->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_interface->external_ids))); + } + } else { + gs_free char *strtmp = NULL; + + ovs_interface = g_slice_new(OpenvswitchInterface); + *ovs_interface = (OpenvswitchInterface){ + .interface_uuid = g_strdup(key), + .name = g_strdup(name), + .type = g_strdup(type), + .connection_uuid = g_strdup(connection_uuid), + .external_ids = g_steal_pointer(&external_ids_arr), + }; + g_hash_table_add(priv->interfaces, ovs_interface); + _LOGT("obj[iface:%s]: added an '%s' interface: %s%s%s, external-ids=%s", + key, + ovs_interface->type, + ovs_interface->name, + NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, + ", ", + ovs_interface->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_interface->external_ids))); + _signal_emit_device_added(self, + ovs_interface->name, + NM_DEVICE_TYPE_OVS_INTERFACE, + ovs_interface->type); + } + + /* The error is a string. No error is indicated by an empty set, + * Why not: [ "set": [] ] ? */ + if (error && json_is_string(error)) { + _signal_emit_interface_failed(self, + ovs_interface->name, + ovs_interface->connection_uuid, + json_string_value(error)); + } + } + + json_object_foreach (port, key, value) { + gs_unref_ptrarray GPtrArray *interfaces = NULL; + OpenvswitchPort * ovs_port; + gs_unref_array GArray *external_ids_arr = NULL; + const char * connection_uuid = NULL; + int r; + + r = json_unpack(value, + "{s:{s:s, s:o, s:o}}", + "new", + "name", + &name, + "external_ids", + &external_ids, + "interfaces", + &items); + if (r != 0) { + gpointer unused; + + r = json_unpack(value, "{s:{}}", "old"); + if (r != 0) + continue; + + if (!g_hash_table_steal_extended(priv->ports, &key, (gpointer *) &ovs_port, &unused)) + continue; + + _LOGT("obj[port:%s]: removed a port: %s%s%s", + key, + ovs_port->name, + NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, + ", ", + ovs_port->connection_uuid, + "")); + _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); + _free_port(ovs_port); + continue; + } + + ovs_port = g_hash_table_lookup(priv->ports, &key); + + if (ovs_port && !nm_streq0(ovs_port->name, name)) { + if (!g_hash_table_steal(priv->ports, ovs_port)) + nm_assert_not_reached(); + _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); + nm_clear_pointer(&ovs_port, _free_port); + } + + _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid); + interfaces = _uuids_to_array(items); + + if (ovs_port) { + gboolean changed = FALSE; + + nm_assert(nm_streq0(ovs_port->name, name)); + + changed |= nm_utils_strdup_reset(&ovs_port->name, name); + changed |= nm_utils_strdup_reset(&ovs_port->connection_uuid, connection_uuid); + if (nm_strv_ptrarray_cmp(ovs_port->interfaces, interfaces) != 0) { + NM_SWAP(&ovs_port->interfaces, &interfaces); + changed = TRUE; + } + if (!_external_ids_equal(ovs_port->external_ids, external_ids_arr)) { + NM_SWAP(&ovs_port->external_ids, &external_ids_arr); + changed = TRUE; + } + if (changed) { + gs_free char *strtmp = NULL; + + _LOGT("obj[port:%s]: changed a port: %s%s%s, external-ids=%s", + key, + ovs_port->name, + NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, + ", ", + ovs_port->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_port->external_ids))); + } + } else { + gs_free char *strtmp = NULL; + + ovs_port = g_slice_new(OpenvswitchPort); + *ovs_port = (OpenvswitchPort){ + .port_uuid = g_strdup(key), + .name = g_strdup(name), + .connection_uuid = g_strdup(connection_uuid), + .interfaces = g_steal_pointer(&interfaces), + .external_ids = g_steal_pointer(&external_ids_arr), + }; + g_hash_table_add(priv->ports, ovs_port); + _LOGT("obj[port:%s]: added a port: %s%s%s, external-ids=%s", + key, + ovs_port->name, + NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, + ", ", + ovs_port->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_port->external_ids))); + _signal_emit_device_added(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); + } + } + + json_object_foreach (bridge, key, value) { + gs_unref_ptrarray GPtrArray *ports = NULL; + OpenvswitchBridge * ovs_bridge; + gs_unref_array GArray *external_ids_arr = NULL; + const char * connection_uuid = NULL; + int r; + + r = json_unpack(value, + "{s:{s:s, s:o, s:o}}", + "new", + "name", + &name, + "external_ids", + &external_ids, + "ports", + &items); + + if (r != 0) { + gpointer unused; + + r = json_unpack(value, "{s:{}}", "old"); + if (r != 0) + continue; + + if (!g_hash_table_steal_extended(priv->bridges, + &key, + (gpointer *) &ovs_bridge, + &unused)) + continue; + + _LOGT("obj[bridge:%s]: removed a bridge: %s%s%s", + key, + ovs_bridge->name, + NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, + ", ", + ovs_bridge->connection_uuid, + "")); + _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); + _free_bridge(ovs_bridge); + continue; + } + + ovs_bridge = g_hash_table_lookup(priv->bridges, &key); + + if (ovs_bridge && !nm_streq0(ovs_bridge->name, name)) { + if (!g_hash_table_steal(priv->bridges, ovs_bridge)) + nm_assert_not_reached(); + _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); + nm_clear_pointer(&ovs_bridge, _free_bridge); + } + + _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid); + ports = _uuids_to_array(items); + + if (ovs_bridge) { + gboolean changed = FALSE; + + nm_assert(nm_streq0(ovs_bridge->name, name)); + + changed = nm_utils_strdup_reset(&ovs_bridge->name, name); + changed = nm_utils_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid); + if (nm_strv_ptrarray_cmp(ovs_bridge->ports, ports) != 0) { + NM_SWAP(&ovs_bridge->ports, &ports); + changed = TRUE; + } + if (!_external_ids_equal(ovs_bridge->external_ids, external_ids_arr)) { + NM_SWAP(&ovs_bridge->external_ids, &external_ids_arr); + changed = TRUE; + } + if (changed) { + gs_free char *strtmp = NULL; + + _LOGT("obj[bridge:%s]: changed a bridge: %s%s%s, external-ids=%s", + key, + ovs_bridge->name, + NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, + ", ", + ovs_bridge->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_bridge->external_ids))); + } + } else { + gs_free char *strtmp = NULL; + + ovs_bridge = g_slice_new(OpenvswitchBridge); + *ovs_bridge = (OpenvswitchBridge){ + .bridge_uuid = g_strdup(key), + .name = g_strdup(name), + .connection_uuid = g_strdup(connection_uuid), + .ports = g_steal_pointer(&ports), + .external_ids = g_steal_pointer(&external_ids_arr), + }; + g_hash_table_add(priv->bridges, ovs_bridge); + _LOGT("obj[bridge:%s]: added a bridge: %s%s%s, external-ids=%s", + key, + ovs_bridge->name, + NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, + ", ", + ovs_bridge->connection_uuid, + ""), + (strtmp = _external_ids_to_string(ovs_bridge->external_ids))); + _signal_emit_device_added(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); + } + } +} + +/** + * ovsdb_got_echo: + * + * Only implemented because the specification mandates it. Actual ovsdb hasn't been + * seen doing this. + */ +static void +ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + nm_auto_decref_json json_t *msg = NULL; + char * reply; + gboolean output_was_empty; + + output_was_empty = priv->output->len == 0; + + msg = json_pack("{s:I, s:O}", "id", id, "result", data); + reply = json_dumps(msg, 0); + g_string_append(priv->output, reply); + free(reply); + + if (output_was_empty) + ovsdb_write(self); +} + +/** + * ovsdb_got_msg:: + * + * Called when a complete JSON object was seen and unmarshalled. + * Either finishes a method call or processes a method call. + */ +static void +ovsdb_got_msg(NMOvsdb *self, json_t *msg) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + json_error_t json_error = { + 0, + }; + json_t * json_id = NULL; + json_int_t id = (json_int_t) -1; + const char *method = NULL; + json_t * params = NULL; + json_t * result = NULL; + json_t * error = NULL; + + if (json_unpack_ex(msg, + &json_error, + 0, + "{s?:o, s?:s, s?:o, s?:o, s?:o}", + "id", + &json_id, + "method", + &method, + "params", + ¶ms, + "result", + &result, + "error", + &error) + == -1) { + _LOGW("couldn't grok the message: %s", json_error.text); + ovsdb_disconnect(self, FALSE, FALSE); + return; + } + + if (json_is_number(json_id)) + id = json_integer_value(json_id); + + if (method) { + /* It's a method call! */ + if (!params) { + _LOGW("a method call with no params: '%s'", method); + ovsdb_disconnect(self, FALSE, FALSE); + return; + } + + if (nm_streq0(method, "update")) { + /* This is a update method call. */ + ovsdb_got_update(self, json_array_get(params, 1)); + } else if (nm_streq0(method, "echo")) { + /* This is an echo request. */ + ovsdb_got_echo(self, id, params); + } else { + _LOGW("got an unknown method call: '%s'", method); + } + return; + } + + if (id >= 0) { + OvsdbMethodCall *call; + gs_free_error GError *local = NULL; + gs_free char * msg_as_str = NULL; + + /* This is a response to a method call. */ + if (c_list_is_empty(&priv->calls_lst_head)) { + _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id); + ovsdb_disconnect(self, FALSE, FALSE); + return; + } + call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); + if (call->call_id != id) { + _LOGE("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, + call->call_id, + (guint64) id); + ovsdb_disconnect(self, FALSE, FALSE); + return; + } + /* Cool, we found a corresponding call. Finish it. */ + + _LOGT_call(call, "response: %s", (msg_as_str = json_dumps(msg, 0))); + + if (!json_is_null(error)) { + /* The response contains an error. */ + g_set_error(&local, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Error call to OVSDB returned an error: %s", + json_string_value(error)); + } + + _call_complete(call, result, local); + + priv->num_failures = 0; + + /* Don't progress further commands in case the callback hit an error + * and disconnected us. */ + if (!priv->conn) + return; + + /* Now we're free to serialize and send the next command, if any. */ + ovsdb_next_command(self); + + return; + } + + /* This is a message we are not interested in. */ + _LOGW("got an unknown message, ignoring"); +} + +/*****************************************************************************/ + +/* Lower level marshalling and demarshalling of the JSON-RPC traffic on the + * ovsdb socket. */ + +static size_t +_json_callback(void *buffer, size_t buflen, void *user_data) +{ + NMOvsdb * self = NM_OVSDB(user_data); + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + if (priv->bufp == priv->input->len) { + /* No more bytes buffered for decoding. */ + return 0; + } + + /* Pass one more byte to the JSON decoder. */ + *(char *) buffer = priv->input->str[priv->bufp]; + priv->bufp++; + + return (size_t) 1; +} + +/** + * ovsdb_read_cb: + * + * Read out the data available from the ovsdb socket and try to deserialize + * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg(). + */ +static void +ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMOvsdb * self = NM_OVSDB(user_data); + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + GInputStream * stream = G_INPUT_STREAM(source_object); + GError * error = NULL; + gssize size; + json_t * msg; + json_error_t json_error = { + 0, + }; + + size = g_input_stream_read_finish(stream, res, &error); + if (size == -1) { + /* ovsdb-server was possibly restarted */ + _LOGW("short read from ovsdb: %s", error->message); + priv->num_failures++; + g_clear_error(&error); + ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); + return; + } + + g_string_append_len(priv->input, priv->buf, size); + do { + priv->bufp = 0; + /* The callback always eats up only up to a single byte. This makes + * it possible for us to identify complete JSON objects in spite of + * us not knowing the length in advance. */ + msg = json_load_callback(_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error); + if (msg) { + ovsdb_got_msg(self, msg); + g_string_erase(priv->input, 0, priv->bufp); + } + json_decref(msg); + } while (msg); + + if (!priv->conn) + return; + + if (size) + ovsdb_read(self); +} + +static void +ovsdb_read(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + g_input_stream_read_async(g_io_stream_get_input_stream(G_IO_STREAM(priv->conn)), + priv->buf, + sizeof(priv->buf), + G_PRIORITY_DEFAULT, + NULL, + ovsdb_read_cb, + self); +} + +static void +ovsdb_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GOutputStream * stream = G_OUTPUT_STREAM(source_object); + NMOvsdb * self = NM_OVSDB(user_data); + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + GError * error = NULL; + gssize size; + + size = g_output_stream_write_finish(stream, res, &error); + if (size == -1) { + /* ovsdb-server was possibly restarted */ + _LOGW("short write to ovsdb: %s", error->message); + priv->num_failures++; + g_clear_error(&error); + ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); + return; + } + + if (!priv->conn) + return; + + g_string_erase(priv->output, 0, size); + + ovsdb_write(self); +} + +static void +ovsdb_write(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + GOutputStream * stream; + + if (!priv->output->len) + return; + + stream = g_io_stream_get_output_stream(G_IO_STREAM(priv->conn)); + if (g_output_stream_has_pending(stream)) + return; + + g_output_stream_write_async(stream, + priv->output->str, + priv->output->len, + G_PRIORITY_DEFAULT, + NULL, + ovsdb_write_cb, + self); +} + +/*****************************************************************************/ + +/* Routines to maintain the ovsdb connection. */ + +/** + * ovsdb_disconnect: + * + * Clean up the internal state to the point equivalent to before connecting. + * Apart from clean shutdown this is a good response to unexpected trouble, + * since the next method call attempt a will trigger reconnect which hopefully + * puts us back in sync. + */ +static void +ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + OvsdbMethodCall *call; + + nm_assert(!retry || !is_disposing); + + if (!priv->client) + return; + + _LOGD("disconnecting from ovsdb, retry %d", retry); + + /* FIXME(shutdown): NMOvsdb should process the pending calls before + * shutting down, and cancel the remaining calls after the timeout. */ + + if (retry) { + if (!c_list_is_empty(&priv->calls_lst_head)) { + call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); + call->call_id = CALL_ID_UNSPEC; + } + } else { + gs_free_error GError *error = NULL; + + if (is_disposing) + nm_utils_error_set_cancelled(&error, is_disposing, "NMOvsdb"); + else + nm_utils_error_set(&error, NM_UTILS_ERROR_NOT_READY, "disconnected from ovsdb"); + while ((call = c_list_last_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst))) + _call_complete(call, NULL, error); + } + + priv->bufp = 0; + g_string_truncate(priv->input, 0); + g_string_truncate(priv->output, 0); + g_clear_object(&priv->client); + g_clear_object(&priv->conn); + nm_clear_g_free(&priv->db_uuid); + nm_clear_g_cancellable(&priv->cancellable); + + if (retry) + ovsdb_try_connect(self); +} + +static void +_check_ready(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + nm_assert(!priv->ready); + + if (priv->num_pending_deletions == 0) { + priv->ready = TRUE; + g_signal_emit(self, signals[READY], 0); + nm_manager_unblock_failed_ovs_interfaces(nm_manager_get()); + } +} + +static void +_del_initial_iface_cb(GError *error, gpointer user_data) +{ + NMOvsdb * self; + gs_free char * ifname = NULL; + NMOvsdbPrivate *priv; + + nm_utils_user_data_unpack(user_data, &self, &ifname); + + if (nm_utils_error_is_cancelled_or_disposing(error)) + return; + + priv = NM_OVSDB_GET_PRIVATE(self); + nm_assert(priv->num_pending_deletions > 0); + priv->num_pending_deletions--; + + _LOGD("delete initial interface '%s': %s %s%s%s, pending %u", + ifname, + error ? "error" : "success", + error ? "(" : "", + error ? error->message : "", + error ? ")" : "", + priv->num_pending_deletions); + + _check_ready(self); +} + +static void +ovsdb_cleanup_initial_interfaces(NMOvsdb *self) +{ + NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self); + const OpenvswitchInterface *interface; + NMUtilsUserData * data; + GHashTableIter iter; + + if (priv->ready || priv->num_pending_deletions != 0) + return; + + /* Delete OVS interfaces added by NM. Bridges and ports and + * not considered because they are deleted automatically + * when no interface is present. */ + g_hash_table_iter_init(&iter, self->_priv.interfaces); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) { + if (interface->connection_uuid) { + priv->num_pending_deletions++; + _LOGD("deleting initial interface '%s' (pending: %u)", + interface->name, + priv->num_pending_deletions); + data = nm_utils_user_data_pack(self, g_strdup(interface->name)); + nm_ovsdb_del_interface(self, interface->name, _del_initial_iface_cb, data); + } + } + + _check_ready(self); +} + +static void +_monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data) +{ + if (error) { + if (!nm_utils_error_is_cancelled_or_disposing(error)) { + _LOGI("%s", error->message); + ovsdb_disconnect(self, FALSE, FALSE); + } + return; + } + + /* Treat the first response the same as the subsequent "update" + * messages we eventually get. */ + ovsdb_got_update(self, result); + + ovsdb_cleanup_initial_interfaces(self); +} + +static void +_client_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GSocketClient * client = G_SOCKET_CLIENT(source_object); + NMOvsdb * self = NM_OVSDB(user_data); + NMOvsdbPrivate * priv; + GError * error = NULL; + GSocketConnection *conn; + + conn = g_socket_client_connect_finish(client, res, &error); + if (conn == NULL) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOGI("%s", error->message); + + ovsdb_disconnect(self, FALSE, FALSE); + g_clear_error(&error); + return; + } + + priv = NM_OVSDB_GET_PRIVATE(self); + priv->conn = conn; + g_clear_object(&priv->cancellable); + + ovsdb_read(self); + ovsdb_next_command(self); +} + +/** + * ovsdb_try_connect: + * + * Establish a connection to ovsdb unless it's already established or being + * established. Queues a monitor command as a very first one so that we're in + * sync when other commands are issued. + */ +static void +ovsdb_try_connect(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + GSocketAddress *addr; + + if (priv->client) + return; + + /* TODO: This should probably be made configurable via NetworkManager.conf */ + addr = g_unix_socket_address_new(RUNSTATEDIR "/openvswitch/db.sock"); + + priv->client = g_socket_client_new(); + priv->cancellable = g_cancellable_new(); + g_socket_client_connect_async(priv->client, + G_SOCKET_CONNECTABLE(addr), + priv->cancellable, + _client_connect_cb, + self); + g_object_unref(addr); + + /* Queue a monitor call before any other command, ensuring that we have an up + * to date view of existing bridged that we need for add and remove ops. */ + ovsdb_call_method(self, + _monitor_bridges_cb, + NULL, + TRUE, + OVSDB_MONITOR, + OVSDB_METHOD_PAYLOAD_MONITOR()); +} + +/*****************************************************************************/ + +/* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of + * their ovsdb entries without having to deal with ovsdb complexities themselves. */ + +typedef struct { + NMOvsdbCallback callback; + gpointer user_data; +} OvsdbCall; + +static void +_transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data) +{ + OvsdbCall * call = user_data; + const char *err; + const char *err_details; + size_t index; + json_t * value; + + if (error) + goto out; + + json_array_foreach (result, index, value) { + if (json_unpack(value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) { + g_set_error(&error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Error running the transaction: %s: %s", + err, + err_details); + goto out; + } + } + +out: + call->callback(error, call->user_data); + nm_g_slice_free(call); +} + +static OvsdbCall * +ovsdb_call_new(NMOvsdbCallback callback, gpointer user_data) +{ + OvsdbCall *call; + + call = g_slice_new(OvsdbCall); + *call = (OvsdbCall){ + .callback = callback, + .user_data = user_data, + }; + return call; +} + +gboolean +nm_ovsdb_is_ready(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + return priv->ready; +} + +void +nm_ovsdb_add_interface(NMOvsdb * self, + NMConnection * bridge, + NMConnection * port, + NMConnection * interface, + NMDevice * bridge_device, + NMDevice * interface_device, + NMOvsdbCallback callback, + gpointer user_data) +{ + ovsdb_call_method(self, + _transact_cb, + ovsdb_call_new(callback, user_data), + FALSE, + OVSDB_ADD_INTERFACE, + OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(bridge, + port, + interface, + bridge_device, + interface_device)); +} + +void +nm_ovsdb_del_interface(NMOvsdb * self, + const char * ifname, + NMOvsdbCallback callback, + gpointer user_data) +{ + ovsdb_call_method(self, + _transact_cb, + ovsdb_call_new(callback, user_data), + FALSE, + OVSDB_DEL_INTERFACE, + OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(ifname)); +} + +void +nm_ovsdb_set_interface_mtu(NMOvsdb * self, + const char * ifname, + guint32 mtu, + NMOvsdbCallback callback, + gpointer user_data) +{ + ovsdb_call_method(self, + _transact_cb, + ovsdb_call_new(callback, user_data), + FALSE, + OVSDB_SET_INTERFACE_MTU, + OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(ifname, mtu)); +} + +void +nm_ovsdb_set_external_ids(NMOvsdb * self, + NMDeviceType device_type, + const char * ifname, + const char * connection_uuid, + NMSettingOvsExternalIDs *s_exid_old, + NMSettingOvsExternalIDs *s_exid_new) +{ + gs_unref_hashtable GHashTable *exid_old = NULL; + gs_unref_hashtable GHashTable *exid_new = NULL; + + exid_old = s_exid_old + ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_old)) + : NULL; + exid_new = s_exid_new + ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_new)) + : NULL; + + ovsdb_call_method(self, + NULL, + NULL, + FALSE, + OVSDB_SET_EXTERNAL_IDS, + OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(device_type, + ifname, + connection_uuid, + exid_old, + exid_new)); +} + +/*****************************************************************************/ + +static void +nm_ovsdb_init(NMOvsdb *self) +{ + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + c_list_init(&priv->calls_lst_head); + + priv->input = g_string_new(NULL); + priv->output = g_string_new(NULL); + priv->bridges = + g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL); + priv->ports = + g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_port, NULL); + priv->interfaces = + g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_interface, NULL); + + ovsdb_try_connect(self); +} + +static void +dispose(GObject *object) +{ + NMOvsdb * self = NM_OVSDB(object); + NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); + + ovsdb_disconnect(self, FALSE, TRUE); + + nm_assert(c_list_is_empty(&priv->calls_lst_head)); + + if (priv->input) { + g_string_free(priv->input, TRUE); + priv->input = NULL; + } + if (priv->output) { + g_string_free(priv->output, TRUE); + priv->output = NULL; + } + + nm_clear_pointer(&priv->bridges, g_hash_table_destroy); + nm_clear_pointer(&priv->ports, g_hash_table_destroy); + nm_clear_pointer(&priv->interfaces, g_hash_table_destroy); + + G_OBJECT_CLASS(nm_ovsdb_parent_class)->dispose(object); +} + +static void +nm_ovsdb_class_init(NMOvsdbClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + + signals[DEVICE_ADDED] = g_signal_new(NM_OVSDB_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_STRING, + G_TYPE_UINT, + G_TYPE_STRING); + + signals[DEVICE_REMOVED] = g_signal_new(NM_OVSDB_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_STRING, + G_TYPE_UINT, + G_TYPE_STRING); + + signals[INTERFACE_FAILED] = g_signal_new(NM_OVSDB_INTERFACE_FAILED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[READY] = g_signal_new(NM_OVSDB_READY, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/core/devices/ovs/nm-ovsdb.h b/src/core/devices/ovs/nm-ovsdb.h new file mode 100644 index 0000000..d0b4d19 --- /dev/null +++ b/src/core/devices/ovs/nm-ovsdb.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_OVSDB_H__ +#define __NETWORKMANAGER_OVSDB_H__ + +#define NM_TYPE_OVSDB (nm_ovsdb_get_type()) +#define NM_OVSDB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_OVSDB, NMOvsdb)) +#define NM_OVSDB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_OVSDB, NMOvsdbClass)) +#define NM_IS_OVSDB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_OVSDB)) +#define NM_IS_OVSDB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_OVSDB)) +#define NM_OVSDB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_OVSDB, NMOvsdbClass)) + +#define NM_OVSDB_DEVICE_ADDED "device-added" +#define NM_OVSDB_DEVICE_REMOVED "device-removed" +#define NM_OVSDB_INTERFACE_FAILED "interface-failed" +#define NM_OVSDB_READY "ready" + +typedef struct _NMOvsdb NMOvsdb; +typedef struct _NMOvsdbClass NMOvsdbClass; + +typedef void (*NMOvsdbCallback)(GError *error, gpointer user_data); + +NMOvsdb *nm_ovsdb_get(void); + +GType nm_ovsdb_get_type(void); + +void nm_ovsdb_add_interface(NMOvsdb * self, + NMConnection * bridge, + NMConnection * port, + NMConnection * interface, + NMDevice * bridge_device, + NMDevice * interface_device, + NMOvsdbCallback callback, + gpointer user_data); + +void nm_ovsdb_del_interface(NMOvsdb * self, + const char * ifname, + NMOvsdbCallback callback, + gpointer user_data); + +void nm_ovsdb_set_interface_mtu(NMOvsdb * self, + const char * ifname, + guint32 mtu, + NMOvsdbCallback callback, + gpointer user_data); + +struct _NMSettingOvsExternalIDs; + +void nm_ovsdb_set_external_ids(NMOvsdb * self, + NMDeviceType device_type, + const char * ifname, + const char * connection_uuid, + struct _NMSettingOvsExternalIDs *s_exid_old, + struct _NMSettingOvsExternalIDs *s_exid_new); + +gboolean nm_ovsdb_is_ready(NMOvsdb *self); + +#endif /* __NETWORKMANAGER_OVSDB_H__ */ diff --git a/src/core/devices/team/meson.build b/src/core/devices/team/meson.build new file mode 100644 index 0000000..d0ff4ca --- /dev/null +++ b/src/core/devices/team/meson.build @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_device_plugin_team = shared_module( + 'nm-device-plugin-team', + sources: files( + 'nm-device-team.c', + 'nm-team-factory.c', + ), + dependencies: [ + core_plugin_dep, + jansson_dep, + libteamdctl_dep, + ], + c_args: daemon_c_flags, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_device_plugin_team + +test( + 'check-local-devices-team', + check_exports, + args: [ + libnm_device_plugin_team.full_path(), + linker_script_devices, + ], +) diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c new file mode 100644 index 0000000..7b7a679 --- /dev/null +++ b/src/core/devices/team/nm-device-team.c @@ -0,0 +1,1096 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Jiri Pirko + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-team.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-jansson.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-config.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "nm-ip4-config.h" +#include "nm-std-aux/nm-dbus-compat.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceTeam +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceTeam, PROP_CONFIG, ); + +typedef struct { + struct teamdctl * tdc; + char * config; + GPid teamd_pid; + guint teamd_process_watch; + guint teamd_timeout; + guint teamd_read_timeout; + guint teamd_dbus_watch; + bool kill_in_progress : 1; + GFileMonitor * usock_monitor; + NMDeviceStageState stage1_state : 3; +} NMDeviceTeamPrivate; + +struct _NMDeviceTeam { + NMDevice parent; + NMDeviceTeamPrivate _priv; +}; + +struct _NMDeviceTeamClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceTeam, nm_device_team, NM_TYPE_DEVICE) + +#define NM_DEVICE_TEAM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceTeam, NM_IS_DEVICE_TEAM, NMDevice) + +/*****************************************************************************/ + +static gboolean teamd_start(NMDeviceTeam *self); + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingTeam *s_team; + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_TEAM_SETTING_NAME, + existing_connections, + NULL, + _("Team connection"), + "team", + NULL, + TRUE); + + s_team = nm_connection_get_setting_team(connection); + if (!s_team) { + s_team = (NMSettingTeam *) nm_setting_team_new(); + nm_connection_add_setting(connection, NM_SETTING(s_team)); + } + + return TRUE; +} + +static gboolean +ensure_teamd_connection(NMDevice *device) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + int err; + + if (priv->tdc) + return TRUE; + + priv->tdc = teamdctl_alloc(); + g_assert(priv->tdc); + err = teamdctl_connect(priv->tdc, nm_device_get_iface(device), NULL, NULL); + if (err != 0) { + _LOGE(LOGD_TEAM, "failed to connect to teamd (err=%d)", err); + teamdctl_free(priv->tdc); + priv->tdc = NULL; + } + + return !!priv->tdc; +} + +static const char * +_get_config(NMDeviceTeam *self) +{ + return nm_str_not_empty(NM_DEVICE_TEAM_GET_PRIVATE(self)->config); +} + +static gboolean +teamd_read_config(NMDeviceTeam *self) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + const char * config = NULL; + int err; + + if (priv->tdc) { + err = teamdctl_config_actual_get_raw_direct(priv->tdc, (char **) &config); + if (err) + return FALSE; + if (!config) { + /* set "" to distinguish an empty result from no config at all. */ + config = ""; + } + } + + if (!nm_streq0(config, priv->config)) { + g_free(priv->config); + priv->config = g_strdup(config); + _notify(self, PROP_CONFIG); + } + + return TRUE; +} + +static gboolean +teamd_read_timeout_cb(gpointer user_data) +{ + NMDeviceTeam * self = user_data; + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + priv->teamd_read_timeout = 0; + teamd_read_config(self); + return G_SOURCE_REMOVE; +} + +static void +update_connection(NMDevice *device, NMConnection *connection) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMSettingTeam * s_team = nm_connection_get_setting_team(connection); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + struct teamdctl * tdc = priv->tdc; + + if (!s_team) { + s_team = (NMSettingTeam *) nm_setting_team_new(); + nm_connection_add_setting(connection, (NMSetting *) s_team); + } + + /* Read the configuration only if not already set */ + if (!priv->config && ensure_teamd_connection(device)) + teamd_read_config(self); + + /* Restore previous tdc state */ + if (priv->tdc && !tdc) { + teamdctl_disconnect(priv->tdc); + teamdctl_free(priv->tdc); + priv->tdc = NULL; + } + + g_object_set(G_OBJECT(s_team), NM_SETTING_TEAM_CONFIG, _get_config(self), NULL); +} + +/*****************************************************************************/ + +static gboolean +master_update_slave_connection(NMDevice * self, + NMDevice * slave, + NMConnection *connection, + GError ** error) +{ + NMSettingTeamPort *s_port; + char * port_config = NULL; + int err = 0; + struct teamdctl * tdc; + const char * team_port_config = NULL; + const char * iface = nm_device_get_iface(self); + const char * iface_slave = nm_device_get_iface(slave); + + tdc = teamdctl_alloc(); + if (!tdc) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "update slave connection for slave '%s' failed to connect to teamd for master " + "%s (out of memory?)", + iface_slave, + iface); + g_return_val_if_reached(FALSE); + } + + err = teamdctl_connect(tdc, iface, NULL, NULL); + if (err) { + teamdctl_free(tdc); + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "update slave connection for slave '%s' failed to connect to teamd for master " + "%s (err=%d)", + iface_slave, + iface, + err); + return FALSE; + } + + err = teamdctl_port_config_get_raw_direct(tdc, iface_slave, (char **) &team_port_config); + port_config = g_strdup(team_port_config); + teamdctl_disconnect(tdc); + teamdctl_free(tdc); + if (err) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "update slave connection for slave '%s' failed to get configuration from teamd " + "master %s (err=%d)", + iface_slave, + iface, + err); + g_free(port_config); + return FALSE; + } + + s_port = nm_connection_get_setting_team_port(connection); + if (!s_port) { + s_port = (NMSettingTeamPort *) nm_setting_team_port_new(); + nm_connection_add_setting(connection, NM_SETTING(s_port)); + } + + g_object_set(G_OBJECT(s_port), NM_SETTING_TEAM_PORT_CONFIG, port_config, NULL); + g_free(port_config); + + g_object_set(nm_connection_get_setting_connection(connection), + NM_SETTING_CONNECTION_MASTER, + iface, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_TEAM_SETTING_NAME, + NULL); + return TRUE; +} + +/*****************************************************************************/ + +static void +teamd_kill_cb(pid_t pid, gboolean success, int child_status, void *user_data) +{ + gs_unref_object NMDeviceTeam *self = user_data; + NMDeviceTeamPrivate * priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + priv->kill_in_progress = FALSE; + + if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_PREPARE) { + _LOGT(LOGD_TEAM, "kill terminated"); + return; + } + + _LOGT(LOGD_TEAM, "kill terminated, starting teamd..."); + if (!teamd_start(self)) { + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } +} + +static void +teamd_cleanup(NMDeviceTeam *self, gboolean free_tdc) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + nm_clear_g_source(&priv->teamd_process_watch); + nm_clear_g_source(&priv->teamd_timeout); + nm_clear_g_source(&priv->teamd_read_timeout); + + if (priv->teamd_pid > 0) { + priv->kill_in_progress = TRUE; + nm_utils_kill_child_async(priv->teamd_pid, + SIGTERM, + LOGD_TEAM, + "teamd", + 2000, + teamd_kill_cb, + g_object_ref(self)); + priv->teamd_pid = 0; + } + + if (priv->tdc && free_tdc) { + teamdctl_disconnect(priv->tdc); + teamdctl_free(priv->tdc); + priv->tdc = NULL; + } +} + +static gboolean +teamd_timeout_cb(gpointer user_data) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(user_data); + NMDevice * device = NM_DEVICE(self); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + g_return_val_if_fail(priv->teamd_timeout, FALSE); + priv->teamd_timeout = 0; + + if (priv->teamd_pid && !priv->tdc) { + /* Timed out launching our own teamd process */ + _LOGW(LOGD_TEAM, "teamd timed out"); + teamd_cleanup(self, TRUE); + + g_warn_if_fail(nm_device_is_activating(device)); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } else { + /* Read again the configuration after the timeout since it might + * have changed. + */ + if (!teamd_read_config(self)) { + _LOGW(LOGD_TEAM, "failed to read teamd configuration"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } + } + + return G_SOURCE_REMOVE; +} + +static void +teamd_ready(NMDeviceTeam *self) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + gboolean success; + + if (priv->kill_in_progress) { + /* If we are currently killing teamd, we are not + * interested in knowing when it becomes ready. */ + return; + } + + nm_device_queue_recheck_assume(device); + + /* Grab a teamd control handle even if we aren't going to use it + * immediately. But if we are, and grabbing it failed, fail the + * device activation. + */ + success = ensure_teamd_connection(device); + + if (nm_device_get_state(device) != NM_DEVICE_STATE_PREPARE + || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING) + return; + + if (success) + success = teamd_read_config(self); + + if (!success) { + teamd_cleanup(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + return; + } + + priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +teamd_gone(NMDeviceTeam *self) +{ + NMDevice * device = NM_DEVICE(self); + NMDeviceState state; + + teamd_cleanup(self, TRUE); + state = nm_device_get_state(device); + + /* Attempt to respawn teamd */ + if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_ACTIVATED) { + if (!teamd_start(self)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } + } +} + +static void +teamd_dbus_appeared(GDBusConnection *connection, + const char * name, + const char * name_owner, + gpointer user_data) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + g_return_if_fail(priv->teamd_dbus_watch); + + _LOGI(LOGD_TEAM, "teamd appeared on D-Bus"); + + /* If another teamd grabbed the bus name while our teamd was starting, + * just ignore the death of our teamd and run with the existing one. + */ + if (priv->teamd_process_watch) { + gs_unref_variant GVariant *ret = NULL; + guint32 pid; + + ret = g_dbus_connection_call_sync(connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID", + g_variant_new("(s)", name_owner), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 2000, + NULL, + NULL); + + if (ret) { + g_variant_get(ret, "(u)", &pid); + if (pid != priv->teamd_pid) + teamd_cleanup(self, FALSE); + } else { + /* The process that registered on the bus died. If it's + * the teamd instance we just started, ignore the event + * as we already detect the failure through the process + * watch. If it's a previous instance that got killed, + * also ignore that as our new instance will register + * again. */ + _LOGD(LOGD_TEAM, "failed to determine D-Bus name owner, ignoring"); + return; + } + } + + teamd_ready(self); +} + +static void +teamd_dbus_vanished(GDBusConnection *dbus_connection, const char *name, gpointer user_data) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + g_return_if_fail(priv->teamd_dbus_watch); + + if (!priv->tdc) { + /* g_bus_watch_name will always raise an initial signal, to indicate whether the + * name exists/not exists initially. Do not take this as a failure if it hadn't + * previously appeared. + */ + _LOGD(LOGD_TEAM, "teamd not on D-Bus (ignored)"); + return; + } + + _LOGI(LOGD_TEAM, "teamd vanished from D-Bus"); + + teamd_gone(self); +} + +static void +monitor_changed_cb(GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMDeviceTeam *self = NM_DEVICE_TEAM(user_data); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CREATED: + _LOGI(LOGD_TEAM, "file %s was created", g_file_get_path(file)); + teamd_ready(self); + break; + case G_FILE_MONITOR_EVENT_DELETED: + _LOGI(LOGD_TEAM, "file %s was deleted", g_file_get_path(file)); + teamd_gone(self); + break; + default:; + } +} + +static void +teamd_process_watch_cb(GPid pid, int status, gpointer user_data) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState state = nm_device_get_state(device); + + g_return_if_fail(priv->teamd_process_watch); + + _LOGD(LOGD_TEAM, "teamd %lld died with status %d", (long long) pid, status); + priv->teamd_pid = 0; + priv->teamd_process_watch = 0; + + /* If teamd quit within 5 seconds of starting, it's probably hosed + * and will just die again, so fail the activation. + */ + if (priv->teamd_timeout && (state >= NM_DEVICE_STATE_PREPARE) + && (state <= NM_DEVICE_STATE_ACTIVATED)) { + _LOGW(LOGD_TEAM, + "teamd process %lld quit unexpectedly; failing activation", + (long long) pid); + teamd_cleanup(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } +} + +static void +teamd_child_setup(gpointer user_data) +{ + nm_utils_setpgid(NULL); + signal(SIGPIPE, SIG_IGN); +} + +static const char ** +teamd_env(void) +{ + const char **env = g_new0(const char *, 2); + + if (nm_config_get_is_debug(nm_config_get())) + env[0] = "TEAM_LOG_OUTPUT=stderr"; + else + env[0] = "TEAM_LOG_OUTPUT=syslog"; + + return env; +} + +static gboolean +teamd_kill(NMDeviceTeam *self, const char *teamd_binary, GError **error) +{ + gs_unref_ptrarray GPtrArray *argv = NULL; + gs_free char * tmp_str = NULL; + gs_free const char ** envp = NULL; + + if (!teamd_binary) { + teamd_binary = nm_utils_find_helper("teamd", NULL, error); + if (!teamd_binary) { + _LOGW(LOGD_TEAM, "Activation: (team) failed to start teamd: teamd binary not found"); + return FALSE; + } + } + + argv = g_ptr_array_new(); + g_ptr_array_add(argv, (gpointer) teamd_binary); + g_ptr_array_add(argv, (gpointer) "-k"); + g_ptr_array_add(argv, (gpointer) "-t"); + g_ptr_array_add(argv, (gpointer) nm_device_get_iface(NM_DEVICE(self))); + g_ptr_array_add(argv, NULL); + + envp = teamd_env(); + + _LOGD(LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv(" ", (char **) argv->pdata))); + return g_spawn_sync("/", + (char **) argv->pdata, + (char **) envp, + 0, + teamd_child_setup, + NULL, + NULL, + NULL, + NULL, + error); +} + +static gboolean +teamd_start(NMDeviceTeam *self) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + const char * iface = nm_device_get_ip_iface(NM_DEVICE(self)); + NMConnection * connection; + gs_unref_ptrarray GPtrArray *argv = NULL; + gs_free_error GError * error = NULL; + gs_free char * tmp_str = NULL; + const char * teamd_binary; + const char * config; + nm_auto_free const char *config_free = NULL; + NMSettingTeam * s_team; + gs_free char * cloned_mac = NULL; + gs_free const char ** envp = NULL; + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + s_team = nm_connection_get_setting_team(connection); + if (!s_team) + g_return_val_if_reached(FALSE); + + nm_assert(iface); + + teamd_binary = nm_utils_find_helper("teamd", NULL, NULL); + if (!teamd_binary) { + _LOGW(LOGD_TEAM, "Activation: (team) failed to start teamd: teamd binary not found"); + return FALSE; + } + + if (priv->teamd_process_watch || priv->teamd_pid > 0 || priv->tdc) { + g_warn_if_reached(); + if (!priv->teamd_pid) + teamd_kill(self, teamd_binary, NULL); + teamd_cleanup(self, TRUE); + } + + /* Start teamd now */ + argv = g_ptr_array_new(); + g_ptr_array_add(argv, (gpointer) teamd_binary); + g_ptr_array_add(argv, (gpointer) "-o"); + g_ptr_array_add(argv, (gpointer) "-n"); + g_ptr_array_add(argv, (gpointer) "-U"); + if (priv->teamd_dbus_watch) + g_ptr_array_add(argv, (gpointer) "-D"); + g_ptr_array_add(argv, (gpointer) "-N"); + g_ptr_array_add(argv, (gpointer) "-t"); + g_ptr_array_add(argv, (gpointer) iface); + + config = nm_setting_team_get_config(s_team); + if (!nm_device_hw_addr_get_cloned(NM_DEVICE(self), + connection, + FALSE, + &cloned_mac, + NULL, + &error)) { + _LOGW(LOGD_DEVICE, "set-hw-addr: %s", error->message); + return FALSE; + } + + if (cloned_mac) { + json_t * json, *hwaddr; + json_error_t jerror; + + /* Inject the hwaddr property into the JSON configuration. + * While doing so, detect potential conflicts */ + + json = json_loads(config ?: "{}", JSON_REJECT_DUPLICATES, &jerror); + g_return_val_if_fail(json, FALSE); + + hwaddr = json_object_get(json, "hwaddr"); + if (hwaddr) { + if (!json_is_string(hwaddr) || !nm_streq0(json_string_value(hwaddr), cloned_mac)) + _LOGW(LOGD_TEAM, + "set-hw-addr: can't set team cloned-mac-address as the JSON configuration " + "already contains \"hwaddr\""); + } else { + hwaddr = json_string(cloned_mac); + json_object_set(json, "hwaddr", hwaddr); + config = config_free = + json_dumps(json, JSON_INDENT(0) | JSON_ENSURE_ASCII | JSON_SORT_KEYS); + _LOGD(LOGD_TEAM, + "set-hw-addr: injected \"hwaddr\" \"%s\" into team configuration", + cloned_mac); + json_decref(hwaddr); + } + json_decref(json); + } + + if (config) { + g_ptr_array_add(argv, (gpointer) "-c"); + g_ptr_array_add(argv, (gpointer) config); + } + + if (nm_logging_enabled(LOGL_DEBUG, LOGD_TEAM)) + g_ptr_array_add(argv, (gpointer) "-gg"); + g_ptr_array_add(argv, NULL); + + envp = teamd_env(); + + _LOGD(LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv(" ", (char **) argv->pdata))); + if (!g_spawn_async("/", + (char **) argv->pdata, + (char **) envp, + G_SPAWN_DO_NOT_REAP_CHILD, + teamd_child_setup, + NULL, + &priv->teamd_pid, + &error)) { + _LOGW(LOGD_TEAM, "Activation: (team) failed to start teamd: %s", error->message); + teamd_cleanup(self, TRUE); + return FALSE; + } + + /* Start a timeout for teamd to appear at D-Bus */ + if (!priv->teamd_timeout) + priv->teamd_timeout = g_timeout_add_seconds(5, teamd_timeout_cb, self); + + /* Monitor the child process so we know when it dies */ + priv->teamd_process_watch = g_child_watch_add(priv->teamd_pid, teamd_process_watch_cb, self); + + _LOGI(LOGD_TEAM, "Activation: (team) started teamd [pid %u]...", (guint) priv->teamd_pid); + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + NMSettingTeam * s_team; + const char * cfg; + + if (nm_device_sys_iface_state_is_external(device)) + return NM_ACT_STAGE_RETURN_SUCCESS; + + if (nm_device_sys_iface_state_is_external_or_assume(device)) { + if (ensure_teamd_connection(device)) + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + s_team = nm_device_get_applied_setting(device, NM_TYPE_SETTING_TEAM); + if (!s_team) + g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE); + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED) + return NM_ACT_STAGE_RETURN_SUCCESS; + + priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING; + + if (priv->tdc) { + /* If the existing teamd config is the same as we're about to use, + * then we can proceed. If it's not the same, and we have a PID, + * kill it so we can respawn it with the right config. If we don't + * have a PID, then we must fail. + */ + cfg = teamdctl_config_get_raw(priv->tdc); + if (cfg && nm_streq0(cfg, nm_setting_team_get_config(s_team))) { + _LOGD(LOGD_TEAM, "using existing matching teamd config"); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + if (!priv->teamd_pid) { + _LOGD(LOGD_TEAM, "existing teamd config mismatch; killing existing via teamdctl"); + if (!teamd_kill(self, NULL, &error)) { + _LOGW(LOGD_TEAM, + "existing teamd config mismatch; failed to kill existing teamd: %s", + error->message); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + _LOGD(LOGD_TEAM, "existing teamd config mismatch; respawning..."); + teamd_cleanup(self, TRUE); + } + + if (priv->kill_in_progress) { + _LOGT(LOGD_TEAM, "kill in progress, wait before starting teamd"); + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (!teamd_start(self)) + return NM_ACT_STAGE_RETURN_FAILURE; + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT; + + if (nm_device_sys_iface_state_is_external(device)) + return; + + if (priv->teamd_pid || priv->tdc) + _LOGI(LOGD_TEAM, "deactivation: stopping teamd..."); + + if (!priv->teamd_pid) + teamd_kill(self, NULL, NULL); + + teamd_cleanup(self, TRUE); +} + +static gboolean +enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + gboolean success = TRUE; + const char * slave_iface = nm_device_get_ip_iface(slave); + NMSettingTeamPort * s_team_port; + + nm_device_master_check_slave_physical_port(device, slave, LOGD_TEAM); + + if (configure) { + nm_device_take_down(slave, TRUE); + + s_team_port = nm_connection_get_setting_team_port(connection); + if (s_team_port) { + const char *config = nm_setting_team_port_get_config(s_team_port); + + if (config) { + if (!priv->tdc) { + _LOGW(LOGD_TEAM, + "enslaved team port %s config not changed, not connected to teamd", + slave_iface); + } else { + int err; + char *sanitized_config; + + sanitized_config = g_strdelimit(g_strdup(config), "\r\n", ' '); + err = teamdctl_port_config_update_raw(priv->tdc, slave_iface, sanitized_config); + g_free(sanitized_config); + if (err != 0) { + _LOGE(LOGD_TEAM, + "failed to update config for port %s (err=%d)", + slave_iface, + err); + return FALSE; + } + } + } + } + success = nm_platform_link_enslave(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + nm_device_get_ip_ifindex(slave)); + nm_device_bring_up(slave, TRUE, NULL); + + if (!success) + return FALSE; + + nm_clear_g_source(&priv->teamd_read_timeout); + priv->teamd_read_timeout = g_timeout_add_seconds(5, teamd_read_timeout_cb, self); + + _LOGI(LOGD_TEAM, "enslaved team port %s", slave_iface); + } else + _LOGI(LOGD_TEAM, "team port %s was enslaved", slave_iface); + + return TRUE; +} + +static void +release_slave(NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(device); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + gboolean do_release, success; + NMSettingTeamPort * s_port; + int ifindex_slave; + int ifindex; + + do_release = configure; + if (do_release) { + ifindex = nm_device_get_ifindex(device); + if (ifindex <= 0 || !nm_platform_link_get(nm_device_get_platform(device), ifindex)) + do_release = FALSE; + } + + ifindex_slave = nm_device_get_ip_ifindex(slave); + + if (ifindex_slave <= 0) { + _LOGD(LOGD_TEAM, "team port %s is already released", nm_device_get_ip_iface(slave)); + } else if (do_release) { + success = nm_platform_link_release(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + ifindex_slave); + if (success) + _LOGI(LOGD_TEAM, "released team port %s", nm_device_get_ip_iface(slave)); + else + _LOGW(LOGD_TEAM, "failed to release team port %s", nm_device_get_ip_iface(slave)); + + /* Kernel team code "closes" the port when releasing it, (which clears + * IFF_UP), so we must bring it back up here to ensure carrier changes and + * other state is noticed by the now-released port. + */ + if (!nm_device_bring_up(slave, TRUE, NULL)) { + _LOGW(LOGD_TEAM, + "released team port %s could not be brought up", + nm_device_get_ip_iface(slave)); + } + + nm_clear_g_source(&priv->teamd_read_timeout); + priv->teamd_read_timeout = g_timeout_add_seconds(5, teamd_read_timeout_cb, self); + } else + _LOGI(LOGD_TEAM, "team port %s was released", nm_device_get_ip_iface(slave)); + + /* Delete any port configuration we previously set */ + if (configure && priv->tdc + && (s_port = nm_device_get_applied_setting(slave, NM_TYPE_SETTING_TEAM_PORT)) + && (nm_setting_team_port_get_config(s_port))) + teamdctl_port_config_update_raw(priv->tdc, nm_device_get_ip_iface(slave), "{}"); +} + +static gboolean +create_and_realize(NMDevice * device, + NMConnection * connection, + NMDevice * parent, + const NMPlatformLink **out_plink, + GError ** error) +{ + const char *iface = nm_device_get_iface(device); + int r; + + r = nm_platform_link_team_add(nm_device_get_platform(device), iface, out_plink); + if (r < 0) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_CREATION_FAILED, + "Failed to create team master interface '%s' for '%s': %s", + iface, + nm_connection_get_id(connection), + nm_strerror(r)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceTeam *self = NM_DEVICE_TEAM(object); + + switch (prop_id) { + case PROP_CONFIG: + g_value_set_string(value, _get_config(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_team_init(NMDeviceTeam *self) +{ + nm_assert(nm_device_is_master(NM_DEVICE(self))); +} + +static void +constructed(GObject *object) +{ + NMDevice * device = NM_DEVICE(object); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(device); + gs_free char * tmp_str = NULL; + gs_unref_object GFile *file = NULL; + GError * error; + + G_OBJECT_CLASS(nm_device_team_parent_class)->constructed(object); + + if (nm_dbus_manager_get_dbus_connection(nm_dbus_manager_get())) { + /* Register D-Bus name watcher */ + tmp_str = g_strdup_printf("org.libteam.teamd.%s", nm_device_get_ip_iface(device)); + priv->teamd_dbus_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, + tmp_str, + G_BUS_NAME_WATCHER_FLAGS_NONE, + teamd_dbus_appeared, + teamd_dbus_vanished, + NM_DEVICE(device), + NULL); + return; + } + + /* No D-Bus, watch unix socket */ + tmp_str = g_strdup_printf("/run/teamd/%s.sock", nm_device_get_ip_iface(device)); + file = g_file_new_for_path(tmp_str); + priv->usock_monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, &error); + if (!priv->usock_monitor) { + nm_log_warn(LOGD_TEAM, "error monitoring %s: %s", tmp_str, error->message); + } else { + g_signal_connect(priv->usock_monitor, "changed", G_CALLBACK(monitor_changed_cb), object); + } +} + +NMDevice * +nm_device_team_new(const char *iface) +{ + return g_object_new(NM_TYPE_DEVICE_TEAM, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_DRIVER, + "team", + NM_DEVICE_TYPE_DESC, + "Team", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_TEAM, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_TEAM, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceTeam * self = NM_DEVICE_TEAM(object); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self); + + if (priv->teamd_dbus_watch) { + g_bus_unwatch_name(priv->teamd_dbus_watch); + priv->teamd_dbus_watch = 0; + } + + if (priv->usock_monitor) { + g_signal_handlers_disconnect_by_data(priv->usock_monitor, object); + g_clear_object(&priv->usock_monitor); + } + + teamd_cleanup(self, TRUE); + nm_clear_g_free(&priv->config); + + G_OBJECT_CLASS(nm_device_team_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_team = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_TEAM, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves", "ao", NM_DEVICE_SLAVES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Config", + "s", + NM_DEVICE_TEAM_CONFIG), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_team_class_init(NMDeviceTeamClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_team); + + device_class->connection_type_supported = NM_SETTING_TEAM_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_TEAM_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_TEAM); + + device_class->is_master = TRUE; + device_class->create_and_realize = create_and_realize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->complete_connection = complete_connection; + device_class->update_connection = update_connection; + device_class->master_update_slave_connection = master_update_slave_connection; + + device_class->act_stage1_prepare_also_for_external_or_assume = TRUE; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; + device_class->deactivate = deactivate; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + + obj_properties[PROP_CONFIG] = g_param_spec_string(NM_DEVICE_TEAM_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/team/nm-device-team.h b/src/core/devices/team/nm-device-team.h new file mode 100644 index 0000000..6f5cff9 --- /dev/null +++ b/src/core/devices/team/nm-device-team.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Jiri Pirko + */ + +#ifndef __NETWORKMANAGER_DEVICE_TEAM_H__ +#define __NETWORKMANAGER_DEVICE_TEAM_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_DEVICE_TEAM (nm_device_team_get_type()) +#define NM_DEVICE_TEAM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_TEAM, NMDeviceTeam)) +#define NM_DEVICE_TEAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass)) +#define NM_IS_DEVICE_TEAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_TEAM)) +#define NM_IS_DEVICE_TEAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_TEAM)) +#define NM_DEVICE_TEAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass)) + +/* Properties */ +#define NM_DEVICE_TEAM_CONFIG "config" + +typedef struct _NMDeviceTeam NMDeviceTeam; +typedef struct _NMDeviceTeamClass NMDeviceTeamClass; + +GType nm_device_team_get_type(void); + +NMDevice *nm_device_team_new(const char *iface); + +#endif /* __NETWORKMANAGER_DEVICE_TEAM_H__ */ diff --git a/src/core/devices/team/nm-team-factory.c b/src/core/devices/team/nm-team-factory.c new file mode 100644 index 0000000..ce623e2 --- /dev/null +++ b/src/core/devices/team/nm-team-factory.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-manager.h" +#include "devices/nm-device-factory.h" +#include "nm-device-team.h" +#include "platform/nm-platform.h" +#include "nm-core-internal.h" + +/*****************************************************************************/ + +#define NM_TYPE_TEAM_FACTORY (nm_team_factory_get_type()) +#define NM_TEAM_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_TEAM_FACTORY, NMTeamFactory)) +#define NM_TEAM_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_TEAM_FACTORY, NMTeamFactoryClass)) +#define NM_IS_TEAM_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_TEAM_FACTORY)) +#define NM_IS_TEAM_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_TEAM_FACTORY)) +#define NM_TEAM_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_TEAM_FACTORY, NMTeamFactoryClass)) + +typedef struct { + NMDeviceFactory parent; +} NMTeamFactory; + +typedef struct { + NMDeviceFactoryClass parent; +} NMTeamFactoryClass; + +static GType nm_team_factory_get_type(void); + +G_DEFINE_TYPE(NMTeamFactory, nm_team_factory, NM_TYPE_DEVICE_FACTORY) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES( + NM_LINK_TYPE_TEAM) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_TEAM_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_TEAM); + return g_object_new(NM_TYPE_TEAM_FACTORY, NULL); +} + +/*****************************************************************************/ + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + return nm_device_team_new(iface); +} + +/*****************************************************************************/ + +static void +nm_team_factory_init(NMTeamFactory *self) +{} + +static void +nm_team_factory_class_init(NMTeamFactoryClass *klass) +{ + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + factory_class->create_device = create_device; + factory_class->get_supported_types = get_supported_types; +} diff --git a/src/core/devices/tests/meson.build b/src/core/devices/tests/meson.build new file mode 100644 index 0000000..1bc8837 --- /dev/null +++ b/src/core/devices/tests/meson.build @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_units = [ + 'test-acd', + 'test-lldp', +] + +foreach test_unit: test_units + exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + ) + + test( + 'devices/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endforeach diff --git a/src/core/devices/tests/test-acd.c b/src/core/devices/tests/test-acd.c new file mode 100644 index 0000000..875dfff --- /dev/null +++ b/src/core/devices/tests/test-acd.c @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "n-acd/src/n-acd.h" + +#include + +#include "devices/nm-acd-manager.h" +#include "platform/tests/test-common.h" + +#define IFACE_VETH0 "nm-test-veth0" +#define IFACE_VETH1 "nm-test-veth1" + +#define ADDR1 0x01010101 +#define ADDR2 0x02020202 +#define ADDR3 0x03030303 +#define ADDR4 0x04040404 + +/*****************************************************************************/ + +static gboolean +_skip_acd_test_check(void) +{ + NAcd * acd; + NAcdConfig * config; + const guint8 hwaddr[ETH_ALEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + int r; + static int skip = -1; + + if (skip == -1) { + r = n_acd_config_new(&config); + g_assert(r == 0); + + n_acd_config_set_ifindex(config, 1); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, hwaddr, sizeof(hwaddr)); + + r = n_acd_new(&acd, config); + n_acd_config_free(config); + if (r == 0) + n_acd_unref(acd); + + skip = (r != 0); + } + return skip; +} + +#define _skip_acd_test() \ + ({ \ + gboolean _skip = _skip_acd_test_check(); \ + \ + if (_skip) \ + g_test_skip("Cannot create NAcd. Running under valgind?"); \ + _skip; \ + }) + +/*****************************************************************************/ + +typedef struct { + int ifindex0; + int ifindex1; + const guint8 *hwaddr0; + const guint8 *hwaddr1; + size_t hwaddr0_len; + size_t hwaddr1_len; +} test_fixture; + +static void +fixture_setup(test_fixture *fixture, gconstpointer user_data) +{ + /* create veth pair. */ + fixture->ifindex0 = + nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex; + fixture->ifindex1 = + nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex; + + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, fixture->ifindex0, NULL)); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, fixture->ifindex1, NULL)); + + fixture->hwaddr0 = + nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex0, &fixture->hwaddr0_len); + fixture->hwaddr1 = + nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex1, &fixture->hwaddr1_len); +} + +typedef struct { + in_addr_t addresses[8]; + in_addr_t peer_addresses[8]; + gboolean expected_result[8]; +} TestInfo; + +static void +acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data) +{ + g_main_loop_quit(user_data); +} + +static void +test_acd_common(test_fixture *fixture, TestInfo *info) +{ + nm_auto_free_acdmgr NMAcdManager *manager = NULL; + nm_auto_unref_gmainloop GMainLoop *loop = NULL; + int i; + const guint WAIT_TIME_OPTIMISTIC = 50; + guint wait_time; + static const NMAcdCallbacks callbacks = { + .probe_terminated_callback = acd_manager_probe_terminated, + .user_data_destroy = (GDestroyNotify) g_main_loop_unref, + }; + int r; + + if (_skip_acd_test()) + return; + + /* first, try with a short waittime. We hope that this is long enough + * to successfully complete the test. Only if that's not the case, we + * assume the computer is currently busy (high load) and we retry with + * a longer timeout. */ + wait_time = WAIT_TIME_OPTIMISTIC; +again: + + nm_clear_pointer(&loop, g_main_loop_unref); + loop = g_main_loop_new(NULL, FALSE); + + nm_clear_pointer(&manager, nm_acd_manager_free); + manager = nm_acd_manager_new(fixture->ifindex0, + fixture->hwaddr0, + fixture->hwaddr0_len, + &callbacks, + g_main_loop_ref(loop)); + g_assert(manager != NULL); + + for (i = 0; info->addresses[i]; i++) + g_assert(nm_acd_manager_add_address(manager, info->addresses[i])); + + for (i = 0; info->peer_addresses[i]; i++) { + nmtstp_ip4_address_add(NULL, + FALSE, + fixture->ifindex1, + info->peer_addresses[i], + 24, + 0, + 3600, + 1800, + 0, + NULL); + } + + r = nm_acd_manager_start_probe(manager, wait_time); + g_assert_cmpint(r, ==, 0); + + g_assert(nmtst_main_loop_run(loop, 2000)); + + for (i = 0; info->addresses[i]; i++) { + gboolean val; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + val = nm_acd_manager_check_address(manager, info->addresses[i]); + if (val == info->expected_result[i]) + continue; + + if (wait_time == WAIT_TIME_OPTIMISTIC) { + /* probably we just had a glitch and the system took longer than + * expected. Re-verify with a large timeout this time. */ + wait_time = 1000; + goto again; + } + + g_error("expected check for address #%d (%s) to %s, but it didn't", + i, + _nm_utils_inet4_ntop(info->addresses[i], sbuf), + info->expected_result[i] ? "detect no duplicated" : "detect a duplicate"); + } +} + +static void +test_acd_probe_1(test_fixture *fixture, gconstpointer user_data) +{ + TestInfo info = {.addresses = {ADDR1, ADDR2, ADDR3}, + .peer_addresses = {ADDR4}, + .expected_result = {TRUE, TRUE, TRUE}}; + + test_acd_common(fixture, &info); +} + +static void +test_acd_probe_2(test_fixture *fixture, gconstpointer user_data) +{ + TestInfo info = {.addresses = {ADDR1, ADDR2, ADDR3, ADDR4}, + .peer_addresses = {ADDR3, ADDR2}, + .expected_result = {TRUE, FALSE, FALSE, TRUE}}; + + test_acd_common(fixture, &info); +} + +static void +test_acd_announce(test_fixture *fixture, gconstpointer user_data) +{ + nm_auto_free_acdmgr NMAcdManager *manager = NULL; + nm_auto_unref_gmainloop GMainLoop *loop = NULL; + int r; + + if (_skip_acd_test()) + return; + + manager = + nm_acd_manager_new(fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len, NULL, NULL); + g_assert(manager != NULL); + + g_assert(nm_acd_manager_add_address(manager, ADDR1)); + g_assert(nm_acd_manager_add_address(manager, ADDR2)); + + loop = g_main_loop_new(NULL, FALSE); + r = nm_acd_manager_announce_addresses(manager); + g_assert_cmpint(r, ==, 0); + g_assert(!nmtst_main_loop_run(loop, 200)); +} + +static void +fixture_teardown(test_fixture *fixture, gconstpointer user_data) +{ + nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex0); + nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex1); +} + +NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + g_test_add("/acd/probe/1", + test_fixture, + NULL, + fixture_setup, + test_acd_probe_1, + fixture_teardown); + g_test_add("/acd/probe/2", + test_fixture, + NULL, + fixture_setup, + test_acd_probe_2, + fixture_teardown); + g_test_add("/acd/announce", + test_fixture, + NULL, + fixture_setup, + test_acd_announce, + fixture_teardown); +} diff --git a/src/core/devices/tests/test-lldp.c b/src/core/devices/tests/test-lldp.c new file mode 100644 index 0000000..04c3b5b --- /dev/null +++ b/src/core/devices/tests/test-lldp.c @@ -0,0 +1,1309 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include + +#include "devices/nm-lldp-listener.h" +#include "systemd/nm-sd.h" + +#include "platform/tests/test-common.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +static GVariant * +get_lldp_neighbor(GVariant * neighbors, + int chassis_id_type, + const char *chassis_id, + int port_id_type, + const char *port_id) +{ + GVariantIter iter; + GVariant * variant; + GVariant * result = NULL; + + nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}")); + + g_assert(chassis_id_type >= -1 && chassis_id_type <= G_MAXUINT8); + g_assert(port_id_type >= -1 && port_id_type <= G_MAXUINT8); + + g_variant_iter_init(&iter, neighbors); + while (g_variant_iter_next(&iter, "@a{sv}", &variant)) { + gs_unref_variant GVariant *v_chassis_id_type = NULL; + gs_unref_variant GVariant *v_chassis_id = NULL; + gs_unref_variant GVariant *v_port_id_type = NULL; + gs_unref_variant GVariant *v_port_id = NULL; + + v_chassis_id_type = + g_variant_lookup_value(variant, NM_LLDP_ATTR_CHASSIS_ID_TYPE, G_VARIANT_TYPE_UINT32); + g_assert(v_chassis_id_type); + + v_chassis_id = + g_variant_lookup_value(variant, NM_LLDP_ATTR_CHASSIS_ID, G_VARIANT_TYPE_STRING); + g_assert(v_chassis_id); + + v_port_id_type = + g_variant_lookup_value(variant, NM_LLDP_ATTR_PORT_ID_TYPE, G_VARIANT_TYPE_UINT32); + g_assert(v_port_id_type); + + v_port_id = g_variant_lookup_value(variant, NM_LLDP_ATTR_PORT_ID, G_VARIANT_TYPE_STRING); + g_assert(v_port_id); + + if (nm_streq(g_variant_get_string(v_chassis_id, NULL), chassis_id) + && nm_streq(g_variant_get_string(v_port_id, NULL), port_id) + && NM_IN_SET(chassis_id_type, -1, g_variant_get_uint32(v_chassis_id_type)) + && NM_IN_SET(port_id_type, -1, g_variant_get_uint32(v_port_id_type))) { + g_assert(!result); + result = variant; + } else + g_variant_unref(variant); + } + + return result; +} + +typedef struct { + int ifindex; + int fd; + guint8 mac[ETH_ALEN]; +} TestRecvFixture; + +typedef struct { + gsize frame_len; + const uint8_t *frame; + const char * as_variant; +} TestRecvFrame; +#define TEST_RECV_FRAME_DEFINE(name, _as_variant, ...) \ + static const guint8 _##name##_v[] = {__VA_ARGS__}; \ + static const TestRecvFrame name = { \ + .as_variant = _as_variant, \ + .frame_len = sizeof(_##name##_v), \ + .frame = _##name##_v, \ + } + +typedef struct { + guint expected_num_called; + gsize frames_len; + const TestRecvFrame *frames[10]; + void (*check)(GMainLoop *loop, NMLldpListener *listener); +} TestRecvData; +#define TEST_RECV_DATA_DEFINE(name, _expected_num_called, _check, ...) \ + static const TestRecvData name = { \ + .expected_num_called = _expected_num_called, \ + .check = _check, \ + .frames_len = NM_NARG(__VA_ARGS__), \ + .frames = {__VA_ARGS__}, \ + } + +#define TEST_IFNAME "nm-tap-test0" + +TEST_RECV_FRAME_DEFINE( + _test_recv_data0_frame0, + "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, " + "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, " + "0x33, 0x06, 0x02, 0x00, 0x78, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, " + "0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': , " + "'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': , 'port-id': <'1/3'>, " + "'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': " + "<'SYS'>, 'system-description': <'foo'>}", + /* Ethernet header */ + 0x01, + 0x80, + 0xc2, + 0x00, + 0x00, + 0x03, /* Destination MAC */ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, /* Source MAC */ + 0x88, + 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, + 0x07, + 0x04, + 0x00, + 0x01, + 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, + 0x04, + 0x05, + 0x04, + 0x04, + 0x05, + 0x31, + 0x2f, + 0x33, /* Port: interface name, "1/3" */ + 0x06, + 0x02, + 0x00, + 0x78, /* TTL: 120 seconds */ + /* LLDP optional TLVs */ + 0x08, + 0x04, + 0x50, + 0x6f, + 0x72, + 0x74, /* Port Description: "Port" */ + 0x0a, + 0x03, + 0x53, + 0x59, + 0x53, /* System Name: "SYS" */ + 0x0c, + 0x04, + 0x66, + 0x6f, + 0x6f, + 0x00, /* System Description: "foo" (NULL-terminated) */ + 0x00, + 0x00 /* End Of LLDPDU */ +); + +static void +_test_recv_data0_check_do(GMainLoop *loop, NMLldpListener *listener, const TestRecvFrame *frame) +{ + GVariant * neighbors, *attr; + gs_unref_variant GVariant *neighbor = NULL; + + neighbors = nm_lldp_listener_get_neighbors(listener); + nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}")); + g_assert_cmpint(g_variant_n_children(neighbors), ==, 1); + + neighbor = get_lldp_neighbor(neighbors, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, + "00:01:02:03:04:05", + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, + "1/3"); + g_assert(neighbor); + g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 4); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING); + nmtst_assert_variant_bytestring(attr, frame->frame, frame->frame_len); + nm_clear_g_variant(&attr); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "Port"); + nm_clear_g_variant(&attr); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "SYS"); + nm_clear_g_variant(&attr); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE); + nm_clear_g_variant(&attr); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "foo"); + nm_clear_g_variant(&attr); +} + +static void +_test_recv_data0_check(GMainLoop *loop, NMLldpListener *listener) +{ + _test_recv_data0_check_do(loop, listener, &_test_recv_data0_frame0); +} + +TEST_RECV_DATA_DEFINE(_test_recv_data0, 1, _test_recv_data0_check, &_test_recv_data0_frame0); +TEST_RECV_DATA_DEFINE(_test_recv_data0_twice, + 1, + _test_recv_data0_check, + &_test_recv_data0_frame0, + &_test_recv_data0_frame0); + +TEST_RECV_FRAME_DEFINE( + _test_recv_data1_frame0, + /* lldp.detailed.pcap from + * https://wiki.wireshark.org/SampleCaptures#Link_Layer_Discovery_Protocol_.28LLDP.29 */ + "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x88, " + "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x04, 0x04, 0x05, 0x31, 0x2f, " + "0x31, 0x06, 0x02, 0x00, 0x78, 0x08, 0x17, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, " + "0x30, 0x2d, 0x34, 0x38, 0x2d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x31, 0x30, 0x30, 0x31, 0x00, " + "0x0a, 0x0d, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x00, " + "0x0c, 0x4c, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x20, " + "0x2d, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x34, 0x65, 0x2e, " + "0x31, 0x20, 0x28, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x35, 0x29, 0x20, 0x62, 0x79, 0x20, " + "0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, " + "0x30, 0x35, 0x2f, 0x32, 0x37, 0x2f, 0x30, 0x35, 0x20, 0x30, 0x34, 0x3a, 0x35, 0x33, 0x3a, " + "0x31, 0x31, 0x00, 0x0e, 0x04, 0x00, 0x14, 0x00, 0x14, 0x10, 0x0e, 0x07, 0x06, 0x00, 0x01, " + "0x30, 0xf9, 0xad, 0xa0, 0x02, 0x00, 0x00, 0x03, 0xe9, 0x00, 0xfe, 0x07, 0x00, 0x12, 0x0f, " + "0x02, 0x07, 0x01, 0x00, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0x6c, 0x00, 0x00, 0x10, " + "0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x12, " + "0x0f, 0x04, 0x05, 0xf2, 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, 0x01, 0xe8, 0xfe, 0x07, 0x00, " + "0x80, 0xc2, 0x02, 0x01, 0x00, 0x00, 0xfe, 0x16, 0x00, 0x80, 0xc2, 0x03, 0x01, 0xe8, 0x0f, " + "0x76, 0x32, 0x2d, 0x30, 0x34, 0x38, 0x38, 0x2d, 0x30, 0x33, 0x2d, 0x30, 0x35, 0x30, 0x35, " + "0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, 0x00, 0x00, 0x00]>, 'chassis-id-type': , " + "'chassis-id': <'00:01:30:F9:AD:A0'>, 'port-id-type': , 'port-id': <'1/1'>, " + "'destination': <'nearest-bridge'>, 'port-description': <'Summit300-48-Port 1001'>, " + "'system-name': <'Summit300-48'>, 'system-description': <'Summit300-48 - Version 7.4e.1 (Build " + "5) by Release_Master 05/27/05 04:53:11'>, 'system-capabilities': , " + "'management-addresses': <[{'address-subtype': , 'address': <[byte 0x00, 0x01, 0x30, " + "0xf9, 0xad, 0xa0]>, 'interface-number-subtype': , 'interface-number': }]>, 'ieee-802-1-pvid': , 'ieee-802-1-ppvid': , " + "'ieee-802-1-ppvid-flags': , 'ieee-802-1-ppvids': <[{'ppvid': , 'flags': " + "}]>, 'ieee-802-1-vid': , 'ieee-802-1-vlan-name': <'v2-0488-03-0505'>, " + "'ieee-802-1-vlans': <[{'vid': , 'name': <'v2-0488-03-0505'>}]>, " + "'ieee-802-3-mac-phy-conf': <{'autoneg': , 'pmd-autoneg-cap': , " + "'operational-mau-type': }>, 'ieee-802-3-power-via-mdi': <{'mdi-power-support': " + ", 'pse-power-pair': , 'power-class': }>, " + "'ieee-802-3-max-frame-size': }", + /* ethernet header */ + 0x01, + 0x80, + 0xc2, + 0x00, + 0x00, + 0x0e, /* destination mac */ + 0x00, + 0x01, + 0x30, + 0xf9, + 0xad, + 0xa0, /* source mac */ + 0x88, + 0xcc, /* ethernet type */ + + 0x02, + 0x07, + 0x04, + 0x00, + 0x01, + 0x30, /* Chassis Subtype */ + 0xf9, + 0xad, + 0xa0, + 0x04, + 0x04, + 0x05, + 0x31, + 0x2f, + 0x31, /* Port Subtype */ + 0x06, + 0x02, + 0x00, + 0x78, /* Time To Live */ + 0x08, + 0x17, + 0x53, + 0x75, + 0x6d, + 0x6d, /* Port Description */ + 0x69, + 0x74, + 0x33, + 0x30, + 0x30, + 0x2d, + 0x34, + 0x38, + 0x2d, + 0x50, + 0x6f, + 0x72, + 0x74, + 0x20, + 0x31, + 0x30, + 0x30, + 0x31, + 0x00, + 0x0a, + 0x0d, + 0x53, + 0x75, + 0x6d, + 0x6d, /* System Name */ + 0x69, + 0x74, + 0x33, + 0x30, + 0x30, + 0x2d, + 0x34, + 0x38, + 0x00, + 0x0c, + 0x4c, + 0x53, + 0x75, + 0x6d, + 0x6d, /* System Description */ + 0x69, + 0x74, + 0x33, + 0x30, + 0x30, + 0x2d, + 0x34, + 0x38, + 0x20, + 0x2d, + 0x20, + 0x56, + 0x65, + 0x72, + 0x73, + 0x69, + 0x6f, + 0x6e, + 0x20, + 0x37, + 0x2e, + 0x34, + 0x65, + 0x2e, + 0x31, + 0x20, + 0x28, + 0x42, + 0x75, + 0x69, + 0x6c, + 0x64, + 0x20, + 0x35, + 0x29, + 0x20, + 0x62, + 0x79, + 0x20, + 0x52, + 0x65, + 0x6c, + 0x65, + 0x61, + 0x73, + 0x65, + 0x5f, + 0x4d, + 0x61, + 0x73, + 0x74, + 0x65, + 0x72, + 0x20, + 0x30, + 0x35, + 0x2f, + 0x32, + 0x37, + 0x2f, + 0x30, + 0x35, + 0x20, + 0x30, + 0x34, + 0x3a, + 0x35, + 0x33, + 0x3a, + 0x31, + 0x31, + 0x00, + 0x0e, + 0x04, + 0x00, + 0x14, + 0x00, + 0x14, /* Capabilities */ + 0x10, + 0x0e, + 0x07, + 0x06, + 0x00, + 0x01, /* Management Address */ + 0x30, + 0xf9, + 0xad, + 0xa0, + 0x02, + 0x00, + 0x00, + 0x03, + 0xe9, + 0x00, + 0xfe, + 0x07, + 0x00, + 0x12, + 0x0f, + 0x02, /* IEEE 802.3 - Power Via MDI */ + 0x07, + 0x01, + 0x00, + 0xfe, + 0x09, + 0x00, + 0x12, + 0x0f, + 0x01, /* IEEE 802.3 - MAC/PHY Configuration/Status */ + 0x03, + 0x6c, + 0x00, + 0x00, + 0x10, + 0xfe, + 0x09, + 0x00, + 0x12, + 0x0f, + 0x03, /* IEEE 802.3 - Link Aggregation */ + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x06, + 0x00, + 0x12, + 0x0f, + 0x04, /* IEEE 802.3 - Maximum Frame Size */ + 0x05, + 0xf2, + 0xfe, + 0x06, + 0x00, + 0x80, + 0xc2, + 0x01, /* IEEE 802.1 - Port VLAN ID */ + 0x01, + 0xe8, + 0xfe, + 0x07, + 0x00, + 0x80, + 0xc2, + 0x02, /* IEEE 802.1 - Port and Protocol VLAN ID */ + 0x01, + 0x00, + 0x00, + 0xfe, + 0x16, + 0x00, + 0x80, + 0xc2, + 0x03, /* IEEE 802.1 - VLAN Name */ + 0x01, + 0xe8, + 0x0f, + 0x76, + 0x32, + 0x2d, + 0x30, + 0x34, + 0x38, + 0x38, + 0x2d, + 0x30, + 0x33, + 0x2d, + 0x30, + 0x35, + 0x30, + 0x35, + 0xfe, + 0x05, + 0x00, + 0x80, + 0xc2, + 0x04, /* IEEE 802.1 - Protocol Identity */ + 0x00, + 0x00, + 0x00 /* End of LLDPDU */ +); + +static void +_test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener) +{ + GVariant * neighbors, *attr, *child; + gs_unref_variant GVariant *neighbor = NULL; + guint v_uint = 0; + const char * v_str = NULL; + + neighbors = nm_lldp_listener_get_neighbors(listener); + nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}")); + g_assert_cmpint(g_variant_n_children(neighbors), ==, 1); + + neighbor = get_lldp_neighbor(neighbors, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, + "00:01:30:F9:AD:A0", + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, + "1/1"); + g_assert(neighbor); + g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 16); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING); + nmtst_assert_variant_bytestring(attr, + _test_recv_data1_frame0.frame, + _test_recv_data1_frame0.frame_len); + nm_clear_g_variant(&attr); + + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, NM_LLDP_DEST_NEAREST_BRIDGE); + nm_clear_g_variant(&attr); + + /* unsupported: Time To Live */ + + /* Port Description */ + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "Summit300-48-Port 1001"); + nm_clear_g_variant(&attr); + + /* System Name */ + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "Summit300-48"); + nm_clear_g_variant(&attr); + + /* System Description */ + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string( + attr, + "Summit300-48 - Version 7.4e.1 (Build 5) by Release_Master 05/27/05 04:53:11"); + nm_clear_g_variant(&attr); + + /* Capabilities */ + attr = + g_variant_lookup_value(neighbor, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 20); + nm_clear_g_variant(&attr); + + /* Management Address */ + attr = g_variant_lookup_value(neighbor, + NM_LLDP_ATTR_MANAGEMENT_ADDRESSES, + G_VARIANT_TYPE("aa{sv}")); + g_assert(attr); + g_assert_cmpuint(g_variant_n_children(attr), ==, 1); + child = g_variant_get_child_value(attr, 0); + g_assert(child); + g_assert(g_variant_lookup(child, "interface-number", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 1001); + g_assert(g_variant_lookup(child, "interface-number-subtype", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 2); + g_assert(g_variant_lookup(child, "address-subtype", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 6); + nm_clear_g_variant(&child); + nm_clear_g_variant(&attr); + + /* IEEE 802.3 - Power Via MDI */ + attr = g_variant_lookup_value(neighbor, + NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI, + G_VARIANT_TYPE_VARDICT); + g_assert(attr); + g_assert(g_variant_lookup(attr, "mdi-power-support", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 7); + g_assert(g_variant_lookup(attr, "pse-power-pair", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 1); + g_assert(g_variant_lookup(attr, "power-class", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 0); + nm_clear_g_variant(&attr); + + /* IEEE 802.3 - MAC/PHY Configuration/Status */ + attr = g_variant_lookup_value(neighbor, + NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF, + G_VARIANT_TYPE_VARDICT); + g_assert(attr); + g_assert(g_variant_lookup(attr, "autoneg", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 3); + g_assert(g_variant_lookup(attr, "pmd-autoneg-cap", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 0x6c00); + g_assert(g_variant_lookup(attr, "operational-mau-type", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 16); + nm_clear_g_variant(&attr); + + /* unsupported: IEEE 802.3 - Link Aggregation */ + + /* Maximum Frame Size */ + attr = g_variant_lookup_value(neighbor, + NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE, + G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 1522); + nm_clear_g_variant(&attr); + + /* IEEE 802.1 - Port VLAN ID */ + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_PVID, G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 488); + nm_clear_g_variant(&attr); + + /* IEEE 802.1 - Port and Protocol VLAN ID */ + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID, G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 0); + nm_clear_g_variant(&attr); + attr = g_variant_lookup_value(neighbor, + NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, + G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 1); + nm_clear_g_variant(&attr); + + /* new PPVID attributes */ + attr = + g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVIDS, G_VARIANT_TYPE("aa{sv}")); + g_assert_cmpuint(g_variant_n_children(attr), ==, 1); + child = g_variant_get_child_value(attr, 0); + g_assert(child); + g_assert(g_variant_lookup(child, "ppvid", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 0); + g_assert(g_variant_lookup(child, "flags", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 1); + nm_clear_g_variant(&child); + nm_clear_g_variant(&attr); + + /* IEEE 802.1 - VLAN Name */ + attr = + g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, G_VARIANT_TYPE_STRING); + nmtst_assert_variant_string(attr, "v2-0488-03-0505"); + nm_clear_g_variant(&attr); + attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_VID, G_VARIANT_TYPE_UINT32); + nmtst_assert_variant_uint32(attr, 488); + nm_clear_g_variant(&attr); + + /* new VLAN attributes */ + attr = + g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_VLANS, G_VARIANT_TYPE("aa{sv}")); + g_assert_cmpuint(g_variant_n_children(attr), ==, 1); + child = g_variant_get_child_value(attr, 0); + g_assert(child); + g_assert(g_variant_lookup(child, "vid", "u", &v_uint)); + g_assert_cmpint(v_uint, ==, 488); + g_assert(g_variant_lookup(child, "name", "&s", &v_str)); + g_assert_cmpstr(v_str, ==, "v2-0488-03-0505"); + nm_clear_g_variant(&child); + nm_clear_g_variant(&attr); + + /* unsupported: IEEE 802.1 - Protocol Identity */ +} + +TEST_RECV_DATA_DEFINE(_test_recv_data1, 1, _test_recv_data1_check, &_test_recv_data1_frame0); + +TEST_RECV_FRAME_DEFINE( + _test_recv_data2_frame0_ttl1, + "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, " + "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, " + "0x33, 0x06, 0x02, 0x00, 0x01, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, " + "0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': , " + "'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': , 'port-id': <'1/3'>, " + "'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': " + "<'SYS'>, 'system-description': <'foo'>}", + /* Ethernet header */ + 0x01, + 0x80, + 0xc2, + 0x00, + 0x00, + 0x03, /* Destination MAC */ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, /* Source MAC */ + 0x88, + 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, + 0x07, + 0x04, + 0x00, + 0x01, + 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, + 0x04, + 0x05, + 0x04, + 0x04, + 0x05, + 0x31, + 0x2f, + 0x33, /* Port: interface name, "1/3" */ + 0x06, + 0x02, + 0x00, + 0x01, /* TTL: 1 seconds */ + /* LLDP optional TLVs */ + 0x08, + 0x04, + 0x50, + 0x6f, + 0x72, + 0x74, /* Port Description: "Port" */ + 0x0a, + 0x03, + 0x53, + 0x59, + 0x53, /* System Name: "SYS" */ + 0x0c, + 0x04, + 0x66, + 0x6f, + 0x6f, + 0x00, /* System Description: "foo" (NULL-terminated) */ + 0x00, + 0x00 /* End Of LLDPDU */ +); + +static void +_test_recv_data2_ttl1_check(GMainLoop *loop, NMLldpListener *listener) +{ + gulong notify_id; + GVariant *neighbors; + + _test_recv_data0_check_do(loop, listener, &_test_recv_data2_frame0_ttl1); + + /* wait for signal. */ + notify_id = g_signal_connect(listener, + "notify::" NM_LLDP_LISTENER_NEIGHBORS, + nmtst_main_loop_quit_on_notify, + loop); + if (!nmtst_main_loop_run(loop, 5000)) + g_assert_not_reached(); + nm_clear_g_signal_handler(listener, ¬ify_id); + + neighbors = nm_lldp_listener_get_neighbors(listener); + nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}")); + g_assert_cmpint(g_variant_n_children(neighbors), ==, 0); +} + +TEST_RECV_DATA_DEFINE(_test_recv_data2_ttl1, + 1, + _test_recv_data2_ttl1_check, + &_test_recv_data2_frame0_ttl1); + +static void +_test_recv_fixture_setup(TestRecvFixture *fixture, gconstpointer user_data) +{ + const NMPlatformLink *link; + nm_auto_close int fd = -1; + + fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC); + if (fd == -1) { + g_test_skip("Unable to open /dev/net/tun"); + fixture->ifindex = 0; + return; + } + + if (nmtst_get_rand_bool()) { + const NMPlatformLnkTun lnk = { + .type = IFF_TAP, + .pi = FALSE, + .vnet_hdr = FALSE, + .multi_queue = FALSE, + .persist = FALSE, + }; + + nm_close(nm_steal_fd(&fd)); + + link = nmtstp_link_tun_add(NM_PLATFORM_GET, FALSE, TEST_IFNAME, &lnk, &fd); + g_assert(link); + nmtstp_link_set_updown(NM_PLATFORM_GET, -1, link->ifindex, TRUE); + link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 0); + } else { + int s; + struct ifreq ifr = {}; + int r; + + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + nm_utils_ifname_cpy(ifr.ifr_name, TEST_IFNAME); + + r = ioctl(fd, TUNSETIFF, &ifr); + if (r != 0) { + g_assert_cmpint(errno, ==, 0); + g_assert_cmpint(r, ==, 0); + } + + /* Bring the interface up */ + s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + g_assert(s >= 0); + + ifr.ifr_flags |= IFF_UP; + r = ioctl(s, SIOCSIFFLAGS, &ifr); + if (r != 0) { + g_assert_cmpint(errno, ==, 0); + g_assert_cmpint(r, ==, 0); + } + + nm_close(s); + + link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 100); + } + + fixture->ifindex = link->ifindex; + fixture->fd = nm_steal_fd(&fd); + memcpy(fixture->mac, link->l_address.data, ETH_ALEN); +} + +typedef struct { + int num_called; +} TestRecvCallbackInfo; + +static void +lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data) +{ + TestRecvCallbackInfo *info = user_data; + + info->num_called++; +} + +static void +test_recv(TestRecvFixture *fixture, gconstpointer user_data) +{ + const TestRecvData *data = user_data; + gs_unref_object NMLldpListener *listener = NULL; + GMainLoop * loop; + TestRecvCallbackInfo info = {}; + gsize i_frames; + gulong notify_id; + GError * error = NULL; + guint sd_id; + + if (fixture->ifindex == 0) { + g_test_skip("Tun device not available"); + return; + } + + listener = nm_lldp_listener_new(); + g_assert(listener != NULL); + g_assert(nm_lldp_listener_start(listener, fixture->ifindex, &error)); + g_assert_no_error(error); + + notify_id = g_signal_connect(listener, + "notify::" NM_LLDP_LISTENER_NEIGHBORS, + (GCallback) lldp_neighbors_changed, + &info); + loop = g_main_loop_new(NULL, FALSE); + sd_id = nm_sd_event_attach_default(); + + for (i_frames = 0; i_frames < data->frames_len; i_frames++) { + const TestRecvFrame *f = data->frames[i_frames]; + + g_assert(write(fixture->fd, f->frame, f->frame_len) == f->frame_len); + } + + if (nmtst_main_loop_run(loop, 500)) + g_assert_not_reached(); + + g_assert_cmpint(info.num_called, ==, data->expected_num_called); + + nm_clear_g_signal_handler(listener, ¬ify_id); + + data->check(loop, listener); + + nm_clear_g_source(&sd_id); + nm_clear_pointer(&loop, g_main_loop_unref); +} + +static void +_test_recv_fixture_teardown(TestRecvFixture *fixture, gconstpointer user_data) +{ + if (fixture->ifindex) + nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex); +} + +/*****************************************************************************/ + +static void +test_parse_frames(gconstpointer test_data) +{ + const TestRecvFrame *frame = test_data; + gs_unref_variant GVariant *v_neighbor = NULL; + gs_unref_variant GVariant *attr = NULL; + gs_free char * as_variant = NULL; + + v_neighbor = nmtst_lldp_parse_from_raw(frame->frame, frame->frame_len); + g_assert(v_neighbor); + + attr = g_variant_lookup_value(v_neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING); + nmtst_assert_variant_bytestring(attr, frame->frame, frame->frame_len); + nm_clear_g_variant(&attr); + + as_variant = g_variant_print(v_neighbor, TRUE); + g_assert(as_variant); + g_assert_cmpstr(frame->as_variant, ==, as_variant); +} + +/*****************************************************************************/ + +TEST_RECV_FRAME_DEFINE( + _test_parse_frames_3, + /* https://github.com/the-tcpdump-group/tcpdump/blob/c4f8796bf8bec740621a360eded236d8991ea00f/tests/lldp_mudurl.pcap */ + "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x88, " + "0xcc, 0x02, 0x07, 0x04, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x04, 0x07, 0x03, 0x00, 0x23, " + "0x54, 0xc2, 0x57, 0x02, 0x06, 0x02, 0x00, 0x78, 0x0a, 0x1c, 0x75, 0x70, 0x73, 0x74, 0x61, " + "0x69, 0x72, 0x73, 0x2e, 0x6f, 0x66, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x69, 0x6d, 0x72, " + "0x69, 0x67, 0x68, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x5c, 0x55, 0x62, 0x75, 0x6e, 0x74, " + "0x75, 0x20, 0x31, 0x34, 0x2e, 0x30, 0x34, 0x2e, 0x35, 0x20, 0x4c, 0x54, 0x53, 0x20, 0x4c, " + "0x69, 0x6e, 0x75, 0x78, 0x20, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x31, 0x30, 0x36, " + "0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x23, 0x31, 0x35, 0x33, 0x2d, 0x55, " + "0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x54, 0x75, 0x65, 0x20, 0x44, " + "0x65, 0x63, 0x20, 0x36, 0x20, 0x31, 0x35, 0x3a, 0x34, 0x35, 0x3a, 0x31, 0x33, 0x20, 0x55, " + "0x54, 0x43, 0x20, 0x32, 0x30, 0x31, 0x36, 0x20, 0x69, 0x36, 0x38, 0x36, 0x0e, 0x04, 0x00, " + "0x9c, 0x00, 0x08, 0x10, 0x0c, 0x05, 0x01, 0x3e, 0x0c, 0xad, 0x72, 0x02, 0x00, 0x00, 0x00, " + "0x02, 0x00, 0x10, 0x18, 0x11, 0x02, 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, 0x00, 0x04, 0x02, " + "0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x04, " + "0x65, 0x74, 0x68, 0x30, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, " + "0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0xec, 0xc3, 0x00, 0x10, 0xfe, 0x40, 0x00, 0x00, " + "0x5e, 0x01, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x69, 0x6d, 0x72, 0x69, 0x67, " + "0x68, 0x74, 0x2e, 0x6d, 0x75, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, " + "0x63, 0x6f, 0x6d, 0x2f, 0x2e, 0x77, 0x65, 0x6c, 0x6c, 0x2d, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, " + "0x2f, 0x6d, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x6f, 0x6d, 0x69, 0x74, 0x76, 0x32, " + "0x2e, 0x30, 0x00, 0x00]>, 'chassis-id-type': , 'chassis-id': <'00:23:54:C2:57:02'>, " + "'port-id-type': , 'port-id': <'00:23:54:C2:57:02'>, 'destination': " + "<'nearest-bridge'>, 'port-description': <'eth0'>, 'system-name': " + "<'upstairs.ofcourseimright.com'>, 'system-description': <'Ubuntu 14.04.5 LTS Linux " + "3.13.0-106-generic #153-Ubuntu SMP Tue Dec 6 15:45:13 UTC 2016 i686'>, 'system-capabilities': " + ", 'management-addresses': <[{'address-subtype': , 'address': <[byte " + "0x3e, 0x0c, 0xad, 0x72]>, 'interface-number-subtype': , 'interface-number': }, {'address-subtype': , 'address': <[byte 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, " + "0x00, 0x04, 0x02, 0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02]>, 'interface-number-subtype': " + ", 'interface-number': }]>, 'ieee-802-3-mac-phy-conf': <{'autoneg': " + ", 'pmd-autoneg-cap': , 'operational-mau-type': }>, " + "'mud-url': <'https://imright.mud.example.com/.well-known/mud/v1/vomitv2.0'>}", + 0x01, + 0x80, + 0xc2, + 0x00, + 0x00, + 0x0e, /* ethernet destination */ + 0x00, + 0x23, + 0x54, + 0xc2, + 0x57, + 0x02, /* ethernet source */ + 0x88, + 0xcc, /* ethernet type */ + + 0x02, + 0x07, + 0x04, + 0x00, + 0x23, + 0x54, + 0xc2, + 0x57, + 0x02, + 0x04, + 0x07, + 0x03, + 0x00, + 0x23, + 0x54, + 0xc2, + 0x57, + 0x02, + 0x06, + 0x02, + 0x00, + 0x78, + 0x0a, + 0x1c, + 0x75, + 0x70, + 0x73, + 0x74, + 0x61, + 0x69, + 0x72, + 0x73, + 0x2e, + 0x6f, + 0x66, + 0x63, + 0x6f, + 0x75, + 0x72, + 0x73, + 0x65, + 0x69, + 0x6d, + 0x72, + 0x69, + 0x67, + 0x68, + 0x74, + 0x2e, + 0x63, + 0x6f, + 0x6d, + 0x0c, + 0x5c, + 0x55, + 0x62, + 0x75, + 0x6e, + 0x74, + 0x75, + 0x20, + 0x31, + 0x34, + 0x2e, + 0x30, + 0x34, + 0x2e, + 0x35, + 0x20, + 0x4c, + 0x54, + 0x53, + 0x20, + 0x4c, + 0x69, + 0x6e, + 0x75, + 0x78, + 0x20, + 0x33, + 0x2e, + 0x31, + 0x33, + 0x2e, + 0x30, + 0x2d, + 0x31, + 0x30, + 0x36, + 0x2d, + 0x67, + 0x65, + 0x6e, + 0x65, + 0x72, + 0x69, + 0x63, + 0x20, + 0x23, + 0x31, + 0x35, + 0x33, + 0x2d, + 0x55, + 0x62, + 0x75, + 0x6e, + 0x74, + 0x75, + 0x20, + 0x53, + 0x4d, + 0x50, + 0x20, + 0x54, + 0x75, + 0x65, + 0x20, + 0x44, + 0x65, + 0x63, + 0x20, + 0x36, + 0x20, + 0x31, + 0x35, + 0x3a, + 0x34, + 0x35, + 0x3a, + 0x31, + 0x33, + 0x20, + 0x55, + 0x54, + 0x43, + 0x20, + 0x32, + 0x30, + 0x31, + 0x36, + 0x20, + 0x69, + 0x36, + 0x38, + 0x36, + 0x0e, + 0x04, + 0x00, + 0x9c, + 0x00, + 0x08, + 0x10, + 0x0c, + 0x05, + 0x01, + 0x3e, + 0x0c, + 0xad, + 0x72, + 0x02, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x10, + 0x18, + 0x11, + 0x02, + 0x20, + 0x01, + 0x08, + 0xa8, + 0x10, + 0x06, + 0x00, + 0x04, + 0x02, + 0x23, + 0x54, + 0xff, + 0xfe, + 0xc2, + 0x57, + 0x02, + 0x02, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x08, + 0x04, + 0x65, + 0x74, + 0x68, + 0x30, + 0xfe, + 0x09, + 0x00, + 0x12, + 0x0f, + 0x03, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x09, + 0x00, + 0x12, + 0x0f, + 0x01, + 0x03, + 0xec, + 0xc3, + 0x00, + 0x10, + 0xfe, + 0x40, + 0x00, + 0x00, + 0x5e, + 0x01, + 0x68, + 0x74, + 0x74, + 0x70, + 0x73, + 0x3a, + 0x2f, + 0x2f, + 0x69, + 0x6d, + 0x72, + 0x69, + 0x67, + 0x68, + 0x74, + 0x2e, + 0x6d, + 0x75, + 0x64, + 0x2e, + 0x65, + 0x78, + 0x61, + 0x6d, + 0x70, + 0x6c, + 0x65, + 0x2e, + 0x63, + 0x6f, + 0x6d, + 0x2f, + 0x2e, + 0x77, + 0x65, + 0x6c, + 0x6c, + 0x2d, + 0x6b, + 0x6e, + 0x6f, + 0x77, + 0x6e, + 0x2f, + 0x6d, + 0x75, + 0x64, + 0x2f, + 0x76, + 0x31, + 0x2f, + 0x76, + 0x6f, + 0x6d, + 0x69, + 0x74, + 0x76, + 0x32, + 0x2e, + + 0x30, + 0x00, + 0x00, /* ethernet trailer */ +); + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_assert_logging(argc, argv, "WARN", "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ +#define _TEST_ADD_RECV(testpath, testdata) \ + g_test_add(testpath, \ + TestRecvFixture, \ + testdata, \ + _test_recv_fixture_setup, \ + test_recv, \ + _test_recv_fixture_teardown) + _TEST_ADD_RECV("/lldp/recv/0", &_test_recv_data0); + _TEST_ADD_RECV("/lldp/recv/0_twice", &_test_recv_data0_twice); + _TEST_ADD_RECV("/lldp/recv/1", &_test_recv_data1); + _TEST_ADD_RECV("/lldp/recv/2_ttl1", &_test_recv_data2_ttl1); + + g_test_add_data_func("/lldp/parse-frames/0", &_test_recv_data0_frame0, test_parse_frames); + g_test_add_data_func("/lldp/parse-frames/1", &_test_recv_data1_frame0, test_parse_frames); + g_test_add_data_func("/lldp/parse-frames/2", &_test_recv_data2_frame0_ttl1, test_parse_frames); + g_test_add_data_func("/lldp/parse-frames/3", &_test_parse_frames_3, test_parse_frames); +} diff --git a/src/core/devices/wifi/meson.build b/src/core/devices/wifi/meson.build new file mode 100644 index 0000000..743937d --- /dev/null +++ b/src/core/devices/wifi/meson.build @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +iwd_sources = files() +if enable_iwd + iwd_sources += files( + 'nm-device-iwd.c', + 'nm-iwd-manager.c', + ) +endif + +libnm_device_plugin_wifi_static = static_library( + 'nm-device-plugin-wifi-static', + sources: files( + 'nm-device-olpc-mesh.c', + 'nm-device-wifi-p2p.c', + 'nm-device-wifi.c', + 'nm-wifi-ap.c', + 'nm-wifi-common.c', + 'nm-wifi-p2p-peer.c', + 'nm-wifi-utils.c', + ) + iwd_sources, + dependencies: [ + core_plugin_dep, + ], + c_args: daemon_c_flags, +) + +libnm_device_plugin_wifi_static_dep = declare_dependency( + link_with: libnm_device_plugin_wifi_static, +) + +libnm_device_plugin_wifi = shared_module( + 'nm-device-plugin-wifi', + sources: files( + 'nm-wifi-factory.c', + ), + dependencies: [ + core_plugin_dep, + libnm_device_plugin_wifi_static_dep + ], + c_args: daemon_c_flags, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_device_plugin_wifi + +test( + 'check-local-devices-wifi', + check_exports, + args: [libnm_device_plugin_wifi.full_path(), linker_script_devices], +) + +if enable_tests + test_unit = 'test-devices-wifi' + + exe = executable( + test_unit, + 'tests/' + test_unit + '.c', + dependencies: [ + libNetworkManagerTest_dep, + libnm_device_plugin_wifi_static_dep, + ], + c_args: test_c_flags, + ) + + test( + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endif diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c new file mode 100644 index 0000000..8bfdcaa --- /dev/null +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -0,0 +1,3488 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Intel Corporation + */ + +#include "nm-default.h" + +#include "nm-device-iwd.h" + +#include + +#include "devices/nm-device-private.h" +#include "devices/nm-device.h" +#include "nm-act-request.h" +#include "nm-config.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-iwd-manager.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-setting-8021x.h" +#include "nm-setting-connection.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-wireless.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-utils.h" +#include "nm-wifi-common.h" +#include "nm-wifi-utils.h" +#include "settings/nm-settings-connection.h" +#include "settings/nm-settings.h" +#include "supplicant/nm-supplicant-types.h" +#include "nm-auth-utils.h" +#include "nm-manager.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceIwd +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIwd, + PROP_MODE, + PROP_BITRATE, + PROP_ACCESS_POINTS, + PROP_ACTIVE_ACCESS_POINT, + PROP_CAPABILITIES, + PROP_SCANNING, + PROP_LAST_SCAN, ); + +typedef struct { + GDBusObject * dbus_obj; + GDBusProxy * dbus_device_proxy; + GDBusProxy * dbus_station_proxy; + GDBusProxy * dbus_ap_proxy; + GDBusProxy * dbus_adhoc_proxy; + CList aps_lst_head; + NMWifiAP * current_ap; + GCancellable * cancellable; + NMDeviceWifiCapabilities capabilities; + NMActRequestGetSecretsCallId *wifi_secrets_id; + guint periodic_scan_id; + guint periodic_update_id; + bool enabled : 1; + bool can_scan : 1; + bool nm_autoconnect : 1; + bool iwd_autoconnect : 1; + bool scanning : 1; + bool scan_requested : 1; + bool act_mode_switch : 1; + bool secrets_failed : 1; + bool networks_requested : 1; + bool networks_changed : 1; + gint64 last_scan; + uint32_t ap_id; + guint32 rate; + NMEtherAddr current_ap_bssid; + GDBusMethodInvocation * pending_agent_request; + NMActiveConnection * assumed_ac; + guint assumed_ac_timeout; +} NMDeviceIwdPrivate; + +struct _NMDeviceIwd { + NMDevice parent; + NMDeviceIwdPrivate _priv; +}; + +struct _NMDeviceIwdClass { + NMDeviceClass parent; +}; + +/*****************************************************************************/ + +G_DEFINE_TYPE(NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE) + +#define NM_DEVICE_IWD_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD, NMDevice) + +/*****************************************************************************/ + +static void schedule_periodic_scan(NMDeviceIwd *self, gboolean initial_scan); + +static gboolean check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic); + +/*****************************************************************************/ + +static void +_ap_dump(NMDeviceIwd *self, NMLogLevel log_level, const NMWifiAP *ap, const char *prefix) +{ + char buf[1024]; + + buf[0] = '\0'; + _NMLOG(log_level, + LOGD_WIFI_SCAN, + "wifi-ap: %-7s %s", + prefix, + nm_wifi_ap_to_string(ap, buf, sizeof(buf), 0)); +} + +/* Callers ensure we're not removing current_ap */ +static void +ap_add_remove(NMDeviceIwd *self, + gboolean is_adding, /* or else is removing */ + NMWifiAP * ap, + gboolean recheck_available_connections) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (is_adding) { + g_object_ref(ap); + ap->wifi_device = NM_DEVICE(self); + c_list_link_tail(&priv->aps_lst_head, &ap->aps_lst); + nm_dbus_object_export(NM_DBUS_OBJECT(ap)); + _ap_dump(self, LOGL_DEBUG, ap, "added"); + nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, TRUE); + } else { + ap->wifi_device = NULL; + c_list_unlink(&ap->aps_lst); + _ap_dump(self, LOGL_DEBUG, ap, "removed"); + } + + _notify(self, PROP_ACCESS_POINTS); + + if (!is_adding) { + nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, FALSE); + nm_dbus_object_clear_and_unexport(&ap); + } + + if (priv->enabled && !priv->iwd_autoconnect) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + + if (recheck_available_connections) + nm_device_recheck_available_connections(NM_DEVICE(self)); +} + +static void +set_current_ap(NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections) +{ + NMDeviceIwdPrivate *priv; + NMWifiAP * old_ap; + + g_return_if_fail(NM_IS_DEVICE_IWD(self)); + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + old_ap = priv->current_ap; + + if (old_ap == new_ap) + return; + + if (new_ap) + priv->current_ap = g_object_ref(new_ap); + else + priv->current_ap = NULL; + + if (old_ap) { + if (nm_wifi_ap_get_fake(old_ap)) + ap_add_remove(self, FALSE, old_ap, recheck_available_connections); + g_object_unref(old_ap); + } + + memset(&priv->current_ap_bssid, 0, ETH_ALEN); + _notify(self, PROP_ACTIVE_ACCESS_POINT); + _notify(self, PROP_MODE); +} + +static void +remove_all_aps(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMWifiAP * ap, *ap_safe; + + if (c_list_is_empty(&priv->aps_lst_head)) + return; + + c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) + ap_add_remove(self, FALSE, ap, FALSE); + + if (!priv->iwd_autoconnect) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + + nm_device_recheck_available_connections(NM_DEVICE(self)); +} + +static NM80211ApSecurityFlags +ap_security_flags_from_network_type(const char *type) +{ + NM80211ApSecurityFlags flags; + + if (nm_streq(type, "psk")) + flags = NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (nm_streq(type, "8021x")) + flags = NM_802_11_AP_SEC_KEY_MGMT_802_1X; + else + return NM_802_11_AP_SEC_NONE; + + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + return flags; +} + +static NMWifiAP * +ap_from_network(NMDeviceIwd *self, + GDBusProxy * network, + NMRefString *bss_path, + gint64 last_seen_msec, + int16_t signal) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gs_unref_variant GVariant *name_value = NULL; + gs_unref_variant GVariant *type_value = NULL; + const char * name; + const char * type; + uint32_t ap_id; + gs_unref_bytes GBytes *ssid = NULL; + NMWifiAP * ap; + NMSupplicantBssInfo bss_info; + + g_return_val_if_fail(network, NULL); + + name_value = g_dbus_proxy_get_cached_property(network, "Name"); + type_value = g_dbus_proxy_get_cached_property(network, "Type"); + if (!name_value || !g_variant_is_of_type(name_value, G_VARIANT_TYPE_STRING) || !type_value + || !g_variant_is_of_type(type_value, G_VARIANT_TYPE_STRING)) + return NULL; + + name = g_variant_get_string(name_value, NULL); + type = g_variant_get_string(type_value, NULL); + + if (nm_streq(type, "wep")) { + /* WEP not supported */ + return NULL; + } + + /* What we get from IWD are networks, or ESSs, that may contain + * multiple APs, or BSSs, each. We don't get information about any + * specific BSSs within an ESS but we can safely present each ESS + * as an individual BSS to NM, which will be seen as ESSs comprising + * a single BSS each. NM won't be able to handle roaming but IWD + * already does that. We fake the BSSIDs as they don't play any + * role either. + */ + ap_id = priv->ap_id++; + + ssid = g_bytes_new(name, NM_MIN(32u, strlen(name))); + + bss_info = (NMSupplicantBssInfo){ + .bss_path = bss_path, + .last_seen_msec = last_seen_msec, + .bssid_valid = TRUE, + .mode = NM_802_11_MODE_INFRA, + .rsn_flags = ap_security_flags_from_network_type(type), + .ssid = ssid, + .signal_percent = nm_wifi_utils_level_to_quality(signal / 100), + .frequency = 2417, + .max_rate = 65000, + .bssid = NM_ETHER_ADDR_INIT(0x00, 0x01, 0x02, ap_id >> 16, ap_id >> 8, ap_id), + }; + + ap = nm_wifi_ap_new_from_properties(&bss_info); + + nm_assert(bss_path == nm_wifi_ap_get_supplicant_path(ap)); + + return ap; +} + +static void +insert_ap_from_network(NMDeviceIwd *self, + GHashTable * aps, + const char * path, + gint64 last_seen_msec, + int16_t signal) +{ + gs_unref_object GDBusProxy *network_proxy = NULL; + nm_auto_ref_string NMRefString *bss_path = nm_ref_string_new(path); + NMWifiAP * ap; + + if (g_hash_table_lookup(aps, bss_path)) { + _LOGD(LOGD_WIFI, "Duplicate network at %s", path); + return; + } + + network_proxy = + nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), path, NM_IWD_NETWORK_INTERFACE); + + ap = ap_from_network(self, network_proxy, bss_path, last_seen_msec, signal); + if (!ap) + return; + + g_hash_table_insert(aps, bss_path, ap); +} + +static void +get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *variant = NULL; + GVariantIter * networks; + const char * path; + int16_t signal; + NMWifiAP * ap, *ap_safe, *new_ap; + gboolean changed; + GHashTableIter ap_iter; + gs_unref_hashtable GHashTable *new_aps = NULL; + gint64 last_seen_msec; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant && nm_utils_error_is_cancelled(error)) + return; + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + priv->networks_requested = FALSE; + + if (!variant) { + _LOGE(LOGD_WIFI, "Station.GetOrderedNetworks failed: %s", error->message); + return; + } + + if (!g_variant_is_of_type(variant, G_VARIANT_TYPE("(a(on))"))) { + _LOGE(LOGD_WIFI, + "Station.GetOrderedNetworks returned type %s instead of (a(on))", + g_variant_get_type_string(variant)); + return; + } + + new_aps = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_object_unref); + g_variant_get(variant, "(a(on))", &networks); + + last_seen_msec = nm_utils_get_monotonic_timestamp_msec(); + while (g_variant_iter_next(networks, "(&on)", &path, &signal)) + insert_ap_from_network(self, new_aps, path, last_seen_msec, signal); + + g_variant_iter_free(networks); + + changed = priv->networks_changed; + priv->networks_changed = FALSE; + + c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) { + new_ap = g_hash_table_lookup(new_aps, nm_wifi_ap_get_supplicant_path(ap)); + if (new_ap) { + if (nm_wifi_ap_set_strength(ap, nm_wifi_ap_get_strength(new_ap))) { + _ap_dump(self, LOGL_TRACE, ap, "updated"); + changed = TRUE; + } + g_hash_table_remove(new_aps, nm_wifi_ap_get_supplicant_path(ap)); + continue; + } + + if (ap == priv->current_ap) { + /* Normally IWD will prevent the current AP from being + * removed from the list and set a low signal strength, + * but just making sure. + */ + continue; + } + + ap_add_remove(self, FALSE, ap, FALSE); + changed = TRUE; + } + + g_hash_table_iter_init(&ap_iter, new_aps); + while (g_hash_table_iter_next(&ap_iter, NULL, (gpointer) &ap)) { + ap_add_remove(self, TRUE, ap, FALSE); + g_hash_table_iter_remove(&ap_iter); + changed = TRUE; + } + + if (changed) { + if (!priv->iwd_autoconnect) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + + nm_device_recheck_available_connections(NM_DEVICE(self)); + } +} + +static void +update_aps(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (!priv->cancellable) + priv->cancellable = g_cancellable_new(); + + g_dbus_proxy_call(priv->dbus_station_proxy, + "GetOrderedNetworks", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 2000, + priv->cancellable, + get_ordered_networks_cb, + self); + priv->networks_requested = TRUE; +} + +static void +periodic_update(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + int ifindex; + guint32 new_rate; + int percent; + NMEtherAddr bssid; + gboolean ap_changed = FALSE; + NMPlatform * platform; + + ifindex = nm_device_get_ifindex(NM_DEVICE(self)); + if (ifindex <= 0) + g_return_if_reached(); + + platform = nm_device_get_platform(NM_DEVICE(self)); + + /* TODO: obtain quality through the net.connman.iwd.SignalLevelAgent API. + * For now we're waking up for the rate/BSSID updates anyway. + */ + if (!nm_platform_wifi_get_station(platform, ifindex, &bssid, &percent, &new_rate)) { + _LOGD(LOGD_WIFI, "BSSID / quality / rate platform query failed"); + return; + } + + if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) { +#if NM_MORE_LOGGING + ap_changed = TRUE; +#endif + } + + if (new_rate != priv->rate) { + priv->rate = new_rate; + _notify(self, PROP_BITRATE); + } + + if (nm_ether_addr_is_valid(&bssid) && !nm_ether_addr_equal(&bssid, &priv->current_ap_bssid)) { + priv->current_ap_bssid = bssid; + ap_changed |= nm_wifi_ap_set_address_bin(priv->current_ap, &bssid); + ap_changed |= nm_wifi_ap_set_freq(priv->current_ap, + nm_platform_wifi_get_frequency(platform, ifindex)); + } + + if (ap_changed) + _ap_dump(self, LOGL_DEBUG, priv->current_ap, "updated"); +} + +static gboolean +periodic_update_cb(gpointer user_data) +{ + periodic_update(user_data); + return TRUE; +} + +static void +send_disconnect(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + g_dbus_proxy_call(priv->dbus_station_proxy, + "Disconnect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +static void +wifi_secrets_cancel(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (priv->wifi_secrets_id) + nm_act_request_cancel_secrets(NULL, priv->wifi_secrets_id); + nm_assert(!priv->wifi_secrets_id); + + if (priv->pending_agent_request) { + g_dbus_method_invocation_return_error_literal(priv->pending_agent_request, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "NM secrets request cancelled"); + g_clear_object(&priv->pending_agent_request); + } +} + +static void +cleanup_assumed_connect(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (!priv->assumed_ac) + return; + + g_signal_handlers_disconnect_by_data(priv->assumed_ac, self); + g_clear_object(&priv->assumed_ac); +} + +static void +cleanup_association_attempt(NMDeviceIwd *self, gboolean disconnect) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + cleanup_assumed_connect(self); + wifi_secrets_cancel(self); + + set_current_ap(self, NULL, TRUE); + nm_clear_g_source(&priv->periodic_update_id); + nm_clear_g_source(&priv->assumed_ac_timeout); + + if (disconnect && priv->dbus_station_proxy) + send_disconnect(self); +} + +static void +reset_mode(NMDeviceIwd * self, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + g_dbus_proxy_call( + priv->dbus_device_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", NM_IWD_DEVICE_INTERFACE, "Mode", g_variant_new_string("station")), + G_DBUS_CALL_FLAGS_NONE, + 2000, + cancellable, + callback, + user_data); +} + +static gboolean +get_variant_boolean(GVariant *v, const char *property) +{ + if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, + "Property %s not cached or not boolean type", + property); + + return FALSE; + } + + return g_variant_get_boolean(v); +} + +static const char * +get_variant_state(GVariant *v) +{ + if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string"); + + return "unknown"; + } + + return g_variant_get_string(v, NULL); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (!priv->dbus_obj) + return; + + if (priv->dbus_station_proxy) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (NM_IN_STRSET(get_variant_state(value), "disconnecting", "disconnected")) + return; + } + + cleanup_association_attempt(self, TRUE); + priv->act_mode_switch = FALSE; + + if (!priv->dbus_station_proxy) + reset_mode(self, NULL, NULL, NULL); +} + +static void +disconnect_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + gs_unref_object NMDeviceIwd *self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + callback(NM_DEVICE(self), error, callback_user_data); +} + +static void +disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMDeviceIwd *self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + gs_free_error GError *cancelled_error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + callback(NM_DEVICE(self), cancelled_error, callback_user_data); +} + +static void +deactivate_async(NMDevice * device, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer callback_user_data) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gpointer user_data; + + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + + user_data = nm_utils_user_data_pack(g_object_ref(self), callback, callback_user_data); + + if (!priv->dbus_obj) { + nm_utils_invoke_on_idle(cancellable, disconnect_cb_on_idle, user_data); + return; + } + + cleanup_association_attempt(self, FALSE); + priv->act_mode_switch = FALSE; + + if (priv->dbus_station_proxy) { + g_dbus_proxy_call(priv->dbus_station_proxy, + "Disconnect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + disconnect_cb, + user_data); + } else + reset_mode(self, cancellable, disconnect_cb, user_data); +} + +static gboolean +is_connection_known_network(NMConnection *connection) +{ + NMIwdNetworkSecurity security; + gs_free char * ssid = NULL; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, &security)) + return FALSE; + + return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid, security); +} + +static gboolean +is_ap_known_network(NMWifiAP *ap) +{ + gs_unref_object GDBusProxy *network_proxy = NULL; + gs_unref_variant GVariant *known_network = NULL; + + network_proxy = + nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap)), + NM_IWD_NETWORK_INTERFACE); + if (!network_proxy) + return FALSE; + + known_network = g_dbus_proxy_get_cached_property(network_proxy, "KnownNetwork"); + return nm_g_variant_is_of_type(known_network, G_VARIANT_TYPE_OBJECT_PATH); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wireless; + const char * mac; + const char *const * mac_blacklist; + int i; + const char * perm_hw_addr; + const char * mode; + NMIwdNetworkSecurity security; + GBytes * ssid; + const guint8 * ssid_bytes; + gsize ssid_len; + + if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_wireless = nm_connection_get_setting_wireless(connection); + + /* complete_connection would be called (if at all) before this function + * so an SSID should always be set. IWD doesn't support non-UTF8 SSIDs + * (ignores BSSes with such SSIDs and has no way to represent them on + * DBus) so we can cut it short for connections with a non-UTF8 SSID. + */ + ssid = nm_setting_wireless_get_ssid(s_wireless); + if (!ssid) + return FALSE; + + ssid_bytes = g_bytes_get_data(ssid, &ssid_len); + if (!g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "non-UTF-8 connection SSID not supported by IWD backend"); + return FALSE; + } + + perm_hw_addr = nm_device_get_permanent_hw_address(device); + mac = nm_setting_wireless_get_mac_address(s_wireless); + if (perm_hw_addr) { + if (mac && !nm_utils_hwaddr_matches(mac, -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "device MAC address does not match the profile"); + return FALSE; + } + + /* Check for MAC address blacklist */ + mac_blacklist = nm_setting_wireless_get_mac_address_blacklist(s_wireless); + for (i = 0; mac_blacklist[i]; i++) { + nm_assert(nm_utils_hwaddr_valid(mac_blacklist[i], ETH_ALEN)); + + if (nm_utils_hwaddr_matches(mac_blacklist[i], -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "MAC address blacklisted"); + return FALSE; + } + } + } else if (mac) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has no valid MAC address as required by profile"); + return FALSE; + } + + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) + || security == NM_IWD_NETWORK_SECURITY_WEP) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection authentication type not supported by IWD backend"); + return FALSE; + } + + mode = nm_setting_wireless_get_mode(s_wireless); + + /* Hidden SSIDs only supported in client mode */ + if (nm_setting_wireless_get_hidden(s_wireless) + && !NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { + nm_utils_error_set_literal( + error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "non-infrastructure hidden networks not supported by the IWD backend"); + return FALSE; + } + + if (NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { + /* 8021x networks can only be used if they've been provisioned on the IWD side and + * thus are Known Networks. + */ + if (security == NM_IWD_NETWORK_SECURITY_8021X) { + if (!is_connection_known_network(connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "802.1x connections must have IWD provisioning files"); + return FALSE; + } + } else if (!NM_IN_SET(security, + NM_IWD_NETWORK_SECURITY_NONE, + NM_IWD_NETWORK_SECURITY_PSK)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "IWD backend only supports Open, PSK and 802.1x network " + "authentication in Infrastructure mode"); + return FALSE; + } + } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "device does not support Access Point mode"); + return FALSE; + } + + if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_PSK)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "IWD backend only supports PSK authentication in AP mode"); + return FALSE; + } + } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "device does not support Ad-Hoc mode"); + return FALSE; + } + + if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) { + nm_utils_error_set_literal( + error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "IWD backend only supports Open and PSK authentication in Ad-Hoc mode"); + return FALSE; + } + } else { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "'%s' type profiles not supported by IWD backend", + mode); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wifi; + const char * mode; + NMWifiAP * ap = NULL; + NMIwdNetworkSecurity security; + + s_wifi = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wifi, FALSE); + + /* a connection that is available for a certain @specific_object, MUST + * also be available in general (without @specific_object). */ + + if (specific_object) { + ap = nm_wifi_ap_lookup_for_device(NM_DEVICE(self), specific_object); + if (!ap) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "requested access point not found"); + return FALSE; + } + if (!nm_wifi_ap_check_compatible(ap, connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "requested access point is not compatible with profile"); + return FALSE; + } + } + + /* AP and Ad-Hoc connections can be activated independent of the scan list */ + mode = nm_setting_wireless_get_mode(s_wifi); + if (NM_IN_STRSET(mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) + return TRUE; + + /* Hidden SSIDs obviously don't always appear in the scan list either. + * + * For an explicit user-activation-request, a connection is considered + * available because for hidden Wi-Fi, clients didn't consistently + * set the 'hidden' property to indicate hidden SSID networks. If + * activating but the network isn't available let the device recheck + * availability. + */ + if (nm_setting_wireless_get_hidden(s_wifi) + || NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP)) + return TRUE; + + if (!ap) + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + + if (!ap) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "no compatible access point found"); + return FALSE; + } + + /* 8021x networks can only be used if they've been provisioned on the IWD side and + * thus are Known Networks. + */ + if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) + && security == NM_IWD_NETWORK_SECURITY_8021X) { + if (!is_ap_known_network(ap)) { + nm_utils_error_set_literal( + error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "802.1x network is not an IWD Known Network (missing provisioning file?)"); + return FALSE; + } + } + + return TRUE; +} + +/* To be used where the SSID has been validated before */ +static char * +iwd_ssid_to_str(const GBytes *ssid) +{ + const guint8 *ssid_bytes; + gsize ssid_len; + + ssid_bytes = g_bytes_get_data((GBytes *) ssid, &ssid_len); + nm_assert(ssid && g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)); + return g_strndup((const char *) ssid_bytes, ssid_len); +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wifi; + gs_free char * ssid_utf8 = NULL; + NMWifiAP * ap; + GBytes * ssid = NULL; + gboolean hidden = FALSE; + const char * mode; + + s_wifi = nm_connection_get_setting_wireless(connection); + + mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL; + + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP) || !specific_object) { + const guint8 *ssid_bytes; + gsize ssid_len; + + /* If not given a specific object, we need at minimum an SSID */ + if (!s_wifi) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wireless' setting is required if no AP path was given."); + return FALSE; + } + + ssid = nm_setting_wireless_get_ssid(s_wifi); + ssid_bytes = g_bytes_get_data(ssid, &ssid_len); + + if (!ssid || ssid_len == 0 || !g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wireless' setting with a valid UTF-8 SSID is required if no AP " + "path was given."); + return FALSE; + } + } + + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + ap = NULL; + } else if (!specific_object) { + /* Find a compatible AP in the scan list */ + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + if (!ap) { + /* If we still don't have an AP, then the WiFI settings needs to be + * fully specified by the client. Might not be able to find an AP + * if the network isn't broadcasting the SSID for example. + */ + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + + /* We could either require the profile to be marked as hidden by the + * client or at least check that a hidden AP with a matching security + * type is in range using Station.GetHiddenAccessPoints(). For now + * assume it is hidden even though that will reveal the SSID on the + * air. + */ + hidden = TRUE; + } + } else { + ap = nm_wifi_ap_lookup_for_device(NM_DEVICE(self), specific_object); + if (!ap) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, + "The access point %s was not in the scan list.", + specific_object); + return FALSE; + } + + ssid = nm_wifi_ap_get_ssid(ap); + + /* Add a wifi setting if one doesn't exist yet */ + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + } + } + + if (ap) { + if (!nm_wifi_ap_complete_connection(ap, connection, FALSE, error)) + return FALSE; + } + + ssid_utf8 = iwd_ssid_to_str(ssid); + nm_utils_complete_generic( + nm_device_get_platform(device), + connection, + NM_SETTING_WIRELESS_SETTING_NAME, + existing_connections, + ssid_utf8, + ssid_utf8, + NULL, + nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device), + TRUE); + + if (hidden) + g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); + + return TRUE; +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDeviceState state = nm_device_get_state(device); + + /* Available if either the device is UP and in station mode + * or in AP/Ad-Hoc modes while activating or activated. Device + * may be temporarily DOWN while activating or deactivating and + * we don't want it to be marked unavailable because of this. + * + * For reference: + * We call nm_device_queue_recheck_available whenever + * priv->enabled changes or priv->dbus_station_proxy changes. + */ + return priv->dbus_obj && priv->enabled + && (priv->dbus_station_proxy + || (state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING)); +} + +static gboolean +get_autoconnect_allowed(NMDevice *device) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(NM_DEVICE_IWD(device)); + + return priv->nm_autoconnect; +} + +static gboolean +can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMConnection * connection; + NMSettingWireless * s_wifi; + NMWifiAP * ap; + const char * mode; + guint64 timestamp = 0; + + nm_assert(!specific_object || !*specific_object); + + if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class)->can_auto_connect(device, sett_conn, NULL)) + return FALSE; + + connection = nm_settings_connection_get_connection(sett_conn); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wifi, FALSE); + + /* Don't auto-activate AP or Ad-Hoc connections. + * Note the wpa_supplicant backend has the opposite policy. + */ + mode = nm_setting_wireless_get_mode(s_wifi); + if (mode && g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0) + return FALSE; + + /* Don't autoconnect to networks that have been tried at least once + * but haven't been successful, since these are often accidental choices + * from the menu and the user may not know the password. + */ + if (nm_settings_connection_get_timestamp(sett_conn, ×tamp)) { + if (timestamp == 0) + return FALSE; + } + + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + if (ap) { + /* All good; connection is usable */ + NM_SET_OUT(specific_object, g_strdup(nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)))); + return TRUE; + } + + return FALSE; +} + +const CList * +_nm_device_iwd_get_aps(NMDeviceIwd *self) +{ + return &NM_DEVICE_IWD_GET_PRIVATE(self)->aps_lst_head; +} + +static void +scan_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant && nm_utils_error_is_cancelled(error)) + return; + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + priv->scan_requested = FALSE; + priv->last_scan = nm_utils_get_monotonic_timestamp_msec(); + _notify(self, PROP_LAST_SCAN); + + /* On success, priv->scanning becomes true right before or right + * after this callback, so the next automatic scan will be + * scheduled when priv->scanning goes back to false. On error, + * schedule a retry now. + */ + if (error && !priv->scanning) + schedule_periodic_scan(self, FALSE); +} + +static void +dbus_request_scan_cb(NMDevice * device, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv; + gs_unref_variant GVariant *scan_options = user_data; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + if (check_scanning_prohibited(self, FALSE)) { + g_dbus_method_invocation_return_error_literal(context, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "Scanning not allowed at this time"); + return; + } + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (!priv->can_scan) { + g_dbus_method_invocation_return_error_literal(context, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "Scanning not allowed while unavailable"); + return; + } + + if (scan_options) { + gs_unref_variant GVariant *val = g_variant_lookup_value(scan_options, "ssids", NULL); + + if (val) { + g_dbus_method_invocation_return_error_literal(context, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "'ssid' scan option not supported"); + return; + } + } + + if (!priv->scanning && !priv->scan_requested) { + g_dbus_proxy_call(priv->dbus_station_proxy, + "Scan", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + scan_cb, + self); + priv->scan_requested = TRUE; + } + + g_dbus_method_invocation_return_value(context, NULL); +} + +void +_nm_device_iwd_request_scan(NMDeviceIwd *self, GVariant *options, GDBusMethodInvocation *invocation) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + + if (!priv->can_scan) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "Scanning not allowed while unavailable"); + return; + } + + nm_device_auth_request(device, + invocation, + NULL, + NM_AUTH_PERMISSION_WIFI_SCAN, + TRUE, + NULL, + dbus_request_scan_cb, + nm_g_variant_ref(options)); +} + +static gboolean +check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + g_return_val_if_fail(priv->dbus_obj != NULL, TRUE); + + switch (nm_device_get_state(NM_DEVICE(self))) { + case NM_DEVICE_STATE_UNKNOWN: + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_DEACTIVATING: + /* Prohibit scans when unusable or activating */ + return TRUE; + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_FAILED: + case NM_DEVICE_STATE_ACTIVATED: + case NM_DEVICE_STATE_NEED_AUTH: + break; + } + + /* Prohibit scans if IWD is busy */ + return !priv->can_scan; +} + +static const char * +get_agent_request_network_path(GDBusMethodInvocation *invocation) +{ + const char *method_name = g_dbus_method_invocation_get_method_name(invocation); + GVariant * params = g_dbus_method_invocation_get_parameters(invocation); + const char *network_path = NULL; + + if (nm_streq(method_name, "RequestPassphrase")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestPrivateKeyPassphrase")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestUserNameAndPassword")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestUserPassword")) { + const char *user; + g_variant_get(params, "(ss)", &network_path, &user); + } + + return network_path; +} + +/* + * try_reply_agent_request + * + * Check if the connection settings already have the secrets corresponding + * to the IWD agent method that was invoked. If they do, send the method reply + * with the appropriate secrets. Otherwise, return the missing secret's setting + * name and key so the caller can send a NM secrets request with this data. + * Return TRUE in either case, return FALSE if an error is detected. + */ +static gboolean +try_reply_agent_request(NMDeviceIwd * self, + NMConnection * connection, + GDBusMethodInvocation *invocation, + const char ** setting_name, + const char ** setting_key, + gboolean * replied) +{ + const char * method_name = g_dbus_method_invocation_get_method_name(invocation); + NMSettingWirelessSecurity *s_wireless_sec; + NMSetting8021x * s_8021x; + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + s_8021x = nm_connection_get_setting_802_1x(connection); + + *replied = FALSE; + + if (nm_streq(method_name, "RequestPassphrase")) { + const char *psk; + + if (!s_wireless_sec) + return FALSE; + + psk = nm_setting_wireless_security_get_psk(s_wireless_sec); + if (psk) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the PSK to the IWD Agent"); + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", psk)); + *replied = TRUE; + return TRUE; + } + + *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + *setting_key = NM_SETTING_WIRELESS_SECURITY_PSK; + return TRUE; + } else if (nm_streq(method_name, "RequestPrivateKeyPassphrase")) { + const char *password; + + if (!s_8021x) + return FALSE; + + password = nm_setting_802_1x_get_private_key_password(s_8021x); + if (password) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the private key password to the IWD Agent"); + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password)); + *replied = TRUE; + return TRUE; + } + + *setting_name = NM_SETTING_802_1X_SETTING_NAME; + *setting_key = NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD; + return TRUE; + } else if (nm_streq(method_name, "RequestUserNameAndPassword")) { + const char *identity, *password; + + if (!s_8021x) + return FALSE; + + identity = nm_setting_802_1x_get_identity(s_8021x); + password = nm_setting_802_1x_get_password(s_8021x); + if (identity && password) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the username and password to the IWD Agent"); + + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(ss)", identity, password)); + *replied = TRUE; + return TRUE; + } + + *setting_name = NM_SETTING_802_1X_SETTING_NAME; + if (!identity) + *setting_key = NM_SETTING_802_1X_IDENTITY; + else + *setting_key = NM_SETTING_802_1X_PASSWORD; + return TRUE; + } else if (nm_streq(method_name, "RequestUserPassword")) { + const char *password; + + if (!s_8021x) + return FALSE; + + password = nm_setting_802_1x_get_password(s_8021x); + if (password) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the user password to the IWD Agent"); + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password)); + *replied = TRUE; + return TRUE; + } + + *setting_name = NM_SETTING_802_1X_SETTING_NAME; + *setting_key = NM_SETTING_802_1X_PASSWORD; + return TRUE; + } else + return FALSE; +} + +static gboolean +assumed_ac_timeout_cb(gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + nm_assert(priv->assumed_ac); + + priv->assumed_ac_timeout = 0; + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + /* NMDevice's state change -> NMActRequests/NMActiveConnection's state + * change -> assumed_connection_state_changed_before_managed() -> + * cleanup_association_attempt() so no need to call it explicitly. + */ + return G_SOURCE_REMOVE; +} + +static void wifi_secrets_get_one(NMDeviceIwd * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char * setting_key, + GDBusMethodInvocation * invocation); + +static void +wifi_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * s_connection, + GError * error, + gpointer user_data) +{ + NMDeviceIwd * self; + NMDeviceIwdPrivate * priv; + NMDevice * device; + GDBusMethodInvocation * invocation; + const char * setting_name; + const char * setting_key; + gboolean replied; + NMSecretAgentGetSecretsFlags get_secret_flags = + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + nm_utils_user_data_unpack(user_data, &self, &invocation); + + g_return_if_fail(NM_IS_DEVICE_IWD(self)); + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + device = NM_DEVICE(self); + + g_return_if_fail(priv->wifi_secrets_id == call_id); + + priv->wifi_secrets_id = NULL; + + if (nm_utils_error_is_cancelled(error)) { + priv->secrets_failed = TRUE; + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "NM secrets request cancelled"); + return; + } + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_act_request_get_settings_connection(req) == s_connection); + + if (nm_device_get_state(device) != NM_DEVICE_STATE_NEED_AUTH) + goto secrets_error; + + if (error) { + _LOGW(LOGD_WIFI, "%s", error->message); + goto secrets_error; + } + + if (!try_reply_agent_request(self, + nm_act_request_get_applied_connection(req), + invocation, + &setting_name, + &setting_key, + &replied)) + goto secrets_error; + + if (replied) { + /* If we replied to the secrets request from IWD in the "disconnected" + * state and IWD doesn't move to a new state within 1 second, assume + * something went wrong (shouldn't happen). If a state change arrives + * after that nothing is lost, state_changed() will try to assume the + * connection again. + */ + if (priv->assumed_ac) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (nm_streq(get_variant_state(value), "disconnected")) + priv->assumed_ac_timeout = g_timeout_add_seconds(1, assumed_ac_timeout_cb, self); + } + + /* Change state back to what it was before NEED_AUTH */ + nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + return; + } + + if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req), NULL)) + get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + + /* Request further secrets if we still need something */ + wifi_secrets_get_one(self, setting_name, get_secret_flags, setting_key, invocation); + return; + +secrets_error: + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "NM secrets request failed"); + + if (priv->assumed_ac) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + /* NMDevice's state change -> NMActRequests/NMActiveConnection's state + * change -> assumed_connection_state_changed_before_managed() -> + * cleanup_association_attempt() so no need to call it explicitly. + */ + } else { + priv->secrets_failed = TRUE; + /* Now wait for the Connect callback to update device state */ + } +} + +static void +wifi_secrets_get_one(NMDeviceIwd * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char * setting_key, + GDBusMethodInvocation * invocation) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMActRequest * req; + + wifi_secrets_cancel(self); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv->wifi_secrets_id = nm_act_request_get_secrets(req, + TRUE, + setting_name, + flags, + NM_MAKE_STRV(setting_key), + wifi_secrets_cb, + nm_utils_user_data_pack(self, invocation)); +} + +static void +network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDevice * device = NM_DEVICE(self); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + NMConnection * connection; + gs_free char * ssid = NULL; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; + GVariant * value; + gboolean disconnect; + + disconnect = !priv->iwd_autoconnect + || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL); + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + gs_free char *dbus_error = NULL; + + /* Connection failed; radio problems or if the network wasn't + * open, the passwords or certificates may be wrong. + */ + + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) Network.Connect failed: %s", + error->message); + + if (nm_utils_error_is_cancelled(error)) + return; + + if (!NM_IN_SET(nm_device_get_state(device), + NM_DEVICE_STATE_CONFIG, + NM_DEVICE_STATE_NEED_AUTH)) + return; + + connection = nm_device_get_applied_connection(device); + if (!connection) + goto failed; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) + dbus_error = g_dbus_error_get_remote_error(error); + + if (nm_streq0(dbus_error, "net.connman.iwd.Failed")) { + nm_connection_clear_secrets(connection); + + /* If secrets were wrong, we'd be getting a net.connman.iwd.Failed */ + reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + } else if (nm_streq0(dbus_error, "net.connman.iwd.Aborted") && priv->secrets_failed) { + /* If agent call was cancelled we'd be getting a net.connman.iwd.Aborted */ + reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + } + + goto failed; + } + + nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); + + disconnect = TRUE; + + connection = nm_device_get_applied_connection(device); + if (!connection) + goto failed; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL)) + goto failed; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Connected to '%s'.", + ssid); + nm_device_activate_schedule_stage3_ip_config_start(device); + + return; + +failed: + /* If necessary call Disconnect to make sure IWD's autoconnect is disabled */ + cleanup_association_attempt(self, disconnect); + + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + if (!priv->iwd_autoconnect && nm_streq(get_variant_state(value), "disconnected")) { + schedule_periodic_scan(self, TRUE); + + if (!priv->nm_autoconnect) { + priv->nm_autoconnect = true; + nm_device_emit_recheck_auto_activate(device); + } + } + g_variant_unref(value); +} + +static void +act_failed_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDevice * device = NM_DEVICE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant && nm_utils_error_is_cancelled(error)) + return; + + /* Change state to FAILED unless already done by state_changed + * which may have been triggered by the station interface + * appearing on DBus. + */ + if (nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG) + nm_device_queue_state(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); +} + +static void +act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + gs_free char * ssid = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) {AccessPoint,AdHoc}.Start() failed: %s", + error->message); + + if (nm_utils_error_is_cancelled(error)) + return; + + if (!NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG)) + return; + + goto error; + } + + nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); + + if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device), + &ssid, + NULL)) + goto error; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Started '%s'.", + ssid); + nm_device_activate_schedule_stage3_ip_config_start(device); + + return; + +error: + reset_mode(self, priv->cancellable, act_failed_cb, self); +} + +/* Check if we're activating an AP/AdHoc connection and if the target + * DBus interface has appeared already. If so proceed to call Start or + * StartOpen on that interface. + */ +static void +act_check_interface(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMSettingWireless * s_wireless; + GDBusProxy * proxy = NULL; + gs_free char * ssid = NULL; + const char * mode; + NMIwdNetworkSecurity security; + + if (!priv->act_mode_switch) + return; + + s_wireless = + (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + + mode = nm_setting_wireless_get_mode(s_wireless); + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) + proxy = priv->dbus_ap_proxy; + else if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) + proxy = priv->dbus_adhoc_proxy; + + if (!proxy) + return; + + priv->act_mode_switch = FALSE; + + if (!NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG)) + return; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device), + &ssid, + &security)) + goto failed; + + if (security == NM_IWD_NETWORK_SECURITY_NONE) { + g_dbus_proxy_call(proxy, + "StartOpen", + g_variant_new("(s)", ssid), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->cancellable, + act_start_cb, + self); + } else if (security == NM_IWD_NETWORK_SECURITY_PSK) { + NMSettingWirelessSecurity *s_wireless_sec; + const char * psk; + + s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting( + device, + NM_TYPE_SETTING_WIRELESS_SECURITY); + psk = nm_setting_wireless_security_get_psk(s_wireless_sec); + + if (!psk) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) No PSK for '%s'.", ssid); + goto failed; + } + + g_dbus_proxy_call(proxy, + "Start", + g_variant_new("(ss)", ssid, psk), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->cancellable, + act_start_cb, + self); + } else + goto failed; + + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid); + return; + +failed: + reset_mode(self, priv->cancellable, act_failed_cb, self); +} + +static void +act_set_mode_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) Setting Device.Mode failed: %s", + error->message); + + if (nm_utils_error_is_cancelled(error)) + return; + + if (!NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG) + || !priv->act_mode_switch) + return; + + priv->act_mode_switch = FALSE; + nm_device_queue_state(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) IWD Device.Mode set successfully"); + + act_check_interface(self); +} + +static void +act_set_mode(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + const char * iwd_mode; + const char * mode; + NMSettingWireless * s_wireless; + + s_wireless = + (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + mode = nm_setting_wireless_get_mode(s_wireless); + + /* We need to first set interface mode (Device.Mode) to ap or ad-hoc. + * We can't directly queue a call to the Start/StartOpen method on + * the DBus interface that's going to be created after the property + * set call returns. + */ + iwd_mode = nm_streq(mode, NM_SETTING_WIRELESS_MODE_AP) ? "ap" : "ad-hoc"; + + if (!priv->cancellable) + priv->cancellable = g_cancellable_new(); + + g_dbus_proxy_call( + priv->dbus_device_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", NM_IWD_DEVICE_INTERFACE, "Mode", g_variant_new("s", iwd_mode)), + G_DBUS_CALL_FLAGS_NONE, + 2000, + priv->cancellable, + act_set_mode_cb, + self); + priv->act_mode_switch = TRUE; +} + +static void +act_psk_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * s_connection, + GError * error, + gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv; + NMDevice * device; + + if (nm_utils_error_is_cancelled(error)) + return; + + priv = NM_DEVICE_IWD_GET_PRIVATE(self); + device = NM_DEVICE(self); + + g_return_if_fail(priv->wifi_secrets_id == call_id); + priv->wifi_secrets_id = NULL; + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_act_request_get_settings_connection(req) == s_connection); + + if (nm_device_get_state(device) != NM_DEVICE_STATE_NEED_AUTH) + goto secrets_error; + + if (error) { + _LOGW(LOGD_WIFI, "%s", error->message); + goto secrets_error; + } + + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) missing PSK request completed"); + + /* Change state back to what it was before NEED_AUTH */ + nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + act_set_mode(self); + return; + +secrets_error: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + cleanup_association_attempt(self, FALSE); +} + +static void +set_powered(NMDeviceIwd *self, gboolean powered) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + g_dbus_proxy_call( + priv->dbus_device_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", NM_IWD_DEVICE_INTERFACE, "Powered", g_variant_new("b", powered)), + G_DBUS_CALL_FLAGS_NONE, + 2000, + NULL, + NULL, + NULL); +} + +/*****************************************************************************/ + +static NMWifiAP * +find_ap_by_supplicant_path(NMDeviceIwd *self, const NMRefString *path) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMWifiAP * tmp; + + c_list_for_each_entry (tmp, &priv->aps_lst_head, aps_lst) + if (nm_wifi_ap_get_supplicant_path(tmp) == path) + return tmp; + + return NULL; +} + +static void +assumed_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMDeviceIwd *self) +{ + NMSettingsConnection * sett_conn = nm_active_connection_get_settings_connection(active); + NMActiveConnectionState state = nm_active_connection_get_state(active); + + /* Delete the temporary connection created for an external IWD connection + * (triggered by somebody outside of NM, be it IWD autoconnect or a + * parallel client), unless it's been referenced by a Known Network + * object since, which would remove the EXTERNAL flag. + * + * Note we can't do this too early, e.g. at the same time that we're + * setting the device state to FAILED or DISCONNECTING because the + * connection shouldn't disappear while it's still being used. We do + * this on the connection's transition to DEACTIVATED same as as + * NMManager does for external activations. + */ + if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + + g_signal_handlers_disconnect_by_func(active, assumed_connection_state_changed, NULL); + + if (sett_conn + && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + nm_settings_connection_delete(sett_conn, FALSE); +} + +static void +assumed_connection_state_changed_before_managed(NMActiveConnection *active, + GParamSpec * pspec, + NMDeviceIwd * self) +{ + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMActiveConnectionState state = nm_active_connection_get_state(active); + gboolean disconnect; + + if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + + /* When an assumed connection fails we always get called, even if the + * activation hasn't reached PREPARE or CONFIG, e.g. because of a policy + * or authorization problem in NMManager. .deactivate would only be + * called starting at some stage so we can't rely on that. + * + * If the error happened before PREPARE (where we set a non-NULL + * priv->current_ap) that will mean NM is somehow blocking autoconnect + * so we want to call IWD's Station.Disconnect() to block its + * autoconnect. If this happens during or after PREPARE, we just + * clean up and wait for a new attempt by IWD. + * + * cleanup_association_attempt will clear priv->assumed_ac, disconnect + * this callback from the signal and also send a Disconnect to IWD if + * needed. + * + * Note this function won't be called after IWD transitions to + * "connected" (and NMDevice to IP_CONFIG) as we disconnect from the + * signal at that point, cleanup_association_attempt() will be + * triggered by an IWD state change instead. + */ + disconnect = !priv->current_ap; + cleanup_association_attempt(self, disconnect); +} + +static void +assume_connection(NMDeviceIwd *self, NMWifiAP *ap) +{ + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingsConnection *sett_conn; + gs_unref_object NMAuthSubject *subject = NULL; + NMActiveConnection * ac; + gs_free_error GError *error = NULL; + + /* We can use the .update_connection / nm_device_emit_recheck_assume + * API but we can also pass an assumed/external activation type + * directly to nm_manager_activate_connection() and skip the + * complicated process of creating a matching connection, taking + * advantage of the Known Networks pointing directly to a mirror + * connection. The only downside seems to be + * nm_manager_activate_connection() goes through the extra + * authorization. + * + * However for now we implement a similar behaviour using a normal + * "managed" activation. For one, assumed/external + * connection state is not reflected in nm_manager_get_state() until + * fully activated. Secondly setting the device state to FAILED + * is treated as ACTIVATED so we'd have to find another way to signal + * that stage2 is failing asynchronously. Thirdly the connection + * becomes "managed" only when ACTIVATED but for IWD it's really + * managed when IP_CONFIG starts. + */ + sett_conn = nm_iwd_manager_get_ap_mirror_connection(nm_iwd_manager_get(), ap); + if (!sett_conn) + goto error; + + subject = nm_auth_subject_new_internal(); + ac = nm_manager_activate_connection( + NM_MANAGER_GET, + sett_conn, + NULL, + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)), + NM_DEVICE(self), + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + &error); + + if (!ac) { + _LOGW(LOGD_WIFI, "Activation: (wifi) assume error: %s", error->message); + goto error; + } + + /* If no Known Network existed for this AP, we generated a temporary + * NMSettingsConnection with the EXTERNAL flag. It is not referenced by + * any Known Network objects at this time so we want to delete it if the + * IWD connection ends up failing or a later part of the activation fails + * before IWD created a Known Network. + * Setting the activation type to EXTERNAL would do this by causing + * NM_ACTIVATION_STATE_FLAG_EXTERNAL to be set on the NMActiveConnection + * but we don't want the connection to be marked EXTERNAL because we + * will be assuming the ownership of it in IP_CONFIG or thereabouts. + * + * This callback stays connected forever while the second one gets + * disconnected when we reset the activation type to managed. + */ + g_signal_connect(ac, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(assumed_connection_state_changed), + NULL); + g_signal_connect(ac, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(assumed_connection_state_changed_before_managed), + self); + priv->assumed_ac = g_object_ref(ac); + + return; + +error: + send_disconnect(self); + + if (sett_conn + && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + nm_settings_connection_delete(sett_conn, FALSE); +} + +static void +assumed_connection_progress_to_ip_config(NMDeviceIwd *self, gboolean was_postponed) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState dev_state = nm_device_get_state(device); + + wifi_secrets_cancel(self); + nm_clear_g_source(&priv->assumed_ac_timeout); + + /* NM takes over the activation from this point on so clear the assumed + * activation state and if we were using NM_ACTIVATION_TYPE_ASSUMED or + * _EXTERNAL we'd need to reset the activation type to _MANAGED at this + * point instead of waiting for the ACTIVATED state (as done in + * nm_active_connection_set_state). + */ + cleanup_assumed_connect(self); + + if (dev_state == NM_DEVICE_STATE_NEED_AUTH) + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_CONFIG, + NM_DEVICE_STATE_REASON_NONE); + + /* If stage2 had returned NM_ACT_STAGE_RETURN_POSTPONE, we tell NMDevice + * that stage2 is done. + */ + if (was_postponed) + nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self)); +} + +static void +initial_check_assume(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + const char * network_path_str; + nm_auto_ref_string NMRefString *network_path = NULL; + NMWifiAP * ap = NULL; + gs_unref_variant GVariant *state_value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + gs_unref_variant GVariant *cn_value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork"); + + if (!NM_IN_STRSET(get_variant_state(state_value), "connecting", "connected", "roaming")) + return; + + if (!priv->iwd_autoconnect) { + send_disconnect(self); + return; + } + + if (!cn_value || !g_variant_is_of_type(cn_value, G_VARIANT_TYPE_OBJECT_PATH)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork property not cached or not an object path"); + return; + } + + network_path_str = g_variant_get_string(cn_value, NULL); + network_path = nm_ref_string_new(network_path_str); + ap = find_ap_by_supplicant_path(self, network_path); + + if (!ap) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork points to an unknown Network %s", + network_path_str); + return; + } + + _LOGD(LOGD_DEVICE | LOGD_WIFI, "assuming connection in initial_check_assume"); + assume_connection(self, ap); +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMWifiAP * ap = NULL; + gs_unref_object NMWifiAP *ap_fake = NULL; + NMActRequest * req; + NMConnection * connection; + NMSettingWireless * s_wireless; + const char * mode; + const char * ap_path; + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wireless, NM_ACT_STAGE_RETURN_FAILURE); + + /* AP, Ad-Hoc modes never use a specific object or existing scanned AP */ + mode = nm_setting_wireless_get_mode(s_wireless); + if (NM_IN_STRSET(mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) + goto add_new; + + ap_path = nm_active_connection_get_specific_object(NM_ACTIVE_CONNECTION(req)); + ap = ap_path ? nm_wifi_ap_lookup_for_device(NM_DEVICE(self), ap_path) : NULL; + if (ap) { + set_current_ap(self, ap, TRUE); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + if (ap) { + nm_active_connection_set_specific_object(NM_ACTIVE_CONNECTION(req), + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap))); + set_current_ap(self, ap, TRUE); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + + /* In infrastructure mode the specific object should be set by now except + * for a first-time connection to a hidden network. If a hidden network is + * a Known Network it should still have been in the AP list. + */ + if (!nm_setting_wireless_get_hidden(s_wireless) || is_connection_known_network(connection)) + return NM_ACT_STAGE_RETURN_FAILURE; + +add_new: + /* If the user is trying to connect to an AP that NM doesn't yet know about + * (hidden network or something) or starting a Hotspot, create a fake AP + * from the security settings in the connection. This "fake" AP gets used + * until the real one is found in the scan list (Ad-Hoc or Hidden), or until + * the device is deactivated (Ad-Hoc or Hotspot). + */ + ap_fake = nm_wifi_ap_new_fake_from_connection(connection); + if (!ap_fake) + g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE); + + if (nm_wifi_ap_is_hotspot(ap_fake)) + nm_wifi_ap_set_address(ap_fake, nm_device_get_hw_address(device)); + + g_object_freeze_notify(G_OBJECT(self)); + ap_add_remove(self, TRUE, ap_fake, FALSE); + g_object_thaw_notify(G_OBJECT(self)); + set_current_ap(self, ap_fake, FALSE); + nm_active_connection_set_specific_object(NM_ACTIVE_CONNECTION(req), + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap_fake))); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMActRequest * req; + NMConnection * connection; + NMSettingWireless * s_wireless; + const char * mode; + + req = nm_device_get_act_request(device); + connection = nm_act_request_get_applied_connection(req); + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wireless, NM_ACT_STAGE_RETURN_FAILURE); + + mode = nm_setting_wireless_get_mode(s_wireless); + + if (NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { + gs_unref_object GDBusProxy *network_proxy = NULL; + NMWifiAP * ap = priv->current_ap; + + if (!ap) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + + /* With priv->iwd_autoconnect, if we're assuming a connection because + * of a state change to "connecting", signal stage 2 is still running. + * If "connected" or "roaming", we can go right to the IP_CONFIG state + * and there's nothing left to do in CONFIG. + * If we're assuming the connection because of an agent request we + * switch to NEED_AUTH and actually send the request now that we + * have an activation request. + * + * This all assumes ConnectedNetwork hasn't changed. + */ + if (priv->assumed_ac) { + gboolean result; + + if (!priv->pending_agent_request) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (nm_streq(get_variant_state(value), "connecting")) { + return NM_ACT_STAGE_RETURN_POSTPONE; + } else { + /* This basically forgets that the connection was "assumed" + * as we can treat it like any connection triggered by a + * Network.Connect() call from now on. + */ + assumed_connection_progress_to_ip_config(self, FALSE); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + } + + result = nm_device_iwd_agent_query(self, priv->pending_agent_request); + g_clear_object(&priv->pending_agent_request); + nm_assert(result); + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + /* 802.1x networks that are not IWD Known Networks will definitely + * fail, for other combinations we will let the Connect call fail + * or ask us for any missing secrets through the Agent. + */ + if (nm_connection_get_setting_802_1x(connection) && !is_ap_known_network(ap)) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) access point '%s' has 802.1x security but is not configured " + "in IWD.", + nm_connection_get_id(connection)); + + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + goto out_fail; + } + + priv->secrets_failed = FALSE; + + if (nm_wifi_ap_get_fake(ap)) { + gs_free char *ssid = NULL; + + if (!nm_setting_wireless_get_hidden(s_wireless)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) target network not known to IWD but is not " + "marked hidden"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + + /* Use Station.ConnectHiddenNetwork method instead of Network proxy. */ + g_dbus_proxy_call(priv->dbus_station_proxy, + "ConnectHiddenNetwork", + g_variant_new("(s)", ssid), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->cancellable, + network_connect_cb, + self); + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + network_proxy = nm_iwd_manager_get_dbus_interface( + nm_iwd_manager_get(), + nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap)), + NM_IWD_NETWORK_INTERFACE); + if (!network_proxy) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) could not get Network interface proxy for %s", + nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap))); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + + if (!priv->cancellable) + priv->cancellable = g_cancellable_new(); + + /* Call Network.Connect. No timeout because IWD already handles + * timeouts. + */ + g_dbus_proxy_call(network_proxy, + "Connect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->cancellable, + network_connect_cb, + self); + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (NM_IN_STRSET(mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) { + NMSettingWirelessSecurity *s_wireless_sec; + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + if (s_wireless_sec && !nm_setting_wireless_security_get_psk(s_wireless_sec)) { + /* PSK is missing from the settings, have to request it */ + + wifi_secrets_cancel(self); + + priv->wifi_secrets_id = + nm_act_request_get_secrets(req, + TRUE, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION, + NM_MAKE_STRV(NM_SETTING_WIRELESS_SECURITY_PSK), + act_psk_cb, + self); + nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); + } else + act_set_mode(self); + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) iwd cannot handle mode %s", mode); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + +out_fail: + cleanup_association_attempt(self, FALSE); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + return nm_device_get_configured_mtu_from_connection(device, + NM_TYPE_SETTING_WIRELESS, + out_source); +} + +static gboolean +periodic_scan_timeout_cb(gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + priv->periodic_scan_id = 0; + + if (priv->scanning || priv->scan_requested) + return FALSE; + + g_dbus_proxy_call(priv->dbus_station_proxy, + "Scan", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + scan_cb, + self); + priv->scan_requested = TRUE; + + return FALSE; +} + +static void +schedule_periodic_scan(NMDeviceIwd *self, gboolean initial_scan) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + guint interval; + + /* Automatically start a scan after a disconnect, mode change or device UP, + * otherwise scan periodically every 10 seconds if needed for NM's + * autoconnect. There's no need to scan When using IWD's autoconnect or + * when connected, we update the AP list on UI requests. + * + * (initial_scan && disconnected && !priv->iwd_autoconnect) override + * priv->scanning below because of an IWD quirk where a device will often + * be in the autoconnect state and scanning at the time of our initial_scan, + * but our logic will then send it a Disconnect() causing IWD to exit + * autoconnect and interrupt the ongoing scan, meaning that we still want + * a new scan ASAP. + */ + if (!priv->can_scan || priv->scan_requested || priv->current_ap || priv->iwd_autoconnect) + interval = -1; + else if (initial_scan && priv->scanning) + interval = 0; + else if (priv->scanning) + interval = -1; + else if (!priv->periodic_scan_id) + interval = 10; + else + return; + + nm_clear_g_source(&priv->periodic_scan_id); + + if (interval != (guint) -1) + priv->periodic_scan_id = g_timeout_add_seconds(interval, periodic_scan_timeout_cb, self); +} + +static void +set_can_scan(NMDeviceIwd *self, gboolean can_scan) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (priv->can_scan == can_scan) + return; + + priv->can_scan = can_scan; + + if (!priv->iwd_autoconnect) + schedule_periodic_scan(self, TRUE); +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wireless; + const char * mode; + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + break; + case NM_DEVICE_STATE_UNAVAILABLE: + /* + * If the device is enabled and the IWD manager is ready, + * transition to DISCONNECTED because the device is now + * ready to use. + */ + if (priv->enabled && priv->dbus_station_proxy) { + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (old_state == NM_DEVICE_STATE_UNAVAILABLE) + initial_check_assume(self); + break; + case NM_DEVICE_STATE_IP_CONFIG: + s_wireless = + (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + mode = nm_setting_wireless_get_mode(s_wireless); + if (!priv->periodic_update_id + && NM_IN_STRSET(mode, + NULL, + NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_MODE_ADHOC)) { + priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); + periodic_update(self); + } + break; + default: + break; + } +} + +static gboolean +get_enabled(NMDevice *device) +{ + return NM_DEVICE_IWD_GET_PRIVATE(device)->enabled; +} + +static void +set_enabled(NMDevice *device, gboolean enabled) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDeviceState state; + + enabled = !!enabled; + + if (priv->enabled == enabled) + return; + + priv->enabled = enabled; + + _LOGD(LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled"); + + state = nm_device_get_state(device); + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + _LOGD(LOGD_WIFI, "(%s): device blocked by UNMANAGED state", enabled ? "enable" : "disable"); + return; + } + + if (priv->dbus_obj) + set_powered(self, enabled); + + if (enabled) { + if (state != NM_DEVICE_STATE_UNAVAILABLE) + _LOGW(LOGD_CORE, "not in expected unavailable state!"); + + if (priv->dbus_station_proxy) { + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + } else { + nm_device_state_changed(device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE); + } +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + /* Only handle wireless setting here, delegate other settings to parent class */ + if (nm_streq(setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys( + diffs, + NM_SETTING_WIRELESS_SETTING_NAME, + error, + NM_SETTING_WIRELESS_SEEN_BSSIDS, /* ignored */ + NM_SETTING_WIRELESS_MTU); /* reapplied with IP config */ + } + + device_class = NM_DEVICE_CLASS(nm_device_iwd_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(object); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + const char ** list; + + switch (prop_id) { + case PROP_MODE: + if (!priv->current_ap) + g_value_set_uint(value, NM_802_11_MODE_UNKNOWN); + else if (nm_wifi_ap_is_hotspot(priv->current_ap)) + g_value_set_uint(value, NM_802_11_MODE_AP); + else + g_value_set_uint(value, nm_wifi_ap_get_mode(priv->current_ap)); + + break; + case PROP_BITRATE: + g_value_set_uint(value, priv->rate); + break; + case PROP_CAPABILITIES: + g_value_set_uint(value, priv->capabilities); + break; + case PROP_ACCESS_POINTS: + list = nm_wifi_aps_get_paths(&priv->aps_lst_head, TRUE); + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list)); + break; + case PROP_ACTIVE_ACCESS_POINT: + nm_dbus_utils_g_value_set_object_path(value, priv->current_ap); + break; + case PROP_SCANNING: + g_value_set_boolean(value, priv->scanning); + break; + case PROP_LAST_SCAN: + g_value_set_int64( + value, + priv->last_scan > 0 + ? nm_utils_monotonic_timestamp_as_boottime(priv->last_scan, NM_UTILS_NSEC_PER_MSEC) + : (gint64) -1); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +state_changed(NMDeviceIwd *self, const char *new_state) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState dev_state = nm_device_get_state(device); + gboolean nm_connection = priv->current_ap || priv->assumed_ac; + gboolean iwd_connection = FALSE; + NMWifiAP * ap = NULL; + gboolean can_connect = priv->nm_autoconnect; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state); + + if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) { + gs_unref_variant GVariant *value = NULL; + const char * network_path_str; + nm_auto_ref_string NMRefString *network_path = NULL; + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork"); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork property not cached or not an object path"); + return; + } + + iwd_connection = TRUE; + network_path_str = g_variant_get_string(value, NULL); + network_path = nm_ref_string_new(network_path_str); + ap = find_ap_by_supplicant_path(self, network_path); + + if (!ap) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork points to an unknown Network %s", + network_path_str); + return; + } + } + + /* Don't allow scanning while connecting, disconnecting or roaming */ + set_can_scan(self, NM_IN_STRSET(new_state, "connected", "disconnected")); + + priv->nm_autoconnect = FALSE; + + if (nm_connection && iwd_connection && priv->current_ap && ap != priv->current_ap) { + gboolean switch_ap = priv->iwd_autoconnect && priv->assumed_ac; + + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "IWD is connecting to the wrong AP, %s activation", + switch_ap ? "replacing" : "aborting"); + cleanup_association_attempt(self, !switch_ap); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + + if (switch_ap) + assume_connection(self, ap); + return; + } + + if (priv->iwd_autoconnect && iwd_connection) { + if (dev_state < NM_DEVICE_STATE_DISCONNECTED) + return; + + /* If IWD is in any state other than disconnected and the NMDevice is + * in DISCONNECTED then someone else, possibly IWD's autoconnect, has + * commanded an action and we need to update our NMDevice's state to + * match, including finding the NMSettingsConnection and NMWifiAP + * matching the network pointed to by Station.ConnectedNetwork. + * + * If IWD is in the connected state and we're in CONFIG, we only have + * to signal that the existing connection request has advanced to a new + * state. If the connection request came from NM, we must have used + * Network.Connect() so that method call's callback will update the + * connection request, otherwise we do it here. + * + * If IWD is disconnecting or just disconnected, the common code below + * (independent from priv->iwd_autoconnect) will handle this case. + * If IWD is disconnecting but we never saw a connection request in the + * first place (maybe because we're only startig up) we won't be + * setting up an NMActiveConnection just to put the NMDevice in the + * DEACTIVATING state and we ignore this case. + * + * If IWD was in the disconnected state and transitioned to + * "connecting" but we were already in NEED_AUTH because we handled an + * agent query -- IWD normally stays in "disconnected" until it has all + * the secrets -- we record this fact and remain in NEED_AUTH. + */ + if (!nm_connection) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "This is a new connection, 'assuming' it"); + assume_connection(self, ap); + return; + } + + if (priv->assumed_ac && dev_state >= NM_DEVICE_STATE_PREPARE + && dev_state < NM_DEVICE_STATE_IP_CONFIG + && NM_IN_STRSET(new_state, "connected", "roaming")) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Updating assumed activation state"); + assumed_connection_progress_to_ip_config(self, TRUE); + return; + } + + if (priv->assumed_ac) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Clearing assumed activation timeout"); + nm_clear_g_source(&priv->assumed_ac_timeout); + return; + } + } else if (!priv->iwd_autoconnect && iwd_connection) { + /* If we were connecting, do nothing, the confirmation of + * a connection success is handled in the Device.Connect + * method return callback. Otherwise, IWD must have connected + * without Network Manager's will so for simplicity force a + * disconnect. + */ + if (nm_connection) + return; + + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection, asking IWD to disconnect"); + send_disconnect(self); + } else if (NM_IN_STRSET(new_state, "disconnecting", "disconnected")) { + /* If necessary, call Disconnect on the IWD device object to make sure + * it disables its autoconnect. + */ + if ((!priv->iwd_autoconnect + || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)) + && !priv->wifi_secrets_id && !priv->pending_agent_request) + send_disconnect(self); + + /* + * If IWD is still handling the Connect call, let our Connect + * callback for the dbus method handle the failure. The main + * reason we don't want to handle the failure here is because the + * method callback will have more information on the specific + * failure reason. + * + * If IWD is handling an autoconnect agent call, let the agent's + * Cancel() handler take care of this. + */ + if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH) + && !priv->assumed_ac) + return; + if (NM_IN_SET(dev_state, NM_DEVICE_STATE_NEED_AUTH) && priv->assumed_ac) + return; + + if (nm_connection) { + cleanup_association_attempt(self, FALSE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + } + } else if (!nm_streq(new_state, "unknown")) { + _LOGE(LOGD_WIFI, "State %s unknown", new_state); + return; + } + + /* Don't allow new connection until iwd exits disconnecting and no + * Connect callback is pending. + */ + if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) { + priv->nm_autoconnect = TRUE; + if (!can_connect) + nm_device_emit_recheck_auto_activate(device); + } +} + +static void +scanning_changed(NMDeviceIwd *self, gboolean new_scanning) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (new_scanning == priv->scanning) + return; + + priv->scanning = new_scanning; + + _notify(self, PROP_SCANNING); + + if (!priv->scanning) { + update_aps(self); + + if (!priv->scan_requested && !priv->iwd_autoconnect) + schedule_periodic_scan(self, FALSE); + } +} + +static void +station_properties_changed(GDBusProxy *proxy, + GVariant * changed_properties, + GStrv invalidate_properties, + gpointer user_data) +{ + NMDeviceIwd *self = user_data; + const char * new_str; + gboolean new_bool; + + if (g_variant_lookup(changed_properties, "State", "&s", &new_str)) + state_changed(self, new_str); + + if (g_variant_lookup(changed_properties, "Scanning", "b", &new_bool)) + scanning_changed(self, new_bool); +} + +static void +ap_adhoc_properties_changed(GDBusProxy *proxy, + GVariant * changed_properties, + GStrv invalidate_properties, + gpointer user_data) +{ + NMDeviceIwd *self = user_data; + gboolean new_bool; + + if (g_variant_lookup(changed_properties, "Started", "b", &new_bool)) + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "IWD AP/AdHoc state is now %s", + new_bool ? "Started" : "Stopped"); +} + +static void +powered_changed(NMDeviceIwd *self, gboolean new_powered) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + GDBusInterface * interface; + + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + + interface = + new_powered ? g_dbus_object_get_interface(priv->dbus_obj, NM_IWD_AP_INTERFACE) : NULL; + + if (priv->dbus_ap_proxy) { + g_signal_handlers_disconnect_by_func(priv->dbus_ap_proxy, + ap_adhoc_properties_changed, + self); + g_clear_object(&priv->dbus_ap_proxy); + } + + if (interface) { + priv->dbus_ap_proxy = G_DBUS_PROXY(interface); + g_signal_connect(priv->dbus_ap_proxy, + "g-properties-changed", + G_CALLBACK(ap_adhoc_properties_changed), + self); + + if (priv->act_mode_switch) + act_check_interface(self); + else + reset_mode(self, NULL, NULL, NULL); + } + + interface = + new_powered ? g_dbus_object_get_interface(priv->dbus_obj, NM_IWD_ADHOC_INTERFACE) : NULL; + + if (priv->dbus_adhoc_proxy) { + g_signal_handlers_disconnect_by_func(priv->dbus_adhoc_proxy, + ap_adhoc_properties_changed, + self); + g_clear_object(&priv->dbus_adhoc_proxy); + } + + if (interface) { + priv->dbus_adhoc_proxy = G_DBUS_PROXY(interface); + g_signal_connect(priv->dbus_adhoc_proxy, + "g-properties-changed", + G_CALLBACK(ap_adhoc_properties_changed), + self); + + if (priv->act_mode_switch) + act_check_interface(self); + else + reset_mode(self, NULL, NULL, NULL); + } + + /* We expect one of the three interfaces to always be present when + * device is Powered so if AP and AdHoc are not present we should + * be in station mode. + */ + if (new_powered && !priv->dbus_ap_proxy && !priv->dbus_adhoc_proxy) { + interface = g_dbus_object_get_interface(priv->dbus_obj, NM_IWD_STATION_INTERFACE); + if (!interface) { + _LOGE(LOGD_WIFI, + "Interface %s not found on obj %s", + NM_IWD_STATION_INTERFACE, + g_dbus_object_get_object_path(priv->dbus_obj)); + interface = NULL; + } + } else + interface = NULL; + + if (priv->dbus_station_proxy) { + g_signal_handlers_disconnect_by_func(priv->dbus_station_proxy, + station_properties_changed, + self); + g_clear_object(&priv->dbus_station_proxy); + } + + if (interface) { + GVariant *value; + + priv->dbus_station_proxy = G_DBUS_PROXY(interface); + g_signal_connect(priv->dbus_station_proxy, + "g-properties-changed", + G_CALLBACK(station_properties_changed), + self); + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "Scanning"); + priv->scanning = get_variant_boolean(value, "Scanning"); + g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + state_changed(self, get_variant_state(value)); + g_variant_unref(value); + + update_aps(self); + + /* When a device is brought UP in station mode, including after a mode + * switch, IWD re-enables autoconnect. This is unlike NM's autoconnect + * where a mode change doesn't interfere with the + * BLOCKED_MANUAL_DISCONNECT flag. + */ + if (priv->iwd_autoconnect) { + nm_device_autoconnect_blocked_unset(NM_DEVICE(self), + NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL); + } + } else { + set_can_scan(self, FALSE); + priv->scanning = FALSE; + priv->scan_requested = FALSE; + priv->nm_autoconnect = FALSE; + cleanup_association_attempt(self, FALSE); + remove_all_aps(self); + } +} + +static void +device_properties_changed(GDBusProxy *proxy, + GVariant * changed_properties, + GStrv invalidate_properties, + gpointer user_data) +{ + NMDeviceIwd *self = user_data; + gboolean new_bool; + + if (g_variant_lookup(changed_properties, "Powered", "b", &new_bool)) + powered_changed(self, new_bool); +} + +static void +config_changed(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMDeviceIwd * self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gboolean old_iwd_ac = priv->iwd_autoconnect; + + priv->iwd_autoconnect = + nm_config_data_get_device_config_boolean(config_data, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, + NM_DEVICE(self), + TRUE, + TRUE); + + if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) { + gs_unref_variant GVariant *value = NULL; + + if (!priv->iwd_autoconnect + && !nm_device_autoconnect_blocked_get(NM_DEVICE(self), + NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)) + send_disconnect(self); + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + state_changed(self, get_variant_state(value)); + } +} + +void +nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + GDBusInterface * interface; + gs_unref_variant GVariant *value = NULL; + gs_unref_object GDBusProxy *adapter_proxy = NULL; + GVariantIter * iter; + const char * mode; + gboolean powered; + NMDeviceWifiCapabilities capabilities; + + if (!nm_g_object_ref_set(&priv->dbus_obj, object)) + return; + + if (priv->dbus_device_proxy) { + g_signal_handlers_disconnect_by_func(priv->dbus_device_proxy, + device_properties_changed, + self); + g_clear_object(&priv->dbus_device_proxy); + + powered_changed(self, FALSE); + + priv->act_mode_switch = FALSE; + + g_signal_handlers_disconnect_by_func(nm_config_get(), config_changed, self); + } + + if (!object) + return; + + interface = g_dbus_object_get_interface(object, NM_IWD_DEVICE_INTERFACE); + if (!interface) { + _LOGE(LOGD_WIFI, + "Interface %s not found on obj %s", + NM_IWD_DEVICE_INTERFACE, + g_dbus_object_get_object_path(object)); + g_clear_object(&priv->dbus_obj); + return; + } + + priv->dbus_device_proxy = G_DBUS_PROXY(interface); + + g_signal_connect(priv->dbus_device_proxy, + "g-properties-changed", + G_CALLBACK(device_properties_changed), + self); + + /* Parse list of interface modes supported by adapter (wiphy) */ + + value = g_dbus_proxy_get_cached_property(priv->dbus_device_proxy, "Adapter"); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "Adapter property not cached or not an object path"); + goto error; + } + + adapter_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + g_variant_get_string(value, NULL), + NM_IWD_WIPHY_INTERFACE); + if (!adapter_proxy) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "Can't get DBus proxy for IWD Adapter for IWD Device"); + goto error; + } + + g_variant_unref(value); + value = g_dbus_proxy_get_cached_property(adapter_proxy, "SupportedModes"); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, + "SupportedModes property not cached or not a string array"); + goto error; + } + + capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN; + + g_variant_get(value, "as", &iter); + while (g_variant_iter_next(iter, "&s", &mode)) { + if (nm_streq(mode, "ap")) + capabilities |= NM_WIFI_DEVICE_CAP_AP; + else if (nm_streq(mode, "ad-hoc")) + capabilities |= NM_WIFI_DEVICE_CAP_ADHOC; + } + g_variant_iter_free(iter); + + if (priv->capabilities != capabilities) { + priv->capabilities = capabilities; + _notify(self, PROP_CAPABILITIES); + } + + /* Update iwd_autoconnect before any state_changed call */ + g_signal_connect(nm_config_get(), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed), + self); + config_changed(NULL, NM_CONFIG_GET_DATA, 0, NULL, self); + + g_variant_unref(value); + value = g_dbus_proxy_get_cached_property(priv->dbus_device_proxy, "Powered"); + powered = get_variant_boolean(value, "Powered"); + + if (powered != priv->enabled) + set_powered(self, priv->enabled); + else if (powered) + powered_changed(self, TRUE); + + return; + +error: + g_signal_handlers_disconnect_by_func(priv->dbus_device_proxy, device_properties_changed, self); + g_clear_object(&priv->dbus_device_proxy); +} + +gboolean +nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) +{ + NMDevice * device = NM_DEVICE(self); + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDeviceState state = nm_device_get_state(device); + const char * setting_name; + const char * setting_key; + gboolean replied; + NMWifiAP * ap; + NMSecretAgentGetSecretsFlags get_secret_flags = + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + nm_auto_ref_string NMRefString *network_path = NULL; + + if (!invocation) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + gboolean disconnect; + + if (!priv->wifi_secrets_id && !priv->pending_agent_request) + return FALSE; + + _LOGI(LOGD_WIFI, "IWD agent request is being cancelled"); + wifi_secrets_cancel(self); + + if (state == NM_DEVICE_STATE_NEED_AUTH) + nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + /* The secrets request is being cancelled. If we don't have an assumed + * connection than we've probably called Network.Connect and that method + * call's callback is going to handle the failure. And if the state was + * not "disconnected" then let the state change handler process the + * failure. + */ + if (!priv->assumed_ac) + return TRUE; + + if (!nm_streq(get_variant_state(value), "disconnected")) + return TRUE; + + disconnect = nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL); + cleanup_association_attempt(self, disconnect); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return TRUE; + } + + if (state > NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) { + _LOGW(LOGD_WIFI, "Can't handle the IWD agent request in current device state"); + return FALSE; + } + + if (priv->wifi_secrets_id || priv->pending_agent_request) { + _LOGW(LOGD_WIFI, "There's already a pending agent request for this device"); + return FALSE; + } + + network_path = nm_ref_string_new(get_agent_request_network_path(invocation)); + ap = find_ap_by_supplicant_path(self, network_path); + if (!ap) { + _LOGW(LOGD_WIFI, "IWD Network object not found for the agent request"); + return FALSE; + } + + if (priv->assumed_ac) { + const char *ac_ap_path = nm_active_connection_get_specific_object(priv->assumed_ac); + + if (!nm_streq(ac_ap_path, nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)))) { + _LOGW(LOGD_WIFI, + "Dropping an existing assumed connection to create a new one based on the IWD " + "agent request network parameter"); + + if (priv->current_ap) + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + + cleanup_association_attempt(self, FALSE); + priv->pending_agent_request = g_object_ref(invocation); + assume_connection(self, ap); + return TRUE; + } + + if (state != NM_DEVICE_STATE_CONFIG) { + _LOGI(LOGD_WIFI, "IWD agent request deferred until in CONFIG"); + priv->pending_agent_request = g_object_ref(invocation); + return TRUE; + } + + /* Otherwise handle as usual */ + } else if (!priv->current_ap) { + _LOGI(LOGD_WIFI, "IWD is asking for secrets without explicit connect request"); + + if (priv->iwd_autoconnect) { + priv->pending_agent_request = g_object_ref(invocation); + assume_connection(self, ap); + return TRUE; + } + + send_disconnect(self); + return FALSE; + } else if (priv->current_ap) { + if (priv->current_ap != ap) { + _LOGW(LOGD_WIFI, "IWD agent request for a wrong network object"); + cleanup_association_attempt(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return FALSE; + } + + /* Otherwise handle as usual */ + } + + if (!try_reply_agent_request(self, + nm_device_get_applied_connection(device), + invocation, + &setting_name, + &setting_key, + &replied)) { + priv->secrets_failed = TRUE; + return FALSE; + } + + if (replied) + return TRUE; + + /* Normally require new secrets every time IWD asks for them. + * IWD only queries us if it has not saved the secrets (e.g. by policy) + * or a previous attempt has failed with current secrets so it wants + * a fresh set. However if this is a new connection it may include + * all of the needed settings already so allow using these, too. + * Connection timestamp is set after activation or after first + * activation failure (to 0). + */ + if (nm_settings_connection_get_timestamp(nm_device_get_settings_connection(device), NULL)) + get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + + nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NO_SECRETS); + wifi_secrets_get_one(self, setting_name, get_secret_flags, setting_key, invocation); + + return TRUE; +} + +void +nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool add) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMWifiAP * ap = NULL; + bool recheck; + nm_auto_ref_string NMRefString *bss_path = NULL; + + bss_path = nm_ref_string_new(g_dbus_proxy_get_object_path(network)); + ap = find_ap_by_supplicant_path(self, bss_path); + + /* We could schedule an update_aps(self) idle call here but up to IWD 1.9 + * when a hidden network connection is attempted, that network is initially + * only added as a Network object but not shown in GetOrderedNetworks() + * return values, and for some corner case scenarios it's beneficial to + * have that Network reflected in our ap list so that we don't attempt + * calling ConnectHiddenNetwork() on it, as that will fail in 1.9. But we + * can skip recheck-available if we're currently scanning or in the middle + * of a GetOrderedNetworks() call as that will trigger the recheck too. + */ + recheck = priv->enabled && !priv->scanning && !priv->networks_requested; + + if (!add) { + if (ap) { + ap_add_remove(self, FALSE, ap, recheck); + priv->networks_changed |= !recheck; + } + + return; + } + + if (!ap) { + ap = ap_from_network(self, + network, + bss_path, + nm_utils_get_monotonic_timestamp_msec(), + -10000); + if (!ap) + return; + + ap_add_remove(self, TRUE, ap, recheck); + g_object_unref(ap); + priv->networks_changed |= !recheck; + return; + } +} + +static void +autoconnect_changed(NMDevice *device, GParamSpec *pspec, NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gs_unref_variant GVariant *value = NULL; + + /* Note IWD normally remains in "disconnected" during a secret request + * and we don't want to interrupt it by calling Station.Disconnect(). + */ + if (!priv->dbus_station_proxy || !priv->iwd_autoconnect + || !nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL) + || priv->wifi_secrets_id || priv->pending_agent_request) + return; + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + if (!nm_streq(get_variant_state(value), "disconnected")) + return; + + send_disconnect(self); +} + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + nm_assert(NM_IS_DEVICE_IWD(device)); + + return "wifi"; +} + +/*****************************************************************************/ + +static void +nm_device_iwd_init(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + c_list_init(&priv->aps_lst_head); + + g_signal_connect(self, "notify::" NM_DEVICE_AUTOCONNECT, G_CALLBACK(autoconnect_changed), self); + + /* Make sure the manager is running */ + (void) nm_iwd_manager_get(); +} + +NMDevice * +nm_device_iwd_new(const char *iface) +{ + return g_object_new(NM_TYPE_DEVICE_IWD, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "802.11 Wi-Fi", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WIFI, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WIFI, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WLAN, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceIwd * self = NM_DEVICE_IWD(object); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->cancellable); + + g_signal_handlers_disconnect_by_func(self, autoconnect_changed, self); + nm_device_iwd_set_dbus_object(self, NULL); + + G_OBJECT_CLASS(nm_device_iwd_parent_class)->dispose(object); + + nm_assert(c_list_is_empty(&priv->aps_lst_head)); +} + +static void +nm_device_iwd_class_init(NMDeviceIwdClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->dispose = dispose; + + dbus_object_class->interface_infos = + NM_DBUS_INTERFACE_INFOS(&nm_interface_info_device_wireless); + + device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI); + + device_class->can_auto_connect = can_auto_connect; + device_class->is_available = is_available; + device_class->get_autoconnect_allowed = get_autoconnect_allowed; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->get_enabled = get_enabled; + device_class->set_enabled = set_enabled; + device_class->get_type_description = get_type_description; + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->get_configured_mtu = get_configured_mtu; + device_class->deactivate = deactivate; + device_class->deactivate_async = deactivate_async; + device_class->can_reapply_change = can_reapply_change; + + /* Stage 1 needed only for the set_current_ap() call. Stage 2 is + * needed if we're assuming a connection still in the "connecting" + * state or on an agent request. + */ + device_class->act_stage1_prepare_also_for_external_or_assume = TRUE; + device_class->act_stage2_config_also_for_external_or_assume = TRUE; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_IWD_MODE, + "", + "", + NM_802_11_MODE_UNKNOWN, + NM_802_11_MODE_AP, + NM_802_11_MODE_INFRA, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BITRATE] = g_param_spec_uint(NM_DEVICE_IWD_BITRATE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACCESS_POINTS] = + g_param_spec_boxed(NM_DEVICE_IWD_ACCESS_POINTS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVE_ACCESS_POINT] = + g_param_spec_string(NM_DEVICE_IWD_ACTIVE_ACCESS_POINT, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_IWD_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_WIFI_DEVICE_CAP_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_DEVICE_IWD_SCANNING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LAST_SCAN] = g_param_spec_int64(NM_DEVICE_IWD_LAST_SCAN, + "", + "", + -1, + G_MAXINT64, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wifi/nm-device-iwd.h b/src/core/devices/wifi/nm-device-iwd.h new file mode 100644 index 0000000..ce94c9e --- /dev/null +++ b/src/core/devices/wifi/nm-device-iwd.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Intel Corporation + */ + +#ifndef __NETWORKMANAGER_DEVICE_IWD_H__ +#define __NETWORKMANAGER_DEVICE_IWD_H__ + +#include "devices/nm-device.h" +#include "nm-wifi-ap.h" +#include "nm-device-wifi.h" + +#define NM_TYPE_DEVICE_IWD (nm_device_iwd_get_type()) +#define NM_DEVICE_IWD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwd)) +#define NM_DEVICE_IWD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IWD, NMDeviceIwdClass)) +#define NM_IS_DEVICE_IWD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IWD)) +#define NM_IS_DEVICE_IWD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_IWD)) +#define NM_DEVICE_IWD_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwdClass)) + +#define NM_DEVICE_IWD_MODE NM_DEVICE_WIFI_MODE +#define NM_DEVICE_IWD_BITRATE NM_DEVICE_WIFI_BITRATE +#define NM_DEVICE_IWD_ACCESS_POINTS NM_DEVICE_WIFI_ACCESS_POINTS +#define NM_DEVICE_IWD_ACTIVE_ACCESS_POINT NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT +#define NM_DEVICE_IWD_CAPABILITIES NM_DEVICE_WIFI_CAPABILITIES +#define NM_DEVICE_IWD_SCANNING NM_DEVICE_WIFI_SCANNING +#define NM_DEVICE_IWD_LAST_SCAN NM_DEVICE_WIFI_LAST_SCAN + +typedef struct _NMDeviceIwd NMDeviceIwd; +typedef struct _NMDeviceIwdClass NMDeviceIwdClass; + +GType nm_device_iwd_get_type(void); + +NMDevice *nm_device_iwd_new(const char *iface); + +void nm_device_iwd_set_dbus_object(NMDeviceIwd *device, GDBusObject *object); + +gboolean nm_device_iwd_agent_query(NMDeviceIwd *device, GDBusMethodInvocation *invocation); + +const CList *_nm_device_iwd_get_aps(NMDeviceIwd *self); + +void _nm_device_iwd_request_scan(NMDeviceIwd * self, + GVariant * options, + GDBusMethodInvocation *invocation); + +void nm_device_iwd_network_add_remove(NMDeviceIwd *device, GDBusProxy *network, bool add); + +#endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */ diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c new file mode 100644 index 0000000..1554411 --- /dev/null +++ b/src/core/devices/wifi/nm-device-olpc-mesh.c @@ -0,0 +1,551 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Dan Williams + * Sjoerd Simons + * Daniel Drake + * Copyright (C) 2005 - 2014 Red Hat, Inc. + * Copyright (C) 2008 Collabora Ltd. + * Copyright (C) 2009 One Laptop per Child + */ + +#include "nm-default.h" + +#include "nm-device-olpc-mesh.h" + +#include +#include +#include +#include +#include +#include + +#include "devices/nm-device.h" +#include "nm-device-wifi.h" +#include "devices/nm-device-private.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-act-request.h" +#include "nm-setting-connection.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-manager.h" +#include "platform/nm-platform.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceOlpcMesh +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceOlpcMesh, PROP_COMPANION, PROP_ACTIVE_CHANNEL, ); + +typedef struct { + NMDevice * companion; + NMManager *manager; + bool stage1_waiting : 1; +} NMDeviceOlpcMeshPrivate; + +struct _NMDeviceOlpcMesh { + NMDevice parent; + NMDeviceOlpcMeshPrivate _priv; +}; + +struct _NMDeviceOlpcMeshClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE) + +#define NM_DEVICE_OLPC_MESH_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceOlpcMesh, NM_IS_DEVICE_OLPC_MESH, NMDevice) + +/*****************************************************************************/ + +static gboolean +get_autoconnect_allowed(NMDevice *device) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + /* We can't even connect if we don't have a companion yet. */ + if (!priv->companion) + return FALSE; + + /* We must not attempt to autoconnect when the companion is connected or + * connecting, * because we'd tear down its connection. */ + if (nm_device_get_state(priv->companion) > NM_DEVICE_STATE_DISCONNECTED) + return FALSE; + + return TRUE; +} + +#define DEFAULT_SSID "olpc-mesh" + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMSettingOlpcMesh *s_mesh; + + s_mesh = nm_connection_get_setting_olpc_mesh(connection); + if (!s_mesh) { + s_mesh = (NMSettingOlpcMesh *) nm_setting_olpc_mesh_new(); + nm_connection_add_setting(connection, NM_SETTING(s_mesh)); + } + + if (!nm_setting_olpc_mesh_get_ssid(s_mesh)) { + gs_unref_bytes GBytes *ssid = NULL; + + ssid = g_bytes_new_static(DEFAULT_SSID, NM_STRLEN(DEFAULT_SSID)); + g_object_set(G_OBJECT(s_mesh), NM_SETTING_OLPC_MESH_SSID, ssid, NULL); + } + + if (!nm_setting_olpc_mesh_get_dhcp_anycast_address(s_mesh)) { + const char *anycast = "c0:27:c0:27:c0:27"; + + g_object_set(G_OBJECT(s_mesh), NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS, anycast, NULL); + } + + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_OLPC_MESH_SETTING_NAME, + existing_connections, + NULL, + _("Mesh"), + NULL, + NULL, + FALSE); /* No IPv6 by default */ + + return TRUE; +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + /* disconnect companion device, if it is connected */ + if (nm_device_get_act_request(NM_DEVICE(priv->companion))) { + _LOGI(LOGD_OLPC, "disconnecting companion device %s", nm_device_get_iface(priv->companion)); + /* FIXME: VPN stuff here is a bug; but we can't really change API now... */ + nm_device_state_changed(NM_DEVICE(priv->companion), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + _LOGI(LOGD_OLPC, "companion %s disconnected", nm_device_get_iface(priv->companion)); + } + + /* wait with continuing configuration until the companion device is done scanning */ + if (nm_device_wifi_get_scanning(NM_DEVICE_WIFI(priv->companion))) { + priv->stage1_waiting = TRUE; + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + priv->stage1_waiting = FALSE; + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static gboolean +_mesh_set_channel(NMDeviceOlpcMesh *self, guint32 channel) +{ + NMPlatform *platform; + int ifindex = nm_device_get_ifindex(NM_DEVICE(self)); + guint32 old_channel; + + platform = nm_device_get_platform(NM_DEVICE(self)); + old_channel = nm_platform_mesh_get_channel(platform, ifindex); + + if (channel == 0) + channel = old_channel; + + /* We want to call this even if the channel number is the same, + * because that actually starts the mesh with the configured mesh ID. */ + if (!nm_platform_mesh_set_channel(platform, ifindex, channel)) + return FALSE; + + if (old_channel != channel) + _notify(self, PROP_ACTIVE_CHANNEL); + + return TRUE; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device); + NMSettingOlpcMesh *s_mesh; + GBytes * ssid; + const char * anycast_addr; + gboolean success; + + s_mesh = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OLPC_MESH); + g_return_val_if_fail(s_mesh, NM_ACT_STAGE_RETURN_FAILURE); + + ssid = nm_setting_olpc_mesh_get_ssid(s_mesh); + + nm_device_take_down(NM_DEVICE(self), TRUE); + success = nm_platform_mesh_set_ssid(nm_device_get_platform(device), + nm_device_get_ifindex(device), + g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid)); + nm_device_bring_up(NM_DEVICE(self), TRUE, NULL); + if (!success) { + _LOGW(LOGD_WIFI, "Unable to set the mesh ID"); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + anycast_addr = nm_setting_olpc_mesh_get_dhcp_anycast_address(s_mesh); + nm_device_set_dhcp_anycast_address(device, anycast_addr); + + if (!_mesh_set_channel(self, nm_setting_olpc_mesh_get_channel(s_mesh))) { + _LOGW(LOGD_WIFI, "Unable to set the mesh channel"); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH(device); + + if (!NM_DEVICE_OLPC_MESH_GET_PRIVATE(self)->companion) { + _LOGD(LOGD_WIFI, "not available because companion not found"); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +companion_cleanup(NMDeviceOlpcMesh *self) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + if (priv->companion) { + nm_device_wifi_scanning_prohibited_track(NM_DEVICE_WIFI(priv->companion), self, FALSE); + g_signal_handlers_disconnect_by_data(priv->companion, self); + g_clear_object(&priv->companion); + } + _notify(self, PROP_COMPANION); +} + +static void +companion_notify_cb(NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(user_data); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + nm_assert(NM_IS_DEVICE_WIFI(companion)); + nm_assert(priv->companion == (gpointer) companion); + + if (!priv->stage1_waiting) + return; + + if (!nm_device_wifi_get_scanning(NM_DEVICE_WIFI(companion))) { + priv->stage1_waiting = FALSE; + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); + } +} + +/* disconnect from mesh if someone starts using the companion */ +static void +companion_state_changed_cb(NMDeviceWifi * companion, + NMDeviceState state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH(user_data); + NMDeviceState self_state = nm_device_get_state(NM_DEVICE(self)); + + if (old_state > NM_DEVICE_STATE_DISCONNECTED && state <= NM_DEVICE_STATE_DISCONNECTED) { + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + } + + if (self_state < NM_DEVICE_STATE_PREPARE || self_state > NM_DEVICE_STATE_ACTIVATED + || state < NM_DEVICE_STATE_PREPARE || state > NM_DEVICE_STATE_ACTIVATED) + return; + + _LOGD(LOGD_OLPC, "disconnecting mesh due to companion connectivity"); + /* FIXME: VPN stuff here is a bug; but we can't really change API now... */ + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); +} + +static gboolean +companion_autoconnect_allowed_cb(NMDeviceWifi *companion, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH(user_data); + NMDeviceState state = nm_device_get_state(NM_DEVICE(self)); + + /* Don't allow the companion to autoconnect while a mesh connection is + * active */ + return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_ACTIVATED); +} + +static gboolean +check_companion(NMDeviceOlpcMesh *self, NMDevice *other) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + const char * my_addr, *their_addr; + + if (!NM_IS_DEVICE_WIFI(other)) + return FALSE; + + my_addr = nm_device_get_hw_address(NM_DEVICE(self)); + their_addr = nm_device_get_hw_address(other); + if (!nm_utils_hwaddr_matches(my_addr, -1, their_addr, -1)) + return FALSE; + + nm_assert(priv->companion == NULL); + priv->companion = g_object_ref(other); + + _LOGI(LOGD_OLPC, "found companion Wi-Fi device %s", nm_device_get_iface(other)); + + g_signal_connect(G_OBJECT(other), + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(companion_state_changed_cb), + self); + + g_signal_connect(G_OBJECT(other), + "notify::" NM_DEVICE_WIFI_SCANNING, + G_CALLBACK(companion_notify_cb), + self); + + g_signal_connect(G_OBJECT(other), + NM_DEVICE_AUTOCONNECT_ALLOWED, + G_CALLBACK(companion_autoconnect_allowed_cb), + self); + + _notify(self, PROP_COMPANION); + + return TRUE; +} + +static void +device_added_cb(NMManager *manager, NMDevice *other, gpointer user_data) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(user_data); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + if (!priv->companion && check_companion(self, other)) { + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_remove_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_COMPANION, + FALSE); + } +} + +static void +device_removed_cb(NMManager *manager, NMDevice *other, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH(user_data); + + if (other == NM_DEVICE_OLPC_MESH_GET_PRIVATE(self)->companion) + companion_cleanup(self); +} + +static void +find_companion(NMDeviceOlpcMesh *self) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + const CList * tmp_lst; + NMDevice * candidate; + + if (priv->companion) + return; + + nm_device_add_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, TRUE); + + /* Try to find the companion if it's already known to the NMManager */ + nm_manager_for_each_device (priv->manager, candidate, tmp_lst) { + if (check_companion(self, candidate)) { + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_NONE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_remove_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_COMPANION, + TRUE); + break; + } + } +} + +static void +state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + if (new_state == NM_DEVICE_STATE_UNAVAILABLE) + find_companion(self); + + if (priv->companion) { + gboolean temporarily_prohibited = FALSE; + + if (new_state >= NM_DEVICE_STATE_PREPARE && new_state <= NM_DEVICE_STATE_IP_CONFIG) { + /* Don't allow the companion to scan while configuring the mesh interface */ + temporarily_prohibited = TRUE; + } + nm_device_wifi_scanning_prohibited_track(NM_DEVICE_WIFI(priv->companion), + self, + temporarily_prohibited); + } +} + +static guint32 +get_dhcp_timeout_for_device(NMDevice *device, int addr_family) +{ + /* shorter timeout for mesh connectivity */ + return 20; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(object); + NMDevice * device = NM_DEVICE(self); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_COMPANION: + nm_dbus_utils_g_value_set_object_path(value, priv->companion); + break; + case PROP_ACTIVE_CHANNEL: + g_value_set_uint(value, + nm_platform_mesh_get_channel(nm_device_get_platform(device), + nm_device_get_ifindex(device))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_olpc_mesh_init(NMDeviceOlpcMesh *self) +{} + +static void +constructed(GObject *object) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(object); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_device_olpc_mesh_parent_class)->constructed(object); + + priv->manager = g_object_ref(NM_MANAGER_GET); + + g_signal_connect(priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK(device_added_cb), self); + g_signal_connect(priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK(device_removed_cb), self); +} + +NMDevice * +nm_device_olpc_mesh_new(const char *iface) +{ + return g_object_new(NM_TYPE_DEVICE_OLPC_MESH, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "802.11 OLPC Mesh", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_OLPC_MESH, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_OLPC_MESH, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(object); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self); + + companion_cleanup(self); + + if (priv->manager) { + g_signal_handlers_disconnect_by_func(priv->manager, G_CALLBACK(device_added_cb), self); + g_signal_handlers_disconnect_by_func(priv->manager, G_CALLBACK(device_removed_cb), self); + g_clear_object(&priv->manager); + } + + G_OBJECT_CLASS(nm_device_olpc_mesh_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_OLPC_MESH, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Companion", + "o", + NM_DEVICE_OLPC_MESH_COMPANION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "ActiveChannel", + "u", + NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_olpc_mesh_class_init(NMDeviceOlpcMeshClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->dispose = dispose; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_olpc_mesh); + + device_class->connection_type_supported = NM_SETTING_OLPC_MESH_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_OLPC_MESH_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OLPC_MESH); + + device_class->get_autoconnect_allowed = get_autoconnect_allowed; + device_class->complete_connection = complete_connection; + device_class->is_available = is_available; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->state_changed = state_changed; + device_class->get_dhcp_timeout_for_device = get_dhcp_timeout_for_device; + + obj_properties[PROP_COMPANION] = g_param_spec_string(NM_DEVICE_OLPC_MESH_COMPANION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVE_CHANNEL] = + g_param_spec_uint(NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.h b/src/core/devices/wifi/nm-device-olpc-mesh.h new file mode 100644 index 0000000..79b7fd5 --- /dev/null +++ b/src/core/devices/wifi/nm-device-olpc-mesh.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Dan Williams + * Sjoerd Simons + * Daniel Drake + * Copyright (C) 2005 Red Hat, Inc. + * Copyright (C) 2008 Collabora Ltd. + * Copyright (C) 2009 One Laptop per Child + */ + +#ifndef __NETWORKMANAGER_DEVICE_OLPC_MESH_H__ +#define __NETWORKMANAGER_DEVICE_OLPC_MESH_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_DEVICE_OLPC_MESH (nm_device_olpc_mesh_get_type()) +#define NM_DEVICE_OLPC_MESH(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMesh)) +#define NM_DEVICE_OLPC_MESH_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass)) +#define NM_IS_DEVICE_OLPC_MESH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OLPC_MESH)) +#define NM_IS_DEVICE_OLPC_MESH_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OLPC_MESH)) +#define NM_DEVICE_OLPC_MESH_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass)) + +#define NM_DEVICE_OLPC_MESH_COMPANION "companion" +#define NM_DEVICE_OLPC_MESH_BITRATE "bitrate" +#define NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL "active-channel" + +typedef struct _NMDeviceOlpcMesh NMDeviceOlpcMesh; +typedef struct _NMDeviceOlpcMeshClass NMDeviceOlpcMeshClass; + +GType nm_device_olpc_mesh_get_type(void); + +NMDevice *nm_device_olpc_mesh_new(const char *iface); + +#endif /* __NETWORKMANAGER_DEVICE_OLPC_MESH_H__ */ diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c new file mode 100644 index 0000000..b2f071f --- /dev/null +++ b/src/core/devices/wifi/nm-device-wifi-p2p.c @@ -0,0 +1,1280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-wifi-p2p.h" + +#include + +#include "supplicant/nm-supplicant-manager.h" +#include "supplicant/nm-supplicant-interface.h" + +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "nm-act-request.h" +#include "nm-core-internal.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-ip4-config.h" +#include "nm-manager.h" +#include "nm-manager.h" +#include "nm-setting-wifi-p2p.h" +#include "nm-utils.h" +#include "nm-wifi-p2p-peer.h" +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "settings/nm-settings.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceWifiP2P +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceWifiP2P, PROP_PEERS, ); + +typedef struct { + NMSupplicantManager *sup_mgr; + + /* NOTE: In theory management and group ifaces could be identical. However, + * in practice, this cannot happen currently as NMDeviceWifiP2P is only + * created for existing non-P2P interfaces. + * (i.e. a single standalone P2P interface is not supported at this point) + */ + NMSupplicantInterface *mgmt_iface; + NMSupplicantInterface *group_iface; + + CList peers_lst_head; + + guint find_peer_timeout_id; + guint sup_timeout_id; + guint peer_dump_id; + guint peer_missing_id; + + bool is_waiting_for_supplicant : 1; +} NMDeviceWifiP2PPrivate; + +struct _NMDeviceWifiP2P { + NMDevice parent; + NMDeviceWifiP2PPrivate _priv; +}; + +struct _NMDeviceWifiP2PClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceWifiP2P, nm_device_wifi_p2p, NM_TYPE_DEVICE) + +#define NM_DEVICE_WIFI_P2P_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceWifiP2P, NM_IS_DEVICE_WIFI_P2P, NMDevice) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p; +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added; +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed; + +static void supplicant_group_interface_release(NMDeviceWifiP2P *self); +static void supplicant_interfaces_release(NMDeviceWifiP2P *self, gboolean set_is_waiting); + +/*****************************************************************************/ + +static void +_peer_dump(NMDeviceWifiP2P * self, + NMLogLevel log_level, + const NMWifiP2PPeer *peer, + const char * prefix, + gint32 now_s) +{ + char buf[1024]; + + _NMLOG(log_level, + LOGD_WIFI_SCAN, + "wifi-peer: %-7s %s", + prefix, + nm_wifi_p2p_peer_to_string(peer, buf, sizeof(buf), now_s)); +} + +static gboolean +peer_list_dump(gpointer user_data) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(user_data); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + priv->peer_dump_id = 0; + + if (_LOGD_ENABLED(LOGD_WIFI_SCAN)) { + NMWifiP2PPeer *peer; + gint32 now_s = nm_utils_get_monotonic_timestamp_sec(); + + _LOGD(LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s); + c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst) + _peer_dump(self, LOGL_DEBUG, peer, "dump", now_s); + } + return G_SOURCE_REMOVE; +} + +static void +schedule_peer_list_dump(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (!priv->peer_dump_id && _LOGD_ENABLED(LOGD_WIFI_SCAN)) + priv->peer_dump_id = g_timeout_add_seconds(1, peer_list_dump, self); +} + +/*****************************************************************************/ + +static void +_set_is_waiting_for_supplicant(NMDeviceWifiP2P *self, gboolean is_waiting) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (priv->is_waiting_for_supplicant == (!!is_waiting)) + return; + + priv->is_waiting_for_supplicant = is_waiting; + + if (is_waiting) + nm_device_add_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, + TRUE); + else + nm_device_remove_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, + TRUE); +} + +/*****************************************************************************/ + +static gboolean +check_connection_peer_joined(NMDeviceWifiP2P *device) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(device); + NMConnection * conn = nm_device_get_applied_connection(NM_DEVICE(device)); + NMWifiP2PPeer * peer; + const char * group; + const char *const * groups; + + if (!conn || !priv->group_iface) + return FALSE; + + /* Comparing the object path found on the group_iface with the peers + * found on the mgmt_iface is legal. */ + group = nm_supplicant_interface_get_p2p_group_path(priv->group_iface); + if (!group) + return FALSE; + + /* NOTE: We currently only support connections to a specific peer */ + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, conn); + if (!peer) + return FALSE; + + groups = nm_wifi_p2p_peer_get_groups(peer); + if (!groups || !g_strv_contains(groups, group)) + return FALSE; + + return TRUE; +} + +static gboolean +disconnect_on_connection_peer_missing_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + _LOGW(LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection."); + + priv->peer_missing_id = 0; + + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + return FALSE; +} + +static void +update_disconnect_on_connection_peer_missing(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMDeviceState state; + + state = nm_device_get_state(NM_DEVICE(self)); + if (state < NM_DEVICE_STATE_IP_CONFIG || state > NM_DEVICE_STATE_ACTIVATED) { + nm_clear_g_source(&priv->peer_missing_id); + return; + } + + if (check_connection_peer_joined(self)) { + if (nm_clear_g_source(&priv->peer_missing_id)) + _LOGD(LOGD_WIFI, "Peer requested in connection is joined, removing timeout"); + return; + } + + if (priv->peer_missing_id == 0) { + _LOGD(LOGD_WIFI, "Peer requested in connection is missing, adding timeout"); + priv->peer_missing_id = + g_timeout_add_seconds(5, disconnect_on_connection_peer_missing_cb, self); + } +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate * priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMSupplicantInterfaceState supplicant_state; + + if (!priv->mgmt_iface) + return FALSE; + + supplicant_state = nm_supplicant_interface_get_state(priv->mgmt_iface); + return nm_supplicant_interface_state_is_operational(supplicant_state); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + if (!NM_DEVICE_CLASS(nm_device_wifi_p2p_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + /* TODO: Allow limitting the interface using the HW-address? */ + + /* We don't need to check anything else here. The P2P device will only + * exists if we are able to establish a P2P connection, and there should + * be no further restrictions necessary. + */ + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + gs_free char * setting_name = NULL; + NMSettingWifiP2P *s_wifi_p2p; + NMWifiP2PPeer * peer; + const char * setting_peer; + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + + if (!specific_object) { + /* If not given a specific object, we need at minimum a peer address */ + if (!s_wifi_p2p) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A '%s' setting is required if no Peer path was given", + NM_SETTING_WIFI_P2P_SETTING_NAME); + return FALSE; + } + + setting_peer = nm_setting_wifi_p2p_get_peer(s_wifi_p2p); + if (!setting_peer) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A '%s' setting with a valid Peer is required if no Peer path was given", + NM_SETTING_WIFI_P2P_SETTING_NAME); + return FALSE; + } + + } else { + peer = nm_wifi_p2p_peer_lookup_for_device(NM_DEVICE(self), specific_object); + if (!peer) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, + "The P2P peer %s is unknown", + specific_object); + return FALSE; + } + + setting_peer = nm_wifi_p2p_peer_get_address(peer); + g_return_val_if_fail(setting_peer, FALSE); + } + + /* Add a Wi-Fi P2P setting if one doesn't exist yet */ + if (!s_wifi_p2p) { + s_wifi_p2p = NM_SETTING_WIFI_P2P(nm_setting_wifi_p2p_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wifi_p2p)); + } + + g_object_set(G_OBJECT(s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL); + + setting_name = g_strdup_printf("Wi-Fi P2P Peer %s", setting_peer); + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_WIFI_P2P_SETTING_NAME, + existing_connections, + setting_name, + setting_name, + NULL, + NULL, + TRUE); + + return TRUE; +} + +/* + * supplicant_find_timeout_cb + * + * Called when the supplicant has been unable to find the peer we want to connect to. + */ +static gboolean +supplicant_find_timeout_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(user_data); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + priv->find_peer_timeout_id = 0; + + nm_supplicant_interface_p2p_cancel_connect(priv->mgmt_iface); + + if (nm_device_is_activating(device)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi-p2p) could not find peer, failing activation"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + } + + return G_SOURCE_REMOVE; +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMConnection * connection; + NMSettingWifiP2P * s_wifi_p2p; + NMWifiP2PPeer * peer; + + if (!priv->mgmt_iface) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + g_return_val_if_fail(s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE); + + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + if (!peer) { + /* Set up a timeout on the find attempt and run a find for the same period of time */ + if (priv->find_peer_timeout_id == 0) { + priv->find_peer_timeout_id = + g_timeout_add_seconds(10, supplicant_find_timeout_cb, self); + + nm_supplicant_interface_p2p_start_find(priv->mgmt_iface, 10); + } + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/* + * supplicant_connection_timeout_cb + * + * Called when the supplicant has been unable to connect to a peer + * within a specified period of time. + */ +static gboolean +supplicant_connection_timeout_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(user_data); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + priv->sup_timeout_id = 0; + + nm_supplicant_interface_p2p_cancel_connect(priv->mgmt_iface); + + if (nm_device_is_activating(device)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi-p2p) connecting took too long, failing activation"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + } + + return G_SOURCE_REMOVE; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMConnection * connection; + NMSettingWifiP2P * s_wifi_p2p; + NMWifiP2PPeer * peer; + GBytes * wfd_ies; + + if (nm_clear_g_source(&priv->find_peer_timeout_id)) + nm_assert_not_reached(); + + if (!priv->mgmt_iface) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + connection = nm_device_get_applied_connection(device); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + nm_assert( + NM_IS_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P))); + + /* The prepare stage ensures that the peer has been found */ + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + if (!peer) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* Set the WFD IEs before trying to establish the connection. */ + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p); + nm_supplicant_manager_set_wfd_ies(priv->sup_mgr, wfd_ies); + + /* TODO: Grab secrets if we don't have them yet! */ + + /* TODO: Fix "pbc" being hardcoded here! */ + nm_supplicant_interface_p2p_connect(priv->mgmt_iface, + nm_wifi_p2p_peer_get_supplicant_path(peer), + "pbc", + NULL); + + /* Set up a timeout on the connect attempt */ + if (priv->sup_timeout_id == 0) { + priv->sup_timeout_id = g_timeout_add_seconds(45, supplicant_connection_timeout_cb, self); + } + + /* We'll get stage3 started when the P2P group has been started */ + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +emit_signal_p2p_peer_add_remove(NMDeviceWifiP2P *device, + NMWifiP2PPeer * peer, + gboolean is_added /* or else is_removed */) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(device), + &interface_info_device_wifi_p2p, + is_added ? &nm_signal_info_wifi_p2p_peer_added + : &nm_signal_info_wifi_p2p_peer_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(peer))); +} + +static void +peer_add_remove(NMDeviceWifiP2P *self, + gboolean is_adding, /* or else removing */ + NMWifiP2PPeer * peer, + gboolean recheck_available_connections) +{ + NMDevice * device = NM_DEVICE(self); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (is_adding) { + g_object_ref(peer); + peer->wifi_device = device; + c_list_link_tail(&priv->peers_lst_head, &peer->peers_lst); + nm_dbus_object_export(NM_DBUS_OBJECT(peer)); + _peer_dump(self, LOGL_DEBUG, peer, "added", 0); + + emit_signal_p2p_peer_add_remove(self, peer, TRUE); + } else { + peer->wifi_device = NULL; + c_list_unlink(&peer->peers_lst); + _peer_dump(self, LOGL_DEBUG, peer, "removed", 0); + } + + _notify(self, PROP_PEERS); + + if (!is_adding) { + emit_signal_p2p_peer_add_remove(self, peer, FALSE); + nm_dbus_object_clear_and_unexport(&peer); + } + + if (is_adding) { + /* If we are in prepare state, then we are currently runnign a find + * to search for the requested peer. */ + if (priv->find_peer_timeout_id != 0) { + NMConnection *connection; + + nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_PREPARE); + + connection = nm_device_get_applied_connection(device); + nm_assert(NM_IS_CONNECTION(connection)); + + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + if (peer) { + /* A peer for the connection was found, cancel the timeout and go to configure state. */ + nm_clear_g_source(&priv->find_peer_timeout_id); + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); + } + } + + /* TODO: We may want to re-check auto-activation here, otherwise it will never work. */ + } + + update_disconnect_on_connection_peer_missing(self); +} + +static void +remove_all_peers(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMWifiP2PPeer * peer; + + if (c_list_is_empty(&priv->peers_lst_head)) + return; + + while ((peer = c_list_first_entry(&priv->peers_lst_head, NMWifiP2PPeer, peers_lst))) + peer_add_remove(self, FALSE, peer, FALSE); + + nm_device_recheck_available_connections(NM_DEVICE(self)); +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + gboolean indicate_addressing_running; + NMConnection *connection; + const char * method; + + connection = nm_device_get_applied_connection(device); + + method = nm_utils_get_ip_config_method(connection, addr_family); + + if (addr_family == AF_INET) + indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + else { + indicate_addressing_running = NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP); + } + + if (indicate_addressing_running) + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + TRUE); + + return NM_DEVICE_CLASS(nm_device_wifi_p2p_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + int ifindex = nm_device_get_ip_ifindex(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + nm_clear_g_source(&priv->find_peer_timeout_id); + nm_clear_g_source(&priv->sup_timeout_id); + nm_clear_g_source(&priv->peer_missing_id); + + if (priv->mgmt_iface) + nm_supplicant_interface_p2p_cancel_connect(priv->mgmt_iface); + + if (priv->group_iface) + nm_supplicant_interface_p2p_disconnect(priv->group_iface); + + /* Clear any critical protocol notification in the Wi-Fi stack */ + if (ifindex > 0) + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + ifindex, + FALSE); +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + *out_source = NM_DEVICE_MTU_SOURCE_NONE; + return 0; +} + +static const char * +get_auto_ip_config_method(NMDevice *device, int addr_family) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + /* Override the AUTO method to mean shared if we are group owner. */ + if (priv->group_iface && nm_supplicant_interface_get_p2p_group_owner(priv->group_iface)) { + if (addr_family == AF_INET) + return NM_SETTING_IP4_CONFIG_METHOD_SHARED; + + if (addr_family == AF_INET6) + return NM_SETTING_IP6_CONFIG_METHOD_SHARED; + } + + return NULL; +} + +static gboolean +unmanaged_on_quit(NMDevice *self) +{ + return TRUE; +} + +static void +supplicant_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(user_data); + NMDevice * device = NM_DEVICE(self); + NMSupplicantInterfaceState new_state = new_state_i; + NMSupplicantInterfaceState old_state = old_state_i; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "supplicant management interface state: %s -> %s", + nm_supplicant_interface_state_to_string(old_state), + nm_supplicant_interface_state_to_string(new_state)); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + supplicant_interfaces_release(self, TRUE); + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + _LOGD(LOGD_WIFI, "supplicant ready"); + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + _set_is_waiting_for_supplicant(self, FALSE); + } +} + +static void +supplicant_iface_peer_changed_cb(NMSupplicantInterface *iface, + NMSupplicantPeerInfo * peer_info, + gboolean is_present, + NMDeviceWifiP2P * self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + NMWifiP2PPeer * found_peer; + + found_peer = + nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head, peer_info->peer_path->str); + + if (!is_present) { + if (!found_peer) + return; + + peer_add_remove(self, FALSE, found_peer, TRUE); + goto out; + } + + if (found_peer) { + if (!nm_wifi_p2p_peer_update_from_properties(found_peer, peer_info)) + return; + + update_disconnect_on_connection_peer_missing(self); + _peer_dump(self, LOGL_DEBUG, found_peer, "updated", 0); + } else { + gs_unref_object NMWifiP2PPeer *peer = NULL; + + peer = nm_wifi_p2p_peer_new_from_properties(peer_info); + peer_add_remove(self, TRUE, peer, TRUE); + } + +out: + schedule_peer_list_dump(self); +} + +static void +check_group_iface_ready(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (!priv->group_iface) + return; + + if (!nm_supplicant_interface_state_is_operational( + nm_supplicant_interface_get_state(priv->group_iface))) + return; + + if (!nm_supplicant_interface_get_p2p_group_joined(priv->group_iface)) + return; + + nm_clear_g_source(&priv->sup_timeout_id); + update_disconnect_on_connection_peer_missing(self); + + nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self)); +} + +static void +supplicant_group_iface_is_ready(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + _LOGD(LOGD_WIFI, "P2P Group supplicant ready"); + + if (!nm_device_set_ip_iface(NM_DEVICE(self), + nm_supplicant_interface_get_ifname(priv->group_iface))) { + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + _set_is_waiting_for_supplicant(self, FALSE); + check_group_iface_ready(self); +} + +static void +supplicant_group_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(user_data); + NMSupplicantInterfaceState new_state = new_state_i; + NMSupplicantInterfaceState old_state = old_state_i; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "P2P Group supplicant interface state: %s -> %s", + nm_supplicant_interface_state_to_string(old_state), + nm_supplicant_interface_state_to_string(new_state)); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + supplicant_group_interface_release(self); + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + return; + } + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + supplicant_group_iface_is_ready(self); + return; + } +} + +static void +supplicant_group_iface_group_finished_cb(NMSupplicantInterface *iface, + const char * iface_path, + void * user_data) +{ + NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P(user_data); + + supplicant_group_interface_release(self); + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); +} + +static void +supplicant_iface_group_joined_updated_cb(NMSupplicantInterface *iface, + GParamSpec * pspec, + void * user_data) +{ + NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P(user_data); + + check_group_iface_ready(self); +} + +static void +supplicant_iface_group_started_cb(NMSupplicantInterface *iface, + NMSupplicantInterface *group_iface, + NMDeviceWifiP2P * self) +{ + NMDeviceWifiP2PPrivate * priv; + NMSupplicantInterfaceState state; + + g_return_if_fail(self); + + if (!nm_device_is_activating(NM_DEVICE(self))) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "P2P: WPA supplicant notified a group start but we are not trying to connect! " + "Ignoring the event."); + return; + } + + priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + supplicant_group_interface_release(self); + + priv->group_iface = g_object_ref(group_iface); + + /* We need to wait for the interface to be ready and the group + * information to be resolved. */ + g_signal_connect(priv->group_iface, + "notify::" NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, + G_CALLBACK(supplicant_iface_group_joined_updated_cb), + self); + + g_signal_connect(priv->group_iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK(supplicant_group_iface_state_cb), + self); + + g_signal_connect(priv->group_iface, + NM_SUPPLICANT_INTERFACE_GROUP_FINISHED, + G_CALLBACK(supplicant_group_iface_group_finished_cb), + self); + + state = nm_supplicant_interface_get_state(priv->group_iface); + if (state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + _set_is_waiting_for_supplicant(self, TRUE); + return; + } + + supplicant_group_iface_is_ready(self); +} + +static void +supplicant_group_interface_release(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (!priv->group_iface) + return; + + g_signal_handlers_disconnect_by_data(priv->group_iface, self); + + nm_supplicant_interface_p2p_disconnect(priv->group_iface); + + g_clear_object(&priv->group_iface); +} + +static void +supplicant_interfaces_release(NMDeviceWifiP2P *self, gboolean set_is_waiting) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + nm_clear_g_source(&priv->peer_dump_id); + + remove_all_peers(self); + + if (priv->mgmt_iface) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "P2P: Releasing WPA supplicant interface."); + nm_supplicant_manager_set_wfd_ies(priv->sup_mgr, NULL); + g_signal_handlers_disconnect_by_data(priv->mgmt_iface, self); + g_clear_object(&priv->mgmt_iface); + nm_clear_g_source(&priv->find_peer_timeout_id); + nm_clear_g_source(&priv->sup_timeout_id); + } + + supplicant_group_interface_release(self); + + if (set_is_waiting) + _set_is_waiting_for_supplicant(self, TRUE); +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(device); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + update_disconnect_on_connection_peer_missing(self); + + if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) { + /* Clean up the supplicant interface because in these states the + * device cannot be used. + * Do not clean up for the UNMANAGED to UNAVAILABLE transition which + * will happen during initialization. + */ + if (priv->mgmt_iface && old_state > new_state) + supplicant_interfaces_release(self, TRUE); + + /* TODO: More cleanup needed? */ + } + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + break; + case NM_DEVICE_STATE_UNAVAILABLE: + if (!priv->mgmt_iface + || !nm_supplicant_interface_state_is_operational( + nm_supplicant_interface_get_state(priv->mgmt_iface))) + _set_is_waiting_for_supplicant(self, TRUE); + break; + case NM_DEVICE_STATE_NEED_AUTH: + /* Disconnect? */ + break; + case NM_DEVICE_STATE_IP_CHECK: + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + FALSE); + break; + case NM_DEVICE_STATE_ACTIVATED: + //activation_success_handler (device); + break; + case NM_DEVICE_STATE_FAILED: + /* Clear any critical protocol notification in the wifi stack. + * At this point the IP device may have been removed already. */ + nm_supplicant_manager_set_wfd_ies(priv->sup_mgr, NULL); + if (nm_device_get_ip_ifindex(device) > 0) + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + FALSE); + break; + case NM_DEVICE_STATE_DISCONNECTED: + nm_supplicant_manager_set_wfd_ies(priv->sup_mgr, NULL); + break; + default: + break; + } +} + +static void +impl_device_wifi_p2p_start_find(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(obj); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + gs_unref_variant GVariant *options = NULL; + const char * opts_key; + GVariant * opts_val; + GVariantIter iter; + gint32 timeout = 30; + + g_variant_get(parameters, "(@a{sv})", &options); + + g_variant_iter_init(&iter, options); + while (g_variant_iter_next(&iter, "{&sv}", &opts_key, &opts_val)) { + _nm_unused gs_unref_variant GVariant *opts_val_free = opts_val; + + if (nm_streq(opts_key, "timeout")) { + if (!g_variant_is_of_type(opts_val, G_VARIANT_TYPE_INT32)) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "\"timeout\" must be an integer \"i\""); + return; + } + + timeout = g_variant_get_int32(opts_val); + if (timeout <= 0 || timeout > 600) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "The timeout for a find operation needs to be in the range of 1-600s."); + return; + } + + continue; + } + + g_dbus_method_invocation_return_error(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Unsupported options key \"%s\"", + opts_key); + return; + } + + if (!priv->mgmt_iface) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "WPA Supplicant management interface is currently unavailable."); + return; + } + + nm_supplicant_interface_p2p_start_find(priv->mgmt_iface, timeout); + + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static void +impl_device_wifi_p2p_stop_find(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(obj); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (!priv->mgmt_iface) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "WPA Supplicant management interface is currently unavailable."); + return; + } + + nm_supplicant_interface_p2p_stop_find(priv->mgmt_iface); + + g_dbus_method_invocation_return_value(invocation, NULL); +} + +/*****************************************************************************/ + +NMSupplicantInterface * +nm_device_wifi_p2p_get_mgmt_iface(NMDeviceWifiP2P *self) +{ + g_return_val_if_fail(NM_IS_DEVICE_WIFI_P2P(self), NULL); + + return NM_DEVICE_WIFI_P2P_GET_PRIVATE(self)->mgmt_iface; +} + +void +nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self, NMSupplicantInterface *iface) +{ + NMDeviceWifiP2PPrivate *priv; + + g_return_if_fail(NM_IS_DEVICE_WIFI_P2P(self)); + g_return_if_fail(!iface || NM_IS_SUPPLICANT_INTERFACE(iface)); + + priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + if (priv->mgmt_iface == iface) + goto done; + + supplicant_interfaces_release(self, FALSE); + + if (!iface) + goto done; + + _LOGD(LOGD_DEVICE | LOGD_WIFI, + "P2P: WPA supplicant management interface changed to %s.", + nm_ref_string_get_str(nm_supplicant_interface_get_object_path(iface))); + + priv->mgmt_iface = g_object_ref(iface); + + g_signal_connect(priv->mgmt_iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK(supplicant_iface_state_cb), + self); + g_signal_connect(priv->mgmt_iface, + NM_SUPPLICANT_INTERFACE_PEER_CHANGED, + G_CALLBACK(supplicant_iface_peer_changed_cb), + self); + g_signal_connect(priv->mgmt_iface, + NM_SUPPLICANT_INTERFACE_GROUP_STARTED, + G_CALLBACK(supplicant_iface_group_started_cb), + self); +done: + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + _set_is_waiting_for_supplicant(self, + !priv->mgmt_iface + || !nm_supplicant_interface_state_is_operational( + nm_supplicant_interface_get_state(priv->mgmt_iface))); +} + +void +nm_device_wifi_p2p_remove(NMDeviceWifiP2P *self) +{ + g_signal_emit_by_name(self, NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + return "wifi-p2p"; +} + +/*****************************************************************************/ + +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "PeerAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), ); + +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "PeerRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WIFI_P2P, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "StartFind", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), ), + .handle = impl_device_wifi_p2p_start_find, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("StopFind", ), + .handle = impl_device_wifi_p2p_stop_find, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wifi_p2p_peer_added, + &nm_signal_info_wifi_p2p_peer_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers", + "ao", + NM_DEVICE_WIFI_P2P_PEERS), ), ), + .legacy_property_changed = FALSE, +}; + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(object); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + const char ** list; + + switch (prop_id) { + case PROP_PEERS: + list = nm_wifi_p2p_peers_get_paths(&priv->peers_lst_head); + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_wifi_p2p_init(NMDeviceWifiP2P *self) +{ + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self); + + c_list_init(&priv->peers_lst_head); + + priv->sup_mgr = g_object_ref(nm_supplicant_manager_get()); +} + +static void +constructed(GObject *object) +{ + NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P(object); + + G_OBJECT_CLASS(nm_device_wifi_p2p_parent_class)->constructed(object); + + _set_is_waiting_for_supplicant(self, TRUE); +} + +NMDeviceWifiP2P * +nm_device_wifi_p2p_new(const char *iface) +{ + return g_object_new(NM_TYPE_DEVICE_WIFI_P2P, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "802.11 Wi-Fi P2P", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WIFI_P2P, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WIFI, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WLAN, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceWifiP2P * self = NM_DEVICE_WIFI_P2P(object); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(object); + + g_clear_object(&priv->sup_mgr); + + supplicant_interfaces_release(self, FALSE); + + G_OBJECT_CLASS(nm_device_wifi_p2p_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMDeviceWifiP2P * peer = NM_DEVICE_WIFI_P2P(object); + NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(peer); + + nm_assert(c_list_is_empty(&priv->peers_lst_head)); + + G_OBJECT_CLASS(nm_device_wifi_p2p_parent_class)->finalize(object); +} + +static void +nm_device_wifi_p2p_class_init(NMDeviceWifiP2PClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wifi_p2p); + + device_class->connection_type_supported = NM_SETTING_WIFI_P2P_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI_P2P); + device_class->get_type_description = get_type_description; + + /* Do we need compatibility checking or is the default good enough? */ + device_class->is_available = is_available; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->get_configured_mtu = get_configured_mtu; + device_class->get_auto_ip_config_method = get_auto_ip_config_method; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + + device_class->deactivate = deactivate; + device_class->unmanaged_on_quit = unmanaged_on_quit; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_PEERS] = g_param_spec_boxed(NM_DEVICE_WIFI_P2P_PEERS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.h b/src/core/devices/wifi/nm-device-wifi-p2p.h new file mode 100644 index 0000000..d1aadd8 --- /dev/null +++ b/src/core/devices/wifi/nm-device-wifi-p2p.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_DEVICE_WIFI_P2P_H__ +#define __NM_DEVICE_WIFI_P2P_H__ + +#include "devices/nm-device.h" +#include "supplicant/nm-supplicant-interface.h" + +#define NM_TYPE_DEVICE_WIFI_P2P (nm_device_wifi_p2p_get_type()) +#define NM_DEVICE_WIFI_P2P(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P)) +#define NM_DEVICE_WIFI_P2P_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass)) +#define NM_IS_DEVICE_WIFI_P2P(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WIFI_P2P)) +#define NM_IS_DEVICE_WIFI_P2P_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_WIFI_P2P)) +#define NM_DEVICE_WIFI_P2P_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass)) + +#define NM_DEVICE_WIFI_P2P_PEERS "peers" +#define NM_DEVICE_WIFI_P2P_GROUPS "groups" + +typedef struct _NMDeviceWifiP2P NMDeviceWifiP2P; +typedef struct _NMDeviceWifiP2PClass NMDeviceWifiP2PClass; + +GType nm_device_wifi_p2p_get_type(void); + +NMDeviceWifiP2P *nm_device_wifi_p2p_new(const char *iface); + +NMSupplicantInterface *nm_device_wifi_p2p_get_mgmt_iface(NMDeviceWifiP2P *self); +void nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self, NMSupplicantInterface *iface); + +void nm_device_wifi_p2p_remove(NMDeviceWifiP2P *self); + +#endif /* __NM_DEVICE_WIFI_P2P_H__ */ diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c new file mode 100644 index 0000000..6375412 --- /dev/null +++ b/src/core/devices/wifi/nm-device-wifi.c @@ -0,0 +1,3862 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-wifi.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-device-wifi-p2p.h" +#include "nm-wifi-ap.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "devices/nm-device.h" +#include "devices/nm-device-private.h" +#include "nm-dbus-manager.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-act-request.h" +#include "supplicant/nm-supplicant-manager.h" +#include "supplicant/nm-supplicant-interface.h" +#include "supplicant/nm-supplicant-config.h" +#include "nm-setting-connection.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" +#include "nm-setting-ip4-config.h" +#include "nm-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "platform/nm-platform.h" +#include "nm-auth-utils.h" +#include "settings/nm-settings-connection.h" +#include "settings/nm-settings.h" +#include "nm-wifi-utils.h" +#include "nm-wifi-common.h" +#include "nm-core-internal.h" +#include "nm-config.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceWifi +#include "devices/nm-device-logging.h" + +#define SCAN_INTERVAL_SEC_MIN 3 +#define SCAN_INTERVAL_SEC_STEP 20 +#define SCAN_INTERVAL_SEC_MAX 120 + +#define SCAN_EXTRA_DELAY_MSEC 500 + +#define SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC (5 * 60) + +#define SCAN_REQUEST_SSIDS_MAX_NUM 32u +#define SCAN_REQUEST_SSIDS_MAX_AGE_MSEC (3 * 60 * NM_UTILS_MSEC_PER_SEC) + +#define _LOGT_scan(...) _LOGT(LOGD_WIFI_SCAN, "wifi-scan: " __VA_ARGS__) + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceWifi, + PROP_MODE, + PROP_BITRATE, + PROP_ACCESS_POINTS, + PROP_ACTIVE_ACCESS_POINT, + PROP_CAPABILITIES, + PROP_SCANNING, + PROP_LAST_SCAN, ); + +enum { + P2P_DEVICE_CREATED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + CList aps_lst_head; + GHashTable *aps_idx_by_supplicant_path; + + CList scanning_prohibited_lst_head; + + GCancellable *scan_request_cancellable; + + GSource *scan_request_delay_source; + + NMWifiAP *current_ap; + + GHashTable *scan_request_ssids_hash; + CList scan_request_ssids_lst_head; + + NMActRequestGetSecretsCallId *wifi_secrets_id; + + NMSupplicantManager * sup_mgr; + NMSupplMgrCreateIfaceHandle *sup_create_handle; + NMSupplicantInterface * sup_iface; + + gint64 scan_last_complete_msec; + gint64 scan_periodic_next_msec; + + gint64 scan_last_request_started_at_msec; + + guint scan_kickoff_timeout_id; + + guint ap_dump_id; + + guint periodic_update_id; + + guint link_timeout_id; + guint reacquire_iface_id; + guint wps_timeout_id; + guint sup_timeout_id; /* supplicant association timeout */ + + NMDeviceWifiCapabilities capabilities; + NMSettingWirelessWakeOnWLan wowlan_restore; + + NMDeviceWifiP2P *p2p_device; + NM80211Mode mode; + + guint32 failed_iface_count; + gint32 hw_addr_scan_expire; + + guint32 rate; + + guint8 scan_periodic_interval_sec; + + bool enabled : 1; /* rfkilled or not */ + bool scan_is_scanning : 1; + bool scan_periodic_allowed : 1; + bool scan_explicit_allowed : 1; + bool scan_explicit_requested : 1; + bool ssid_found : 1; + bool hidden_probe_scan_warn : 1; + +} NMDeviceWifiPrivate; + +struct _NMDeviceWifi { + NMDevice parent; + NMDeviceWifiPrivate _priv; +}; + +struct _NMDeviceWifiClass { + NMDeviceClass parent; +}; + +/*****************************************************************************/ + +G_DEFINE_TYPE(NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE) + +#define NM_DEVICE_WIFI_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI, NMDevice) + +/*****************************************************************************/ + +static void supplicant_iface_state_down(NMDeviceWifi *self); + +static void cleanup_association_attempt(NMDeviceWifi *self, gboolean disconnect); + +static void supplicant_iface_state(NMDeviceWifi * self, + NMSupplicantInterfaceState new_state, + NMSupplicantInterfaceState old_state, + int disconnect_reason, + gboolean is_real_signal); + +static void supplicant_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data); + +static void supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface, + NMSupplicantBssInfo * bss_info, + gboolean is_present, + NMDeviceWifi * self); + +static void supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface, + GVariant * credentials, + NMDeviceWifi * self); + +static void supplicant_iface_notify_current_bss(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceWifi * self); + +static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceWifi * self); + +static void periodic_update(NMDeviceWifi *self); + +static void ap_add_remove(NMDeviceWifi *self, + gboolean is_adding, + NMWifiAP * ap, + gboolean recheck_available_connections); + +static void _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset); + +static void recheck_p2p_availability(NMDeviceWifi *self); + +static void _scan_kickoff(NMDeviceWifi *self); + +static gboolean _scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff); + +/*****************************************************************************/ + +typedef struct { + GBytes *ssid; + CList lst; + gint64 timestamp_msec; +} ScanRequestSsidData; + +static void +_scan_request_ssids_remove(ScanRequestSsidData *srs_data) +{ + c_list_unlink_stale(&srs_data->lst); + g_bytes_unref(srs_data->ssid); + nm_g_slice_free(srs_data); +} + +static void +_scan_request_ssids_remove_with_hash(NMDeviceWifiPrivate *priv, ScanRequestSsidData *srs_data) +{ + nm_assert(srs_data); + nm_assert(nm_g_hash_table_lookup(priv->scan_request_ssids_hash, srs_data) == srs_data); + if (!g_hash_table_remove(priv->scan_request_ssids_hash, srs_data)) + nm_assert_not_reached(); + _scan_request_ssids_remove(srs_data); +} + +static void +_scan_request_ssids_remove_all(NMDeviceWifiPrivate *priv, + gint64 cutoff_with_now_msec, + guint cutoff_at_len) +{ + ScanRequestSsidData *srs_data; + + nm_assert((!priv->scan_request_ssids_hash) + == c_list_is_empty(&priv->scan_request_ssids_lst_head)); + if (!priv->scan_request_ssids_hash) + return; + + if (cutoff_at_len == 0) { + nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy); + while ( + (srs_data = + c_list_first_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) + _scan_request_ssids_remove(srs_data); + return; + } + + if (cutoff_with_now_msec != 0) { + gint64 cutoff_time_msec; + + /* remove all entries that are older than a max-age. */ + nm_assert(cutoff_with_now_msec > 0); + cutoff_time_msec = cutoff_with_now_msec - SCAN_REQUEST_SSIDS_MAX_AGE_MSEC; + while ( + (srs_data = + c_list_last_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) { + if (srs_data->timestamp_msec > cutoff_time_msec) + break; + _scan_request_ssids_remove_with_hash(priv, srs_data); + } + } + + if (cutoff_at_len != G_MAXUINT) { + guint i; + + /* trim the list to cutoff_at_len elements. */ + i = nm_g_hash_table_size(priv->scan_request_ssids_hash); + for (; i > cutoff_at_len; i--) { + ScanRequestSsidData *d; + + d = c_list_last_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst); + _scan_request_ssids_remove_with_hash(priv, d); + } + } + + nm_assert(nm_g_hash_table_size(priv->scan_request_ssids_hash) <= SCAN_REQUEST_SSIDS_MAX_NUM); + nm_assert(nm_g_hash_table_size(priv->scan_request_ssids_hash) + == c_list_length(&priv->scan_request_ssids_lst_head)); + if (c_list_is_empty(&priv->scan_request_ssids_lst_head)) + nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy); +} + +static GPtrArray * +_scan_request_ssids_fetch(NMDeviceWifiPrivate *priv, gint64 now_msec) +{ + ScanRequestSsidData *srs_data; + GPtrArray * ssids; + guint len; + + _scan_request_ssids_remove_all(priv, now_msec, G_MAXUINT); + + len = nm_g_hash_table_size(priv->scan_request_ssids_hash); + if (len == 0) + return NULL; + + ssids = g_ptr_array_new_full(len, (GDestroyNotify) g_bytes_unref); + nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy); + while ((srs_data = + c_list_first_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) { + g_ptr_array_add(ssids, g_steal_pointer(&srs_data->ssid)); + _scan_request_ssids_remove(srs_data); + } + return ssids; +} + +static void +_scan_request_ssids_track(NMDeviceWifiPrivate *priv, const GPtrArray *ssids) +{ + CList old_lst_head; + gint64 now_msec; + guint i; + + if (!ssids || ssids->len == 0) + return; + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + if (!priv->scan_request_ssids_hash) + priv->scan_request_ssids_hash = g_hash_table_new(nm_pgbytes_hash, nm_pgbytes_equal); + + /* Do a little dance. New elements shall keep their order as in @ssids, but all + * new elements should be sorted in the list preexisting elements of the list. + * First move the old elements away, and splice them back afterwards. */ + c_list_init(&old_lst_head); + c_list_splice(&old_lst_head, &priv->scan_request_ssids_lst_head); + + for (i = 0; i < ssids->len; i++) { + GBytes * ssid = ssids->pdata[i]; + ScanRequestSsidData *d; + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(ScanRequestSsidData, ssid) == 0); + d = g_hash_table_lookup(priv->scan_request_ssids_hash, &ssid); + if (!d) { + d = g_slice_new(ScanRequestSsidData); + *d = (ScanRequestSsidData){ + .lst = C_LIST_INIT(d->lst), + .timestamp_msec = now_msec, + .ssid = g_bytes_ref(ssid), + }; + g_hash_table_add(priv->scan_request_ssids_hash, d); + } else + d->timestamp_msec = now_msec; + c_list_link_tail(&priv->scan_request_ssids_lst_head, &d->lst); + } + + c_list_splice(&priv->scan_request_ssids_lst_head, &old_lst_head); + + /* Trim the excess. After our splice with old_lst_head, the list contains the new + * elements (from @ssids) at the front (in there original order), followed by older elements. */ + _scan_request_ssids_remove_all(priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM); +} + +/*****************************************************************************/ + +void +nm_device_wifi_scanning_prohibited_track(NMDeviceWifi *self, + gpointer tag, + gboolean temporarily_prohibited) +{ + NMDeviceWifiPrivate *priv; + NMCListElem * elem; + + g_return_if_fail(NM_IS_DEVICE_WIFI(self)); + nm_assert(tag); + + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + /* We track these with a simple CList. This would be not efficient, if + * there would be many users that need to be tracked at the same time (there + * aren't). In fact, most of the time there is no NMDeviceOlpcMesh and + * nobody tracks itself here. Optimize for that and simplicity. */ + + elem = nm_c_list_elem_find_first(&priv->scanning_prohibited_lst_head, iter, iter == tag); + + if (!temporarily_prohibited) { + if (!elem) + return; + nm_c_list_elem_free(elem); + } else { + if (elem) + return; + c_list_link_tail(&priv->scanning_prohibited_lst_head, &nm_c_list_elem_new_stale(tag)->lst); + } + + _scan_notify_allowed(self, NM_TERNARY_DEFAULT); +} + +/*****************************************************************************/ + +static void +_ap_dump(NMDeviceWifi * self, + NMLogLevel log_level, + const NMWifiAP *ap, + const char * prefix, + gint64 now_msec) +{ + char buf[1024]; + + buf[0] = '\0'; + _NMLOG(log_level, + LOGD_WIFI_SCAN, + "wifi-ap: %-7s %s", + prefix, + nm_wifi_ap_to_string(ap, buf, sizeof(buf), now_msec)); +} + +gboolean +nm_device_wifi_get_scanning(NMDeviceWifi *self) +{ + g_return_val_if_fail(NM_IS_DEVICE_WIFI(self), FALSE); + + return NM_DEVICE_WIFI_GET_PRIVATE(self)->scan_is_scanning; +} + +static gboolean +_scan_is_scanning_eval(NMDeviceWifiPrivate *priv) +{ + return priv->scan_request_cancellable || priv->scan_request_delay_source + || (priv->sup_iface && nm_supplicant_interface_get_scanning(priv->sup_iface)); +} + +static gboolean +_scan_notify_is_scanning(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gboolean last_scan_changed = FALSE; + NMDeviceState state; + gboolean scanning; + + scanning = _scan_is_scanning_eval(priv); + if (scanning == priv->scan_is_scanning) + return FALSE; + + priv->scan_is_scanning = scanning; + + if (!scanning || priv->scan_last_complete_msec == 0) { + last_scan_changed = TRUE; + priv->scan_last_complete_msec = nm_utils_get_monotonic_timestamp_msec(); + } + + _LOGD(LOGD_WIFI, + "wifi-scan: scanning-state: %s%s", + scanning ? "scanning" : "idle", + last_scan_changed ? " (notify last-scan)" : ""); + + state = nm_device_get_state(NM_DEVICE(self)); + + if (scanning) { + /* while the device is activating/activated, we don't need the pending + * action. The pending action exists to delay startup complete, while + * activating that is already achieved via other means. */ + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED) + nm_device_add_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE); + } + + nm_gobject_notify_together(self, PROP_SCANNING, last_scan_changed ? PROP_LAST_SCAN : PROP_0); + + _scan_kickoff(self); + + if (!_scan_is_scanning_eval(priv)) { + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_remove_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE); + } + + return TRUE; +} + +static gboolean +_scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gboolean explicit_allowed; + gboolean periodic_allowed; + NMDeviceState state; + gboolean changed = FALSE; + + state = nm_device_get_state(NM_DEVICE(self)); + + explicit_allowed = FALSE; + periodic_allowed = FALSE; + + if (!c_list_is_empty(&priv->scanning_prohibited_lst_head)) { + /* something prohibits scanning. */ + } else if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP)) { + /* Don't scan when a an AP or Ad-Hoc connection is active as it will + * disrupt connected clients or peers. */ + } else if (NM_IN_SET(state, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_FAILED)) { + /* Can always scan when disconnected */ + explicit_allowed = TRUE; + periodic_allowed = TRUE; + } else if (NM_IN_SET(state, NM_DEVICE_STATE_ACTIVATED)) { + /* Prohibit periodic scans when connected; we ask the supplicant to + * background scan for us, unless the connection is locked to a specific + * BSSID (in which case scanning is effectively disabled). */ + periodic_allowed = FALSE; + + /* Prohibit scans if the supplicant is busy */ + if (priv->sup_iface) { + explicit_allowed = !NM_IN_SET(nm_supplicant_interface_get_state(priv->sup_iface), + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, + NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE); + } else + explicit_allowed = FALSE; + } + + if (explicit_allowed != priv->scan_explicit_allowed + || periodic_allowed != priv->scan_periodic_allowed) { + priv->scan_periodic_allowed = periodic_allowed; + priv->scan_explicit_allowed = explicit_allowed; + _LOGT_scan("scan-periodic-allowed=%d, scan-explicit-allowed=%d", + periodic_allowed, + explicit_allowed); + changed = TRUE; + } + + if (do_kickoff == NM_TERNARY_TRUE || (do_kickoff == NM_TERNARY_DEFAULT && changed)) + _scan_kickoff(self); + + return changed; +} + +static void +supplicant_iface_notify_scanning_cb(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceWifi * self) +{ + _scan_notify_is_scanning(self); +} + +static gboolean +unmanaged_on_quit(NMDevice *self) +{ + /* Wi-Fi devices cannot be assumed and are always taken down. + * However, also when being disconnected, we scan and thus + * set the MAC address to a random value. + * + * We must restore the original MAC address when quitting, thus + * signal to unmanage the device. */ + return TRUE; +} + +static void +supplicant_interface_acquire_cb(NMSupplicantManager * supplicant_manager, + NMSupplMgrCreateIfaceHandle *handle, + NMSupplicantInterface * iface, + GError * error, + gpointer user_data) +{ + NMDeviceWifi * self = user_data; + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (nm_utils_error_is_cancelled(error)) + return; + + nm_assert(priv->sup_create_handle == handle); + + priv->sup_create_handle = NULL; + + if (error) { + _LOGE(LOGD_WIFI, "Couldn't initialize supplicant interface: %s", error->message); + supplicant_iface_state_down(self); + nm_device_remove_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, + TRUE); + return; + } + + priv->sup_iface = g_object_ref(iface); + + g_signal_connect(priv->sup_iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK(supplicant_iface_state_cb), + self); + g_signal_connect(priv->sup_iface, + NM_SUPPLICANT_INTERFACE_BSS_CHANGED, + G_CALLBACK(supplicant_iface_bss_changed_cb), + self); + g_signal_connect(priv->sup_iface, + NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS, + G_CALLBACK(supplicant_iface_wps_credentials_cb), + self); + g_signal_connect(priv->sup_iface, + "notify::" NM_SUPPLICANT_INTERFACE_SCANNING, + G_CALLBACK(supplicant_iface_notify_scanning_cb), + self); + g_signal_connect(priv->sup_iface, + "notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS, + G_CALLBACK(supplicant_iface_notify_current_bss), + self); + g_signal_connect(priv->sup_iface, + "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, + G_CALLBACK(supplicant_iface_notify_p2p_available), + self); + + _scan_notify_is_scanning(self); + + if (nm_supplicant_interface_get_state(priv->sup_iface) + != NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + /* fake an initial state change. */ + supplicant_iface_state(user_data, + NM_SUPPLICANT_INTERFACE_STATE_STARTING, + nm_supplicant_interface_get_state(priv->sup_iface), + 0, + FALSE); + } +} + +static void +supplicant_interface_acquire(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_assert(!priv->sup_iface); + nm_assert(!priv->sup_create_handle); + + priv->sup_create_handle = + nm_supplicant_manager_create_interface(priv->sup_mgr, + nm_device_get_ifindex(NM_DEVICE(self)), + NM_SUPPLICANT_DRIVER_WIRELESS, + supplicant_interface_acquire_cb, + self); + nm_device_add_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE); +} + +static void +supplicant_interface_release(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (nm_clear_pointer(&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel)) + nm_device_remove_pending_action(NM_DEVICE(self), + NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, + TRUE); + + nm_clear_g_source(&priv->scan_kickoff_timeout_id); + nm_clear_g_source_inst(&priv->scan_request_delay_source); + nm_clear_g_cancellable(&priv->scan_request_cancellable); + + _scan_request_ssids_remove_all(priv, 0, 0); + + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; + + nm_clear_g_source(&priv->ap_dump_id); + + if (priv->sup_iface) { + /* Clear supplicant interface signal handlers */ + g_signal_handlers_disconnect_by_data(priv->sup_iface, self); + + /* Tell the supplicant to disconnect from the current AP */ + nm_supplicant_interface_disconnect(priv->sup_iface); + + g_clear_object(&priv->sup_iface); + } + + if (priv->p2p_device) { + /* Signal to P2P device to also release its reference */ + nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, NULL); + } + + _scan_notify_is_scanning(self); +} + +static void +update_seen_bssids_cache(NMDeviceWifi *self, NMWifiAP *ap) +{ + g_return_if_fail(NM_IS_DEVICE_WIFI(self)); + + if (ap == NULL) + return; + + /* Don't cache the BSSID for Ad-Hoc APs */ + if (nm_wifi_ap_get_mode(ap) != NM_802_11_MODE_INFRA) + return; + + if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_ACTIVATED + && nm_device_has_unmodified_applied_connection(NM_DEVICE(self), + NM_SETTING_COMPARE_FLAG_NONE)) { + nm_settings_connection_add_seen_bssid(nm_device_get_settings_connection(NM_DEVICE(self)), + nm_wifi_ap_get_address(ap)); + } +} + +static void +set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_connections) +{ + NMDeviceWifiPrivate *priv; + NMWifiAP * old_ap; + + g_return_if_fail(NM_IS_DEVICE_WIFI(self)); + + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + old_ap = priv->current_ap; + + if (old_ap == new_ap) + return; + + if (new_ap) { + priv->current_ap = g_object_ref(new_ap); + + /* Update seen BSSIDs cache */ + update_seen_bssids_cache(self, priv->current_ap); + } else + priv->current_ap = NULL; + + if (old_ap) { + NM80211Mode mode = nm_wifi_ap_get_mode(old_ap); + + /* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */ + if (NM_IN_SET(mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP) || nm_wifi_ap_get_fake(old_ap)) + ap_add_remove(self, FALSE, old_ap, recheck_available_connections); + g_object_unref(old_ap); + } + + _notify(self, PROP_ACTIVE_ACCESS_POINT); +} + +static void +periodic_update(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + int ifindex; + guint32 new_rate; + int percent; + + if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_ACTIVATED) { + /* BSSID and signal strength have meaningful values only if the device + * is activated and not scanning. + */ + return; + } + + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (!nm_supplicant_interface_state_is_associated( + nm_supplicant_interface_get_state(priv->sup_iface)) + || nm_supplicant_interface_get_scanning(priv->sup_iface)) { + /* Only update current AP if we're actually talking to something, otherwise + * assume the old one (if any) is still valid until we're told otherwise or + * the connection fails. + */ + return; + } + + if (priv->mode == NM_802_11_MODE_AP) { + /* In AP mode we currently have nothing to do. */ + return; + } + + ifindex = nm_device_get_ifindex(NM_DEVICE(self)); + if (ifindex <= 0) + g_return_if_reached(); + + if (priv->current_ap + && nm_platform_wifi_get_station(nm_device_get_platform(NM_DEVICE(self)), + ifindex, + NULL, + &percent, + &new_rate)) { + if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) { +#if NM_MORE_LOGGING + _ap_dump(self, LOGL_TRACE, priv->current_ap, "updated", 0); +#endif + } + + if (new_rate != priv->rate) { + priv->rate = new_rate; + _notify(self, PROP_BITRATE); + } + } +} + +static gboolean +periodic_update_cb(gpointer user_data) +{ + periodic_update(user_data); + return TRUE; +} + +static void +ap_add_remove(NMDeviceWifi *self, + gboolean is_adding, /* or else removing */ + NMWifiAP * ap, + gboolean recheck_available_connections) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (is_adding) { + g_object_ref(ap); + ap->wifi_device = NM_DEVICE(self); + c_list_link_tail(&priv->aps_lst_head, &ap->aps_lst); + if (!g_hash_table_insert(priv->aps_idx_by_supplicant_path, + nm_wifi_ap_get_supplicant_path(ap), + ap)) + nm_assert_not_reached(); + nm_dbus_object_export(NM_DBUS_OBJECT(ap)); + _ap_dump(self, LOGL_DEBUG, ap, "added", 0); + nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, TRUE); + } else { + ap->wifi_device = NULL; + c_list_unlink(&ap->aps_lst); + if (!g_hash_table_remove(priv->aps_idx_by_supplicant_path, + nm_wifi_ap_get_supplicant_path(ap))) + nm_assert_not_reached(); + _ap_dump(self, LOGL_DEBUG, ap, "removed", 0); + } + + _notify(self, PROP_ACCESS_POINTS); + + if (!is_adding) { + nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, FALSE); + nm_dbus_object_clear_and_unexport(&ap); + } + + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + if (recheck_available_connections) + nm_device_recheck_available_connections(NM_DEVICE(self)); +} + +static void +remove_all_aps(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMWifiAP * ap; + + if (c_list_is_empty(&priv->aps_lst_head)) + return; + + set_current_ap(self, NULL, FALSE); + + while ((ap = c_list_first_entry(&priv->aps_lst_head, NMWifiAP, aps_lst))) + ap_add_remove(self, FALSE, ap, FALSE); + + nm_device_recheck_available_connections(NM_DEVICE(self)); +} + +static gboolean +wake_on_wlan_restore(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSettingWirelessWakeOnWLan w; + + w = priv->wowlan_restore; + if (w == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) + return TRUE; + + priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; + return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET, + nm_device_get_ifindex(NM_DEVICE(self)), + w); +} + +static void +disconnect_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data) +{ + gs_unref_object NMDeviceWifi *self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + /* error will be freed by sup_iface */ + callback(NM_DEVICE(self), error, callback_user_data); +} + +static void +disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMDeviceWifi *self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + gs_free_error GError *cancelled_error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + callback(NM_DEVICE(self), cancelled_error, callback_user_data); +} + +static void +deactivate_async(NMDevice * device, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer callback_user_data) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gpointer user_data; + + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + + user_data = nm_utils_user_data_pack(g_object_ref(self), callback, callback_user_data); + if (!priv->sup_iface) { + nm_utils_invoke_on_idle(cancellable, disconnect_cb_on_idle, user_data); + return; + } + + cleanup_association_attempt(self, FALSE); + + nm_supplicant_interface_disconnect_async(priv->sup_iface, + cancellable, + disconnect_cb, + user_data); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + int ifindex = nm_device_get_ifindex(device); + + nm_clear_g_source(&priv->periodic_update_id); + + cleanup_association_attempt(self, TRUE); + + priv->rate = 0; + + set_current_ap(self, NULL, TRUE); + + if (!wake_on_wlan_restore(self)) + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot unconfigure WoWLAN."); + + /* Clear any critical protocol notification in the Wi-Fi stack */ + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE); + + /* Ensure we're in infrastructure mode after deactivation; some devices + * (usually older ones) don't scan well in adhoc mode. + */ + if (nm_platform_wifi_get_mode(nm_device_get_platform(device), ifindex) + != NM_802_11_MODE_INFRA) { + nm_device_take_down(NM_DEVICE(self), TRUE); + nm_platform_wifi_set_mode(nm_device_get_platform(device), ifindex, NM_802_11_MODE_INFRA); + nm_device_bring_up(NM_DEVICE(self), TRUE, NULL); + } + + if (priv->mode != NM_802_11_MODE_INFRA) { + priv->mode = NM_802_11_MODE_INFRA; + _notify(self, PROP_MODE); + } + + _scan_notify_allowed(self, NM_TERNARY_TRUE); +} + +static void +deactivate_reset_hw_addr(NMDevice *device) +{ + _hw_addr_set_scanning((NMDeviceWifi *) device, TRUE); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSettingWireless * s_wireless; + const char * mac; + const char *const * mac_blacklist; + int i; + const char * mode; + const char * perm_hw_addr; + + if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_wireless = nm_connection_get_setting_wireless(connection); + + perm_hw_addr = nm_device_get_permanent_hw_address(device); + mac = nm_setting_wireless_get_mac_address(s_wireless); + if (perm_hw_addr) { + if (mac && !nm_utils_hwaddr_matches(mac, -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device MAC address does not match the profile"); + return FALSE; + } + + /* Check for MAC address blacklist */ + mac_blacklist = nm_setting_wireless_get_mac_address_blacklist(s_wireless); + for (i = 0; mac_blacklist[i]; i++) { + if (!nm_utils_hwaddr_valid(mac_blacklist[i], ETH_ALEN)) { + g_warn_if_reached(); + return FALSE; + } + + if (nm_utils_hwaddr_matches(mac_blacklist[i], -1, perm_hw_addr, -1)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "MAC address blacklisted"); + return FALSE; + } + } + } else if (mac) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has no valid MAC address as required by profile"); + return FALSE; + } + + /* Early exit if supplicant or device doesn't support requested mode */ + mode = nm_setting_wireless_get_mode(s_wireless); + if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the device does not support Ad-Hoc networks"); + return FALSE; + } + } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the device does not support Access Point mode"); + return FALSE; + } + + if (priv->sup_iface) { + if (nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) + == NM_TERNARY_FALSE) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "wpa_supplicant does not support Access Point mode"); + return FALSE; + } + } + } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_MESH)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the device does not support Mesh mode"); + return FALSE; + } + + if (priv->sup_iface) { + if (nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_MESH) + == NM_TERNARY_FALSE) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "wpa_supplicant does not support Mesh mode"); + return FALSE; + } + } + } + + // FIXME: check channel/freq/band against bands the hardware supports + // FIXME: check encryption against device capabilities + // FIXME: check bitrate against device capabilities + + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSettingWireless * s_wifi; + const char * mode; + + s_wifi = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wifi, FALSE); + + /* a connection that is available for a certain @specific_object, MUST + * also be available in general (without @specific_object). */ + + if (specific_object) { + NMWifiAP *ap; + + ap = nm_wifi_ap_lookup_for_device(NM_DEVICE(self), specific_object); + if (!ap) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "requested access point not found"); + return FALSE; + } + if (!nm_wifi_ap_check_compatible(ap, connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "requested access point is not compatible with profile"); + return FALSE; + } + return TRUE; + } + + /* Ad-Hoc, AP and Mesh connections are always available because they may be + * started at any time. + */ + mode = nm_setting_wireless_get_mode(s_wifi); + if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0 + || g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0 + || g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) + return TRUE; + + /* Hidden SSIDs obviously don't always appear in the scan list either. + * + * For an explicit user-activation-request, a connection is considered + * available because for hidden Wi-Fi, clients didn't consistently + * set the 'hidden' property to indicate hidden SSID networks. If + * activating but the network isn't available let the device recheck + * availability. + */ + if (nm_setting_wireless_get_hidden(s_wifi) + || NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP)) + return TRUE; + + if (!nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "no compatible access point found"); + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSettingWireless * s_wifi; + gs_free char * ssid_utf8 = NULL; + NMWifiAP * ap; + GBytes * ssid = NULL; + GBytes * setting_ssid = NULL; + gboolean hidden = FALSE; + const char * mode; + + s_wifi = nm_connection_get_setting_wireless(connection); + + mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL; + + if (!specific_object) { + /* If not given a specific object, we need at minimum an SSID */ + if (!s_wifi) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wireless' setting is required if no AP path was given."); + return FALSE; + } + + setting_ssid = nm_setting_wireless_get_ssid(s_wifi); + if (!setting_ssid || g_bytes_get_size(setting_ssid) == 0) { + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wireless' setting with a valid SSID is required if no AP path was given."); + return FALSE; + } + + if (!nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) { + /* Find a compatible AP in the scan list */ + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + + /* If we still don't have an AP, then the WiFI settings needs to be + * fully specified by the client. Might not be able to find an AP + * if the network isn't broadcasting the SSID for example. + */ + if (!ap) { + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + + hidden = TRUE; + } + } else { + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + ap = NULL; + } + } else if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + ap = NULL; + } else { + ap = nm_wifi_ap_lookup_for_device(NM_DEVICE(self), specific_object); + if (!ap) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, + "The access point %s was not in the scan list.", + specific_object); + return FALSE; + } + } + + /* Add a wifi setting if one doesn't exist yet */ + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + } + + if (ap) + ssid = nm_wifi_ap_get_ssid(ap); + + if (ssid == NULL) { + /* The AP must be hidden. Connecting to a Wi-Fi AP requires the SSID + * as part of the initial handshake, so check the connection details + * for the SSID. The AP object will still be used for encryption + * settings and such. + */ + ssid = nm_setting_wireless_get_ssid(s_wifi); + } + + if (ssid == NULL) { + /* If there's no SSID on the AP itself, and no SSID in the + * connection data, then we cannot connect at all. Return an error. + */ + g_set_error_literal( + error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + ap ? "A 'wireless' setting with a valid SSID is required for hidden access points." + : "Cannot create 'wireless' setting due to missing SSID."); + return FALSE; + } + + if (ap) { + /* If the SSID is a well-known SSID, lock the connection to the AP's + * specific BSSID so NM doesn't autoconnect to some random wifi net. + */ + if (!nm_wifi_ap_complete_connection(ap, + connection, + nm_wifi_utils_is_manf_default_ssid(ssid), + error)) + return FALSE; + } + + ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); + nm_utils_complete_generic( + nm_device_get_platform(device), + connection, + NM_SETTING_WIRELESS_SETTING_NAME, + existing_connections, + ssid_utf8, + ssid_utf8, + NULL, + nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device), + TRUE); + + if (hidden) + g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); + + return TRUE; +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSupplicantInterfaceState supplicant_state; + + if (!priv->enabled) + return FALSE; + + if (!priv->sup_iface) + return FALSE; + + supplicant_state = nm_supplicant_interface_get_state(priv->sup_iface); + if (supplicant_state <= NM_SUPPLICANT_INTERFACE_STATE_STARTING + || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) + return FALSE; + + return TRUE; +} + +static gboolean +get_autoconnect_allowed(NMDevice *device) +{ + return !NM_DEVICE_WIFI_GET_PRIVATE(NM_DEVICE_WIFI(device))->scan_is_scanning; +} + +static gboolean +can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMConnection * connection; + NMSettingWireless * s_wifi; + NMWifiAP * ap; + const char * method6, *mode; + gboolean auto4, auto6; + + nm_assert(!specific_object || !*specific_object); + + if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class)->can_auto_connect(device, sett_conn, NULL)) + return FALSE; + + connection = nm_settings_connection_get_connection(sett_conn); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wifi, FALSE); + + /* Always allow autoconnect for AP and non-autoconf Ad-Hoc or Mesh */ + auto4 = nm_streq0(nm_utils_get_ip_config_method(connection, AF_INET), + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + method6 = nm_utils_get_ip_config_method(connection, AF_INET6); + auto6 = nm_streq0(method6, NM_SETTING_IP6_CONFIG_METHOD_AUTO) + || nm_streq0(method6, NM_SETTING_IP6_CONFIG_METHOD_DHCP); + + mode = nm_setting_wireless_get_mode(s_wifi); + + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) + return TRUE; + else if (!auto4 && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) + return TRUE; + else if (!auto4 && !auto6 && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_MESH)) + return TRUE; + + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + if (ap) { + /* All good; connection is usable */ + NM_SET_OUT(specific_object, g_strdup(nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)))); + return TRUE; + } + + return FALSE; +} + +const CList * +_nm_device_wifi_get_aps(NMDeviceWifi *self) +{ + return &NM_DEVICE_WIFI_GET_PRIVATE(self)->aps_lst_head; +} + +static void +_hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset) +{ + NMDevice * device = (NMDevice *) self; + NMDeviceWifiPrivate *priv; + guint32 now; + gboolean randomize; + + g_return_if_fail(NM_IS_DEVICE_WIFI(self)); + + if (nm_device_is_activating(device) || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) + return; + + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + randomize = nm_config_data_get_device_config_boolean( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS, + device, + TRUE, + TRUE); + + if (!randomize) { + /* expire the temporary MAC address used during scanning */ + priv->hw_addr_scan_expire = 0; + + if (do_reset) { + priv->scan_last_request_started_at_msec = G_MININT64; + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; + nm_device_hw_addr_reset(device, "scanning"); + } + return; + } + + now = nm_utils_get_monotonic_timestamp_sec(); + + if (now >= priv->hw_addr_scan_expire) { + gs_free char *generate_mac_address_mask = NULL; + gs_free char *hw_addr_scan = NULL; + + /* the random MAC address for scanning expires after a while. + * + * We don't bother with to update the MAC address exactly when + * it expires, instead on the next scan request, we will generate + * a new one.*/ + priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC; + + generate_mac_address_mask = nm_config_data_get_device_config( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK, + device, + NULL); + + priv->scan_last_request_started_at_msec = G_MININT64; + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; + hw_addr_scan = nm_utils_hw_addr_gen_random_eth(nm_device_get_initial_hw_address(device), + generate_mac_address_mask); + nm_device_hw_addr_set(device, hw_addr_scan, "scanning", TRUE); + } +} + +static GPtrArray * +ssids_options_to_ptrarray(GVariant *value, GError **error) +{ + gs_unref_ptrarray GPtrArray *ssids = NULL; + gsize num_ssids; + gsize i; + + nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aay"))); + + num_ssids = g_variant_n_children(value); + if (num_ssids > 32) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "too many SSIDs requested to scan"); + return NULL; + } + + if (num_ssids) { + ssids = g_ptr_array_new_full(num_ssids, (GDestroyNotify) g_bytes_unref); + for (i = 0; i < num_ssids; i++) { + gs_unref_variant GVariant *v = NULL; + gsize len; + const guint8 * bytes; + + v = g_variant_get_child_value(value, i); + bytes = g_variant_get_fixed_array(v, &len, sizeof(guint8)); + if (len > 32) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "SSID at index %d more than 32 bytes", + (int) i); + return NULL; + } + + g_ptr_array_add(ssids, g_bytes_new(bytes, len)); + } + } + + return g_steal_pointer(&ssids); +} + +GPtrArray * +nmtst_ssids_options_to_ptrarray(GVariant *value, GError **error) +{ + return ssids_options_to_ptrarray(value, error); +} + +static void +dbus_request_scan_cb(NMDevice * device, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *ssids = user_data; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + _scan_request_ssids_track(priv, ssids); + priv->scan_explicit_requested = TRUE; + _scan_kickoff(self); + g_dbus_method_invocation_return_value(context, NULL); +} + +void +_nm_device_wifi_request_scan(NMDeviceWifi * self, + GVariant * options, + GDBusMethodInvocation *invocation) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + gs_unref_ptrarray GPtrArray *ssids = NULL; + + if (options) { + gs_unref_variant GVariant *val = g_variant_lookup_value(options, "ssids", NULL); + + if (val) { + gs_free_error GError *ssid_error = NULL; + + if (!g_variant_is_of_type(val, G_VARIANT_TYPE("aay"))) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Invalid 'ssid' scan option"); + return; + } + + ssids = ssids_options_to_ptrarray(val, &ssid_error); + if (ssid_error) { + g_dbus_method_invocation_return_gerror(invocation, ssid_error); + return; + } + } + } + + if (!priv->enabled || !priv->sup_iface + || nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "Scanning not allowed while unavailable"); + return; + } + + nm_device_auth_request(device, + invocation, + NULL, + NM_AUTH_PERMISSION_WIFI_SCAN, + TRUE, + NULL, + dbus_request_scan_cb, + g_steal_pointer(&ssids)); +} + +static gboolean +hidden_filter_func(NMSettings *settings, NMSettingsConnection *set_con, gpointer user_data) +{ + NMConnection * connection = nm_settings_connection_get_connection(set_con); + NMSettingWireless *s_wifi; + + if (!nm_connection_is_type(connection, NM_SETTING_WIRELESS_SETTING_NAME)) + return FALSE; + s_wifi = nm_connection_get_setting_wireless(connection); + if (!s_wifi) + return FALSE; + if (nm_streq0(nm_setting_wireless_get_mode(s_wifi), NM_SETTING_WIRELESS_MODE_AP)) + return FALSE; + return nm_setting_wireless_get_hidden(s_wifi); +} + +static GPtrArray * +_scan_request_ssids_build_hidden(NMDeviceWifi *self, + gint64 now_msec, + gboolean * out_has_hidden_profiles) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids(priv->sup_iface); + gs_free NMSettingsConnection **connections = NULL; + gs_unref_ptrarray GPtrArray *ssids = NULL; + gs_unref_hashtable GHashTable *unique_ssids = NULL; + guint connections_len; + guint n_hidden; + guint i; + + NM_SET_OUT(out_has_hidden_profiles, FALSE); + + /* collect all pending explicit SSIDs. */ + ssids = _scan_request_ssids_fetch(priv, now_msec); + + if (max_scan_ssids == 0) { + /* no space. @ssids will be ignored. */ + return NULL; + } + + if (ssids) { + if (ssids->len < max_scan_ssids) { + /* Add wildcard SSID using a static wildcard SSID used for every scan */ + g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty())); + } + if (ssids->len >= max_scan_ssids) { + /* there is no more space. Use what we have. */ + g_ptr_array_set_size(ssids, max_scan_ssids); + return g_steal_pointer(&ssids); + } + } + + connections = nm_settings_get_connections_clone(nm_device_get_settings((NMDevice *) self), + &connections_len, + hidden_filter_func, + NULL, + NULL, + NULL); + if (!connections[0]) + return g_steal_pointer(&ssids); + + if (!ssids) { + ssids = g_ptr_array_new_full(max_scan_ssids, (GDestroyNotify) g_bytes_unref); + /* Add wildcard SSID using a static wildcard SSID used for every scan */ + g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty())); + } + + unique_ssids = g_hash_table_new(nm_gbytes_hash, nm_gbytes_equal); + for (i = 1; i < ssids->len; i++) { + if (!g_hash_table_add(unique_ssids, ssids->pdata[i])) + nm_assert_not_reached(); + } + + g_qsort_with_data(connections, + connections_len, + sizeof(NMSettingsConnection *), + nm_settings_connection_cmp_timestamp_p_with_data, + NULL); + + n_hidden = 0; + for (i = 0; i < connections_len; i++) { + NMSettingWireless *s_wifi; + GBytes * ssid; + + if (ssids->len >= max_scan_ssids) + break; + + if (n_hidden > 4) { + /* we allow at most 4 hidden profiles to be actively scanned. The + * reason is speed and to not disclose too many SSIDs. */ + break; + } + + s_wifi = nm_connection_get_setting_wireless( + nm_settings_connection_get_connection(connections[i])); + ssid = nm_setting_wireless_get_ssid(s_wifi); + + if (!g_hash_table_add(unique_ssids, ssid)) + continue; + + g_ptr_array_add(ssids, g_bytes_ref(ssid)); + n_hidden++; + } + + NM_SET_OUT(out_has_hidden_profiles, n_hidden > 0); + return g_steal_pointer(&ssids); +} + +static gboolean +_scan_request_delay_cb(gpointer user_data) +{ + NMDeviceWifi * self = user_data; + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_clear_g_source_inst(&priv->scan_request_delay_source); + + _LOGT_scan("scan request completed (after extra delay)"); + + _scan_notify_is_scanning(self); + return G_SOURCE_REMOVE; +} + +static void +_scan_supplicant_request_scan_cb(NMSupplicantInterface *supp_iface, + GCancellable * cancellable, + gpointer user_data) +{ + NMDeviceWifi * self; + NMDeviceWifiPrivate *priv; + + if (g_cancellable_is_cancelled(cancellable)) + return; + + self = user_data; + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + _LOGT_scan("scan request completed (D-Bus request)"); + + /* we just completed a scan request, but possibly the supplicant's state is not yet toggled + * to "scanning". That means, our internal scanning state "priv->scan_is_scanning" would already + * flip to idle, while in a moment the supplicant would toggle the state again. + * + * Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */ + nm_clear_g_source_inst(&priv->scan_request_delay_source); + priv->scan_request_delay_source = + nm_g_source_attach(nm_g_timeout_source_new(SCAN_EXTRA_DELAY_MSEC, + G_PRIORITY_DEFAULT, + _scan_request_delay_cb, + self, + NULL), + NULL); + + g_clear_object(&priv->scan_request_cancellable); + _scan_notify_is_scanning(self); +} + +static gboolean +_scan_kickoff_timeout_cb(gpointer user_data) +{ + NMDeviceWifi * self = user_data; + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + priv->scan_kickoff_timeout_id = 0; + _scan_kickoff(self); + return G_SOURCE_REMOVE; +} + +static void +_scan_kickoff(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *ssids = NULL; + gboolean is_explict = FALSE; + NMDeviceState device_state; + gboolean has_hidden_profiles; + gint64 now_msec; + gint64 ratelimit_duration_msec; + + if (!priv->sup_iface) { + _LOGT_scan("kickoff: don't scan (has no supplicant interface)"); + return; + } + + if (priv->scan_request_cancellable) { + _LOGT_scan("kickoff: don't scan (has scan_request_cancellable)"); + /* We are currently waiting for a scan request to complete. Wait longer. */ + return; + } + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + _scan_request_ssids_remove_all(priv, now_msec, G_MAXUINT); + + device_state = nm_device_get_state(NM_DEVICE(self)); + if (device_state > NM_DEVICE_STATE_DISCONNECTED && device_state <= NM_DEVICE_STATE_ACTIVATED) { + /* while we are activated, we rate limit more. */ + ratelimit_duration_msec = 8000; + } else + ratelimit_duration_msec = 1500; + + if (priv->scan_last_request_started_at_msec + ratelimit_duration_msec > now_msec) { + _LOGT_scan( + "kickoff: don't scan (rate limited for another %d.%03d sec%s)", + (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) + / 1000), + (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) + % 1000), + !priv->scan_kickoff_timeout_id ? ", schedule timeout" : ""); + if (!priv->scan_kickoff_timeout_id + && (priv->scan_explicit_allowed || priv->scan_periodic_allowed)) { + priv->scan_kickoff_timeout_id = g_timeout_add(priv->scan_last_request_started_at_msec + + ratelimit_duration_msec - now_msec, + _scan_kickoff_timeout_cb, + self); + } + return; + } + + if (priv->scan_explicit_requested) { + if (!priv->scan_explicit_allowed) { + _LOGT_scan("kickoff: don't scan (explicit scan requested but not allowed)"); + return; + } + priv->scan_explicit_requested = FALSE; + is_explict = TRUE; + } else { + if (!priv->scan_periodic_allowed) { + _LOGT_scan("kickoff: don't scan (periodic scan currently not allowed)"); + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; + nm_clear_g_source(&priv->scan_kickoff_timeout_id); + return; + } + + nm_assert(priv->scan_explicit_allowed); + + if (now_msec < priv->scan_periodic_next_msec) { + _LOGT_scan("kickoff: don't scan (periodic scan waiting for another %d.%03d sec%s)", + (int) ((priv->scan_periodic_next_msec - now_msec) / 1000), + (int) ((priv->scan_periodic_next_msec - now_msec) % 1000), + !priv->scan_kickoff_timeout_id ? ", schedule timeout" : ""); + if (!priv->scan_kickoff_timeout_id) { + priv->scan_kickoff_timeout_id = + g_timeout_add_seconds((priv->scan_periodic_next_msec - now_msec + 999) / 1000, + _scan_kickoff_timeout_cb, + self); + } + return; + } + + priv->scan_periodic_interval_sec = + NM_CLAMP(((int) priv->scan_periodic_interval_sec) * 3 / 2, + SCAN_INTERVAL_SEC_MIN, + SCAN_INTERVAL_SEC_MAX); + priv->scan_periodic_next_msec = now_msec + 1000 * priv->scan_periodic_interval_sec; + } + + ssids = _scan_request_ssids_build_hidden(self, now_msec, &has_hidden_profiles); + if (has_hidden_profiles) { + if (priv->hidden_probe_scan_warn) { + priv->hidden_probe_scan_warn = FALSE; + _LOGW(LOGD_WIFI, + "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. " + "This makes you trackable"); + } + } else if (!is_explict) + priv->hidden_probe_scan_warn = TRUE; + + if (_LOGD_ENABLED(LOGD_WIFI)) { + gs_free char *ssids_str = NULL; + guint ssids_len = 0; + + if (ssids) { + gs_strfreev char **strv = NULL; + guint i; + + strv = g_new(char *, ssids->len + 1u); + for (i = 0; i < ssids->len; i++) + strv[i] = _nm_utils_ssid_to_string(ssids->pdata[i]); + strv[i] = NULL; + + nm_assert(ssids->len > 0); + nm_assert(ssids->len == NM_PTRARRAY_LEN(strv)); + + ssids_str = g_strjoinv(", ", strv); + ssids_len = ssids->len; + } + _LOGD(LOGD_WIFI, + "wifi-scan: start %s scan (%u SSIDs to probe scan%s%s%s)", + is_explict ? "explicit" : "periodic", + ssids_len, + NM_PRINT_FMT_QUOTED(ssids_str, " [", ssids_str, "]", "")); + } + + priv->scan_last_request_started_at_msec = now_msec; + + if (is_explict) + _LOGT_scan("kickoff: explicit scan starting"); + else { + _LOGT_scan("kickoff: periodic scan starting (next scan is scheduled in %d.%03d sec)", + (int) ((priv->scan_periodic_next_msec - now_msec) / 1000), + (int) ((priv->scan_periodic_next_msec - now_msec) % 1000)); + } + + _hw_addr_set_scanning(self, FALSE); + + priv->scan_request_cancellable = g_cancellable_new(); + nm_supplicant_interface_request_scan(priv->sup_iface, + ssids ? (GBytes *const *) ssids->pdata : NULL, + ssids ? ssids->len : 0u, + priv->scan_request_cancellable, + _scan_supplicant_request_scan_cb, + self); + + /* It's OK to call _scan_notify_is_scanning() again. They mutually call each other, + * but _scan_kickoff() sets "priv->scan_request_cancellable" which will stop + * them from recursing indefinitely. */ + _scan_notify_is_scanning(self); +} + +/**************************************************************************** + * WPA Supplicant control stuff + * + */ + +static gboolean +ap_list_dump(gpointer user_data) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + priv->ap_dump_id = 0; + + if (_LOGD_ENABLED(LOGD_WIFI_SCAN)) { + NMWifiAP *ap; + gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + char str_buf[100]; + + _LOGD(LOGD_WIFI_SCAN, + "APs: [now:%u.%03u, last:%s]", + (guint)(now_msec / NM_UTILS_MSEC_PER_SEC), + (guint)(now_msec % NM_UTILS_MSEC_PER_SEC), + priv->scan_last_complete_msec > 0 + ? nm_sprintf_buf(str_buf, + "%u.%03u", + (guint)(priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC), + (guint)(priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC)) + : "-1"); + c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst) + _ap_dump(self, LOGL_DEBUG, ap, "dump", now_msec); + } + return G_SOURCE_REMOVE; +} + +static void +schedule_ap_list_dump(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (!priv->ap_dump_id && _LOGD_ENABLED(LOGD_WIFI_SCAN)) + priv->ap_dump_id = g_timeout_add_seconds(1, ap_list_dump, self); +} + +static void +try_fill_ssid_for_hidden_ap(NMDeviceWifi *self, NMWifiAP *ap) +{ + const char * bssid; + NMSettingsConnection *const *connections; + guint i; + + g_return_if_fail(nm_wifi_ap_get_ssid(ap) == NULL); + + bssid = nm_wifi_ap_get_address(ap); + g_return_if_fail(bssid); + + /* Look for this AP's BSSID in the seen-bssids list of a connection, + * and if a match is found, copy over the SSID */ + connections = nm_settings_get_connections(nm_device_get_settings((NMDevice *) self), NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMSettingWireless * s_wifi; + + if (!nm_settings_connection_has_seen_bssid(sett_conn, bssid)) + continue; + s_wifi = + nm_connection_get_setting_wireless(nm_settings_connection_get_connection(sett_conn)); + if (!s_wifi) + continue; + + nm_wifi_ap_set_ssid(ap, nm_setting_wireless_get_ssid(s_wifi)); + break; + } +} + +static void +supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface, + NMSupplicantBssInfo * bss_info, + gboolean is_present, + NMDeviceWifi * self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMWifiAP * found_ap; + GBytes * ssid; + + found_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, bss_info->bss_path); + + if (!is_present) { + if (!found_ap) + return; + if (found_ap == priv->current_ap) { + /* The current AP cannot be removed (to prevent NM indicating that + * it is connected, but to nothing), but it must be removed later + * when the current AP is changed or cleared. Set 'fake' to + * indicate that this AP is now unknown to the supplicant. + */ + if (nm_wifi_ap_set_fake(found_ap, TRUE)) + _ap_dump(self, LOGL_DEBUG, found_ap, "updated", 0); + } else { + ap_add_remove(self, FALSE, found_ap, TRUE); + schedule_ap_list_dump(self); + } + return; + } + + if (found_ap) { + if (!nm_wifi_ap_update_from_properties(found_ap, bss_info)) + return; + _ap_dump(self, LOGL_DEBUG, found_ap, "updated", 0); + } else { + gs_unref_object NMWifiAP *ap = NULL; + + if (!bss_info->bssid_valid) { + /* We failed to initialize the info about the AP. This can + * happen due to an error in the D-Bus communication. In this case + * we ignore the info. */ + return; + } + + ap = nm_wifi_ap_new_from_properties(bss_info); + + /* Let the manager try to fill in the SSID from seen-bssids lists */ + ssid = nm_wifi_ap_get_ssid(ap); + if (!ssid || _nm_utils_is_empty_ssid(ssid)) { + /* Try to fill the SSID from the AP database */ + try_fill_ssid_for_hidden_ap(self, ap); + + ssid = nm_wifi_ap_get_ssid(ap); + if (ssid && !_nm_utils_is_empty_ssid(ssid)) { + gs_free char *s = NULL; + + /* Yay, matched it, no longer treat as hidden */ + _LOGD(LOGD_WIFI, + "matched hidden AP %s => %s", + nm_wifi_ap_get_address(ap), + (s = _nm_utils_ssid_to_string(ssid))); + } else { + /* Didn't have an entry for this AP in the database */ + _LOGD(LOGD_WIFI, "failed to match hidden AP %s", nm_wifi_ap_get_address(ap)); + } + } + + ap_add_remove(self, TRUE, ap, TRUE); + } + + /* Update the current AP if the supplicant notified a current BSS change + * before it sent the current BSS's scan result. + */ + if (nm_supplicant_interface_get_current_bss(iface) == bss_info->bss_path) + supplicant_iface_notify_current_bss(priv->sup_iface, NULL, self); + + schedule_ap_list_dump(self); +} + +static void +cleanup_association_attempt(NMDeviceWifi *self, gboolean disconnect) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_clear_g_source(&priv->sup_timeout_id); + nm_clear_g_source(&priv->link_timeout_id); + nm_clear_g_source(&priv->wps_timeout_id); + if (disconnect && priv->sup_iface) + nm_supplicant_interface_disconnect(priv->sup_iface); +} + +static void +cleanup_supplicant_failures(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_clear_g_source(&priv->reacquire_iface_id); + priv->failed_iface_count = 0; +} + +static void +wifi_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMDevice * device = user_data; + NMDeviceWifi * self = user_data; + NMDeviceWifiPrivate *priv; + + g_return_if_fail(NM_IS_DEVICE_WIFI(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + g_return_if_fail(priv->wifi_secrets_id == call_id); + + priv->wifi_secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_return_if_fail(req == nm_device_get_act_request(device)); + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail(nm_act_request_get_settings_connection(req) == connection); + + if (error) { + _LOGW(LOGD_WIFI, "no secrets: %s", error->message); + + /* Even if WPS is still pending, let's abort the activation when the secret + * request returns. + * + * This means, a user can only effectively use WPS when also running a secret + * agent, and pressing the push button while being prompted for the password. + * Note, that in the secret prompt the user can see that WPS is in progress + * (via the NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE flag). + * + * Previously, WPS was not cancelled when the secret request returns. + * Note that in common use-cases WPS is enabled in the connection profile + * but it won't succeed (because it's disabled in the AP or because the + * user is not prepared to press the push button). + * That means for example, during boot we would try to autoconnect with WPS. + * At that point, there is no secret-agent running, and WPS is pending for + * full 30 seconds. If in the meantime a secret agent registers (because + * of logging into the DE), the profile is still busy waiting for WPS to time + * out. Only after that delay, autoconnect starts again (note that autoconnect gets + * not blocked in this case, because a secret agent registered in the meantime). + * + * It seems wrong to continue doing WPS if the user is not aware + * that WPS is ongoing. The user is required to perform an action (push button), + * and must be told via the secret prompt. + * If no secret-agent is running, if the user cancels the secret-request, or any + * other error to obtain secrets, the user apparently does not want WPS either. + */ + nm_clear_g_source(&priv->wps_timeout_id); + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +wifi_secrets_cancel(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + if (priv->wifi_secrets_id) + nm_act_request_cancel_secrets(NULL, priv->wifi_secrets_id); + nm_assert(!priv->wifi_secrets_id); +} + +static void +supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface, + GVariant * credentials, + NMDeviceWifi * self) +{ + NMActRequest * req; + gs_unref_variant GVariant *val_key = NULL; + gs_unref_variant GVariant *secrets = NULL; + gs_free_error GError *error = NULL; + const char * array; + gsize psk_len = 0; + + if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_NEED_AUTH) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials"); + return; + } + + _LOGI(LOGD_DEVICE | LOGD_WIFI, "WPS: Updating the connection with credentials"); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + val_key = g_variant_lookup_value(credentials, "Key", G_VARIANT_TYPE_BYTESTRING); + if (val_key) { + char psk[64]; + + array = g_variant_get_fixed_array(val_key, &psk_len, 1); + if (psk_len >= 8 && psk_len <= 63) { + memcpy(psk, array, psk_len); + psk[psk_len] = '\0'; + if (g_utf8_validate(psk, psk_len, NULL)) { + secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PSK, + psk); + g_variant_ref_sink(secrets); + } + } + if (!secrets) + _LOGW(LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK"); + } + + if (!secrets) + return; + + if (!nm_settings_connection_new_secrets(nm_act_request_get_settings_connection(req), + nm_act_request_get_applied_connection(req), + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, + &error)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "WPS: Could not update the connection with credentials: %s", + error->message); + return; + } + + wifi_secrets_cancel(self); + nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE); +} + +static gboolean +wps_timeout_cb(gpointer user_data) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + priv->wps_timeout_id = 0; + if (!priv->wifi_secrets_id) { + /* Fail only if the secrets are not being requested. */ + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } + + return G_SOURCE_REMOVE; +} + +static void +wifi_secrets_get_secrets(NMDeviceWifi * self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMActRequest * req; + + wifi_secrets_cancel(self); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + priv->wifi_secrets_id = + nm_act_request_get_secrets(req, TRUE, setting_name, flags, NULL, wifi_secrets_cb, self); + g_return_if_fail(priv->wifi_secrets_id); +} + +/* + * link_timeout_cb + * + * Called when the link to the access point has been down for a specified + * period of time. + */ +static gboolean +link_timeout_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + _LOGW(LOGD_WIFI, "link timed out."); + + priv->link_timeout_id = 0; + + /* Disconnect event while activated; the supplicant hasn't been able + * to reassociate within the timeout period, so the connection must + * fail. + */ + if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED) + return FALSE; + + set_current_ap(self, NULL, TRUE); + + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT + : NM_DEVICE_STATE_REASON_SSID_NOT_FOUND); + return FALSE; +} + +static gboolean +need_new_8021x_secrets(NMDeviceWifi * self, + NMSupplicantInterfaceState old_state, + const char ** setting_name) +{ + NMSetting8021x * s_8021x; + NMSettingWirelessSecurity *s_wsec; + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + NMConnection * connection; + + g_return_val_if_fail(setting_name, FALSE); + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + g_return_val_if_fail(connection != NULL, FALSE); + + /* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's + * attempting to authenticate with the AP. + */ + if (old_state != NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED) + return FALSE; + + /* If it's an 802.1x or LEAP connection with "always ask"/unsaved secrets + * then we need to ask again because it might be an OTP token and the PIN + * may have changed. + */ + + s_8021x = nm_connection_get_setting_802_1x(connection); + if (s_8021x) { + if (!nm_setting_get_secret_flags(NM_SETTING(s_8021x), + NM_SETTING_802_1X_PASSWORD, + &secret_flags, + NULL)) + g_assert_not_reached(); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + *setting_name = NM_SETTING_802_1X_SETTING_NAME; + return *setting_name ? TRUE : FALSE; + } + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (s_wsec) { + if (!nm_setting_get_secret_flags(NM_SETTING(s_wsec), + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + &secret_flags, + NULL)) + g_assert_not_reached(); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + return *setting_name ? TRUE : FALSE; + } + + /* Not a LEAP or 802.1x connection */ + return FALSE; +} + +static gboolean +need_new_wpa_psk(NMDeviceWifi * self, + NMSupplicantInterfaceState old_state, + int disconnect_reason, + const char ** setting_name) +{ + NMSettingWirelessSecurity *s_wsec; + NMConnection * connection; + const char * key_mgmt = NULL; + + g_return_val_if_fail(setting_name, FALSE); + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + g_return_val_if_fail(connection, FALSE); + + /* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */ + if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE) + return FALSE; + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (s_wsec) + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + + if (g_strcmp0(key_mgmt, "wpa-psk") == 0) { +/* -4 (locally-generated WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY) usually + * means the driver missed beacons from the AP. This usually happens + * due to driver bugs or faulty power-save management. It doesn't + * indicate that the PSK is wrong. + */ +#define LOCAL_WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY -4 + if (disconnect_reason == LOCAL_WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY) + return FALSE; + + *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + return TRUE; + } + + /* Not a WPA-PSK connection */ + return FALSE; +} + +static gboolean +handle_8021x_or_psk_auth_fail(NMDeviceWifi * self, + NMSupplicantInterfaceState new_state, + NMSupplicantInterfaceState old_state, + int disconnect_reason) +{ + NMDevice * device = NM_DEVICE(self); + NMActRequest *req; + const char * setting_name = NULL; + gboolean handled = FALSE; + + g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_val_if_fail(req != NULL, FALSE); + + if (need_new_8021x_secrets(self, old_state, &setting_name) + || need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) { + nm_act_request_clear_secrets(req); + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) disconnected during association, asking for new key"); + + cleanup_association_attempt(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + wifi_secrets_get_secrets(self, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); + handled = TRUE; + } + + return handled; +} + +static gboolean +reacquire_interface_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + priv->reacquire_iface_id = 0; + priv->failed_iface_count++; + + _LOGW(LOGD_WIFI, "re-acquiring supplicant interface (#%d).", priv->failed_iface_count); + + if (!priv->sup_iface) + supplicant_interface_acquire(self); + + return G_SOURCE_REMOVE; +} + +static void +supplicant_iface_state_down(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + cleanup_association_attempt(self, FALSE); + + /* If the device is already in UNAVAILABLE state then the state change + * is a NOP and the interface won't be re-acquired in the device state + * change handler. So ensure we have a new one here so that we're + * ready if the supplicant comes back. + */ + supplicant_interface_release(self); + if (priv->failed_iface_count < 5) + priv->reacquire_iface_id = g_timeout_add_seconds(10, reacquire_interface_cb, self); + else + _LOGI(LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up"); +} + +static void +supplicant_iface_state(NMDeviceWifi * self, + NMSupplicantInterfaceState new_state, + NMSupplicantInterfaceState old_state, + int disconnect_reason, + gboolean is_real_signal) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState devstate; + gboolean scanning; + gboolean scan_changed; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "supplicant interface state: %s -> %s%s", + nm_supplicant_interface_state_to_string(old_state), + nm_supplicant_interface_state_to_string(new_state), + is_real_signal ? "" : " (simulated signal)"); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + supplicant_iface_state_down(self); + goto out; + } + + devstate = nm_device_get_state(device); + scanning = nm_supplicant_interface_get_scanning(priv->sup_iface); + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + _LOGD(LOGD_WIFI, "supplicant ready"); + nm_device_queue_recheck_available(NM_DEVICE(device), + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; + } + + /* In these states we know the supplicant is actually talking to something */ + if (new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING + && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) + priv->ssid_found = TRUE; + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) + recheck_p2p_availability(self); + + switch (new_state) { + case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED: + nm_clear_g_source(&priv->sup_timeout_id); + nm_clear_g_source(&priv->link_timeout_id); + nm_clear_g_source(&priv->wps_timeout_id); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (devstate == NM_DEVICE_STATE_CONFIG) { + NMSettingWireless *s_wifi; + GBytes * ssid; + gs_free char * ssid_str = NULL; + + s_wifi = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS); + + g_return_if_fail(s_wifi); + + ssid = nm_setting_wireless_get_ssid(s_wifi); + g_return_if_fail(ssid); + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. %s %s", + priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot" + : "Connected to wireless network", + (ssid_str = _nm_utils_ssid_to_string(ssid))); + nm_device_activate_schedule_stage3_ip_config_start(device); + } else if (devstate == NM_DEVICE_STATE_ACTIVATED) + periodic_update(self); + break; + case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED: + if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating(device)) { + /* Disconnect of an 802.1x/LEAP connection during authentication, + * or disconnect of a WPA-PSK connection during the 4-way handshake, + * often means secrets are wrong. Not always the case, but until we + * have more information from wpa_supplicant about why the + * disconnect happened this is the best we can do. + */ + if (handle_8021x_or_psk_auth_fail(self, new_state, old_state, disconnect_reason)) + break; + } + + /* Otherwise, it might be a stupid driver or some transient error, so + * let the supplicant try to reconnect a few more times. Give it more + * time if a scan is in progress since the link might be dropped during + * the scan but will be re-established when the scan is done. + */ + if (devstate == NM_DEVICE_STATE_ACTIVATED) { + if (priv->link_timeout_id == 0) { + priv->link_timeout_id = + g_timeout_add_seconds(scanning ? 30 : 15, link_timeout_cb, self); + priv->ssid_found = FALSE; + } + } + break; + case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE: + /* we would clear _scan_has_pending_action_set() and trigger a new scan. + * However, we don't want to cancel the current pending action, so force + * a new scan request. */ + break; + default: + break; + } + +out: + scan_changed = _scan_notify_allowed(self, NM_TERNARY_FALSE); + scan_changed |= _scan_notify_is_scanning(self); + if (scan_changed) + _scan_kickoff(self); + + if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) + nm_device_remove_pending_action(device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE); +} + +static void +supplicant_iface_state_cb(NMSupplicantInterface *iface, + int new_state_i, + int old_state_i, + int disconnect_reason, + gpointer user_data) +{ + supplicant_iface_state(user_data, new_state_i, old_state_i, disconnect_reason, TRUE); +} + +static void +supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI(user_data); + NMDevice * device = NM_DEVICE(self); + + if (error && !nm_utils_error_is_cancelled_or_disposing(error) + && nm_device_is_activating(device)) { + cleanup_association_attempt(self, TRUE); + nm_device_queue_state(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } +} + +static void +supplicant_iface_notify_current_bss(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceWifi * self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMRefString * current_bss; + NMWifiAP * new_ap = NULL; + + current_bss = nm_supplicant_interface_get_current_bss(iface); + if (current_bss) + new_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, current_bss); + + if (new_ap != priv->current_ap) { + const char * new_bssid = NULL; + GBytes * new_ssid = NULL; + const char * old_bssid = NULL; + GBytes * old_ssid = NULL; + gs_free char *new_ssid_s = NULL; + gs_free char *old_ssid_s = NULL; + + /* Don't ever replace a "fake" current AP if we don't know about the + * supplicant's current BSS yet. It'll get replaced when we receive + * the current BSS's scan result. + */ + if (new_ap == NULL && nm_wifi_ap_get_fake(priv->current_ap)) + return; + + if (new_ap) { + new_bssid = nm_wifi_ap_get_address(new_ap); + new_ssid = nm_wifi_ap_get_ssid(new_ap); + } + + if (priv->current_ap) { + old_bssid = nm_wifi_ap_get_address(priv->current_ap); + old_ssid = nm_wifi_ap_get_ssid(priv->current_ap); + } + + _LOGD(LOGD_WIFI, + "roamed from BSSID %s (%s) to %s (%s)", + old_bssid ?: "(none)", + (old_ssid_s = _nm_utils_ssid_to_string(old_ssid)), + new_bssid ?: "(none)", + (new_ssid_s = _nm_utils_ssid_to_string(new_ssid))); + + if (new_bssid) { + /* The new AP could be in a different layer 3 network + * and so the old DHCP lease could be no longer valid. + * Also, some APs (e.g. Cisco) can be configured to drop + * all traffic until DHCP completes. To support such + * cases, renew the lease when roaming to a new AP. */ + nm_device_update_dynamic_ip_setup(NM_DEVICE(self)); + } + + set_current_ap(self, new_ap, TRUE); + } +} + +/* We bind the existence of the P2P device to a wifi device that is being + * managed by NetworkManager and is capable of P2P operation. + * Note that some care must be taken here, because we don't want to re-create + * the device every time the supplicant interface is destroyed (e.g. due to + * a suspend/resume cycle). + * Therefore, this function will be called when a change in the P2P capability + * is detected and the supplicant interface has been initialised. + */ +static void +recheck_p2p_availability(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gboolean p2p_available; + + g_object_get(priv->sup_iface, NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available, NULL); + + if (p2p_available && !priv->p2p_device) { + gs_free char *iface_name = NULL; + + /* Create a P2P device. "p2p-dev-" is the same prefix as chosen by + * wpa_supplicant internally. + */ + iface_name = g_strconcat("p2p-dev-", nm_device_get_iface(NM_DEVICE(self)), NULL); + + priv->p2p_device = nm_device_wifi_p2p_new(iface_name); + + nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface); + + g_signal_emit(self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device); + g_object_add_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device); + g_object_unref(priv->p2p_device); + return; + } + + if (p2p_available && priv->p2p_device) { + nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface); + return; + } + + if (!p2p_available && priv->p2p_device) { + /* Destroy the P2P device. */ + g_object_remove_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device); + nm_device_wifi_p2p_remove(g_steal_pointer(&priv->p2p_device)); + return; + } +} + +static void +supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface, + GParamSpec * pspec, + NMDeviceWifi * self) +{ + if (nm_supplicant_interface_get_state(iface) > NM_SUPPLICANT_INTERFACE_STATE_STARTING) + recheck_p2p_availability(self); +} + +static gboolean +handle_auth_or_fail(NMDeviceWifi *self, NMActRequest *req, gboolean new_secrets) +{ + NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + const char * setting_name; + NMConnection * applied_connection; + NMSettingWirelessSecurity * s_wsec; + const char * bssid = NULL; + NM80211ApFlags ap_flags; + NMSettingWirelessSecurityWpsMethod wps_method; + const char * type; + NMSecretAgentGetSecretsFlags get_secret_flags = + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + g_return_val_if_fail(NM_IS_DEVICE_WIFI(self), FALSE); + + if (!req) { + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_val_if_fail(req, FALSE); + } + + if (!nm_device_auth_retries_try_next(NM_DEVICE(self))) + return FALSE; + + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); + + applied_connection = nm_act_request_get_applied_connection(req); + s_wsec = nm_connection_get_setting_wireless_security(applied_connection); + wps_method = nm_setting_wireless_security_get_wps_method(s_wsec); + + /* Negotiate the WPS method */ + if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT) + wps_method = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO; + + if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO && priv->current_ap) { + /* Determine the method to use from AP capabilities. */ + ap_flags = nm_wifi_ap_get_flags(priv->current_ap); + if (ap_flags & NM_802_11_AP_FLAGS_WPS_PBC) + wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC; + if (ap_flags & NM_802_11_AP_FLAGS_WPS_PIN) + wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN; + if (ap_flags & NM_802_11_AP_FLAGS_WPS + && wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO) { + /* The AP doesn't specify which methods are supported. Allow all. */ + wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC; + wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN; + } + } + + if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC) { + get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE; + type = "pbc"; + } else if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) { + type = "pin"; + } else + type = NULL; + + if (type) { + priv->wps_timeout_id = g_timeout_add_seconds(30, wps_timeout_cb, self); + if (priv->current_ap) + bssid = nm_wifi_ap_get_address(priv->current_ap); + nm_supplicant_interface_enroll_wps(priv->sup_iface, type, bssid, NULL); + } + + nm_act_request_clear_secrets(req); + setting_name = nm_connection_need_secrets(applied_connection, NULL); + if (!setting_name) { + _LOGW(LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + return FALSE; + } + + if (new_secrets) + get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + wifi_secrets_get_secrets(self, setting_name, get_secret_flags); + return TRUE; +} + +/* + * supplicant_connection_timeout_cb + * + * Called when the supplicant has been unable to connect to an access point + * within a specified period of time. + */ +static gboolean +supplicant_connection_timeout_cb(gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceWifi * self = NM_DEVICE_WIFI(user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMActRequest * req; + NMConnection * connection; + + cleanup_association_attempt(self, TRUE); + + if (!nm_device_is_activating(device)) + return FALSE; + + /* Timed out waiting for a successful connection to the AP; if the AP's + * security requires network-side authentication (like WPA or 802.1x) + * and the connection attempt timed out then it's likely the authentication + * information (passwords, pin codes, etc) are wrong. + */ + + req = nm_device_get_act_request(device); + g_assert(req); + + connection = nm_act_request_get_applied_connection(req); + g_assert(connection); + + if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH, NM_802_11_MODE_AP)) { + /* In Ad-Hoc and AP modes there's nothing to check the encryption key + * (if any), so supplicant timeouts here are almost certainly the wifi + * driver being really stupid. + */ + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) %s network creation took too long, failing activation", + priv->mode == NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return FALSE; + } + + g_assert(priv->mode == NM_802_11_MODE_INFRA); + + if (priv->ssid_found && nm_connection_get_setting_wireless_security(connection)) { + guint64 timestamp = 0; + gboolean new_secrets = TRUE; + + /* Connection failed; either driver problems, the encryption key is + * wrong, or the passwords or certificates were wrong. + */ + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) association took too long"); + + /* Ask for new secrets only if we've never activated this connection + * before. If we've connected before, don't bother the user with + * dialogs, just retry or fail, and if we never connect the user can + * fix the password somewhere else. + */ + if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req), + ×tamp)) + new_secrets = !timestamp; + + if (handle_auth_or_fail(self, req, new_secrets)) + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets"); + else { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } + } else { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) association took too long, failing activation"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT + : NM_DEVICE_STATE_REASON_SSID_NOT_FOUND); + } + + return FALSE; +} + +static NMSupplicantConfig * +build_supplicant_config(NMDeviceWifi *self, + NMConnection *connection, + guint32 fixed_freq, + GError ** error) +{ + NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSupplicantConfig * config = NULL; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity * s_wireless_sec; + NMSettingWirelessSecurityPmf pmf; + NMSettingWirelessSecurityFils fils; + NMTernary ap_isolation; + + g_return_val_if_fail(priv->sup_iface, NULL); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wireless != NULL, NULL); + + config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface)); + + /* Warn if AP mode may not be supported */ + if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP) + && nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) + != NM_TERNARY_TRUE) { + _LOGW(LOGD_WIFI, "Supplicant may not support AP mode; connection may time out."); + } + + if (!nm_supplicant_config_add_setting_wireless(config, s_wireless, fixed_freq, error)) { + g_prefix_error(error, "802-11-wireless: "); + goto error; + } + + if (!nm_supplicant_config_add_bgscan(config, connection, error)) { + g_prefix_error(error, "bgscan: "); + goto error; + } + + ap_isolation = nm_setting_wireless_get_ap_isolation(s_wireless); + if (ap_isolation == NM_TERNARY_DEFAULT) { + ap_isolation = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + "wifi.ap-isolation", + NM_DEVICE(self), + NM_TERNARY_FALSE, + NM_TERNARY_TRUE, + NM_TERNARY_FALSE); + } + nm_supplicant_config_set_ap_isolation(config, ap_isolation == NM_TERNARY_TRUE); + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + if (s_wireless_sec) { + NMSetting8021x *s_8021x; + const char * con_uuid = nm_connection_get_uuid(connection); + guint32 mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)), + nm_device_get_ifindex(NM_DEVICE(self))); + + g_assert(con_uuid); + + /* Configure PMF (802.11w) */ + pmf = nm_setting_wireless_security_get_pmf(s_wireless_sec); + if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) { + pmf = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + "wifi-sec.pmf", + NM_DEVICE(self), + NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE, + NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED, + NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL); + } + + /* Configure FILS (802.11ai) */ + fils = nm_setting_wireless_security_get_fils(s_wireless_sec); + if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) { + fils = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + "wifi-sec.fils", + NM_DEVICE(self), + NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE, + NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED, + NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL); + } + + s_8021x = nm_connection_get_setting_802_1x(connection); + if (!nm_supplicant_config_add_setting_wireless_security(config, + s_wireless_sec, + s_8021x, + con_uuid, + mtu, + pmf, + fils, + error)) { + g_prefix_error(error, "802-11-wireless-security: "); + goto error; + } + } else { + if (!nm_supplicant_config_add_no_security(config, error)) { + g_prefix_error(error, "unsecured-option: "); + goto error; + } + } + + return config; + +error: + g_object_unref(config); + return NULL; +} + +/*****************************************************************************/ + +static gboolean +wake_on_wlan_enable(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMSettingWirelessWakeOnWLan wowl; + NMSettingWireless * s_wireless; + + s_wireless = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS); + if (s_wireless) { + wowl = nm_setting_wireless_get_wake_on_wlan(s_wireless); + if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT) + goto found; + } + + wowl = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + "wifi.wake-on-wlan", + NM_DEVICE(self), + NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE, + G_MAXINT32, + NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT); + + if (NM_FLAGS_ANY(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) { + if (!nm_utils_is_power_of_two(wowl)) { + _LOGD(LOGD_WIFI, + "invalid default value %u for wake-on-wlan: " + "'default' and 'ignore' are exclusive flags", + (guint) wowl); + wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT; + } + } else if (NM_FLAGS_ANY(wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) { + _LOGD(LOGD_WIFI, "invalid default value %u for wake-on-wlan", (guint) wowl); + wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT; + } + if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT) + goto found; + + wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; +found: + if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) { + priv->wowlan_restore = wowl; + return TRUE; + } + + priv->wowlan_restore = + nm_platform_wifi_get_wake_on_wlan(NM_PLATFORM_GET, nm_device_get_ifindex(NM_DEVICE(self))); + + return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET, + nm_device_get_ifindex(NM_DEVICE(self)), + wowl); +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMWifiAP * ap = NULL; + gs_unref_object NMWifiAP *ap_fake = NULL; + NMActRequest * req; + NMConnection * connection; + NMSettingWireless * s_wireless; + const char * mode; + const char * ap_path; + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wireless, NM_ACT_STAGE_RETURN_FAILURE); + + nm_supplicant_interface_cancel_wps(priv->sup_iface); + + mode = nm_setting_wireless_get_mode(s_wireless); + if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0) + priv->mode = NM_802_11_MODE_INFRA; + else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) + priv->mode = NM_802_11_MODE_ADHOC; + else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) { + priv->mode = NM_802_11_MODE_AP; + + /* Scanning not done in AP mode; clear the scan list */ + remove_all_aps(self); + } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) + priv->mode = NM_802_11_MODE_MESH; + _notify(self, PROP_MODE); + + /* expire the temporary MAC address used during scanning */ + priv->hw_addr_scan_expire = 0; + + /* Set spoof MAC to the interface */ + if (!nm_device_hw_addr_set_cloned(device, connection, TRUE)) { + *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* AP and Mesh modes never use a specific object or existing scanned AP */ + if (!NM_IN_SET(priv->mode, NM_802_11_MODE_AP, NM_802_11_MODE_MESH)) { + ap_path = nm_active_connection_get_specific_object(NM_ACTIVE_CONNECTION(req)); + ap = ap_path ? nm_wifi_ap_lookup_for_device(NM_DEVICE(self), ap_path) : NULL; + } + if (!ap) + ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); + + if (!ap) { + /* If the user is trying to connect to an AP that NM doesn't yet know about + * (hidden network or something), starting a Hotspot or joining a Mesh, + * create a fake APfrom the security settings in the connection. This "fake" + * AP gets used until the real one is found in the scan list (Ad-Hoc or Hidden), + * or until the device is deactivated (Hotspot). + */ + ap_fake = nm_wifi_ap_new_fake_from_connection(connection); + if (!ap_fake) + g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE); + + if (nm_wifi_ap_is_hotspot(ap_fake)) + nm_wifi_ap_set_address(ap_fake, nm_device_get_hw_address(device)); + + g_object_freeze_notify(G_OBJECT(self)); + ap_add_remove(self, TRUE, ap_fake, TRUE); + g_object_thaw_notify(G_OBJECT(self)); + ap = ap_fake; + } + + _scan_notify_allowed(self, NM_TERNARY_DEFAULT); + + set_current_ap(self, ap, FALSE); + nm_active_connection_set_specific_object(NM_ACTIVE_CONNECTION(req), + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap))); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap) +{ + NMDevice * device = NM_DEVICE(self); + const char * band = nm_setting_wireless_get_band(s_wifi); + const guint32 a_freqs[] = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0}; + const guint32 bg_freqs[] = {2412, 2437, 2462, 2472, 0}; + guint32 freq = 0; + + g_assert(ap); + + if (nm_wifi_ap_get_freq(ap)) + return; + + if (g_strcmp0(band, "a") == 0) + freq = nm_platform_wifi_find_frequency(nm_device_get_platform(device), + nm_device_get_ifindex(device), + a_freqs); + else + freq = nm_platform_wifi_find_frequency(nm_device_get_platform(device), + nm_device_get_ifindex(device), + bg_freqs); + + if (!freq) + freq = (g_strcmp0(band, "a") == 0) ? 5180 : 2462; + + if (nm_wifi_ap_set_freq(ap, freq)) + _ap_dump(self, LOGL_DEBUG, ap, "updated", 0); +} + +static void +set_powersave(NMDevice *device) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMSettingWireless * s_wireless; + NMSettingWirelessPowersave val; + + s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + + g_return_if_fail(s_wireless); + + val = nm_setting_wireless_get_powersave(s_wireless); + if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) { + val = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + "wifi.powersave", + device, + NM_SETTING_WIRELESS_POWERSAVE_IGNORE, + NM_SETTING_WIRELESS_POWERSAVE_ENABLE, + NM_SETTING_WIRELESS_POWERSAVE_IGNORE); + } + + _LOGT(LOGD_WIFI, "powersave is set to %u", (unsigned) val); + + if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE) + return; + + nm_platform_wifi_set_powersave(nm_device_get_platform(device), + nm_device_get_ifindex(device), + val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE); +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gs_unref_object NMSupplicantConfig *config = NULL; + NM80211Mode ap_mode; + NMActRequest * req; + NMWifiAP * ap; + NMConnection * connection; + const char * setting_name; + NMSettingWireless * s_wireless; + GError * error = NULL; + guint timeout; + NMActRequest * request; + NMActiveConnection * master_ac; + NMDevice * master; + + nm_clear_g_source(&priv->sup_timeout_id); + nm_clear_g_source(&priv->link_timeout_id); + nm_clear_g_source(&priv->wps_timeout_id); + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + ap = priv->current_ap; + if (!ap) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + + ap_mode = nm_wifi_ap_get_mode(ap); + + connection = nm_act_request_get_applied_connection(req); + s_wireless = nm_connection_get_setting_wireless(connection); + nm_assert(s_wireless); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets(connection, NULL); + if (setting_name) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) access point '%s' has security, but secrets are required.", + nm_connection_get_id(connection)); + + if (!handle_auth_or_fail(self, req, FALSE)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + goto out_fail; + } + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (!wake_on_wlan_enable(self)) + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN."); + + /* have secrets, or no secrets required */ + if (nm_connection_get_setting_wireless_security(connection)) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) connection '%s' has security, and secrets exist. No new secrets " + "needed.", + nm_connection_get_id(connection)); + } else { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) connection '%s' requires no security. No secrets needed.", + nm_connection_get_id(connection)); + } + + priv->ssid_found = FALSE; + + /* Supplicant requires an initial frequency for Ad-Hoc, Hotspot and Mesh; + * if the user didn't specify one and we didn't find an AP that matched + * the connection, just pick a frequency the device supports. + */ + if (NM_IN_SET(ap_mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH) || nm_wifi_ap_is_hotspot(ap)) + ensure_hotspot_frequency(self, s_wireless, ap); + + if (ap_mode == NM_802_11_MODE_INFRA) + set_powersave(device); + + /* Build up the supplicant configuration */ + config = build_supplicant_config(self, connection, nm_wifi_ap_get_freq(ap), &error); + if (!config) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) couldn't build wireless configuration: %s", + error->message); + g_clear_error(&error); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + goto out_fail; + } + + /* Tell the supplicant in which bridge the interface is */ + if ((request = nm_device_get_act_request(device)) + && (master_ac = nm_active_connection_get_master(NM_ACTIVE_CONNECTION(request))) + && (master = nm_active_connection_get_device(master_ac)) + && nm_device_get_device_type(master) == NM_DEVICE_TYPE_BRIDGE) { + nm_supplicant_interface_set_bridge(priv->sup_iface, nm_device_get_iface(master)); + } else + nm_supplicant_interface_set_bridge(priv->sup_iface, NULL); + + nm_supplicant_interface_assoc(priv->sup_iface, config, supplicant_iface_assoc_cb, self); + + /* Set up a timeout on the association attempt */ + timeout = nm_device_get_supplicant_timeout(NM_DEVICE(self)); + priv->sup_timeout_id = g_timeout_add_seconds(timeout, supplicant_connection_timeout_cb, self); + + if (!priv->periodic_update_id) + priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); + + /* We'll get stage3 started when the supplicant connects */ + return NM_ACT_STAGE_RETURN_POSTPONE; + +out_fail: + cleanup_association_attempt(self, TRUE); + wake_on_wlan_restore(self); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + gboolean indicate_addressing_running; + NMConnection *connection; + const char * method; + + connection = nm_device_get_applied_connection(device); + + method = nm_utils_get_ip_config_method(connection, addr_family); + if (addr_family == AF_INET) + indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + else { + indicate_addressing_running = NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP); + } + + if (indicate_addressing_running) + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device), + TRUE); + + return NM_DEVICE_CLASS(nm_device_wifi_parent_class) + ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason); +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + return nm_device_get_configured_mtu_from_connection(device, + NM_TYPE_SETTING_WIRELESS, + out_source); +} + +static gboolean +is_static_wep(NMConnection *connection) +{ + NMSettingWirelessSecurity *s_wsec; + const char * str; + + g_return_val_if_fail(connection != NULL, FALSE); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (!s_wsec) + return FALSE; + + str = nm_setting_wireless_security_get_key_mgmt(s_wsec); + if (g_strcmp0(str, "none") != 0) + return FALSE; + + str = nm_setting_wireless_security_get_auth_alg(s_wsec); + if (g_strcmp0(str, "leap") == 0) + return FALSE; + + return TRUE; +} + +static NMActStageReturn +act_stage4_ip_config_timeout(NMDevice * device, + int addr_family, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMConnection * connection; + NMSettingIPConfig * s_ip; + gboolean may_fail; + + connection = nm_device_get_applied_connection(device); + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + may_fail = nm_setting_ip_config_get_may_fail(s_ip); + + if (priv->mode == NM_802_11_MODE_AP) + goto call_parent; + + if (may_fail || !is_static_wep(connection)) { + /* Not static WEP or failure allowed; let superclass handle it */ + goto call_parent; + } + + /* If IP configuration times out and it's a static WEP connection, that + * usually means the WEP key is wrong. WEP's Open System auth mode has + * no provision for figuring out if the WEP key is wrong, so you just have + * to wait for DHCP to fail to figure it out. For all other Wi-Fi security + * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the + * connection would have failed before IP configuration. + * + * Activation failed, we must have bad encryption key */ + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) could not get IP configuration for connection '%s'.", + nm_connection_get_id(connection)); + + if (!handle_auth_or_fail(self, NULL, TRUE)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets"); + return NM_ACT_STAGE_RETURN_POSTPONE; + +call_parent: + return NM_DEVICE_CLASS(nm_device_wifi_parent_class) + ->act_stage4_ip_config_timeout(device, addr_family, out_failure_reason); +} + +static void +activation_success_handler(NMDevice *device) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + int ifindex = nm_device_get_ifindex(device); + NMActRequest * req; + + req = nm_device_get_act_request(device); + g_assert(req); + + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE); + + /* There should always be a current AP, either a fake one because we haven't + * seen a scan result for the activated AP yet, or a real one from the + * supplicant's scan list. + */ + g_warn_if_fail(priv->current_ap); + if (priv->current_ap) { + if (nm_wifi_ap_get_fake(priv->current_ap)) { + gboolean ap_changed = FALSE; + gboolean update_bssid = !nm_wifi_ap_get_address(priv->current_ap); + gboolean update_rate = !nm_wifi_ap_get_max_bitrate(priv->current_ap); + NMEtherAddr bssid; + guint32 rate; + + /* If the activation AP hasn't been seen by the supplicant in a scan + * yet, it will be "fake". This usually happens for Ad-Hoc and + * AP-mode connections. Fill in the details from the device itself + * until the supplicant sends the scan result. + */ + if (!nm_wifi_ap_get_freq(priv->current_ap)) + ap_changed |= nm_wifi_ap_set_freq( + priv->current_ap, + nm_platform_wifi_get_frequency(nm_device_get_platform(device), ifindex)); + + if ((update_bssid || update_rate) + && nm_platform_wifi_get_station(nm_device_get_platform(device), + ifindex, + update_bssid ? &bssid : NULL, + NULL, + update_rate ? &rate : NULL)) { + if (update_bssid && nm_ether_addr_is_valid(&bssid)) + ap_changed |= nm_wifi_ap_set_address_bin(priv->current_ap, &bssid); + if (update_rate) + ap_changed |= nm_wifi_ap_set_max_bitrate(priv->current_ap, rate); + } + + if (ap_changed) + _ap_dump(self, LOGL_DEBUG, priv->current_ap, "updated", 0); + } + + nm_active_connection_set_specific_object( + NM_ACTIVE_CONNECTION(req), + nm_dbus_object_get_path(NM_DBUS_OBJECT(priv->current_ap))); + } + + periodic_update(self); + + update_seen_bssids_cache(self, priv->current_ap); + + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + gboolean clear_aps = FALSE; + + if (new_state > NM_DEVICE_STATE_ACTIVATED) + wifi_secrets_cancel(self); + + if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) { + /* Clean up the supplicant interface because in these states the + * device cannot be used. + */ + supplicant_interface_release(self); + + nm_clear_g_source(&priv->periodic_update_id); + + cleanup_association_attempt(self, TRUE); + cleanup_supplicant_failures(self); + remove_all_aps(self); + } + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + clear_aps = TRUE; + break; + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device is enabled and the supplicant manager is ready, + * acquire a supplicant interface and transition to DISCONNECTED because + * the device is now ready to use. + */ + if (priv->enabled && (nm_device_get_firmware_missing(device) == FALSE)) { + if (!priv->sup_iface) + supplicant_interface_acquire(self); + } + clear_aps = TRUE; + break; + case NM_DEVICE_STATE_NEED_AUTH: + if (priv->sup_iface) + nm_supplicant_interface_disconnect(priv->sup_iface); + break; + case NM_DEVICE_STATE_IP_CHECK: + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ifindex(device), + FALSE); + break; + case NM_DEVICE_STATE_ACTIVATED: + activation_success_handler(device); + break; + case NM_DEVICE_STATE_FAILED: + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), + nm_device_get_ifindex(device), + FALSE); + break; + case NM_DEVICE_STATE_DISCONNECTED: + break; + default: + break; + } + + if (clear_aps) + remove_all_aps(self); + + _scan_notify_allowed(self, NM_TERNARY_DEFAULT); +} + +static gboolean +get_enabled(NMDevice *device) +{ + return NM_DEVICE_WIFI_GET_PRIVATE(device)->enabled; +} + +static void +set_enabled(NMDevice *device, gboolean enabled) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + NMDeviceState state; + + enabled = !!enabled; + + if (priv->enabled == enabled) + return; + + priv->enabled = enabled; + + _LOGD(LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled"); + + state = nm_device_get_state(NM_DEVICE(self)); + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + _LOGD(LOGD_WIFI, "(%s): device blocked by UNMANAGED state", enabled ? "enable" : "disable"); + return; + } + + if (enabled) { + gboolean no_firmware = FALSE; + + if (state != NM_DEVICE_STATE_UNAVAILABLE) + _LOGW(LOGD_CORE, "not in expected unavailable state!"); + + if (!nm_device_bring_up(NM_DEVICE(self), TRUE, &no_firmware)) { + _LOGD(LOGD_WIFI, "enable blocked by failure to bring device up"); + + if (no_firmware) + nm_device_set_firmware_missing(NM_DEVICE(device), TRUE); + else { + /* The device sucks, or the kernel was lying to us about the killswitch state */ + priv->enabled = FALSE; + } + return; + } + + /* Re-initialize the supplicant interface and wait for it to be ready */ + cleanup_supplicant_failures(self); + supplicant_interface_release(self); + supplicant_interface_acquire(self); + + _LOGD(LOGD_WIFI, "enable waiting on supplicant state"); + } else { + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_take_down(NM_DEVICE(self), TRUE); + } +} + +static gboolean +get_guessed_metered(NMDevice *device) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + return priv->current_ap && nm_wifi_ap_get_metered(priv->current_ap); +} + +static gboolean +can_reapply_change(NMDevice * device, + const char *setting_name, + NMSetting * s_old, + NMSetting * s_new, + GHashTable *diffs, + GError ** error) +{ + NMDeviceClass *device_class; + + /* Only handle wireless setting here, delegate other settings to parent class */ + if (nm_streq(setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys( + diffs, + NM_SETTING_WIRELESS_SETTING_NAME, + error, + NM_SETTING_WIRELESS_SEEN_BSSIDS, /* ignored */ + NM_SETTING_WIRELESS_MTU, /* reapplied with IP config */ + NM_SETTING_WIRELESS_WAKE_ON_WLAN); + } + + device_class = NM_DEVICE_CLASS(nm_device_wifi_parent_class); + return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); +} + +static void +reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI(device); + NMDeviceState state = nm_device_get_state(device); + + NM_DEVICE_CLASS(nm_device_wifi_parent_class)->reapply_connection(device, con_old, con_new); + + _LOGD(LOGD_DEVICE, "reapplying wireless settings"); + + if (state >= NM_DEVICE_STATE_CONFIG && !wake_on_wlan_enable(self)) + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN."); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + const char ** list; + + switch (prop_id) { + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_BITRATE: + g_value_set_uint(value, priv->rate); + break; + case PROP_CAPABILITIES: + g_value_set_uint(value, priv->capabilities); + break; + case PROP_ACCESS_POINTS: + list = nm_wifi_aps_get_paths(&priv->aps_lst_head, TRUE); + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list)); + break; + case PROP_ACTIVE_ACCESS_POINT: + nm_dbus_utils_g_value_set_object_path(value, priv->current_ap); + break; + case PROP_SCANNING: + g_value_set_boolean(value, nm_device_wifi_get_scanning(self)); + break; + case PROP_LAST_SCAN: + g_value_set_int64( + value, + priv->scan_last_complete_msec > 0 + ? nm_utils_monotonic_timestamp_as_boottime(priv->scan_last_complete_msec, + NM_UTILS_NSEC_PER_MSEC) + : (gint64) -1); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceWifi * device = NM_DEVICE_WIFI(object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(device); + + switch (prop_id) { + case PROP_CAPABILITIES: + /* construct-only */ + priv->capabilities = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_wifi_init(NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + c_list_init(&priv->aps_lst_head); + c_list_init(&priv->scanning_prohibited_lst_head); + c_list_init(&priv->scan_request_ssids_lst_head); + priv->aps_idx_by_supplicant_path = g_hash_table_new(nm_direct_hash, NULL); + + priv->scan_last_request_started_at_msec = G_MININT64; + priv->hidden_probe_scan_warn = TRUE; + priv->mode = NM_802_11_MODE_INFRA; + priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; +} + +static void +constructed(GObject *object) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_device_wifi_parent_class)->constructed(object); + + if (priv->capabilities & NM_WIFI_DEVICE_CAP_AP) + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "driver supports Access Point (AP) mode"); + + /* Connect to the supplicant manager */ + priv->sup_mgr = g_object_ref(nm_supplicant_manager_get()); +} + +NMDevice * +nm_device_wifi_new(const char *iface, NMDeviceWifiCapabilities capabilities) +{ + return g_object_new(NM_TYPE_DEVICE_WIFI, + NM_DEVICE_IFACE, + iface, + NM_DEVICE_TYPE_DESC, + "802.11 Wi-Fi", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WIFI, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WIFI, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WLAN, + NM_DEVICE_WIFI_CAPABILITIES, + (guint) capabilities, + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_assert(c_list_is_empty(&priv->scanning_prohibited_lst_head)); + + nm_clear_g_source(&priv->periodic_update_id); + + wifi_secrets_cancel(self); + + cleanup_association_attempt(self, TRUE); + supplicant_interface_release(self); + cleanup_supplicant_failures(self); + + g_clear_object(&priv->sup_mgr); + + remove_all_aps(self); + + if (priv->p2p_device) { + /* Destroy the P2P device. */ + g_object_remove_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device); + nm_device_wifi_p2p_remove(g_steal_pointer(&priv->p2p_device)); + } + + G_OBJECT_CLASS(nm_device_wifi_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMDeviceWifi * self = NM_DEVICE_WIFI(object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); + + nm_assert(c_list_is_empty(&priv->aps_lst_head)); + nm_assert(g_hash_table_size(priv->aps_idx_by_supplicant_path) == 0); + + g_hash_table_unref(priv->aps_idx_by_supplicant_path); + + G_OBJECT_CLASS(nm_device_wifi_parent_class)->finalize(object); +} + +static void +nm_device_wifi_class_init(NMDeviceWifiClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + dbus_object_class->interface_infos = + NM_DBUS_INTERFACE_INFOS(&nm_interface_info_device_wireless); + + device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI); + + device_class->can_auto_connect = can_auto_connect; + device_class->get_autoconnect_allowed = get_autoconnect_allowed; + device_class->is_available = is_available; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->get_enabled = get_enabled; + device_class->get_guessed_metered = get_guessed_metered; + device_class->set_enabled = set_enabled; + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->get_configured_mtu = get_configured_mtu; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout; + device_class->deactivate_async = deactivate_async; + device_class->deactivate = deactivate; + device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr; + device_class->unmanaged_on_quit = unmanaged_on_quit; + device_class->can_reapply_change = can_reapply_change; + device_class->reapply_connection = reapply_connection; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_WIFI_MODE, + "", + "", + NM_802_11_MODE_UNKNOWN, + NM_802_11_MODE_AP, + NM_802_11_MODE_INFRA, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BITRATE] = g_param_spec_uint(NM_DEVICE_WIFI_BITRATE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACCESS_POINTS] = + g_param_spec_boxed(NM_DEVICE_WIFI_ACCESS_POINTS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVE_ACCESS_POINT] = + g_param_spec_string(NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_WIFI_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_WIFI_DEVICE_CAP_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_DEVICE_WIFI_SCANNING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LAST_SCAN] = g_param_spec_int64(NM_DEVICE_WIFI_LAST_SCAN, + "", + "", + -1, + G_MAXINT64, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[P2P_DEVICE_CREATED] = g_signal_new(NM_DEVICE_WIFI_P2P_DEVICE_CREATED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); +} diff --git a/src/core/devices/wifi/nm-device-wifi.h b/src/core/devices/wifi/nm-device-wifi.h new file mode 100644 index 0000000..d9e9038 --- /dev/null +++ b/src/core/devices/wifi/nm-device-wifi.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2016 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_WIFI_H__ +#define __NETWORKMANAGER_DEVICE_WIFI_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_DEVICE_WIFI (nm_device_wifi_get_type()) +#define NM_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi)) +#define NM_DEVICE_WIFI_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass)) +#define NM_IS_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WIFI)) +#define NM_IS_DEVICE_WIFI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_WIFI)) +#define NM_DEVICE_WIFI_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass)) + +#define NM_DEVICE_WIFI_MODE "mode" +#define NM_DEVICE_WIFI_BITRATE "bitrate" +#define NM_DEVICE_WIFI_ACCESS_POINTS "access-points" +#define NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT "active-access-point" +#define NM_DEVICE_WIFI_CAPABILITIES "wireless-capabilities" +#define NM_DEVICE_WIFI_SCANNING "scanning" +#define NM_DEVICE_WIFI_LAST_SCAN "last-scan" + +#define NM_DEVICE_WIFI_P2P_DEVICE_CREATED "p2p-device-created" + +typedef struct _NMDeviceWifi NMDeviceWifi; +typedef struct _NMDeviceWifiClass NMDeviceWifiClass; + +GType nm_device_wifi_get_type(void); + +NMDevice *nm_device_wifi_new(const char *iface, NMDeviceWifiCapabilities capabilities); + +const CList *_nm_device_wifi_get_aps(NMDeviceWifi *self); + +void _nm_device_wifi_request_scan(NMDeviceWifi * self, + GVariant * options, + GDBusMethodInvocation *invocation); + +GPtrArray *nmtst_ssids_options_to_ptrarray(GVariant *value, GError **error); + +gboolean nm_device_wifi_get_scanning(NMDeviceWifi *self); + +void nm_device_wifi_scanning_prohibited_track(NMDeviceWifi *self, + gpointer tag, + gboolean temporarily_prohibited); + +#endif /* __NETWORKMANAGER_DEVICE_WIFI_H__ */ diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c new file mode 100644 index 0000000..5ded8e1 --- /dev/null +++ b/src/core/devices/wifi/nm-iwd-manager.c @@ -0,0 +1,1359 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Intel Corporation + */ + +#include "nm-default.h" + +#include "nm-iwd-manager.h" + +#include + +#include "nm-core-internal.h" +#include "nm-manager.h" +#include "nm-device-iwd.h" +#include "nm-wifi-utils.h" +#include "nm-glib-aux/nm-random-utils.h" +#include "settings/nm-settings.h" +#include "nm-std-aux/nm-dbus-compat.h" + +/*****************************************************************************/ + +typedef struct { + const char * name; + NMIwdNetworkSecurity security; + char buf[0]; +} KnownNetworkId; + +typedef struct { + GDBusProxy * known_network; + NMSettingsConnection *mirror_connection; +} KnownNetworkData; + +typedef struct { + NMManager * manager; + NMSettings * settings; + GCancellable * cancellable; + gboolean running; + GDBusObjectManager *object_manager; + guint agent_id; + char * agent_path; + GHashTable * known_networks; + NMDeviceIwd * last_agent_call_device; +} NMIwdManagerPrivate; + +struct _NMIwdManager { + GObject parent; + NMIwdManagerPrivate _priv; +}; + +struct _NMIwdManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT) + +#define NM_IWD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIwdManager, NM_IS_IWD_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "iwd-manager" +#define _NMLOG_DOMAIN LOGD_WIFI + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled(level, _NMLOG_DOMAIN)) { \ + char __prefix[32]; \ + \ + if (self) \ + g_snprintf(__prefix, \ + sizeof(__prefix), \ + "%s[%p]", \ + ""_NMLOG_PREFIX_NAME \ + "", \ + (self)); \ + else \ + g_strlcpy(__prefix, _NMLOG_PREFIX_NAME, sizeof(__prefix)); \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, + KnownNetworkData * data); + +/*****************************************************************************/ + +static const char * +get_variant_string_or_null(GVariant *v) +{ + if (!v) + return NULL; + + if (!g_variant_is_of_type(v, G_VARIANT_TYPE_STRING) + && !g_variant_is_of_type(v, G_VARIANT_TYPE_OBJECT_PATH)) + return NULL; + + return g_variant_get_string(v, NULL); +} + +static const char * +get_property_string_or_null(GDBusProxy *proxy, const char *property) +{ + gs_unref_variant GVariant *value = NULL; + + if (!proxy || !property) + return NULL; + + value = g_dbus_proxy_get_cached_property(proxy, property); + + return get_variant_string_or_null(value); +} + +static gboolean +get_property_bool(GDBusProxy *proxy, const char *property, gboolean default_val) +{ + gs_unref_variant GVariant *value = NULL; + + if (!proxy || !property) + return default_val; + + value = g_dbus_proxy_get_cached_property(proxy, property); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) + return default_val; + + return g_variant_get_boolean(value); +} + +static NMDeviceIwd * +get_device_from_network(NMIwdManager *self, GDBusProxy *network) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char * ifname; + const char * device_path; + NMDevice * device; + gs_unref_object GDBusInterface *device_obj = NULL; + + /* Try not to rely on the path of the Device being a prefix of the + * Network's object path. + */ + + device_path = get_property_string_or_null(network, "Device"); + if (!device_path) { + _LOGD("Device not cached for network at %s", g_dbus_proxy_get_object_path(network)); + return NULL; + } + + device_obj = g_dbus_object_manager_get_interface(priv->object_manager, + device_path, + NM_IWD_DEVICE_INTERFACE); + + ifname = get_property_string_or_null(G_DBUS_PROXY(device_obj), "Name"); + if (!ifname) { + _LOGD("Name not cached for device at %s", device_path); + return NULL; + } + + device = nm_manager_get_device(priv->manager, ifname, NM_DEVICE_TYPE_WIFI); + if (!device || !NM_IS_DEVICE_IWD(device)) { + _LOGD("NM device %s is not an IWD-managed device", ifname); + return NULL; + } + + return NM_DEVICE_IWD(device); +} + +static void +agent_dbus_method_cb(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char * network_path; + NMDeviceIwd * device; + gs_free char * name_owner = NULL; + gs_unref_object GDBusInterface *network = NULL; + + /* Be paranoid and check the sender address */ + name_owner = g_dbus_object_manager_client_get_name_owner( + G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager)); + if (!nm_streq0(name_owner, sender)) + goto return_error; + + if (!strcmp(method_name, "Cancel")) { + const char *reason = NULL; + + g_variant_get(parameters, "(&s)", &reason); + _LOGD("agent-request: Cancel reason: %s", reason); + + if (!priv->last_agent_call_device) + goto return_error; + + if (nm_device_iwd_agent_query(priv->last_agent_call_device, NULL)) { + priv->last_agent_call_device = NULL; + g_dbus_method_invocation_return_value(invocation, NULL); + return; + } + + priv->last_agent_call_device = NULL; + goto return_error; + } + + if (!strcmp(method_name, "RequestUserPassword")) + g_variant_get(parameters, "(&os)", &network_path, NULL); + else + g_variant_get(parameters, "(&o)", &network_path); + + network = g_dbus_object_manager_get_interface(priv->object_manager, + network_path, + NM_IWD_NETWORK_INTERFACE); + if (!network) { + _LOGE("agent-request: unable to find the network object"); + goto return_error; + } + + device = get_device_from_network(self, G_DBUS_PROXY(network)); + if (!device) { + _LOGD("agent-request: device not found in IWD Agent request"); + goto return_error; + } + + if (nm_device_iwd_agent_query(device, invocation)) { + priv->last_agent_call_device = device; + return; + } + + _LOGD("agent-request: device %s did not handle the IWD Agent request", + nm_device_get_iface(NM_DEVICE(device))); + +return_error: + /* IWD doesn't look at the specific error */ + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "Secrets not available for this connection"); +} + +static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + "net.connman.iwd.Agent", + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO( + "RequestPassphrase", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("passphrase", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO( + "RequestPrivateKeyPassphrase", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("passphrase", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO( + "RequestUserNameAndPassword", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("user", "s"), + NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO( + "RequestUserPassword", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), + NM_DEFINE_GDBUS_ARG_INFO("user", "s"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("Cancel", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("reason", "s"), ), ), ), ); + +static guint +iwd_agent_export(GDBusConnection *connection, gpointer user_data, char **agent_path, GError **error) +{ + static const GDBusInterfaceVTable vtable = { + .method_call = agent_dbus_method_cb, + }; + char path[50]; + unsigned int rnd; + guint id; + + nm_utils_random_bytes(&rnd, sizeof(rnd)); + + nm_sprintf_buf(path, "/agent/%u", rnd); + + id = + g_dbus_connection_register_object(connection, + path, + NM_UNCONST_PTR(GDBusInterfaceInfo, &iwd_agent_iface_info), + &vtable, + user_data, + NULL, + error); + + if (id) + *agent_path = g_strdup(path); + return id; +} + +static void +register_agent(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusInterface * agent_manager; + + agent_manager = g_dbus_object_manager_get_interface(priv->object_manager, + "/net/connman/iwd", /* IWD 1.0+ */ + NM_IWD_AGENT_MANAGER_INTERFACE); + if (!agent_manager) { + _LOGE("unable to register the IWD Agent: PSK/8021x Wi-Fi networks may not work"); + return; + } + + /* Register our agent */ + g_dbus_proxy_call(G_DBUS_PROXY(agent_manager), + "RegisterAgent", + g_variant_new("(o)", priv->agent_path), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); + + g_object_unref(agent_manager); +} + +/*****************************************************************************/ + +static KnownNetworkId * +known_network_id_new(const char *name, NMIwdNetworkSecurity security) +{ + KnownNetworkId *id; + gsize strsize = strlen(name) + 1; + + id = g_malloc(sizeof(KnownNetworkId) + strsize); + id->name = id->buf; + id->security = security; + memcpy(id->buf, name, strsize); + + return id; +} + +static guint +known_network_id_hash(KnownNetworkId *id) +{ + NMHashState h; + + nm_hash_init(&h, 1947951703u); + nm_hash_update_val(&h, id->security); + nm_hash_update_str(&h, id->name); + return nm_hash_complete(&h); +} + +static gboolean +known_network_id_equal(KnownNetworkId *a, KnownNetworkId *b) +{ + return a->security == b->security && nm_streq(a->name, b->name); +} + +static void +known_network_data_free(KnownNetworkData *network) +{ + if (!network) + return; + + g_object_unref(network->known_network); + mirror_connection_take_and_delete(network->mirror_connection, network); + g_slice_free(KnownNetworkData, network); +} + +/*****************************************************************************/ + +static void +set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char * ifname; + int ifindex; + NMDevice * device; + int errsv; + + ifname = get_property_string_or_null(proxy, "Name"); + if (!ifname) { + _LOGE("Name not cached for Device at %s", g_dbus_proxy_get_object_path(proxy)); + return; + } + + ifindex = if_nametoindex(ifname); + + if (!ifindex) { + errsv = errno; + _LOGE("if_nametoindex failed for Name %s for Device at %s: %i", + ifname, + g_dbus_proxy_get_object_path(proxy), + errsv); + return; + } + + device = nm_manager_get_device_by_ifindex(priv->manager, ifindex); + if (!NM_IS_DEVICE_IWD(device)) { + _LOGE("IWD device named %s is not a Wifi device", ifname); + return; + } + + nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object); +} + +static void +known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + nm_log_warn(LOGD_WIFI, + "Updating %s on IWD known network %s failed: %s", + (const char *) user_data, + g_dbus_proxy_get_object_path(G_DBUS_PROXY(source)), + error->message); + } +} + +static void +sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNetworkData *data) +{ + NMSettingsConnectionIntFlags flags; + NMConnection * conn = nm_settings_connection_get_connection(sett_conn); + NMSettingConnection * s_conn = nm_connection_get_setting_connection(conn); + gboolean nm_autoconnectable = nm_setting_connection_get_autoconnect(s_conn); + gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE); + + nm_assert(sett_conn == data->mirror_connection); + + if (iwd_autoconnectable == nm_autoconnectable) + return; + + /* If this is a generated connection it may be ourselves updating it */ + flags = nm_settings_connection_get_flags(data->mirror_connection); + if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) + return; + + nm_log_dbg(LOGD_WIFI, + "Updating AutoConnect on known network at %s based on connection %s", + g_dbus_proxy_get_object_path(data->known_network), + nm_settings_connection_get_id(data->mirror_connection)); + g_dbus_proxy_call(data->known_network, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", + NM_IWD_KNOWN_NETWORK_INTERFACE, + "AutoConnect", + g_variant_new_boolean(nm_autoconnectable)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + known_network_update_cb, + "AutoConnect"); +} + +/* Look up an existing NMSettingsConnection for a network that has been + * preprovisioned with an IWD config file or has been connected to before, + * or create a new in-memory NMSettingsConnection object. This will let + * users control the few supported properties (mainly make it + * IWD-autoconnectable or not), remove/forget the network, or, for a + * WPA2-Enterprise type network it will inform the NM autoconnect mechanism + * and the clients that this networks needs no additional EAP configuration + * from the user. + */ +static NMSettingsConnection * +mirror_connection(NMIwdManager * self, + const KnownNetworkId *id, + gboolean create_new, + GDBusProxy * known_network) +{ + NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); + NMSettingsConnection *const *iter; + gs_unref_object NMConnection *connection = NULL; + NMSettingsConnection * settings_connection = NULL; + char uuid[37]; + NMSetting * setting; + gs_free_error GError *error = NULL; + gs_unref_bytes GBytes *new_ssid = NULL; + gsize ssid_len = strlen(id->name); + gboolean autoconnectable = TRUE; + gboolean hidden = FALSE; + gboolean exact_match = TRUE; + const char * key_mgmt = NULL; + + if (known_network) { + autoconnectable = get_property_bool(known_network, "AutoConnect", TRUE); + hidden = get_property_bool(known_network, "Hidden", FALSE); + } + + for (iter = nm_settings_get_connections(priv->settings, NULL); *iter; iter++) { + NMSettingsConnection *sett_conn = *iter; + NMConnection * conn = nm_settings_connection_get_connection(sett_conn); + NMIwdNetworkSecurity security; + NMSettingWireless * s_wifi; + const guint8 * ssid_bytes; + gsize ssid_len2; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security)) + continue; + + if (security != id->security) + continue; + + s_wifi = nm_connection_get_setting_wireless(conn); + if (!s_wifi) + continue; + + /* The SSID must be UTF-8 if it matches since id->name is known to be + * valid UTF-8, so just memcmp them. + */ + ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wifi), &ssid_len2); + if (!ssid_bytes || ssid_len2 != ssid_len || memcmp(ssid_bytes, id->name, ssid_len)) + continue; + + exact_match = TRUE; + + if (known_network) { + NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn); + + if (nm_setting_connection_get_autoconnect(s_conn) != autoconnectable + || nm_setting_wireless_get_hidden(s_wifi) != hidden) + exact_match = FALSE; + } + + switch (id->security) { + case NM_IWD_NETWORK_SECURITY_WEP: + case NM_IWD_NETWORK_SECURITY_NONE: + case NM_IWD_NETWORK_SECURITY_PSK: + break; + case NM_IWD_NETWORK_SECURITY_8021X: + { + NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x(conn); + gboolean external = FALSE; + guint i; + + for (i = 0; i < nm_setting_802_1x_get_num_eap_methods(s_8021x); i++) { + if (nm_streq(nm_setting_802_1x_get_eap_method(s_8021x, i), "external")) { + external = TRUE; + break; + } + } + + /* Prefer returning connections with EAP method "external" */ + if (!external) + exact_match = FALSE; + } + } + + if (!settings_connection || exact_match) + settings_connection = sett_conn; + + if (exact_match) + break; + } + + if (settings_connection && known_network && !exact_match) { + NMSettingsConnectionIntFlags flags = nm_settings_connection_get_flags(settings_connection); + + /* If we found a connection and it's generated (likely by ourselves) + * it may have been created on a request by + * nm_iwd_manager_get_ap_mirror_connection() when no Known Network + * was available so we didn't have access to its properties other + * than Name and Security. Copy their values to the generated + * NMConnection. + * TODO: avoid notify signals triggering our own watch. + * + * If on the other hand this is a user-created NMConnection we + * should try to copy the properties from it to IWD's Known Network + * using the Properties DBus interface in case the user created an + * NM connection before IWD appeared on the bus, or before IWD + * created its Known Network object. + */ + if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + NMConnection *tmp_conn = nm_settings_connection_get_connection(settings_connection); + NMSettingConnection *s_conn = nm_connection_get_setting_connection(tmp_conn); + NMSettingWireless * s_wifi = nm_connection_get_setting_wireless(tmp_conn); + + g_object_set(G_OBJECT(s_conn), + NM_SETTING_CONNECTION_AUTOCONNECT, + autoconnectable, + NULL); + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_HIDDEN, hidden, NULL); + } else { + KnownNetworkData data = {known_network, settings_connection}; + sett_conn_changed(settings_connection, 0, &data); + } + } + + if (settings_connection && known_network) { + /* Reset NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL now that the + * connection is going to be referenced by a known network, we don't + * want it to be deleted when activation fails anymore. + */ + nm_settings_connection_set_flags_full(settings_connection, + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + 0); + } + + /* If we already have an NMSettingsConnection matching this + * KnownNetwork, whether it's saved or an in-memory connection + * potentially created by ourselves then we have nothing left to + * do here. + */ + if (settings_connection || !create_new) + return settings_connection; + + connection = nm_simple_connection_new(); + + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_CONNECTION, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_CONNECTION_ID, + id->name, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_buf(uuid), + NM_SETTING_CONNECTION_AUTOCONNECT, + autoconnectable, + NULL)); + nm_connection_add_setting(connection, setting); + + new_ssid = g_bytes_new(id->name, ssid_len); + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS, + NM_SETTING_WIRELESS_SSID, + new_ssid, + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_HIDDEN, + hidden, + NULL)); + nm_connection_add_setting(connection, setting); + + switch (id->security) { + case NM_IWD_NETWORK_SECURITY_WEP: + key_mgmt = "none"; + break; + case NM_IWD_NETWORK_SECURITY_NONE: + key_mgmt = NULL; + break; + case NM_IWD_NETWORK_SECURITY_PSK: + key_mgmt = "wpa-psk"; + break; + case NM_IWD_NETWORK_SECURITY_8021X: + key_mgmt = "wpa-eap"; + break; + } + + if (key_mgmt) { + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + key_mgmt, + NULL)); + nm_connection_add_setting(connection, setting); + } + + if (id->security == NM_IWD_NETWORK_SECURITY_8021X) { + /* "password" and "private-key-password" may be requested by the IWD agent + * from NM and IWD will implement a specific secret cache policy so by + * default respect that policy and don't save copies of those secrets in + * NM settings. The saved values can not be used anyway because of our + * use of NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW. + */ + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_802_1X, + NM_SETTING_802_1X_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NULL)); + nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), "external"); + nm_connection_add_setting(connection, setting); + } + + if (!nm_connection_normalize(connection, NULL, NULL, NULL)) + return NULL; + + if (!nm_settings_add_connection( + priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | (known_network ? 0 : NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + &settings_connection, + &error)) { + _LOGW("failed to add a mirror NMConnection for IWD's Known Network '%s': %s", + id->name, + error->message); + return NULL; + } + + return settings_connection; +} + +static void +mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, KnownNetworkData *data) +{ + NMSettingsConnectionIntFlags flags; + + if (!sett_conn) + return; + + flags = nm_settings_connection_get_flags(sett_conn); + + /* If connection has not been saved since we created it + * in interface_added it too can be removed now. */ + if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) + nm_settings_connection_delete(sett_conn, FALSE); + + g_signal_handlers_disconnect_by_data(sett_conn, data); + g_object_unref(sett_conn); +} + +static void +interface_added(GDBusObjectManager *object_manager, + GDBusObject * object, + GDBusInterface * interface, + gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusProxy * proxy; + const char * iface_name; + + if (!priv->running) + return; + + g_return_if_fail(G_IS_DBUS_PROXY(interface)); + + proxy = G_DBUS_PROXY(interface); + iface_name = g_dbus_proxy_get_interface_name(proxy); + + if (nm_streq(iface_name, NM_IWD_DEVICE_INTERFACE)) { + set_device_dbus_object(self, proxy, object); + return; + } + + if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) { + KnownNetworkId * id; + KnownNetworkId * orig_id; + KnownNetworkData * data; + NMIwdNetworkSecurity security; + const char * type_str, *name; + NMSettingsConnection *sett_conn = NULL; + + type_str = get_property_string_or_null(proxy, "Type"); + name = get_property_string_or_null(proxy, "Name"); + if (!type_str || !name) + return; + + if (nm_streq(type_str, "open")) + security = NM_IWD_NETWORK_SECURITY_NONE; + else if (nm_streq(type_str, "psk")) + security = NM_IWD_NETWORK_SECURITY_PSK; + else if (nm_streq(type_str, "8021x")) + security = NM_IWD_NETWORK_SECURITY_8021X; + else + return; + + id = known_network_id_new(name, security); + + if (g_hash_table_lookup_extended(priv->known_networks, + id, + (void **) &orig_id, + (void **) &data)) { + _LOGW("DBus error: KnownNetwork already exists ('%s', %s)", name, type_str); + nm_g_object_ref_set(&data->known_network, proxy); + g_free(id); + id = orig_id; + } else { + data = g_slice_new0(KnownNetworkData); + data->known_network = g_object_ref(proxy); + g_hash_table_insert(priv->known_networks, id, data); + } + + sett_conn = mirror_connection(self, id, TRUE, proxy); + + if (sett_conn && sett_conn != data->mirror_connection) { + NMSettingsConnection *sett_conn_old = data->mirror_connection; + + data->mirror_connection = nm_g_object_ref(sett_conn); + mirror_connection_take_and_delete(sett_conn_old, data); + + g_signal_connect(sett_conn, + NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, + G_CALLBACK(sett_conn_changed), + data); + } + + return; + } + + if (nm_streq(iface_name, NM_IWD_NETWORK_INTERFACE)) { + NMDeviceIwd *device = get_device_from_network(self, proxy); + + if (device) + nm_device_iwd_network_add_remove(device, proxy, TRUE); + + return; + } +} + +static void +interface_removed(GDBusObjectManager *object_manager, + GDBusObject * object, + GDBusInterface * interface, + gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusProxy * proxy; + const char * iface_name; + + g_return_if_fail(G_IS_DBUS_PROXY(interface)); + + proxy = G_DBUS_PROXY(interface); + iface_name = g_dbus_proxy_get_interface_name(proxy); + + if (nm_streq(iface_name, NM_IWD_DEVICE_INTERFACE)) { + set_device_dbus_object(self, proxy, NULL); + return; + } + + if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) { + KnownNetworkId id; + const char * type_str; + + type_str = get_property_string_or_null(proxy, "Type"); + id.name = get_property_string_or_null(proxy, "Name"); + if (!type_str || !id.name) + return; + + if (nm_streq(type_str, "open")) + id.security = NM_IWD_NETWORK_SECURITY_NONE; + else if (nm_streq(type_str, "psk")) + id.security = NM_IWD_NETWORK_SECURITY_PSK; + else if (nm_streq(type_str, "8021x")) + id.security = NM_IWD_NETWORK_SECURITY_8021X; + else + return; + + g_hash_table_remove(priv->known_networks, &id); + return; + } + + if (nm_streq(iface_name, NM_IWD_NETWORK_INTERFACE)) { + NMDeviceIwd *device = get_device_from_network(self, proxy); + + if (device) + nm_device_iwd_network_add_remove(device, proxy, FALSE); + + return; + } +} + +static void +object_added(GDBusObjectManager *object_manager, GDBusObject *object, gpointer user_data) +{ + GList *interfaces, *iter; + + interfaces = g_dbus_object_get_interfaces(object); + + for (iter = interfaces; iter; iter = iter->next) { + GDBusInterface *interface = G_DBUS_INTERFACE(iter->data); + + interface_added(NULL, object, interface, user_data); + } + + g_list_free_full(interfaces, g_object_unref); +} + +static void +object_removed(GDBusObjectManager *object_manager, GDBusObject *object, gpointer user_data) +{ + GList *interfaces, *iter; + + interfaces = g_dbus_object_get_interfaces(object); + + for (iter = interfaces; iter; iter = iter->next) { + GDBusInterface *interface = G_DBUS_INTERFACE(iter->data); + + interface_removed(NULL, object, interface, user_data); + } + + g_list_free_full(interfaces, g_object_unref); +} + +static void +connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); + NMConnection * conn = nm_settings_connection_get_connection(sett_conn); + NMSettingWireless * s_wireless; + KnownNetworkData * data; + KnownNetworkId id; + char ssid_buf[33]; + const guint8 * ssid_bytes; + gsize ssid_len; + NMSettingsConnection *new_mirror_conn; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security)) + return; + + s_wireless = nm_connection_get_setting_wireless(conn); + if (!s_wireless) + return; + + ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wireless), &ssid_len); + if (!ssid_bytes || ssid_len > 32 || memchr(ssid_bytes, 0, ssid_len)) + return; + + memcpy(ssid_buf, ssid_bytes, ssid_len); + ssid_buf[ssid_len] = '\0'; + id.name = ssid_buf; + data = g_hash_table_lookup(priv->known_networks, &id); + if (!data) + return; + + if (data->mirror_connection != sett_conn) + return; + + g_clear_object(&data->mirror_connection); + + /* Don't call Forget on the Known Network until there's no longer *any* + * matching NMSettingsConnection (debatable) + */ + new_mirror_conn = mirror_connection(self, &id, FALSE, NULL); + if (new_mirror_conn) { + data->mirror_connection = g_object_ref(new_mirror_conn); + return; + } + + if (!priv->running) + return; + + g_dbus_proxy_call(data->known_network, + "Forget", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +static gboolean +_om_has_name_owner(GDBusObjectManager *object_manager) +{ + gs_free char *name_owner = NULL; + + nm_assert(G_IS_DBUS_OBJECT_MANAGER_CLIENT(object_manager)); + + name_owner = + g_dbus_object_manager_client_get_name_owner(G_DBUS_OBJECT_MANAGER_CLIENT(object_manager)); + return !!name_owner; +} + +static void +release_object_manager(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + if (!priv->object_manager) + return; + + g_signal_handlers_disconnect_by_data(priv->object_manager, self); + + if (priv->agent_id) { + GDBusConnection * agent_connection; + GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager); + + agent_connection = g_dbus_object_manager_client_get_connection(omc); + + /* We're is called when we're shutting down (i.e. our DBus connection + * is being closed, and IWD will detect this) or IWD was stopped so + * in either case calling UnregisterAgent will not do anything. + */ + g_dbus_connection_unregister_object(agent_connection, priv->agent_id); + priv->agent_id = 0; + nm_clear_g_free(&priv->agent_path); + } + + g_clear_object(&priv->object_manager); +} + +static void prepare_object_manager(NMIwdManager *self); + +static void +name_owner_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusObjectManager * object_manager = G_DBUS_OBJECT_MANAGER(object); + + nm_assert(object_manager == priv->object_manager); + + if (_om_has_name_owner(object_manager)) { + release_object_manager(self); + prepare_object_manager(self); + } else { + const CList *tmp_lst; + NMDevice * device; + + if (!priv->running) + return; + + priv->running = false; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (NM_IS_DEVICE_IWD(device)) { + nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), NULL); + } + } + } +} + +static void +device_added(NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GList * objects, *iter; + + if (!NM_IS_DEVICE_IWD(device)) + return; + + if (!priv->running) + return; + + /* Here we handle a potential scenario where IWD's DBus objects for the + * new device popped up before the NMDevice. The + * interface_added/object_added signals have been received already and + * the handlers couldn't do much because the NMDevice wasn't there yet + * so now we go over the Network and Device interfaces again. In this + * exact order for "object path" property consistency -- see reasoning + * in object_compare_interfaces. + */ + objects = g_dbus_object_manager_get_objects(priv->object_manager); + + for (iter = objects; iter; iter = iter->next) { + GDBusObject * object = G_DBUS_OBJECT(iter->data); + gs_unref_object GDBusInterface *interface = NULL; + + interface = g_dbus_object_get_interface(object, NM_IWD_NETWORK_INTERFACE); + if (!interface) + continue; + + if (NM_DEVICE_IWD(device) == get_device_from_network(self, (GDBusProxy *) interface)) + nm_device_iwd_network_add_remove(NM_DEVICE_IWD(device), (GDBusProxy *) interface, TRUE); + } + + for (iter = objects; iter; iter = iter->next) { + GDBusObject * object = G_DBUS_OBJECT(iter->data); + gs_unref_object GDBusInterface *interface = NULL; + const char * obj_ifname; + + interface = g_dbus_object_get_interface(object, NM_IWD_DEVICE_INTERFACE); + obj_ifname = get_property_string_or_null((GDBusProxy *) interface, "Name"); + + if (!obj_ifname || strcmp(nm_device_get_iface(device), obj_ifname)) + continue; + + nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object); + break; + } + + g_list_free_full(objects, g_object_unref); +} + +static void +device_removed(NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + if (!NM_IS_DEVICE_IWD(device)) + return; + + if (priv->last_agent_call_device == NM_DEVICE_IWD(device)) + priv->last_agent_call_device = NULL; +} + +/* This is used to sort the list of objects returned by GetManagedObjects() + * based on the DBus interfaces available on these objects in such a way that + * the interface_added calls happen in the right order. The order is defined + * by how some DBus interfaces point to interfaces on other objects using + * DBus properties of the type "object path" ("o" signature). This creates + * "dependencies" between objects. + * + * When NM and IWD are running, the InterfacesAdded signals should come in + * an order that ensures consistency of those object paths. For example + * when a Network interface is added with a KnownNetwork property, or that + * property is assigned a new value, the KnownNetwork object pointed to by + * it will have been added in an earlier InterfacesAdded signal. Similarly + * Station.ConnectedNetwork and Station.GetOrdereNetworks() only point to + * existing Network objects. (There may be circular dependencies but during + * initialization we only need a subset of those properties that doesn't + * have this problem.) + * + * But GetManagedObjects doesn't guarantee this kind of consistency so we + * order the returned object list ourselves to simplify the job of + * interface_added(). Objects that don't have any interfaces listed in + * interface_order are moved to the end of the list. + */ +static int +object_compare_interfaces(gconstpointer a, gconstpointer b) +{ + static const char *interface_order[] = { + NM_IWD_KNOWN_NETWORK_INTERFACE, + NM_IWD_NETWORK_INTERFACE, + NM_IWD_DEVICE_INTERFACE, + }; + int rank_a = G_N_ELEMENTS(interface_order); + int rank_b = G_N_ELEMENTS(interface_order); + guint pos; + + for (pos = 0; interface_order[pos]; pos++) { + GDBusInterface *iface_a; + GDBusInterface *iface_b; + + if (rank_a == G_N_ELEMENTS(interface_order) + && (iface_a = g_dbus_object_get_interface(G_DBUS_OBJECT(a), interface_order[pos]))) { + rank_a = pos; + g_object_unref(iface_a); + } + + if (rank_b == G_N_ELEMENTS(interface_order) + && (iface_b = g_dbus_object_get_interface(G_DBUS_OBJECT(b), interface_order[pos]))) { + rank_b = pos; + g_object_unref(iface_b); + } + } + + return rank_a - rank_b; +} + +static void +got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GError * error = NULL; + GDBusObjectManager * object_manager; + GDBusConnection * connection; + + object_manager = g_dbus_object_manager_client_new_for_bus_finish(result, &error); + if (object_manager == NULL) { + _LOGE("failed to acquire IWD Object Manager: Wi-Fi will not be available (%s)", + error->message); + g_clear_error(&error); + return; + } + + priv->object_manager = object_manager; + + g_signal_connect(priv->object_manager, + "notify::name-owner", + G_CALLBACK(name_owner_changed), + self); + + nm_assert(G_IS_DBUS_OBJECT_MANAGER_CLIENT(object_manager)); + + connection = + g_dbus_object_manager_client_get_connection(G_DBUS_OBJECT_MANAGER_CLIENT(object_manager)); + + priv->agent_id = iwd_agent_export(connection, self, &priv->agent_path, &error); + if (!priv->agent_id) { + _LOGE("failed to export the IWD Agent: PSK/8021x Wi-Fi networks may not work: %s", + error->message); + g_clear_error(&error); + } + + if (_om_has_name_owner(object_manager)) { + GList *objects, *iter; + + priv->running = true; + + g_signal_connect(priv->object_manager, + "interface-added", + G_CALLBACK(interface_added), + self); + g_signal_connect(priv->object_manager, + "interface-removed", + G_CALLBACK(interface_removed), + self); + g_signal_connect(priv->object_manager, "object-added", G_CALLBACK(object_added), self); + g_signal_connect(priv->object_manager, "object-removed", G_CALLBACK(object_removed), self); + + g_hash_table_remove_all(priv->known_networks); + + objects = g_dbus_object_manager_get_objects(object_manager); + objects = g_list_sort(objects, object_compare_interfaces); + for (iter = objects; iter; iter = iter->next) + object_added(NULL, G_DBUS_OBJECT(iter->data), self); + + g_list_free_full(objects, g_object_unref); + + if (priv->agent_id) + register_agent(self); + } +} + +static void +prepare_object_manager(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + g_dbus_object_manager_client_new_for_bus(NM_IWD_BUS_TYPE, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + NM_IWD_SERVICE, + "/", + NULL, + NULL, + NULL, + priv->cancellable, + got_object_manager, + self); +} + +gboolean +nm_iwd_manager_is_known_network(NMIwdManager *self, const char *name, NMIwdNetworkSecurity security) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + KnownNetworkId kn_id = {name, security}; + + return g_hash_table_contains(priv->known_networks, &kn_id); +} + +NMSettingsConnection * +nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap) +{ + NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); + KnownNetworkData * data; + char name_buf[33]; + KnownNetworkId kn_id = {name_buf, NM_IWD_NETWORK_SECURITY_NONE}; + const guint8 * ssid_bytes; + gsize ssid_len; + NM80211ApFlags flags = nm_wifi_ap_get_flags(ap); + NM80211ApSecurityFlags sec_flags = nm_wifi_ap_get_wpa_flags(ap) | nm_wifi_ap_get_rsn_flags(ap); + + ssid_bytes = g_bytes_get_data(nm_wifi_ap_get_ssid(ap), &ssid_len); + ssid_len = MIN(ssid_len, 32); + memcpy(name_buf, ssid_bytes, ssid_len); + name_buf[ssid_len] = '\0'; + + if (flags & NM_802_11_AP_FLAGS_PRIVACY) + kn_id.security = NM_IWD_NETWORK_SECURITY_WEP; + + if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + kn_id.security = NM_IWD_NETWORK_SECURITY_PSK; + else if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + kn_id.security = NM_IWD_NETWORK_SECURITY_8021X; + + /* Right now it's easier for us to do a name+security lookup than to use + * the Network.KnownNetwork property to look up by path. + */ + data = g_hash_table_lookup(priv->known_networks, &kn_id); + if (data) + return data->mirror_connection; + + /* We have no KnownNetwork for this AP, we're probably connecting to it for + * the first time. This is not a usual/supported scenario so we don't need + * to bother too much about creating a great mirror connection, we don't + * even have any more information than the Name & Type properties on the + * Network interface. This *should* never happen for an 8021x type network. + */ + return mirror_connection(self, &kn_id, TRUE, NULL); +} + +GDBusProxy * +nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusInterface * interface; + + if (!priv->object_manager) + return NULL; + + interface = g_dbus_object_manager_get_interface(priv->object_manager, path, name); + + return interface ? G_DBUS_PROXY(interface) : NULL; +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMIwdManager, nm_iwd_manager_get, NM_TYPE_IWD_MANAGER); + +static void +nm_iwd_manager_init(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + priv->manager = g_object_ref(NM_MANAGER_GET); + g_signal_connect(priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK(device_added), self); + g_signal_connect(priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK(device_removed), self); + + priv->settings = g_object_ref(NM_SETTINGS_GET); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK(connection_removed), + self); + + priv->cancellable = g_cancellable_new(); + + priv->known_networks = g_hash_table_new_full((GHashFunc) known_network_id_hash, + (GEqualFunc) known_network_id_equal, + g_free, + (GDestroyNotify) known_network_data_free); + + prepare_object_manager(self); +} + +static void +dispose(GObject *object) +{ + NMIwdManager * self = (NMIwdManager *) object; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + release_object_manager(self); + + nm_clear_g_cancellable(&priv->cancellable); + + if (priv->settings) { + g_signal_handlers_disconnect_by_data(priv->settings, self); + g_clear_object(&priv->settings); + } + + /* This may trigger mirror connection removals so it happens + * after the g_signal_handlers_disconnect_by_data above. + */ + nm_clear_pointer(&priv->known_networks, g_hash_table_destroy); + + if (priv->manager) { + g_signal_handlers_disconnect_by_data(priv->manager, self); + g_clear_object(&priv->manager); + } + + priv->last_agent_call_device = NULL; + + G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object); +} + +static void +nm_iwd_manager_class_init(NMIwdManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; +} diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h new file mode 100644 index 0000000..466f67c --- /dev/null +++ b/src/core/devices/wifi/nm-iwd-manager.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Intel Corporation + */ + +#ifndef __NETWORKMANAGER_IWD_MANAGER_H__ +#define __NETWORKMANAGER_IWD_MANAGER_H__ + +#include "devices/nm-device.h" +#include "nm-wifi-utils.h" +#include "nm-wifi-ap.h" + +#define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM +#define NM_IWD_SERVICE "net.connman.iwd" + +#define NM_IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager" +#define NM_IWD_WIPHY_INTERFACE "net.connman.iwd.Adapter" +#define NM_IWD_DEVICE_INTERFACE "net.connman.iwd.Device" +#define NM_IWD_NETWORK_INTERFACE "net.connman.iwd.Network" +#define NM_IWD_AGENT_INTERFACE "net.connman.iwd.Agent" +#define NM_IWD_WSC_INTERFACE "net.connman.iwd.WiFiSimpleConfiguration" +#define NM_IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork" +#define NM_IWD_SIGNAL_AGENT_INTERFACE "net.connman.iwd.SignalLevelAgent" +#define NM_IWD_AP_INTERFACE "net.connman.iwd.AccessPoint" +#define NM_IWD_ADHOC_INTERFACE "net.connman.iwd.AdHoc" +#define NM_IWD_STATION_INTERFACE "net.connman.iwd.Station" + +#define NM_TYPE_IWD_MANAGER (nm_iwd_manager_get_type()) +#define NM_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IWD_MANAGER, NMIwdManager)) +#define NM_IWD_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_IWD_MANAGER, NMIwdManagerClass)) +#define NM_IS_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_IWD_MANAGER)) +#define NM_IS_IWD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_IWD_MANAGER)) +#define NM_IWD_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IWD_MANAGER, NMIwdManagerClass)) + +typedef struct _NMIwdManager NMIwdManager; +typedef struct _NMIwdManagerClass NMIwdManagerClass; + +GType nm_iwd_manager_get_type(void); + +NMIwdManager *nm_iwd_manager_get(void); + +gboolean nm_iwd_manager_is_known_network(NMIwdManager * self, + const char * name, + NMIwdNetworkSecurity security); + +NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap); + +GDBusProxy * +nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name); + +#endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c new file mode 100644 index 0000000..4df2f35 --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-ap.c @@ -0,0 +1,1051 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-ap.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "devices/nm-device.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-setting-wireless.h" +#include "nm-utils.h" +#include "nm-wifi-utils.h" +#include "platform/nm-platform.h" +#include "supplicant/nm-supplicant-interface.h" + +#define PROTO_WPA "wpa" +#define PROTO_RSN "rsn" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMWifiAP, + PROP_FLAGS, + PROP_WPA_FLAGS, + PROP_RSN_FLAGS, + PROP_SSID, + PROP_FREQUENCY, + PROP_HW_ADDRESS, + PROP_MODE, + PROP_MAX_BITRATE, + PROP_STRENGTH, + PROP_LAST_SEEN, ); + +struct _NMWifiAPPrivate { + /* Scanned or cached values */ + GBytes * ssid; + char * address; + NM80211Mode mode; + guint8 strength; + guint32 freq; /* Frequency in MHz; ie 2412 (== 2.412 GHz) */ + guint32 max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */ + + gint64 + last_seen_msec; /* Timestamp when the AP was seen lastly (in nm_utils_get_monotonic_timestamp_*() scale). + * Note that this value might be negative! */ + + NM80211ApFlags flags; /* General flags */ + NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */ + NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */ + + bool metered : 1; + + /* Non-scanned attributes */ + bool fake : 1; /* Whether or not the AP is from a scan */ + bool hotspot : 1; /* Whether the AP is a local device's hotspot network */ +}; + +typedef struct _NMWifiAPPrivate NMWifiAPPrivate; + +struct _NMWifiAPClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMWifiAP, nm_wifi_ap, NM_TYPE_DBUS_OBJECT) + +#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiAP, NM_IS_WIFI_AP) + +/*****************************************************************************/ + +GBytes * +nm_wifi_ap_get_ssid(const NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), NULL); + + return NM_WIFI_AP_GET_PRIVATE(ap)->ssid; +} + +gboolean +nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid) +{ + NMWifiAPPrivate *priv; + gsize l; + + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + + if (!ssid) { + /* we don't clear the SSID, once we have it. We can only update + * it by a better value. */ + return FALSE; + } + + l = g_bytes_get_size(ssid); + if (l == 0 || l > 32) + g_return_val_if_reached(FALSE); + + priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (ssid == priv->ssid) + return FALSE; + if (priv->ssid && g_bytes_equal(ssid, priv->ssid)) + return FALSE; + + g_bytes_ref(ssid); + nm_clear_pointer(&priv->ssid, g_bytes_unref); + priv->ssid = ssid; + + _notify(ap, PROP_SSID); + return TRUE; +} + +static gboolean +nm_wifi_ap_set_flags(NMWifiAP *ap, NM80211ApFlags flags) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->flags != flags) { + priv->flags = flags; + _notify(ap, PROP_FLAGS); + return TRUE; + } + return FALSE; +} + +static gboolean +nm_wifi_ap_set_wpa_flags(NMWifiAP *ap, NM80211ApSecurityFlags flags) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->wpa_flags != flags) { + priv->wpa_flags = flags; + _notify(ap, PROP_WPA_FLAGS); + return TRUE; + } + return FALSE; +} + +static gboolean +nm_wifi_ap_set_rsn_flags(NMWifiAP *ap, NM80211ApSecurityFlags flags) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->rsn_flags != flags) { + priv->rsn_flags = flags; + _notify(ap, PROP_RSN_FLAGS); + return TRUE; + } + return FALSE; +} + +const char * +nm_wifi_ap_get_address(const NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), NULL); + + return NM_WIFI_AP_GET_PRIVATE(ap)->address; +} + +gboolean +nm_wifi_ap_set_address_bin(NMWifiAP *ap, const NMEtherAddr *addr) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + nm_assert(addr); + + if (!priv->address || !nm_utils_hwaddr_matches(addr, ETH_ALEN, priv->address, -1)) { + g_free(priv->address); + priv->address = nm_utils_hwaddr_ntoa(addr, ETH_ALEN); + _notify(ap, PROP_HW_ADDRESS); + return TRUE; + } + return FALSE; +} + +gboolean +nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr) +{ + NMEtherAddr addr_buf; + + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + if (!addr || !nm_utils_hwaddr_aton(addr, &addr_buf, sizeof(addr_buf))) + g_return_val_if_reached(FALSE); + + return nm_wifi_ap_set_address_bin(ap, &addr_buf); +} + +NM80211Mode +nm_wifi_ap_get_mode(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), NM_802_11_MODE_UNKNOWN); + + return NM_WIFI_AP_GET_PRIVATE(ap)->mode; +} + +static gboolean +nm_wifi_ap_set_mode(NMWifiAP *ap, NM80211Mode mode) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + nm_assert(NM_IN_SET(mode, + NM_802_11_MODE_UNKNOWN, + NM_802_11_MODE_ADHOC, + NM_802_11_MODE_INFRA, + NM_802_11_MODE_MESH)); + + if (priv->mode != mode) { + priv->mode = mode; + _notify(ap, PROP_MODE); + return TRUE; + } + return FALSE; +} + +gboolean +nm_wifi_ap_is_hotspot(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + + return NM_WIFI_AP_GET_PRIVATE(ap)->hotspot; +} + +gint8 +nm_wifi_ap_get_strength(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), 0); + + return NM_WIFI_AP_GET_PRIVATE(ap)->strength; +} + +gboolean +nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->strength != strength) { + priv->strength = strength; + _notify(ap, PROP_STRENGTH); + return TRUE; + } + return FALSE; +} + +guint32 +nm_wifi_ap_get_freq(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), 0); + + return NM_WIFI_AP_GET_PRIVATE(ap)->freq; +} + +gboolean +nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->freq != freq) { + priv->freq = freq; + _notify(ap, PROP_FREQUENCY); + return TRUE; + } + return FALSE; +} + +guint32 +nm_wifi_ap_get_max_bitrate(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), 0); + g_return_val_if_fail(nm_dbus_object_is_exported(NM_DBUS_OBJECT(ap)), 0); + + return NM_WIFI_AP_GET_PRIVATE(ap)->max_bitrate; +} + +gboolean +nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate) +{ + NMWifiAPPrivate *priv; + + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + + priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->max_bitrate != bitrate) { + priv->max_bitrate = bitrate; + _notify(ap, PROP_MAX_BITRATE); + return TRUE; + } + return FALSE; +} + +gboolean +nm_wifi_ap_get_fake(const NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + + return NM_WIFI_AP_GET_PRIVATE(ap)->fake; +} + +gboolean +nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake) +{ + NMWifiAPPrivate *priv; + + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + + priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->fake != !!fake) { + priv->fake = fake; + return TRUE; + } + return FALSE; +} + +NM80211ApFlags +nm_wifi_ap_get_flags(const NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), NM_802_11_AP_FLAGS_NONE); + + return NM_WIFI_AP_GET_PRIVATE(ap)->flags; +} + +static gboolean +nm_wifi_ap_set_last_seen(NMWifiAP *ap, gint32 last_seen_msec) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap); + + if (priv->last_seen_msec != last_seen_msec) { + priv->last_seen_msec = last_seen_msec; + _notify(ap, PROP_LAST_SEEN); + return TRUE; + } + return FALSE; +} + +gboolean +nm_wifi_ap_get_metered(const NMWifiAP *self) +{ + return NM_WIFI_AP_GET_PRIVATE(self)->metered; +} + +NM80211ApSecurityFlags +nm_wifi_ap_get_wpa_flags(const NMWifiAP *self) +{ + return NM_WIFI_AP_GET_PRIVATE(self)->wpa_flags; +} + +NM80211ApSecurityFlags +nm_wifi_ap_get_rsn_flags(const NMWifiAP *self) +{ + return NM_WIFI_AP_GET_PRIVATE(self)->rsn_flags; +} + +/*****************************************************************************/ + +gboolean +nm_wifi_ap_update_from_properties(NMWifiAP *ap, const NMSupplicantBssInfo *bss_info) +{ + NMWifiAPPrivate *priv; + gboolean changed = FALSE; + + g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE); + g_return_val_if_fail(bss_info, FALSE); + nm_assert(NM_IS_REF_STRING(bss_info->bss_path)); + + priv = NM_WIFI_AP_GET_PRIVATE(ap); + + nm_assert(!ap->_supplicant_path || ap->_supplicant_path == bss_info->bss_path); + + g_object_freeze_notify(G_OBJECT(ap)); + + if (!ap->_supplicant_path) { + ap->_supplicant_path = nm_ref_string_ref(bss_info->bss_path); + changed = TRUE; + } + + changed |= nm_wifi_ap_set_flags(ap, bss_info->ap_flags); + changed |= nm_wifi_ap_set_mode(ap, bss_info->mode); + changed |= nm_wifi_ap_set_strength(ap, bss_info->signal_percent); + changed |= nm_wifi_ap_set_freq(ap, bss_info->frequency); + changed |= nm_wifi_ap_set_ssid(ap, bss_info->ssid); + + if (bss_info->bssid_valid) + changed |= nm_wifi_ap_set_address_bin(ap, &bss_info->bssid); + else { + /* we don't actually clear the value. */ + } + + changed |= nm_wifi_ap_set_max_bitrate(ap, bss_info->max_rate); + + if (priv->metered != bss_info->metered) { + priv->metered = bss_info->metered; + changed = TRUE; + } + + changed |= nm_wifi_ap_set_wpa_flags(ap, bss_info->wpa_flags); + changed |= nm_wifi_ap_set_rsn_flags(ap, bss_info->rsn_flags); + + changed |= nm_wifi_ap_set_last_seen(ap, bss_info->last_seen_msec); + + changed |= nm_wifi_ap_set_fake(ap, FALSE); + + g_object_thaw_notify(G_OBJECT(ap)); + + return changed; +} + +static gboolean +has_proto(NMSettingWirelessSecurity *sec, const char *proto) +{ + guint32 num_protos = nm_setting_wireless_security_get_num_protos(sec); + guint32 i; + + if (num_protos == 0) + return TRUE; /* interpret no protos as "all" */ + + for (i = 0; i < num_protos; i++) { + if (!strcmp(nm_setting_wireless_security_get_proto(sec, i), proto)) + return TRUE; + } + return FALSE; +} + +static void +add_pair_ciphers(NMWifiAP *ap, NMSettingWirelessSecurity *sec) +{ + NMWifiAPPrivate * priv = NM_WIFI_AP_GET_PRIVATE(ap); + guint32 num = nm_setting_wireless_security_get_num_pairwise(sec); + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + guint32 i; + + /* If no ciphers are specified, that means "all" WPA ciphers */ + if (num == 0) { + flags |= NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP; + } else { + for (i = 0; i < num; i++) { + const char *cipher = nm_setting_wireless_security_get_pairwise(sec, i); + + if (!strcmp(cipher, "tkip")) + flags |= NM_802_11_AP_SEC_PAIR_TKIP; + else if (!strcmp(cipher, "ccmp")) + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + } + } + + if (has_proto(sec, PROTO_WPA)) + nm_wifi_ap_set_wpa_flags(ap, priv->wpa_flags | flags); + if (has_proto(sec, PROTO_RSN)) + nm_wifi_ap_set_rsn_flags(ap, priv->rsn_flags | flags); +} + +static void +add_group_ciphers(NMWifiAP *ap, NMSettingWirelessSecurity *sec) +{ + NMWifiAPPrivate * priv = NM_WIFI_AP_GET_PRIVATE(ap); + guint32 num = nm_setting_wireless_security_get_num_groups(sec); + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + guint32 i; + + /* If no ciphers are specified, that means "all" WPA ciphers */ + if (num == 0) { + flags |= NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_GROUP_CCMP; + } else { + for (i = 0; i < num; i++) { + const char *cipher = nm_setting_wireless_security_get_group(sec, i); + + if (!strcmp(cipher, "wep40")) + flags |= NM_802_11_AP_SEC_GROUP_WEP40; + else if (!strcmp(cipher, "wep104")) + flags |= NM_802_11_AP_SEC_GROUP_WEP104; + else if (!strcmp(cipher, "tkip")) + flags |= NM_802_11_AP_SEC_GROUP_TKIP; + else if (!strcmp(cipher, "ccmp")) + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + } + } + + if (has_proto(sec, PROTO_WPA)) + nm_wifi_ap_set_wpa_flags(ap, priv->wpa_flags | flags); + if (has_proto(sec, PROTO_RSN)) + nm_wifi_ap_set_rsn_flags(ap, priv->rsn_flags | flags); +} + +const char * +nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 now_msec) +{ + const NMWifiAPPrivate *priv; + const char * supplicant_id = "-"; + const char * export_path; + guint32 chan; + gs_free char * ssid_to_free = NULL; + char str_buf_ts[100]; + + g_return_val_if_fail(NM_IS_WIFI_AP(self), NULL); + + priv = NM_WIFI_AP_GET_PRIVATE(self); + + chan = nm_utils_wifi_freq_to_channel(priv->freq); + if (self->_supplicant_path) + supplicant_id = strrchr(self->_supplicant_path->str, '/') ?: supplicant_id; + + export_path = nm_dbus_object_get_path(NM_DBUS_OBJECT(self)); + if (export_path) + export_path = strrchr(export_path, '/') ?: export_path; + else + export_path = "/"; + + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + + g_snprintf(str_buf, + buf_len, + "%17s %-35s [ %c %3u %3u%% %c%c %c%c W:%04X R:%04X ] %s sup:%s [nm:%s]", + priv->address ?: "(none)", + (ssid_to_free = _nm_utils_ssid_to_string(priv->ssid)), + (priv->mode == NM_802_11_MODE_ADHOC + ? '*' + : (priv->hotspot + ? '#' + : (priv->fake ? 'f' : (priv->mode == NM_802_11_MODE_MESH ? 'm' : 'a')))), + chan, + priv->strength, + priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_', + priv->metered ? 'M' : '_', + priv->flags & NM_802_11_AP_FLAGS_WPS ? 'W' : '_', + priv->flags & NM_802_11_AP_FLAGS_WPS_PIN + ? 'p' + : (priv->flags & NM_802_11_AP_FLAGS_WPS_PBC ? '#' : '_'), + priv->wpa_flags & 0xFFFF, + priv->rsn_flags & 0xFFFF, + priv->last_seen_msec != G_MININT64 + ? nm_sprintf_buf(str_buf_ts, + "%3u.%03us", + (guint)((now_msec - priv->last_seen_msec) / 1000), + (guint)((now_msec - priv->last_seen_msec) % 1000)) + : " ", + supplicant_id, + export_path); + return str_buf; +} + +static guint +freq_to_band(guint32 freq) +{ + if (freq >= 4915 && freq <= 5825) + return 5; + else if (freq >= 2412 && freq <= 2484) + return 2; + return 0; +} + +gboolean +nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection) +{ + NMWifiAPPrivate * priv; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + GBytes * ssid; + const char * mode; + const char * band; + const char * bssid; + guint32 channel; + + g_return_val_if_fail(NM_IS_WIFI_AP(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + priv = NM_WIFI_AP_GET_PRIVATE(self); + + s_wireless = nm_connection_get_setting_wireless(connection); + if (s_wireless == NULL) + return FALSE; + + ssid = nm_setting_wireless_get_ssid(s_wireless); + if (ssid != priv->ssid) { + if (!ssid || !priv->ssid) + return FALSE; + if (!g_bytes_equal(ssid, priv->ssid)) + return FALSE; + } + + bssid = nm_setting_wireless_get_bssid(s_wireless); + if (bssid && (!priv->address || !nm_utils_hwaddr_matches(bssid, -1, priv->address, -1))) + return FALSE; + + mode = nm_setting_wireless_get_mode(s_wireless); + if (mode) { + if (!strcmp(mode, "infrastructure") && (priv->mode != NM_802_11_MODE_INFRA)) + return FALSE; + if (!strcmp(mode, "adhoc") && (priv->mode != NM_802_11_MODE_ADHOC)) + return FALSE; + if (!strcmp(mode, "ap") && (priv->mode != NM_802_11_MODE_INFRA || priv->hotspot != TRUE)) + return FALSE; + if (!strcmp(mode, "mesh") && (priv->mode != NM_802_11_MODE_MESH)) + return FALSE; + } + + band = nm_setting_wireless_get_band(s_wireless); + if (band) { + guint ap_band = freq_to_band(priv->freq); + + if (!strcmp(band, "a") && ap_band != 5) + return FALSE; + else if (!strcmp(band, "bg") && ap_band != 2) + return FALSE; + } + + channel = nm_setting_wireless_get_channel(s_wireless); + if (channel) { + guint32 ap_chan = nm_utils_wifi_freq_to_channel(priv->freq); + + if (channel != ap_chan) + return FALSE; + } + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + + return nm_setting_wireless_ap_security_compatible(s_wireless, + s_wireless_sec, + priv->flags, + priv->wpa_flags, + priv->rsn_flags, + priv->mode); +} + +gboolean +nm_wifi_ap_complete_connection(NMWifiAP * self, + NMConnection *connection, + gboolean lock_bssid, + GError ** error) +{ + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self); + + g_return_val_if_fail(connection != NULL, FALSE); + + return nm_wifi_utils_complete_connection(priv->ssid, + priv->address, + priv->mode, + priv->freq, + priv->flags, + priv->wpa_flags, + priv->rsn_flags, + connection, + lock_bssid, + error); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMWifiAP * self = NM_WIFI_AP(object); + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_FLAGS: + g_value_set_uint(value, priv->flags); + break; + case PROP_WPA_FLAGS: + g_value_set_uint(value, priv->wpa_flags); + break; + case PROP_RSN_FLAGS: + g_value_set_uint(value, priv->rsn_flags); + break; + case PROP_SSID: + g_value_take_variant(value, nm_utils_gbytes_to_variant_ay(priv->ssid)); + break; + case PROP_FREQUENCY: + g_value_set_uint(value, priv->freq); + break; + case PROP_HW_ADDRESS: + g_value_set_string(value, priv->address); + break; + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_MAX_BITRATE: + g_value_set_uint(value, priv->max_bitrate); + break; + case PROP_STRENGTH: + g_value_set_uchar(value, priv->strength); + break; + case PROP_LAST_SEEN: + g_value_set_int(value, + priv->last_seen_msec != G_MININT64 ? (int) NM_MAX( + nm_utils_monotonic_timestamp_as_boottime(priv->last_seen_msec, + NM_UTILS_NSEC_PER_MSEC) + / 1000, + 1) + : -1); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_wifi_ap_init(NMWifiAP *self) +{ + NMWifiAPPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_WIFI_AP, NMWifiAPPrivate); + + self->_priv = priv; + + c_list_init(&self->aps_lst); + + priv->mode = NM_802_11_MODE_INFRA; + priv->flags = NM_802_11_AP_FLAGS_NONE; + priv->wpa_flags = NM_802_11_AP_SEC_NONE; + priv->rsn_flags = NM_802_11_AP_SEC_NONE; + priv->last_seen_msec = G_MININT64; +} + +NMWifiAP * +nm_wifi_ap_new_from_properties(const NMSupplicantBssInfo *bss_info) +{ + NMWifiAP *ap; + + ap = g_object_new(NM_TYPE_WIFI_AP, NULL); + nm_wifi_ap_update_from_properties(ap, bss_info); + return ap; +} + +NMWifiAP * +nm_wifi_ap_new_fake_from_connection(NMConnection *connection) +{ + NMWifiAP * ap; + NMWifiAPPrivate * priv; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + const char * mode, *band, *key_mgmt; + guint32 channel; + NM80211ApSecurityFlags flags; + gboolean psk = FALSE, eap = FALSE, adhoc = FALSE; + + g_return_val_if_fail(connection != NULL, NULL); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_val_if_fail(s_wireless != NULL, NULL); + + ap = g_object_new(NM_TYPE_WIFI_AP, NULL); + priv = NM_WIFI_AP_GET_PRIVATE(ap); + priv->fake = TRUE; + + nm_wifi_ap_set_ssid(ap, nm_setting_wireless_get_ssid(s_wireless)); + + // FIXME: bssid too? + + mode = nm_setting_wireless_get_mode(s_wireless); + if (mode) { + if (!strcmp(mode, "infrastructure")) + nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA); + else if (!strcmp(mode, "adhoc")) { + nm_wifi_ap_set_mode(ap, NM_802_11_MODE_ADHOC); + adhoc = TRUE; + } else if (!strcmp(mode, "mesh")) + nm_wifi_ap_set_mode(ap, NM_802_11_MODE_MESH); + else if (!strcmp(mode, "ap")) { + nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA); + NM_WIFI_AP_GET_PRIVATE(ap)->hotspot = TRUE; + } else + goto error; + } else { + nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA); + } + + band = nm_setting_wireless_get_band(s_wireless); + channel = nm_setting_wireless_get_channel(s_wireless); + + if (band && channel) { + guint32 freq = nm_utils_wifi_channel_to_freq(channel, band); + + if (freq == 0) + goto error; + + nm_wifi_ap_set_freq(ap, freq); + } + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + /* Assume presence of a security setting means the AP is encrypted */ + if (!s_wireless_sec) + goto done; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec); + + /* Everything below here uses encryption */ + nm_wifi_ap_set_flags(ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY); + + /* Static & Dynamic WEP */ + if (!strcmp(key_mgmt, "none") || !strcmp(key_mgmt, "ieee8021x")) + goto done; + + psk = nm_streq(key_mgmt, "wpa-psk"); + eap = nm_streq(key_mgmt, "wpa-eap") || nm_streq(key_mgmt, "wpa-eap-suite-b-192"); + if (!adhoc && (psk || eap)) { + if (has_proto(s_wireless_sec, PROTO_WPA)) { + flags = priv->wpa_flags + | (eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK); + nm_wifi_ap_set_wpa_flags(ap, flags); + } + if (has_proto(s_wireless_sec, PROTO_RSN)) { + flags = priv->rsn_flags + | (eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK); + nm_wifi_ap_set_rsn_flags(ap, flags); + } + + add_pair_ciphers(ap, s_wireless_sec); + add_group_ciphers(ap, s_wireless_sec); + } else if (adhoc && psk) { + /* Ad-Hoc has special requirements: proto=RSN, pairwise=CCMP and + * group=CCMP. + */ + flags = priv->wpa_flags | NM_802_11_AP_SEC_KEY_MGMT_PSK; + + /* Clear ciphers; only CCMP is supported */ + flags &= ~(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104 + | NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_GROUP_WEP40 + | NM_802_11_AP_SEC_GROUP_WEP104 | NM_802_11_AP_SEC_GROUP_TKIP); + + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + nm_wifi_ap_set_rsn_flags(ap, flags); + + /* Don't use Ad-Hoc WPA (WPA-none) anymore */ + nm_wifi_ap_set_wpa_flags(ap, NM_802_11_AP_SEC_NONE); + } +done: + return ap; + +error: + g_object_unref(ap); + return NULL; +} + +static void +finalize(GObject *object) +{ + NMWifiAP * self = NM_WIFI_AP(object); + NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self); + + nm_assert(!self->wifi_device); + nm_assert(c_list_is_empty(&self->aps_lst)); + + nm_ref_string_unref(self->_supplicant_path); + if (priv->ssid) + g_bytes_unref(priv->ssid); + g_free(priv->address); + + G_OBJECT_CLASS(nm_wifi_ap_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_access_point = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_ACCESS_POINT, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Flags", "u", NM_WIFI_AP_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WpaFlags", "u", NM_WIFI_AP_WPA_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RsnFlags", "u", NM_WIFI_AP_RSN_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ssid", "ay", NM_WIFI_AP_SSID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Frequency", + "u", + NM_WIFI_AP_FREQUENCY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_WIFI_AP_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_WIFI_AP_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("MaxBitrate", + "u", + NM_WIFI_AP_MAX_BITRATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Strength", "y", NM_WIFI_AP_STRENGTH), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("LastSeen", + "i", + NM_WIFI_AP_LAST_SEEN), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_wifi_ap_class_init(NMWifiAPClass *ap_class) +{ +#define ALL_SEC_FLAGS \ + (NM_802_11_AP_SEC_NONE | NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104 \ + | NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_WEP40 \ + | NM_802_11_AP_SEC_GROUP_WEP104 | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_GROUP_CCMP \ + | NM_802_11_AP_SEC_KEY_MGMT_PSK | NM_802_11_AP_SEC_KEY_MGMT_802_1X \ + | NM_802_11_AP_SEC_KEY_MGMT_SAE | NM_802_11_AP_SEC_KEY_MGMT_OWE \ + | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM | NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192) + + GObjectClass * object_class = G_OBJECT_CLASS(ap_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(ap_class); + + g_type_class_add_private(object_class, sizeof(NMWifiAPPrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH_ACCESS_POINT); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_access_point); + + object_class->get_property = get_property; + object_class->finalize = finalize; + + obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_WIFI_AP_FLAGS, + "", + "", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_FLAGS_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WPA_FLAGS] = g_param_spec_uint(NM_WIFI_AP_WPA_FLAGS, + "", + "", + NM_802_11_AP_SEC_NONE, + ALL_SEC_FLAGS, + NM_802_11_AP_SEC_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_RSN_FLAGS] = g_param_spec_uint(NM_WIFI_AP_RSN_FLAGS, + "", + "", + NM_802_11_AP_SEC_NONE, + ALL_SEC_FLAGS, + NM_802_11_AP_SEC_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SSID] = g_param_spec_variant(NM_WIFI_AP_SSID, + "", + "", + G_VARIANT_TYPE("ay"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FREQUENCY] = g_param_spec_uint(NM_WIFI_AP_FREQUENCY, + "", + "", + 0, + 10000, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HW_ADDRESS] = + g_param_spec_string(NM_WIFI_AP_HW_ADDRESS, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MODE] = g_param_spec_uint(NM_WIFI_AP_MODE, + "", + "", + NM_802_11_MODE_ADHOC, + NM_802_11_MODE_INFRA, + NM_802_11_MODE_INFRA, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MAX_BITRATE] = g_param_spec_uint(NM_WIFI_AP_MAX_BITRATE, + "", + "", + 0, + G_MAXUINT16, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STRENGTH] = g_param_spec_uchar(NM_WIFI_AP_STRENGTH, + "", + "", + 0, + G_MAXINT8, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LAST_SEEN] = g_param_spec_int(NM_WIFI_AP_LAST_SEEN, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +const char ** +nm_wifi_aps_get_paths(const CList *aps_lst_head, gboolean include_without_ssid) +{ + NMWifiAP * ap; + gsize i, n; + const char **list; + const char * path; + + n = c_list_length(aps_lst_head); + list = g_new(const char *, n + 1); + + i = 0; + if (n > 0) { + c_list_for_each_entry (ap, aps_lst_head, aps_lst) { + nm_assert(i < n); + if (!include_without_ssid && !nm_wifi_ap_get_ssid(ap)) + continue; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)); + nm_assert(path); + + list[i++] = path; + } + nm_assert(i <= n); + nm_assert(!include_without_ssid || i == n); + } + list[i] = NULL; + return list; +} + +NMWifiAP * +nm_wifi_aps_find_first_compatible(const CList *aps_lst_head, NMConnection *connection) +{ + NMWifiAP *ap; + + g_return_val_if_fail(connection, NULL); + + c_list_for_each_entry (ap, aps_lst_head, aps_lst) { + if (nm_wifi_ap_check_compatible(ap, connection)) + return ap; + } + return NULL; +} + +/*****************************************************************************/ + +NMWifiAP * +nm_wifi_ap_lookup_for_device(NMDevice *device, const char *exported_path) +{ + NMWifiAP *ap; + + g_return_val_if_fail(NM_IS_DEVICE(device), NULL); + + ap = nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(device)), + exported_path); + if (!ap || !NM_IS_WIFI_AP(ap) || ap->wifi_device != device) + return NULL; + + return ap; +} diff --git a/src/core/devices/wifi/nm-wifi-ap.h b/src/core/devices/wifi/nm-wifi-ap.h new file mode 100644 index 0000000..bdd7241 --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-ap.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_WIFI_AP_H__ +#define __NM_WIFI_AP_H__ + +#include "nm-dbus-object.h" +#include "nm-dbus-interface.h" +#include "nm-connection.h" + +#define NM_TYPE_WIFI_AP (nm_wifi_ap_get_type()) +#define NM_WIFI_AP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_AP, NMWifiAP)) +#define NM_WIFI_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_AP, NMWifiAPClass)) +#define NM_IS_WIFI_AP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_AP)) +#define NM_IS_WIFI_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_AP)) +#define NM_WIFI_AP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_AP, NMWifiAPClass)) + +#define NM_WIFI_AP_FLAGS "flags" +#define NM_WIFI_AP_WPA_FLAGS "wpa-flags" +#define NM_WIFI_AP_RSN_FLAGS "rsn-flags" +#define NM_WIFI_AP_SSID "ssid" +#define NM_WIFI_AP_FREQUENCY "frequency" +#define NM_WIFI_AP_HW_ADDRESS "hw-address" +#define NM_WIFI_AP_MODE "mode" +#define NM_WIFI_AP_MAX_BITRATE "max-bitrate" +#define NM_WIFI_AP_STRENGTH "strength" +#define NM_WIFI_AP_LAST_SEEN "last-seen" + +typedef struct { + NMDBusObject parent; + NMDevice * wifi_device; + CList aps_lst; + NMRefString * _supplicant_path; + struct _NMWifiAPPrivate *_priv; +} NMWifiAP; + +struct _NMSupplicantBssInfo; + +typedef struct _NMWifiAPClass NMWifiAPClass; + +GType nm_wifi_ap_get_type(void); + +NMWifiAP *nm_wifi_ap_new_from_properties(const struct _NMSupplicantBssInfo *bss_info); +NMWifiAP *nm_wifi_ap_new_fake_from_connection(NMConnection *connection); + +gboolean nm_wifi_ap_update_from_properties(NMWifiAP * ap, + const struct _NMSupplicantBssInfo *bss_info); + +gboolean nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection); + +gboolean nm_wifi_ap_complete_connection(NMWifiAP * self, + NMConnection *connection, + gboolean lock_bssid, + GError ** error); + +static inline NMRefString * +nm_wifi_ap_get_supplicant_path(NMWifiAP *ap) +{ + g_return_val_if_fail(NM_IS_WIFI_AP(ap), NULL); + + return ap->_supplicant_path; +} + +GBytes * nm_wifi_ap_get_ssid(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid); +const char * nm_wifi_ap_get_address(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr); +gboolean nm_wifi_ap_set_address_bin(NMWifiAP *ap, const NMEtherAddr *addr); +NM80211Mode nm_wifi_ap_get_mode(NMWifiAP *ap); +gboolean nm_wifi_ap_is_hotspot(NMWifiAP *ap); +gint8 nm_wifi_ap_get_strength(NMWifiAP *ap); +gboolean nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength); +guint32 nm_wifi_ap_get_freq(NMWifiAP *ap); +gboolean nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq); +guint32 nm_wifi_ap_get_max_bitrate(NMWifiAP *ap); +gboolean nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate); +gboolean nm_wifi_ap_get_fake(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake); +NM80211ApFlags nm_wifi_ap_get_flags(const NMWifiAP *self); +gboolean nm_wifi_ap_get_metered(const NMWifiAP *self); +NM80211ApSecurityFlags nm_wifi_ap_get_wpa_flags(const NMWifiAP *self); +NM80211ApSecurityFlags nm_wifi_ap_get_rsn_flags(const NMWifiAP *self); + +const char * +nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 now_msec); + +const char **nm_wifi_aps_get_paths(const CList *aps_lst_head, gboolean include_without_ssid); + +NMWifiAP *nm_wifi_aps_find_first_compatible(const CList *aps_lst_head, NMConnection *connection); + +NMWifiAP *nm_wifi_ap_lookup_for_device(NMDevice *device, const char *exported_path); + +#endif /* __NM_WIFI_AP_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c new file mode 100644 index 0000000..053537a --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-common.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-common.h" + +#include "devices/nm-device.h" +#include "nm-wifi-ap.h" +#include "nm-device-wifi.h" +#include "nm-dbus-manager.h" + +#if WITH_IWD + #include "nm-device-iwd.h" +#endif + +/*****************************************************************************/ + +void +nm_device_wifi_emit_signal_access_point(NMDevice *device, + NMWifiAP *ap, + gboolean is_added /* or else is_removed */) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(device), + &nm_interface_info_device_wireless, + is_added ? &nm_signal_info_wireless_access_point_added + : &nm_signal_info_wireless_access_point_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap))); +} + +/*****************************************************************************/ + +static const CList * +_dispatch_get_aps(NMDevice *device) +{ +#if WITH_IWD + if (NM_IS_DEVICE_IWD(device)) + return _nm_device_iwd_get_aps(NM_DEVICE_IWD(device)); +#endif + return _nm_device_wifi_get_aps(NM_DEVICE_WIFI(device)); +} + +static void +_dispatch_request_scan(NMDevice *device, GVariant *options, GDBusMethodInvocation *invocation) +{ +#if WITH_IWD + if (NM_IS_DEVICE_IWD(device)) { + _nm_device_iwd_request_scan(NM_DEVICE_IWD(device), options, invocation); + return; + } +#endif + _nm_device_wifi_request_scan(NM_DEVICE_WIFI(device), options, invocation); +} + +static void +impl_device_wifi_get_access_points(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + gs_free const char **list = NULL; + GVariant * v; + const CList * all_aps; + + /* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */ + + all_aps = _dispatch_get_aps(NM_DEVICE(obj)); + list = nm_wifi_aps_get_paths(all_aps, FALSE); + v = g_variant_new_objv(list, -1); + g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&v, 1)); +} + +static void +impl_device_wifi_get_all_access_points(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + gs_free const char **list = NULL; + GVariant * v; + const CList * all_aps; + + /* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */ + + all_aps = _dispatch_get_aps(NM_DEVICE(obj)); + list = nm_wifi_aps_get_paths(all_aps, TRUE); + v = g_variant_new_objv(list, -1); + g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&v, 1)); +} + +static void +impl_device_wifi_request_scan(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + gs_unref_variant GVariant *options = NULL; + + /* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */ + + g_variant_get(parameters, "(@a{sv})", &options); + + _dispatch_request_scan(NM_DEVICE(obj), options, invocation); +} + +const GDBusSignalInfo nm_signal_info_wireless_access_point_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "AccessPointAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("access_point", "o"), ), ); + +const GDBusSignalInfo nm_signal_info_wireless_access_point_removed = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "AccessPointRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("access_point", "o"), ), ); + +const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WIRELESS, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetAccessPoints", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("access_points", "ao"), ), ), + .handle = impl_device_wifi_get_access_points, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetAllAccessPoints", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("access_points", "ao"), ), ), + .handle = impl_device_wifi_get_all_access_points, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "RequestScan", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), ), + .handle = impl_device_wifi_request_scan, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &nm_signal_info_wireless_access_point_added, + &nm_signal_info_wireless_access_point_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress", + "s", + NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PermHwAddress", + "s", + NM_DEVICE_PERM_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_DEVICE_WIFI_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Bitrate", + "u", + NM_DEVICE_WIFI_BITRATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AccessPoints", + "ao", + NM_DEVICE_WIFI_ACCESS_POINTS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveAccessPoint", + "o", + NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WirelessCapabilities", + "u", + NM_DEVICE_WIFI_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastScan", + "x", + NM_DEVICE_WIFI_LAST_SCAN), ), ), + .legacy_property_changed = TRUE, +}; diff --git a/src/core/devices/wifi/nm-wifi-common.h b/src/core/devices/wifi/nm-wifi-common.h new file mode 100644 index 0000000..fd6f47e --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-common.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_WIFI_COMMON_H__ +#define __NM_WIFI_COMMON_H__ + +#include "nm-dbus-utils.h" +#include "nm-wifi-ap.h" + +/*****************************************************************************/ + +void nm_device_wifi_emit_signal_access_point(NMDevice *device, + NMWifiAP *ap, + gboolean is_added /* or else is_removed */); + +extern const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless; +extern const GDBusSignalInfo nm_signal_info_wireless_access_point_added; +extern const GDBusSignalInfo nm_signal_info_wireless_access_point_removed; + +#endif /* __NM_WIFI_COMMON_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c new file mode 100644 index 0000000..c4afd3f --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-factory.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "devices/nm-device-factory.h" +#include "nm-setting-wireless.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-device-wifi.h" +#include "nm-device-wifi-p2p.h" +#include "nm-device-olpc-mesh.h" +#include "nm-device-iwd.h" +#include "settings/nm-settings-connection.h" +#include "platform/nm-platform.h" +#include "nm-config.h" + +/*****************************************************************************/ + +#define NM_TYPE_WIFI_FACTORY (nm_wifi_factory_get_type()) +#define NM_WIFI_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_FACTORY, NMWifiFactory)) +#define NM_WIFI_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_FACTORY, NMWifiFactoryClass)) +#define NM_IS_WIFI_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_FACTORY)) +#define NM_IS_WIFI_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_FACTORY)) +#define NM_WIFI_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_FACTORY, NMWifiFactoryClass)) + +typedef struct { + NMDeviceFactory parent; +} NMWifiFactory; + +typedef struct { + NMDeviceFactoryClass parent; +} NMWifiFactoryClass; + +static GType nm_wifi_factory_get_type(void); + +G_DEFINE_TYPE(NMWifiFactory, nm_wifi_factory, NM_TYPE_DEVICE_FACTORY) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES( + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_WIFI, NM_LINK_TYPE_OLPC_MESH) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_OLPC_MESH_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + return g_object_new(NM_TYPE_WIFI_FACTORY, NULL); +} + +/*****************************************************************************/ + +static void +p2p_device_created(NMDeviceWifi *device, NMDeviceWifiP2P *p2p_device, NMDeviceFactory *self) +{ + nm_log_info(LOGD_PLATFORM | LOGD_WIFI, + "Wi-Fi P2P device controlled by interface %s created", + nm_device_get_iface(NM_DEVICE(device))); + + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device); +} + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + gs_free char *backend = NULL; + + g_return_val_if_fail(iface != NULL, NULL); + g_return_val_if_fail(plink != NULL, NULL); + g_return_val_if_fail(g_strcmp0(iface, plink->name) == 0, NULL); + g_return_val_if_fail(NM_IN_SET(plink->type, NM_LINK_TYPE_WIFI, NM_LINK_TYPE_OLPC_MESH), NULL); + + if (plink->type != NM_LINK_TYPE_WIFI) + return nm_device_olpc_mesh_new(iface); + + backend = nm_config_data_get_device_config_by_pllink(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND, + plink, + "wifi", + NULL); + nm_strstrip(backend); + + nm_log_dbg(LOGD_PLATFORM | LOGD_WIFI, + "(%s) config: backend is %s%s%s%s", + iface, + NM_PRINT_FMT_QUOTE_STRING(backend), + WITH_IWD ? " (iwd support enabled)" : ""); + if (!backend || !g_ascii_strcasecmp(backend, "wpa_supplicant")) { + NMDevice * device; + NMDeviceWifiCapabilities capabilities; + NM80211Mode mode; + + if (!nm_platform_wifi_get_capabilities(NM_PLATFORM_GET, plink->ifindex, &capabilities)) { + nm_log_warn(LOGD_PLATFORM | LOGD_WIFI, + "(%s) failed to initialize Wi-Fi driver for ifindex %d", + iface, + plink->ifindex); + return NULL; + } + + /* Ignore monitor-mode and other unhandled interface types. + * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage + * them if/when they change to a handled type. + */ + mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex); + if (mode == NM_802_11_MODE_UNKNOWN) { + *out_ignore = TRUE; + return NULL; + } + + device = nm_device_wifi_new(iface, capabilities); + + g_signal_connect_object(device, + NM_DEVICE_WIFI_P2P_DEVICE_CREATED, + G_CALLBACK(p2p_device_created), + factory, + 0); + + return device; + } +#if WITH_IWD + else if (!g_ascii_strcasecmp(backend, "iwd")) + return nm_device_iwd_new(iface); +#endif + + nm_log_warn(LOGD_PLATFORM | LOGD_WIFI, + "(%s) config: unknown or unsupported wifi-backend %s", + iface, + backend); + return NULL; +} + +/*****************************************************************************/ + +static void +nm_wifi_factory_init(NMWifiFactory *self) +{} + +static void +nm_wifi_factory_class_init(NMWifiFactoryClass *klass) +{ + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + factory_class->create_device = create_device; + factory_class->get_supported_types = get_supported_types; +} diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c new file mode 100644 index 0000000..01f4f9b --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c @@ -0,0 +1,699 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-p2p-peer.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "devices/nm-device.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-setting-wireless.h" +#include "nm-utils.h" +#include "nm-wifi-utils.h" +#include "platform/nm-platform.h" +#include "supplicant/nm-supplicant-types.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMWifiP2PPeer, + PROP_NAME, + PROP_MANUFACTURER, + PROP_MODEL, + PROP_MODEL_NUMBER, + PROP_SERIAL, + PROP_WFD_IES, + PROP_HW_ADDRESS, + PROP_STRENGTH, + PROP_LAST_SEEN, + PROP_FLAGS, ); + +struct _NMWifiP2PPeerPrivate { + NMRefString *supplicant_path; /* D-Bus object path of this Peer from wpa_supplicant */ + + /* Scanned or cached values */ + char *name; + char *manufacturer; + char *model; + char *model_number; + char *serial; + + char *address; + + GBytes *wfd_ies; + + const char **groups; + + guint8 strength; + + NM80211ApFlags flags; /* General flags */ + + /* Non-scanned attributes */ + gint32 + last_seen; /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_sec()) */ +}; + +typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate; + +struct _NMWifiP2PPeerClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMWifiP2PPeer, nm_wifi_p2p_peer, NM_TYPE_DBUS_OBJECT) + +#define NM_WIFI_P2P_PEER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMWifiP2PPeer, NM_IS_WIFI_P2P_PEER) + +/*****************************************************************************/ + +const char ** +nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head) +{ + NMWifiP2PPeer *peer; + const char ** list; + const char * path; + gsize i, n; + + n = c_list_length(peers_lst_head); + list = g_new(const char *, n + 1); + + i = 0; + if (n > 0) { + c_list_for_each_entry (peer, peers_lst_head, peers_lst) { + nm_assert(i < n); + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(peer)); + nm_assert(path); + + list[i++] = path; + } + nm_assert(i <= n); + } + list[i] = NULL; + return list; +} + +NMWifiP2PPeer * +nm_wifi_p2p_peers_find_first_compatible(const CList *peers_lst_head, NMConnection *connection) +{ + NMWifiP2PPeer *peer; + + g_return_val_if_fail(connection, NULL); + + c_list_for_each_entry (peer, peers_lst_head, peers_lst) { + if (nm_wifi_p2p_peer_check_compatible(peer, connection)) + return peer; + } + return NULL; +} + +NMWifiP2PPeer * +nm_wifi_p2p_peers_find_by_supplicant_path(const CList *peers_lst_head, const char *path) +{ + NMWifiP2PPeer *peer; + + g_return_val_if_fail(path != NULL, NULL); + + c_list_for_each_entry (peer, peers_lst_head, peers_lst) { + if (nm_streq0(path, nm_wifi_p2p_peer_get_supplicant_path(peer))) + return peer; + } + return NULL; +} + +/*****************************************************************************/ + +NMWifiP2PPeer * +nm_wifi_p2p_peer_lookup_for_device(NMDevice *device, const char *exported_path) +{ + NMWifiP2PPeer *peer; + + g_return_val_if_fail(NM_IS_DEVICE(device), NULL); + + peer = (NMWifiP2PPeer *) nm_dbus_manager_lookup_object( + nm_dbus_object_get_manager(NM_DBUS_OBJECT(device)), + exported_path); + if (!peer || !NM_IS_WIFI_P2P_PEER(peer) || peer->wifi_device != device) + return NULL; + + return peer; +} + +/*****************************************************************************/ + +const char * +nm_wifi_p2p_peer_get_supplicant_path(NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return nm_ref_string_get_str(NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->supplicant_path); +} + +const char * +nm_wifi_p2p_peer_get_name(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->name; +} + +gboolean +nm_wifi_p2p_peer_set_name(NMWifiP2PPeer *peer, const char *str) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (!nm_utils_strdup_reset(&priv->name, str)) + return FALSE; + _notify(peer, PROP_NAME); + return TRUE; +} + +const char * +nm_wifi_p2p_peer_get_manufacturer(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->manufacturer; +} + +gboolean +nm_wifi_p2p_peer_set_manufacturer(NMWifiP2PPeer *peer, const char *str) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (!nm_utils_strdup_reset(&priv->manufacturer, str)) + return FALSE; + _notify(peer, PROP_MANUFACTURER); + return TRUE; +} + +const char * +nm_wifi_p2p_peer_get_model(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->model; +} + +gboolean +nm_wifi_p2p_peer_set_model(NMWifiP2PPeer *peer, const char *str) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (!nm_utils_strdup_reset(&priv->model, str)) + return FALSE; + _notify(peer, PROP_MODEL); + return TRUE; +} + +const char * +nm_wifi_p2p_peer_get_model_number(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->model_number; +} + +gboolean +nm_wifi_p2p_peer_set_model_number(NMWifiP2PPeer *peer, const char *str) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (!nm_utils_strdup_reset(&priv->model_number, str)) + return FALSE; + _notify(peer, PROP_MODEL_NUMBER); + return TRUE; +} + +const char * +nm_wifi_p2p_peer_get_serial(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->serial; +} + +gboolean +nm_wifi_p2p_peer_set_serial(NMWifiP2PPeer *peer, const char *str) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (!nm_utils_strdup_reset(&priv->serial, str)) + return FALSE; + _notify(peer, PROP_SERIAL); + return TRUE; +} + +GBytes * +nm_wifi_p2p_peer_get_wfd_ies(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->wfd_ies; +} + +gboolean +nm_wifi_p2p_peer_set_wfd_ies(NMWifiP2PPeer *peer, GBytes *wfd_ies) +{ + NMWifiP2PPeerPrivate *priv; + gs_unref_bytes GBytes *wfd_ies_old = NULL; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE); + + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (nm_gbytes_equal0(priv->wfd_ies, wfd_ies)) + return FALSE; + + wfd_ies_old = g_steal_pointer(&priv->wfd_ies); + priv->wfd_ies = wfd_ies ? g_bytes_ref(wfd_ies) : NULL; + + _notify(peer, PROP_WFD_IES); + return TRUE; +} + +const char *const * +nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->groups; +} + +const char * +nm_wifi_p2p_peer_get_address(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->address; +} + +static gboolean +nm_wifi_p2p_peer_set_address_bin(NMWifiP2PPeer *peer, const guint8 addr[static ETH_ALEN]) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (priv->address && nm_utils_hwaddr_matches(addr, ETH_ALEN, priv->address, -1)) + return FALSE; + + g_free(priv->address); + priv->address = nm_utils_hwaddr_ntoa(addr, ETH_ALEN); + _notify(peer, PROP_HW_ADDRESS); + return TRUE; +} + +gboolean +nm_wifi_p2p_peer_set_address(NMWifiP2PPeer *peer, const char *addr) +{ + guint8 addr_buf[ETH_ALEN]; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE); + + if (!addr || !nm_utils_hwaddr_aton(addr, addr_buf, sizeof(addr_buf))) + g_return_val_if_reached(FALSE); + + return nm_wifi_p2p_peer_set_address_bin(peer, addr_buf); +} + +gint8 +nm_wifi_p2p_peer_get_strength(NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), 0); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->strength; +} + +gboolean +nm_wifi_p2p_peer_set_strength(NMWifiP2PPeer *peer, const gint8 strength) +{ + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (priv->strength != strength) { + priv->strength = strength; + _notify(peer, PROP_STRENGTH); + return TRUE; + } + return FALSE; +} + +NM80211ApFlags +nm_wifi_p2p_peer_get_flags(const NMWifiP2PPeer *peer) +{ + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NM_802_11_AP_FLAGS_NONE); + + return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->flags; +} + +static gboolean +nm_wifi_p2p_peer_set_last_seen(NMWifiP2PPeer *peer, gint32 last_seen) +{ + NMWifiP2PPeerPrivate *priv; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE); + + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + if (priv->last_seen != last_seen) { + priv->last_seen = last_seen; + _notify(peer, PROP_LAST_SEEN); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +gboolean +nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantPeerInfo *peer_info) +{ + NMWifiP2PPeerPrivate *priv; + gboolean changed = FALSE; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE); + g_return_val_if_fail(peer_info, FALSE); + nm_assert(NM_IS_REF_STRING(peer_info->peer_path)); + + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + + nm_assert(!priv->supplicant_path || priv->supplicant_path == peer_info->peer_path); + + g_object_freeze_notify(G_OBJECT(peer)); + + if (!priv->supplicant_path) { + priv->supplicant_path = nm_ref_string_ref(peer_info->peer_path); + changed = TRUE; + } + + changed |= nm_wifi_p2p_peer_set_strength(peer, peer_info->signal_percent); + changed |= nm_wifi_p2p_peer_set_name(peer, peer_info->device_name); + changed |= nm_wifi_p2p_peer_set_manufacturer(peer, peer_info->manufacturer); + changed |= nm_wifi_p2p_peer_set_model(peer, peer_info->model); + changed |= nm_wifi_p2p_peer_set_model_number(peer, peer_info->model_number); + changed |= nm_wifi_p2p_peer_set_serial(peer, peer_info->serial); + + if (peer_info->address_valid) + changed |= nm_wifi_p2p_peer_set_address_bin(peer, peer_info->address); + else { + /* we don't reset the address. */ + } + + changed |= nm_wifi_p2p_peer_set_wfd_ies(peer, peer_info->ies); + changed |= nm_wifi_p2p_peer_set_last_seen(peer, peer_info->last_seen_msec / 1000u); + + /* We currently only use the groups information internally to check if + * the peer is still joined. */ + if (!nm_utils_strv_equal(priv->groups, peer_info->groups)) { + g_free(priv->groups); + priv->groups = nm_utils_strv_dup_packed(peer_info->groups, -1); + changed |= TRUE; + } + + g_object_thaw_notify(G_OBJECT(peer)); + + return changed; +} + +const char * +nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_len, gint32 now_s) +{ + const NMWifiP2PPeerPrivate *priv; + const char * supplicant_id = "-"; + const char * export_path; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(self), NULL); + + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self); + + if (priv->supplicant_path) + supplicant_id = strrchr(priv->supplicant_path->str, '/') ?: supplicant_id; + + export_path = nm_dbus_object_get_path(NM_DBUS_OBJECT(self)); + if (export_path) + export_path = strrchr(export_path, '/') ?: export_path; + else + export_path = "/"; + + g_snprintf(str_buf, + buf_len, + "%17s [n:%s, m:%s, mod:%s, mod_num:%s, ser:%s] %3us sup:%s [nm:%s]", + priv->address ?: "(none)", + priv->name, + priv->manufacturer, + priv->model, + priv->model_number, + priv->serial, + priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_sec()) + - priv->last_seen) + : -1, + supplicant_id, + export_path); + + return str_buf; +} + +gboolean +nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection) +{ + NMWifiP2PPeerPrivate *priv; + NMSettingWifiP2P * s_wifi_p2p; + const char * hwaddr; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self); + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + if (s_wifi_p2p == NULL) + return FALSE; + + hwaddr = nm_setting_wifi_p2p_get_peer(s_wifi_p2p); + if (hwaddr && (!priv->address || !nm_utils_hwaddr_matches(hwaddr, -1, priv->address, -1))) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMWifiP2PPeer * self = NM_WIFI_P2P_PEER(object); + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_FLAGS: + g_value_set_uint(value, priv->flags); + break; + case PROP_NAME: + g_value_set_string(value, priv->name); + break; + case PROP_MANUFACTURER: + g_value_set_string(value, priv->manufacturer); + break; + case PROP_MODEL: + g_value_set_string(value, priv->model); + break; + case PROP_MODEL_NUMBER: + g_value_set_string(value, priv->model_number); + break; + case PROP_SERIAL: + g_value_set_string(value, priv->serial); + break; + case PROP_WFD_IES: + g_value_take_variant(value, nm_utils_gbytes_to_variant_ay(priv->wfd_ies)); + break; + case PROP_HW_ADDRESS: + g_value_set_string(value, priv->address); + break; + case PROP_STRENGTH: + g_value_set_uchar(value, priv->strength); + break; + case PROP_LAST_SEEN: + g_value_set_int(value, + priv->last_seen > 0 + ? (int) nm_utils_monotonic_timestamp_as_boottime(priv->last_seen, + NM_UTILS_NSEC_PER_SEC) + : -1); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_wifi_p2p_peer_init(NMWifiP2PPeer *self) +{ + NMWifiP2PPeerPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerPrivate); + + self->_priv = priv; + + c_list_init(&self->peers_lst); + + priv->flags = NM_802_11_AP_FLAGS_NONE; + priv->last_seen = -1; +} + +NMWifiP2PPeer * +nm_wifi_p2p_peer_new_from_properties(const NMSupplicantPeerInfo *peer_info) +{ + NMWifiP2PPeer *peer; + + g_return_val_if_fail(peer_info, NULL); + + peer = g_object_new(NM_TYPE_WIFI_P2P_PEER, NULL); + nm_wifi_p2p_peer_update_from_properties(peer, peer_info); + return peer; +} + +static void +finalize(GObject *object) +{ + NMWifiP2PPeer * self = NM_WIFI_P2P_PEER(object); + NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self); + + nm_assert(!self->wifi_device); + nm_assert(c_list_is_empty(&self->peers_lst)); + + nm_ref_string_unref(priv->supplicant_path); + g_free(priv->name); + g_free(priv->manufacturer); + g_free(priv->model); + g_free(priv->model_number); + g_free(priv->serial); + g_free(priv->address); + g_bytes_unref(priv->wfd_ies); + g_free(priv->groups); + + G_OBJECT_CLASS(nm_wifi_p2p_peer_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_WIFI_P2P_PEER, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + /* Before 1.24, we wrongly exposed a property "Groups" of type "as". Don't reuse that property name. */ + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Flags", "u", NM_WIFI_P2P_PEER_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Name", "s", NM_WIFI_P2P_PEER_NAME), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Manufacturer", + "s", + NM_WIFI_P2P_PEER_MANUFACTURER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Model", "s", NM_WIFI_P2P_PEER_MODEL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ModelNumber", + "s", + NM_WIFI_P2P_PEER_MODEL_NUMBER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Serial", "s", NM_WIFI_P2P_PEER_SERIAL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("WfdIEs", + "ay", + NM_WIFI_P2P_PEER_WFD_IES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", + "s", + NM_WIFI_P2P_PEER_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Strength", + "y", + NM_WIFI_P2P_PEER_STRENGTH), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastSeen", + "i", + NM_WIFI_P2P_PEER_LAST_SEEN), ), ), + .legacy_property_changed = FALSE, +}; + +static void +nm_wifi_p2p_peer_class_init(NMWifiP2PPeerClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMWifiP2PPeerPrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH_WIFI_P2P_PEER); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_p2p_peer); + + object_class->get_property = get_property; + object_class->finalize = finalize; + + obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_WIFI_P2P_PEER_FLAGS, + "", + "", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_FLAGS_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NAME] = g_param_spec_string(NM_WIFI_P2P_PEER_NAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MANUFACTURER] = + g_param_spec_string(NM_WIFI_P2P_PEER_MANUFACTURER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MODEL] = g_param_spec_string(NM_WIFI_P2P_PEER_MODEL, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MODEL_NUMBER] = + g_param_spec_string(NM_WIFI_P2P_PEER_MODEL_NUMBER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SERIAL] = g_param_spec_string(NM_WIFI_P2P_PEER_SERIAL, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WFD_IES] = g_param_spec_variant(NM_WIFI_P2P_PEER_WFD_IES, + "", + "", + G_VARIANT_TYPE("ay"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HW_ADDRESS] = + g_param_spec_string(NM_WIFI_P2P_PEER_HW_ADDRESS, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STRENGTH] = g_param_spec_uchar(NM_WIFI_P2P_PEER_STRENGTH, + "", + "", + 0, + G_MAXINT8, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_LAST_SEEN] = g_param_spec_int(NM_WIFI_P2P_PEER_LAST_SEEN, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h new file mode 100644 index 0000000..ee4bfb5 --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_WIFI_P2P_PEER_H__ +#define __NM_WIFI_P2P_PEER_H__ + +#include "nm-dbus-object.h" +#include "nm-dbus-interface.h" +#include "nm-connection.h" + +#define NM_TYPE_WIFI_P2P_PEER (nm_wifi_p2p_peer_get_type()) +#define NM_WIFI_P2P_PEER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer)) +#define NM_WIFI_P2P_PEER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass)) +#define NM_IS_WIFI_P2P_PEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_P2P_PEER)) +#define NM_IS_WIFI_P2P_PEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_P2P_PEER)) +#define NM_WIFI_P2P_PEER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass)) + +#define NM_WIFI_P2P_PEER_FLAGS "flags" +#define NM_WIFI_P2P_PEER_NAME "name" +#define NM_WIFI_P2P_PEER_MANUFACTURER "manufacturer" +#define NM_WIFI_P2P_PEER_MODEL "model" +#define NM_WIFI_P2P_PEER_MODEL_NUMBER "model-number" +#define NM_WIFI_P2P_PEER_SERIAL "serial" +#define NM_WIFI_P2P_PEER_WFD_IES "wfd-ies" +#define NM_WIFI_P2P_PEER_HW_ADDRESS "hw-address" +#define NM_WIFI_P2P_PEER_STRENGTH "strength" +#define NM_WIFI_P2P_PEER_LAST_SEEN "last-seen" + +typedef struct { + NMDBusObject parent; + NMDevice * wifi_device; + CList peers_lst; + struct _NMWifiP2PPeerPrivate *_priv; +} NMWifiP2PPeer; + +typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass; + +struct _NMSupplicantPeerInfo; + +GType nm_wifi_p2p_peer_get_type(void); + +NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_properties(const struct _NMSupplicantPeerInfo *peer_info); + +gboolean nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer * peer, + const struct _NMSupplicantPeerInfo *peer_info); + +gboolean nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection); + +const char *nm_wifi_p2p_peer_get_supplicant_path(NMWifiP2PPeer *peer); + +const char *nm_wifi_p2p_peer_get_name(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_name(NMWifiP2PPeer *peer, const char *name); +const char *nm_wifi_p2p_peer_get_manufacturer(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_manufacturer(NMWifiP2PPeer *peer, const char *manufacturer); +const char *nm_wifi_p2p_peer_get_model(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_model(NMWifiP2PPeer *peer, const char *model); +const char *nm_wifi_p2p_peer_get_model_number(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_model_number(NMWifiP2PPeer *peer, const char *number); +const char *nm_wifi_p2p_peer_get_serial(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_serial(NMWifiP2PPeer *peer, const char *serial); + +GBytes * nm_wifi_p2p_peer_get_wfd_ies(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_wfd_ies(NMWifiP2PPeer *peer, GBytes *bytes); + +const char *const *nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer); + +const char * nm_wifi_p2p_peer_get_address(const NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_address(NMWifiP2PPeer *peer, const char *addr); +gint8 nm_wifi_p2p_peer_get_strength(NMWifiP2PPeer *peer); +gboolean nm_wifi_p2p_peer_set_strength(NMWifiP2PPeer *peer, gint8 strength); +NM80211ApFlags nm_wifi_p2p_peer_get_flags(const NMWifiP2PPeer *self); + +const char * +nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_len, gint32 now_s); + +const char **nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head); + +NMWifiP2PPeer *nm_wifi_p2p_peers_find_first_compatible(const CList * peers_lst_head, + NMConnection *connection); + +NMWifiP2PPeer *nm_wifi_p2p_peers_find_by_supplicant_path(const CList *peers_lst_head, + const char * path); + +NMWifiP2PPeer *nm_wifi_p2p_peer_lookup_for_device(NMDevice *device, const char *exported_path); + +#endif /* __NM_WIFI_P2P_PEER_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c new file mode 100644 index 0000000..f0b772c --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-utils.c @@ -0,0 +1,943 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-utils.h" + +#include + +#include "nm-utils.h" +#include "nm-core-internal.h" + +static gboolean +verify_no_wep(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) +{ + if (nm_setting_wireless_security_get_wep_key(s_wsec, 0) + || nm_setting_wireless_security_get_wep_key(s_wsec, 1) + || nm_setting_wireless_security_get_wep_key(s_wsec, 2) + || nm_setting_wireless_security_get_wep_key(s_wsec, 3) + || nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) + || nm_setting_wireless_security_get_wep_key_type(s_wsec)) { + /* Dynamic WEP cannot have any WEP keys set */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("%s is incompatible with static WEP keys"), + tag); + g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_leap(NMSettingWirelessSecurity *s_wsec, + NMSetting8021x * s_8021x, + gboolean adhoc, + GError ** error) +{ + const char *key_mgmt, *auth_alg, *leap_username; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); + + /* One (or both) of two things indicates we want LEAP: + * 1) auth_alg == 'leap' + * 2) valid leap_username + * + * LEAP always requires a LEAP username. + */ + + if (auth_alg) { + if (!strcmp(auth_alg, "leap")) { + /* LEAP authentication requires at least a LEAP username */ + if (!leap_username) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("LEAP authentication requires a LEAP username")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); + return FALSE; + } + } else if (leap_username) { + /* Leap username requires 'leap' auth */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("LEAP username requires 'leap' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); + return FALSE; + } + } + + if (leap_username) { + if (key_mgmt && strcmp(key_mgmt, "ieee8021x")) { + /* LEAP requires ieee8021x key management */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("LEAP authentication requires IEEE 802.1x key management")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + } + + /* At this point if auth_alg is set it must be 'leap', and if key_mgmt + * is set it must be 'ieee8021x'. + */ + if (leap_username) { + if (auth_alg) + g_assert(strcmp(auth_alg, "leap") == 0); + if (key_mgmt) + g_assert(strcmp(key_mgmt, "ieee8021x") == 0); + + if (adhoc) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("LEAP authentication is incompatible with Ad-Hoc mode")); + g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + return FALSE; + } + + if (!verify_no_wep(s_wsec, "LEAP", error)) + return FALSE; + + if (s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("LEAP authentication is incompatible with 802.1x setting")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +verify_no_wpa(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) +{ + const char *key_mgmt; + int n, i; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + if (key_mgmt && !strncmp(key_mgmt, "wpa", 3)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a connection using '%s' authentication cannot use WPA key management"), + tag); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (nm_setting_wireless_security_get_num_protos(s_wsec)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a connection using '%s' authentication cannot specify WPA protocols"), + tag); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PROTO); + return FALSE; + } + + n = nm_setting_wireless_security_get_num_pairwise(s_wsec); + for (i = 0; i < n; i++) { + const char *pw; + + pw = nm_setting_wireless_security_get_pairwise(s_wsec, i); + if (!strcmp(pw, "tkip") || !strcmp(pw, "ccmp")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a connection using '%s' authentication cannot specify WPA ciphers"), + tag); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PAIRWISE); + return FALSE; + } + } + + n = nm_setting_wireless_security_get_num_groups(s_wsec); + for (i = 0; i < n; i++) { + const char *gr; + + gr = nm_setting_wireless_security_get_group(s_wsec, i); + if (strcmp(gr, "wep40") && strcmp(gr, "wep104")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a connection using '%s' authentication cannot specify WPA ciphers"), + tag); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_GROUP); + return FALSE; + } + } + + if (nm_setting_wireless_security_get_psk(s_wsec)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a connection using '%s' authentication cannot specify a WPA password"), + tag); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PSK); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_dynamic_wep(NMSettingWirelessSecurity *s_wsec, + NMSetting8021x * s_8021x, + gboolean adhoc, + GError ** error) +{ + const char *key_mgmt, *auth_alg, *leap_username; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); + + g_return_val_if_fail(leap_username == NULL, TRUE); + + if (key_mgmt) { + if (!strcmp(key_mgmt, "ieee8021x")) { + if (!s_8021x) { + /* 802.1x key management requires an 802.1x setting */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("Dynamic WEP requires an 802.1x setting")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + + if (auth_alg && strcmp(auth_alg, "open")) { + /* 802.1x key management must use "open" authentication */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Dynamic WEP requires 'open' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + /* Dynamic WEP incompatible with anything static WEP related */ + if (!verify_no_wep(s_wsec, "Dynamic WEP", error)) + return FALSE; + } else if (!strcmp(key_mgmt, "none")) { + if (s_8021x) { + /* 802.1x setting requires 802.1x key management */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Dynamic WEP requires 'ieee8021x' key management")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + } + } else if (s_8021x) { + /* 802.1x setting incompatible with anything but 'open' auth */ + if (auth_alg && strcmp(auth_alg, "open")) { + /* 802.1x key management must use "open" authentication */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Dynamic WEP requires 'open' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + /* Dynamic WEP incompatible with anything static WEP related */ + if (!verify_no_wep(s_wsec, "Dynamic WEP", error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_wpa_psk(NMSettingWirelessSecurity *s_wsec, + NMSetting8021x * s_8021x, + gboolean adhoc, + guint32 wpa_flags, + guint32 rsn_flags, + GError ** error) +{ + const char *key_mgmt, *auth_alg; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + + if (!nm_streq0(key_mgmt, "wpa-psk")) + return TRUE; + + if (s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("WPA-PSK authentication is incompatible with 802.1x")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + + if (auth_alg && !nm_streq(auth_alg, "open")) { + /* WPA must use "open" authentication */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA-PSK requires 'open' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + /* Make sure the AP's capabilities support WPA-PSK */ + if (!(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Access point does not support PSK but setting requires it")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (adhoc) { + /* Ad-Hoc RSN requires 'rsn' proto, 'ccmp' pairwise, and 'ccmp' group */ + if (nm_setting_wireless_security_get_num_protos(s_wsec) != 1 + || !nm_streq0(nm_setting_wireless_security_get_proto(s_wsec, 0), "rsn")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA Ad-Hoc authentication requires 'rsn' protocol")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PROTO); + return FALSE; + } + + if (nm_setting_wireless_security_get_num_pairwise(s_wsec) != 1 + || !nm_streq0(nm_setting_wireless_security_get_pairwise(s_wsec, 0), "ccmp")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA Ad-Hoc authentication requires 'ccmp' pairwise cipher")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PAIRWISE); + return FALSE; + } + + if (nm_setting_wireless_security_get_num_groups(s_wsec) != 1 + || !nm_streq0(nm_setting_wireless_security_get_group(s_wsec, 0), "ccmp")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA Ad-Hoc requires 'ccmp' group cipher")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_GROUP); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +verify_wpa_eap(NMSettingWirelessSecurity *s_wsec, + NMSetting8021x * s_8021x, + guint32 wpa_flags, + guint32 rsn_flags, + GError ** error) +{ + const char *key_mgmt, *auth_alg; + gboolean is_wpa_eap = FALSE; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + + if (key_mgmt) { + if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) { + if (!s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("WPA-EAP authentication requires an 802.1x setting")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + + if (auth_alg && strcmp(auth_alg, "open")) { + /* WPA must use "open" authentication */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA-EAP requires 'open' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + is_wpa_eap = TRUE; + } else if (s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("802.1x setting requires 'wpa-eap' key management")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + } + + if (is_wpa_eap || s_8021x) { + /* Make sure the AP's capabilities support WPA-EAP */ + if (!(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("Access point does not support 802.1x but setting requires it")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +verify_adhoc(NMSettingWirelessSecurity *s_wsec, + NMSetting8021x * s_8021x, + gboolean adhoc, + GError ** error) +{ + const char *key_mgmt = NULL, *leap_username = NULL, *auth_alg = NULL; + + if (!adhoc) + return TRUE; + + if (s_wsec) { + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); + } + + if (key_mgmt && !NM_IN_STRSET(key_mgmt, "none", "wpa-psk")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Ad-Hoc mode requires 'none' or 'wpa-psk' key management")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("Ad-Hoc mode is incompatible with 802.1x security")); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + + if (leap_username) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Ad-Hoc mode is incompatible with LEAP security")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + if (auth_alg && !nm_streq(auth_alg, "open")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Ad-Hoc mode requires 'open' authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_wifi_utils_complete_connection(GBytes * ap_ssid, + const char * bssid, + NM80211Mode ap_mode, + guint32 ap_freq, + guint32 ap_flags, + guint32 ap_wpa_flags, + guint32 ap_rsn_flags, + NMConnection *connection, + gboolean lock_bssid, + GError ** error) +{ + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x * s_8021x; + GBytes * ssid; + const char * mode, *key_mgmt, *auth_alg, *leap_username; + gboolean adhoc = FALSE; + gboolean mesh = FALSE; + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + s_wsec = nm_connection_get_setting_wireless_security(connection); + s_8021x = nm_connection_get_setting_802_1x(connection); + + /* Fill in missing SSID */ + ssid = nm_setting_wireless_get_ssid(s_wifi); + if (!ssid) + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_SSID, ap_ssid, NULL); + else if (!ap_ssid || !g_bytes_equal(ssid, ap_ssid)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("connection does not match access point")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID); + return FALSE; + } + + if (lock_bssid && !nm_setting_wireless_get_bssid(s_wifi)) + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL); + + /* And mode */ + mode = nm_setting_wireless_get_mode(s_wifi); + if (mode) { + gboolean valid = FALSE; + + /* Make sure the supplied mode matches the AP's */ + if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_INFRA) + || !strcmp(mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (ap_mode == NM_802_11_MODE_INFRA) + valid = TRUE; + } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { + if (ap_mode == NM_802_11_MODE_ADHOC) + valid = TRUE; + adhoc = TRUE; + } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_MESH)) { + if (ap_mode == NM_802_11_MODE_MESH) + valid = TRUE; + mesh = TRUE; + } + + if (valid == FALSE) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("connection does not match access point")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MODE); + return FALSE; + } + } else { + mode = NM_SETTING_WIRELESS_MODE_INFRA; + if (ap_mode == NM_802_11_MODE_ADHOC) { + mode = NM_SETTING_WIRELESS_MODE_ADHOC; + adhoc = TRUE; + } else if (ap_mode == NM_802_11_MODE_MESH) { + mode = NM_SETTING_WIRELESS_MODE_MESH; + mesh = TRUE; + } + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, mode, NULL); + } + + /* For now mesh requires channel and band, fill them only if both not present. + * Do not check existing values against an existing ap/mesh point, + * mesh join will start a new network if required */ + if (mesh) { + const char *band; + guint32 channel; + gboolean band_valid = TRUE; + gboolean chan_valid = TRUE; + gboolean valid; + + band = nm_setting_wireless_get_band(s_wifi); + channel = nm_setting_wireless_get_channel(s_wifi); + + valid = ((band == NULL) && (channel == 0)) || ((band != NULL) && (channel != 0)); + + if ((band == NULL) && (channel == 0)) { + channel = nm_utils_wifi_freq_to_channel(ap_freq); + if (channel) { + g_object_set(s_wifi, NM_SETTING_WIRELESS_CHANNEL, channel, NULL); + } else { + chan_valid = FALSE; + } + + band = nm_utils_wifi_freq_to_band(ap_freq); + if (band) { + g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL); + } else { + band_valid = FALSE; + } + } + + if (!valid || !chan_valid || !band_valid) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("connection does not match mesh point")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MODE); + return FALSE; + } + } + + /* Security */ + + /* Open */ + if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY) && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) + && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { + /* Make sure the connection doesn't specify security */ + if (s_wsec || s_8021x) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("Access point is unencrypted but setting specifies security")); + if (s_wsec) + g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + else + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + return TRUE; + } + + /* Everything else requires security */ + if (!s_wsec) { + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); + + /* Ad-Hoc checks */ + if (!verify_adhoc(s_wsec, s_8021x, adhoc, error)) + return FALSE; + + /* Static WEP, Dynamic WEP, or LEAP */ + if ((ap_flags & NM_802_11_AP_FLAGS_PRIVACY) && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) + && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { + const char *tag = "WEP"; + gboolean is_dynamic_wep = FALSE; + + if (!verify_leap(s_wsec, s_8021x, adhoc, error)) + return FALSE; + + if (leap_username) { + tag = "LEAP"; + } else { + /* Static or Dynamic WEP */ + if (!verify_dynamic_wep(s_wsec, s_8021x, adhoc, error)) + return FALSE; + + if (s_8021x || (key_mgmt && !strcmp(key_mgmt, "ieee8021x"))) { + is_dynamic_wep = TRUE; + tag = "Dynamic WEP"; + } + } + + /* Nothing WPA-related can be set */ + if (!verify_no_wpa(s_wsec, tag, error)) + return FALSE; + + if (leap_username) { + /* LEAP */ + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "leap", + NULL); + } else if (is_dynamic_wep) { + /* Dynamic WEP */ + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + + if (s_8021x) { + /* Dynamic WEP requires a valid 802.1x setting since we can't + * autocomplete 802.1x. + */ + if (!nm_setting_verify(NM_SETTING(s_8021x), NULL, error)) + return FALSE; + } + } else { + /* Static WEP */ + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + } + + return TRUE; + } + + /* WPA/RSN */ + g_assert(ap_wpa_flags || ap_rsn_flags); + + /* Ensure key management is valid for WPA */ + if ((key_mgmt && !strcmp(key_mgmt, "ieee8021x")) || leap_username) { + g_set_error_literal( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA authentication is incompatible with non-EAP (original) LEAP or Dynamic WEP")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + /* 'shared' auth incompatible with any type of WPA */ + if (auth_alg && strcmp(auth_alg, "open")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPA authentication is incompatible with Shared Key authentication")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + if (!verify_no_wep(s_wsec, "WPA", error)) + return FALSE; + + if (!verify_wpa_psk(s_wsec, s_8021x, adhoc, ap_wpa_flags, ap_rsn_flags, error)) + return FALSE; + + if (!adhoc && !verify_wpa_eap(s_wsec, s_8021x, ap_wpa_flags, ap_rsn_flags, error)) + return FALSE; + + if (adhoc) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + } else if (s_8021x) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-eap", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + /* Leave proto/pairwise/group as client set them; if they are unset the + * supplicant will figure out the best combination at connect time. + */ + + /* 802.1x also requires the client to completely fill in the 8021x + * setting. Since there's so much configuration required for it, there's + * no way it can be automatically completed. + */ + } else if ((key_mgmt && !strcmp(key_mgmt, "sae")) + || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "sae", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + } else if ((key_mgmt && !strcmp(key_mgmt, "owe")) + || NM_FLAGS_ANY(ap_rsn_flags, + NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "owe", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + } else if ((key_mgmt && !strcmp(key_mgmt, "wpa-psk")) + || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + /* Leave proto/pairwise/group as client set them; if they are unset the + * supplicant will figure out the best combination at connect time. + */ + } else if ((key_mgmt && !strcmp(key_mgmt, "wpa-eap-suite-b-192")) + || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) { + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-eap-suite-b-192", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Failed to determine AP security information")); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_wifi_utils_is_manf_default_ssid(GBytes *ssid) +{ + const guint8 *ssid_p; + gsize ssid_l; + int i; + /* + * List of manufacturer default SSIDs that are often unchanged by users. + * + * NOTE: this list should *not* contain networks that you would like to + * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot". + */ + static const char *manf_defaults[] = { + "linksys", + "linksys-a", + "linksys-g", + "default", + "belkin54g", + "NETGEAR", + "o2DSL", + "WLAN", + "ALICE-WLAN", + "Speedport W 501V", + "TURBONETT", + }; + + ssid_p = g_bytes_get_data(ssid, &ssid_l); + + for (i = 0; i < G_N_ELEMENTS(manf_defaults); i++) { + if (ssid_l == strlen(manf_defaults[i])) { + if (memcmp(manf_defaults[i], ssid_p, ssid_l) == 0) + return TRUE; + } + } + return FALSE; +} + +/* To be used for connections where the SSID has been validated before */ +gboolean +nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, + char ** ssid, + NMIwdNetworkSecurity *security) +{ + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + const char * key_mgmt = NULL; + + s_wireless = nm_connection_get_setting_wireless(connection); + if (!s_wireless) + return FALSE; + + if (ssid) { + GBytes * bytes = nm_setting_wireless_get_ssid(s_wireless); + gsize ssid_len; + const char *ssid_str = (const char *) g_bytes_get_data(bytes, &ssid_len); + + nm_assert(bytes && g_utf8_validate(ssid_str, ssid_len, NULL)); + NM_SET_OUT(ssid, g_strndup(ssid_str, ssid_len)); + } + + if (!security) + return TRUE; + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + if (!s_wireless_sec) { + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_NONE); + return TRUE; + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec); + nm_assert(key_mgmt); + + if (NM_IN_STRSET(key_mgmt, "none", "ieee8021x")) + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_WEP); + else if (nm_streq(key_mgmt, "wpa-psk")) + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_PSK); + else if (nm_streq(key_mgmt, "wpa-eap")) + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_8021X); + else + return FALSE; + + return TRUE; +} diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h new file mode 100644 index 0000000..6646bca --- /dev/null +++ b/src/core/devices/wifi/nm-wifi-utils.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NM_WIFI_UTILS_H__ +#define __NM_WIFI_UTILS_H__ + +#include "nm-dbus-interface.h" +#include "nm-connection.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" + +typedef enum { + NM_IWD_NETWORK_SECURITY_NONE, + NM_IWD_NETWORK_SECURITY_WEP, + NM_IWD_NETWORK_SECURITY_PSK, + NM_IWD_NETWORK_SECURITY_8021X, +} NMIwdNetworkSecurity; + +gboolean nm_wifi_utils_complete_connection(GBytes * ssid, + const char * bssid, + NM80211Mode mode, + guint32 ap_freq, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + NMConnection *connection, + gboolean lock_bssid, + GError ** error); + +gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid); + +gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, + char ** ssid, + NMIwdNetworkSecurity *security); + +#endif /* __NM_WIFI_UTILS_H__ */ diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c new file mode 100644 index 0000000..3ff0963 --- /dev/null +++ b/src/core/devices/wifi/tests/test-devices-wifi.c @@ -0,0 +1,1609 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "devices/wifi/nm-wifi-utils.h" +#include "devices/wifi/nm-device-wifi.h" +#include "nm-core-internal.h" + +#include "nm-test-utils-core.h" + +#define DEBUG 1 + +/*****************************************************************************/ + +#define COMPARE(src, expected, success, error, edomain, ecode) \ + { \ + if (expected) { \ + if (!success) { \ + g_assert(error != NULL); \ + g_warning("Failed to complete connection: %s", error->message); \ + } \ + g_assert(success == TRUE); \ + g_assert(error == NULL); \ + \ + success = nm_connection_compare(src, expected, NM_SETTING_COMPARE_FLAG_EXACT); \ + if (success == FALSE && DEBUG) { \ + g_print("\n- COMPLETED ---------------------------------\n"); \ + nm_connection_dump(src); \ + g_print("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \ + nm_connection_dump(expected); \ + g_print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \ + } \ + g_assert(success == TRUE); \ + } else { \ + if (success) { \ + g_print("\n- COMPLETED ---------------------------------\n"); \ + nm_connection_dump(src); \ + } \ + g_assert(success == FALSE); \ + g_assert_error(error, edomain, ecode); \ + } \ + \ + g_clear_error(&error); \ + } + +static gboolean +complete_connection(const char * ssid, + const char * bssid, + NM80211Mode mode, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean lock_bssid, + NMConnection *src, + GError ** error) +{ + gs_unref_bytes GBytes *ssid_b = NULL; + NMSettingWireless * s_wifi; + + /* Add a wifi setting if one doesn't exist */ + s_wifi = nm_connection_get_setting_wireless(src); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(src, NM_SETTING(s_wifi)); + } + + ssid_b = g_bytes_new(ssid, strlen(ssid)); + + return nm_wifi_utils_complete_connection(ssid_b, + bssid, + mode, + 0, + flags, + wpa_flags, + rsn_flags, + src, + lock_bssid, + error); +} + +typedef struct { + const char *key; + const char *str; + guint32 uint; +} KeyData; + +static void +set_items(NMSetting *setting, const KeyData *items) +{ + const KeyData *item; + GParamSpec * pspec; + GBytes * tmp; + + for (item = items; item && item->key; item++) { + g_assert(item->key); + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), item->key); + g_assert(pspec); + + if (pspec->value_type == G_TYPE_STRING) { + g_assert(item->uint == 0); + if (item->str) + g_object_set(G_OBJECT(setting), item->key, item->str, NULL); + } else if (pspec->value_type == G_TYPE_UINT) { + g_assert(item->str == NULL); + g_object_set(G_OBJECT(setting), item->key, item->uint, NULL); + } else if (pspec->value_type == G_TYPE_INT) { + int foo = (int) item->uint; + + g_assert(item->str == NULL); + g_object_set(G_OBJECT(setting), item->key, foo, NULL); + } else if (pspec->value_type == G_TYPE_BOOLEAN) { + gboolean foo = !!(item->uint); + + g_assert(item->str == NULL); + g_object_set(G_OBJECT(setting), item->key, foo, NULL); + } else if (pspec->value_type == G_TYPE_BYTES) { + g_assert(item->str); + tmp = g_bytes_new(item->str, strlen(item->str)); + g_object_set(G_OBJECT(setting), item->key, tmp, NULL); + g_bytes_unref(tmp); + } else { + /* Special types, check based on property name */ + if (!strcmp(item->key, NM_SETTING_WIRELESS_SECURITY_PROTO)) + nm_setting_wireless_security_add_proto(NM_SETTING_WIRELESS_SECURITY(setting), + item->str); + else if (!strcmp(item->key, NM_SETTING_WIRELESS_SECURITY_PAIRWISE)) + nm_setting_wireless_security_add_pairwise(NM_SETTING_WIRELESS_SECURITY(setting), + item->str); + else if (!strcmp(item->key, NM_SETTING_WIRELESS_SECURITY_GROUP)) + nm_setting_wireless_security_add_group(NM_SETTING_WIRELESS_SECURITY(setting), + item->str); + else if (!strcmp(item->key, NM_SETTING_802_1X_EAP)) + nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), item->str); + } + } +} + +static NMSettingWireless * +fill_wifi_empty(NMConnection *connection) +{ + NMSettingWireless *s_wifi; + + s_wifi = nm_connection_get_setting_wireless(connection); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + } + return s_wifi; +} + +static NMSettingWireless * +fill_wifi(NMConnection *connection, const KeyData items[]) +{ + NMSettingWireless *s_wifi; + + s_wifi = nm_connection_get_setting_wireless(connection); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + } + + set_items(NM_SETTING(s_wifi), items); + return s_wifi; +} + +static NMSettingWirelessSecurity * +fill_wsec(NMConnection *connection, const KeyData items[]) +{ + NMSettingWirelessSecurity *s_wsec; + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (!s_wsec) { + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + } + + set_items(NM_SETTING(s_wsec), items); + return s_wsec; +} + +static NMSetting8021x * +fill_8021x(NMConnection *connection, const KeyData items[]) +{ + NMSetting8021x *s_8021x; + + s_8021x = nm_connection_get_setting_802_1x(connection); + if (!s_8021x) { + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + } + + set_items(NM_SETTING(s_8021x), items); + return s_8021x; +} + +static NMConnection * +create_basic(const char *ssid, const char *bssid, NM80211Mode mode) +{ + NMConnection * connection; + NMSettingWireless *s_wifi = NULL; + GBytes * tmp; + + connection = nm_simple_connection_new(); + + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + /* SSID */ + tmp = g_bytes_new(ssid, strlen(ssid)); + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_SSID, tmp, NULL); + g_bytes_unref(tmp); + + /* BSSID */ + if (bssid) + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL); + + if (mode == NM_802_11_MODE_INFRA) + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, "infrastructure", NULL); + else if (mode == NM_802_11_MODE_ADHOC) + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, "adhoc", NULL); + else + g_assert_not_reached(); + + return connection; +} + +/*****************************************************************************/ + +static void +test_lock_bssid(void) +{ + NMConnection *src, *expected; + const char * bssid = "01:02:03:04:05:06"; + const char * ssid = "blahblah"; + gboolean success; + GError * error = NULL; + + src = nm_simple_connection_new(); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + TRUE, + src, + &error); + expected = create_basic(ssid, bssid, NM_802_11_MODE_INFRA); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_open_ap_empty_connection(void) +{ + NMConnection *src, *expected; + const char * bssid = "01:02:03:04:05:06"; + const char * ssid = "blahblah"; + gboolean success; + GError * error = NULL; + + /* Test that an empty source connection is correctly filled with the + * SSID and Infra modes of the given AP details. + */ + + src = nm_simple_connection_new(); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_open_ap_leap_connection_1(gconstpointer add_wifi) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, "Bill Smith", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that a basic connection filled with a LEAP username is + * rejected when completion is attempted with an open AP. LEAP requires + * the AP to have the Privacy bit set. + */ + + src = nm_simple_connection_new(); + if (add_wifi) + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* We expect failure */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_open_ap_leap_connection_2(void) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that a basic connection specifying IEEE8021x security (ie, Dynamic + * WEP or LEAP) is rejected when completion is attempted with an open AP. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* We expect failure */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_open_ap_wep_connection(gconstpointer add_wifi) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = { + {NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, "11111111111111111111111111", 0}, + {NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, NULL, 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that a static WEP connection is rejected when completion is + * attempted with an open AP. + */ + + src = nm_simple_connection_new(); + if (add_wifi) + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* We expect failure */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_ap_wpa_psk_connection_base(const char * key_mgmt, + const char * auth_alg, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean add_wifi, + guint error_code, + NMConnection *expected) +{ + NMConnection *src; + const char * ssid = "blahblah"; + const char * bssid = "01:02:03:04:05:06"; + const KeyData exp_wifi[] = {{NM_SETTING_WIRELESS_SSID, ssid, 0}, + {NM_SETTING_WIRELESS_MODE, "infrastructure", 0}, + {NULL}}; + const KeyData both_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0}, + {NM_SETTING_WIRELESS_SECURITY_PSK, "asdfasdfasdfasdfasdfafs", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + src = nm_simple_connection_new(); + if (add_wifi) + fill_wifi_empty(src); + fill_wsec(src, both_wsec); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + flags, + wpa_flags, + rsn_flags, + FALSE, + src, + &error); + if (expected) { + fill_wifi(expected, exp_wifi); + fill_wsec(expected, both_wsec); + } + COMPARE(src, expected, success, error, NM_CONNECTION_ERROR, error_code); + + g_object_unref(src); +} + +static void +test_open_ap_wpa_psk_connection_1(void) +{ + /* Test that a WPA-PSK connection filling only the PSK itself and *not* + * filling the wifi setting is rejected when completion is attempted with + * an open AP. + */ + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_SETTING, + NULL); +} + +static void +test_open_ap_wpa_psk_connection_2(void) +{ + /* Test that a WPA-PSK connection filling only the PSK itself and also + * filling the wifi setting is rejected when completion is attempted with + * an open AP. + */ + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + TRUE, + NM_CONNECTION_ERROR_INVALID_SETTING, + NULL); +} + +static void +test_open_ap_wpa_psk_connection_3(void) +{ + /* Test that a WPA-PSK connection filling the PSK and setting the auth alg + * to 'open' is rejected when completion is attempted with an open AP. + */ + test_ap_wpa_psk_connection_base(NULL, + "open", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_SETTING, + NULL); +} + +static void +test_open_ap_wpa_psk_connection_4(void) +{ + /* Test that a WPA-PSK connection filling the PSK and setting the auth alg + * to 'shared' is rejected when completion is attempted with an open AP. + * Shared auth cannot be used with WPA. + */ + test_ap_wpa_psk_connection_base(NULL, + "shared", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_SETTING, + NULL); +} + +static void +test_open_ap_wpa_psk_connection_5(void) +{ + /* Test that a WPA-PSK connection filling the PSK, the auth algorithm, and + * key management is rejected when completion is attempted with an open AP. + */ + test_ap_wpa_psk_connection_base("wpa-psk", + "open", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_SETTING, + NULL); +} + +/*****************************************************************************/ + +static void +test_ap_wpa_eap_connection_base(const char *key_mgmt, + const char *auth_alg, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean add_wifi, + guint error_code) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_empty[] = {{NULL}}; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + src = nm_simple_connection_new(); + if (add_wifi) + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + fill_8021x(src, src_empty); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + flags, + wpa_flags, + rsn_flags, + FALSE, + src, + &error); + /* Failure expected */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, error_code); + + g_object_unref(src); +} + +enum { + IDX_NONE = 0, + IDX_OPEN, + IDX_PRIV, + IDX_WPA_PSK_PTKIP_GTKIP, + IDX_WPA_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_RSN_PSK_PCCMP_GCCMP, + IDX_RSN_PSK_PCCMP_GCCMP, + IDX_RSN_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_8021X, + IDX_RSN_8021X, +}; + +static guint32 +flags_for_idx(guint32 idx) +{ + if (idx == IDX_OPEN) + return NM_802_11_AP_FLAGS_NONE; + else if (idx == IDX_PRIV || idx == IDX_WPA_PSK_PTKIP_GTKIP + || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP || idx == IDX_RSN_PSK_PCCMP_GCCMP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP || idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP || idx == IDX_WPA_8021X || idx == IDX_RSN_8021X) + return NM_802_11_AP_FLAGS_PRIVACY; + else + g_assert_not_reached(); +} + +static guint32 +wpa_flags_for_idx(guint32 idx) +{ + if (idx == IDX_OPEN || idx == IDX_PRIV || idx == IDX_RSN_8021X || idx == IDX_RSN_PSK_PCCMP_GCCMP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_NONE; + else if (idx == IDX_WPA_PSK_PTKIP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (NM_IN_SET(idx, IDX_WPA_PSK_PTKIP_PCCMP_GTKIP, IDX_WPA_RSN_PSK_PCCMP_GCCMP)) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_8021X) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_KEY_MGMT_802_1X; + else + g_assert_not_reached(); +} + +static guint32 +rsn_flags_for_idx(guint32 idx) +{ + if (idx == IDX_OPEN || idx == IDX_PRIV || idx == IDX_WPA_8021X || idx == IDX_WPA_PSK_PTKIP_GTKIP + || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_NONE; + else if (idx == IDX_RSN_PSK_PCCMP_GCCMP) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP + | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_RSN_8021X) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP + | NM_802_11_AP_SEC_KEY_MGMT_802_1X; + else + g_assert_not_reached(); +} + +static guint32 +error_code_for_idx(guint32 idx, guint num) +{ + if (idx == IDX_OPEN) + return NM_CONNECTION_ERROR_INVALID_SETTING; + else if (idx == IDX_PRIV) { + if (num <= 3) + return NM_CONNECTION_ERROR_MISSING_PROPERTY; + else + return NM_CONNECTION_ERROR_INVALID_PROPERTY; + } else if (idx == IDX_WPA_PSK_PTKIP_GTKIP || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP || idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP || idx == IDX_RSN_PSK_PCCMP_GCCMP) + if (num == 4) + return NM_CONNECTION_ERROR_INVALID_PROPERTY; + else + return NM_CONNECTION_ERROR_INVALID_SETTING; + else + g_assert_not_reached(); +} + +static void +test_ap_wpa_eap_connection_1(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + + test_ap_wpa_eap_connection_base(NULL, + NULL, + flags_for_idx(idx), + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + error_code_for_idx(idx, 1)); +} + +static void +test_ap_wpa_eap_connection_2(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + + test_ap_wpa_eap_connection_base(NULL, + NULL, + flags_for_idx(idx), + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + TRUE, + error_code_for_idx(idx, 2)); +} + +static void +test_ap_wpa_eap_connection_3(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + + test_ap_wpa_eap_connection_base(NULL, + "open", + flags_for_idx(idx), + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + error_code_for_idx(idx, 3)); +} + +static void +test_ap_wpa_eap_connection_4(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + + test_ap_wpa_eap_connection_base(NULL, + "shared", + flags_for_idx(idx), + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + error_code_for_idx(idx, 4)); +} + +static void +test_ap_wpa_eap_connection_5(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + + test_ap_wpa_eap_connection_base("wpa-eap", + "open", + flags_for_idx(idx), + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + error_code_for_idx(idx, 5)); +} + +/*****************************************************************************/ + +static void +test_priv_ap_empty_connection(void) +{ + NMConnection *src, *expected; + const char * bssid = "01:02:03:04:05:06"; + const char * ssid = "blahblah"; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", 0}, {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that an empty connection is completed to a valid Static WEP + * connection when completed with an AP with the Privacy bit set. + */ + + src = nm_simple_connection_new(); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + + /* Static WEP connection expected */ + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec(expected, exp_wsec); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_priv_ap_leap_connection_1(gconstpointer add_wifi) +{ + NMConnection *src, *expected; + const char * ssid = "blahblah"; + const char * bssid = "01:02:03:04:05:06"; + const char * leap_username = "Bill Smith"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0}, + {NULL}}; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0}, + {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that an minimal LEAP connection specifying only key management and + * the LEAP username is completed to a full LEAP connection when completed + * with an AP with the Privacy bit set. + */ + + src = nm_simple_connection_new(); + if (add_wifi) + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* We expect success here; since LEAP APs just set the 'privacy' flag + * there's no way to determine from the AP's beacon whether it's static WEP, + * dynamic WEP, or LEAP. + */ + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec(expected, exp_wsec); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_priv_ap_leap_connection_2(void) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that an minimal LEAP connection specifying only key management and + * the LEAP auth alg is completed to a full LEAP connection when completed + * with an AP with the Privacy bit set. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* We expect failure here, we need a LEAP username */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_priv_ap_dynamic_wep_1(void) +{ + NMConnection *src, *expected; + const char * ssid = "blahblah"; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + const KeyData both_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0}, + {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0}, + {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0}, + {NULL}}; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that an minimal Dynamic WEP connection specifying key management, + * the auth algorithm, and valid 802.1x setting is completed to a valid + * Dynamic WEP connection when completed with an AP with the Privacy bit set. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + fill_8021x(src, both_8021x); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + + /* We expect a completed Dynamic WEP connection */ + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec(expected, exp_wsec); + fill_8021x(expected, both_8021x); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_priv_ap_dynamic_wep_2(void) +{ + NMConnection *src, *expected; + const char * ssid = "blahblah"; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, {NULL}}; + const KeyData both_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0}, + {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0}, + {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0}, + {NULL}}; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that an minimal Dynamic WEP connection specifying only the auth + * algorithm and a valid 802.1x setting is completed to a valid Dynamic + * WEP connection when completed with an AP with the Privacy bit set. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + fill_8021x(src, both_8021x); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + + /* We expect a completed Dynamic WEP connection */ + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec(expected, exp_wsec); + fill_8021x(expected, both_8021x); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_priv_ap_dynamic_wep_3(void) +{ + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", 0}, {NULL}}; + const KeyData src_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0}, + {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0}, + {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Ensure that a basic connection specifying 'shared' auth and an 802.1x + * setting is rejected, as 802.1x is incompatible with 'shared' auth. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + fill_8021x(src, src_8021x); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + src, + &error); + /* Expect failure; shared is not compatible with dynamic WEP */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_priv_ap_wpa_psk_connection_1(void) +{ + /* Test that a basic WPA-PSK connection is rejected when completion is + * attempted with an AP with just the Privacy bit set. Lack of WPA/RSN + * flags means the AP provides Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +static void +test_priv_ap_wpa_psk_connection_2(void) +{ + /* Test that a basic WPA-PSK connection is rejected when completion is + * attempted with an AP with just the Privacy bit set. Lack of WPA/RSN + * flags means the AP provides Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + TRUE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +static void +test_priv_ap_wpa_psk_connection_3(void) +{ + /* Test that a basic WPA-PSK connection specifying only the auth algorithm + * is rejected when completion is attempted with an AP with just the Privacy + * bit set. Lack of WPA/RSN flags means the AP provides Static/Dynamic WEP + * or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base(NULL, + "open", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +static void +test_priv_ap_wpa_psk_connection_4(void) +{ + /* Test that a basic WPA-PSK connection specifying only the auth algorithm + * is rejected when completion is attempted with an AP with just the Privacy + * bit set. Lack of WPA/RSN flags means the AP provides Static/Dynamic WEP + * or LEAP, not WPA. Second, 'shared' auth is incompatible with WPA. + */ + test_ap_wpa_psk_connection_base(NULL, + "shared", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +static void +test_priv_ap_wpa_psk_connection_5(void) +{ + /* Test that a WPA-PSK connection specifying both the key management and + * auth algorithm is rejected when completion is attempted with an AP with + * just the Privacy bit set. Lack of WPA/RSN flags means the AP provides + * Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base("wpa-psk", + "open", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +/*****************************************************************************/ + +static void +test_wpa_ap_empty_connection(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *src, *expected; + const char * bssid = "01:02:03:04:05:06"; + const char * ssid = "blahblah"; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that a basic WPA-PSK connection specifying just key management and + * the auth algorithm is completed successfully when given an AP with WPA + * or RSN flags. + */ + + src = nm_simple_connection_new(); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + src, + &error); + + /* WPA connection expected */ + expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec(expected, exp_wsec); + COMPARE(src, expected, success, error, 0, 0); + + g_object_unref(src); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_wpa_ap_leap_connection_1(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *src; + const char * ssid = "blahblah"; + const char * bssid = "01:02:03:04:05:06"; + const char * leap_username = "Bill Smith"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that completion of a LEAP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support LEAP. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection(ssid, + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + src, + &error); + /* Expect failure here; WPA APs don't support old-school LEAP */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_wpa_ap_leap_connection_2(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0}, + {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that completion of a LEAP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support LEAP. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + src, + &error); + /* We expect failure here, we need a LEAP username */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_wpa_ap_dynamic_wep_connection(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *src; + const char * bssid = "01:02:03:04:05:06"; + const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, {NULL}}; + gboolean success; + GError * error = NULL; + + /* Test that completion of a Dynamic WEP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support Dynamic WEP. + */ + + src = nm_simple_connection_new(); + fill_wifi_empty(src); + fill_wsec(src, src_wsec); + success = complete_connection("blahblah", + bssid, + NM_802_11_MODE_INFRA, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + src, + &error); + /* We expect failure here since Dynamic WEP is incompatible with WPA */ + COMPARE(src, NULL, success, error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_unref(src); +} + +/*****************************************************************************/ + +static void +test_wpa_ap_wpa_psk_connection_1(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *expected; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + + expected = nm_simple_connection_new(); + fill_wsec(expected, exp_wsec); + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + expected); + g_object_unref(expected); +} + +static void +test_wpa_ap_wpa_psk_connection_2(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *expected; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + + expected = nm_simple_connection_new(); + fill_wsec(expected, exp_wsec); + test_ap_wpa_psk_connection_base(NULL, + NULL, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + TRUE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + expected); + g_object_unref(expected); +} + +static void +test_wpa_ap_wpa_psk_connection_3(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *expected; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + + expected = nm_simple_connection_new(); + fill_wsec(expected, exp_wsec); + test_ap_wpa_psk_connection_base(NULL, + "open", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + expected); + g_object_unref(expected); +} + +static void +test_wpa_ap_wpa_psk_connection_4(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + test_ap_wpa_psk_connection_base(NULL, + "shared", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + NULL); +} + +static void +test_wpa_ap_wpa_psk_connection_5(gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT(data); + NMConnection *expected; + const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0}, + {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, + {NULL}}; + + expected = nm_simple_connection_new(); + fill_wsec(expected, exp_wsec); + test_ap_wpa_psk_connection_base("wpa-psk", + "open", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx(idx), + rsn_flags_for_idx(idx), + FALSE, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + expected); + g_object_unref(expected); +} + +/*****************************************************************************/ + +static void +test_strength_dbm(void) +{ + /* boundary conditions first */ + g_assert_cmpint(nm_wifi_utils_level_to_quality(-1), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-40), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-30), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-100), ==, 0); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-200), ==, 0); + + g_assert_cmpint(nm_wifi_utils_level_to_quality(-81), ==, 32); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-92), ==, 14); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-74), ==, 44); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-81), ==, 32); + g_assert_cmpint(nm_wifi_utils_level_to_quality(-66), ==, 57); +} + +static void +test_strength_percent(void) +{ + int i; + + /* boundary conditions first */ + g_assert_cmpint(nm_wifi_utils_level_to_quality(0), ==, 0); + g_assert_cmpint(nm_wifi_utils_level_to_quality(100), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(110), ==, 100); + + for (i = 0; i <= 100; i++) + g_assert_cmpint(nm_wifi_utils_level_to_quality(i), ==, i); +} + +static void +test_strength_wext(void) +{ + /* boundary conditions that we assume aren't WEXT first */ + g_assert_cmpint(nm_wifi_utils_level_to_quality(256), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(110), ==, 100); + + /* boundary conditions that we assume are WEXT */ + g_assert_cmpint(nm_wifi_utils_level_to_quality(111), ==, 0); + g_assert_cmpint(nm_wifi_utils_level_to_quality(150), ==, 0); + g_assert_cmpint(nm_wifi_utils_level_to_quality(225), ==, 100); + g_assert_cmpint(nm_wifi_utils_level_to_quality(255), ==, 100); + + g_assert_cmpint(nm_wifi_utils_level_to_quality(157), ==, 2); + g_assert_cmpint(nm_wifi_utils_level_to_quality(200), ==, 74); + g_assert_cmpint(nm_wifi_utils_level_to_quality(215), ==, 99); +} + +#define _assert_strength_in_range(x) \ + ({ \ + guint32 _x = (x); \ + g_assert_cmpint(_x, >=, 0); \ + g_assert_cmpint(_x, <=, 100); \ + }) + +static void +test_strength_all(void) +{ + int val; + + for (val = -200; val < 300; val++) + _assert_strength_in_range(nm_wifi_utils_level_to_quality(val)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MININT)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MAXINT)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MININT32)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MAXINT32)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MININT16)); + _assert_strength_in_range(nm_wifi_utils_level_to_quality(G_MAXINT16)); +} + +/*****************************************************************************/ + +static void +do_test_ssids_options_to_ptrarray(const char *const *ssids) +{ + GVariantBuilder builder; + gs_unref_variant GVariant *variant = NULL; + gs_unref_ptrarray GPtrArray *ssids_arr = NULL; + gs_free_error GError *error = NULL; + gsize len; + gsize i; + + g_assert(ssids); + + len = NM_PTRARRAY_LEN(ssids); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aay")); + for (i = 0; i < len; i++) { + const char *ssid = ssids[i]; + + g_variant_builder_add( + &builder, + "@ay", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, ssid, strlen(ssid), 1)); + } + variant = g_variant_builder_end(&builder); + + if (nmtst_get_rand_bool()) + g_variant_ref_sink(variant); + + ssids_arr = nmtst_ssids_options_to_ptrarray(variant, &error); + g_assert(!error); + if (len == 0) { + g_assert(!ssids_arr); + return; + } + g_assert_cmpint(len, ==, ssids_arr->len); + for (i = 0; i < len; i++) { + const char *ssid = ssids[i]; + GBytes * bytes = ssids_arr->pdata[i]; + + g_assert(nm_utils_gbytes_equal_mem(bytes, ssid, strlen(ssid))); + } +} + +static void +test_ssids_options_to_ptrarray(void) +{ + do_test_ssids_options_to_ptrarray(NM_PTRARRAY_EMPTY(const char *)); + do_test_ssids_options_to_ptrarray(NM_MAKE_STRV("ab")); + do_test_ssids_options_to_ptrarray(NM_MAKE_STRV("ab", "cd", "fsdfdsf")); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + gsize i; + + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/wifi/lock_bssid", test_lock_bssid); + + /* Open AP tests; make sure that connections to be completed that have + * various security-related settings already set cause the completion + * to fail. + */ + g_test_add_func("/wifi/open_ap/empty_connection", test_open_ap_empty_connection); + g_test_add_data_func("/wifi/open_ap/leap_connection/1", + (gconstpointer) TRUE, + test_open_ap_leap_connection_1); + g_test_add_data_func("/wifi/open_ap/leap_connection/1_no_add_wifi", + (gconstpointer) FALSE, + test_open_ap_leap_connection_1); + g_test_add_func("/wifi/open_ap/leap_connection/2", test_open_ap_leap_connection_2); + g_test_add_data_func("/wifi/open_ap/wep_connection_true", + (gconstpointer) TRUE, + test_open_ap_wep_connection); + g_test_add_data_func("/wifi/open_ap/wep_connection_false", + (gconstpointer) FALSE, + test_open_ap_wep_connection); + + g_test_add_func("/wifi/open_ap/wpa_psk_connection/1", test_open_ap_wpa_psk_connection_1); + g_test_add_func("/wifi/open_ap/wpa_psk_connection/2", test_open_ap_wpa_psk_connection_2); + g_test_add_func("/wifi/open_ap/wpa_psk_connection/3", test_open_ap_wpa_psk_connection_3); + g_test_add_func("/wifi/open_ap/wpa_psk_connection/4", test_open_ap_wpa_psk_connection_4); + g_test_add_func("/wifi/open_ap/wpa_psk_connection/5", test_open_ap_wpa_psk_connection_5); + + g_test_add_data_func("/wifi/open_ap/wpa_eap_connection/1", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_1); + g_test_add_data_func("/wifi/open_ap/wpa_eap_connection/2", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_2); + g_test_add_data_func("/wifi/open_ap/wpa_eap_connection/3", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_3); + g_test_add_data_func("/wifi/open_ap/wpa_eap_connection/4", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_4); + g_test_add_data_func("/wifi/open_ap/wpa_eap_connection/5", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_5); + + /* WEP AP tests */ + g_test_add_func("/wifi/priv_ap/empty_connection", test_priv_ap_empty_connection); + g_test_add_data_func("/wifi/priv_ap/leap_connection/1", + (gconstpointer) FALSE, + test_priv_ap_leap_connection_1); + g_test_add_func("/wifi/priv_ap/leap_connection/2", test_priv_ap_leap_connection_2); + + g_test_add_func("/wifi/priv_ap/dynamic_wep/1", test_priv_ap_dynamic_wep_1); + g_test_add_func("/wifi/priv_ap/dynamic_wep/2", test_priv_ap_dynamic_wep_2); + g_test_add_func("/wifi/priv_ap/dynamic_wep/3", test_priv_ap_dynamic_wep_3); + + g_test_add_func("/wifi/priv_ap/wpa_psk_connection/1", test_priv_ap_wpa_psk_connection_1); + g_test_add_func("/wifi/priv_ap/wpa_psk_connection/2", test_priv_ap_wpa_psk_connection_2); + g_test_add_func("/wifi/priv_ap/wpa_psk_connection/3", test_priv_ap_wpa_psk_connection_3); + g_test_add_func("/wifi/priv_ap/wpa_psk_connection/4", test_priv_ap_wpa_psk_connection_4); + g_test_add_func("/wifi/priv_ap/wpa_psk_connection/5", test_priv_ap_wpa_psk_connection_5); + + g_test_add_data_func("/wifi/priv_ap/wpa_eap_connection/1", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_1); + g_test_add_data_func("/wifi/priv_ap/wpa_eap_connection/2", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_2); + g_test_add_data_func("/wifi/priv_ap/wpa_eap_connection/3", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_3); + g_test_add_data_func("/wifi/priv_ap/wpa_eap_connection/4", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_4); + g_test_add_data_func("/wifi/priv_ap/wpa_eap_connection/5", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_5); + +#define ADD_FUNC(func) \ + do { \ + char *name_idx = g_strdup_printf("/wifi/wpa_psk/" G_STRINGIFY(func) "/%zd", i); \ + g_test_add_data_func(name_idx, (gconstpointer) i, func); \ + g_free(name_idx); \ + } while (0) + + /* WPA-PSK tests */ + for (i = IDX_WPA_PSK_PTKIP_GTKIP; i <= IDX_WPA_RSN_PSK_PCCMP_GCCMP; i++) { + ADD_FUNC(test_wpa_ap_empty_connection); + ADD_FUNC(test_wpa_ap_leap_connection_1); + ADD_FUNC(test_wpa_ap_leap_connection_2); + ADD_FUNC(test_wpa_ap_dynamic_wep_connection); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_1); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_2); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_3); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_4); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_5); + ADD_FUNC(test_ap_wpa_eap_connection_1); + ADD_FUNC(test_ap_wpa_eap_connection_2); + ADD_FUNC(test_ap_wpa_eap_connection_3); + ADD_FUNC(test_ap_wpa_eap_connection_4); + ADD_FUNC(test_ap_wpa_eap_connection_5); + } + +#undef ADD_FUNC +#define ADD_FUNC(func) \ + do { \ + char *name_idx = g_strdup_printf("/wifi/rsn_psk/" G_STRINGIFY(func) "/%zd", i); \ + g_test_add_data_func(name_idx, (gconstpointer) i, func); \ + g_free(name_idx); \ + } while (0) + + /* RSN-PSK tests */ + for (i = IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP; i <= IDX_RSN_PSK_PTKIP_PCCMP_GTKIP; i++) { + ADD_FUNC(test_wpa_ap_empty_connection); + ADD_FUNC(test_wpa_ap_leap_connection_1); + ADD_FUNC(test_wpa_ap_leap_connection_2); + ADD_FUNC(test_wpa_ap_dynamic_wep_connection); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_1); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_2); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_3); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_4); + ADD_FUNC(test_wpa_ap_wpa_psk_connection_5); + ADD_FUNC(test_ap_wpa_eap_connection_1); + ADD_FUNC(test_ap_wpa_eap_connection_2); + ADD_FUNC(test_ap_wpa_eap_connection_3); + ADD_FUNC(test_ap_wpa_eap_connection_4); + ADD_FUNC(test_ap_wpa_eap_connection_5); + } + +#undef ADD_FUNC + + /* Scanned signal strength conversion tests */ + g_test_add_func("/wifi/strength/dbm", test_strength_dbm); + g_test_add_func("/wifi/strength/percent", test_strength_percent); + g_test_add_func("/wifi/strength/wext", test_strength_wext); + g_test_add_func("/wifi/strength/all", test_strength_all); + + g_test_add_func("/wifi/ssids_options_to_ptrarray", test_ssids_options_to_ptrarray); + + return g_test_run(); +} diff --git a/src/core/devices/wwan/libnm-wwan.ver b/src/core/devices/wwan/libnm-wwan.ver new file mode 100644 index 0000000..c368a59 --- /dev/null +++ b/src/core/devices/wwan/libnm-wwan.ver @@ -0,0 +1,41 @@ +{ +global: + nm_modem_act_stage1_prepare; + nm_modem_act_stage2_config; + nm_modem_check_connection_compatible; + nm_modem_claim; + nm_modem_complete_connection; + nm_modem_deactivate; + nm_modem_deactivate_async; + nm_modem_device_state_changed; + nm_modem_get_apn; + nm_modem_get_capabilities; + nm_modem_get_configured_mtu; + nm_modem_get_control_port; + nm_modem_get_device_id; + nm_modem_get_driver; + nm_modem_get_iid; + nm_modem_get_ip_ifindex; + nm_modem_get_operator_code; + nm_modem_get_path; + nm_modem_get_secrets; + nm_modem_get_state; + nm_modem_get_type; + nm_modem_get_uid; + nm_modem_ip4_pre_commit; + nm_modem_is_claimed; + nm_modem_manager_get; + nm_modem_manager_get_modems; + nm_modem_manager_get_type; + nm_modem_manager_name_owner_get; + nm_modem_manager_name_owner_ref; + nm_modem_manager_name_owner_unref; + nm_modem_owns_port; + nm_modem_set_mm_enabled; + nm_modem_stage3_ip4_config_start; + nm_modem_stage3_ip6_config_start; + nm_modem_state_to_string; + nm_modem_unclaim; +local: + *; +}; diff --git a/src/core/devices/wwan/meson.build b/src/core/devices/wwan/meson.build new file mode 100644 index 0000000..87af042 --- /dev/null +++ b/src/core/devices/wwan/meson.build @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +wwan_inc = include_directories('.') + +linker_script = join_paths(meson.current_source_dir(), 'libnm-wwan.ver') + +libnm_wwan = shared_module( + 'nm-wwan', + sources: files( + 'nm-service-providers.c', + 'nm-modem-broadband.c', + 'nm-modem.c', + 'nm-modem-manager.c', + ) + (enable_ofono ? files('nm-modem-ofono.c') : files()), + dependencies: [ + core_plugin_dep, + libsystemd_dep, + mm_glib_dep, + ], + c_args: daemon_c_flags, + link_args: '-Wl,--version-script,@0@'.format(linker_script), + link_depends: linker_script, + install: true, + install_dir: nm_plugindir, +) + +libnm_wwan_dep = declare_dependency( + include_directories: wwan_inc, + link_with: libnm_wwan, +) + +core_plugins += libnm_wwan + +test( + 'check-wwan', + check_exports, + args: [ + libnm_wwan.full_path(), + linker_script, + ], +) + +libnm_device_plugin_wwan = shared_module( + 'nm-device-plugin-wwan', + sources: files( + 'nm-device-modem.c', + 'nm-wwan-factory.c', + ), + dependencies: [ + core_plugin_dep, + libsystemd_dep, + mm_glib_dep, + ], + c_args: daemon_c_flags, + link_with: libnm_wwan, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, + install_rpath: nm_plugindir, +) + +core_plugins += libnm_device_plugin_wwan + +run_target( + 'check-local-devices-wwan', + command: [check_exports, libnm_device_plugin_wwan.full_path(), linker_script_devices], + depends: libnm_device_plugin_wwan, +) + +if enable_tests + exe = executable( + 'test-service-providers', + files( + 'tests/test-service-providers.c', + 'nm-service-providers.c', + ), + include_directories: wwan_inc, + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + ) + test( + 'wwan/test-service-providers', + test_script, + timeout: default_test_timeout, + args: test_args + [exe.full_path()], + ) +endif diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c new file mode 100644 index 0000000..a715ceb --- /dev/null +++ b/src/core/devices/wwan/nm-device-modem.c @@ -0,0 +1,957 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-modem.h" + +#include "nm-modem.h" +#include "nm-ip4-config.h" +#include "devices/nm-device-private.h" +#include "nm-rfkill-manager.h" +#include "settings/nm-settings-connection.h" +#include "nm-modem-broadband.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceModem +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceModem, + PROP_MODEM, + PROP_CAPABILITIES, + PROP_CURRENT_CAPABILITIES, + PROP_DEVICE_ID, + PROP_OPERATOR_CODE, + PROP_APN, ); + +typedef struct { + NMModem * modem; + NMDeviceModemCapabilities caps; + NMDeviceModemCapabilities current_caps; + char * device_id; + char * operator_code; + char * apn; + bool rf_enabled : 1; + NMDeviceStageState stage1_state : 3; +} NMDeviceModemPrivate; + +struct _NMDeviceModem { + NMDevice parent; + NMDeviceModemPrivate _priv; +}; + +struct _NMDeviceModemClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE) + +#define NM_DEVICE_MODEM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceModem, NM_IS_DEVICE_MODEM, NMDevice) + +/*****************************************************************************/ + +static void +ppp_failed(NMModem *modem, guint i_reason, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceStateReason reason = i_reason; + + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + break; + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_activate_ip4_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET); + else if (nm_device_activate_ip6_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET6); + else if (nm_device_activate_ip4_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else if (nm_device_activate_ip6_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET6, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + _LOGW(LOGD_MB, + "PPP failure in unexpected state %u", + (guint) nm_device_get_state(device)); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + break; + default: + break; + } +} + +static void +modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceStateReason reason = i_reason; + + if (nm_device_get_state(device) != NM_DEVICE_STATE_PREPARE + || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING) { + nm_assert_not_reached(); + success = FALSE; + } + + if (!success) { + /* There are several reasons to block autoconnection at device level: + * + * - Wrong SIM-PIN: The device won't autoconnect because it doesn't make sense + * to retry the connection with the same PIN. This error also makes autoconnection + * blocked at settings level, so not even a modem unplug and replug will allow + * autoconnection again. It is somewhat redundant to block autoconnection at + * both device and setting level really. + * + * - SIM wrong or not inserted: If the modem is reporting a SIM not inserted error, + * we can block autoconnection at device level, so that if the same device is + * unplugged and replugged with a SIM (or if a SIM hotplug event happens in MM, + * recreating the device completely), we can try the autoconnection again. + * + * - Modem initialization failed: For some reason unknown to NM, the modem wasn't + * initialized correctly, which leads to an unusable device. A device unplug and + * replug may solve the issue, so make it a device-level autoconnection blocking + * reason. + */ + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_SIM_MISSING); + break; + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_INIT_FAILED); + break; + default: + break; + } + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + return; + } + + priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +modem_auth_requested(NMModem *modem, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + /* Auth requests (PIN, PAP/CHAP passwords, etc) only get handled + * during activation. + */ + if (!nm_device_is_activating(device)) + return; + + nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); +} + +static void +modem_auth_result(NMModem *modem, GError *error, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + + if (error) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpointer user_data) +{ + NMDeviceModem *self = NM_DEVICE_MODEM(user_data); + NMDevice * device = NM_DEVICE(self); + + g_return_if_fail(nm_device_activate_ip4_state_in_conf(device) == TRUE); + + if (error) { + _LOGW(LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s", error->message); + nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG_CAST(config)); + nm_device_activate_schedule_ip_config_result(device, AF_INET, NULL); + } +} + +static void +modem_ip6_config_result(NMModem * modem, + NMIP6Config *config, + gboolean do_slaac, + GError * error, + gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDevice * device = NM_DEVICE(self); + NMActStageReturn ret; + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + gs_unref_object NMIP6Config *ignored = NULL; + gboolean got_config = !!config; + + g_return_if_fail(nm_device_activate_ip6_state_in_conf(device) == TRUE); + + if (error) { + _LOGW(LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message); + nm_device_ip_method_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + /* Re-enable IPv6 on the interface */ + nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "0"); + + if (config) + nm_device_set_dev2_ip_config(device, AF_INET6, NM_IP_CONFIG_CAST(config)); + + if (do_slaac == FALSE) { + if (got_config) + nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL); + else { + _LOGW(LOGD_MB | LOGD_IP6, + "retrieving IPv6 configuration failed: SLAAC not requested and no addresses"); + nm_device_ip_method_failed(device, + AF_INET6, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + return; + } + + /* Start SLAAC now that we have a link-local address from the modem */ + ret = + NM_DEVICE_CLASS(nm_device_modem_parent_class) + ->act_stage3_ip_config_start(device, AF_INET6, (gpointer *) &ignored, &failure_reason); + + nm_assert(ignored == NULL); + + switch (ret) { + case NM_ACT_STAGE_RETURN_FAILURE: + nm_device_ip_method_failed(device, AF_INET6, failure_reason); + break; + case NM_ACT_STAGE_RETURN_IP_FAIL: + /* all done */ + nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL); + break; + case NM_ACT_STAGE_RETURN_POSTPONE: + /* let SLAAC run */ + break; + default: + /* Should never get here since we've assured that the IPv6 method + * will either be "auto" or "ignored" when starting IPv6 configuration. + */ + nm_assert_not_reached(); + } +} + +static void +ip_ifindex_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + if (!nm_device_is_activating(device)) + return; + + if (!nm_device_set_ip_ifindex(device, nm_modem_get_ip_ifindex(modem))) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + /* Disable IPv6 immediately on the interface since NM handles IPv6 + * internally, and leaving it enabled could allow the kernel's IPv6 + * RA handling code to run before NM is ready. + */ + nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "1"); +} + +static void +operator_code_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + const char * operator_code = nm_modem_get_operator_code(modem); + + if (g_strcmp0(priv->operator_code, operator_code) != 0) { + g_free(priv->operator_code); + priv->operator_code = g_strdup(operator_code); + _notify(self, PROP_OPERATOR_CODE); + } +} + +static void +apn_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + const char * apn = nm_modem_get_apn(modem); + + if (g_strcmp0(priv->apn, apn) != 0) { + g_free(priv->apn); + priv->apn = g_strdup(apn); + _notify(self, PROP_APN); + } +} + +static void +ids_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + nm_device_recheck_available_connections(NM_DEVICE(user_data)); +} + +static void +modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_data) +{ + NMModemState new_state = new_state_i; + NMModemState old_state = old_state_i; + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMDeviceState dev_state = nm_device_get_state(device); + + if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING + && priv->rf_enabled) { + /* Called when the ModemManager modem enabled state is changed externally + * to NetworkManager (eg something using MM's D-Bus API directly). + */ + if (nm_device_is_activating(device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + /* user-initiated action, hence DISCONNECTED not FAILED */ + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + return; + } + } + + if (new_state < NM_MODEM_STATE_CONNECTING && old_state >= NM_MODEM_STATE_CONNECTING + && dev_state >= NM_DEVICE_STATE_NEED_AUTH && dev_state <= NM_DEVICE_STATE_ACTIVATED) { + /* Fail the device if the modem disconnects unexpectedly while the + * device is activating/activated. */ + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } + + if (new_state > NM_MODEM_STATE_LOCKED && old_state == NM_MODEM_STATE_LOCKED) { + /* If the modem is now unlocked, enable/disable it according to the + * device's enabled/disabled state. + */ + nm_modem_set_mm_enabled(priv->modem, priv->rf_enabled); + + if (dev_state == NM_DEVICE_STATE_NEED_AUTH) { + /* The modem was unlocked externally to NetworkManager, + * deactivate so the default connection can be + * automatically activated again */ + nm_device_state_changed(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_MODEM_AVAILABLE); + } + + /* Now allow connections without a PIN to be available */ + nm_device_recheck_available_connections(device); + } + + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, + NM_DEVICE_STATE_REASON_MODEM_FAILED); +} + +static void +modem_removed_cb(NMModem *modem, gpointer user_data) +{ + g_signal_emit_by_name(NM_DEVICE(user_data), NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static gboolean +owns_iface(NMDevice *device, const char *iface) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + g_return_val_if_fail(priv->modem, FALSE); + + return nm_modem_owns_port(priv->modem, iface); +} + +/*****************************************************************************/ + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + g_return_if_fail(priv->modem); + + if (new_state == NM_DEVICE_STATE_UNAVAILABLE && old_state < NM_DEVICE_STATE_UNAVAILABLE) { + /* Log initial modem state */ + _LOGI(LOGD_MB, + "modem state '%s'", + nm_modem_state_to_string(nm_modem_get_state(priv->modem))); + } + nm_modem_device_state_changed(priv->modem, new_state, old_state); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_NON_KERNEL; +} + +static const char * +get_type_description(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + if (NM_FLAGS_HAS(priv->current_caps, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)) + return "gsm"; + if (NM_FLAGS_HAS(priv->current_caps, NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)) + return "cdma"; + return NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_type_description(device); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + GError *local = NULL; + + if (!NM_DEVICE_CLASS(nm_device_modem_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (!nm_modem_check_connection_compatible(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, + connection, + error ? &local : NULL)) { + if (error) { + g_set_error(error, + NM_UTILS_ERROR, + g_error_matches(local, + NM_UTILS_ERROR, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE) + ? NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE + : NM_UTILS_ERROR_UNKNOWN, + "modem is incompatible with connection: %s", + local->message); + g_error_free(local); + } + return FALSE; + } + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMModemState state; + + if (!priv->rf_enabled) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "RFKILL for modem enabled"); + return FALSE; + } + + if (!priv->modem) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem not available"); + return FALSE; + } + + state = nm_modem_get_state(priv->modem); + if (state <= NM_MODEM_STATE_INITIALIZING) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem not initialized"); + return FALSE; + } + + if (state == NM_MODEM_STATE_LOCKED) { + if (!nm_connection_get_setting_gsm(connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem is locked without pin available"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + return nm_modem_complete_connection(priv->modem, + nm_device_get_iface(device), + connection, + existing_connections, + error); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + nm_modem_deactivate(priv->modem, device); + priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT; +} + +/*****************************************************************************/ + +static void +modem_deactivate_async_cb(NMModem *modem, GError *error, gpointer user_data) +{ + gs_unref_object NMDevice * self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + callback(self, error, callback_user_data); +} + +static void +deactivate_async(NMDevice * self, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer user_data) +{ + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + + nm_modem_deactivate_async(NM_DEVICE_MODEM_GET_PRIVATE(self)->modem, + self, + cancellable, + modem_deactivate_async_cb, + nm_utils_user_data_pack(g_object_ref(self), callback, user_data)); +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMActRequest * req; + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_INIT) { + priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING; + return nm_modem_act_stage1_prepare(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, + req, + out_failure_reason); + } + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + + nm_assert(priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + nm_modem_act_stage2_config(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + nm_assert_addr_family(addr_family); + + if (addr_family == AF_INET) { + return nm_modem_stage3_ip4_config_start(priv->modem, + device, + NM_DEVICE_CLASS(nm_device_modem_parent_class), + out_failure_reason); + } else { + return nm_modem_stage3_ip6_config_start(priv->modem, device, out_failure_reason); + } +} + +static void +ip4_config_pre_commit(NMDevice *device, NMIP4Config *config) +{ + nm_modem_ip4_pre_commit(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, device, config); +} + +static gboolean +get_ip_iface_identifier(NMDevice *device, NMUtilsIPv6IfaceId *out_iid) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + gboolean success; + + g_return_val_if_fail(priv->modem, FALSE); + success = nm_modem_get_iid(priv->modem, out_iid); + if (!success) + success = + NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_ip_iface_identifier(device, out_iid); + return success; +} + +/*****************************************************************************/ + +static gboolean +get_enabled(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMModemState modem_state = nm_modem_get_state(priv->modem); + + return priv->rf_enabled && (modem_state >= NM_MODEM_STATE_LOCKED); +} + +static void +set_enabled(NMDevice *device, gboolean enabled) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + /* Called only by the Manager in response to rfkill switch changes or + * global user WWAN enable/disable preference changes. + */ + priv->rf_enabled = enabled; + + if (priv->modem) { + /* Sync the ModemManager modem enabled/disabled with rfkill/user preference */ + nm_modem_set_mm_enabled(priv->modem, enabled); + } + + if (enabled == FALSE) { + nm_device_state_changed(device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE); + } +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMModemState modem_state; + + if (!priv->rf_enabled) + return FALSE; + + g_assert(priv->modem); + modem_state = nm_modem_get_state(priv->modem); + if (modem_state <= NM_MODEM_STATE_INITIALIZING) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +set_modem(NMDeviceModem *self, NMModem *modem) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + g_return_if_fail(modem != NULL); + + priv->modem = nm_modem_claim(modem); + + g_signal_connect(modem, NM_MODEM_PPP_FAILED, G_CALLBACK(ppp_failed), self); + g_signal_connect(modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK(modem_prepare_result), self); + g_signal_connect(modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK(modem_ip4_config_result), self); + g_signal_connect(modem, NM_MODEM_IP6_CONFIG_RESULT, G_CALLBACK(modem_ip6_config_result), self); + g_signal_connect(modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK(modem_auth_requested), self); + g_signal_connect(modem, NM_MODEM_AUTH_RESULT, G_CALLBACK(modem_auth_result), self); + g_signal_connect(modem, NM_MODEM_STATE_CHANGED, G_CALLBACK(modem_state_cb), self); + g_signal_connect(modem, NM_MODEM_REMOVED, G_CALLBACK(modem_removed_cb), self); + + g_signal_connect(modem, + "notify::" NM_MODEM_IP_IFINDEX, + G_CALLBACK(ip_ifindex_changed_cb), + self); + g_signal_connect(modem, "notify::" NM_MODEM_DEVICE_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, "notify::" NM_MODEM_SIM_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, "notify::" NM_MODEM_SIM_OPERATOR_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, + "notify::" NM_MODEM_OPERATOR_CODE, + G_CALLBACK(operator_code_changed_cb), + self); + g_signal_connect(modem, "notify::" NM_MODEM_APN, G_CALLBACK(apn_changed_cb), self); +} + +static guint32 +get_dhcp_timeout_for_device(NMDevice *device, int addr_family) +{ + /* DHCP is always done by the modem firmware, not by the network, and + * by the time we get around to DHCP the firmware should already know + * the IP addressing details. So the DHCP timeout can be much shorter. + */ + return 15; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object(value, priv->modem); + break; + case PROP_CAPABILITIES: + g_value_set_uint(value, priv->caps); + break; + case PROP_CURRENT_CAPABILITIES: + g_value_set_uint(value, priv->current_caps); + break; + case PROP_DEVICE_ID: + g_value_set_string(value, priv->device_id); + break; + case PROP_OPERATOR_CODE: + g_value_set_string(value, priv->operator_code); + break; + case PROP_APN: + g_value_set_string(value, priv->apn); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + set_modem(NM_DEVICE_MODEM(object), g_value_get_object(value)); + break; + case PROP_CAPABILITIES: + priv->caps = g_value_get_uint(value); + break; + case PROP_CURRENT_CAPABILITIES: + priv->current_caps = g_value_get_uint(value); + break; + case PROP_DEVICE_ID: + /* construct-only */ + priv->device_id = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_modem_init(NMDeviceModem *self) +{} + +NMDevice * +nm_device_modem_new(NMModem *modem) +{ + NMDeviceModemCapabilities caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMDeviceModemCapabilities current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + + g_return_val_if_fail(NM_IS_MODEM(modem), NULL); + + /* Load capabilities */ + nm_modem_get_capabilities(modem, &caps, ¤t_caps); + + return g_object_new(NM_TYPE_DEVICE_MODEM, + NM_DEVICE_UDI, + nm_modem_get_path(modem), + NM_DEVICE_IFACE, + nm_modem_get_uid(modem), + NM_DEVICE_DRIVER, + nm_modem_get_driver(modem), + NM_DEVICE_TYPE_DESC, + "Broadband", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_MODEM, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WWAN, + NM_DEVICE_MODEM_MODEM, + modem, + NM_DEVICE_MODEM_CAPABILITIES, + caps, + NM_DEVICE_MODEM_CURRENT_CAPABILITIES, + current_caps, + NM_DEVICE_MODEM_DEVICE_ID, + nm_modem_get_device_id(modem), + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + if (priv->modem) { + g_signal_handlers_disconnect_by_data(priv->modem, NM_DEVICE_MODEM(object)); + nm_clear_pointer(&priv->modem, nm_modem_unclaim); + } + + nm_clear_g_free(&priv->device_id); + nm_clear_g_free(&priv->operator_code); + nm_clear_g_free(&priv->apn); + + G_OBJECT_CLASS(nm_device_modem_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_modem = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_MODEM, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ModemCapabilities", + "u", + NM_DEVICE_MODEM_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CurrentCapabilities", + "u", + NM_DEVICE_MODEM_CURRENT_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DeviceId", + "s", + NM_DEVICE_MODEM_DEVICE_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("OperatorCode", + "s", + NM_DEVICE_MODEM_OPERATOR_CODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Apn", "s", NM_DEVICE_MODEM_APN), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_modem_class_init(NMDeviceModemClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_modem); + + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_type_description = get_type_description; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->deactivate_async = deactivate_async; + device_class->deactivate = deactivate; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->ip4_config_pre_commit = ip4_config_pre_commit; + device_class->get_enabled = get_enabled; + device_class->set_enabled = set_enabled; + device_class->owns_iface = owns_iface; + device_class->is_available = is_available; + device_class->get_ip_iface_identifier = get_ip_iface_identifier; + device_class->get_configured_mtu = nm_modem_get_configured_mtu; + device_class->get_dhcp_timeout_for_device = get_dhcp_timeout_for_device; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_MODEM] = + g_param_spec_object(NM_DEVICE_MODEM_MODEM, + "", + "", + NM_TYPE_MODEM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_MODEM_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CURRENT_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_MODEM_CURRENT_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICE_ID] = + g_param_spec_string(NM_DEVICE_MODEM_DEVICE_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_OPERATOR_CODE] = + g_param_spec_string(NM_DEVICE_MODEM_OPERATOR_CODE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_APN] = g_param_spec_string(NM_DEVICE_MODEM_APN, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wwan/nm-device-modem.h b/src/core/devices/wwan/nm-device-modem.h new file mode 100644 index 0000000..f171d76 --- /dev/null +++ b/src/core/devices/wwan/nm-device-modem.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_MODEM_H__ +#define __NETWORKMANAGER_DEVICE_MODEM_H__ + +#include "devices/nm-device.h" +#include "nm-modem.h" + +#define NM_TYPE_DEVICE_MODEM (nm_device_modem_get_type()) +#define NM_DEVICE_MODEM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModem)) +#define NM_DEVICE_MODEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) +#define NM_IS_DEVICE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_MODEM)) +#define NM_IS_DEVICE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_MODEM)) +#define NM_DEVICE_MODEM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) + +#define NM_DEVICE_MODEM_MODEM "modem" +#define NM_DEVICE_MODEM_CAPABILITIES "modem-capabilities" +#define NM_DEVICE_MODEM_CURRENT_CAPABILITIES "current-capabilities" +#define NM_DEVICE_MODEM_DEVICE_ID "device-id" +#define NM_DEVICE_MODEM_OPERATOR_CODE "operator-code" +#define NM_DEVICE_MODEM_APN "apn" + +typedef struct _NMDeviceModem NMDeviceModem; +typedef struct _NMDeviceModemClass NMDeviceModemClass; + +GType nm_device_modem_get_type(void); + +NMDevice *nm_device_modem_new(NMModem *modem); + +#endif /* __NETWORKMANAGER_DEVICE_MODEM_H__ */ diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c new file mode 100644 index 0000000..d2debac --- /dev/null +++ b/src/core/devices/wwan/nm-modem-broadband.c @@ -0,0 +1,1625 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Aleksander Morgado + */ + +#include "nm-default.h" + +#include "nm-modem-broadband.h" +#include "nm-service-providers.h" + +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" + +#define NM_MODEM_BROADBAND_MODEM "modem" + +static gboolean +MODEM_CAPS_3GPP(MMModemCapability caps) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + /* MM_MODEM_CAPABILITY_LTE_ADVANCED is marked as deprecated since ModemManager 1.14.0. + * + * The flag probably was never used, it certainly isn't used since 1.14.0. + * + * Still, just to be sure, there is no harm in checking it here. Suppress the + * warning, it should have no bad effect. + */ + return NM_FLAGS_ANY(caps, + (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE + | MM_MODEM_CAPABILITY_LTE_ADVANCED)); + G_GNUC_END_IGNORE_DEPRECATIONS +} + +#define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO)) + +/* Maximum time to keep the DBus call waiting for a connection result. + * This value is greater than the default timeout in ModemManager (180s since + * 1.16), so that whenever possible the timeout happens first there instead of + * in NetworkManager. */ +#define MODEM_CONNECT_TIMEOUT_SECS 200 + +/*****************************************************************************/ + +typedef enum { + CONNECT_STEP_FIRST, + CONNECT_STEP_WAIT_FOR_SIM, + CONNECT_STEP_UNLOCK, + CONNECT_STEP_WAIT_FOR_READY, + CONNECT_STEP_CONNECT, + CONNECT_STEP_LAST, +} ConnectStep; + +typedef struct { + NMModemBroadband *self; + ConnectStep step; + + MMModemCapability caps; + NMConnection * connection; + GCancellable * cancellable; + MMSimpleConnectProperties *connect_properties; + GArray * ip_types; + guint ip_types_i; + guint ip_type_tries; + GError * first_error; +} ConnectContext; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_MODEM, ); + +typedef struct { + /* The modem object from dbus */ + MMObject *modem_object; + /* Per-interface objects */ + MMModem * modem_iface; + MMModem3gpp * modem_3gpp_iface; + MMModemSimple *simple_iface; + MMSim * sim_iface; + + /* Connection setup */ + ConnectContext *ctx; + + MMBearer * bearer; + MMBearerIpConfig *ipv4_config; + MMBearerIpConfig *ipv6_config; + + guint idle_id_ip4; + guint idle_id_ip6; + + guint32 pin_tries; +} NMModemBroadbandPrivate; + +struct _NMModemBroadband { + NMModem parent; + NMModemBroadbandPrivate _priv; +}; + +struct _NMModemBroadbandClass { + NMModemClass parent; +}; + +G_DEFINE_TYPE(NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM) + +#define NM_MODEM_BROADBAND_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMModemBroadband, NM_IS_MODEM_BROADBAND) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG_PREFIX_NAME "modem-broadband" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \ + NMModemBroadband *const __self = (self); \ + char __prefix_name[128]; \ + const char * __uid; \ + \ + _nm_log(_level, \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + ((__self && __self->_priv.ctx) \ + ? nm_connection_get_uuid(__self->_priv.ctx->connection) \ + : NULL), \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (__self ? ({ \ + ((__uid = nm_modem_get_uid((NMModem *) __self)) \ + ? nm_sprintf_buf(__prefix_name, "[%s]", __uid) \ + : "(null)"); \ + }) \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static NMDeviceStateReason +translate_mm_error(NMModemBroadband *self, GError *error) +{ + NMDeviceStateReason reason; + + g_return_val_if_fail(error != NULL, NM_DEVICE_STATE_REASON_UNKNOWN); + + if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_CARRIER)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_BUSY)) + reason = NM_DEVICE_STATE_REASON_MODEM_BUSY; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_ANSWER)) + reason = NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_WRONG; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD)) + reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT; + else { + /* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */ + _LOGD("unmapped error detected: '%s'", error->message); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + + return reason; +} + +/*****************************************************************************/ + +static void +get_capabilities(NMModem * _self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(_self); + MMModemCapability all_supported = MM_MODEM_CAPABILITY_NONE; + MMModemCapability *supported; + guint n_supported; + + /* For now, we don't care about the capability combinations, just merge all + * combinations in a single mask */ + if (mm_modem_get_supported_capabilities(self->_priv.modem_iface, &supported, &n_supported)) { + guint i; + + for (i = 0; i < n_supported; i++) + all_supported |= supported[i]; + + g_free(supported); + } + + *modem_caps = (NMDeviceModemCapabilities) all_supported; + *current_caps = + (NMDeviceModemCapabilities) mm_modem_get_current_capabilities(self->_priv.modem_iface); +} + +static gboolean +owns_port(NMModem *_self, const char *iface) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(_self); + const MMModemPortInfo *ports = NULL; + guint n_ports = 0, i; + + mm_modem_peek_ports(self->_priv.modem_iface, &ports, &n_ports); + for (i = 0; i < n_ports; i++) { + if (nm_streq0(iface, ports[i].name)) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static void +ask_for_pin(NMModemBroadband *self) +{ + guint32 tries; + + tries = self->_priv.pin_tries++; + nm_modem_get_secrets(NM_MODEM(self), + NM_SETTING_GSM_SETTING_NAME, + tries ? TRUE : FALSE, + NM_SETTING_GSM_PIN); +} + +static NMModemIPMethod +get_bearer_ip_method(MMBearerIpConfig *config) +{ + MMBearerIpMethod mm_method; + + mm_method = mm_bearer_ip_config_get_method(config); + if (mm_method == MM_BEARER_IP_METHOD_PPP) + return NM_MODEM_IP_METHOD_PPP; + else if (mm_method == MM_BEARER_IP_METHOD_STATIC) + return NM_MODEM_IP_METHOD_STATIC; + else if (mm_method == MM_BEARER_IP_METHOD_DHCP) + return NM_MODEM_IP_METHOD_AUTO; + return NM_MODEM_IP_METHOD_UNKNOWN; +} + +static MMSimpleConnectProperties * +create_cdma_connect_properties(NMConnection *connection) +{ + MMSimpleConnectProperties *properties; + + properties = mm_simple_connect_properties_new(); + +#if !MM_CHECK_VERSION(1, 9, 1) + { + NMSettingCdma *setting; + const char * str; + + setting = nm_connection_get_setting_cdma(connection); + str = nm_setting_cdma_get_number(setting); + if (str) + mm_simple_connect_properties_set_number(properties, str); + } +#endif + + return properties; +} + +static MMSimpleConnectProperties * +create_gsm_connect_properties(NMConnection *connection, + const char * apn, + const char * username, + const char * password) +{ + NMSettingGsm * setting; + NMSettingPpp * s_ppp; + MMSimpleConnectProperties *properties; + const char * str; + + setting = nm_connection_get_setting_gsm(connection); + + properties = mm_simple_connect_properties_new(); + + mm_simple_connect_properties_set_apn(properties, apn ?: ""); + if (username) + mm_simple_connect_properties_set_user(properties, username); + if (password) + mm_simple_connect_properties_set_password(properties, password); + + str = nm_setting_gsm_get_network_id(setting); + if (str) + mm_simple_connect_properties_set_operator_id(properties, str); + + str = nm_setting_gsm_get_pin(setting); + if (str) + mm_simple_connect_properties_set_pin(properties, str); + + /* Roaming */ + if (nm_setting_gsm_get_home_only(setting)) + mm_simple_connect_properties_set_allow_roaming(properties, FALSE); + + /* For IpMethod == STATIC or DHCP */ + s_ppp = nm_connection_get_setting_ppp(connection); + if (s_ppp) { + MMBearerAllowedAuth allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + + if (nm_setting_ppp_get_noauth(s_ppp)) + allowed_auth = MM_BEARER_ALLOWED_AUTH_NONE; + if (!nm_setting_ppp_get_refuse_pap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_PAP; + if (!nm_setting_ppp_get_refuse_chap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_CHAP; + if (!nm_setting_ppp_get_refuse_mschap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAP; + if (!nm_setting_ppp_get_refuse_mschapv2(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAPV2; + if (!nm_setting_ppp_get_refuse_eap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_EAP; + + mm_simple_connect_properties_set_allowed_auth(properties, allowed_auth); + } + + return properties; +} + +static void +connect_context_clear(NMModemBroadband *self) +{ + if (self->_priv.ctx) { + ConnectContext *ctx = self->_priv.ctx; + + g_clear_error(&ctx->first_error); + nm_clear_pointer(&ctx->ip_types, g_array_unref); + nm_clear_g_cancellable(&ctx->cancellable); + g_clear_object(&ctx->connection); + g_clear_object(&ctx->connect_properties); + g_clear_object(&ctx->self); + g_slice_free(ConnectContext, ctx); + self->_priv.ctx = NULL; + } +} + +static void connect_context_step(NMModemBroadband *self); + +static void +connect_ready(MMModemSimple *simple_iface, GAsyncResult *res, NMModemBroadband *self) +{ + ConnectContext *ctx; + GError * error = NULL; + NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN; + NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN; + MMBearer * bearer; + + bearer = mm_modem_simple_connect_finish(simple_iface, res, &error); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + + ctx = self->_priv.ctx; + + if (!ctx) + return; + + self->_priv.bearer = bearer; + + if (!self->_priv.bearer) { + if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) + || (g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) + && mm_modem_get_unlock_required(self->_priv.modem_iface) + == MM_MODEM_LOCK_SIM_PIN)) { + g_error_free(error); + + /* Request PIN */ + ask_for_pin(self); + connect_context_clear(self); + return; + } + + /* Save the error, if it's the first one */ + if (!ctx->first_error) { + /* Strip remote error info before saving it */ + if (g_dbus_error_is_remote_error(error)) + g_dbus_error_strip_remote_error(error); + ctx->first_error = error; + } else + g_clear_error(&error); + + if (ctx->ip_type_tries == 0 && g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY)) { + /* Try one more time */ + ctx->ip_type_tries++; + } else { + /* If the modem/provider lies and the IP type we tried isn't supported, + * retry with the next one, if any. + */ + ctx->ip_types_i++; + ctx->ip_type_tries = 0; + } + connect_context_step(self); + return; + } + + /* Grab IP configurations */ + self->_priv.ipv4_config = mm_bearer_get_ipv4_config(self->_priv.bearer); + if (self->_priv.ipv4_config) + ip4_method = get_bearer_ip_method(self->_priv.ipv4_config); + + self->_priv.ipv6_config = mm_bearer_get_ipv6_config(self->_priv.bearer); + if (self->_priv.ipv6_config) + ip6_method = get_bearer_ip_method(self->_priv.ipv6_config); + + if (!nm_modem_set_data_port(NM_MODEM(self), + NM_PLATFORM_GET, + mm_bearer_get_interface(self->_priv.bearer), + ip4_method, + ip6_method, + mm_bearer_get_ip_timeout(self->_priv.bearer), + &error)) { + _LOGW("failed to connect modem: %s", error->message); + g_error_free(error); + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + connect_context_clear(self); + return; + } + + ctx->step++; + connect_context_step(self); +} + +static void +send_pin_ready(MMSim *sim, GAsyncResult *result, NMModemBroadband *self) +{ + gs_free_error GError *error = NULL; + + mm_sim_send_pin_finish(sim, result, &error); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (!self->_priv.ctx || self->_priv.ctx->step != CONNECT_STEP_UNLOCK) + g_return_if_reached(); + + if (error) { + if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) + || (g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) + && mm_modem_get_unlock_required(self->_priv.modem_iface) == MM_MODEM_LOCK_SIM_PIN)) + ask_for_pin(self); + else + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, translate_mm_error(self, error)); + return; + } + + self->_priv.ctx->step++; + connect_context_step(self); +} + +static void +find_gsm_apn_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + NMModemBroadband * self = user_data; + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + ConnectContext * ctx = priv->ctx; + + if (error) { + _LOGW("failed to connect '%s': APN not found: %s", + nm_connection_get_id(ctx->connection), + error->message); + + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_GSM_APN_FAILED); + connect_context_clear(self); + return; + } + + /* Blank APN ("") means the default subscription APN */ + ctx->connect_properties = + create_gsm_connect_properties(ctx->connection, apn, username, password); + g_return_if_fail(ctx->connect_properties); + connect_context_step(self); +} + +static gboolean +try_create_connect_properties(NMModemBroadband *self) +{ + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + ConnectContext * ctx = priv->ctx; + + if (MODEM_CAPS_3GPP(ctx->caps)) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection); + + if (!s_gsm || nm_setting_gsm_get_auto_config(s_gsm)) { + gs_unref_object MMModem3gpp *modem_3gpp = NULL; + const char * network_id = NULL; + + s_gsm = nm_connection_get_setting_gsm(ctx->connection); + if (s_gsm) + network_id = nm_setting_gsm_get_network_id(s_gsm); + if (!network_id) { + if (mm_modem_get_state(self->_priv.modem_iface) < MM_MODEM_STATE_REGISTERED) + return FALSE; + modem_3gpp = mm_object_get_modem_3gpp(priv->modem_object); + network_id = mm_modem_3gpp_get_operator_code(modem_3gpp); + } + if (!network_id) { + _LOGW("failed to connect '%s': unable to determine the network id", + nm_connection_get_id(ctx->connection)); + goto out; + } + + nm_service_providers_find_gsm_apn(MOBILE_BROADBAND_PROVIDER_INFO_DATABASE, + network_id, + ctx->cancellable, + find_gsm_apn_cb, + self); + } else { + ctx->connect_properties = + create_gsm_connect_properties(ctx->connection, + nm_setting_gsm_get_apn(s_gsm), + nm_setting_gsm_get_username(s_gsm), + nm_setting_gsm_get_password(s_gsm)); + g_return_val_if_fail(ctx->connect_properties, TRUE); + } + + return TRUE; + } else if (MODEM_CAPS_3GPP2(ctx->caps)) { + ctx->connect_properties = create_cdma_connect_properties(ctx->connection); + g_return_val_if_fail(ctx->connect_properties, FALSE); + return TRUE; + } else { + _LOGW("failed to connect '%s': not a mobile broadband modem", + nm_connection_get_id(ctx->connection)); + } + +out: + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + connect_context_clear(self); + return TRUE; +} + +static void +connect_context_step(NMModemBroadband *self) +{ + ConnectContext *ctx = self->_priv.ctx; + + switch (ctx->step) { + case CONNECT_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_WAIT_FOR_SIM: + if (MODEM_CAPS_3GPP(ctx->caps) && !self->_priv.sim_iface) { + /* Have to wait for the SIM to show up */ + break; + } + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_UNLOCK: + if (MODEM_CAPS_3GPP(ctx->caps) + && mm_modem_get_unlock_required(self->_priv.modem_iface) == MM_MODEM_LOCK_SIM_PIN) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection); + const char * pin = nm_setting_gsm_get_pin(s_gsm); + + /* If we have a PIN already, send it. If we don't, get it. */ + if (pin) { + mm_sim_send_pin(self->_priv.sim_iface, + pin, + ctx->cancellable, + (GAsyncReadyCallback) send_pin_ready, + self); + } else { + ask_for_pin(self); + } + break; + } + ctx->step++; + /* fall-through */ + case CONNECT_STEP_WAIT_FOR_READY: + { + GError *error = NULL; + + if (mm_modem_get_state(self->_priv.modem_iface) <= MM_MODEM_STATE_LOCKED) + break; + + if (!try_create_connect_properties(self)) + break; + + if (!self->_priv.ctx) + break; + + /* Build up list of IP types that we need to use in the retries */ + ctx->ip_types = nm_modem_get_connection_ip_type(NM_MODEM(self), ctx->connection, &error); + if (!ctx->ip_types) { + _LOGW("failed to connect '%s': %s", + nm_connection_get_id(ctx->connection), + error->message); + g_clear_error(&error); + + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + connect_context_clear(self); + break; + } + + ctx->step++; + } + /* fall-through */ + case CONNECT_STEP_CONNECT: + if (!ctx->connect_properties) + break; + + if (ctx->ip_types_i < ctx->ip_types->len) { + NMModemIPType current; + + current = g_array_index(ctx->ip_types, NMModemIPType, ctx->ip_types_i); + + if (current == NM_MODEM_IP_TYPE_IPV4) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV4); + else if (current == NM_MODEM_IP_TYPE_IPV6) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV6); + else if (current == NM_MODEM_IP_TYPE_IPV4V6) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV4V6); + else + g_return_if_reached(); + + _nm_modem_set_apn(NM_MODEM(self), + mm_simple_connect_properties_get_apn(ctx->connect_properties)); + + _LOGD("launching connection with ip type '%s' (try %d)", + nm_modem_ip_type_to_string(current), + ctx->ip_type_tries + 1); + + mm_modem_simple_connect(self->_priv.simple_iface, + ctx->connect_properties, + ctx->cancellable, + (GAsyncReadyCallback) connect_ready, + self); + break; + } + + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_LAST: + if (self->_priv.ipv4_config || self->_priv.ipv6_config) + nm_modem_emit_prepare_result(NM_MODEM(self), TRUE, NM_DEVICE_STATE_REASON_NONE); + else { + /* If we have a saved error from a previous attempt, use it */ + if (!ctx->first_error) + ctx->first_error = g_error_new_literal(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "invalid bearer IP configuration"); + + _LOGW("failed to connect modem: %s", ctx->first_error->message); + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + translate_mm_error(self, ctx->first_error)); + } + + connect_context_clear(self); + break; + } +} + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * _self, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + + /* Make sure we can get the Simple interface from the modem */ + if (!self->_priv.simple_iface) { + self->_priv.simple_iface = mm_object_get_modem_simple(self->_priv.modem_object); + if (!self->_priv.simple_iface) { + _LOGW("cannot access the Simple mobile broadband modem interface"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + connect_context_clear(self); + + /* Allocate new context for this connect stage attempt */ + self->_priv.ctx = g_slice_new0(ConnectContext); + self->_priv.ctx->caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + self->_priv.ctx->cancellable = g_cancellable_new(); + self->_priv.ctx->connection = g_object_ref(connection); + + g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(self->_priv.simple_iface), + MODEM_CONNECT_TIMEOUT_SECS * 1000); + connect_context_step(self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible_with_modem(NMModem *_self, NMConnection *connection, GError **error) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + MMModemCapability modem_caps; + + modem_caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + + if (MODEM_CAPS_3GPP(modem_caps)) { + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error)) + return FALSE; + + return TRUE; + } + + if (MODEM_CAPS_3GPP2(modem_caps)) { + if (!_nm_connection_check_main_setting(connection, NM_SETTING_CDMA_SETTING_NAME, error)) + return FALSE; + + return TRUE; + } + + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL) + && !_nm_connection_check_main_setting(connection, NM_SETTING_CDMA_SETTING_NAME, NULL)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection type %s is not supported by modem", + nm_connection_get_connection_type(connection)); + return FALSE; + } + + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem lacks capabilities for %s profile", + nm_connection_get_connection_type(connection)); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +complete_connection(NMModem * modem, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(modem); + MMModemCapability modem_caps; + NMSettingPpp * s_ppp; + + modem_caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + + /* PPP settings common to 3GPP and 3GPP2 */ + s_ppp = nm_connection_get_setting_ppp(connection); + if (!s_ppp) { + s_ppp = (NMSettingPpp *) nm_setting_ppp_new(); + g_object_set(G_OBJECT(s_ppp), + NM_SETTING_PPP_LCP_ECHO_FAILURE, + 5, + NM_SETTING_PPP_LCP_ECHO_INTERVAL, + 30, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ppp)); + } + + if (MODEM_CAPS_3GPP(modem_caps)) { + NMSettingGsm *s_gsm; + + s_gsm = nm_connection_get_setting_gsm(connection); + if (!s_gsm) { + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + nm_connection_add_setting(connection, NM_SETTING(s_gsm)); + g_object_set(G_OBJECT(s_gsm), NM_SETTING_GSM_AUTO_CONFIG, TRUE, NULL); + } + + if (!nm_setting_gsm_get_device_id(s_gsm)) { + g_object_set(G_OBJECT(s_gsm), + NM_SETTING_GSM_DEVICE_ID, + nm_modem_get_device_id(modem), + NULL); + } + + nm_utils_complete_generic(NM_PLATFORM_GET, + connection, + NM_SETTING_GSM_SETTING_NAME, + existing_connections, + NULL, + _("GSM connection"), + NULL, + NULL, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + if (MODEM_CAPS_3GPP2(modem_caps)) { + NMSettingCdma *s_cdma; + + s_cdma = nm_connection_get_setting_cdma(connection); + if (!s_cdma) { + s_cdma = (NMSettingCdma *) nm_setting_cdma_new(); + nm_connection_add_setting(connection, NM_SETTING(s_cdma)); + } + + if (!nm_setting_cdma_get_number(s_cdma)) + g_object_set(G_OBJECT(s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL); + + nm_utils_complete_generic(NM_PLATFORM_GET, + connection, + NM_SETTING_CDMA_SETTING_NAME, + existing_connections, + NULL, + _("CDMA connection"), + NULL, + iface, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Device is not a mobile broadband modem"); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +get_user_pass(NMModem *modem, NMConnection *connection, const char **user, const char **pass) +{ + NMSettingGsm * s_gsm; + NMSettingCdma *s_cdma; + + s_gsm = nm_connection_get_setting_gsm(connection); + s_cdma = nm_connection_get_setting_cdma(connection); + if (!s_gsm && !s_cdma) + return FALSE; + + if (user) { + if (s_gsm) + *user = nm_setting_gsm_get_username(s_gsm); + else if (s_cdma) + *user = nm_setting_cdma_get_username(s_cdma); + } + if (pass) { + if (s_gsm) + *pass = nm_setting_gsm_get_password(s_gsm); + else if (s_cdma) + *pass = nm_setting_cdma_get_password(s_cdma); + } + + return TRUE; +} + +/*****************************************************************************/ +/* Query/Update enabled state */ + +static void +set_power_state_low_ready(MMModem *modem, GAsyncResult *result, NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_set_power_state_finish(modem, result, &error)) { + /* Log but ignore errors; not all modems support low power state */ + _LOGD("failed to set modem low power state: %s", NM_G_ERROR_MSG(error)); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +modem_disable_ready(MMModem *modem_iface, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + + if (mm_modem_disable_finish(modem_iface, res, &error)) { + /* Once disabled, move to low-power mode */ + mm_modem_set_power_state(modem_iface, + MM_MODEM_POWER_STATE_LOW, + NULL, + (GAsyncReadyCallback) set_power_state_low_ready, + g_object_ref(self)); + } else { + _LOGW("failed to disable modem: %s", NM_G_ERROR_MSG(error)); + nm_modem_set_prev_state(NM_MODEM(self), "disable failed"); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +modem_enable_ready(MMModem *modem_iface, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_enable_finish(modem_iface, res, &error)) { + _LOGW("failed to enable modem: %s", NM_G_ERROR_MSG(error)); + nm_modem_set_prev_state(NM_MODEM(self), "enable failed"); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +set_mm_enabled(NMModem *_self, gboolean enabled) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + + if (enabled) { + mm_modem_enable(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) modem_enable_ready, + g_object_ref(self)); + } else { + mm_modem_disable(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) modem_disable_ready, + g_object_ref(self)); + } +} + +/*****************************************************************************/ +/* IPv4 method static */ + +static gboolean +static_stage3_ip4_done(NMModemBroadband *self) +{ + GError * error = NULL; + gs_unref_object NMIP4Config *config = NULL; + const char * data_port; + const char * address_string; + const char * gw_string; + guint32 address_network; + guint32 gw = 0; + NMPlatformIP4Address address; + const char ** dns; + guint i; + guint32 ip4_route_table, ip4_route_metric; + NMPlatformIP4Route * r; + guint32 mtu_n; + + g_return_val_if_fail(self->_priv.ipv4_config, FALSE); + g_return_val_if_fail(self->_priv.bearer, FALSE); + + self->_priv.idle_id_ip4 = 0; + + _LOGI("IPv4 static configuration:"); + + /* Fully fail if invalid IP address retrieved */ + address_string = mm_bearer_ip_config_get_address(self->_priv.ipv4_config); + if (!address_string + || !nm_utils_parse_inaddr_bin(AF_INET, address_string, NULL, &address_network)) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s", + nm_modem_get_uid(NM_MODEM(self)), + NM_PRINT_FMT_QUOTE_STRING(address_string)); + goto out; + } + + /* Missing gateway not a hard failure */ + gw_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv4_config); + if (gw_string && !nm_utils_parse_inaddr_bin(AF_INET, gw_string, NULL, &gw)) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"", + nm_modem_get_uid(NM_MODEM(self)), + gw_string); + goto out; + } + + data_port = mm_bearer_get_interface(self->_priv.bearer); + g_return_val_if_fail(data_port, FALSE); + config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port)); + + memset(&address, 0, sizeof(address)); + address.address = address_network; + address.peer_address = address_network; + address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv4_config); + address.addr_source = NM_IP_CONFIG_SOURCE_WWAN; + if (address.plen <= 32) + nm_ip4_config_add_address(config, &address); + + _LOGI(" address %s/%d", address_string, address.plen); + + nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL); + r = &(NMPlatformIP4Route){ + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = gw, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + nm_ip4_config_add_route(config, r, NULL); + _LOGI(" gateway %s", gw_string); + + /* DNS servers */ + dns = mm_bearer_ip_config_get_dns(self->_priv.ipv4_config); + for (i = 0; dns && dns[i]; i++) { + if (nm_utils_parse_inaddr_bin(AF_INET, dns[i], NULL, &address_network) + && address_network > 0) { + nm_ip4_config_add_nameserver(config, address_network); + _LOGI(" DNS %s", dns[i]); + } + } + +#if MM_CHECK_VERSION(1, 4, 0) + mtu_n = mm_bearer_ip_config_get_mtu(self->_priv.ipv4_config); + if (mtu_n) { + nm_ip4_config_set_mtu(config, mtu_n, NM_IP_CONFIG_SOURCE_WWAN); + _LOGI(" MTU %u", mtu_n); + } +#endif + +out: + g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, config, error); + g_clear_error(&error); + return FALSE; +} + +static NMActStageReturn +static_stage3_ip4_config_start(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + /* We schedule it in an idle just to follow the same logic as in the + * generic modem implementation. */ + nm_clear_g_source(&priv->idle_id_ip4); + priv->idle_id_ip4 = g_idle_add((GSourceFunc) static_stage3_ip4_done, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* IPv6 method static */ + +static gboolean +stage3_ip6_done(NMModemBroadband *self) +{ + GError * error = NULL; + NMIP6Config * config = NULL; + const char * data_port; + const char * address_string; + NMPlatformIP6Address address; + NMModemIPMethod ip_method; + const char ** dns; + guint i; + + g_return_val_if_fail(self->_priv.ipv6_config, FALSE); + + self->_priv.idle_id_ip6 = 0; + memset(&address, 0, sizeof(address)); + + ip_method = get_bearer_ip_method(self->_priv.ipv6_config); + + address_string = mm_bearer_ip_config_get_address(self->_priv.ipv6_config); + if (!address_string) { + /* DHCP/SLAAC is allowed to skip addresses; other methods require it */ + if (ip_method != NM_MODEM_IP_METHOD_AUTO) { + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: no address given", + nm_modem_get_uid(NM_MODEM(self))); + } + goto out; + } + + /* Fail if invalid IP address retrieved */ + if (!inet_pton(AF_INET6, address_string, (void *) &(address.address))) { + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: invalid address given '%s'", + nm_modem_get_uid(NM_MODEM(self)), + address_string); + goto out; + } + + _LOGI("IPv6 base configuration:"); + + data_port = mm_bearer_get_interface(self->_priv.bearer); + g_return_val_if_fail(data_port, FALSE); + + config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port)); + + address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv6_config); + if (address.plen <= 128) + nm_ip6_config_add_address(config, &address); + + _LOGI(" address %s/%d", address_string, address.plen); + + address_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv6_config); + if (address_string) { + guint32 ip6_route_table, ip6_route_metric; + + if (inet_pton(AF_INET6, address_string, &address.address) != 1) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'", + nm_modem_get_uid(NM_MODEM(self)), + address_string); + goto out; + } + + nm_modem_get_route_parameters(NM_MODEM(self), + NULL, + NULL, + &ip6_route_table, + &ip6_route_metric); + { + const NMPlatformIP6Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = address.address, + .table_coerced = nm_platform_route_table_coerce(ip6_route_table), + .metric = ip6_route_metric, + }; + + _LOGI(" gateway %s", address_string); + nm_ip6_config_add_route(config, &r, NULL); + } + } else if (ip_method == NM_MODEM_IP_METHOD_STATIC) { + /* Gateway required for the 'static' method */ + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: missing gateway", + nm_modem_get_uid(NM_MODEM(self))); + goto out; + } + + /* DNS servers */ + dns = mm_bearer_ip_config_get_dns(self->_priv.ipv6_config); + for (i = 0; dns && dns[i]; i++) { + struct in6_addr addr; + + if (inet_pton(AF_INET6, dns[i], &addr)) { + nm_ip6_config_add_nameserver(config, &addr); + _LOGI(" DNS %s", dns[i]); + } + } + +out: + nm_modem_emit_ip6_config_result(NM_MODEM(self), config, error); + g_clear_object(&config); + g_clear_error(&error); + return FALSE; +} + +static NMActStageReturn +stage3_ip6_config_request(NMModem *modem, NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + /* We schedule it in an idle just to follow the same logic as in the + * generic modem implementation. */ + nm_clear_g_source(&priv->idle_id_ip6); + priv->idle_id_ip6 = g_idle_add((GSourceFunc) stage3_ip6_done, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* Disconnect */ + +typedef struct { + NMModemBroadband * self; + _NMModemDisconnectCallback callback; + gpointer callback_user_data; + GCancellable * cancellable; + gboolean warn; +} DisconnectContext; + +static void +disconnect_context_complete(DisconnectContext *ctx, GError *error) +{ + if (ctx->callback) + ctx->callback(NM_MODEM(ctx->self), error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->self); + g_slice_free(DisconnectContext, ctx); +} + +static void +disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable) +{ + DisconnectContext *ctx = user_data; + gs_free_error GError *cancelled_error = NULL; + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + disconnect_context_complete(ctx, cancelled_error); +} + +static void +simple_disconnect_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + MMModemSimple * modem_iface = MM_MODEM_SIMPLE(source_object); + DisconnectContext *ctx = user_data; + gs_free_error GError *error = NULL; + + if (!mm_modem_simple_disconnect_finish(modem_iface, res, &error)) { + if (ctx->warn && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) { + NMModemBroadband *self = ctx->self; + + _LOGW("failed to disconnect modem: %s", error->message); + } + } + + disconnect_context_complete(ctx, error); +} + +static void +disconnect(NMModem * modem, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + DisconnectContext *ctx; + + connect_context_clear(self); + _nm_modem_set_apn(NM_MODEM(self), NULL); + + ctx = g_slice_new0(DisconnectContext); + ctx->self = g_object_ref(self); + ctx->cancellable = nm_g_object_ref(cancellable); + ctx->callback = callback; + ctx->callback_user_data = user_data; + + /* Don't bother warning on FAILED since the modem is already gone */ + ctx->warn = warn; + + /* Already cancelled or no simple-iface? We are done. */ + if (!ctx->self->_priv.simple_iface || g_cancellable_is_cancelled(cancellable)) { + nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx); + return; + } + + _LOGD("notifying ModemManager about the modem disconnection"); + mm_modem_simple_disconnect(self->_priv.simple_iface, + NULL, /* bearer path; if NULL given ALL get disconnected */ + cancellable, + simple_disconnect_ready, + ctx); +} + +/*****************************************************************************/ + +static void +deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(modem); + + /* TODO: cancel SimpleConnect() if any */ + + /* Cleanup IPv4 addresses and routes */ + g_clear_object(&self->_priv.ipv4_config); + g_clear_object(&self->_priv.ipv6_config); + g_clear_object(&self->_priv.bearer); + + self->_priv.pin_tries = 0; + + NM_MODEM_CLASS(nm_modem_broadband_parent_class) + ->deactivate_cleanup(modem, device, stop_ppp_manager); +} + +/*****************************************************************************/ + +#define MAP_STATE(name) \ + case MM_MODEM_STATE_##name: \ + return NM_MODEM_STATE_##name; + +static NMModemState +mm_state_to_nm(MMModemState mm_state) +{ + switch (mm_state) { + MAP_STATE(UNKNOWN) + MAP_STATE(FAILED) + MAP_STATE(INITIALIZING) + MAP_STATE(LOCKED) + MAP_STATE(DISABLED) + MAP_STATE(DISABLING) + MAP_STATE(ENABLING) + MAP_STATE(ENABLED) + MAP_STATE(SEARCHING) + MAP_STATE(REGISTERED) + MAP_STATE(DISCONNECTING) + MAP_STATE(CONNECTING) + MAP_STATE(CONNECTED) + } + return NM_MODEM_STATE_UNKNOWN; +} + +static void +modem_state_changed(MMModem * modem, + MMModemState old_state, + MMModemState new_state, + MMModemStateChangeReason reason, + NMModemBroadband * self) +{ + /* After the SIM is unlocked MM1 will move the device to INITIALIZING which + * is an unavailable state. That makes state handling confusing here, so + * suppress this state change and let the modem move from LOCKED to DISABLED. + */ + if (new_state == MM_MODEM_STATE_INITIALIZING && old_state == MM_MODEM_STATE_LOCKED) + return; + + nm_modem_set_state(NM_MODEM(self), + mm_state_to_nm(new_state), + mm_modem_state_change_reason_get_string(reason)); + + if (self->_priv.ctx && self->_priv.ctx->step == CONNECT_STEP_WAIT_FOR_READY) + connect_context_step(self); +} + +/*****************************************************************************/ + +static NMModemIPType +mm_ip_family_to_nm(MMBearerIpFamily family) +{ + NMModemIPType nm_type = NM_MODEM_IP_TYPE_UNKNOWN; + + if (family & MM_BEARER_IP_FAMILY_IPV4) + nm_type |= NM_MODEM_IP_TYPE_IPV4; + if (family & MM_BEARER_IP_FAMILY_IPV6) + nm_type |= NM_MODEM_IP_TYPE_IPV6; + if (family & MM_BEARER_IP_FAMILY_IPV4V6) + nm_type |= MM_BEARER_IP_FAMILY_IPV4V6; + + return nm_type; +} + +static void +get_sim_ready(MMModem *modem, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + MMSim * new_sim; + + new_sim = mm_modem_get_sim_finish(modem, res, &error); + if (new_sim != self->_priv.sim_iface) { + g_clear_object(&self->_priv.sim_iface); + self->_priv.sim_iface = new_sim; + } else + g_clear_object(&new_sim); + + if (self->_priv.sim_iface) { + g_object_set(G_OBJECT(self), + NM_MODEM_SIM_ID, + mm_sim_get_identifier(self->_priv.sim_iface), + NM_MODEM_SIM_OPERATOR_ID, + mm_sim_get_operator_identifier(self->_priv.sim_iface), + NULL); + + /* If we're waiting for the SIM during a connect, proceed with the connect */ + if (self->_priv.ctx && self->_priv.ctx->step == CONNECT_STEP_WAIT_FOR_SIM) + connect_context_step(self); + } else { + _NMLOG(g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND) ? LOGL_INFO + : LOGL_WARN, + "failed to retrieve SIM object: %s", + NM_G_ERROR_MSG(error)); + } + g_clear_error(&error); + g_object_unref(self); +} + +static void +sim_changed(MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem == self->_priv.modem_iface); + + if (mm_modem_get_sim_path(self->_priv.modem_iface)) { + mm_modem_get_sim(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) get_sim_ready, + g_object_ref(self)); + } else + g_object_set(G_OBJECT(self), NM_MODEM_SIM_ID, NULL, NM_MODEM_SIM_OPERATOR_ID, NULL, NULL); +} + +static void +supported_ip_families_changed(MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem == self->_priv.modem_iface); + + g_object_set(G_OBJECT(self), + NM_MODEM_IP_TYPES, + mm_ip_family_to_nm(mm_modem_get_supported_ip_families(modem)), + NULL); +} + +static void +operator_code_changed(MMModem3gpp *modem_3gpp, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem_3gpp == self->_priv.modem_3gpp_iface); + _nm_modem_set_operator_code(NM_MODEM(self), mm_modem_3gpp_get_operator_code(modem_3gpp)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object(value, self->_priv.modem_object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + self->_priv.modem_object = g_value_dup_object(value); + self->_priv.modem_iface = mm_object_get_modem(self->_priv.modem_object); + g_return_if_fail(self->_priv.modem_iface); + self->_priv.modem_3gpp_iface = mm_object_get_modem_3gpp(self->_priv.modem_object); + + g_signal_connect(self->_priv.modem_iface, + "state-changed", + G_CALLBACK(modem_state_changed), + self); + g_signal_connect(self->_priv.modem_iface, "notify::sim", G_CALLBACK(sim_changed), self); + sim_changed(self->_priv.modem_iface, NULL, self); + g_signal_connect(self->_priv.modem_iface, + "notify::supported-ip-families", + G_CALLBACK(supported_ip_families_changed), + self); + + if (self->_priv.modem_3gpp_iface) { + g_signal_connect(self->_priv.modem_3gpp_iface, + "notify::operator-code", + G_CALLBACK(operator_code_changed), + self); + } + + /* Note: don't grab the Simple iface here; the Modem interface is the + * only one assumed to be always valid and available */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_broadband_init(NMModemBroadband *self) +{} + +NMModem * +nm_modem_broadband_new(GObject *object, GError **error) +{ + MMObject * modem_object; + MMModem * modem_iface; + MMModem3gpp * modem_3gpp_iface; + const char *const *drivers; + const char * operator_code = NULL; + gs_free char * driver = NULL; + + g_return_val_if_fail(MM_IS_OBJECT(object), NULL); + modem_object = MM_OBJECT(object); + + /* Ensure we have the 'Modem' interface and the primary port at least */ + modem_iface = mm_object_peek_modem(modem_object); + g_return_val_if_fail(modem_iface, NULL); + g_return_val_if_fail(mm_modem_get_primary_port(modem_iface), NULL); + + /* Build a single string with all drivers listed */ + drivers = mm_modem_get_drivers(modem_iface); + if (drivers) + driver = g_strjoinv(", ", (char **) drivers); + + modem_3gpp_iface = mm_object_peek_modem_3gpp(modem_object); + if (modem_3gpp_iface) + operator_code = mm_modem_3gpp_get_operator_code(modem_3gpp_iface); + + return g_object_new(NM_TYPE_MODEM_BROADBAND, + NM_MODEM_PATH, + mm_object_get_path(modem_object), + NM_MODEM_UID, + mm_modem_get_primary_port(modem_iface), + NM_MODEM_CONTROL_PORT, + mm_modem_get_primary_port(modem_iface), + NM_MODEM_IP_TYPES, + mm_ip_family_to_nm(mm_modem_get_supported_ip_families(modem_iface)), + NM_MODEM_STATE, + (int) mm_state_to_nm(mm_modem_get_state(modem_iface)), + NM_MODEM_DEVICE_ID, + mm_modem_get_device_identifier(modem_iface), + NM_MODEM_BROADBAND_MODEM, + modem_object, + NM_MODEM_DRIVER, + driver, + NM_MODEM_OPERATOR_CODE, + operator_code, + NULL); +} + +static void +dispose(GObject *object) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(object); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + nm_clear_g_source(&priv->idle_id_ip4); + nm_clear_g_source(&priv->idle_id_ip6); + + connect_context_clear(self); + g_clear_object(&self->_priv.ipv4_config); + g_clear_object(&self->_priv.ipv6_config); + g_clear_object(&self->_priv.bearer); + + if (self->_priv.modem_iface) { + g_signal_handlers_disconnect_by_data(self->_priv.modem_iface, self); + g_clear_object(&self->_priv.modem_iface); + } + + if (self->_priv.modem_3gpp_iface) { + g_signal_handlers_disconnect_by_data(self->_priv.modem_3gpp_iface, self); + g_clear_object(&self->_priv.modem_3gpp_iface); + } + + g_clear_object(&self->_priv.simple_iface); + g_clear_object(&self->_priv.sim_iface); + g_clear_object(&self->_priv.modem_object); + + G_OBJECT_CLASS(nm_modem_broadband_parent_class)->dispose(object); +} + +static void +nm_modem_broadband_class_init(NMModemBroadbandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMModemClass *modem_class = NM_MODEM_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + modem_class->get_capabilities = get_capabilities; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; + modem_class->stage3_ip6_config_request = stage3_ip6_config_request; + modem_class->disconnect = disconnect; + modem_class->deactivate_cleanup = deactivate_cleanup; + modem_class->set_mm_enabled = set_mm_enabled; + modem_class->get_user_pass = get_user_pass; + modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem; + modem_class->complete_connection = complete_connection; + modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare; + modem_class->owns_port = owns_port; + + obj_properties[PROP_MODEM] = + g_param_spec_object(NM_MODEM_BROADBAND_MODEM, + "", + "", + MM_GDBUS_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wwan/nm-modem-broadband.h b/src/core/devices/wwan/nm-modem-broadband.h new file mode 100644 index 0000000..627fe25 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-broadband.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - Aleksander Morgado + */ + +#ifndef __NETWORKMANAGER_MODEM_BROADBAND_H__ +#define __NETWORKMANAGER_MODEM_BROADBAND_H__ + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_BROADBAND (nm_modem_broadband_get_type()) +#define NM_MODEM_BROADBAND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadband)) +#define NM_MODEM_BROADBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) +#define NM_IS_MODEM_BROADBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_BROADBAND)) +#define NM_IS_MODEM_BROADBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_BROADBAND)) +#define NM_MODEM_BROADBAND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) + +typedef struct _NMModemBroadband NMModemBroadband; +typedef struct _NMModemBroadbandClass NMModemBroadbandClass; + +GType nm_modem_broadband_get_type(void); + +NMModem *nm_modem_broadband_new(GObject *object, GError **error); + +#endif /* __NETWORKMANAGER_MODEM_BROADBAND_H__ */ diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c new file mode 100644 index 0000000..d05c77a --- /dev/null +++ b/src/core/devices/wwan/nm-modem-manager.c @@ -0,0 +1,876 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Canonical Ltd. + */ + +#include "nm-default.h" + +#include "nm-modem-manager.h" + +#include + +#if HAVE_LIBSYSTEMD + #include +#else + #define sd_booted() FALSE +#endif + +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-modem.h" +#include "nm-modem-broadband.h" + +#if WITH_OFONO + #include "nm-modem-ofono.h" +#endif + +#define MODEM_POKE_INTERVAL 120 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMModemManager, PROP_NAME_OWNER, ); + +enum { + MODEM_ADDED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GDBusConnection *dbus_connection; + + /* used during g_bus_get() and later during mm_manager_new(). */ + GCancellable *main_cancellable; + + struct { + MMManager * manager; + GCancellable *poke_cancellable; + gulong handle_name_owner_changed_id; + gulong handle_object_added_id; + gulong handle_object_removed_id; + guint relaunch_id; + + /* this only has one use: that the logging line about + * ModemManager available distinguishes between first-time + * and later name-owner-changed. */ + enum { + LOG_AVAILABLE_NOT_INITIALIZED = 0, + LOG_AVAILABLE_YES, + LOG_AVAILABLE_NO, + } log_available : 3; + + GDBusProxy * proxy; + GCancellable *proxy_cancellable; + guint proxy_ref_count; + char * proxy_name_owner; + } modm; + +#if WITH_OFONO + struct { + GDBusProxy * proxy; + GCancellable *cancellable; + } ofono; +#endif + + GHashTable *modems; +} NMModemManagerPrivate; + +struct _NMModemManager { + GObject parent; + NMModemManagerPrivate _priv; +}; + +struct _NMModemManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMModemManager, nm_modem_manager, G_TYPE_OBJECT) + +#define NM_MODEM_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMModemManager, NM_IS_MODEM_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "modem-manager", __VA_ARGS__) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMModemManager, nm_modem_manager_get, NM_TYPE_MODEM_MANAGER); + +/*****************************************************************************/ + +static void modm_schedule_manager_relaunch(NMModemManager *self, guint n_seconds); +static void modm_ensure_manager(NMModemManager *self); + +/*****************************************************************************/ + +static void +handle_new_modem(NMModemManager *self, NMModem *modem) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + const char * path; + + path = nm_modem_get_path(modem); + if (g_hash_table_lookup(priv->modems, path)) { + g_warn_if_reached(); + return; + } + + /* Track the new modem */ + g_hash_table_insert(priv->modems, g_strdup(path), modem); + g_signal_emit(self, signals[MODEM_ADDED], 0, modem); +} + +static gboolean +remove_one_modem(gpointer key, gpointer value, gpointer user_data) +{ + nm_modem_emit_removed(NM_MODEM(value)); + return TRUE; +} + +/*****************************************************************************/ + +NMModem ** +nm_modem_manager_get_modems(NMModemManager *self, guint *out_len) +{ + g_return_val_if_fail(NM_IS_MODEM_MANAGER(self), NULL); + + return (NMModem **) nm_utils_hash_values_to_array(NM_MODEM_MANAGER_GET_PRIVATE(self)->modems, + NULL, + NULL, + out_len); +} + +/*****************************************************************************/ + +static void +modm_clear_manager(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (!priv->modm.manager) + return; + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_name_owner_changed_id); + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_object_added_id); + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_object_removed_id); + g_clear_object(&priv->modm.manager); +} + +static void +modm_handle_object_added(MMManager *modem_manager, MMObject *modem_object, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + const char * path; + MMModem * modem_iface; + NMModem * modem; + GError * error = NULL; + + /* Ensure we don't have the same modem already */ + path = mm_object_get_path(modem_object); + if (g_hash_table_lookup(priv->modems, path)) { + _LOGW("modem with path %s already exists, ignoring", path); + return; + } + + /* Ensure we have the 'Modem' interface at least */ + modem_iface = mm_object_peek_modem(modem_object); + if (!modem_iface) { + _LOGW("modem with path %s doesn't have the Modem interface, ignoring", path); + return; + } + + /* Ensure we have a primary port reported */ + if (!mm_modem_get_primary_port(modem_iface)) { + _LOGW("modem with path %s has unknown primary port, ignoring", path); + return; + } + + /* Create a new modem object */ + modem = nm_modem_broadband_new(G_OBJECT(modem_object), &error); + if (modem) + handle_new_modem(self, modem); + else + _LOGW("failed to create modem: %s", error->message); + g_clear_error(&error); +} + +static void +modm_handle_object_removed(MMManager *manager, MMObject *modem_object, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + NMModem * modem; + const char * path; + + path = mm_object_get_path(modem_object); + modem = (NMModem *) g_hash_table_lookup(priv->modems, path); + if (!modem) + return; + + nm_modem_emit_removed(modem); + g_hash_table_remove(priv->modems, path); +} + +static void +modm_manager_available(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + GList * modems, *l; + + if (priv->modm.log_available != LOG_AVAILABLE_YES) { + _LOGI("ModemManager %savailable", priv->modm.log_available ? "now " : ""); + priv->modm.log_available = LOG_AVAILABLE_YES; + } + + /* Update initial modems list */ + modems = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(priv->modm.manager)); + for (l = modems; l; l = g_list_next(l)) + modm_handle_object_added(priv->modm.manager, MM_OBJECT(l->data), self); + g_list_free_full(modems, (GDestroyNotify) g_object_unref); +} + +static void +modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * name_owner; + + /* Quit poking, if any */ + nm_clear_g_source(&priv->modm.relaunch_id); + + name_owner = + g_dbus_object_manager_client_get_name_owner(G_DBUS_OBJECT_MANAGER_CLIENT(modem_manager)); + if (!name_owner) { + if (priv->modm.log_available != LOG_AVAILABLE_NO) { + _LOGI("ModemManager %savailable", priv->modm.log_available ? "no longer " : "not "); + priv->modm.log_available = LOG_AVAILABLE_NO; + } + + /* If not managed by systemd, schedule relaunch */ + if (!sd_booted()) + modm_schedule_manager_relaunch(self, 0); + + return; + } + + /* Available! */ + g_free(name_owner); + + /* Hack alert: GDBusObjectManagerClient won't signal neither 'object-added' + * nor 'object-removed' if it was created while there was no ModemManager in + * the bus. This hack avoids this issue until we get a GIO with the fix + * included... */ + modm_clear_manager(self); + modm_ensure_manager(self); + + /* Whenever GDBusObjectManagerClient is fixed, we can just do the following: + * modm_manager_available (self); + */ +} + +static void +modm_manager_poke_cb(GObject *connection, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *result = NULL; + + result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection), res, &error); + + if (!result && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->modm.poke_cancellable); + + if (error) { + _LOGW("error poking ModemManager: %s", error->message); + + /* Don't reschedule poke is MM service doesn't exist. */ + if (!g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) + && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) { + /* Setup timeout to relaunch */ + modm_schedule_manager_relaunch(self, MODEM_POKE_INTERVAL); + } + } +} + +static void +modm_manager_poke(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->modm.poke_cancellable); + priv->modm.poke_cancellable = g_cancellable_new(); + + /* If there is no current owner right away, ensure we poke to get one */ + g_dbus_connection_call(priv->dbus_connection, + NM_MODEM_MANAGER_MM_DBUS_SERVICE, + NM_MODEM_MANAGER_MM_DBUS_PATH, + DBUS_INTERFACE_PEER, + "Ping", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->modm.poke_cancellable, + modm_manager_poke_cb, + self); +} + +static void +modm_manager_check_name_owner(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + gs_free char * name_owner = NULL; + + name_owner = g_dbus_object_manager_client_get_name_owner( + G_DBUS_OBJECT_MANAGER_CLIENT(priv->modm.manager)); + if (name_owner) { + modm_manager_available(self); + return; + } + + /* If the lifecycle is not managed by systemd, poke */ + if (!sd_booted()) + modm_manager_poke(self); +} + +static void +modm_manager_new_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + MMManager * modem_manager; + + modem_manager = mm_manager_new_finish(res, &error); + if (!modem_manager && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_assert(!priv->modm.manager); + + g_clear_object(&priv->main_cancellable); + + if (!modem_manager) { + /* We're not really supposed to get any error here. If we do get one, + * though, just re-schedule the MMManager creation after some time. + * During this period, name-owner changes won't be followed. */ + _LOGW("error creating ModemManager client: %s", error->message); + /* Setup timeout to relaunch */ + modm_schedule_manager_relaunch(self, MODEM_POKE_INTERVAL); + return; + } + + priv->modm.manager = modem_manager; + + /* Setup signals in the GDBusObjectManagerClient */ + priv->modm.handle_name_owner_changed_id = + g_signal_connect(priv->modm.manager, + "notify::name-owner", + G_CALLBACK(modm_handle_name_owner_changed), + self); + priv->modm.handle_object_added_id = g_signal_connect(priv->modm.manager, + "object-added", + G_CALLBACK(modm_handle_object_added), + self); + priv->modm.handle_object_removed_id = g_signal_connect(priv->modm.manager, + "object-removed", + G_CALLBACK(modm_handle_object_removed), + self); + + modm_manager_check_name_owner(self); +} + +static void +modm_ensure_manager(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_assert(priv->dbus_connection); + + /* Create the GDBusObjectManagerClient. We do not request to autostart, as + * we don't really want the MMManager creation to fail. We can always poke + * later on if we want to request the autostart */ + if (!priv->modm.manager) { + if (!priv->main_cancellable) + priv->main_cancellable = g_cancellable_new(); + mm_manager_new(priv->dbus_connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + priv->main_cancellable, + modm_manager_new_cb, + self); + return; + } + + /* If already available, recheck name owner! */ + modm_manager_check_name_owner(self); +} + +static gboolean +modm_schedule_manager_relaunch_cb(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + priv->modm.relaunch_id = 0; + modm_ensure_manager(self); + return G_SOURCE_REMOVE; +} + +static void +modm_schedule_manager_relaunch(NMModemManager *self, guint n_seconds) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + /* No need to pass an extra reference to self; timeout/idle will be + * cancelled if the object gets disposed. */ + if (n_seconds) + priv->modm.relaunch_id = + g_timeout_add_seconds(n_seconds, (GSourceFunc) modm_schedule_manager_relaunch_cb, self); + else + priv->modm.relaunch_id = g_idle_add((GSourceFunc) modm_schedule_manager_relaunch_cb, self); +} + +/*****************************************************************************/ + +static void +modm_proxy_name_owner_reset(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * name = NULL; + + if (priv->modm.proxy) + name = g_dbus_proxy_get_name_owner(priv->modm.proxy); + + if (nm_streq0(priv->modm.proxy_name_owner, name)) { + g_free(name); + return; + } + g_free(priv->modm.proxy_name_owner); + priv->modm.proxy_name_owner = name; + + _notify(self, PROP_NAME_OWNER); +} + +static void +modm_proxy_name_owner_changed_cb(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + modm_proxy_name_owner_reset(user_data); +} + +static void +modm_proxy_new_cb(GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + GDBusProxy * proxy; + gs_free_error GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->modm.proxy_cancellable); + + if (!proxy) { + _LOGW("could not obtain D-Bus proxy for ModemManager: %s", error->message); + return; + } + + priv->modm.proxy = proxy; + g_signal_connect(priv->modm.proxy, + "notify::g-name-owner", + G_CALLBACK(modm_proxy_name_owner_changed_cb), + self); + + modm_proxy_name_owner_reset(self); +} + +void +nm_modem_manager_name_owner_ref(NMModemManager *self) +{ + NMModemManagerPrivate *priv; + + g_return_if_fail(NM_IS_MODEM_MANAGER(self)); + + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (priv->modm.proxy_ref_count++ > 0) { + /* only try once to create the proxy. If proxy creation + * for the first "ref" failed, it's unclear what to do. + * The proxy is hosed. */ + return; + } + + nm_assert(!priv->modm.proxy && !priv->modm.proxy_cancellable); + + priv->modm.proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + NM_MODEM_MANAGER_MM_DBUS_SERVICE, + NM_MODEM_MANAGER_MM_DBUS_PATH, + NM_MODEM_MANAGER_MM_DBUS_INTERFACE, + priv->modm.proxy_cancellable, + modm_proxy_new_cb, + self); +} + +void +nm_modem_manager_name_owner_unref(NMModemManager *self) +{ + NMModemManagerPrivate *priv; + + g_return_if_fail(NM_IS_MODEM_MANAGER(self)); + + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_return_if_fail(priv->modm.proxy_ref_count > 0); + + if (--priv->modm.proxy_ref_count > 0) + return; + + nm_clear_g_cancellable(&priv->modm.proxy_cancellable); + g_clear_object(&priv->modm.proxy); + + modm_proxy_name_owner_reset(self); +} + +const char * +nm_modem_manager_name_owner_get(NMModemManager *self) +{ + g_return_val_if_fail(NM_IS_MODEM_MANAGER(self), NULL); + nm_assert(NM_MODEM_MANAGER_GET_PRIVATE(self)->modm.proxy_ref_count > 0); + + return NM_MODEM_MANAGER_GET_PRIVATE(self)->modm.proxy_name_owner; +} + +/*****************************************************************************/ + +#if WITH_OFONO + +static void +ofono_create_modem(NMModemManager *self, const char *path) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + NMModem * modem = NULL; + + /* Ensure duplicate modems aren't created. Because we're not using the + * ObjectManager interface there's a race during oFono startup where we + * receive ModemAdded signals before GetModems() returns, so some of the + * modems returned from GetModems() may already have been created. + */ + if (!g_hash_table_lookup(priv->modems, path)) { + modem = nm_modem_ofono_new(path); + if (modem) + handle_new_modem(self, modem); + else + _LOGW("Failed to create oFono modem for %s", path); + } +} + +static void +ofono_signal_cb(GDBusProxy *proxy, + char * sender_name, + char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMModemManager * self = NM_MODEM_MANAGER(user_data); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * object_path; + NMModem * modem; + + if (g_strcmp0(signal_name, "ModemAdded") == 0) { + g_variant_get(parameters, "(oa{sv})", &object_path, NULL); + _LOGI("oFono modem appeared: %s", object_path); + + ofono_create_modem(NM_MODEM_MANAGER(user_data), object_path); + g_free(object_path); + } else if (g_strcmp0(signal_name, "ModemRemoved") == 0) { + g_variant_get(parameters, "(o)", &object_path); + _LOGI("oFono modem removed: %s", object_path); + + modem = (NMModem *) g_hash_table_lookup(priv->modems, object_path); + if (modem) { + nm_modem_emit_removed(modem); + g_hash_table_remove(priv->modems, object_path); + } else { + _LOGW("could not remove modem %s, not found in table", object_path); + } + g_free(object_path); + } +} + +static void +ofono_enumerate_devices_done(GObject *proxy, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GVariant * results; + GVariantIter * iter; + const char * path; + + results = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error); + if (!results && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->ofono.cancellable); + + if (!results) { + _LOGW("failed to enumerate oFono devices: %s", error->message); + return; + } + + g_variant_get(results, "(a(oa{sv}))", &iter); + while (g_variant_iter_loop(iter, "(&oa{sv})", &path, NULL)) + ofono_create_modem(self, path); + g_variant_iter_free(iter); + g_variant_unref(results); +} + +static void +ofono_check_name_owner(NMModemManager *self, gboolean first_invocation) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + gs_free char * name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner(G_DBUS_PROXY(priv->ofono.proxy)); + if (name_owner) { + _LOGI("oFono is %savailable", first_invocation ? "" : "now "); + + nm_clear_g_cancellable(&priv->ofono.cancellable); + priv->ofono.cancellable = g_cancellable_new(); + + g_dbus_proxy_call(priv->ofono.proxy, + "GetModems", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->ofono.cancellable, + ofono_enumerate_devices_done, + self); + } else { + GHashTableIter iter; + NMModem * modem; + + _LOGI("oFono is %savailable", first_invocation ? "not " : "no longer "); + + /* Remove any oFono modems that might be left around */ + g_hash_table_iter_init(&iter, priv->modems); + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &modem)) { + if (NM_IS_MODEM_OFONO(modem)) { + nm_modem_emit_removed(modem); + g_hash_table_iter_remove(&iter); + } + } + } +} + +static void +ofono_name_owner_changed(GDBusProxy *ofono_proxy, GParamSpec *pspec, NMModemManager *self) +{ + ofono_check_name_owner(self, FALSE); +} + +static void +ofono_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_finish(res, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->ofono.cancellable); + + if (!proxy) { + _LOGW("error getting oFono bus proxy: %s", error->message); + return; + } + + priv->ofono.proxy = proxy; + + g_signal_connect(priv->ofono.proxy, + "notify::g-name-owner", + G_CALLBACK(ofono_name_owner_changed), + self); + + g_signal_connect(priv->ofono.proxy, "g-signal", G_CALLBACK(ofono_signal_cb), self); + + ofono_check_name_owner(self, TRUE); +} + +static void +ofono_init_proxy(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_assert(priv->dbus_connection); + nm_assert(!priv->ofono.cancellable); + + priv->ofono.cancellable = g_cancellable_new(); + + g_dbus_proxy_new(priv->dbus_connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + OFONO_DBUS_PATH, + OFONO_DBUS_INTERFACE, + priv->ofono.cancellable, + ofono_proxy_new_cb, + self); +} +#endif + +/*****************************************************************************/ + +static void +bus_get_ready(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GDBusConnection * connection; + + connection = g_bus_get_finish(res, &error); + if (!connection && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (!connection) { + _LOGW("error getting bus connection: %s", error->message); + return; + } + + priv->dbus_connection = connection; + + modm_ensure_manager(self); +#if WITH_OFONO + ofono_init_proxy(self); +#endif +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModemManager * self = NM_MODEM_MANAGER(object); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_NAME_OWNER: + g_value_set_string(value, priv->modm.proxy_name_owner); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_manager_init(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + priv->modems = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref); + + priv->main_cancellable = g_cancellable_new(); + + g_bus_get(G_BUS_TYPE_SYSTEM, priv->main_cancellable, bus_get_ready, self); +} + +static void +dispose(GObject *object) +{ + NMModemManager * self = NM_MODEM_MANAGER(object); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->main_cancellable); + nm_clear_g_cancellable(&priv->modm.poke_cancellable); + + nm_clear_g_source(&priv->modm.relaunch_id); + + nm_clear_g_cancellable(&priv->modm.proxy_cancellable); + g_clear_object(&priv->modm.proxy); + nm_clear_g_free(&priv->modm.proxy_name_owner); + + modm_clear_manager(self); + +#if WITH_OFONO + if (priv->ofono.proxy) { + g_signal_handlers_disconnect_by_func(priv->ofono.proxy, ofono_name_owner_changed, self); + g_signal_handlers_disconnect_by_func(priv->ofono.proxy, ofono_signal_cb, self); + g_clear_object(&priv->ofono.proxy); + } + nm_clear_g_cancellable(&priv->ofono.cancellable); +#endif + + g_clear_object(&priv->dbus_connection); + + if (priv->modems) { + g_hash_table_foreach_remove(priv->modems, remove_one_modem, object); + g_hash_table_destroy(priv->modems); + priv->modems = NULL; + } + + G_OBJECT_CLASS(nm_modem_manager_parent_class)->dispose(object); +} + +static void +nm_modem_manager_class_init(NMModemManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + + obj_properties[PROP_NAME_OWNER] = + g_param_spec_string(NM_MODEM_MANAGER_NAME_OWNER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[MODEM_ADDED] = g_signal_new(NM_MODEM_MANAGER_MODEM_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_MODEM); +} diff --git a/src/core/devices/wwan/nm-modem-manager.h b/src/core/devices/wwan/nm-modem-manager.h new file mode 100644 index 0000000..e89a032 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-manager.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 Canonical Ltd. + */ + +#ifndef __NETWORKMANAGER_MODEM_MANAGER_H__ +#define __NETWORKMANAGER_MODEM_MANAGER_H__ + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_MANAGER (nm_modem_manager_get_type()) +#define NM_MODEM_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_MANAGER, NMModemManager)) +#define NM_MODEM_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_MANAGER, NMModemManagerClass)) +#define NM_IS_MODEM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_MANAGER)) +#define NM_IS_MODEM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_MANAGER)) +#define NM_MODEM_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_MANAGER, NMModemManagerClass)) + +#define NM_MODEM_MANAGER_MODEM_ADDED "modem-added" + +#define NM_MODEM_MANAGER_NAME_OWNER "name-owner" + +#define NM_MODEM_MANAGER_MM_DBUS_SERVICE "org.freedesktop.ModemManager1" +#define NM_MODEM_MANAGER_MM_DBUS_PATH "/org/freedesktop/ModemManager1" +#define NM_MODEM_MANAGER_MM_DBUS_INTERFACE "org.freedesktop.ModemManager1" + +typedef struct _NMModemManager NMModemManager; +typedef struct _NMModemManagerClass NMModemManagerClass; + +GType nm_modem_manager_get_type(void); + +NMModemManager *nm_modem_manager_get(void); + +void nm_modem_manager_name_owner_ref(NMModemManager *self); +void nm_modem_manager_name_owner_unref(NMModemManager *self); + +const char *nm_modem_manager_name_owner_get(NMModemManager *self); + +NMModem **nm_modem_manager_get_modems(NMModemManager *self, guint *out_len); + +#endif /* __NETWORKMANAGER_MODEM_MANAGER_H__ */ diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c new file mode 100644 index 0000000..ec43cd8 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-ofono.c @@ -0,0 +1,1239 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2016 Canonical Ltd. + */ + +#include "nm-default.h" + +#include "nm-modem-ofono.h" + +#include "nm-core-internal.h" +#include "devices/nm-device-private.h" +#include "nm-modem.h" +#include "platform/nm-platform.h" +#include "nm-ip4-config.h" + +#define VARIANT_IS_OF_TYPE_BOOLEAN(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_BOOLEAN))) +#define VARIANT_IS_OF_TYPE_STRING(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING))) +#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_OBJECT_PATH))) +#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING_ARRAY))) +#define VARIANT_IS_OF_TYPE_DICTIONARY(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_DICTIONARY))) + +/*****************************************************************************/ + +typedef struct { + GHashTable *connect_properties; + + GDBusProxy *modem_proxy; + GDBusProxy *connman_proxy; + GDBusProxy *context_proxy; + GDBusProxy *sim_proxy; + + GCancellable *modem_proxy_cancellable; + GCancellable *connman_proxy_cancellable; + GCancellable *context_proxy_cancellable; + GCancellable *sim_proxy_cancellable; + + GError *property_error; + + char *context_path; + char *imsi; + + gboolean modem_online; + gboolean gprs_attached; + + NMIP4Config *ip4_config; +} NMModemOfonoPrivate; + +struct _NMModemOfono { + NMModem parent; + NMModemOfonoPrivate _priv; +}; + +struct _NMModemOfonoClass { + NMModemClass parent; +}; + +G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM) + +#define NM_MODEM_OFONO_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMModemOfono, NM_IS_MODEM_OFONO) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG_PREFIX_NAME "modem-ofono" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \ + NMModemOfono *const __self = (self); \ + char __prefix_name[128]; \ + const char * __uid; \ + \ + _nm_log(_level, \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (__self ? ({ \ + ((__uid = nm_modem_get_uid((NMModem *) __self)) \ + ? nm_sprintf_buf(__prefix_name, "[%s]", __uid) \ + : "(null)"); \ + }) \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +get_capabilities(NMModem * _self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + /* FIXME: auto-detect capabilities to allow LTE */ + *modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + *current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; +} + +static void +update_modem_state(NMModemOfono *self) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + NMModemState state = nm_modem_get_state(NM_MODEM(self)); + NMModemState new_state = NM_MODEM_STATE_DISABLED; + const char * reason = NULL; + + _LOGI("'Attached': %s 'Online': %s 'IMSI': %s", + priv->gprs_attached ? "true" : "false", + priv->modem_online ? "true" : "false", + priv->imsi); + + if (priv->modem_online == FALSE) { + reason = "modem 'Online=false'"; + } else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) { + reason = "modem not ready"; + } else if (priv->gprs_attached == FALSE) { + new_state = NM_MODEM_STATE_SEARCHING; + reason = "modem searching"; + } else { + new_state = NM_MODEM_STATE_REGISTERED; + reason = "modem ready"; + } + + if (state != new_state) + nm_modem_set_state(NM_MODEM(self), new_state, reason); +} + +/* Disconnect */ +typedef struct { + NMModemOfono * self; + _NMModemDisconnectCallback callback; + gpointer callback_user_data; + GCancellable * cancellable; + gboolean warn; +} DisconnectContext; + +static void +disconnect_context_complete(DisconnectContext *ctx, GError *error) +{ + if (ctx->callback) + ctx->callback(NM_MODEM(ctx->self), error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->self); + g_slice_free(DisconnectContext, ctx); +} + +static void +disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable) +{ + DisconnectContext *ctx = user_data; + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + ("modem is currently not connected")); + } + disconnect_context_complete(ctx, error); +} + +static void +disconnect_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + DisconnectContext *ctx = user_data; + NMModemOfono * self = ctx->self; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v = NULL; + + v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + disconnect_context_complete(ctx, error); + return; + } + + if (error && ctx->warn) + _LOGW("failed to disconnect modem: %s", error->message); + + _LOGD("modem disconnected"); + + update_modem_state(self); + disconnect_context_complete(ctx, error); +} + +static void +disconnect(NMModem * modem, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + DisconnectContext * ctx; + NMModemState state = nm_modem_get_state(NM_MODEM(self)); + + _LOGD("warn: %s modem_state: %s", warn ? "TRUE" : "FALSE", nm_modem_state_to_string(state)); + + ctx = g_slice_new0(DisconnectContext); + ctx->self = g_object_ref(self); + ctx->cancellable = nm_g_object_ref(cancellable); + ctx->warn = warn; + ctx->callback = callback; + ctx->callback_user_data = user_data; + + if (state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled(cancellable)) { + nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx); + return; + } + + nm_modem_set_state(NM_MODEM(self), + NM_MODEM_STATE_DISCONNECTING, + nm_modem_state_to_string(NM_MODEM_STATE_DISCONNECTING)); + + g_dbus_proxy_call(priv->context_proxy, + "SetProperty", + g_variant_new("(sv)", "Active", g_variant_new("b", warn)), + G_DBUS_CALL_FLAGS_NONE, + 20000, + ctx->cancellable, + disconnect_done, + ctx); +} + +static void +deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + /* TODO: cancel SimpleConnect() if any */ + + g_clear_object(&priv->ip4_config); + + NM_MODEM_CLASS(nm_modem_ofono_parent_class) + ->deactivate_cleanup(modem, device, stop_ppp_manager); +} + +static gboolean +check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection, GError **error) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + const char * id; + + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection type %s is not supported by ofono modem", + nm_connection_get_connection_type(connection)); + return FALSE; + } + + if (!priv->imsi) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem has no IMSI"); + return FALSE; + } + + id = nm_connection_get_id(connection); + + if (!strstr(id, "/context")) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the connection ID has no context"); + return FALSE; + } + + if (!strstr(id, priv->imsi)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the connection ID does not contain the IMSI"); + return FALSE; + } + + return TRUE; +} + +static void +handle_sim_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (g_strcmp0(property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING(v)) { + gsize length; + const char *value_str = g_variant_get_string(v, &length); + + _LOGD("SubscriberIdentify found"); + + /* Check for empty DBus string value */ + if (length && g_strcmp0(value_str, "(null)") != 0 + && g_strcmp0(value_str, priv->imsi) != 0) { + if (priv->imsi != NULL) { + _LOGW("SimManager:'SubscriberIdentity' changed: %s", priv->imsi); + g_free(priv->imsi); + } + + priv->imsi = g_strdup(value_str); + update_modem_state(self); + } + } +} + +static void +sim_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_sim_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->sim_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting sim properties: %s", error->message); + return; + } + + _LOGD("sim v_properties is type: %s", g_variant_get_type_string(v_properties)); + + v_dict = g_variant_get_child_value(v_properties, 0); + if (!v_dict) { + _LOGW("error getting sim properties: no v_dict"); + return; + } + + _LOGD("sim v_dict is type: %s", g_variant_get_type_string(v_dict)); + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_sim_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +_sim_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGW("failed to create SimManager proxy: %s", error->message); + g_clear_object(&priv->sim_proxy_cancellable); + return; + } + + priv->sim_proxy = proxy; + + /* Watch for custom ofono PropertyChanged signals */ + _nm_dbus_signal_connect(priv->sim_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(sim_property_changed), + self); + + g_dbus_proxy_call(priv->sim_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->sim_proxy_cancellable, + sim_get_properties_done, + self); +} + +static void +handle_sim_iface(NMModemOfono *self, gboolean found) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + _LOGD("SimManager interface %sfound", found ? "" : "not "); + + if (!found && (priv->sim_proxy || priv->sim_proxy_cancellable)) { + _LOGI("SimManager interface disappeared"); + nm_clear_g_cancellable(&priv->sim_proxy_cancellable); + if (priv->sim_proxy) { + g_signal_handlers_disconnect_by_data(priv->sim_proxy, self); + g_clear_object(&priv->sim_proxy); + } + nm_clear_g_free(&priv->imsi); + update_modem_state(self); + } else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) { + _LOGI("found new SimManager interface"); + + priv->sim_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_SIM_MANAGER, + priv->sim_proxy_cancellable, /* GCancellable */ + _sim_proxy_new_cb, + self); + } +} + +static void +handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (g_strcmp0(property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN(v)) { + gboolean attached = g_variant_get_boolean(v); + gboolean old_attached = priv->gprs_attached; + + _LOGD("Attached: %s", attached ? "True" : "False"); + + if (priv->gprs_attached != attached) { + priv->gprs_attached = attached; + + _LOGI("Attached %s -> %s", + old_attached ? "true" : "false", + attached ? "true" : "false"); + + update_modem_state(self); + } + } +} + +static void +connman_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_connman_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->connman_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting connman properties: %s", error->message); + return; + } + + v_dict = g_variant_get_child_value(v_properties, 0); + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_connman_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +_connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGW("failed to create ConnectionManager proxy: %s", error->message); + g_clear_object(&priv->connman_proxy_cancellable); + return; + } + + priv->connman_proxy = proxy; + + _nm_dbus_signal_connect(priv->connman_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(connman_property_changed), + self); + + g_dbus_proxy_call(priv->connman_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->connman_proxy_cancellable, + connman_get_properties_done, + self); +} + +static void +handle_connman_iface(NMModemOfono *self, gboolean found) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + _LOGD("ConnectionManager interface %sfound", found ? "" : "not "); + + if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) { + _LOGI("ConnectionManager interface disappeared"); + nm_clear_g_cancellable(&priv->connman_proxy_cancellable); + if (priv->connman_proxy) { + g_signal_handlers_disconnect_by_data(priv->connman_proxy, self); + g_clear_object(&priv->connman_proxy); + } + + /* The connection manager proxy disappeared, we should + * consider the modem disabled. + */ + priv->gprs_attached = FALSE; + + update_modem_state(self); + } else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) { + _LOGI("found new ConnectionManager interface"); + + priv->connman_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, + priv->connman_proxy_cancellable, + _connman_proxy_new_cb, + self); + } +} + +static void +handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if ((g_strcmp0(property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN(v)) { + gboolean online = g_variant_get_boolean(v); + + _LOGD("Online: %s", online ? "True" : "False"); + + if (online != priv->modem_online) { + priv->modem_online = online; + _LOGI("modem is now %s", online ? "Online" : "Offline"); + update_modem_state(self); + } + + } else if ((g_strcmp0(property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY(v)) { + const char **array, **iter; + gboolean found_connman = FALSE; + gboolean found_sim = FALSE; + + _LOGD("Interfaces found"); + + array = g_variant_get_strv(v, NULL); + if (array) { + for (iter = array; *iter; iter++) { + if (g_strcmp0(OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0) + found_sim = TRUE; + else if (g_strcmp0(OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0) + found_connman = TRUE; + } + g_free(array); + } + + handle_sim_iface(self, found_sim); + handle_connman_iface(self, found_connman); + } +} + +static void +modem_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_modem_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->modem_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting modem properties: %s", error->message); + return; + } + + v_dict = g_variant_get_child_value(v_properties, 0); + if (!v_dict) { + _LOGW("error getting modem properties: no v_dict"); + return; + } + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_modem_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v = NULL; + + v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->context_proxy_cancellable); + + nm_clear_pointer(&priv->connect_properties, g_hash_table_destroy); + + if (error) { + _LOGW("connection failed: %s", error->message); + + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY); + /* + * FIXME: add code to check for InProgress so that the + * connection doesn't continue to try and activate, + * leading to the connection being disabled, and a 5m + * timeout... + */ + } +} + +static void +context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + NMPlatformIP4Address addr; + gboolean ret = FALSE; + gs_unref_variant GVariant *v_dict = NULL; + const char * interface; + const char * s; + const char ** array, **iter; + guint32 address_network, gateway_network; + guint32 ip4_route_table, ip4_route_metric; + int ifindex; + GError * error = NULL; + + _LOGD("PropertyChanged: %s", property); + + /* + * TODO: might be a good idea and re-factor this to mimic bluez-device, + * ie. have this function just check the key, and call a sub-func to + * handle the action. + */ + + if (g_strcmp0(property, "Settings") != 0) + return; + + v_dict = g_variant_get_child_value(v, 0); + if (!v_dict) { + _LOGW("error getting IPv4 Settings: no v_dict"); + goto out; + } + + _LOGI("IPv4 static Settings:"); + + if (!g_variant_lookup(v_dict, "Interface", "&s", &interface)) { + _LOGW("Settings 'Interface' missing"); + goto out; + } + + _LOGD("Interface: %s", interface); + if (!nm_modem_set_data_port(NM_MODEM(self), + NM_PLATFORM_GET, + interface, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_UNKNOWN, + 0, + &error)) { + _LOGW("failed to connect to modem: %s", error->message); + g_clear_error(&error); + goto out; + } + + ifindex = nm_modem_get_ip_ifindex(NM_MODEM(self)); + nm_assert(ifindex > 0); + + /* TODO: verify handling of ip4_config; check other places it's used... */ + g_clear_object(&priv->ip4_config); + + priv->ip4_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), ifindex); + + if (!g_variant_lookup(v_dict, "Address", "&s", &s)) { + _LOGW("Settings 'Address' missing"); + goto out; + } + if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + _LOGW("can't convert 'Address' %s to addr", s ?: ""); + goto out; + } + memset(&addr, 0, sizeof(addr)); + addr.ifindex = ifindex; + addr.address = address_network; + addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN; + + if (!g_variant_lookup(v_dict, "Netmask", "&s", &s)) { + _LOGW("Settings 'Netmask' missing"); + goto out; + } + if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + _LOGW("invalid 'Netmask': %s", s ?: ""); + goto out; + } + addr.plen = nm_utils_ip4_netmask_to_prefix(address_network); + + _LOGI("Address: %s", nm_platform_ip4_address_to_string(&addr, NULL, 0)); + nm_ip4_config_add_address(priv->ip4_config, &addr); + + if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) { + _LOGW("Settings 'Gateway' missing"); + goto out; + } + if (!nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &gateway_network)) { + _LOGW("invalid 'Gateway': %s", s); + goto out; + } + nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL); + { + const NMPlatformIP4Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = gateway_network, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + + _LOGI("Gateway: %s", s); + nm_ip4_config_add_route(priv->ip4_config, &r, NULL); + } + + if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) { + _LOGW("Settings 'DomainNameServers' missing"); + goto out; + } + if (array) { + for (iter = array; *iter; iter++) { + if (nm_utils_parse_inaddr_bin(AF_INET, *iter, NULL, &address_network) + && address_network) { + _LOGI("DNS: %s", *iter); + nm_ip4_config_add_nameserver(priv->ip4_config, address_network); + } else { + _LOGW("invalid NameServer: %s", *iter); + } + } + + if (iter == array) { + _LOGW("Settings: 'DomainNameServers': none specified"); + g_free(array); + goto out; + } + g_free(array); + } + + if (g_variant_lookup(v_dict, "MessageProxy", "&s", &s)) { + _LOGI("MessageProxy: %s", s); + if (s && nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + nm_modem_get_route_parameters(NM_MODEM(self), + &ip4_route_table, + &ip4_route_metric, + NULL, + NULL); + + { + const NMPlatformIP4Route mms_route = { + .network = address_network, + .plen = 32, + .gateway = gateway_network, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + + nm_ip4_config_add_route(priv->ip4_config, &mms_route, NULL); + } + } else { + _LOGW("invalid MessageProxy: %s", s); + } + } + + ret = TRUE; + +out: + if (nm_modem_get_state(NM_MODEM(self)) != NM_MODEM_STATE_CONNECTED) { + _LOGI("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE"); + nm_modem_emit_prepare_result(NM_MODEM(self), + ret, + ret ? NM_DEVICE_STATE_REASON_NONE + : NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + _LOGW("MODEM_PPP_FAILED"); + nm_modem_emit_ppp_failed(NM_MODEM(self), NM_DEVICE_STATE_REASON_PPP_FAILED); + } +} + +static NMActStageReturn +static_stage3_ip4_config_start(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + GError * error = NULL; + + if (!priv->ip4_config) { + _LOGD("IP4 config not ready(?)"); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + _LOGD("IP4 config is done; setting modem_state -> CONNECTED"); + g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error); + + /* Signal listener takes ownership of the IP4Config */ + priv->ip4_config = NULL; + + nm_modem_set_state(NM_MODEM(self), + NM_MODEM_STATE_CONNECTED, + nm_modem_state_to_string(NM_MODEM_STATE_CONNECTED)); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGE("failed to create ofono ConnectionContext DBus proxy: %s", error->message); + g_clear_object(&priv->context_proxy_cancellable); + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY); + return; + } + + priv->context_proxy = proxy; + + if (!priv->gprs_attached) { + g_clear_object(&priv->context_proxy_cancellable); + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } + + /* We have an old copy of the settings from a previous activation, + * clear it so that we can gate getting the IP config from oFono + * on whether or not we have already received them + */ + g_clear_object(&priv->ip4_config); + + _nm_dbus_signal_connect(priv->context_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(context_property_changed), + self); + + g_dbus_proxy_call(priv->context_proxy, + "SetProperty", + g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)), + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->context_proxy_cancellable, + stage1_prepare_done, + self); +} + +static void +do_context_activate(NMModemOfono *self) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_return_if_fail(NM_IS_MODEM_OFONO(self)); + + nm_clear_g_cancellable(&priv->context_proxy_cancellable); + g_clear_object(&priv->context_proxy); + + priv->context_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + priv->context_path, + OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT, + priv->context_proxy_cancellable, + context_proxy_new_cb, + self); +} + +static GHashTable * +create_connect_properties(NMConnection *connection) +{ + NMSettingGsm *setting; + GHashTable * properties; + const char * str; + + setting = nm_connection_get_setting_gsm(connection); + properties = g_hash_table_new(nm_str_hash, g_str_equal); + + str = nm_setting_gsm_get_apn(setting); + if (str) + g_hash_table_insert(properties, "AccessPointName", g_strdup(str)); + + str = nm_setting_gsm_get_username(setting); + if (str) + g_hash_table_insert(properties, "Username", g_strdup(str)); + + str = nm_setting_gsm_get_password(setting); + if (str) + g_hash_table_insert(properties, "Password", g_strdup(str)); + + return properties; +} + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + const char * context_id; + char ** id = NULL; + + context_id = nm_connection_get_id(connection); + id = g_strsplit(context_id, "/", 0); + g_return_val_if_fail(id[2], NM_ACT_STAGE_RETURN_FAILURE); + + _LOGD("trying %s %s", id[1], id[2]); + + g_free(priv->context_path); + priv->context_path = g_strdup_printf("%s/%s", nm_modem_get_path(modem), id[2]); + g_strfreev(id); + + if (!priv->context_path) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_GSM_APN_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (priv->connect_properties) + g_hash_table_destroy(priv->connect_properties); + + priv->connect_properties = create_connect_properties(connection); + + _LOGI("activating context %s", priv->context_path); + + if (nm_modem_get_state(modem) == NM_MODEM_STATE_REGISTERED) { + do_context_activate(self); + } else { + _LOGW("could not activate context: modem is not registered."); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGE("failed to create ofono modem DBus proxy: %s", error->message); + g_clear_object(&priv->modem_proxy_cancellable); + return; + } + + priv->modem_proxy = proxy; + + _nm_dbus_signal_connect(priv->modem_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(modem_property_changed), + self); + + g_dbus_proxy_call(priv->modem_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->modem_proxy_cancellable, + modem_get_properties_done, + self); +} + +/*****************************************************************************/ + +static void +nm_modem_ofono_init(NMModemOfono *self) +{} + +static void +constructed(GObject *object) +{ + NMModemOfono * self = NM_MODEM_OFONO(object); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + priv->modem_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_MODEM, + priv->modem_proxy_cancellable, + modem_proxy_new_cb, + self); + + G_OBJECT_CLASS(nm_modem_ofono_parent_class)->constructed(object); +} + +NMModem * +nm_modem_ofono_new(const char *path) +{ + gs_free char *basename = NULL; + + g_return_val_if_fail(path != NULL, NULL); + + nm_log_info(LOGD_MB, "ofono: creating new Ofono modem path %s", path); + + /* Use short modem name (not its object path) as the NM device name (which + * comes from NM_MODEM_UID)and the device ID. + */ + basename = g_path_get_basename(path); + + return g_object_new(NM_TYPE_MODEM_OFONO, + NM_MODEM_PATH, + path, + NM_MODEM_UID, + basename, + NM_MODEM_DEVICE_ID, + basename, + NM_MODEM_CONTROL_PORT, + "ofono", /* mandatory */ + NM_MODEM_DRIVER, + "ofono", + NM_MODEM_STATE, + (int) NM_MODEM_STATE_INITIALIZING, + NULL); +} + +static void +dispose(GObject *object) +{ + NMModemOfono * self = NM_MODEM_OFONO(object); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->modem_proxy_cancellable); + nm_clear_g_cancellable(&priv->connman_proxy_cancellable); + nm_clear_g_cancellable(&priv->context_proxy_cancellable); + nm_clear_g_cancellable(&priv->sim_proxy_cancellable); + + if (priv->connect_properties) { + g_hash_table_destroy(priv->connect_properties); + priv->connect_properties = NULL; + } + + g_clear_object(&priv->ip4_config); + + if (priv->modem_proxy) { + g_signal_handlers_disconnect_by_data(priv->modem_proxy, self); + g_clear_object(&priv->modem_proxy); + } + + if (priv->connman_proxy) { + g_signal_handlers_disconnect_by_data(priv->connman_proxy, self); + g_clear_object(&priv->connman_proxy); + } + + if (priv->context_proxy) { + g_signal_handlers_disconnect_by_data(priv->context_proxy, self); + g_clear_object(&priv->context_proxy); + } + + if (priv->sim_proxy) { + g_signal_handlers_disconnect_by_data(priv->sim_proxy, self); + g_clear_object(&priv->sim_proxy); + } + + g_free(priv->imsi); + priv->imsi = NULL; + + G_OBJECT_CLASS(nm_modem_ofono_parent_class)->dispose(object); +} + +static void +nm_modem_ofono_class_init(NMModemOfonoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMModemClass *modem_class = NM_MODEM_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + modem_class->get_capabilities = get_capabilities; + modem_class->disconnect = disconnect; + modem_class->deactivate_cleanup = deactivate_cleanup; + modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem; + + modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; +} diff --git a/src/core/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h new file mode 100644 index 0000000..260e395 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-ofono.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - Canonical Ltd. + */ + +#ifndef NM_MODEM_OFONO_H +#define NM_MODEM_OFONO_H + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_OFONO (nm_modem_ofono_get_type()) +#define NM_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_OFONO, NMModemOfono)) +#define NM_IS_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_OFONO)) +#define NM_MODEM_OFONO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_OFONO, NMModemOfonoClass)) +#define NM_IS_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_OFONO)) +#define NM_MODEM_OFONO_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_OFONO, NMModemOfonoClass)) + +#define OFONO_DBUS_SERVICE "org.ofono" +#define OFONO_DBUS_PATH "/" +#define OFONO_DBUS_INTERFACE "org.ofono.Manager" +#define OFONO_DBUS_INTERFACE_MODEM "org.ofono.Modem" +#define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager" +#define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext" +#define OFONO_DBUS_INTERFACE_SIM_MANAGER "org.ofono.SimManager" + +typedef struct _NMModemOfono NMModemOfono; +typedef struct _NMModemOfonoClass NMModemOfonoClass; + +GType nm_modem_ofono_get_type(void); + +NMModem *nm_modem_ofono_new(const char *path); + +#endif /* NM_MODEM_OFONO_H */ diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c new file mode 100644 index 0000000..4e44122 --- /dev/null +++ b/src/core/devices/wwan/nm-modem.c @@ -0,0 +1,2037 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-modem.h" + +#include +#include +#include + +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-setting-connection.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "nm-netns.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "ppp/nm-ppp-manager-call.h" +#include "ppp/nm-ppp-status.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMModem, + PROP_CONTROL_PORT, + PROP_IP_IFINDEX, + PROP_PATH, + PROP_UID, + PROP_DRIVER, + PROP_STATE, + PROP_DEVICE_ID, + PROP_SIM_ID, + PROP_IP_TYPES, + PROP_SIM_OPERATOR_ID, + PROP_OPERATOR_CODE, + PROP_APN, ); + +enum { + PPP_STATS, + PPP_FAILED, + PREPARE_RESULT, + IP4_CONFIG_RESULT, + IP6_CONFIG_RESULT, + AUTH_REQUESTED, + AUTH_RESULT, + REMOVED, + STATE_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct _NMModemPrivate { + char *uid; + char *path; + char *driver; + char *control_port; + char *data_port; + + /* TODO: ip_iface is solely used for nm_modem_owns_port(). + * We should rework the code that it's not necessary */ + char *ip_iface; + + int ip_ifindex; + NMModemIPMethod ip4_method; + NMModemIPMethod ip6_method; + NMUtilsIPv6IfaceId iid; + NMModemState state; + NMModemState prev_state; /* revert to this state if enable/disable fails */ + char * device_id; + char * sim_id; + NMModemIPType ip_types; + char * sim_operator_id; + char * operator_code; + char * apn; + + NMPPPManager *ppp_manager; + + NMActRequest * act_request; + guint32 secrets_tries; + NMActRequestGetSecretsCallId *secrets_id; + + guint mm_ip_timeout; + + guint32 ip4_route_table; + guint32 ip4_route_metric; + guint32 ip6_route_table; + guint32 ip6_route_metric; + + /* PPP stats */ + guint32 in_bytes; + guint32 out_bytes; + + bool claimed : 1; +} NMModemPrivate; + +G_DEFINE_TYPE(NMModem, nm_modem, G_TYPE_OBJECT) + +#define NM_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMModem, NM_IS_MODEM) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_BUFLEN 64 +#define _NMLOG_PREFIX_NAME "modem" +#define _NMLOG_DOMAIN LOGD_MB + +static const char * +_nmlog_prefix(char *prefix, NMModem *self) +{ + const char *uuid; + int c; + + if (!self) + return ""; + + uuid = nm_modem_get_uid(self); + + if (uuid) { + char pp[_NMLOG_PREFIX_BUFLEN - 5]; + + c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%s]", nm_strquote(pp, sizeof(pp), uuid)); + } else + c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%p]", self); + nm_assert(c < _NMLOG_PREFIX_BUFLEN); + + return prefix; +} + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _prefix[_NMLOG_PREFIX_BUFLEN]; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _nmlog_prefix(_prefix, (self)) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _set_ip_ifindex(NMModem *self, int ifindex, const char *ifname); + +/*****************************************************************************/ +/* State/enabled/connected */ + +static const char *state_table[] = { + [NM_MODEM_STATE_UNKNOWN] = "unknown", + [NM_MODEM_STATE_FAILED] = "failed", + [NM_MODEM_STATE_INITIALIZING] = "initializing", + [NM_MODEM_STATE_LOCKED] = "locked", + [NM_MODEM_STATE_DISABLED] = "disabled", + [NM_MODEM_STATE_DISABLING] = "disabling", + [NM_MODEM_STATE_ENABLING] = "enabling", + [NM_MODEM_STATE_ENABLED] = "enabled", + [NM_MODEM_STATE_SEARCHING] = "searching", + [NM_MODEM_STATE_REGISTERED] = "registered", + [NM_MODEM_STATE_DISCONNECTING] = "disconnecting", + [NM_MODEM_STATE_CONNECTING] = "connecting", + [NM_MODEM_STATE_CONNECTED] = "connected", +}; + +const char * +nm_modem_state_to_string(NMModemState state) +{ + if ((gsize) state < G_N_ELEMENTS(state_table)) + return state_table[state]; + return NULL; +} + +/*****************************************************************************/ + +gboolean +nm_modem_is_claimed(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + + return NM_MODEM_GET_PRIVATE(self)->claimed; +} + +NMModem * +nm_modem_claim(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + priv = NM_MODEM_GET_PRIVATE(self); + + g_return_val_if_fail(!priv->claimed, NULL); + + priv->claimed = TRUE; + return g_object_ref(self); +} + +void +nm_modem_unclaim(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + + g_return_if_fail(priv->claimed); + + /* we don't actually unclaim the instance. This instance should not be re-used + * by another owner, that is because we only claim modems as we receive them. + * There is no mechanism that somebody else would later re-use them again. + * + * // priv->claimed = FALSE; */ + + g_object_unref(self); +} + +/*****************************************************************************/ + +NMModemState +nm_modem_get_state(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->state; +} + +void +nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + NMModemState old_state = priv->state; + + priv->prev_state = NM_MODEM_STATE_UNKNOWN; + + if (new_state != old_state) { + _LOGI("modem state changed, '%s' --> '%s' (reason: %s)", + nm_modem_state_to_string(old_state), + nm_modem_state_to_string(new_state), + reason ?: "none"); + + priv->state = new_state; + _notify(self, PROP_STATE); + g_signal_emit(self, signals[STATE_CHANGED], 0, (int) new_state, (int) old_state); + } +} + +void +nm_modem_set_prev_state(NMModem *self, const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + /* Reset modem to previous state if the state hasn't already changed */ + if (priv->prev_state != NM_MODEM_STATE_UNKNOWN) + nm_modem_set_state(self, priv->prev_state, reason); +} + +void +nm_modem_set_mm_enabled(NMModem *self, gboolean enabled) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + NMModemState prev_state = priv->state; + + if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) { + _LOGD("cannot enable modem: already enabled"); + return; + } + if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) { + _LOGD("cannot disable modem: already disabled"); + return; + } + + if (priv->state <= NM_MODEM_STATE_INITIALIZING) { + _LOGD("cannot enable/disable modem: initializing or failed"); + return; + } else if (priv->state == NM_MODEM_STATE_LOCKED) { + /* Don't try to enable if the modem is locked since that will fail */ + _LOGW("cannot enable/disable modem: locked"); + + /* Try to unlock the modem if it's being enabled */ + if (enabled) + g_signal_emit(self, signals[AUTH_REQUESTED], 0); + return; + } + + /* Not all modem classes support set_mm_enabled */ + if (NM_MODEM_GET_CLASS(self)->set_mm_enabled) + NM_MODEM_GET_CLASS(self)->set_mm_enabled(self, enabled); + + /* Pre-empt the state change signal */ + nm_modem_set_state(self, + enabled ? NM_MODEM_STATE_ENABLING : NM_MODEM_STATE_DISABLING, + "user preference"); + priv->prev_state = prev_state; +} + +void +nm_modem_emit_removed(NMModem *self) +{ + g_signal_emit(self, signals[REMOVED], 0); +} + +void +nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason) +{ + nm_assert(NM_IS_MODEM(self)); + + g_signal_emit(self, signals[PREPARE_RESULT], 0, success, (guint) reason); +} + +void +nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason) +{ + nm_assert(NM_IS_MODEM(self)); + + g_signal_emit(self, signals[PPP_FAILED], 0, (guint) reason); +} + +NMModemIPType +nm_modem_get_supported_ip_types(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->ip_types; +} + +const char * +nm_modem_ip_type_to_string(NMModemIPType ip_type) +{ + switch (ip_type) { + case NM_MODEM_IP_TYPE_IPV4: + return "ipv4"; + case NM_MODEM_IP_TYPE_IPV6: + return "ipv6"; + case NM_MODEM_IP_TYPE_IPV4V6: + return "ipv4v6"; + default: + g_return_val_if_reached("unknown"); + } +} + +static GArray * +build_single_ip_type_array(NMModemIPType type) +{ + return g_array_append_val(g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 1), type); +} + +/** + * nm_modem_get_connection_ip_type: + * @self: the #NMModem + * @connection: the #NMConnection to determine IP type to use + * + * Given a modem and a connection, determine which #NMModemIPTypes to use + * when connecting. + * + * Returns: an array of #NMModemIpType values, in the order in which they + * should be tried. + */ +GArray * +nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMSettingIPConfig *s_ip4, *s_ip6; + const char * method; + gboolean ip4 = TRUE, ip6 = TRUE; + gboolean ip4_may_fail = TRUE, ip6_may_fail = TRUE; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (s_ip4) { + method = nm_setting_ip_config_get_method(s_ip4); + if (g_strcmp0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) + ip4 = FALSE; + ip4_may_fail = nm_setting_ip_config_get_may_fail(s_ip4); + } + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (s_ip6) { + method = nm_setting_ip_config_get_method(s_ip6); + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + ip6 = FALSE; + ip6_may_fail = nm_setting_ip_config_get_may_fail(s_ip6); + } + + if (ip4 && !ip6) { + if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV4)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested IPv4 but IPv4 is " + "unsupported by the modem."); + return NULL; + } + return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV4); + } + + if (ip6 && !ip4) { + if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV6)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested IPv6 but IPv6 is " + "unsupported by the modem."); + return NULL; + } + return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV6); + } + + if (ip4 && ip6) { + NMModemIPType type; + GArray * out; + + out = g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 3); + + /* Modem supports dual-stack? */ + if (priv->ip_types & NM_MODEM_IP_TYPE_IPV4V6) { + type = NM_MODEM_IP_TYPE_IPV4V6; + g_array_append_val(out, type); + } + + /* If IPv6 may-fail=false, we should NOT try IPv4 as fallback */ + if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV4) && ip6_may_fail) { + type = NM_MODEM_IP_TYPE_IPV4; + g_array_append_val(out, type); + } + + /* If IPv4 may-fail=false, we should NOT try IPv6 as fallback */ + if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV6) && ip4_may_fail) { + type = NM_MODEM_IP_TYPE_IPV6; + g_array_append_val(out, type); + } + + if (out->len > 0) + return out; + + /* Error... */ + g_array_unref(out); + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested both IPv4 and IPv6 " + "but dual-stack addressing is unsupported " + "by the modem."); + return NULL; + } + + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection specified no IP configuration!"); + return NULL; +} + +const char * +nm_modem_get_device_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->device_id; +} + +const char * +nm_modem_get_sim_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->sim_id; +} + +const char * +nm_modem_get_sim_operator_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->sim_operator_id; +} + +const char * +nm_modem_get_operator_code(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->operator_code; +} + +const char * +nm_modem_get_apn(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->apn; +} + +/*****************************************************************************/ +/* IP method PPP */ + +static void +ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + + nm_assert(ifindex >= 0); + nm_assert(NM_MODEM_GET_PRIVATE(self)->ppp_manager == ppp_manager); + + if (ifindex <= 0 && iface) { + /* this might happen, if the ifname was already deleted + * and we failed to resolve ifindex. + * + * Forget about the name. */ + iface = NULL; + } + _set_ip_ifindex(self, ifindex, iface); +} + +static void +ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + guint32 i, num; + guint32 bad_dns1 = htonl(0x0A0B0C0D); + guint32 good_dns1 = htonl(0x04020201); /* GTE nameserver */ + guint32 bad_dns2 = htonl(0x0A0B0C0E); + guint32 good_dns2 = htonl(0x04020202); /* GTE nameserver */ + gboolean dns_workaround = FALSE; + + /* Work around a PPP bug (#1732) which causes many mobile broadband + * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers. + * Apparently fixed in ppp-2.4.5 but we've had some reports that this is + * not the case. + * + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=2e09ef6886bbf00bc5a9a641110f801e372ffde6 + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=f8191bf07df374f119a07910a79217c7618f113e + */ + + num = nm_ip4_config_get_num_nameservers(config); + if (num == 2) { + gboolean found1 = FALSE, found2 = FALSE; + + for (i = 0; i < num; i++) { + guint32 ns = nm_ip4_config_get_nameserver(config, i); + + if (ns == bad_dns1) + found1 = TRUE; + else if (ns == bad_dns2) + found2 = TRUE; + } + + /* Be somewhat conservative about substitutions; the "bad" nameservers + * could actually be valid in some cases, so only substitute if ppp + * returns *only* the two bad nameservers. + */ + dns_workaround = (found1 && found2); + } + + if (!num || dns_workaround) { + _LOGW("compensating for invalid PPP-provided nameservers"); + nm_ip4_config_reset_nameservers(config); + nm_ip4_config_add_nameserver(config, good_dns1); + nm_ip4_config_add_nameserver(config, good_dns2); + } + + g_signal_emit(self, signals[IP4_CONFIG_RESULT], 0, config, NULL); +} + +static void +ppp_ip6_config(NMPPPManager * ppp_manager, + const NMUtilsIPv6IfaceId *iid, + NMIP6Config * config, + gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + + NM_MODEM_GET_PRIVATE(self)->iid = *iid; + + nm_modem_emit_ip6_config_result(self, config, NULL); +} + +static void +ppp_stats(NMPPPManager *ppp_manager, guint i_in_bytes, guint i_out_bytes, gpointer user_data) +{ + NMModem * self = NM_MODEM(user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + guint32 in_bytes = i_in_bytes; + guint32 out_bytes = i_out_bytes; + + if (priv->in_bytes != in_bytes || priv->out_bytes != out_bytes) { + priv->in_bytes = in_bytes; + priv->out_bytes = out_bytes; + g_signal_emit(self, signals[PPP_STATS], 0, (guint) in_bytes, (guint) out_bytes); + } +} + +static gboolean +port_speed_is_zero(const char *port) +{ + struct termios options; + nm_auto_close int fd = -1; + gs_free char * path = NULL; + + nm_assert(port); + + if (port[0] != '/') { + if (!port[0] || strchr(port, '/') || NM_IN_STRSET(port, ".", "..")) + return FALSE; + path = g_build_path("/sys/class/tty", port, NULL); + port = path; + } + + fd = open(port, O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC); + if (fd < 0) + return FALSE; + + memset(&options, 0, sizeof(struct termios)); + if (tcgetattr(fd, &options) != 0) + return FALSE; + + return cfgetospeed(&options) == B0; +} + +static NMActStageReturn +ppp_stage3_ip_config_start(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + const char * ppp_name = NULL; + GError * error = NULL; + guint ip_timeout = 30; + guint baud_override = 0; + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE); + + /* If we're already running PPP don't restart it; for example, if both + * IPv4 and IPv6 are requested, IPv4 gets started first, but we use the + * same pppd for both v4 and v6. + */ + if (priv->ppp_manager) + return NM_ACT_STAGE_RETURN_POSTPONE; + + if (NM_MODEM_GET_CLASS(self)->get_user_pass) { + NMConnection *connection = nm_act_request_get_applied_connection(req); + + g_assert(connection); + if (!NM_MODEM_GET_CLASS(self)->get_user_pass(self, connection, &ppp_name, NULL)) + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!priv->data_port) { + _LOGE("error starting PPP (no data port)"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported, + * use the default one (30s) */ + if (priv->mm_ip_timeout > 0) { + _LOGI("using modem-specified IP timeout: %u seconds", priv->mm_ip_timeout); + ip_timeout = priv->mm_ip_timeout; + } + + /* Some tty drivers and modems ignore port speed, but pppd requires the + * port speed to be > 0 or it exits. If the port speed is 0 pass an + * explicit speed to pppd to prevent the exit. + * https://bugzilla.redhat.com/show_bug.cgi?id=1281731 + */ + if (port_speed_is_zero(priv->data_port)) + baud_override = 57600; + + priv->ppp_manager = nm_ppp_manager_create(priv->data_port, &error); + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } + + if (!priv->ppp_manager + || !nm_ppp_manager_start(priv->ppp_manager, + req, + ppp_name, + ip_timeout, + baud_override, + &error)) { + _LOGE("error starting PPP: %s", error->message); + g_error_free(error); + g_clear_object(&priv->ppp_manager); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_CALLBACK(ppp_state_changed), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_CALLBACK(ppp_ifindex_set), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_CALLBACK(ppp_ip4_config), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP6_CONFIG, + G_CALLBACK(ppp_ip6_config), + self); + g_signal_connect(priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATS, G_CALLBACK(ppp_stats), self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +NMActStageReturn +nm_modem_stage3_ip4_config_start(NMModem * self, + NMDevice * device, + NMDeviceClass * device_class, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv; + NMActRequest * req; + NMConnection * connection; + const char * method; + NMActStageReturn ret; + + _LOGD("ip4_config_start"); + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_DEVICE(device), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_DEVICE_CLASS(device_class), NM_ACT_STAGE_RETURN_FAILURE); + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + nm_modem_set_route_parameters_from_device(self, device); + + method = nm_utils_get_ip_config_method(connection, AF_INET); + + /* Only Disabled and Auto methods make sense for WWAN */ + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + return NM_ACT_STAGE_RETURN_SUCCESS; + + if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + _LOGE("unhandled WWAN IPv4 method '%s'; will fail", method); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv = NM_MODEM_GET_PRIVATE(self); + switch (priv->ip4_method) { + case NM_MODEM_IP_METHOD_PPP: + ret = ppp_stage3_ip_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_STATIC: + _LOGD("MODEM_IP_METHOD_STATIC"); + ret = + NM_MODEM_GET_CLASS(self)->static_stage3_ip4_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_AUTO: + _LOGD("MODEM_IP_METHOD_AUTO"); + ret = device_class->act_stage3_ip_config_start(device, AF_INET, NULL, out_failure_reason); + break; + default: + _LOGI("IPv4 configuration disabled"); + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + break; + } + + return ret; +} + +void +nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(modem); + + /* If the modem has an ethernet-type data interface (ie, not PPP and thus + * not point-to-point) and IP config has a /32 prefix, then we assume that + * ARP will be pointless and we turn it off. + */ + if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) { + const NMPlatformIP4Address *address = nm_ip4_config_get_first_address(config); + + g_assert(address); + if (address->plen == 32) + nm_platform_link_set_noarp(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device)); + } +} + +/*****************************************************************************/ + +void +nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *addr; + gboolean do_slaac = TRUE; + + if (error) { + g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, NULL, FALSE, error); + return; + } + + if (config) { + /* If the IPv6 configuration only included a Link-Local address, then + * we have to run SLAAC to get the full IPv6 configuration. + */ + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) { + if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) { + if (!priv->iid.id) + priv->iid.id = ((guint64 *) (&addr->address.s6_addr))[1]; + } else + do_slaac = FALSE; + } + } + g_assert(config || do_slaac); + + g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, config, do_slaac, NULL); +} + +static NMActStageReturn +stage3_ip6_config_request(NMModem *self, NMDeviceStateReason *out_failure_reason) +{ + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +NMActStageReturn +nm_modem_stage3_ip6_config_start(NMModem * self, + NMDevice * device, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv; + NMActRequest * req; + NMActStageReturn ret; + NMConnection * connection; + const char * method; + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + nm_modem_set_route_parameters_from_device(self, device); + + method = nm_utils_get_ip_config_method(connection, AF_INET6); + + /* Only Ignore, Disabled and Auto methods make sense for WWAN */ + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + return NM_ACT_STAGE_RETURN_IP_DONE; + + if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + _LOGW("unhandled WWAN IPv6 method '%s'; will fail", method); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv = NM_MODEM_GET_PRIVATE(self); + switch (priv->ip6_method) { + case NM_MODEM_IP_METHOD_PPP: + ret = ppp_stage3_ip_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_STATIC: + case NM_MODEM_IP_METHOD_AUTO: + /* Both static and DHCP/Auto retrieve a base IP config from the modem + * which in the static case is the full config, and the DHCP/Auto case + * is just the IPv6LL address to use for SLAAC. + */ + ret = NM_MODEM_GET_CLASS(self)->stage3_ip6_config_request(self, out_failure_reason); + break; + default: + _LOGI("IPv6 configuration disabled"); + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + break; + } + + return ret; +} + +guint32 +nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + NMConnection *connection; + NMSetting * setting; + gint64 mtu_default; + guint mtu = 0; + const char * property_name; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(out_source); + + connection = nm_device_get_applied_connection(self); + if (!connection) + g_return_val_if_reached(0); + + setting = (NMSetting *) nm_connection_get_setting_gsm(connection); + if (!setting) + setting = (NMSetting *) nm_connection_get_setting_cdma(connection); + + if (setting) { + g_object_get(setting, "mtu", &mtu, NULL); + if (mtu) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return mtu; + } + + property_name = NM_IS_SETTING_GSM(setting) ? "gsm.mtu" : "cdma.mtu"; + mtu_default = + nm_device_get_configured_mtu_from_connection_default(self, property_name, G_MAXUINT32); + if (mtu_default >= 0) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return (guint32) mtu_default; + } + } + + *out_source = NM_DEVICE_MTU_SOURCE_NONE; + return 0; +} + +/*****************************************************************************/ + +static void +cancel_get_secrets(NMModem *self) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (priv->secrets_id) + nm_act_request_cancel_secrets(priv->act_request, priv->secrets_id); +} + +static void +modem_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMModem * self = NM_MODEM(user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + g_return_if_fail(call_id == priv->secrets_id); + + priv->secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) + || g_error_matches(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS)) + return; + + if (error) + _LOGW("modem-secrets: %s", error->message); + + g_signal_emit(self, signals[AUTH_RESULT], 0, error); +} + +void +nm_modem_get_secrets(NMModem * self, + const char *setting_name, + gboolean request_new, + const char *hint) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + cancel_get_secrets(self); + + if (request_new) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + priv->secrets_id = nm_act_request_get_secrets(priv->act_request, + FALSE, + setting_name, + flags, + NM_MAKE_STRV(hint), + modem_secrets_cb, + self); + g_return_if_fail(priv->secrets_id); + g_signal_emit(self, signals[AUTH_REQUESTED], 0); +} + +/*****************************************************************************/ + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_UNKNOWN); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +NMActStageReturn +nm_modem_act_stage1_prepare(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *hints = NULL; + const char * setting_name = NULL; + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + NMConnection * connection; + + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE); + + if (priv->act_request) + g_object_unref(priv->act_request); + priv->act_request = g_object_ref(req); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + setting_name = nm_connection_need_secrets(connection, &hints); + if (!setting_name) { + nm_assert(!hints); + return NM_MODEM_GET_CLASS(self)->modem_act_stage1_prepare(self, + connection, + out_failure_reason); + } + + /* Secrets required... */ + if (priv->secrets_tries++) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + + if (hints) + g_ptr_array_add(hints, NULL); + + priv->secrets_id = nm_act_request_get_secrets(req, + FALSE, + setting_name, + flags, + hints ? (const char *const *) hints->pdata : NULL, + modem_secrets_cb, + self); + g_return_val_if_fail(priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE); + g_signal_emit(self, signals[AUTH_REQUESTED], 0); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +void +nm_modem_act_stage2_config(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + /* Clear secrets tries counter since secrets were successfully used + * already if we get here. + */ + priv->secrets_tries = 0; +} + +/*****************************************************************************/ + +gboolean +nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_GSM_SETTING_NAME)) { + NMSettingGsm *s_gsm; + const char * str; + + s_gsm = _nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error); + if (!s_gsm) + return FALSE; + + str = nm_setting_gsm_get_device_id(s_gsm); + if (str) { + if (!priv->device_id) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "GSM profile has device-id, device does not"); + return FALSE; + } + if (!nm_streq(str, priv->device_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing device-id than GSM profile"); + return FALSE; + } + } + + /* SIM properties may not be available before the SIM is unlocked, so + * to ensure that autoconnect works, the connection's SIM properties + * are only compared if present on the device. + */ + + if (priv->sim_id && (str = nm_setting_gsm_get_sim_id(s_gsm))) { + if (!nm_streq(str, priv->sim_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing sim-id than GSM profile"); + return FALSE; + } + } + + if (priv->sim_operator_id && (str = nm_setting_gsm_get_sim_operator_id(s_gsm))) { + if (!nm_streq(str, priv->sim_operator_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing sim-operator-id than GSM profile"); + return FALSE; + } + } + } + + return NM_MODEM_GET_CLASS(self)->check_connection_compatible_with_modem(self, + connection, + error); +} + +/*****************************************************************************/ + +gboolean +nm_modem_complete_connection(NMModem * self, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error) +{ + NMModemClass *klass; + + klass = NM_MODEM_GET_CLASS(self); + if (!klass->complete_connection) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "Modem class %s had no complete_connection method", + G_OBJECT_TYPE_NAME(self)); + return FALSE; + } + + return klass->complete_connection(self, iface, connection, existing_connections, error); +} + +/*****************************************************************************/ + +static void +deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemPrivate *priv; + int ifindex; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + + priv->secrets_tries = 0; + + if (priv->act_request) { + cancel_get_secrets(self); + g_object_unref(priv->act_request); + priv->act_request = NULL; + } + + priv->in_bytes = priv->out_bytes = 0; + + if (priv->ppp_manager) { + g_signal_handlers_disconnect_by_data(priv->ppp_manager, self); + if (stop_ppp_manager) + nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL); + g_clear_object(&priv->ppp_manager); + } + + if (device) { + g_return_if_fail(NM_IS_DEVICE(device)); + + if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO + || priv->ip6_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) { + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex > 0) { + NMPlatform *platform = nm_device_get_platform(device); + + nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex); + nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex); + nm_platform_link_set_down(platform, ifindex); + } + } + } + + nm_clear_g_free(&priv->data_port); + priv->mm_ip_timeout = 0; + priv->ip4_method = NM_MODEM_IP_METHOD_UNKNOWN; + priv->ip6_method = NM_MODEM_IP_METHOD_UNKNOWN; + _set_ip_ifindex(self, -1, NULL); +} + +/*****************************************************************************/ + +typedef struct { + NMModem * self; + NMDevice * device; + GCancellable * cancellable; + NMModemDeactivateCallback callback; + gpointer callback_user_data; +} DeactivateContext; + +static void +deactivate_context_complete(DeactivateContext *ctx, GError *error) +{ + NMModem *self = ctx->self; + + _LOGD("modem deactivation finished %s%s%s", + NM_PRINT_FMT_QUOTED(error, "with failure: ", error->message, "", "successfully")); + + if (ctx->callback) + ctx->callback(ctx->self, error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->device); + g_object_unref(ctx->self); + g_slice_free(DeactivateContext, ctx); +} + +static void +_deactivate_call_disconnect_cb(NMModem *self, GError *error, gpointer user_data) +{ + deactivate_context_complete(user_data, error); +} + +static void +_deactivate_call_disconnect(DeactivateContext *ctx) +{ + NM_MODEM_GET_CLASS(ctx->self)->disconnect(ctx->self, + FALSE, + ctx->cancellable, + _deactivate_call_disconnect_cb, + ctx); +} + +static void +_deactivate_ppp_manager_stop_cb(NMPPPManager * ppp_manager, + NMPPPManagerStopHandle *handle, + gboolean was_cancelled, + gpointer user_data) +{ + DeactivateContext *ctx = user_data; + + g_object_unref(ppp_manager); + + if (was_cancelled) { + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled(ctx->cancellable, &error)) + nm_assert_not_reached(); + deactivate_context_complete(ctx, error); + return; + } + + nm_assert(!g_cancellable_is_cancelled(ctx->cancellable)); + _deactivate_call_disconnect(ctx); +} + +void +nm_modem_deactivate_async(NMModem * self, + NMDevice * device, + GCancellable * cancellable, + NMModemDeactivateCallback callback, + gpointer user_data) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + DeactivateContext *ctx; + NMPPPManager * ppp_manager; + + g_return_if_fail(NM_IS_MODEM(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + g_return_if_fail(G_IS_CANCELLABLE(cancellable)); + + ctx = g_slice_new(DeactivateContext); + ctx->self = g_object_ref(self); + ctx->device = g_object_ref(device); + ctx->cancellable = g_object_ref(cancellable); + ctx->callback = callback; + ctx->callback_user_data = user_data; + + ppp_manager = nm_g_object_ref(priv->ppp_manager); + + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, ctx->device, FALSE); + + if (ppp_manager) { + /* If we have a PPP manager, stop it. + * + * Pass on the reference in @ppp_manager. */ + nm_ppp_manager_stop(ppp_manager, ctx->cancellable, _deactivate_ppp_manager_stop_cb, ctx); + return; + } + + _deactivate_call_disconnect(ctx); +} + +/*****************************************************************************/ + +void +nm_modem_deactivate(NMModem *self, NMDevice *device) +{ + /* First cleanup */ + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, device, TRUE); + /* Then disconnect without waiting */ + NM_MODEM_GET_CLASS(self)->disconnect(self, FALSE, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +void +nm_modem_device_state_changed(NMModem *self, NMDeviceState new_state, NMDeviceState old_state) +{ + gboolean was_connected = FALSE, warn = TRUE; + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + if (old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_DEACTIVATING) + was_connected = TRUE; + + priv = NM_MODEM_GET_PRIVATE(self); + + /* Make sure we don't leave the serial device open */ + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_FAILED: + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->act_request) { + cancel_get_secrets(self); + g_object_unref(priv->act_request); + priv->act_request = NULL; + } + + if (was_connected) { + /* Don't bother warning on FAILED since the modem is already gone */ + if (new_state == NM_DEVICE_STATE_FAILED || new_state == NM_DEVICE_STATE_DISCONNECTED) + warn = FALSE; + /* First cleanup */ + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, NULL, TRUE); + NM_MODEM_GET_CLASS(self)->disconnect(self, warn, NULL, NULL, NULL); + } + break; + default: + break; + } +} + +/*****************************************************************************/ + +const char * +nm_modem_get_uid(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->uid; +} + +const char * +nm_modem_get_path(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->path; +} + +const char * +nm_modem_get_driver(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->driver; +} + +const char * +nm_modem_get_control_port(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->control_port; +} + +int +nm_modem_get_ip_ifindex(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_val_if_fail(NM_IS_MODEM(self), 0); + + priv = NM_MODEM_GET_PRIVATE(self); + + /* internally we track an unset ip_ifindex as -1. + * For the caller of nm_modem_get_ip_ifindex(), this + * shall be zero too. */ + return priv->ip_ifindex != -1 ? priv->ip_ifindex : 0; +} + +static void +_set_ip_ifindex(NMModem *self, int ifindex, const char *ifname) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + nm_assert(ifindex >= -1); + nm_assert((ifindex > 0) == !!ifname); + + if (!nm_streq0(priv->ip_iface, ifname)) { + g_free(priv->ip_iface); + priv->ip_iface = g_strdup(ifname); + } + + if (priv->ip_ifindex != ifindex) { + priv->ip_ifindex = ifindex; + _notify(self, PROP_IP_IFINDEX); + } +} + +gboolean +nm_modem_set_data_port(NMModem * self, + NMPlatform * platform, + const char * data_port, + NMModemIPMethod ip4_method, + NMModemIPMethod ip6_method, + guint timeout, + GError ** error) +{ + NMModemPrivate *priv; + gboolean is_ppp; + int ifindex = -1; + + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + g_return_val_if_fail(NM_IS_PLATFORM(platform), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_MODEM_GET_PRIVATE(self); + + if (priv->ppp_manager || priv->data_port || priv->ip_ifindex != -1) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot set data port in activated state"); + /* this really shouldn't happen. Assert. */ + g_return_val_if_reached(FALSE); + } + + if (!data_port) { + g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "missing data port"); + return FALSE; + } + + is_ppp = (ip4_method == NM_MODEM_IP_METHOD_PPP) || (ip6_method == NM_MODEM_IP_METHOD_PPP); + if (is_ppp) { + if (!NM_IN_SET(ip4_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP) + || !NM_IN_SET(ip6_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "conflicting ip methods"); + return FALSE; + } + } else if (!NM_IN_SET(ip4_method, + NM_MODEM_IP_METHOD_UNKNOWN, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO) + || !NM_IN_SET(ip6_method, + NM_MODEM_IP_METHOD_UNKNOWN, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO) + || (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN + && ip6_method == NM_MODEM_IP_METHOD_UNKNOWN)) { + g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "invalid ip methods"); + return FALSE; + } + + if (!is_ppp) { + ifindex = nm_platform_if_nametoindex(platform, data_port); + if (ifindex <= 0) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot find network interface %s", + data_port); + return FALSE; + } + if (!nm_platform_process_events_ensure_link(platform, ifindex, data_port)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot find network interface %s in platform cache", + data_port); + return FALSE; + } + } + + priv->mm_ip_timeout = timeout; + priv->ip4_method = ip4_method; + priv->ip6_method = ip6_method; + if (is_ppp) { + priv->data_port = g_strdup(data_port); + _set_ip_ifindex(self, -1, NULL); + } else { + priv->data_port = NULL; + _set_ip_ifindex(self, ifindex, data_port); + } + return TRUE; +} + +gboolean +nm_modem_owns_port(NMModem *self, const char *iface) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + g_return_val_if_fail(iface != NULL, FALSE); + + if (NM_MODEM_GET_CLASS(self)->owns_port) + return NM_MODEM_GET_CLASS(self)->owns_port(self, iface); + + return NM_IN_STRSET(iface, priv->ip_iface, priv->data_port, priv->control_port); +} + +gboolean +nm_modem_get_iid(NMModem *self, NMUtilsIPv6IfaceId *out_iid) +{ + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + + *out_iid = NM_MODEM_GET_PRIVATE(self)->iid; + return TRUE; +} + +/*****************************************************************************/ + +void +nm_modem_get_route_parameters(NMModem *self, + guint32 *out_ip4_route_table, + guint32 *out_ip4_route_metric, + guint32 *out_ip6_route_table, + guint32 *out_ip6_route_metric) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + NM_SET_OUT(out_ip4_route_table, priv->ip4_route_table); + NM_SET_OUT(out_ip4_route_metric, priv->ip4_route_metric); + NM_SET_OUT(out_ip6_route_table, priv->ip6_route_table); + NM_SET_OUT(out_ip6_route_metric, priv->ip6_route_metric); +} + +void +nm_modem_set_route_parameters(NMModem *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + if (priv->ip4_route_table != ip4_route_table || priv->ip4_route_metric != ip4_route_metric + || priv->ip6_route_table != ip6_route_table || priv->ip6_route_metric != ip6_route_metric) { + priv->ip4_route_table = ip4_route_table; + priv->ip4_route_metric = ip4_route_metric; + priv->ip6_route_table = ip6_route_table; + priv->ip6_route_metric = ip6_route_metric; + + _LOGT("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u", + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } +} + +void +nm_modem_set_route_parameters_from_device(NMModem *self, NMDevice *device) +{ + g_return_if_fail(NM_IS_DEVICE(device)); + + nm_modem_set_route_parameters(self, + nm_device_get_route_table(device, AF_INET), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_table(device, AF_INET6), + nm_device_get_route_metric(device, AF_INET6)); +} + +/*****************************************************************************/ + +void +nm_modem_get_capabilities(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + g_return_if_fail(NM_IS_MODEM(self)); + + NM_MODEM_GET_CLASS(self)->get_capabilities(self, modem_caps, current_caps); +} + +/*****************************************************************************/ + +void +_nm_modem_set_operator_code(NMModem *self, const char *operator_code) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (g_strcmp0(priv->operator_code, operator_code) != 0) { + g_free(priv->operator_code); + priv->operator_code = g_strdup(operator_code); + _notify(self, PROP_OPERATOR_CODE); + } +} + +void +_nm_modem_set_apn(NMModem *self, const char *apn) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (g_strcmp0(priv->apn, apn) != 0) { + g_free(priv->apn); + priv->apn = g_strdup(apn); + _notify(self, PROP_APN); + } +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModem * self = NM_MODEM(object); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string(value, priv->path); + break; + case PROP_DRIVER: + g_value_set_string(value, priv->driver); + break; + case PROP_CONTROL_PORT: + g_value_set_string(value, priv->control_port); + break; + case PROP_IP_IFINDEX: + g_value_set_int(value, nm_modem_get_ip_ifindex(self)); + break; + case PROP_UID: + g_value_set_string(value, priv->uid); + break; + case PROP_STATE: + g_value_set_int(value, priv->state); + break; + case PROP_DEVICE_ID: + g_value_set_string(value, priv->device_id); + break; + case PROP_SIM_ID: + g_value_set_string(value, priv->sim_id); + break; + case PROP_IP_TYPES: + g_value_set_uint(value, priv->ip_types); + break; + case PROP_SIM_OPERATOR_ID: + g_value_set_string(value, priv->sim_operator_id); + break; + case PROP_OPERATOR_CODE: + g_value_set_string(value, priv->operator_code); + break; + case PROP_APN: + g_value_set_string(value, priv->apn); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + const char * s; + + switch (prop_id) { + case PROP_PATH: + /* construct-only */ + priv->path = g_value_dup_string(value); + g_return_if_fail(priv->path); + break; + case PROP_DRIVER: + /* construct-only */ + priv->driver = g_value_dup_string(value); + break; + case PROP_CONTROL_PORT: + /* construct-only */ + priv->control_port = g_value_dup_string(value); + break; + case PROP_UID: + /* construct-only */ + priv->uid = g_value_dup_string(value); + break; + case PROP_STATE: + /* construct-only */ + priv->state = g_value_get_int(value); + break; + case PROP_DEVICE_ID: + /* construct-only */ + priv->device_id = g_value_dup_string(value); + break; + case PROP_SIM_ID: + g_free(priv->sim_id); + priv->sim_id = g_value_dup_string(value); + break; + case PROP_IP_TYPES: + priv->ip_types = g_value_get_uint(value); + break; + case PROP_SIM_OPERATOR_ID: + nm_clear_g_free(&priv->sim_operator_id); + s = g_value_get_string(value); + if (s && s[0]) + priv->sim_operator_id = g_strdup(s); + break; + case PROP_OPERATOR_CODE: + /* construct-only */ + priv->operator_code = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_init(NMModem *self) +{ + NMModemPrivate *priv; + + self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_MODEM, NMModemPrivate); + priv = self->_priv; + + priv->ip_ifindex = -1; + priv->ip4_route_table = RT_TABLE_MAIN; + priv->ip4_route_metric = 700; + priv->ip6_route_table = RT_TABLE_MAIN; + priv->ip6_route_metric = 700; +} + +static void +constructed(GObject *object) +{ + NMModemPrivate *priv; + + G_OBJECT_CLASS(nm_modem_parent_class)->constructed(object); + + priv = NM_MODEM_GET_PRIVATE(NM_MODEM(object)); + + g_return_if_fail(priv->control_port); +} + +/*****************************************************************************/ + +static void +dispose(GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + + g_clear_object(&priv->act_request); + + G_OBJECT_CLASS(nm_modem_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + + g_free(priv->uid); + g_free(priv->path); + g_free(priv->driver); + g_free(priv->control_port); + g_free(priv->data_port); + g_free(priv->ip_iface); + g_free(priv->device_id); + g_free(priv->sim_id); + g_free(priv->sim_operator_id); + g_free(priv->operator_code); + g_free(priv->apn); + + G_OBJECT_CLASS(nm_modem_parent_class)->finalize(object); +} + +static void +nm_modem_class_init(NMModemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMModemPrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + klass->modem_act_stage1_prepare = modem_act_stage1_prepare; + klass->stage3_ip6_config_request = stage3_ip6_config_request; + klass->deactivate_cleanup = deactivate_cleanup; + + obj_properties[PROP_UID] = + g_param_spec_string(NM_MODEM_UID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PATH] = + g_param_spec_string(NM_MODEM_PATH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DRIVER] = + g_param_spec_string(NM_MODEM_DRIVER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONTROL_PORT] = + g_param_spec_string(NM_MODEM_CONTROL_PORT, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP_IFINDEX] = g_param_spec_int(NM_MODEM_IP_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE] = + g_param_spec_int(NM_MODEM_STATE, + "", + "", + NM_MODEM_STATE_UNKNOWN, + _NM_MODEM_STATE_LAST, + NM_MODEM_STATE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICE_ID] = + g_param_spec_string(NM_MODEM_DEVICE_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SIM_ID] = + g_param_spec_string(NM_MODEM_SIM_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP_TYPES] = + g_param_spec_uint(NM_MODEM_IP_TYPES, + "IP Types", + "Supported IP types", + 0, + G_MAXUINT32, + NM_MODEM_IP_TYPE_IPV4, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SIM_OPERATOR_ID] = + g_param_spec_string(NM_MODEM_SIM_OPERATOR_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_OPERATOR_CODE] = + g_param_spec_string(NM_MODEM_OPERATOR_CODE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_APN] = + g_param_spec_string(NM_MODEM_APN, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[PPP_STATS] = g_signal_new(NM_MODEM_PPP_STATS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT /*guint32 in_bytes*/, + G_TYPE_UINT /*guint32 out_bytes*/); + + signals[PPP_FAILED] = g_signal_new(NM_MODEM_PPP_FAILED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[IP4_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP4_CONFIG_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_OBJECT, + G_TYPE_POINTER); + + /** + * NMModem::ip6-config-result: + * @modem: the #NMModem on which the signal is emitted + * @config: the #NMIP6Config to apply to the modem's data port + * @do_slaac: %TRUE if IPv6 SLAAC should be started + * @error: a #GError if any error occurred during IP configuration + * + * This signal is emitted when IPv6 configuration has completed or failed. + * If @error is set the configuration failed. If @config is set, then + * the details should be applied to the data port before any further + * configuration (like SLAAC) is done. @do_slaac indicates whether SLAAC + * should be started after applying @config to the data port. + */ + signals[IP6_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP6_CONFIG_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_OBJECT, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); + + signals[PREPARE_RESULT] = g_signal_new(NM_MODEM_PREPARE_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_BOOLEAN, + G_TYPE_UINT); + + signals[AUTH_REQUESTED] = g_signal_new(NM_MODEM_AUTH_REQUESTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[AUTH_RESULT] = g_signal_new(NM_MODEM_AUTH_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[REMOVED] = g_signal_new(NM_MODEM_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[STATE_CHANGED] = g_signal_new(NM_MODEM_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_INT); +} diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h new file mode 100644 index 0000000..87162cf --- /dev/null +++ b/src/core/devices/wwan/nm-modem.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_MODEM_H__ +#define __NETWORKMANAGER_MODEM_H__ + +#include "ppp/nm-ppp-manager.h" +#include "devices/nm-device.h" + +#define NM_TYPE_MODEM (nm_modem_get_type()) +#define NM_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM, NMModem)) +#define NM_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM, NMModemClass)) +#define NM_IS_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM)) +#define NM_IS_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM)) +#define NM_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM, NMModemClass)) + +/* Properties */ +#define NM_MODEM_UID "uid" +#define NM_MODEM_PATH "path" +#define NM_MODEM_DRIVER "driver" +#define NM_MODEM_CONTROL_PORT "control-port" +#define NM_MODEM_IP_IFINDEX "ip-ifindex" +#define NM_MODEM_STATE "state" +#define NM_MODEM_DEVICE_ID "device-id" +#define NM_MODEM_SIM_ID "sim-id" +#define NM_MODEM_IP_TYPES "ip-types" /* Supported IP types */ +#define NM_MODEM_SIM_OPERATOR_ID "sim-operator-id" +#define NM_MODEM_OPERATOR_CODE "operator-code" +#define NM_MODEM_APN "apn" + +/* Signals */ +#define NM_MODEM_PPP_STATS "ppp-stats" +#define NM_MODEM_PPP_FAILED "ppp-failed" +#define NM_MODEM_PREPARE_RESULT "prepare-result" +#define NM_MODEM_IP4_CONFIG_RESULT "ip4-config-result" +#define NM_MODEM_IP6_CONFIG_RESULT "ip6-config-result" +#define NM_MODEM_AUTH_REQUESTED "auth-requested" +#define NM_MODEM_AUTH_RESULT "auth-result" +#define NM_MODEM_REMOVED "removed" +#define NM_MODEM_STATE_CHANGED "state-changed" + +typedef enum { + NM_MODEM_IP_METHOD_UNKNOWN = 0, + NM_MODEM_IP_METHOD_PPP, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO, /* DHCP and/or SLAAC */ +} NMModemIPMethod; + +/** + * NMModemIPType: + * @NM_MODEM_IP_TYPE_UNKNOWN: unknown or no IP support + * @NM_MODEM_IP_TYPE_IPV4: IPv4-only bearers are supported + * @NM_MODEM_IP_TYPE_IPV6: IPv6-only bearers are supported + * @NM_MODEM_IP_TYPE_IPV4V6: dual-stack IPv4 + IPv6 bearers are supported + * + * Indicates what IP protocols the modem supports for an IP bearer. Any + * combination of flags is possible. For example, (%NM_MODEM_IP_TYPE_IPV4 | + * %NM_MODEM_IP_TYPE_IPV6) indicates that the modem supports IPv4 and IPv6 + * but not simultaneously on the same bearer. + */ +typedef enum { + NM_MODEM_IP_TYPE_UNKNOWN = 0x0, + NM_MODEM_IP_TYPE_IPV4 = 0x1, + NM_MODEM_IP_TYPE_IPV6 = 0x2, + NM_MODEM_IP_TYPE_IPV4V6 = 0x4 +} NMModemIPType; + +typedef enum { /*< underscore_name=nm_modem_state >*/ + NM_MODEM_STATE_UNKNOWN = 0, + NM_MODEM_STATE_FAILED = 1, + NM_MODEM_STATE_INITIALIZING = 2, + NM_MODEM_STATE_LOCKED = 3, + NM_MODEM_STATE_DISABLED = 4, + NM_MODEM_STATE_DISABLING = 5, + NM_MODEM_STATE_ENABLING = 6, + NM_MODEM_STATE_ENABLED = 7, + NM_MODEM_STATE_SEARCHING = 8, + NM_MODEM_STATE_REGISTERED = 9, + NM_MODEM_STATE_DISCONNECTING = 10, + NM_MODEM_STATE_CONNECTING = 11, + NM_MODEM_STATE_CONNECTED = 12, + + _NM_MODEM_STATE_LAST0, + _NM_MODEM_STATE_LAST = _NM_MODEM_STATE_LAST0 - 1, +} NMModemState; + +struct _NMModemPrivate; + +struct _NMModem { + GObject parent; + struct _NMModemPrivate *_priv; +}; + +typedef struct _NMModem NMModem; + +typedef void (*_NMModemDisconnectCallback)(NMModem *modem, GError *error, gpointer user_data); + +typedef struct { + GObjectClass parent; + + void (*get_capabilities)(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + + gboolean (*get_user_pass)(NMModem * modem, + NMConnection *connection, + const char ** user, + const char ** pass); + + gboolean (*check_connection_compatible_with_modem)(NMModem * modem, + NMConnection *connection, + GError ** error); + + gboolean (*complete_connection)(NMModem * modem, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error); + + NMActStageReturn (*modem_act_stage1_prepare)(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason); + + NMActStageReturn (*static_stage3_ip4_config_start)(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason); + + /* Request the IP6 config; when the config returns the modem + * subclass should emit the ip6_config_result signal. + */ + NMActStageReturn (*stage3_ip6_config_request)(NMModem * self, + NMDeviceStateReason *out_failure_reason); + + void (*set_mm_enabled)(NMModem *self, gboolean enabled); + + void (*disconnect)(NMModem * self, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data); + + void (*deactivate_cleanup)(NMModem *self, NMDevice *device, gboolean stop_ppp_manager); + + gboolean (*owns_port)(NMModem *self, const char *iface); +} NMModemClass; + +GType nm_modem_get_type(void); + +gboolean nm_modem_is_claimed(NMModem *modem); +NMModem *nm_modem_claim(NMModem *modem); +void nm_modem_unclaim(NMModem *modem); + +const char *nm_modem_get_path(NMModem *modem); +const char *nm_modem_get_uid(NMModem *modem); +const char *nm_modem_get_control_port(NMModem *modem); +int nm_modem_get_ip_ifindex(NMModem *modem); +const char *nm_modem_get_driver(NMModem *modem); +const char *nm_modem_get_device_id(NMModem *modem); +const char *nm_modem_get_sim_id(NMModem *modem); +const char *nm_modem_get_sim_operator_id(NMModem *modem); +gboolean nm_modem_get_iid(NMModem *modem, NMUtilsIPv6IfaceId *out_iid); +const char *nm_modem_get_operator_code(NMModem *modem); +const char *nm_modem_get_apn(NMModem *modem); + +gboolean nm_modem_set_data_port(NMModem * self, + NMPlatform * platform, + const char * data_port, + NMModemIPMethod ip4_method, + NMModemIPMethod ip6_method, + guint timeout, + GError ** error); + +gboolean nm_modem_owns_port(NMModem *modem, const char *iface); + +void nm_modem_get_capabilities(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + +gboolean +nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error); + +gboolean nm_modem_complete_connection(NMModem * self, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error); + +void nm_modem_get_route_parameters(NMModem *self, + guint32 *out_ip4_route_table, + guint32 *out_ip4_route_metric, + guint32 *out_ip6_route_table, + guint32 *out_ip6_route_metric); + +void nm_modem_set_route_parameters(NMModem *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric); + +void nm_modem_set_route_parameters_from_device(NMModem *modem, NMDevice *device); + +NMActStageReturn nm_modem_act_stage1_prepare(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason); + +void nm_modem_act_stage2_config(NMModem *modem); + +NMActStageReturn nm_modem_stage3_ip4_config_start(NMModem * modem, + NMDevice * device, + NMDeviceClass * device_class, + NMDeviceStateReason *out_failure_reason); + +NMActStageReturn nm_modem_stage3_ip6_config_start(NMModem * modem, + NMDevice * device, + NMDeviceStateReason *out_failure_reason); + +void nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config); + +void nm_modem_get_secrets(NMModem * modem, + const char *setting_name, + gboolean request_new, + const char *hint); + +void nm_modem_deactivate(NMModem *modem, NMDevice *device); + +typedef void (*NMModemDeactivateCallback)(NMModem *self, GError *error, gpointer user_data); + +void nm_modem_deactivate_async(NMModem * self, + NMDevice * device, + GCancellable * cancellable, + NMModemDeactivateCallback callback, + gpointer user_data); + +void +nm_modem_device_state_changed(NMModem *modem, NMDeviceState new_state, NMDeviceState old_state); + +void nm_modem_set_mm_enabled(NMModem *self, gboolean enabled); + +NMModemState nm_modem_get_state(NMModem *self); +void nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason); +void nm_modem_set_prev_state(NMModem *self, const char *reason); +const char * nm_modem_state_to_string(NMModemState state); + +NMModemIPType nm_modem_get_supported_ip_types(NMModem *self); + +/* For the modem-manager only */ +void nm_modem_emit_removed(NMModem *self); + +void nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason); + +void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason); + +GArray *nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error); + +/* For subclasses */ +void nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error); + +const char *nm_modem_ip_type_to_string(NMModemIPType ip_type); + +guint32 +nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force); + +void _nm_modem_set_operator_code(NMModem *self, const char *operator_code); +void _nm_modem_set_apn(NMModem *self, const char *apn); + +#endif /* __NETWORKMANAGER_MODEM_H__ */ diff --git a/src/core/devices/wwan/nm-service-providers.c b/src/core/devices/wwan/nm-service-providers.c new file mode 100644 index 0000000..b918703 --- /dev/null +++ b/src/core/devices/wwan/nm-service-providers.c @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2009 Novell, Inc. + * Author: Tambet Ingo (tambet@gmail.com). + * Copyright (C) 2009 - 2019 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include "nm-default.h" + +#include "nm-service-providers.h" + +typedef enum { + PARSER_TOPLEVEL = 0, + PARSER_COUNTRY, + PARSER_PROVIDER, + PARSER_METHOD_GSM, + PARSER_METHOD_GSM_APN, + PARSER_METHOD_CDMA, + PARSER_DONE, + PARSER_ERROR +} ParseContextState; + +typedef struct { + char * mccmnc; + NMServiceProvidersGsmApnCallback callback; + gpointer user_data; + GCancellable * cancellable; + GMarkupParseContext * ctx; + char buffer[4096]; + + char * text_buffer; + ParseContextState state; + + gboolean mccmnc_matched; + gboolean found_internet_apn; + char * apn; + char * username; + char * password; + char * gateway; + char * auth_method; + GSList * dns; +} ParseContext; + +/*****************************************************************************/ + +static void +parser_toplevel_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "serviceproviders") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "format") == 0) { + if (strcmp(attribute_values[i], "2.0")) { + g_warning("%s: mobile broadband provider database format '%s'" + " not supported.", + __func__, + attribute_values[i]); + parse_context->state = PARSER_ERROR; + break; + } + } + } + } else if (strcmp(name, "country") == 0) { + parse_context->state = PARSER_COUNTRY; + } +} + +static void +parser_country_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + if (strcmp(name, "provider") == 0) + parse_context->state = PARSER_PROVIDER; +} + +static void +parser_provider_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + parse_context->mccmnc_matched = FALSE; + if (strcmp(name, "gsm") == 0) + parse_context->state = PARSER_METHOD_GSM; + else if (strcmp(name, "cdma") == 0) + parse_context->state = PARSER_METHOD_CDMA; +} + +static void +parser_gsm_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "network-id") == 0) { + const char *mcc = NULL, *mnc = NULL; + + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "mcc") == 0) + mcc = attribute_values[i]; + else if (strcmp(attribute_names[i], "mnc") == 0) + mnc = attribute_values[i]; + if (mcc && strlen(mcc) && mnc && strlen(mnc)) { + char *mccmnc = g_strdup_printf("%s%s", mcc, mnc); + + if (strcmp(mccmnc, parse_context->mccmnc) == 0) + parse_context->mccmnc_matched = TRUE; + g_free(mccmnc); + break; + } + } + } else if (strcmp(name, "apn") == 0) { + parse_context->found_internet_apn = FALSE; + nm_clear_g_free(&parse_context->apn); + nm_clear_g_free(&parse_context->username); + nm_clear_g_free(&parse_context->password); + nm_clear_g_free(&parse_context->gateway); + nm_clear_g_free(&parse_context->auth_method); + g_slist_free_full(parse_context->dns, g_free); + parse_context->dns = NULL; + + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "value") == 0) { + parse_context->state = PARSER_METHOD_GSM_APN; + parse_context->apn = g_strstrip(g_strdup(attribute_values[i])); + break; + } + } + } +} + +static void +parser_gsm_apn_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "usage") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if ((strcmp(attribute_names[i], "type") == 0) + && (strcmp(attribute_values[i], "internet") == 0)) { + parse_context->found_internet_apn = TRUE; + break; + } + } + } else if (strcmp(name, "authentication") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "method") == 0) { + nm_clear_g_free(&parse_context->auth_method); + parse_context->auth_method = g_strstrip(g_strdup(attribute_values[i])); + break; + } + } + } +} + +static void +parser_start_element(GMarkupParseContext *context, + const char * element_name, + const char ** attribute_names, + const char ** attribute_values, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + nm_clear_g_free(&parse_context->text_buffer); + + switch (parse_context->state) { + case PARSER_TOPLEVEL: + parser_toplevel_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_COUNTRY: + parser_country_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_PROVIDER: + parser_provider_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_GSM: + parser_gsm_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_GSM_APN: + parser_gsm_apn_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_CDMA: + break; + case PARSER_ERROR: + break; + case PARSER_DONE: + break; + } +} + +static void +parser_country_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "country") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_TOPLEVEL; + } +} + +static void +parser_provider_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "provider") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_COUNTRY; + } +} + +static void +parser_gsm_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "gsm") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_PROVIDER; + } +} + +static void +parser_gsm_apn_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "username") == 0) { + nm_clear_g_free(&parse_context->username); + parse_context->username = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "password") == 0) { + nm_clear_g_free(&parse_context->password); + parse_context->password = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "dns") == 0) { + parse_context->dns = + g_slist_prepend(parse_context->dns, g_steal_pointer(&parse_context->text_buffer)); + } else if (strcmp(name, "gateway") == 0) { + nm_clear_g_free(&parse_context->gateway); + parse_context->gateway = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "apn") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + + if (parse_context->mccmnc_matched && parse_context->found_internet_apn) + parse_context->state = PARSER_DONE; + else + parse_context->state = PARSER_METHOD_GSM; + } +} + +static void +parser_cdma_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "cdma") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_PROVIDER; + } +} + +static void +parser_end_element(GMarkupParseContext *context, + const char * element_name, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + switch (parse_context->state) { + case PARSER_TOPLEVEL: + break; + case PARSER_COUNTRY: + parser_country_end(parse_context, element_name); + break; + case PARSER_PROVIDER: + parser_provider_end(parse_context, element_name); + break; + case PARSER_METHOD_GSM: + parser_gsm_end(parse_context, element_name); + break; + case PARSER_METHOD_GSM_APN: + parser_gsm_apn_end(parse_context, element_name); + break; + case PARSER_METHOD_CDMA: + parser_cdma_end(parse_context, element_name); + break; + case PARSER_ERROR: + break; + case PARSER_DONE: + break; + } +} + +static void +parser_text(GMarkupParseContext *context, + const char * text, + gsize text_len, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + g_free(parse_context->text_buffer); + parse_context->text_buffer = g_strdup(text); +} + +static const GMarkupParser parser = { + .start_element = parser_start_element, + .end_element = parser_end_element, + .text = parser_text, + .passthrough = NULL, + .error = NULL, +}; + +/*****************************************************************************/ + +static void +finish_parse_context(ParseContext *parse_context, GError *error) +{ + if (parse_context->callback) { + if (error) { + parse_context + ->callback(NULL, NULL, NULL, NULL, NULL, NULL, error, parse_context->user_data); + } else { + parse_context->callback(parse_context->apn, + parse_context->username, + parse_context->password, + parse_context->gateway, + parse_context->auth_method, + parse_context->dns, + error, + parse_context->user_data); + } + } + + g_free(parse_context->mccmnc); + g_markup_parse_context_free(parse_context->ctx); + + g_free(parse_context->text_buffer); + g_free(parse_context->apn); + g_free(parse_context->username); + g_free(parse_context->password); + g_free(parse_context->gateway); + g_free(parse_context->auth_method); + g_slist_free_full(parse_context->dns, g_free); + + g_slice_free(ParseContext, parse_context); +} + +static void read_next_chunk(GInputStream *stream, ParseContext *parse_context); + +static void +stream_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM(source_object); + ParseContext *parse_context = user_data; + gssize len; + GError * error = NULL; + + len = g_input_stream_read_finish(stream, res, &error); + if (len == -1) { + g_prefix_error(&error, "Error reading service provider database: "); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (len == 0) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Operator ID '%s' not found in service provider database", + parse_context->mccmnc); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (!g_markup_parse_context_parse(parse_context->ctx, parse_context->buffer, len, &error)) { + g_prefix_error(&error, "Error parsing service provider database: "); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (parse_context->state == PARSER_DONE) { + finish_parse_context(parse_context, NULL); + return; + } + + read_next_chunk(stream, parse_context); +} + +static void +read_next_chunk(GInputStream *stream, ParseContext *parse_context) +{ + g_input_stream_read_async(stream, + parse_context->buffer, + sizeof(parse_context->buffer), + G_PRIORITY_DEFAULT, + parse_context->cancellable, + stream_read_cb, + parse_context); +} + +static void +file_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GFile * file = G_FILE(source_object); + ParseContext * parse_context = user_data; + GFileInputStream *stream; + gs_free_error GError *error = NULL; + + stream = g_file_read_finish(file, res, &error); + if (!stream) { + g_prefix_error(&error, "Error opening service provider database: "); + finish_parse_context(parse_context, error); + return; + } + + read_next_chunk(G_INPUT_STREAM(stream), parse_context); + + g_object_unref(stream); +} + +/*****************************************************************************/ + +void +nm_service_providers_find_gsm_apn(const char * service_providers, + const char * mccmnc, + GCancellable * cancellable, + NMServiceProvidersGsmApnCallback callback, + gpointer user_data) +{ + GFile * file; + ParseContext *parse_context; + + parse_context = g_slice_new0(ParseContext); + parse_context->mccmnc = g_strdup(mccmnc); + parse_context->cancellable = cancellable; + parse_context->callback = callback; + parse_context->user_data = user_data; + parse_context->ctx = g_markup_parse_context_new(&parser, 0, parse_context, NULL); + + file = g_file_new_for_path(service_providers); + + g_file_read_async(file, G_PRIORITY_DEFAULT, cancellable, file_read_cb, parse_context); + + g_object_unref(file); +} diff --git a/src/core/devices/wwan/nm-service-providers.h b/src/core/devices/wwan/nm-service-providers.h new file mode 100644 index 0000000..959f660 --- /dev/null +++ b/src/core/devices/wwan/nm-service-providers.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SERVICE_PROVIDERS_H__ +#define __NETWORKMANAGER_SERVICE_PROVIDERS_H__ + +typedef void (*NMServiceProvidersGsmApnCallback)(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data); + +void nm_service_providers_find_gsm_apn(const char * service_providers, + const char * mccmnc, + GCancellable * cancellable, + NMServiceProvidersGsmApnCallback callback, + gpointer user_data); + +#endif /* __NETWORKMANAGER_SERVICE_PROVIDERS_H__ */ diff --git a/src/core/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c new file mode 100644 index 0000000..7418d7a --- /dev/null +++ b/src/core/devices/wwan/nm-wwan-factory.c @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "devices/nm-device-factory.h" +#include "nm-setting-gsm.h" +#include "nm-setting-cdma.h" +#include "nm-modem-manager.h" +#include "nm-device-modem.h" +#include "platform/nm-platform.h" + +/*****************************************************************************/ + +#define NM_TYPE_WWAN_FACTORY (nm_wwan_factory_get_type()) +#define NM_WWAN_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory)) +#define NM_WWAN_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WWAN_FACTORY, NMWwanFactoryClass)) +#define NM_IS_WWAN_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WWAN_FACTORY)) +#define NM_IS_WWAN_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WWAN_FACTORY)) +#define NM_WWAN_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactoryClass)) + +typedef struct { + NMModemManager *mm; +} NMWwanFactoryPrivate; + +typedef struct { + NMDeviceFactory parent; + NMWwanFactoryPrivate _priv; +} NMWwanFactory; + +typedef struct { + NMDeviceFactoryClass parent; +} NMWwanFactoryClass; + +static GType nm_wwan_factory_get_type(void); + +G_DEFINE_TYPE(NMWwanFactory, nm_wwan_factory, NM_TYPE_DEVICE_FACTORY) + +#define NM_WWAN_FACTORY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMWwanFactory, NM_IS_WWAN_FACTORY) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES( + NM_LINK_TYPE_WWAN_NET) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CDMA_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + return g_object_new(NM_TYPE_WWAN_FACTORY, NULL); +} + +/*****************************************************************************/ + +static void +modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(user_data); + gs_unref_object NMDevice *device = NULL; + const char * driver; + + if (nm_modem_is_claimed(modem)) + return; + + driver = nm_modem_get_driver(modem); + + /* If it was a Bluetooth modem and no bluetooth device claimed it, ignore + * it. The rfcomm port (and thus the modem) gets created automatically + * by the Bluetooth code during the connection process. + */ + if (driver && strstr(driver, "bluetooth")) { + nm_log_dbg( + LOGD_MB, + "WWAN factory ignores bluetooth modem '%s' which should be handled by bluetooth plugin", + nm_modem_get_control_port(modem)); + return; + } + + /* Make the new modem device */ + device = nm_device_modem_new(modem); + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); +} + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + g_return_val_if_fail(plink, NULL); + g_return_val_if_fail(plink->type == NM_LINK_TYPE_WWAN_NET, NULL); + *out_ignore = TRUE; + return NULL; +} + +static void +start(NMDeviceFactory *factory) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(factory); + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self); + + priv->mm = g_object_ref(nm_modem_manager_get()); + + g_signal_connect(priv->mm, NM_MODEM_MANAGER_MODEM_ADDED, G_CALLBACK(modem_added_cb), self); +} + +/*****************************************************************************/ + +static void +nm_wwan_factory_init(NMWwanFactory *self) +{} + +static void +dispose(GObject *object) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(object); + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self); + + if (priv->mm) + g_signal_handlers_disconnect_by_func(priv->mm, modem_added_cb, self); + g_clear_object(&priv->mm); + + /* Chain up to the parent class */ + G_OBJECT_CLASS(nm_wwan_factory_parent_class)->dispose(object); +} + +static void +nm_wwan_factory_class_init(NMWwanFactoryClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + object_class->dispose = dispose; + + factory_class->get_supported_types = get_supported_types; + factory_class->create_device = create_device; + factory_class->start = start; +} diff --git a/src/core/devices/wwan/tests/test-service-providers.c b/src/core/devices/wwan/tests/test-service-providers.c new file mode 100644 index 0000000..2c54a76 --- /dev/null +++ b/src/core/devices/wwan/tests/test-service-providers.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat + */ + +#include "nm-default.h" + +#include "nm-service-providers.h" + +#include "nm-test-utils-core.h" + +static void +test_positive_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_no_error(error); + g_assert_cmpstr(apn, ==, "gprs.example.com"); + g_assert_cmpstr(username, ==, "praise"); + g_assert_cmpstr(password, ==, "santa"); + g_assert_cmpstr(gateway, ==, "192.0.2.3"); + g_assert_cmpstr(auth_method, ==, "pap"); + + g_assert_nonnull(dns); + g_assert_cmpstr(dns->data, ==, "192.0.2.2"); + dns = dns->next; + g_assert_nonnull(dns); + g_assert_cmpstr(dns->data, ==, "192.0.2.1"); + g_assert_null(dns->next); +} + +static void +test_positive(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR + "/src/core/devices/wwan/tests/test-service-providers.xml", + "13337", + NULL, + test_positive_cb, + loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static void +test_negative_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN); +} + +static void +test_negative(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR + "/src/core/devices/wwan/tests/test-service-providers.xml", + "78130", + NULL, + test_negative_cb, + loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static void +test_nonexistent_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_AGAIN); +} + +static void +test_nonexistent(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn("nonexistent.xml", "13337", NULL, test_nonexistent_cb, loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/service-providers/positive", test_positive); + g_test_add_func("/service-providers/negative", test_negative); + g_test_add_func("/service-providers/nonexistent", test_nonexistent); + + return g_test_run(); +} diff --git a/src/core/devices/wwan/tests/test-service-providers.xml b/src/core/devices/wwan/tests/test-service-providers.xml new file mode 100644 index 0000000..f0ca2de --- /dev/null +++ b/src/core/devices/wwan/tests/test-service-providers.xml @@ -0,0 +1,73 @@ + + + + + + + + Sophia + + + + + + APN + 192.0.2.1 + 192.0.2.2 + + + + + + + + Demiurge + + + + + + Unsolicited Nudes MMS + mms + mms + http://mms.example.com/ + 192.0.2.1:8080 + + + + + GPRS + praise + santa + 192.0.2.1 + 192.0.2.2 + 192.0.2.3 + + + + + + Second + worship + doom + + + + + + Personal + + + + + + APN + 192.0.2.1 + 192.0.2.2 + + + + + + + diff --git a/src/core/dhcp/meson.build b/src/core/dhcp/meson.build new file mode 100644 index 0000000..ea0d056 --- /dev/null +++ b/src/core/dhcp/meson.build @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +name = 'nm-dhcp-helper' + +c_flags = [ + '-DG_LOG_DOMAIN="@0@"'.format(name), + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB', +] + +executable( + name, + name + '.c', + dependencies: glib_nm_default_dep, + c_args: c_flags, + link_args: ldflags_linker_script_binary, + link_depends: linker_script_binary, + install: true, + install_dir: nm_libexecdir, +) + +if enable_tests + subdir('tests') +endif diff --git a/src/core/dhcp/nm-dhcp-client-logging.h b/src/core/dhcp/nm-dhcp-client-logging.h new file mode 100644 index 0000000..d69b3eb --- /dev/null +++ b/src/core/dhcp/nm-dhcp-client-logging.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__ +#define __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__ + +#include "nm-dhcp-client.h" + +static inline NMLogDomain +_nm_dhcp_client_get_domain(NMDhcpClient *self) +{ + if (self) { + switch (nm_dhcp_client_get_addr_family(self)) { + case AF_INET: + return LOGD_DHCP4; + case AF_INET6: + return LOGD_DHCP6; + default: + nm_assert_not_reached(); + break; + } + } + return LOGD_DHCP; +} + +#define _NMLOG_PREFIX_NAME "dhcp" +#define _NMLOG_DOMAIN LOGD_DHCP +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + /* we check first for LOGD_DHCP instead of the correct domain. + * In the worst case, we guess wrong and enter the block. + * + * Same for the _NMLOG_ENABLED() macro. Probably it would be more + * expensive to determine the correct value then what we could + * safe. */ \ + if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ + NMDhcpClient * _self = (NMDhcpClient *) (self); \ + const char * __ifname = _self ? nm_dhcp_client_get_iface(_self) : NULL; \ + const NMLogDomain _domain = _nm_dhcp_client_get_domain(_self); \ + \ + nm_log(_level, \ + _domain, \ + __ifname, \ + NULL, \ + "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \ + NM_PRINT_FMT_QUOTED(__ifname, " (", __ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, domain, ifname, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + /* we check first for LOGD_DHCP instead of the correct domain. + * In the worst case, we guess wrong and enter the block. + * + * Same for the _NMLOG_ENABLED() macro. Probably it would be more + * expensive to determine the correct value then what we could + * safe. */ \ + if (nm_logging_enabled(_level, _domain)) { \ + const char *__ifname = (ifname); \ + \ + nm_log(_level, \ + _domain, \ + __ifname, \ + NULL, \ + "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \ + NM_PRINT_FMT_QUOTED(__ifname, " (", __ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#endif /* __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__ */ diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c new file mode 100644 index 0000000..3ee8510 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-client.c @@ -0,0 +1,1355 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-client.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-glib-aux/nm-random-utils.h" + +#include "NetworkManagerUtils.h" +#include "nm-utils.h" +#include "nm-dhcp-utils.h" +#include "platform/nm-platform.h" + +#include "nm-dhcp-client-logging.h" + +/*****************************************************************************/ + +enum { SIGNAL_STATE_CHANGED, SIGNAL_PREFIX_DELEGATED, LAST_SIGNAL }; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDhcpClient, + PROP_ADDR_FAMILY, + PROP_FLAGS, + PROP_HWADDR, + PROP_BROADCAST_HWADDR, + PROP_IFACE, + PROP_IFINDEX, + PROP_MULTI_IDX, + PROP_ROUTE_METRIC, + PROP_ROUTE_TABLE, + PROP_TIMEOUT, + PROP_UUID, + PROP_IAID, + PROP_IAID_EXPLICIT, + PROP_HOSTNAME, + PROP_HOSTNAME_FLAGS, + PROP_MUD_URL, + PROP_VENDOR_CLASS_IDENTIFIER, + PROP_REJECT_SERVERS, ); + +typedef struct _NMDhcpClientPrivate { + NMDedupMultiIndex * multi_idx; + char * iface; + GBytes * hwaddr; + GBytes * bcast_hwaddr; + char * uuid; + GBytes * client_id; + char * hostname; + const char ** reject_servers; + char * mud_url; + GBytes * vendor_class_identifier; + pid_t pid; + guint timeout_id; + guint watch_id; + int addr_family; + int ifindex; + guint32 route_table; + guint32 route_metric; + guint32 timeout; + guint32 iaid; + NMDhcpState state; + NMDhcpHostnameFlags hostname_flags; + bool info_only : 1; + bool use_fqdn : 1; + bool iaid_explicit : 1; +} NMDhcpClientPrivate; + +G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT) + +#define NM_DHCP_CLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDhcpClient, NM_IS_DHCP_CLIENT) + +/*****************************************************************************/ + +pid_t +nm_dhcp_client_get_pid(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->pid; +} + +NMDedupMultiIndex * +nm_dhcp_client_get_multi_idx(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->multi_idx; +} + +const char * +nm_dhcp_client_get_iface(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->iface; +} + +int +nm_dhcp_client_get_ifindex(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->ifindex; +} + +int +nm_dhcp_client_get_addr_family(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), AF_UNSPEC); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->addr_family; +} + +const char * +nm_dhcp_client_get_uuid(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->uuid; +} + +GBytes * +nm_dhcp_client_get_hw_addr(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->hwaddr; +} + +GBytes * +nm_dhcp_client_get_broadcast_hw_addr(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->bcast_hwaddr; +} + +guint32 +nm_dhcp_client_get_route_table(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), RT_TABLE_MAIN); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->route_table; +} + +void +nm_dhcp_client_set_route_table(NMDhcpClient *self, guint32 route_table) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + if (route_table != priv->route_table) { + priv->route_table = route_table; + _notify(self, PROP_ROUTE_TABLE); + } +} + +guint32 +nm_dhcp_client_get_route_metric(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), G_MAXUINT32); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->route_metric; +} + +void +nm_dhcp_client_set_route_metric(NMDhcpClient *self, guint32 route_metric) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + if (route_metric != priv->route_metric) { + priv->route_metric = route_metric; + _notify(self, PROP_ROUTE_METRIC); + } +} + +guint32 +nm_dhcp_client_get_timeout(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), 0); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->timeout; +} + +guint32 +nm_dhcp_client_get_iaid(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), 0); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->iaid; +} + +gboolean +nm_dhcp_client_get_iaid_explicit(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->iaid_explicit; +} + +GBytes * +nm_dhcp_client_get_client_id(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->client_id; +} + +static void +_set_client_id(NMDhcpClient *self, GBytes *client_id, gboolean take) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + nm_assert(!client_id || g_bytes_get_size(client_id) >= 2); + + if (priv->client_id == client_id + || (priv->client_id && client_id && g_bytes_equal(priv->client_id, client_id))) { + if (take && client_id) + g_bytes_unref(client_id); + return; + } + + if (priv->client_id) + g_bytes_unref(priv->client_id); + priv->client_id = client_id; + if (!take && client_id) + g_bytes_ref(client_id); + + { + gs_free char *s = NULL; + + _LOGT("%s: set %s", + nm_dhcp_client_get_addr_family(self) == AF_INET6 ? "duid" : "client-id", + priv->client_id ? (s = nm_dhcp_utils_duid_to_string(priv->client_id)) : "default"); + } +} + +void +nm_dhcp_client_set_client_id(NMDhcpClient *self, GBytes *client_id) +{ + g_return_if_fail(NM_IS_DHCP_CLIENT(self)); + g_return_if_fail(!client_id || g_bytes_get_size(client_id) >= 2); + + _set_client_id(self, client_id, FALSE); +} + +void +nm_dhcp_client_set_client_id_bin(NMDhcpClient *self, + guint8 type, + const guint8 *client_id, + gsize len) +{ + guint8 *buf; + GBytes *b; + + g_return_if_fail(NM_IS_DHCP_CLIENT(self)); + g_return_if_fail(client_id); + g_return_if_fail(len > 0); + + buf = g_malloc(len + 1); + buf[0] = type; + memcpy(buf + 1, client_id, len); + b = g_bytes_new_take(buf, len + 1); + _set_client_id(self, b, TRUE); +} + +const char * +nm_dhcp_client_get_hostname(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->hostname; +} + +NMDhcpHostnameFlags +nm_dhcp_client_get_hostname_flags(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NM_DHCP_HOSTNAME_FLAG_NONE); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->hostname_flags; +} + +gboolean +nm_dhcp_client_get_info_only(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->info_only; +} + +gboolean +nm_dhcp_client_get_use_fqdn(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->use_fqdn; +} + +const char * +nm_dhcp_client_get_mud_url(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->mud_url; +} + +GBytes * +nm_dhcp_client_get_vendor_class_identifier(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE(self)->vendor_class_identifier; +} + +const char *const * +nm_dhcp_client_get_reject_servers(NMDhcpClient *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL); + + return (const char *const *) NM_DHCP_CLIENT_GET_PRIVATE(self)->reject_servers; +} + +/*****************************************************************************/ + +static const char *state_table[NM_DHCP_STATE_MAX + 1] = { + [NM_DHCP_STATE_UNKNOWN] = "unknown", + [NM_DHCP_STATE_BOUND] = "bound", + [NM_DHCP_STATE_EXTENDED] = "extended", + [NM_DHCP_STATE_TIMEOUT] = "timeout", + [NM_DHCP_STATE_EXPIRE] = "expire", + [NM_DHCP_STATE_DONE] = "done", + [NM_DHCP_STATE_FAIL] = "fail", + [NM_DHCP_STATE_TERMINATED] = "terminated", +}; + +static const char * +state_to_string(NMDhcpState state) +{ + if ((gsize) state < G_N_ELEMENTS(state_table)) + return state_table[state]; + return NULL; +} + +static NMDhcpState +reason_to_state(NMDhcpClient *self, const char *iface, const char *reason) +{ + if (g_ascii_strcasecmp(reason, "bound") == 0 || g_ascii_strcasecmp(reason, "bound6") == 0) + return NM_DHCP_STATE_BOUND; + else if (g_ascii_strcasecmp(reason, "renew") == 0 || g_ascii_strcasecmp(reason, "renew6") == 0 + || g_ascii_strcasecmp(reason, "reboot") == 0 + || g_ascii_strcasecmp(reason, "rebind") == 0 + || g_ascii_strcasecmp(reason, "rebind6") == 0) + return NM_DHCP_STATE_EXTENDED; + else if (g_ascii_strcasecmp(reason, "timeout") == 0) + return NM_DHCP_STATE_TIMEOUT; + else if (g_ascii_strcasecmp(reason, "nak") == 0 || g_ascii_strcasecmp(reason, "expire") == 0 + || g_ascii_strcasecmp(reason, "expire6") == 0) + return NM_DHCP_STATE_EXPIRE; + else if (g_ascii_strcasecmp(reason, "end") == 0 || g_ascii_strcasecmp(reason, "stop") == 0 + || g_ascii_strcasecmp(reason, "stopped") == 0) + return NM_DHCP_STATE_DONE; + else if (g_ascii_strcasecmp(reason, "fail") == 0 || g_ascii_strcasecmp(reason, "abend") == 0) + return NM_DHCP_STATE_FAIL; + else if (g_ascii_strcasecmp(reason, "preinit") == 0) + return NM_DHCP_STATE_NOOP; + + _LOGD("unmapped DHCP state '%s'", reason); + return NM_DHCP_STATE_UNKNOWN; +} + +/*****************************************************************************/ + +static void +timeout_cleanup(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + nm_clear_g_source(&priv->timeout_id); +} + +static void +watch_cleanup(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + nm_clear_g_source(&priv->watch_id); +} + +void +nm_dhcp_client_stop_pid(pid_t pid, const char *iface) +{ + char *name = iface ? g_strdup_printf("dhcp-client-%s", iface) : NULL; + + g_return_if_fail(pid > 1); + + nm_utils_kill_child_sync(pid, + SIGTERM, + LOGD_DHCP, + name ?: "dhcp-client", + NULL, + 1000 / 2, + 1000 / 20); + g_free(name); +} + +static void +stop(NMDhcpClient *self, gboolean release) +{ + NMDhcpClientPrivate *priv; + + g_return_if_fail(NM_IS_DHCP_CLIENT(self)); + + priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + if (priv->pid > 0) { + /* Clean up the watch handler since we're explicitly killing the daemon */ + watch_cleanup(self); + nm_dhcp_client_stop_pid(priv->pid, priv->iface); + } + priv->pid = -1; +} + +void +nm_dhcp_client_set_state(NMDhcpClient *self, + NMDhcpState new_state, + NMIPConfig * ip_config, + GHashTable * options) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + gs_free char * event_id = NULL; + + if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) { + g_return_if_fail(NM_IS_IP_CONFIG_ADDR_FAMILY(ip_config, priv->addr_family)); + g_return_if_fail(options); + } else { + g_return_if_fail(!ip_config); + g_return_if_fail(!options); + } + + if (new_state >= NM_DHCP_STATE_BOUND) + timeout_cleanup(self); + if (new_state >= NM_DHCP_STATE_TIMEOUT) + watch_cleanup(self); + + /* The client may send same-state transitions for RENEW/REBIND events and + * the lease may have changed, so handle same-state transitions for the + * EXTENDED and BOUND states. Ignore same-state transitions for other + * events since the lease won't have changed and the state was already handled. + */ + if ((priv->state == new_state) + && !NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) + return; + + if (_LOGI_ENABLED()) { + gs_free const char **keys = NULL; + guint i, nkeys; + + keys = nm_utils_strdict_get_keys(options, TRUE, &nkeys); + for (i = 0; i < nkeys; i++) { + _LOGI("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i])); + } + } + + if (priv->addr_family == AF_INET6) + event_id = nm_dhcp_utils_get_dhcp6_event_id(options); + + _LOGI("state changed %s -> %s%s%s%s", + state_to_string(priv->state), + state_to_string(new_state), + NM_PRINT_FMT_QUOTED(event_id, ", event ID=\"", event_id, "\"", "")); + + priv->state = new_state; + g_signal_emit(G_OBJECT(self), signals[SIGNAL_STATE_CHANGED], 0, new_state, ip_config, options); +} + +static gboolean +transaction_timeout(gpointer user_data) +{ + NMDhcpClient * self = NM_DHCP_CLIENT(user_data); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + priv->timeout_id = 0; + _LOGW("request timed out"); + nm_dhcp_client_set_state(self, NM_DHCP_STATE_TIMEOUT, NULL, NULL); + return G_SOURCE_REMOVE; +} + +static void +daemon_watch_cb(GPid pid, int status, gpointer user_data) +{ + NMDhcpClient * self = NM_DHCP_CLIENT(user_data); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + g_return_if_fail(priv->watch_id); + priv->watch_id = 0; + + if (WIFEXITED(status)) + _LOGI("client pid %d exited with status %d", pid, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + _LOGI("client pid %d killed by signal %d", pid, WTERMSIG(status)); + else if (WIFSTOPPED(status)) + _LOGI("client pid %d stopped by signal %d", pid, WSTOPSIG(status)); + else if (WIFCONTINUED(status)) + _LOGI("client pid %d resumed (by SIGCONT)", pid); + else + _LOGW("client died abnormally"); + + priv->pid = -1; + + nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL, NULL); +} + +void +nm_dhcp_client_start_timeout(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + g_return_if_fail(priv->timeout_id == 0); + + /* Set up a timeout on the transaction to kill it after the timeout */ + + if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY) + return; + + priv->timeout_id = g_timeout_add_seconds(priv->timeout, transaction_timeout, self); +} + +void +nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + g_return_if_fail(priv->pid == -1); + priv->pid = pid; + + nm_dhcp_client_start_timeout(self); + + g_return_if_fail(priv->watch_id == 0); + priv->watch_id = g_child_watch_add(pid, daemon_watch_cb, self); +} + +void +nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + g_return_if_fail(priv->pid == pid); + priv->pid = -1; + + watch_cleanup(self); + timeout_cleanup(self); +} + +gboolean +nm_dhcp_client_start_ip4(NMDhcpClient *self, + GBytes * client_id, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + NMDhcpClientPrivate *priv; + + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + g_return_val_if_fail(priv->pid == -1, FALSE); + g_return_val_if_fail(priv->addr_family == AF_INET, FALSE); + g_return_val_if_fail(priv->uuid != NULL, FALSE); + + if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY) + _LOGI("activation: beginning transaction (no timeout)"); + else + _LOGI("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout); + + nm_dhcp_client_set_client_id(self, client_id); + + return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, + dhcp_anycast_addr, + last_ip4_address, + error); +} + +gboolean +nm_dhcp_client_accept(NMDhcpClient *self, GError **error) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + if (NM_DHCP_CLIENT_GET_CLASS(self)->accept) { + return NM_DHCP_CLIENT_GET_CLASS(self)->accept(self, error); + } + + return TRUE; +} + +gboolean +nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error) +{ + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + + if (NM_DHCP_CLIENT_GET_CLASS(self)->decline) { + return NM_DHCP_CLIENT_GET_CLASS(self)->decline(self, error_message, error); + } + + return TRUE; +} + +static GBytes * +get_duid(NMDhcpClient *self) +{ + return NULL; +} + +gboolean +nm_dhcp_client_start_ip6(NMDhcpClient * self, + GBytes * client_id, + gboolean enforce_duid, + const char * dhcp_anycast_addr, + const struct in6_addr * ll_addr, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error) +{ + NMDhcpClientPrivate *priv; + gs_unref_bytes GBytes *own_client_id = NULL; + + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + g_return_val_if_fail(client_id, FALSE); + + priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + g_return_val_if_fail(priv->pid == -1, FALSE); + g_return_val_if_fail(priv->addr_family == AF_INET6, FALSE); + g_return_val_if_fail(priv->uuid != NULL, FALSE); + g_return_val_if_fail(!priv->client_id, FALSE); + + if (!enforce_duid) + own_client_id = NM_DHCP_CLIENT_GET_CLASS(self)->get_duid(self); + + _set_client_id(self, own_client_id ?: client_id, FALSE); + + if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY) + _LOGI("activation: beginning transaction (no timeout)"); + else + _LOGI("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout); + + return NM_DHCP_CLIENT_GET_CLASS(self) + ->ip6_start(self, dhcp_anycast_addr, ll_addr, privacy, needed_prefixes, error); +} + +void +nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name) +{ + guint64 start_time; + pid_t pid, ppid; + const char * exe; + char proc_path[NM_STRLEN("/proc/%lu/cmdline") + 100]; + gs_free char *pid_contents = NULL, *proc_contents = NULL; + + /* Check for an existing instance and stop it */ + if (!g_file_get_contents(pid_file, &pid_contents, NULL, NULL)) + return; + + pid = _nm_utils_ascii_str_to_int64(pid_contents, 10, 1, G_MAXINT64, 0); + if (pid <= 0) + goto out; + + start_time = nm_utils_get_start_time_for_pid(pid, NULL, &ppid); + if (start_time == 0) + goto out; + + nm_sprintf_buf(proc_path, "/proc/%lu/cmdline", (unsigned long) pid); + if (!g_file_get_contents(proc_path, &proc_contents, NULL, NULL)) + goto out; + + exe = strrchr(proc_contents, '/'); + if (exe) + exe++; + else + exe = proc_contents; + if (!nm_streq0(exe, binary_name)) + goto out; + + if (ppid == getpid()) { + /* the process is our own child. */ + nm_utils_kill_child_sync(pid, SIGTERM, LOGD_DHCP, "dhcp-client", NULL, 1000 / 2, 1000 / 20); + } else { + nm_utils_kill_process_sync(pid, + start_time, + SIGTERM, + LOGD_DHCP, + "dhcp-client", + 1000 / 2, + 1000 / 20, + 2000); + } + +out: + if (remove(pid_file) == -1) { + int errsv = errno; + + nm_log_dbg(LOGD_DHCP, + "dhcp: could not remove pid file \"%s\": %s (%d)", + pid_file, + nm_strerror_native(errsv), + errsv); + } +} + +void +nm_dhcp_client_stop(NMDhcpClient *self, gboolean release) +{ + NMDhcpClientPrivate *priv; + pid_t old_pid = 0; + + g_return_if_fail(NM_IS_DHCP_CLIENT(self)); + + priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + /* Kill the DHCP client */ + old_pid = priv->pid; + NM_DHCP_CLIENT_GET_CLASS(self)->stop(self, release); + if (old_pid > 0) + _LOGI("canceled DHCP transaction, DHCP client pid %d", old_pid); + else + _LOGI("canceled DHCP transaction"); + nm_assert(priv->pid == -1); + + nm_dhcp_client_set_state(self, NM_DHCP_STATE_DONE, NULL, NULL); +} + +/*****************************************************************************/ + +static char * +bytearray_variant_to_string(NMDhcpClient *self, GVariant *value, const char *key) +{ + const guint8 *array; + gsize length; + GString * str; + int i; + unsigned char c; + char * converted = NULL; + + g_return_val_if_fail(value != NULL, NULL); + + array = g_variant_get_fixed_array(value, &length, 1); + + /* Since the DHCP options come through environment variables, they should + * already be UTF-8 safe, but just make sure. + */ + str = g_string_sized_new(length); + for (i = 0; i < length; i++) { + c = array[i]; + + /* Convert NULLs to spaces and non-ASCII characters to ? */ + if (c == '\0') + c = ' '; + else if (c > 127) + c = '?'; + str = g_string_append_c(str, c); + } + str = g_string_append_c(str, '\0'); + + converted = str->str; + if (!g_utf8_validate(converted, -1, NULL)) + _LOGW("option '%s' couldn't be converted to UTF-8", key); + g_string_free(str, FALSE); + return converted; +} + +static int +label_is_unknown_xyz(const char *label) +{ + if (!NM_STR_HAS_PREFIX(label, "unknown_")) + return -EINVAL; + + label += NM_STRLEN("unknown_"); + if (label[0] != '2' || !g_ascii_isdigit(label[1]) || !g_ascii_isdigit(label[2]) + || label[3] != '\0') + return -EINVAL; + + return _nm_utils_ascii_str_to_int64(label, 10, 224, 254, -EINVAL); +} + +#define OLD_TAG "old_" +#define NEW_TAG "new_" + +static void +maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant *value) +{ + char *str_value = NULL; + + g_return_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING)); + + if (g_str_has_prefix(key, OLD_TAG)) + return; + + /* Filter out stuff that's not actually new DHCP options */ + if (NM_IN_STRSET(key, "interface", "pid", "reason", "dhcp_message_type")) + return; + + if (NM_STR_HAS_PREFIX(key, NEW_TAG)) + key += NM_STRLEN(NEW_TAG); + if (NM_STR_HAS_PREFIX(key, "private_") || !key[0]) + return; + + str_value = bytearray_variant_to_string(self, value, key); + if (str_value) { + int priv_opt_num; + + g_hash_table_insert(hash, g_strdup(key), str_value); + + /* dhclient has no special labels for private dhcp options: it uses "unknown_xyz" + * labels for that. We need to identify those to alias them to our "private_xyz" + * format unused in the internal dchp plugins. + */ + if ((priv_opt_num = label_is_unknown_xyz(key)) > 0) { + gs_free guint8 *check_val = NULL; + char * hex_str = NULL; + gsize len; + + /* dhclient passes values from dhcp private options in its own "string" format: + * if the raw values are printable as ascii strings, it will pass the string + * representation; if the values are not printable as an ascii string, it will + * pass a string displaying the hex values (hex string). Try to enforce passing + * always an hex string, converting string representation if needed. + */ + check_val = nm_utils_hexstr2bin_alloc(str_value, FALSE, TRUE, ":", 0, &len); + hex_str = nm_utils_bin2hexstr_full(check_val ?: (guint8 *) str_value, + check_val ? len : strlen(str_value), + ':', + FALSE, + NULL); + g_hash_table_insert(hash, g_strdup_printf("private_%d", priv_opt_num), hex_str); + } + } +} + +void +nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient *self, const NMPlatformIP6Address *prefix) +{ + g_signal_emit(G_OBJECT(self), signals[SIGNAL_PREFIX_DELEGATED], 0, prefix); +} + +gboolean +nm_dhcp_client_handle_event(gpointer unused, + const char * iface, + int pid, + GVariant * options, + const char * reason, + NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv; + guint32 old_state; + guint32 new_state; + gs_unref_hashtable GHashTable *str_options = NULL; + gs_unref_object NMIPConfig *ip_config = NULL; + NMPlatformIP6Address prefix = { + 0, + }; + + g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + g_return_val_if_fail(iface != NULL, FALSE); + g_return_val_if_fail(pid > 0, FALSE); + g_return_val_if_fail(g_variant_is_of_type(options, G_VARIANT_TYPE_VARDICT), FALSE); + g_return_val_if_fail(reason != NULL, FALSE); + + priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + if (g_strcmp0(priv->iface, iface) != 0) + return FALSE; + if (priv->pid != pid) + return FALSE; + + old_state = priv->state; + new_state = reason_to_state(self, priv->iface, reason); + _LOGD("DHCP state '%s' -> '%s' (reason: '%s')", + state_to_string(old_state), + state_to_string(new_state), + reason); + + if (new_state == NM_DHCP_STATE_NOOP) + return TRUE; + + if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) { + GVariantIter iter; + const char * name; + GVariant * value; + + /* Copy options */ + str_options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + g_variant_iter_init(&iter, options); + while (g_variant_iter_next(&iter, "{&sv}", &name, &value)) { + maybe_add_option(self, str_options, name, value); + g_variant_unref(value); + } + + /* Create the IP config */ + if (g_hash_table_size(str_options) > 0) { + if (priv->addr_family == AF_INET) { + ip_config = NM_IP_CONFIG_CAST( + nm_dhcp_utils_ip4_config_from_options(nm_dhcp_client_get_multi_idx(self), + priv->ifindex, + priv->iface, + str_options, + priv->route_table, + priv->route_metric)); + } else { + prefix = nm_dhcp_utils_ip6_prefix_from_options(str_options); + ip_config = NM_IP_CONFIG_CAST( + nm_dhcp_utils_ip6_config_from_options(nm_dhcp_client_get_multi_idx(self), + priv->ifindex, + priv->iface, + str_options, + priv->info_only)); + } + } else + g_warn_if_reached(); + } + + if (!IN6_IS_ADDR_UNSPECIFIED(&prefix.address)) { + /* If we got an IPv6 prefix to delegate, we don't change the state + * of the DHCP client instance. Instead, we just signal the prefix + * to the device. */ + nm_dhcp_client_emit_ipv6_prefix_delegated(self, &prefix); + } else { + /* Fail if no valid IP config was received */ + if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED) && !ip_config) { + _LOGW("client bound but IP config not received"); + new_state = NM_DHCP_STATE_FAIL; + nm_clear_pointer(&str_options, g_hash_table_unref); + } + + nm_dhcp_client_set_state(self, new_state, ip_config, str_options); + } + + return TRUE; +} + +gboolean +nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + in_addr_t addr4 = *(in_addr_t *) addr; + guint i; + + /* IPv6 not implemented yet */ + nm_assert(priv->addr_family == AF_INET); + + if (!priv->reject_servers || !priv->reject_servers[0]) + return FALSE; + + for (i = 0; priv->reject_servers[i]; i++) { + in_addr_t r_addr; + in_addr_t mask; + int r_prefix; + + if (!nm_utils_parse_inaddr_prefix_bin(AF_INET, + priv->reject_servers[i], + NULL, + &r_addr, + &r_prefix)) + nm_assert_not_reached(); + mask = _nm_utils_ip4_prefix_to_netmask(r_prefix < 0 ? 32 : r_prefix); + if ((addr4 & mask) == (r_addr & mask)) + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_IFACE: + g_value_set_string(value, priv->iface); + break; + case PROP_IFINDEX: + g_value_set_int(value, priv->ifindex); + break; + case PROP_HWADDR: + g_value_set_boxed(value, priv->hwaddr); + break; + case PROP_BROADCAST_HWADDR: + g_value_set_boxed(value, priv->bcast_hwaddr); + break; + case PROP_ADDR_FAMILY: + g_value_set_int(value, priv->addr_family); + break; + case PROP_UUID: + g_value_set_string(value, priv->uuid); + break; + case PROP_IAID: + g_value_set_uint(value, priv->iaid); + break; + case PROP_IAID_EXPLICIT: + g_value_set_boolean(value, priv->iaid_explicit); + break; + case PROP_HOSTNAME: + g_value_set_string(value, priv->hostname); + break; + case PROP_ROUTE_METRIC: + g_value_set_uint(value, priv->route_metric); + break; + case PROP_ROUTE_TABLE: + g_value_set_uint(value, priv->route_table); + break; + case PROP_TIMEOUT: + g_value_set_uint(value, priv->timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(object); + guint flags; + + switch (prop_id) { + case PROP_FLAGS: + /* construct-only */ + flags = g_value_get_uint(value); + nm_assert( + (flags & ~((guint)(NM_DHCP_CLIENT_FLAGS_INFO_ONLY | NM_DHCP_CLIENT_FLAGS_USE_FQDN))) + == 0); + priv->info_only = NM_FLAGS_HAS(flags, NM_DHCP_CLIENT_FLAGS_INFO_ONLY); + priv->use_fqdn = NM_FLAGS_HAS(flags, NM_DHCP_CLIENT_FLAGS_USE_FQDN); + break; + case PROP_MULTI_IDX: + /* construct-only */ + priv->multi_idx = g_value_get_pointer(value); + if (!priv->multi_idx) + g_return_if_reached(); + nm_dedup_multi_index_ref(priv->multi_idx); + break; + case PROP_IFACE: + /* construct-only */ + priv->iface = g_value_dup_string(value); + g_return_if_fail(priv->iface); + nm_assert(nm_utils_ifname_valid_kernel(priv->iface, NULL)); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + g_return_if_fail(priv->ifindex > 0); + break; + case PROP_HWADDR: + /* construct-only */ + priv->hwaddr = g_value_dup_boxed(value); + break; + case PROP_BROADCAST_HWADDR: + /* construct-only */ + priv->bcast_hwaddr = g_value_dup_boxed(value); + break; + case PROP_ADDR_FAMILY: + /* construct-only */ + priv->addr_family = g_value_get_int(value); + if (!NM_IN_SET(priv->addr_family, AF_INET, AF_INET6)) + g_return_if_reached(); + break; + case PROP_UUID: + /* construct-only */ + priv->uuid = g_value_dup_string(value); + break; + case PROP_IAID: + /* construct-only */ + priv->iaid = g_value_get_uint(value); + break; + case PROP_IAID_EXPLICIT: + /* construct-only */ + priv->iaid_explicit = g_value_get_boolean(value); + break; + case PROP_HOSTNAME: + /* construct-only */ + priv->hostname = g_value_dup_string(value); + break; + case PROP_HOSTNAME_FLAGS: + /* construct-only */ + priv->hostname_flags = g_value_get_uint(value); + break; + case PROP_MUD_URL: + /* construct-only */ + priv->mud_url = g_value_dup_string(value); + break; + case PROP_ROUTE_TABLE: + priv->route_table = g_value_get_uint(value); + break; + case PROP_ROUTE_METRIC: + priv->route_metric = g_value_get_uint(value); + break; + case PROP_TIMEOUT: + /* construct-only */ + priv->timeout = g_value_get_uint(value); + break; + case PROP_VENDOR_CLASS_IDENTIFIER: + /* construct-only */ + priv->vendor_class_identifier = g_value_dup_boxed(value); + break; + case PROP_REJECT_SERVERS: + /* construct-only */ + priv->reject_servers = nm_utils_strv_dup_packed(g_value_get_boxed(value), -1); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_dhcp_client_init(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate); + self->_priv = priv; + + c_list_init(&self->dhcp_client_lst); + + priv->pid = -1; +} + +static void +dispose(GObject *object) +{ + NMDhcpClient * self = NM_DHCP_CLIENT(object); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + /* Stopping the client is left up to the controlling device + * explicitly since we may want to quit NetworkManager but not terminate + * the DHCP client. + */ + + nm_assert(c_list_is_empty(&self->dhcp_client_lst)); + + watch_cleanup(self); + timeout_cleanup(self); + + nm_clear_g_free(&priv->iface); + nm_clear_g_free(&priv->hostname); + nm_clear_g_free(&priv->uuid); + nm_clear_g_free(&priv->mud_url); + nm_clear_g_free(&priv->reject_servers); + nm_clear_pointer(&priv->client_id, g_bytes_unref); + nm_clear_pointer(&priv->hwaddr, g_bytes_unref); + nm_clear_pointer(&priv->bcast_hwaddr, g_bytes_unref); + nm_clear_pointer(&priv->vendor_class_identifier, g_bytes_unref); + + G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object); + + priv->multi_idx = nm_dedup_multi_index_unref(priv->multi_idx); +} + +static void +nm_dhcp_client_class_init(NMDhcpClientClass *client_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(client_class); + + g_type_class_add_private(client_class, sizeof(NMDhcpClientPrivate)); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + client_class->stop = stop; + client_class->get_duid = get_duid; + + obj_properties[PROP_MULTI_IDX] = + g_param_spec_pointer(NM_DHCP_CLIENT_MULTI_IDX, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IFACE] = + g_param_spec_string(NM_DHCP_CLIENT_INTERFACE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_DHCP_CLIENT_IFINDEX, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HWADDR] = + g_param_spec_boxed(NM_DHCP_CLIENT_HWADDR, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BROADCAST_HWADDR] = + g_param_spec_boxed(NM_DHCP_CLIENT_BROADCAST_HWADDR, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ADDR_FAMILY] = + g_param_spec_int(NM_DHCP_CLIENT_ADDR_FAMILY, + "", + "", + 0, + G_MAXINT, + AF_UNSPEC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_UUID] = + g_param_spec_string(NM_DHCP_CLIENT_UUID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IAID] = + g_param_spec_uint(NM_DHCP_CLIENT_IAID, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IAID_EXPLICIT] = + g_param_spec_boolean(NM_DHCP_CLIENT_IAID_EXPLICIT, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HOSTNAME] = + g_param_spec_string(NM_DHCP_CLIENT_HOSTNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HOSTNAME_FLAGS] = + g_param_spec_uint(NM_DHCP_CLIENT_HOSTNAME_FLAGS, + "", + "", + 0, + G_MAXUINT32, + NM_DHCP_HOSTNAME_FLAG_NONE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MUD_URL] = + g_param_spec_string(NM_DHCP_CLIENT_MUD_URL, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ROUTE_TABLE] = + g_param_spec_uint(NM_DHCP_CLIENT_ROUTE_TABLE, + "", + "", + 0, + G_MAXUINT32, + RT_TABLE_MAIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ROUTE_METRIC] = + g_param_spec_uint(NM_DHCP_CLIENT_ROUTE_METRIC, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_DHCP_TIMEOUT_INFINITY); + obj_properties[PROP_TIMEOUT] = + g_param_spec_uint(NM_DHCP_CLIENT_TIMEOUT, + "", + "", + 1, + G_MAXINT32, + NM_DHCP_TIMEOUT_DEFAULT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FLAGS] = + g_param_spec_uint(NM_DHCP_CLIENT_FLAGS, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_VENDOR_CLASS_IDENTIFIER] = + g_param_spec_boxed(NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER, + "", + "", + G_TYPE_BYTES, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_REJECT_SERVERS] = + g_param_spec_boxed(NM_DHCP_CLIENT_REJECT_SERVERS, + "", + "", + G_TYPE_STRV, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[SIGNAL_STATE_CHANGED] = g_signal_new(NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_UINT, + G_TYPE_OBJECT, + G_TYPE_HASH_TABLE); + + signals[SIGNAL_PREFIX_DELEGATED] = g_signal_new(NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h new file mode 100644 index 0000000..72ab477 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-client.h @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2010 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_CLIENT_H__ +#define __NETWORKMANAGER_DHCP_CLIENT_H__ + +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-dhcp-utils.h" + +#define NM_DHCP_TIMEOUT_DEFAULT ((guint32) 45) /* default DHCP timeout, in seconds */ +#define NM_DHCP_TIMEOUT_INFINITY ((guint32) G_MAXINT32) + +#define NM_TYPE_DHCP_CLIENT (nm_dhcp_client_get_type()) +#define NM_DHCP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClient)) +#define NM_DHCP_CLIENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass)) +#define NM_IS_DHCP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_CLIENT)) +#define NM_IS_DHCP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_CLIENT)) +#define NM_DHCP_CLIENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass)) + +#define NM_DHCP_CLIENT_ADDR_FAMILY "addr-family" +#define NM_DHCP_CLIENT_FLAGS "flags" +#define NM_DHCP_CLIENT_HWADDR "hwaddr" +#define NM_DHCP_CLIENT_BROADCAST_HWADDR "broadcast-hwaddr" +#define NM_DHCP_CLIENT_IFINDEX "ifindex" +#define NM_DHCP_CLIENT_INTERFACE "iface" +#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx" +#define NM_DHCP_CLIENT_HOSTNAME "hostname" +#define NM_DHCP_CLIENT_MUD_URL "mud-url" +#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric" +#define NM_DHCP_CLIENT_ROUTE_TABLE "route-table" +#define NM_DHCP_CLIENT_TIMEOUT "timeout" +#define NM_DHCP_CLIENT_UUID "uuid" +#define NM_DHCP_CLIENT_IAID "iaid" +#define NM_DHCP_CLIENT_IAID_EXPLICIT "iaid-explicit" +#define NM_DHCP_CLIENT_HOSTNAME_FLAGS "hostname-flags" +#define NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER "vendor-class-identifier" +#define NM_DHCP_CLIENT_REJECT_SERVERS "reject-servers" + +#define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed" +#define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated" + +typedef enum { + NM_DHCP_STATE_UNKNOWN = 0, + NM_DHCP_STATE_BOUND, /* new lease */ + NM_DHCP_STATE_EXTENDED, /* lease extended */ + NM_DHCP_STATE_TIMEOUT, /* timed out contacting server */ + NM_DHCP_STATE_DONE, /* client quit or stopped */ + NM_DHCP_STATE_EXPIRE, /* lease expired or NAKed */ + NM_DHCP_STATE_FAIL, /* failed for some reason */ + NM_DHCP_STATE_TERMINATED, /* client is no longer running */ + NM_DHCP_STATE_NOOP, /* state is a non operation for NetworkManager */ + __NM_DHCP_STATE_MAX, + NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1, +} NMDhcpState; + +struct _NMDhcpClientPrivate; + +typedef struct { + GObject parent; + struct _NMDhcpClientPrivate *_priv; + CList dhcp_client_lst; +} NMDhcpClient; + +typedef enum { + NM_DHCP_CLIENT_FLAGS_INFO_ONLY = (1LL << 0), + NM_DHCP_CLIENT_FLAGS_USE_FQDN = (1LL << 1), +} NMDhcpClientFlags; + +typedef struct { + GObjectClass parent; + + gboolean (*ip4_start)(NMDhcpClient *self, + const char * anycast_addr, + const char * last_ip4_address, + GError ** error); + + gboolean (*accept)(NMDhcpClient *self, GError **error); + + gboolean (*decline)(NMDhcpClient *self, const char *error_message, GError **error); + + gboolean (*ip6_start)(NMDhcpClient * self, + const char * anycast_addr, + const struct in6_addr * ll_addr, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error); + + void (*stop)(NMDhcpClient *self, gboolean release); + + /** + * get_duid: + * @self: the #NMDhcpClient + * + * Attempts to find an existing DHCPv6 DUID for this client in the DHCP + * client's persistent configuration. Returned DUID should be the binary + * representation of the DUID. If no DUID is found, %NULL should be + * returned. + */ + GBytes *(*get_duid)(NMDhcpClient *self); +} NMDhcpClientClass; + +GType nm_dhcp_client_get_type(void); + +struct _NMDedupMultiIndex *nm_dhcp_client_get_multi_idx(NMDhcpClient *self); + +pid_t nm_dhcp_client_get_pid(NMDhcpClient *self); + +int nm_dhcp_client_get_addr_family(NMDhcpClient *self); + +const char *nm_dhcp_client_get_iface(NMDhcpClient *self); + +int nm_dhcp_client_get_ifindex(NMDhcpClient *self); + +const char *nm_dhcp_client_get_uuid(NMDhcpClient *self); + +GBytes *nm_dhcp_client_get_duid(NMDhcpClient *self); + +GBytes *nm_dhcp_client_get_hw_addr(NMDhcpClient *self); + +GBytes *nm_dhcp_client_get_broadcast_hw_addr(NMDhcpClient *self); + +guint32 nm_dhcp_client_get_route_table(NMDhcpClient *self); + +void nm_dhcp_client_set_route_table(NMDhcpClient *self, guint32 route_table); + +guint32 nm_dhcp_client_get_route_metric(NMDhcpClient *self); + +void nm_dhcp_client_set_route_metric(NMDhcpClient *self, guint32 route_metric); + +guint32 nm_dhcp_client_get_timeout(NMDhcpClient *self); + +guint32 nm_dhcp_client_get_iaid(NMDhcpClient *self); + +gboolean nm_dhcp_client_get_iaid_explicit(NMDhcpClient *self); + +GBytes *nm_dhcp_client_get_client_id(NMDhcpClient *self); + +const char * nm_dhcp_client_get_hostname(NMDhcpClient *self); +const char * nm_dhcp_client_get_mud_url(NMDhcpClient *self); +const char *const *nm_dhcp_client_get_reject_servers(NMDhcpClient *self); + +NMDhcpHostnameFlags nm_dhcp_client_get_hostname_flags(NMDhcpClient *self); + +gboolean nm_dhcp_client_get_info_only(NMDhcpClient *self); + +gboolean nm_dhcp_client_get_use_fqdn(NMDhcpClient *self); + +GBytes *nm_dhcp_client_get_vendor_class_identifier(NMDhcpClient *self); + +gboolean nm_dhcp_client_start_ip4(NMDhcpClient *self, + GBytes * client_id, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error); + +gboolean nm_dhcp_client_start_ip6(NMDhcpClient * self, + GBytes * client_id, + gboolean enforce_duid, + const char * dhcp_anycast_addr, + const struct in6_addr * ll_addr, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error); + +gboolean nm_dhcp_client_accept(NMDhcpClient *self, GError **error); + +gboolean nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error); + +void nm_dhcp_client_stop(NMDhcpClient *self, gboolean release); + +/* Backend helpers for subclasses */ +void nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name); + +void nm_dhcp_client_stop_pid(pid_t pid, const char *iface); + +void nm_dhcp_client_start_timeout(NMDhcpClient *self); + +void nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid); + +void nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid); + +void nm_dhcp_client_set_state(NMDhcpClient *self, + NMDhcpState new_state, + NMIPConfig * ip_config, + GHashTable * options); /* str:str hash */ + +gboolean nm_dhcp_client_handle_event(gpointer unused, + const char * iface, + int pid, + GVariant * options, + const char * reason, + NMDhcpClient *self); + +void nm_dhcp_client_set_client_id(NMDhcpClient *self, GBytes *client_id); +void nm_dhcp_client_set_client_id_bin(NMDhcpClient *self, + guint8 type, + const guint8 *client_id, + gsize len); + +void nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient * self, + const NMPlatformIP6Address *prefix); + +gboolean nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr); + +/***************************************************************************** + * Client data + *****************************************************************************/ + +typedef struct { + GType (*get_type)(void); + GType (*get_type_per_addr_family)(int addr_family); + const char *name; + const char *(*get_path)(void); + bool experimental : 1; +} NMDhcpClientFactory; + +GType nm_dhcp_nettools_get_type(void); + +extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon; +extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient; +extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd; +extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal; +extern const NMDhcpClientFactory _nm_dhcp_client_factory_systemd; +extern const NMDhcpClientFactory _nm_dhcp_client_factory_nettools; + +#endif /* __NETWORKMANAGER_DHCP_CLIENT_H__ */ diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c new file mode 100644 index 0000000..14a2b96 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-dhclient-utils.c @@ -0,0 +1,714 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-dhclient-utils.h" + +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" + +#include "nm-dhcp-utils.h" +#include "nm-ip4-config.h" +#include "nm-utils.h" +#include "platform/nm-platform.h" +#include "NetworkManagerUtils.h" + +#define TIMEOUT_TAG "timeout " +#define RETRY_TAG "retry " +#define CLIENTID_TAG "send dhcp-client-identifier" + +#define HOSTNAME4_TAG "send host-name" +#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager" + +#define FQDN_TAG_PREFIX "send fqdn." +#define FQDN_TAG FQDN_TAG_PREFIX "fqdn" +#define FQDN_FORMAT FQDN_TAG " \"%s\"; # added by NetworkManager" + +#define ALSOREQ_TAG "also request " +#define REQ_TAG "request " + +#define MUDURLv4_DEF "option mudurl code 161 = text;\n" +#define MUDURLv4_FMT "send mudurl \"%s\";\n" + +#define MUDURLv6_DEF "option dhcp6.mudurl code 112 = text;\n" +#define MUDURLv6_FMT "send dhcp6.mudurl \"%s\";\n" + +static void +add_request(GPtrArray *array, const char *item) +{ + guint i; + + for (i = 0; i < array->len; i++) { + if (nm_streq(array->pdata[i], item)) + return; + } + g_ptr_array_add(array, g_strdup(item)); +} + +static gboolean +grab_request_options(GPtrArray *store, const char *line) +{ + gs_free const char **line_v = NULL; + gsize i; + + /* Grab each 'request' or 'also request' option and save for later */ + line_v = nm_utils_strsplit_set(line, "\t ,"); + for (i = 0; line_v && line_v[i]; i++) { + const char *ss = nm_str_skip_leading_spaces(line_v[i]); + gsize l; + gboolean end = FALSE; + + if (!ss[0]) + continue; + if (ss[0] == ';') { + /* all done */ + return TRUE; + } + + if (!g_ascii_isalnum(ss[0])) + continue; + + l = strlen(ss); + + while (l > 0 && g_ascii_isspace(ss[l - 1])) { + ((char *) ss)[l - 1] = '\0'; + l--; + } + if (l > 0 && ss[l - 1] == ';') { + /* Remove the EOL marker */ + ((char *) ss)[l - 1] = '\0'; + end = TRUE; + } + + if (ss[0]) + add_request(store, ss); + + if (end) + return TRUE; + } + + return FALSE; +} + +static void +add_ip4_config(GString * str, + GBytes * client_id, + const char * hostname, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags) +{ + if (client_id) { + const char *p; + gsize l; + guint i; + + p = g_bytes_get_data(client_id, &l); + nm_assert(p); + + /* Allow type 0 (non-hardware address) to be represented as a string + * as long as all the characters are printable. + */ + for (i = 1; (p[0] == 0) && i < l; i++) { + if (!g_ascii_isprint(p[i]) || p[i] == '\\' || p[i] == '"') + break; + } + + g_string_append(str, CLIENTID_TAG " "); + if (i < l) { + /* Unprintable; convert to a hex string */ + for (i = 0; i < l; i++) { + if (i > 0) + g_string_append_c(str, ':'); + g_string_append_printf(str, "%02x", (guint8) p[i]); + } + } else { + /* Printable; just add to the line with type 0 */ + g_string_append_c(str, '"'); + g_string_append(str, "\\x00"); + g_string_append_len(str, p + 1, l - 1); + g_string_append_c(str, '"'); + } + g_string_append(str, "; # added by NetworkManager\n"); + } + + if (hostname) { + if (use_fqdn) { + g_string_append_printf(str, FQDN_FORMAT "\n", hostname); + + g_string_append_printf(str, + FQDN_TAG_PREFIX "encoded %s;\n", + (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) ? "on" + : "off"); + + g_string_append_printf( + str, + FQDN_TAG_PREFIX "server-update %s;\n", + (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) ? "on" : "off"); + + g_string_append_printf(str, + FQDN_TAG_PREFIX "no-client-update %s;\n", + (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) ? "on" + : "off"); + } else + g_string_append_printf(str, HOSTNAME4_FORMAT "\n", hostname); + } + + g_string_append_c(str, '\n'); + + /* Define options for classless static routes */ + g_string_append( + str, + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"); + g_string_append(str, + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"); + /* Web Proxy Auto-Discovery option (bgo #368423) */ + g_string_append(str, "option wpad code 252 = string;\n"); + + g_string_append_c(str, '\n'); +} + +static void +add_hostname6(GString *str, const char *hostname, NMDhcpHostnameFlags hostname_flags) +{ + if (hostname) { + g_string_append_printf(str, FQDN_FORMAT "\n", hostname); + if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) + g_string_append(str, FQDN_TAG_PREFIX "server-update on;\n"); + if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) + g_string_append(str, FQDN_TAG_PREFIX "no-client-update on;\n"); + g_string_append_c(str, '\n'); + } +} + +static void +add_mud_url_config(GString *str, const char *mud_url, int addr_family) +{ + if (mud_url) { + if (addr_family == AF_INET) { + g_string_append(str, MUDURLv4_DEF); + g_string_append_printf(str, MUDURLv4_FMT, mud_url); + } else { + g_string_append(str, MUDURLv6_DEF); + g_string_append_printf(str, MUDURLv6_FMT, mud_url); + } + } +} + +static GBytes * +read_client_id(const char *str) +{ + gs_free char *s = NULL; + char * p; + int i = 0, j = 0; + + nm_assert(!strncmp(str, CLIENTID_TAG, NM_STRLEN(CLIENTID_TAG))); + str += NM_STRLEN(CLIENTID_TAG); + + if (!g_ascii_isspace(*str)) + return NULL; + while (g_ascii_isspace(*str)) + str++; + + if (*str == '"') { + /* Parse string literal with escape sequences */ + s = g_strdup(str + 1); + p = strrchr(s, '"'); + if (p) + *p = '\0'; + else + return NULL; + + if (!s[0]) + return NULL; + + while (s[i]) { + if (s[i] == '\\' && s[i + 1] == 'x' && g_ascii_isxdigit(s[i + 2]) + && g_ascii_isxdigit(s[i + 3])) { + s[j++] = (g_ascii_xdigit_value(s[i + 2]) << 4) + g_ascii_xdigit_value(s[i + 3]); + i += 4; + continue; + } + if (s[i] == '\\' && s[i + 1] >= '0' && s[i + 1] <= '7' && s[1 + 2] >= '0' + && s[i + 2] <= '7' && s[1 + 3] >= '0' && s[i + 3] <= '7') { + s[j++] = ((s[i + 1] - '0') << 6) + ((s[i + 2] - '0') << 3) + (s[i + 3] - '0'); + i += 4; + continue; + } + s[j++] = s[i++]; + } + return g_bytes_new_take(g_steal_pointer(&s), j); + } + + /* Otherwise, try to read a hexadecimal sequence */ + s = g_strdup(str); + g_strchomp(s); + if (s[strlen(s) - 1] == ';') + s[strlen(s) - 1] = '\0'; + + return nm_utils_hexstr2bin(s); +} + +static gboolean +read_interface(const char *line, char *interface, guint size) +{ + gs_free char *dup = g_strdup(line + NM_STRLEN("interface")); + char * ptr = dup, *end; + + while (g_ascii_isspace(*ptr)) + ptr++; + + if (*ptr == '"') { + ptr++; + end = strchr(ptr, '"'); + if (!end) + return FALSE; + *end = '\0'; + } else { + end = strchr(ptr, ' '); + if (!end) + end = strchr(ptr, '{'); + if (!end) + return FALSE; + *end = '\0'; + } + + if (ptr[0] == '\0' || strlen(ptr) + 1 > size) + return FALSE; + + snprintf(interface, size, "%s", ptr); + + return TRUE; +} + +char * +nm_dhcp_dhclient_create_config(const char * interface, + int addr_family, + GBytes * client_id, + const char * anycast_addr, + const char * hostname, + guint32 timeout, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + const char *const * reject_servers, + const char * orig_path, + const char * orig_contents, + GBytes ** out_new_client_id) +{ + nm_auto_free_gstring GString *new_contents = NULL; + gs_unref_ptrarray GPtrArray *fqdn_opts = NULL; + gs_unref_ptrarray GPtrArray *reqs = NULL; + gboolean reset_reqlist = FALSE; + int i; + + g_return_val_if_fail(!anycast_addr || nm_utils_hwaddr_valid(anycast_addr, ETH_ALEN), NULL); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL); + g_return_val_if_fail(!reject_servers || addr_family == AF_INET, NULL); + nm_assert(!out_new_client_id || !*out_new_client_id); + + new_contents = g_string_new(_("# Created by NetworkManager\n")); + reqs = g_ptr_array_new_full(5, g_free); + + if (orig_contents) { + gs_free const char **lines = NULL; + gsize line_i; + nm_auto_free_gstring GString *blocks_stack = NULL; + guint blocks_skip = 0; + gboolean in_alsoreq = FALSE; + gboolean in_req = FALSE; + char intf[IFNAMSIZ]; + + blocks_stack = g_string_new(NULL); + g_string_append_printf(new_contents, _("# Merged from %s\n\n"), orig_path); + intf[0] = '\0'; + + lines = nm_utils_strsplit_set(orig_contents, "\n\r"); + for (line_i = 0; lines && lines[line_i]; line_i++) { + const char *line = nm_str_skip_leading_spaces(lines[line_i]); + const char *p; + + if (line[0] == '\0') + continue; + + g_strchomp((char *) line); + + p = line; + if (in_req) { + /* pass */ + } else if (strchr(p, '{')) { + if (NM_STR_HAS_PREFIX(p, "lease") || NM_STR_HAS_PREFIX(p, "alias") + || NM_STR_HAS_PREFIX(p, "interface") || NM_STR_HAS_PREFIX(p, "pseudo")) { + /* skip over these blocks, except 'interface' when it + * matches the current interface */ + blocks_skip++; + g_string_append_c(blocks_stack, 'b'); + if (!intf[0] && NM_STR_HAS_PREFIX(p, "interface")) { + if (read_interface(p, intf, sizeof(intf))) + continue; + } + } else { + /* allow other blocks (conditionals) */ + if (!strchr(p, '}')) /* '} else {' */ + g_string_append_c(blocks_stack, 'c'); + } + } else if (strchr(p, '}')) { + if (blocks_stack->len > 0) { + if (blocks_stack->str[blocks_stack->len - 1] == 'b') { + g_string_truncate(blocks_stack, blocks_stack->len - 1); + nm_assert(blocks_skip > 0); + blocks_skip--; + intf[0] = '\0'; + continue; + } + g_string_truncate(blocks_stack, blocks_stack->len - 1); + } + } + + if (blocks_skip > 0 && !intf[0]) + continue; + + if (intf[0] && !nm_streq(intf, interface)) + continue; + + /* Some timing parameters in dhclient should not be imported (timeout, retry). + * The retry parameter will be simply not used as we will exit on first failure. + * The timeout one instead may affect NetworkManager behavior: if the timeout + * elapses before dhcp-timeout dhclient will report failure and cause NM to + * fail the dhcp process before dhcp-timeout. So, always skip importing timeout + * as we will need to add one greater than dhcp-timeout. + */ + if (!strncmp(p, TIMEOUT_TAG, strlen(TIMEOUT_TAG)) + || !strncmp(p, RETRY_TAG, strlen(RETRY_TAG))) + continue; + + if (!strncmp(p, CLIENTID_TAG, strlen(CLIENTID_TAG))) { + /* Override config file "dhcp-client-id" and use one from the connection */ + if (client_id) + continue; + + /* Otherwise, capture and return the existing client id */ + if (out_new_client_id) + nm_clear_pointer(out_new_client_id, g_bytes_unref); + NM_SET_OUT(out_new_client_id, read_client_id(p)); + } + + /* Override config file hostname and use one from the connection */ + if (hostname) { + if (strncmp(p, HOSTNAME4_TAG, strlen(HOSTNAME4_TAG)) == 0) + continue; + if (strncmp(p, FQDN_TAG, strlen(FQDN_TAG)) == 0) + continue; + } + + /* To let user's FQDN options (except "fqdn.fqdn") override the + * default ones set by NM, add them later + */ + if (!strncmp(p, FQDN_TAG_PREFIX, NM_STRLEN(FQDN_TAG_PREFIX))) { + if (!fqdn_opts) + fqdn_opts = g_ptr_array_new_full(5, g_free); + g_ptr_array_add(fqdn_opts, g_strdup(p + NM_STRLEN(FQDN_TAG_PREFIX))); + continue; + } + + /* Ignore 'script' since we pass our own */ + if (g_str_has_prefix(p, "script ")) + continue; + + /* Check for "request" */ + if (!strncmp(p, REQ_TAG, strlen(REQ_TAG))) { + in_req = TRUE; + p += strlen(REQ_TAG); + g_ptr_array_set_size(reqs, 0); + reset_reqlist = TRUE; + } + + /* Save all request options for later use */ + if (in_req) { + in_req = !grab_request_options(reqs, p); + continue; + } + + /* Check for "also require" */ + if (!strncmp(p, ALSOREQ_TAG, strlen(ALSOREQ_TAG))) { + in_alsoreq = TRUE; + p += strlen(ALSOREQ_TAG); + } + + if (in_alsoreq) { + in_alsoreq = !grab_request_options(reqs, p); + continue; + } + + /* Existing configuration line is OK, add it to new configuration */ + g_string_append(new_contents, line); + g_string_append_c(new_contents, '\n'); + } + } else + g_string_append_c(new_contents, '\n'); + + /* ensure dhclient timeout is greater than dhcp-timeout: as dhclient timeout default value is + * 60 seconds, we need this only if dhcp-timeout is greater than 60. + */ + if (timeout >= 60) { + timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32; + g_string_append_printf(new_contents, "timeout %u;\n", timeout); + } + + add_mud_url_config(new_contents, mud_url, addr_family); + + if (reject_servers && reject_servers[0]) { + g_string_append(new_contents, "reject "); + for (i = 0; reject_servers[i]; i++) { + if (i != 0) + g_string_append(new_contents, ", "); + g_string_append(new_contents, reject_servers[i]); + } + g_string_append(new_contents, ";\n"); + } + + if (addr_family == AF_INET) { + add_ip4_config(new_contents, client_id, hostname, use_fqdn, hostname_flags); + add_request(reqs, "rfc3442-classless-static-routes"); + add_request(reqs, "ms-classless-static-routes"); + add_request(reqs, "static-routes"); + add_request(reqs, "wpad"); + add_request(reqs, "ntp-servers"); + add_request(reqs, "root-path"); + } else { + add_hostname6(new_contents, hostname, hostname_flags); + add_request(reqs, "dhcp6.name-servers"); + add_request(reqs, "dhcp6.domain-search"); + + /* FIXME: internal client does not support requesting client-id option. Does this even work? */ + add_request(reqs, "dhcp6.client-id"); + } + + if (reset_reqlist) + g_string_append(new_contents, "request; # override dhclient defaults\n"); + /* And add it to the dhclient configuration */ + for (i = 0; i < reqs->len; i++) + g_string_append_printf(new_contents, "also request %s;\n", (char *) reqs->pdata[i]); + + if (fqdn_opts) { + for (i = 0; i < fqdn_opts->len; i++) { + const char *t = fqdn_opts->pdata[i]; + + if (i == 0) + g_string_append_printf(new_contents, "\n# FQDN options from %s\n", orig_path); + g_string_append_printf(new_contents, FQDN_TAG_PREFIX "%s\n", t); + } + } + + g_string_append_c(new_contents, '\n'); + + if (anycast_addr) { + g_string_append_printf(new_contents, + "interface \"%s\" {\n" + " initial-interval 1; \n" + " anycast-mac ethernet %s;\n" + "}\n", + interface, + anycast_addr); + } + + return g_string_free(g_steal_pointer(&new_contents), FALSE); +} + +/* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */ +char * +nm_dhcp_dhclient_escape_duid(GBytes *duid) +{ + char * escaped; + const guint8 *s, *s0; + gsize len; + char * d; + + g_return_val_if_fail(duid, NULL); + + s0 = g_bytes_get_data(duid, &len); + s = s0; + + d = escaped = g_malloc((len * 4) + 1); + while (s < (s0 + len)) { + if (!g_ascii_isprint(*s)) { + *d++ = '\\'; + *d++ = '0' + ((*s >> 6) & 0x7); + *d++ = '0' + ((*s >> 3) & 0x7); + *d++ = '0' + (*s++ & 0x7); + } else if (*s == '"' || *s == '\'' || *s == '$' || *s == '`' || *s == '\\' || *s == '|' + || *s == '&') { + *d++ = '\\'; + *d++ = *s++; + } else + *d++ = *s++; + } + *d++ = '\0'; + return escaped; +} + +static gboolean +isoctal(const guint8 *p) +{ + return (p[0] >= '0' && p[0] <= '3' && p[1] >= '0' && p[1] <= '7' && p[2] >= '0' && p[2] <= '7'); +} + +GBytes * +nm_dhcp_dhclient_unescape_duid(const char *duid) +{ + GByteArray * unescaped; + const guint8 *p = (const guint8 *) duid; + guint i, len; + guint8 octal; + + /* FIXME: it's wrong to have an "unescape-duid" function. dhclient + * defines a file format with escaping. So we need a general unescape + * function that can handle dhclient syntax. */ + + len = strlen(duid); + unescaped = g_byte_array_sized_new(len); + for (i = 0; i < len; i++) { + if (p[i] == '\\') { + i++; + if (isdigit(p[i])) { + /* Octal escape sequence */ + if (i + 2 >= len || !isoctal(p + i)) + goto error; + octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0'); + g_byte_array_append(unescaped, &octal, 1); + i += 2; + } else { + /* FIXME: don't warn on untrusted data. Either signal an error, or accept + * it silently. */ + + /* One of ", ', $, `, \, |, or & */ + g_warn_if_fail(p[i] == '"' || p[i] == '\'' || p[i] == '$' || p[i] == '`' + || p[i] == '\\' || p[i] == '|' || p[i] == '&'); + g_byte_array_append(unescaped, &p[i], 1); + } + } else + g_byte_array_append(unescaped, &p[i], 1); + } + + return g_byte_array_free_to_bytes(unescaped); + +error: + g_byte_array_free(unescaped, TRUE); + return NULL; +} + +#define DUID_PREFIX "default-duid \"" + +/* Beware: @error may be unset even if the function returns %NULL. */ +GBytes * +nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error) +{ + gs_free char * contents = NULL; + gs_free const char **contents_v = NULL; + gsize i; + + if (!g_file_test(leasefile, G_FILE_TEST_EXISTS)) + return NULL; + + if (!g_file_get_contents(leasefile, &contents, NULL, error)) + return NULL; + + contents_v = nm_utils_strsplit_set(contents, "\n\r"); + for (i = 0; contents_v && contents_v[i]; i++) { + const char *p = nm_str_skip_leading_spaces(contents_v[i]); + GBytes * duid; + + if (!NM_STR_HAS_PREFIX(p, DUID_PREFIX)) + continue; + + p += NM_STRLEN(DUID_PREFIX); + + g_strchomp((char *) p); + + if (!NM_STR_HAS_SUFFIX(p, "\";")) + continue; + + ((char *) p)[strlen(p) - 2] = '\0'; + + duid = nm_dhcp_dhclient_unescape_duid(p); + if (duid) + return duid; + } + + return NULL; +} + +gboolean +nm_dhcp_dhclient_save_duid(const char *leasefile, GBytes *duid, GError **error) +{ + gs_free char * escaped_duid = NULL; + gs_free const char **lines = NULL; + nm_auto_free_gstring GString *s = NULL; + const char *const * iter; + gsize len = 0; + + g_return_val_if_fail(leasefile != NULL, FALSE); + if (!duid) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing duid"); + g_return_val_if_reached(FALSE); + } + + escaped_duid = nm_dhcp_dhclient_escape_duid(duid); + nm_assert(escaped_duid); + + if (g_file_test(leasefile, G_FILE_TEST_EXISTS)) { + gs_free char *contents = NULL; + + if (!g_file_get_contents(leasefile, &contents, &len, error)) { + g_prefix_error(error, "failed to read lease file %s: ", leasefile); + return FALSE; + } + + lines = nm_utils_strsplit_set_with_empty(contents, "\n\r"); + } + + s = g_string_sized_new(len + 50); + g_string_append_printf(s, DUID_PREFIX "%s\";\n", escaped_duid); + + /* Preserve existing leasefile contents */ + if (lines) { + for (iter = lines; *iter; iter++) { + const char *str = *iter; + const char *l; + + /* If we find an uncommented DUID in the file, check if + * equal to the one we are going to write: if so, no need + * to update the lease file, otherwise skip the old DUID. + */ + l = nm_str_skip_leading_spaces(str); + if (g_str_has_prefix(l, DUID_PREFIX)) { + gs_strfreev char **split = NULL; + + split = g_strsplit(l, "\"", -1); + if (split[0] && nm_streq0(split[1], escaped_duid)) + return TRUE; + + continue; + } + + if (str) + g_string_append(s, str); + /* avoid to add an extra '\n' at the end of file */ + if ((iter[1]) != NULL) + g_string_append_c(s, '\n'); + } + } + + if (!g_file_set_contents(leasefile, s->str, -1, error)) { + g_prefix_error(error, "failed to set DUID in lease file %s: ", leasefile); + return FALSE; + } + + return TRUE; +} diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.h b/src/core/dhcp/nm-dhcp-dhclient-utils.h new file mode 100644 index 0000000..ed7c1c7 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-dhclient-utils.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ +#define __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ + +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" + +char *nm_dhcp_dhclient_create_config(const char * interface, + int addr_family, + GBytes * client_id, + const char * anycast_addr, + const char * hostname, + guint32 timeout, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + const char *const * reject_servers, + const char * orig_path, + const char * orig_contents, + GBytes ** out_new_client_id); + +char *nm_dhcp_dhclient_escape_duid(GBytes *duid); + +GBytes *nm_dhcp_dhclient_unescape_duid(const char *duid); + +GBytes *nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error); + +gboolean nm_dhcp_dhclient_save_duid(const char *leasefile, GBytes *duid, GError **error); + +#endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */ diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c new file mode 100644 index 0000000..53d1ba7 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-dhclient.c @@ -0,0 +1,727 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2012 Red Hat, Inc. + */ + +#include +#define __CONFIG_H__ + +#define _XOPEN_SOURCE +#include +#undef _XOPEN_SOURCE + +#include "nm-default.h" + +#if WITH_DHCLIENT + + #include + #include + #include + #include + #include + #include + + #include "nm-glib-aux/nm-dedup-multi.h" + + #include "nm-utils.h" + #include "nm-dhcp-dhclient-utils.h" + #include "nm-dhcp-manager.h" + #include "NetworkManagerUtils.h" + #include "nm-dhcp-listener.h" + #include "nm-dhcp-client-logging.h" + +/*****************************************************************************/ + +static const char * +_addr_family_to_path_part(int addr_family) +{ + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + return (addr_family == AF_INET6) ? "6" : ""; +} + +/*****************************************************************************/ + + #define NM_TYPE_DHCP_DHCLIENT (nm_dhcp_dhclient_get_type()) + #define NM_DHCP_DHCLIENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient)) + #define NM_DHCP_DHCLIENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass)) + #define NM_IS_DHCP_DHCLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCLIENT)) + #define NM_IS_DHCP_DHCLIENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCLIENT)) + #define NM_DHCP_DHCLIENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass)) + +typedef struct _NMDhcpDhclient NMDhcpDhclient; +typedef struct _NMDhcpDhclientClass NMDhcpDhclientClass; + +static GType nm_dhcp_dhclient_get_type(void); + +/*****************************************************************************/ + +typedef struct { + char * conf_file; + const char * def_leasefile; + char * lease_file; + char * pid_file; + NMDhcpListener *dhcp_listener; +} NMDhcpDhclientPrivate; + +struct _NMDhcpDhclient { + NMDhcpClient parent; + NMDhcpDhclientPrivate _priv; +}; + +struct _NMDhcpDhclientClass { + NMDhcpClientClass parent; +}; + +G_DEFINE_TYPE(NMDhcpDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT) + + #define NM_DHCP_DHCLIENT_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT) + +/*****************************************************************************/ + +static const char * +nm_dhcp_dhclient_get_path(void) +{ + return nm_utils_find_helper("dhclient", DHCLIENT_PATH, NULL); +} + +/** + * get_dhclient_leasefile(): + * @addr_family: AF_INET or AF_INET6 + * @iface: the interface name of the device on which DHCP will be done + * @uuid: the connection UUID to which the returned lease should belong + * @out_preferred_path: on return, the "most preferred" leasefile path + * + * Returns the path of an existing leasefile (if any) for this interface and + * connection UUID. Also returns the "most preferred" leasefile path, which + * may be different than any found leasefile. + * + * Returns: an existing leasefile, or %NULL if no matching leasefile could be found + */ +static char * +get_dhclient_leasefile(int addr_family, + const char *iface, + const char *uuid, + char ** out_preferred_path) +{ + gs_free char *path = NULL; + + if (nm_dhcp_utils_get_leasefile_path(addr_family, "dhclient", iface, uuid, &path)) { + NM_SET_OUT(out_preferred_path, g_strdup(path)); + return g_steal_pointer(&path); + } + + NM_SET_OUT(out_preferred_path, g_steal_pointer(&path)); + + /* If the leasefile we're looking for doesn't exist yet in the new location + * (eg, /var/lib/NetworkManager) then look in old locations to maintain + * backwards compatibility with external tools (like dracut) that put + * leasefiles there. + */ + + /* Old Debian, SUSE, and Mandriva location */ + g_free(path); + path = g_strdup_printf(LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease", + _addr_family_to_path_part(addr_family), + uuid, + iface); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return g_steal_pointer(&path); + + /* Old Red Hat and Fedora location */ + g_free(path); + path = g_strdup_printf(LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease", + _addr_family_to_path_part(addr_family), + uuid, + iface); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return g_steal_pointer(&path); + + /* Fail */ + return NULL; +} + +static gboolean +merge_dhclient_config(NMDhcpDhclient * self, + int addr_family, + const char * iface, + const char * conf_file, + GBytes * client_id, + const char * anycast_addr, + const char * hostname, + guint32 timeout, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + const char *const * reject_servers, + const char * orig_path, + GBytes ** out_new_client_id, + GError ** error) +{ + gs_free char *orig = NULL; + gs_free char *new = NULL; + + g_return_val_if_fail(iface, FALSE); + g_return_val_if_fail(conf_file, FALSE); + + if (orig_path && g_file_test(orig_path, G_FILE_TEST_EXISTS)) { + GError *read_error = NULL; + + if (!g_file_get_contents(orig_path, &orig, NULL, &read_error)) { + _LOGW("error reading dhclient configuration %s: %s", orig_path, read_error->message); + g_error_free(read_error); + } + } + + new = nm_dhcp_dhclient_create_config(iface, + addr_family, + client_id, + anycast_addr, + hostname, + timeout, + use_fqdn, + hostname_flags, + mud_url, + reject_servers, + orig_path, + orig, + out_new_client_id); + nm_assert(new); + + return g_file_set_contents(conf_file, new, -1, error); +} + +static char * +find_existing_config(NMDhcpDhclient *self, int addr_family, const char *iface, const char *uuid) +{ + char *path; + + /* NetworkManager-overridden configuration can be used to ship DHCP config + * with NetworkManager itself. It can be uuid-specific, device-specific + * or generic. + */ + if (uuid) { + path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf", + _addr_family_to_path_part(addr_family), + uuid); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + } + + path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf", + _addr_family_to_path_part(addr_family), + iface); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + path = g_strdup_printf(NMCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family)); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + /* Distribution's dhclient configuration is used so that we can use + * configuration shipped with dhclient (if any). + * + * This replaces conditional compilation based on distribution name. Fedora + * and Debian store the configs in /etc/dhcp while upstream defaults to /etc + * which is then used by many other distributions. Some distributions + * (including Fedora) don't even provide a default configuration file. + */ + path = g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s-%s.conf", + _addr_family_to_path_part(addr_family), + iface); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + path = g_strdup_printf(SYSCONFDIR "/dhclient%s-%s.conf", + _addr_family_to_path_part(addr_family), + iface); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + path = + g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s.conf", _addr_family_to_path_part(addr_family)); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + path = g_strdup_printf(SYSCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family)); + _LOGD("looking for existing config %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + return path; + g_free(path); + + return NULL; +} + +/* NM provides interface-specific options; thus the same dhclient config + * file cannot be used since DHCP transactions can happen in parallel. + * Since some distros don't have default per-interface dhclient config files, + * read their single config file and merge that into a custom per-interface + * config file along with the NM options. + */ +static char * +create_dhclient_config(NMDhcpDhclient * self, + int addr_family, + const char * iface, + const char * uuid, + GBytes * client_id, + const char * dhcp_anycast_addr, + const char * hostname, + guint32 timeout, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + const char *const * reject_servers, + GBytes ** out_new_client_id) +{ + gs_free char *orig = NULL; + char *new = NULL; + GError *error = NULL; + + g_return_val_if_fail(iface != NULL, NULL); + + new = g_strdup_printf(NMSTATEDIR "/dhclient%s-%s.conf", + _addr_family_to_path_part(addr_family), + iface); + + _LOGD("creating composite dhclient config %s", new); + + orig = find_existing_config(self, addr_family, iface, uuid); + if (orig) + _LOGD("merging existing dhclient config %s", orig); + else + _LOGD("no existing dhclient configuration to merge"); + + if (!merge_dhclient_config(self, + addr_family, + iface, + new, + client_id, + dhcp_anycast_addr, + hostname, + timeout, + use_fqdn, + hostname_flags, + mud_url, + reject_servers, + orig, + out_new_client_id, + &error)) { + _LOGW("error creating dhclient configuration: %s", error->message); + g_clear_error(&error); + } + + return new; +} + +static gboolean +dhclient_start(NMDhcpClient *client, + const char * mode_opt, + gboolean release, + pid_t * out_pid, + int prefixes, + GError ** error) +{ + NMDhcpDhclient * self = NM_DHCP_DHCLIENT(client); + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *argv = NULL; + pid_t pid; + gs_free_error GError *local = NULL; + const char * iface; + const char * uuid; + const char * system_bus_address; + const char * dhclient_path; + char * binary_name; + gs_free char * cmd_str = NULL; + gs_free char * pid_file = NULL; + gs_free char * system_bus_address_env = NULL; + gs_free char * preferred_leasefile_path = NULL; + const int addr_family = nm_dhcp_client_get_addr_family(client); + + g_return_val_if_fail(!priv->pid_file, FALSE); + + NM_SET_OUT(out_pid, 0); + + dhclient_path = nm_dhcp_dhclient_get_path(); + if (!dhclient_path) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhclient binary not found"); + return FALSE; + } + + iface = nm_dhcp_client_get_iface(client); + uuid = nm_dhcp_client_get_uuid(client); + + pid_file = g_strdup_printf(NMRUNDIR "/dhclient%s-%s.pid", + _addr_family_to_path_part(addr_family), + iface); + + /* Kill any existing dhclient from the pidfile */ + binary_name = g_path_get_basename(dhclient_path); + nm_dhcp_client_stop_existing(pid_file, binary_name); + g_free(binary_name); + + if (release) { + /* release doesn't use the pidfile after killing an old client */ + nm_clear_g_free(&pid_file); + } + + g_free(priv->lease_file); + priv->lease_file = get_dhclient_leasefile(addr_family, iface, uuid, &preferred_leasefile_path); + nm_assert(preferred_leasefile_path); + if (!priv->lease_file) { + /* No existing leasefile, dhclient will create one at the preferred path */ + priv->lease_file = g_steal_pointer(&preferred_leasefile_path); + } else if (!nm_streq0(priv->lease_file, preferred_leasefile_path)) { + gs_unref_object GFile *src = g_file_new_for_path(priv->lease_file); + gs_unref_object GFile *dst = g_file_new_for_path(preferred_leasefile_path); + + /* Try to copy the existing leasefile to the preferred location */ + if (!g_file_copy(src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &local)) { + gs_free char *s_path = NULL; + gs_free char *d_path = NULL; + + /* Failure; just use the existing leasefile */ + _LOGW("failed to copy leasefile %s to %s: %s", + (s_path = g_file_get_path(src)), + (d_path = g_file_get_path(dst)), + local->message); + g_clear_error(&local); + } else { + /* Success; use the preferred leasefile path */ + g_free(priv->lease_file); + priv->lease_file = g_file_get_path(dst); + } + } + + /* Save the DUID to the leasefile dhclient will actually use */ + if (addr_family == AF_INET6) { + if (!nm_dhcp_dhclient_save_duid(priv->lease_file, + nm_dhcp_client_get_client_id(client), + &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "failed to save DUID to '%s': %s", + priv->lease_file, + local->message); + return FALSE; + } + } + + argv = g_ptr_array_new(); + g_ptr_array_add(argv, (gpointer) dhclient_path); + + g_ptr_array_add(argv, (gpointer) "-d"); + + /* Be quiet. dhclient logs to syslog anyway. And we duplicate the syslog + * to stderr in case of NM running with --debug. + */ + g_ptr_array_add(argv, (gpointer) "-q"); + + if (release) + g_ptr_array_add(argv, (gpointer) "-r"); + + if (addr_family == AF_INET6) { + g_ptr_array_add(argv, (gpointer) "-6"); + if (mode_opt) + g_ptr_array_add(argv, (gpointer) mode_opt); + while (prefixes--) + g_ptr_array_add(argv, (gpointer) "-P"); + } + g_ptr_array_add(argv, (gpointer) "-sf"); /* Set script file */ + g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path); + + if (pid_file) { + g_ptr_array_add(argv, (gpointer) "-pf"); /* Set pid file */ + g_ptr_array_add(argv, (gpointer) pid_file); + } + + g_ptr_array_add(argv, (gpointer) "-lf"); /* Set lease file */ + g_ptr_array_add(argv, (gpointer) priv->lease_file); + + if (priv->conf_file) { + g_ptr_array_add(argv, (gpointer) "-cf"); /* Set interface config file */ + g_ptr_array_add(argv, (gpointer) priv->conf_file); + } + + /* Usually the system bus address is well-known; but if it's supposed + * to be something else, we need to push it to dhclient, since dhclient + * sanitizes the environment it gives the action scripts. + */ + system_bus_address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (system_bus_address) { + system_bus_address_env = g_strdup_printf("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address); + g_ptr_array_add(argv, (gpointer) "-e"); + g_ptr_array_add(argv, (gpointer) system_bus_address_env); + } + + g_ptr_array_add(argv, (gpointer) iface); + g_ptr_array_add(argv, NULL); + + _LOGD("running: %s", (cmd_str = g_strjoinv(" ", (char **) argv->pdata))); + + if (!g_spawn_async(NULL, + (char **) argv->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL + | G_SPAWN_STDERR_TO_DEV_NULL, + nm_utils_setpgid, + NULL, + &pid, + &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "dhclient failed to start: %s", + local->message); + return FALSE; + } + + _LOGI("dhclient started with pid %lld", (long long int) pid); + + if (!release) + nm_dhcp_client_watch_child(client, pid); + + priv->pid_file = g_steal_pointer(&pid_file); + + NM_SET_OUT(out_pid, pid); + return TRUE; +} + +static gboolean +ip4_start(NMDhcpClient *client, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + NMDhcpDhclient * self = NM_DHCP_DHCLIENT(client); + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + GBytes * client_id; + gs_unref_bytes GBytes *new_client_id = NULL; + + client_id = nm_dhcp_client_get_client_id(client); + + priv->conf_file = create_dhclient_config(self, + AF_INET, + nm_dhcp_client_get_iface(client), + nm_dhcp_client_get_uuid(client), + client_id, + dhcp_anycast_addr, + nm_dhcp_client_get_hostname(client), + nm_dhcp_client_get_timeout(client), + nm_dhcp_client_get_use_fqdn(client), + nm_dhcp_client_get_hostname_flags(client), + nm_dhcp_client_get_mud_url(client), + nm_dhcp_client_get_reject_servers(client), + &new_client_id); + if (!priv->conf_file) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "error creating dhclient configuration file"); + return FALSE; + } + + if (new_client_id) { + nm_assert(!client_id); + nm_dhcp_client_set_client_id(client, new_client_id); + } + return dhclient_start(client, NULL, FALSE, NULL, 0, error); +} + +static gboolean +ip6_start(NMDhcpClient * client, + const char * dhcp_anycast_addr, + const struct in6_addr * ll_addr, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error) +{ + NMDhcpDhclient * self = NM_DHCP_DHCLIENT(client); + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + + if (nm_dhcp_client_get_iaid_explicit(client)) + _LOGW("dhclient does not support specifying an IAID for DHCPv6, it will be ignored"); + + priv->conf_file = create_dhclient_config(self, + AF_INET6, + nm_dhcp_client_get_iface(client), + nm_dhcp_client_get_uuid(client), + NULL, + dhcp_anycast_addr, + nm_dhcp_client_get_hostname(client), + nm_dhcp_client_get_timeout(client), + TRUE, + nm_dhcp_client_get_hostname_flags(client), + nm_dhcp_client_get_mud_url(client), + NULL, + NULL); + if (!priv->conf_file) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "error creating dhclient configuration file"); + return FALSE; + } + + return dhclient_start(client, + nm_dhcp_client_get_info_only(NM_DHCP_CLIENT(self)) ? "-S" : "-N", + FALSE, + NULL, + needed_prefixes, + error); +} + +static void +stop(NMDhcpClient *client, gboolean release) +{ + NMDhcpDhclient * self = NM_DHCP_DHCLIENT(client); + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + int errsv; + + NM_DHCP_CLIENT_CLASS(nm_dhcp_dhclient_parent_class)->stop(client, release); + + if (priv->conf_file) + if (remove(priv->conf_file) == -1) { + errsv = errno; + _LOGD("could not remove dhcp config file \"%s\": %d (%s)", + priv->conf_file, + errsv, + nm_strerror_native(errsv)); + } + if (priv->pid_file) { + if (remove(priv->pid_file) == -1) { + errsv = errno; + _LOGD("could not remove dhcp pid file \"%s\": %s (%d)", + priv->pid_file, + nm_strerror_native(errsv), + errsv); + } + nm_clear_g_free(&priv->pid_file); + } + + if (release) { + pid_t rpid = -1; + + if (dhclient_start(client, NULL, TRUE, &rpid, 0, NULL)) { + /* Wait a few seconds for the release to happen */ + nm_dhcp_client_stop_pid(rpid, nm_dhcp_client_get_iface(client)); + } + } +} + +static GBytes * +get_duid(NMDhcpClient *client) +{ + NMDhcpDhclient * self = NM_DHCP_DHCLIENT(client); + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + GBytes * duid = NULL; + gs_free char * leasefile = NULL; + GError * error = NULL; + + /* Look in interface-specific leasefile first for backwards compat */ + leasefile = get_dhclient_leasefile(AF_INET6, + nm_dhcp_client_get_iface(client), + nm_dhcp_client_get_uuid(client), + NULL); + if (leasefile) { + _LOGD("looking for DUID in '%s'", leasefile); + duid = nm_dhcp_dhclient_read_duid(leasefile, &error); + if (error) { + _LOGW("failed to read leasefile '%s': %s", leasefile, error->message); + g_clear_error(&error); + } + if (duid) + return duid; + } + + /* Otherwise, read the default machine-wide DUID */ + _LOGD("looking for default DUID in '%s'", priv->def_leasefile); + duid = nm_dhcp_dhclient_read_duid(priv->def_leasefile, &error); + if (error) { + _LOGW("failed to read leasefile '%s': %s", priv->def_leasefile, error->message); + g_clear_error(&error); + } + + return duid; +} + +/*****************************************************************************/ + +static void +nm_dhcp_dhclient_init(NMDhcpDhclient *self) +{ + static const char *const FILES[] = { + SYSCONFDIR "/dhclient6.leases", /* default */ + LOCALSTATEDIR "/lib/dhcp/dhclient6.leases", + LOCALSTATEDIR "/lib/dhclient/dhclient6.leases", + }; + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); + int i; + + priv->def_leasefile = FILES[0]; + for (i = 0; i < G_N_ELEMENTS(FILES); i++) { + if (g_file_test(FILES[i], G_FILE_TEST_EXISTS)) { + priv->def_leasefile = FILES[i]; + break; + } + } + + priv->dhcp_listener = g_object_ref(nm_dhcp_listener_get()); + g_signal_connect(priv->dhcp_listener, + NM_DHCP_LISTENER_EVENT, + G_CALLBACK(nm_dhcp_client_handle_event), + self); +} + +static void +dispose(GObject *object) +{ + NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(object); + + if (priv->dhcp_listener) { + g_signal_handlers_disconnect_by_func(priv->dhcp_listener, + G_CALLBACK(nm_dhcp_client_handle_event), + NM_DHCP_DHCLIENT(object)); + g_clear_object(&priv->dhcp_listener); + } + + nm_clear_g_free(&priv->pid_file); + nm_clear_g_free(&priv->conf_file); + nm_clear_g_free(&priv->lease_file); + + G_OBJECT_CLASS(nm_dhcp_dhclient_parent_class)->dispose(object); +} + +static void +nm_dhcp_dhclient_class_init(NMDhcpDhclientClass *dhclient_class) +{ + NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(dhclient_class); + GObjectClass * object_class = G_OBJECT_CLASS(dhclient_class); + + object_class->dispose = dispose; + + client_class->ip4_start = ip4_start; + client_class->ip6_start = ip6_start; + client_class->stop = stop; + client_class->get_duid = get_duid; +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = { + .name = "dhclient", + .get_type = nm_dhcp_dhclient_get_type, + .get_path = nm_dhcp_dhclient_get_path, +}; + +#endif /* WITH_DHCLIENT */ diff --git a/src/core/dhcp/nm-dhcp-dhcpcanon.c b/src/core/dhcp/nm-dhcp-dhcpcanon.c new file mode 100644 index 0000000..db75958 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-dhcpcanon.c @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 juga + */ + +#include "nm-default.h" + +#if WITH_DHCPCANON + + #include + #include + + #include "nm-utils.h" + #include "nm-dhcp-manager.h" + #include "NetworkManagerUtils.h" + #include "nm-dhcp-listener.h" + #include "nm-dhcp-client-logging.h" + + #define NM_TYPE_DHCP_DHCPCANON (nm_dhcp_dhcpcanon_get_type()) + #define NM_DHCP_DHCPCANON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanon)) + #define NM_DHCP_DHCPCANON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass)) + #define NM_IS_DHCP_DHCPCANON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCANON)) + #define NM_IS_DHCP_DHCPCANON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCANON)) + #define NM_DHCP_DHCPCANON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass)) + +typedef struct _NMDhcpDhcpcanon NMDhcpDhcpcanon; +typedef struct _NMDhcpDhcpcanonClass NMDhcpDhcpcanonClass; + +static GType nm_dhcp_dhcpcanon_get_type(void); + +/*****************************************************************************/ + +typedef struct { + char * conf_file; + const char * def_leasefile; + char * lease_file; + char * pid_file; + NMDhcpListener *dhcp_listener; +} NMDhcpDhcpcanonPrivate; + +struct _NMDhcpDhcpcanon { + NMDhcpClient parent; + NMDhcpDhcpcanonPrivate _priv; +}; + +struct _NMDhcpDhcpcanonClass { + NMDhcpClientClass parent; +}; + +G_DEFINE_TYPE(NMDhcpDhcpcanon, nm_dhcp_dhcpcanon, NM_TYPE_DHCP_CLIENT) + + #define NM_DHCP_DHCPCANON_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDhcpDhcpcanon, NM_IS_DHCP_DHCPCANON) + +/*****************************************************************************/ + +static const char * +nm_dhcp_dhcpcanon_get_path(void) +{ + return nm_utils_find_helper("dhcpcanon", DHCPCANON_PATH, NULL); +} + +static gboolean +dhcpcanon_start(NMDhcpClient *client, + const char * mode_opt, + GBytes * duid, + gboolean release, + pid_t * out_pid, + guint needed_prefixes, + GError ** error) +{ + NMDhcpDhcpcanon * self = NM_DHCP_DHCPCANON(client); + NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *argv = NULL; + pid_t pid; + gs_free_error GError *local = NULL; + const char * iface; + const char * system_bus_address; + const char * dhcpcanon_path; + gs_free char * binary_name = NULL; + gs_free char * pid_file = NULL; + gs_free char * system_bus_address_env = NULL; + int addr_family; + + g_return_val_if_fail(!priv->pid_file, FALSE); + + iface = nm_dhcp_client_get_iface(client); + + addr_family = nm_dhcp_client_get_addr_family(client); + + dhcpcanon_path = nm_dhcp_dhcpcanon_get_path(); + if (!dhcpcanon_path) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon binary not found"); + return FALSE; + } + + _LOGD("dhcpcanon_path: %s", dhcpcanon_path); + + pid_file = g_strdup_printf(RUNSTATEDIR "/dhcpcanon%c-%s.pid", + nm_utils_addr_family_to_char(addr_family), + iface); + _LOGD("pid_file: %s", pid_file); + + /* Kill any existing dhcpcanon from the pidfile */ + binary_name = g_path_get_basename(dhcpcanon_path); + nm_dhcp_client_stop_existing(pid_file, binary_name); + + argv = g_ptr_array_new(); + g_ptr_array_add(argv, (gpointer) dhcpcanon_path); + + g_ptr_array_add(argv, (gpointer) "-sf"); /* Set script file */ + g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path); + + g_ptr_array_add(argv, (gpointer) "-pf"); /* Set pid file */ + g_ptr_array_add(argv, (gpointer) pid_file); + + if (priv->conf_file) { + g_ptr_array_add(argv, (gpointer) "-cf"); /* Set interface config file */ + g_ptr_array_add(argv, (gpointer) priv->conf_file); + } + + /* Usually the system bus address is well-known; but if it's supposed + * to be something else, we need to push it to dhcpcanon, since dhcpcanon + * sanitizes the environment it gives the action scripts. + */ + system_bus_address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (system_bus_address) { + system_bus_address_env = g_strdup_printf("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address); + g_ptr_array_add(argv, (gpointer) "-e"); + g_ptr_array_add(argv, (gpointer) system_bus_address_env); + } + + g_ptr_array_add(argv, (gpointer) iface); + g_ptr_array_add(argv, NULL); + + if (!g_spawn_async(NULL, + (char **) argv->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL + | G_SPAWN_STDERR_TO_DEV_NULL, + nm_utils_setpgid, + NULL, + &pid, + &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "dhcpcanon failed to start: %s", + local->message); + return FALSE; + } + + nm_assert(pid > 0); + _LOGI("dhcpcanon started with pid %d", pid); + nm_dhcp_client_watch_child(client, pid); + priv->pid_file = g_steal_pointer(&pid_file); + return TRUE; +} + +static gboolean +ip4_start(NMDhcpClient *client, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + return dhcpcanon_start(client, NULL, NULL, FALSE, NULL, 0, error); +} + +static void +stop(NMDhcpClient *client, gboolean release) +{ + NMDhcpDhcpcanon * self = NM_DHCP_DHCPCANON(client); + NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE(self); + int errsv; + + NM_DHCP_CLIENT_CLASS(nm_dhcp_dhcpcanon_parent_class)->stop(client, release); + + if (priv->pid_file) { + if (remove(priv->pid_file) == -1) { + errsv = errno; + _LOGD("could not remove dhcp pid file \"%s\": %d (%s)", + priv->pid_file, + errsv, + nm_strerror_native(errsv)); + } + g_free(priv->pid_file); + priv->pid_file = NULL; + } +} + +/*****************************************************************************/ + +static void +nm_dhcp_dhcpcanon_init(NMDhcpDhcpcanon *self) +{ + NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE(self); + + priv->dhcp_listener = g_object_ref(nm_dhcp_listener_get()); + g_signal_connect(priv->dhcp_listener, + NM_DHCP_LISTENER_EVENT, + G_CALLBACK(nm_dhcp_client_handle_event), + self); +} + +static void +dispose(GObject *object) +{ + NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE(object); + + if (priv->dhcp_listener) { + g_signal_handlers_disconnect_by_func(priv->dhcp_listener, + G_CALLBACK(nm_dhcp_client_handle_event), + NM_DHCP_DHCPCANON(object)); + g_clear_object(&priv->dhcp_listener); + } + + nm_clear_g_free(&priv->pid_file); + + G_OBJECT_CLASS(nm_dhcp_dhcpcanon_parent_class)->dispose(object); +} + +static void +nm_dhcp_dhcpcanon_class_init(NMDhcpDhcpcanonClass *dhcpcanon_class) +{ + NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(dhcpcanon_class); + GObjectClass * object_class = G_OBJECT_CLASS(dhcpcanon_class); + + object_class->dispose = dispose; + + client_class->ip4_start = ip4_start; + client_class->stop = stop; +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = { + .name = "dhcpcanon", + .get_type = nm_dhcp_dhcpcanon_get_type, + .get_path = nm_dhcp_dhcpcanon_get_path, +}; + +#endif /* WITH_DHCPCANON */ diff --git a/src/core/dhcp/nm-dhcp-dhcpcd.c b/src/core/dhcp/nm-dhcp-dhcpcd.c new file mode 100644 index 0000000..4f45c3a --- /dev/null +++ b/src/core/dhcp/nm-dhcp-dhcpcd.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008,2020 Roy Marples + * Copyright (C) 2010 Dan Williams + */ + +#include "nm-default.h" + +#if WITH_DHCPCD + + #include + #include + #include + #include + #include + + #include "nm-dhcp-manager.h" + #include "nm-utils.h" + #include "NetworkManagerUtils.h" + #include "nm-dhcp-listener.h" + #include "nm-dhcp-client-logging.h" + +/*****************************************************************************/ + + #define NM_TYPE_DHCP_DHCPCD (nm_dhcp_dhcpcd_get_type()) + #define NM_DHCP_DHCPCD(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcd)) + #define NM_DHCP_DHCPCD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass)) + #define NM_IS_DHCP_DHCPCD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCD)) + #define NM_IS_DHCP_DHCPCD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCD)) + #define NM_DHCP_DHCPCD_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass)) + +typedef struct _NMDhcpDhcpcd NMDhcpDhcpcd; +typedef struct _NMDhcpDhcpcdClass NMDhcpDhcpcdClass; + +static GType nm_dhcp_dhcpcd_get_type(void); + +/*****************************************************************************/ + +typedef struct { + NMDhcpListener *dhcp_listener; +} NMDhcpDhcpcdPrivate; + +struct _NMDhcpDhcpcd { + NMDhcpClient parent; + NMDhcpDhcpcdPrivate _priv; +}; + +struct _NMDhcpDhcpcdClass { + NMDhcpClientClass parent; +}; + +G_DEFINE_TYPE(NMDhcpDhcpcd, nm_dhcp_dhcpcd, NM_TYPE_DHCP_CLIENT) + + #define NM_DHCP_DHCPCD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpDhcpcd, NM_IS_DHCP_DHCPCD) + +/*****************************************************************************/ + +static const char * +nm_dhcp_dhcpcd_get_path(void) +{ + return nm_utils_find_helper("dhcpcd", DHCPCD_PATH, NULL); +} + +static gboolean +ip4_start(NMDhcpClient *client, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + NMDhcpDhcpcd * self = NM_DHCP_DHCPCD(client); + gs_unref_ptrarray GPtrArray *argv = NULL; + pid_t pid; + GError * local; + gs_free char * cmd_str = NULL; + const char * iface; + const char * dhcpcd_path; + const char * hostname; + + pid = nm_dhcp_client_get_pid(client); + g_return_val_if_fail(pid == -1, FALSE); + + iface = nm_dhcp_client_get_iface(client); + + dhcpcd_path = nm_dhcp_dhcpcd_get_path(); + if (!dhcpcd_path) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd binary not found"); + return FALSE; + } + + argv = g_ptr_array_new(); + g_ptr_array_add(argv, (gpointer) dhcpcd_path); + + /* Don't configure anything, we will do that instead. + * This requires dhcpcd-9.3.3 or newer. + * Older versions only had an option not to install a default route, + * dhcpcd still added addresses and other routes so we no longer support that + * as it doesn't fit how NetworkManager wants to work. + */ + g_ptr_array_add(argv, (gpointer) "--noconfigure"); + + g_ptr_array_add(argv, (gpointer) "-B"); /* Don't background on lease (disable fork()) */ + + g_ptr_array_add(argv, (gpointer) "-K"); /* Disable built-in carrier detection */ + + g_ptr_array_add(argv, (gpointer) "-L"); /* Disable built-in IPv4LL */ + + /* --noarp. Don't request or claim the address by ARP; this also disables IPv4LL. */ + g_ptr_array_add(argv, (gpointer) "-A"); + + g_ptr_array_add(argv, (gpointer) "-c"); /* Set script file */ + g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path); + + /* IPv4-only for now. NetworkManager knows better than dhcpcd when to + * run IPv6, and dhcpcd's automatic Router Solicitations cause problems + * with devices that don't expect them. + */ + g_ptr_array_add(argv, (gpointer) "-4"); + + hostname = nm_dhcp_client_get_hostname(client); + + if (hostname) { + if (nm_dhcp_client_get_use_fqdn(client)) { + g_ptr_array_add(argv, (gpointer) "-h"); + g_ptr_array_add(argv, (gpointer) hostname); + g_ptr_array_add(argv, (gpointer) "-F"); + g_ptr_array_add(argv, (gpointer) "both"); + } else { + g_ptr_array_add(argv, (gpointer) "-h"); + g_ptr_array_add(argv, (gpointer) hostname); + } + } + + g_ptr_array_add(argv, (gpointer) iface); + g_ptr_array_add(argv, NULL); + + _LOGD("running: %s", (cmd_str = g_strjoinv(" ", (char **) argv->pdata))); + + if (!g_spawn_async(NULL, + (char **) argv->pdata, + NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL + | G_SPAWN_DO_NOT_REAP_CHILD, + nm_utils_setpgid, + NULL, + &pid, + &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "dhcpcd failed to start: %s", + local->message); + g_error_free(local); + return FALSE; + } + + nm_assert(pid > 0); + _LOGI("dhcpcd started with pid %d", pid); + nm_dhcp_client_watch_child(client, pid); + return TRUE; +} + +static void +stop(NMDhcpClient *client, gboolean release) +{ + NMDhcpDhcpcd *self = NM_DHCP_DHCPCD(client); + pid_t pid; + int sig, errsv; + + pid = nm_dhcp_client_get_pid(client); + sig = release ? SIGALRM : SIGTERM; + _LOGD("sending %s to dhcpcd pid %d", sig == SIGALRM ? "SIGALRM" : "SIGTERM", pid); + + /* dhcpcd-9.x features privilege separation. + * It's not our job to track all these processes so we rely on dhcpcd + * to always cleanup after itself. + * Because it also re-parents itself to PID 1, the process cannot be + * reaped or waited for. + * As such, just send the correct signal. + */ + if (kill(pid, sig) == -1) { + errsv = errno; + _LOGE("failed to kill dhcpcd %d:%s", errsv, strerror(errsv)); + } + + /* When this function exits NM expects the PID to be -1. + * This means we also need to stop watching the pid. + * If we need to know the exit status then we need to refactor NM + * to allow a non -1 to mean we're waiting to exit still. + */ + nm_dhcp_client_stop_watch_child(client, pid); +} + +/*****************************************************************************/ + +static void +nm_dhcp_dhcpcd_init(NMDhcpDhcpcd *self) +{ + NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE(self); + + priv->dhcp_listener = g_object_ref(nm_dhcp_listener_get()); + g_signal_connect(priv->dhcp_listener, + NM_DHCP_LISTENER_EVENT, + G_CALLBACK(nm_dhcp_client_handle_event), + self); +} + +static void +dispose(GObject *object) +{ + NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE(object); + + if (priv->dhcp_listener) { + g_signal_handlers_disconnect_by_func(priv->dhcp_listener, + G_CALLBACK(nm_dhcp_client_handle_event), + NM_DHCP_DHCPCD(object)); + g_clear_object(&priv->dhcp_listener); + } + + G_OBJECT_CLASS(nm_dhcp_dhcpcd_parent_class)->dispose(object); +} + +static void +nm_dhcp_dhcpcd_class_init(NMDhcpDhcpcdClass *dhcpcd_class) +{ + NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(dhcpcd_class); + GObjectClass * object_class = G_OBJECT_CLASS(dhcpcd_class); + + object_class->dispose = dispose; + + client_class->ip4_start = ip4_start; + client_class->stop = stop; +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd = { + .name = "dhcpcd", + .get_type = nm_dhcp_dhcpcd_get_type, + .get_path = nm_dhcp_dhcpcd_get_path, +}; + +#endif /* WITH_DHCPCD */ diff --git a/src/core/dhcp/nm-dhcp-helper-api.h b/src/core/dhcp/nm-dhcp-helper-api.h new file mode 100644 index 0000000..ffbf3f7 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-helper-api.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_DHCP_HELPER_API_H__ +#define __NM_DHCP_HELPER_API_H__ + +/*****************************************************************************/ + +#define NM_DHCP_CLIENT_DBUS_IFACE "org.freedesktop.nm_dhcp_client" + +#define NM_DHCP_HELPER_SERVER_BUS_NAME "org.freedesktop.nm_dhcp_server" +#define NM_DHCP_HELPER_SERVER_OBJECT_PATH "/org/freedesktop/nm_dhcp_server" +#define NM_DHCP_HELPER_SERVER_INTERFACE_NAME "org.freedesktop.nm_dhcp_server" +#define NM_DHCP_HELPER_SERVER_METHOD_NOTIFY "Notify" + +/*****************************************************************************/ + +#endif /* __NM_DHCP_HELPER_API_H__ */ diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c new file mode 100644 index 0000000..94ee181 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-helper.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include + +#include "nm-utils/nm-vpn-plugin-macros.h" + +#include "nm-dhcp-helper-api.h" + +/*****************************************************************************/ + +#if NM_MORE_LOGGING + #define _NMLOG_ENABLED(level) TRUE +#else + #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR) +#endif + +#define _NMLOG(always_enabled, level, ...) \ + G_STMT_START \ + { \ + if ((always_enabled) || _NMLOG_ENABLED(level)) { \ + GTimeVal _tv; \ + \ + g_get_current_time(&_tv); \ + g_print( \ + "nm-dhcp-helper[%ld] %-7s [%ld.%04ld] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \ + (long) getpid(), \ + nm_utils_syslog_to_str(level), \ + _tv.tv_sec, \ + _tv.tv_usec / 100 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _LOGD(...) _NMLOG(TRUE, LOG_INFO, __VA_ARGS__) +#define _LOGI(...) _NMLOG(TRUE, LOG_NOTICE, __VA_ARGS__) +#define _LOGW(...) _NMLOG(TRUE, LOG_WARNING, __VA_ARGS__) +#define _LOGE(...) _NMLOG(TRUE, LOG_ERR, __VA_ARGS__) + +#define _LOGd(...) _NMLOG(FALSE, LOG_INFO, __VA_ARGS__) +#define _LOGi(...) _NMLOG(FALSE, LOG_NOTICE, __VA_ARGS__) +#define _LOGw(...) _NMLOG(FALSE, LOG_WARNING, __VA_ARGS__) + +/*****************************************************************************/ + +static GVariant * +build_signal_parameters(void) +{ + const char *const *environ_iter; + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + /* List environment and format for dbus dict */ + for (environ_iter = (const char *const *) environ; *environ_iter; environ_iter++) { + static const char *const ignore_with_prefix_list[] = + {"PATH", "SHLVL", "_", "PWD", "dhc_dbus", NULL}; + const char * item = *environ_iter; + gs_free char * name = NULL; + const char * val; + const char *const *p; + + val = strchr(item, '='); + if (!val || item == val) + continue; + + name = g_strndup(item, val - item); + val += 1; + + /* Ignore non-DHCP-related environment variables */ + for (p = ignore_with_prefix_list; *p; p++) { + if (strncmp(name, *p, strlen(*p)) == 0) + goto next; + } + + if (!g_utf8_validate(name, -1, NULL)) + continue; + + /* Value passed as a byte array rather than a string, because there are + * no character encoding guarantees with DHCP, and D-Bus requires + * strings to be UTF-8. + * + * Note that we can't use g_variant_new_bytestring() here, because that + * includes the trailing '\0'. (??!?) + */ + g_variant_builder_add(&builder, + "{sv}", + name, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, val, strlen(val), 1)); + +next:; + } + + return g_variant_ref_sink(g_variant_new("(a{sv})", &builder)); +} + +static void +kill_pid(void) +{ + const char *pid_str; + pid_t pid = 0; + + pid_str = getenv("pid"); + if (pid_str) + pid = strtol(pid_str, NULL, 10); + if (pid) { + _LOGI("a fatal error occurred, kill dhclient instance with pid %d", pid); + kill(pid, SIGTERM); + } +} + +int +main(int argc, char *argv[]) +{ + gs_unref_object GDBusConnection *connection = NULL; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *parameters = NULL; + gs_unref_variant GVariant *result = NULL; + gboolean success = FALSE; + guint try_count; + gint64 time_start; + gint64 time_end; + + /* Connecting to the unix socket can fail with EAGAIN if there are too + * many pending connections and the server can't accept them in time + * before reaching backlog capacity. Ideally the server should increase + * the backlog length, but GLib doesn't provide a way to change it for a + * GDBus server. Retry for up to 5 seconds in case of failure. */ + time_start = g_get_monotonic_time(); + time_end = time_start + (5000 * 1000L); + try_count = 0; + +do_connect: + try_count++; + connection = + g_dbus_connection_new_for_address_sync("unix:path=" NMRUNDIR "/private-dhcp", + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, + NULL, + &error); + if (!connection) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + gint64 time_remaining = time_end - g_get_monotonic_time(); + gint64 interval; + + if (time_remaining > 0) { + _LOGi("failure to connect: %s (retry %u, waited %lld ms)", + error->message, + try_count, + (long long) (time_end - time_remaining - time_start) / 1000); + interval = NM_CLAMP((gint64)(100L * (1L << NM_MIN(try_count, 31))), 5000, 100000); + g_usleep(NM_MIN(interval, time_remaining)); + g_clear_error(&error); + goto do_connect; + } + } + + g_dbus_error_strip_remote_error(error); + _LOGE("could not connect to NetworkManager D-Bus socket: %s", error->message); + goto out; + } + + parameters = build_signal_parameters(); + time_end = g_get_monotonic_time() + (200 * 1000L); /* retry for at most 200 milliseconds */ + try_count = 0; + +do_notify: + try_count++; + result = g_dbus_connection_call_sync(connection, + NULL, + NM_DHCP_HELPER_SERVER_OBJECT_PATH, + NM_DHCP_HELPER_SERVER_INTERFACE_NAME, + NM_DHCP_HELPER_SERVER_METHOD_NOTIFY, + parameters, + NULL, + G_DBUS_CALL_FLAGS_NONE, + 1000, + NULL, + &error); + + if (!result) { + gs_free char *s_err = NULL; + + s_err = g_dbus_error_get_remote_error(error); + if (NM_IN_STRSET(s_err, "org.freedesktop.DBus.Error.UnknownMethod")) { + gint64 remaining_time = time_end - g_get_monotonic_time(); + gint64 interval; + + /* I am not sure that a race can actually happen, as we register the object + * on the server side during GDBusServer:new-connection signal. + * + * However, there was also a race for subscribing to an event, so let's just + * do some retry. */ + if (remaining_time > 0) { + _LOGi("failure to call notify: %s (retry %u)", error->message, try_count); + interval = NM_CLAMP((gint64)(100L * (1L << NM_MIN(try_count, 31))), 5000, 25000); + g_usleep(NM_MIN(interval, remaining_time)); + g_clear_error(&error); + goto do_notify; + } + } + _LOGW("failure to call notify: %s (try signal via Event)", error->message); + g_clear_error(&error); + + /* for backward compatibility, try to emit the signal. There is no stable + * API between the dhcp-helper and NetworkManager. However, while upgrading + * the NetworkManager package, a newer helper might want to notify an + * older server, which still uses the "Event". */ + if (!g_dbus_connection_emit_signal(connection, + NULL, + "/", + NM_DHCP_CLIENT_DBUS_IFACE, + "Event", + parameters, + &error)) { + g_dbus_error_strip_remote_error(error); + _LOGE("could not send DHCP Event signal: %s", error->message); + goto out; + } + /* We were able to send the asynchronous Event. Consider that a success. */ + success = TRUE; + } else + success = TRUE; + + if (!g_dbus_connection_flush_sync(connection, NULL, &error)) { + g_dbus_error_strip_remote_error(error); + _LOGE("could not flush D-Bus connection: %s", error->message); + success = FALSE; + goto out; + } + +out: + if (!success) + kill_pid(); + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/core/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c new file mode 100644 index 0000000..279ce49 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-listener.c @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-listener.h" + +#include +#include +#include +#include +#include + +#include "nm-dhcp-helper-api.h" +#include "nm-dhcp-client.h" +#include "nm-dhcp-manager.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" + +#define PRIV_SOCK_PATH NMRUNDIR "/private-dhcp" +#define PRIV_SOCK_TAG "dhcp" + +/*****************************************************************************/ + +const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = { +/* the order here matters, as we will try the plugins in this order to find + * the first available plugin. */ + +#if WITH_DHCPCANON + &_nm_dhcp_client_factory_dhcpcanon, +#endif +#if WITH_DHCLIENT + &_nm_dhcp_client_factory_dhclient, +#endif +#if WITH_DHCPCD + &_nm_dhcp_client_factory_dhcpcd, +#endif + &_nm_dhcp_client_factory_internal, + &_nm_dhcp_client_factory_systemd, + &_nm_dhcp_client_factory_nettools, +}; + +/*****************************************************************************/ + +typedef struct { + NMDBusManager *dbus_mgr; + gulong new_conn_id; + gulong dis_conn_id; + GHashTable * connections; +} NMDhcpListenerPrivate; + +struct _NMDhcpListener { + GObject parent; + NMDhcpListenerPrivate _priv; +}; + +struct _NMDhcpListenerClass { + GObjectClass parent; +}; + +enum { EVENT, LAST_SIGNAL }; +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE(NMDhcpListener, nm_dhcp_listener, G_TYPE_OBJECT) + +#define NM_DHCP_LISTENER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDhcpListener, NM_IS_DHCP_LISTENER) + +NM_DEFINE_SINGLETON_GETTER(NMDhcpListener, nm_dhcp_listener_get, NM_TYPE_DHCP_LISTENER); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "dhcp-listener" +#define _NMLOG_DOMAIN LOGD_DHCP +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMDhcpListener *_self = (self); \ + char _prefix[64]; \ + \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (_self != singleton_instance \ + ? nm_sprintf_buf(_prefix, "%s[%p]", _NMLOG_PREFIX_NAME, _self) \ + : _NMLOG_PREFIX_NAME) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static char * +get_option(GVariant *options, const char *key) +{ + GVariant * value; + const guchar *bytes, *s; + gsize len; + char * converted, *d; + + if (!g_variant_lookup(options, key, "@ay", &value)) + return NULL; + + bytes = g_variant_get_fixed_array(value, &len, 1); + + /* Since the DHCP options come through environment variables, they should + * already be UTF-8 safe, but just make sure. + */ + converted = g_malloc(len + 1); + for (s = bytes, d = converted; s < bytes + len; s++, d++) { + /* Convert NULLs to spaces and non-ASCII characters to ? */ + if (*s == '\0') + *d = ' '; + else if (*s > 127) + *d = '?'; + else + *d = *s; + } + *d = '\0'; + g_variant_unref(value); + + return converted; +} + +static void +_method_call_handle(NMDhcpListener *self, GVariant *parameters) +{ + gs_free char * iface = NULL; + gs_free char * pid_str = NULL; + gs_free char * reason = NULL; + gs_unref_variant GVariant *options = NULL; + int pid; + gboolean handled = FALSE; + + g_variant_get(parameters, "(@a{sv})", &options); + + iface = get_option(options, "interface"); + if (iface == NULL) { + _LOGW("dhcp-event: didn't have associated interface."); + return; + } + + pid_str = get_option(options, "pid"); + pid = _nm_utils_ascii_str_to_int64(pid_str, 10, 0, G_MAXINT32, -1); + if (pid == -1) { + _LOGW("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ?: "(null)"); + return; + } + + reason = get_option(options, "reason"); + if (reason == NULL) { + _LOGW("dhcp-event: (pid %d) DHCP event didn't have a reason", pid); + return; + } + + g_signal_emit(self, signals[EVENT], 0, iface, pid, options, reason, &handled); + if (!handled) { + if (g_ascii_strcasecmp(reason, "RELEASE") == 0) { + /* Ignore event when the dhcp client gets killed and we receive its last message */ + _LOGD("dhcp-event: (pid %d) unhandled RELEASE DHCP event for interface %s", pid, iface); + } else + _LOGW("dhcp-event: (pid %d) unhandled DHCP event for interface %s", pid, iface); + } +} + +static void +_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMDhcpListener *self = NM_DHCP_LISTENER(user_data); + + if (!nm_streq(interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME) + || !nm_streq(method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY)) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); + return; + } + + _method_call_handle(self, parameters); + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( + NM_DHCP_HELPER_SERVER_INTERFACE_NAME, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO(NM_DHCP_HELPER_SERVER_METHOD_NOTIFY, + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("data", "a{sv}"), ), ), ), ); + +static guint +_dbus_connection_register_object(NMDhcpListener *self, GDBusConnection *connection, GError **error) +{ + static const GDBusInterfaceVTable interface_vtable = { + .method_call = _method_call, + }; + + return g_dbus_connection_register_object( + connection, + NM_DHCP_HELPER_SERVER_OBJECT_PATH, + interface_info, + NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable), + self, + NULL, + error); +} + +static void +new_connection_cb(NMDBusManager * mgr, + GDBusConnection * connection, + GDBusObjectManager *manager, + NMDhcpListener * self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + guint registration_id; + GError * error = NULL; + + /* it is important to register the object during the new-connection signal, + * as this avoids races with the connecting object. */ + registration_id = _dbus_connection_register_object(self, connection, &error); + if (!registration_id) { + _LOGE("failure to register %s for connection %p: %s", + NM_DHCP_HELPER_SERVER_OBJECT_PATH, + connection, + error->message); + g_error_free(error); + return; + } + + g_hash_table_insert(priv->connections, connection, GUINT_TO_POINTER(registration_id)); +} + +static void +dis_connection_cb(NMDBusManager *mgr, GDBusConnection *connection, NMDhcpListener *self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + guint id; + + id = GPOINTER_TO_UINT(g_hash_table_lookup(priv->connections, connection)); + if (id) { + g_dbus_connection_unregister_object(connection, id); + g_hash_table_remove(priv->connections, connection); + } +} + +/*****************************************************************************/ + +static void +nm_dhcp_listener_init(NMDhcpListener *self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + + /* Maps GDBusConnection :: signal-id */ + priv->connections = g_hash_table_new(nm_direct_hash, NULL); + + priv->dbus_mgr = g_object_ref(nm_dbus_manager_get()); + + /* Register the socket our DHCP clients will return lease info on */ + nm_dbus_manager_private_server_register(priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG); + priv->new_conn_id = g_signal_connect(priv->dbus_mgr, + NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG, + G_CALLBACK(new_connection_cb), + self); + priv->dis_conn_id = + g_signal_connect(priv->dbus_mgr, + NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG, + G_CALLBACK(dis_connection_cb), + self); +} + +static void +dispose(GObject *object) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(object); + + nm_clear_g_signal_handler(priv->dbus_mgr, &priv->new_conn_id); + nm_clear_g_signal_handler(priv->dbus_mgr, &priv->dis_conn_id); + + nm_clear_pointer(&priv->connections, g_hash_table_destroy); + + g_clear_object(&priv->dbus_mgr); + + G_OBJECT_CLASS(nm_dhcp_listener_parent_class)->dispose(object); +} + +static void +nm_dhcp_listener_class_init(NMDhcpListenerClass *listener_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(listener_class); + + object_class->dispose = dispose; + + signals[EVENT] = g_signal_new(NM_DHCP_LISTENER_EVENT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, + NULL, + NULL, + G_TYPE_BOOLEAN, /* listeners return TRUE if handled */ + 4, + G_TYPE_STRING, /* iface */ + G_TYPE_INT, /* pid */ + G_TYPE_VARIANT, /* options */ + G_TYPE_STRING); /* reason */ +} diff --git a/src/core/dhcp/nm-dhcp-listener.h b/src/core/dhcp/nm-dhcp-listener.h new file mode 100644 index 0000000..5f3c952 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-listener.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_LISTENER_H__ +#define __NETWORKMANAGER_DHCP_LISTENER_H__ + +#define NM_TYPE_DHCP_LISTENER (nm_dhcp_listener_get_type()) +#define NM_DHCP_LISTENER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_LISTENER, NMDhcpListener)) +#define NM_IS_DHCP_LISTENER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_LISTENER)) +#define NM_DHCP_LISTENER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_LISTENER, NMDhcpListenerClass)) + +#define NM_DHCP_LISTENER_EVENT "event" + +typedef struct _NMDhcpListener NMDhcpListener; +typedef struct _NMDhcpListenerClass NMDhcpListenerClass; + +GType nm_dhcp_listener_get_type(void); + +NMDhcpListener *nm_dhcp_listener_get(void); + +#endif /* __NETWORKMANAGER_DHCP_LISTENER_H__ */ diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c new file mode 100644 index 0000000..ca99122 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-manager.c @@ -0,0 +1,682 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "systemd/nm-sd-utils-shared.h" + +#include "nm-config.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +typedef struct { + const NMDhcpClientFactory *client_factory; + char * default_hostname; + CList dhcp_client_lst_head; +} NMDhcpManagerPrivate; + +struct _NMDhcpManager { + GObject parent; + NMDhcpManagerPrivate _priv; +}; + +struct _NMDhcpManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMDhcpManager, nm_dhcp_manager, G_TYPE_OBJECT) + +#define NM_DHCP_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpManager, NM_IS_DHCP_MANAGER) + +/*****************************************************************************/ + +static void client_state_changed(NMDhcpClient * client, + NMDhcpState state, + GObject * ip_config, + GVariant * options, + NMDhcpManager *self); + +/*****************************************************************************/ + +/* default to installed helper, but can be modified for testing */ +const char *nm_dhcp_helper_path = LIBEXECDIR "/nm-dhcp-helper"; + +/*****************************************************************************/ + +static const NMDhcpClientFactory * +_client_factory_find_by_name(const char *name) +{ + int i; + + g_return_val_if_fail(name, NULL); + + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i]; + + if (f && nm_streq(f->name, name)) + return f; + } + return NULL; +} + +static const NMDhcpClientFactory * +_client_factory_available(const NMDhcpClientFactory *client_factory) +{ + if (client_factory && (!client_factory->get_path || client_factory->get_path())) + return client_factory; + return NULL; +} + +static GType +_client_factory_get_gtype(const NMDhcpClientFactory *client_factory, int addr_family) +{ + GType gtype; + nm_auto_unref_gtypeclass NMDhcpClientClass *klass = NULL; + + nm_assert(client_factory); + nm_assert_addr_family(addr_family); + + /* currently, the chosen DHCP plugin for IPv4 and IPv6 is configured in NetworkManager.conf + * and cannot be reloaded. It would be nice to configure the plugin per address family + * or to be able to reload it. + * + * Note that certain options in NetworkManager.conf depend on the chosen DHCP plugin. + * See "dhcp-plugin:" in "Device List Format" (`man NetworkManager.conf`). + * Supporting reloading the plugin would also require to re-evalate the decisions from + * the "Device List Format". Likewise, having per-address family plugins would make the + * "main.dhcp" setting and "dhcp-plugin:" match non-sensical because these configurations + * currently are address family independent. + * + * So actually, we don't want that complexity. We want to phase out all plugins in favor + * of the internal plugin. + * However, certain existing plugins are well known to not support an address family. + * In those cases, we should just silently fallback to the internal plugin. + * + * This could be a problem with forward compatibility if we ever intended to add IPv6 support + * to those plugins. But we don't intend to do so. The internal plugin is the way forward and + * not extending other plugins. */ + + if (client_factory->get_type_per_addr_family) + gtype = client_factory->get_type_per_addr_family(addr_family); + else + gtype = client_factory->get_type(); + + if (client_factory == &_nm_dhcp_client_factory_internal) { + /* we are already using the internal plugin. Nothing to do. */ + goto out; + } + + klass = g_type_class_ref(gtype); + + nm_assert(NM_IS_DHCP_CLIENT_CLASS(klass)); + + if (addr_family == AF_INET6) { + if (!klass->ip6_start) + gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family); + } else { + if (!klass->ip4_start) + gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family); + } + +out: + nm_assert(g_type_is_a(gtype, NM_TYPE_DHCP_CLIENT)); + nm_assert(({ + nm_auto_unref_gtypeclass NMDhcpClientClass *k = g_type_class_ref(gtype); + + (addr_family == AF_INET6 && k->ip6_start) || (addr_family == AF_INET && k->ip4_start); + })); + + return gtype; +} + +/*****************************************************************************/ + +static NMDhcpClient * +get_client_for_ifindex(NMDhcpManager *manager, int addr_family, int ifindex) +{ + NMDhcpManagerPrivate *priv; + NMDhcpClient * client; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(manager), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + priv = NM_DHCP_MANAGER_GET_PRIVATE(manager); + + c_list_for_each_entry (client, &priv->dhcp_client_lst_head, dhcp_client_lst) { + if (nm_dhcp_client_get_ifindex(client) == ifindex + && nm_dhcp_client_get_addr_family(client) == addr_family) + return client; + } + + return NULL; +} + +static void +remove_client(NMDhcpManager *self, NMDhcpClient *client) +{ + g_signal_handlers_disconnect_by_func(client, client_state_changed, self); + c_list_unlink(&client->dhcp_client_lst); + + /* Stopping the client is left up to the controlling device + * explicitly since we may want to quit NetworkManager but not terminate + * the DHCP client. + */ +} + +static void +remove_client_unref(NMDhcpManager *self, NMDhcpClient *client) +{ + remove_client(self, client); + g_object_unref(client); +} + +static void +client_state_changed(NMDhcpClient * client, + NMDhcpState state, + GObject * ip_config, + GVariant * options, + NMDhcpManager *self) +{ + if (state >= NM_DHCP_STATE_TIMEOUT) + remove_client_unref(self, client); +} + +static NMDhcpClient * +client_start(NMDhcpManager * self, + int addr_family, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + GBytes * hwaddr, + GBytes * bcast_hwaddr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + const struct in6_addr * ipv6_ll_addr, + GBytes * dhcp_client_id, + gboolean enforce_duid, + guint32 iaid, + gboolean iaid_explicit, + guint32 timeout, + const char * dhcp_anycast_addr, + const char * hostname, + gboolean hostname_use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + const char * last_ip4_address, + guint needed_prefixes, + GBytes * vendor_class_identifier, + const char *const * reject_servers, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + NMDhcpClient * client; + gboolean success = FALSE; + gsize hwaddr_len; + GType gtype; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + g_return_val_if_fail(iface, NULL); + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(uuid != NULL, NULL); + g_return_val_if_fail(!dhcp_client_id || g_bytes_get_size(dhcp_client_id) >= 2, NULL); + g_return_val_if_fail(!vendor_class_identifier + || g_bytes_get_size(vendor_class_identifier) <= 255, + NULL); + g_return_val_if_fail(!error || !*error, NULL); + + if (addr_family == AF_INET) { + if (!hwaddr || !bcast_hwaddr) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "missing %s address", + hwaddr ? "broadcast" : "MAC"); + return NULL; + } + + hwaddr_len = g_bytes_get_size(hwaddr); + if (hwaddr_len == 0 || hwaddr_len > NM_UTILS_HWADDR_LEN_MAX) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address"); + g_return_val_if_reached(NULL); + } + nm_assert(g_bytes_get_size(hwaddr) == g_bytes_get_size(bcast_hwaddr)); + } else { + hwaddr = NULL; + bcast_hwaddr = NULL; + } + + if (hostname) { + if ((hostname_use_fqdn && !nm_sd_dns_name_is_valid(hostname)) + || (!hostname_use_fqdn && !nm_sd_hostname_is_valid(hostname, FALSE))) { + nm_log_warn(LOGD_DHCP, + "dhcp%c: %s '%s' is invalid, will be ignored", + nm_utils_addr_family_to_char(addr_family), + hostname_use_fqdn ? "FQDN" : "hostname", + hostname); + hostname = NULL; + } + } + + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + /* Kill any old client instance */ + client = get_client_for_ifindex(self, addr_family, ifindex); + if (client) { + /* FIXME: we cannot just call synchronously "stop()" and forget about the client. + * We need to wait for the client to be fully stopped because most/all clients + * cannot quit right away. + * + * FIXME(shutdown): also fix this during shutdown, to wait for all DHCP clients + * to be fully stopped. */ + remove_client(self, client); + nm_dhcp_client_stop(client, FALSE); + g_object_unref(client); + } + + gtype = _client_factory_get_gtype(priv->client_factory, addr_family); + + nm_log_trace(LOGD_DHCP, + "dhcp%c: creating IPv%c DHCP client of type %s", + nm_utils_addr_family_to_char(addr_family), + nm_utils_addr_family_to_char(addr_family), + g_type_name(gtype)); + + client = g_object_new(gtype, + NM_DHCP_CLIENT_MULTI_IDX, + multi_idx, + NM_DHCP_CLIENT_ADDR_FAMILY, + addr_family, + NM_DHCP_CLIENT_INTERFACE, + iface, + NM_DHCP_CLIENT_IFINDEX, + ifindex, + NM_DHCP_CLIENT_HWADDR, + hwaddr, + NM_DHCP_CLIENT_BROADCAST_HWADDR, + bcast_hwaddr, + NM_DHCP_CLIENT_UUID, + uuid, + NM_DHCP_CLIENT_IAID, + (guint) iaid, + NM_DHCP_CLIENT_IAID_EXPLICIT, + iaid_explicit, + NM_DHCP_CLIENT_HOSTNAME, + hostname, + NM_DHCP_CLIENT_MUD_URL, + mud_url, + NM_DHCP_CLIENT_ROUTE_TABLE, + (guint) route_table, + NM_DHCP_CLIENT_ROUTE_METRIC, + (guint) route_metric, + NM_DHCP_CLIENT_TIMEOUT, + (guint) timeout, + NM_DHCP_CLIENT_HOSTNAME_FLAGS, + (guint) hostname_flags, + NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER, + vendor_class_identifier, + NM_DHCP_CLIENT_REJECT_SERVERS, + reject_servers, + NM_DHCP_CLIENT_FLAGS, + (guint)(0 | (hostname_use_fqdn ? NM_DHCP_CLIENT_FLAGS_USE_FQDN : 0) + | (info_only ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY : 0)), + NULL); + nm_assert(client && c_list_is_empty(&client->dhcp_client_lst)); + c_list_link_tail(&priv->dhcp_client_lst_head, &client->dhcp_client_lst); + g_signal_connect(client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK(client_state_changed), + self); + + /* unfortunately, our implementations work differently per address-family regarding client-id/DUID. + * + * - for IPv4, the calling code may determine a client-id (from NM's connection profile). + * If present, it is taken. If not present, the DHCP plugin uses a plugin specific default. + * - for "internal" plugin, the default is just "mac". + * - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback + * to whatever dhclient uses by default. + * We do it this way, because for dhclient the user may configure a default + * outside of NM, and we want to honor that. Worse, dhclient could be a wapper + * script where the wrapper script overwrites the client-id. We need to distinguish + * between: force a particular client-id and leave it unspecified to whatever dhclient + * wants. + * + * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid, + * to determine whether the given client-id must be used. + * - for "internal" plugin @enforce_duid doesn't matter and the given client-id is + * always used. + * - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the + * lease file, and only otherwise fallback to the given client-id. + * - other plugins don't support DHCPv6. + * It's done this way, so that existing dhclient setups don't change behavior on upgrade. + * + * This difference is cumbersome and only exists because of "dhclient" which supports hacking the + * default outside of NetworkManager API. + */ + + if (addr_family == AF_INET) { + success = nm_dhcp_client_start_ip4(client, + dhcp_client_id, + dhcp_anycast_addr, + last_ip4_address, + error); + } else { + success = nm_dhcp_client_start_ip6(client, + dhcp_client_id, + enforce_duid, + dhcp_anycast_addr, + ipv6_ll_addr, + privacy, + needed_prefixes, + error); + } + + if (!success) { + remove_client_unref(self, client); + return NULL; + } + + return g_object_ref(client); +} + +/* Caller owns a reference to the NMDhcpClient on return */ +NMDhcpClient * +nm_dhcp_manager_start_ip4(NMDhcpManager * self, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + GBytes * hwaddr, + GBytes * bcast_hwaddr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + const char * dhcp_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * dhcp_client_id, + guint32 timeout, + const char * dhcp_anycast_addr, + const char * last_ip_address, + GBytes * vendor_class_identifier, + const char *const * reject_servers, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + const char * hostname = NULL; + gs_free char * hostname_tmp = NULL; + gboolean use_fqdn = FALSE; + char * dot; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + if (send_hostname) { + /* Use, in order of preference: + * 1. FQDN from configuration + * 2. hostname from configuration + * 3. system hostname (only host part) + */ + if (dhcp_fqdn) { + hostname = dhcp_fqdn; + use_fqdn = TRUE; + } else if (dhcp_hostname) + hostname = dhcp_hostname; + else { + hostname = priv->default_hostname; + if (hostname) { + hostname_tmp = g_strdup(hostname); + dot = strchr(hostname_tmp, '.'); + if (dot) + *dot = '\0'; + hostname = hostname_tmp; + } + } + } + + return client_start(self, + AF_INET, + multi_idx, + iface, + ifindex, + hwaddr, + bcast_hwaddr, + uuid, + route_table, + route_metric, + NULL, + dhcp_client_id, + FALSE, + 0, + FALSE, + timeout, + dhcp_anycast_addr, + hostname, + use_fqdn, + hostname_flags, + mud_url, + FALSE, + 0, + last_ip_address, + 0, + vendor_class_identifier, + reject_servers, + error); +} + +/* Caller owns a reference to the NMDhcpClient on return */ +NMDhcpClient * +nm_dhcp_manager_start_ip6(NMDhcpManager * self, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + const struct in6_addr * ll_addr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * duid, + gboolean enforce_duid, + guint32 iaid, + gboolean iaid_explicit, + guint32 timeout, + const char * dhcp_anycast_addr, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + const char * hostname = NULL; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + if (send_hostname) { + /* Always prefer the explicit dhcp-hostname if given */ + hostname = dhcp_hostname ?: priv->default_hostname; + } + return client_start(self, + AF_INET6, + multi_idx, + iface, + ifindex, + NULL, + NULL, + uuid, + route_table, + route_metric, + ll_addr, + duid, + enforce_duid, + iaid, + iaid_explicit, + timeout, + dhcp_anycast_addr, + hostname, + TRUE, + hostname_flags, + mud_url, + info_only, + privacy, + NULL, + needed_prefixes, + NULL, + NULL, + error); +} + +void +nm_dhcp_manager_set_default_hostname(NMDhcpManager *manager, const char *hostname) +{ + NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE(manager); + + nm_clear_g_free(&priv->default_hostname); + + /* Never send 'localhost'-type names to the DHCP server */ + if (!nm_utils_is_specific_hostname(hostname)) + return; + + priv->default_hostname = g_strdup(hostname); +} + +const char * +nm_dhcp_manager_get_config(NMDhcpManager *self) +{ + const NMDhcpClientFactory *factory; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + + factory = NM_DHCP_MANAGER_GET_PRIVATE(self)->client_factory; + return factory ? factory->name : NULL; +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER); + +void +nmtst_dhcp_manager_unget(gpointer self) +{ + _nmtst_nm_dhcp_manager_get_reset(self); +} + +static void +nm_dhcp_manager_init(NMDhcpManager *self) +{ + NMDhcpManagerPrivate * priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + NMConfig * config = nm_config_get(); + gs_free char * client_free = NULL; + const char * client; + int i; + const NMDhcpClientFactory *client_factory = NULL; + + c_list_init(&priv->dhcp_client_lst_head); + + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i]; + + if (!f) + continue; + + nm_log_dbg(LOGD_DHCP, + "dhcp-init: enabled DHCP client '%s'%s%s", + f->name, + _client_factory_available(f) ? "" : " (not available)", + f->experimental ? " (undocumented internal plugin)" : ""); + } + + /* Client-specific setup */ + client_free = + nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + client = client_free; + if (nm_config_get_configure_and_quit(config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { + client_factory = &_nm_dhcp_client_factory_internal; + if (client && !nm_streq(client, client_factory->name)) + nm_log_info(LOGD_DHCP, + "dhcp-init: Using internal DHCP client since configure-and-quit is set."); + } else { + if (client) { + client_factory = _client_factory_available(_client_factory_find_by_name(client)); + if (!client_factory) + nm_log_warn(LOGD_DHCP, "dhcp-init: DHCP client '%s' not available", client); + } + if (!client_factory) { + client_factory = _client_factory_find_by_name("" NM_CONFIG_DEFAULT_MAIN_DHCP); + if (!client_factory) + nm_log_err(LOGD_DHCP, + "dhcp-init: default DHCP client '%s' is not installed", + NM_CONFIG_DEFAULT_MAIN_DHCP); + else { + client_factory = _client_factory_available(client_factory); + if (!client_factory) + nm_log_info(LOGD_DHCP, + "dhcp-init: default DHCP client '%s' is not available", + NM_CONFIG_DEFAULT_MAIN_DHCP); + } + } + if (!client_factory) { + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + client_factory = _client_factory_available(_nm_dhcp_manager_factories[i]); + if (client_factory) + break; + } + } + } + + g_return_if_fail(client_factory); + + nm_log_info(LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name); + + /* NOTE: currently the DHCP plugin is chosen once at start. It's not + * possible to reload that configuration. If that ever becomes possible, + * beware that the "dhcp-plugin" device spec made decisions based on + * the previous plugin and may need reevaluation. */ + priv->client_factory = client_factory; +} + +static void +dispose(GObject *object) +{ + NMDhcpManager * self = NM_DHCP_MANAGER(object); + NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + NMDhcpClient * client, *client_safe; + + c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst) + remove_client_unref(self, client); + + G_OBJECT_CLASS(nm_dhcp_manager_parent_class)->dispose(object); + + nm_clear_g_free(&priv->default_hostname); +} + +static void +nm_dhcp_manager_class_init(NMDhcpManagerClass *manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(manager_class); + + object_class->dispose = dispose; +} diff --git a/src/core/dhcp/nm-dhcp-manager.h b/src/core/dhcp/nm-dhcp-manager.h new file mode 100644 index 0000000..f7aba8a --- /dev/null +++ b/src/core/dhcp/nm-dhcp-manager.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2010 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_MANAGER_H__ +#define __NETWORKMANAGER_DHCP_MANAGER_H__ + +#include "nm-dhcp-client.h" +#include "nm-ip4-config.h" +#include "nm-dhcp-config.h" + +#define NM_TYPE_DHCP_MANAGER (nm_dhcp_manager_get_type()) +#define NM_DHCP_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_MANAGER, NMDhcpManager)) +#define NM_DHCP_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_MANAGER, NMDhcpManagerClass)) +#define NM_IS_DHCP_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_MANAGER)) +#define NM_IS_DHCP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_MANAGER)) +#define NM_DHCP_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_MANAGER, NMDhcpManagerClass)) + +typedef struct _NMDhcpManager NMDhcpManager; +typedef struct _NMDhcpManagerClass NMDhcpManagerClass; + +GType nm_dhcp_manager_get_type(void); + +NMDhcpManager *nm_dhcp_manager_get(void); + +const char *nm_dhcp_manager_get_config(NMDhcpManager *self); + +void nm_dhcp_manager_set_default_hostname(NMDhcpManager *manager, const char *hostname); + +NMDhcpClient *nm_dhcp_manager_start_ip4(NMDhcpManager * manager, + struct _NMDedupMultiIndex *multi_idx, + const char * iface, + int ifindex, + GBytes * hwaddr, + GBytes * bcast_hwaddr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + const char * dhcp_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * dhcp_client_id, + guint32 timeout, + const char * dhcp_anycast_addr, + const char * last_ip_address, + GBytes * vendor_class_identifier, + const char *const * reject_servers, + GError ** error); + +NMDhcpClient *nm_dhcp_manager_start_ip6(NMDhcpManager * manager, + struct _NMDedupMultiIndex *multi_idx, + const char * iface, + int ifindex, + const struct in6_addr * ll_addr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * duid, + gboolean enforce_duid, + guint32 iaid, + gboolean iaid_explicit, + guint32 timeout, + const char * dhcp_anycast_addr, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error); + +/* For testing only */ +extern const char *nm_dhcp_helper_path; + +extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6]; + +void nmtst_dhcp_manager_unget(gpointer singleton_instance); + +#endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */ diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c new file mode 100644 index 0000000..87e17f5 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-nettools.c @@ -0,0 +1,1533 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-sd-adapt-shared.h" + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-std-aux/unaligned.h" + +#include "nm-utils.h" +#include "nm-config.h" +#include "nm-dhcp-utils.h" +#include "nm-dhcp-options.h" +#include "nm-core-utils.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-dhcp-client-logging.h" +#include "n-dhcp4/src/n-dhcp4.h" +#include "systemd/nm-sd-utils-shared.h" +#include "systemd/nm-sd-utils-dhcp.h" + +/*****************************************************************************/ + +#define NM_TYPE_DHCP_NETTOOLS (nm_dhcp_nettools_get_type()) +#define NM_DHCP_NETTOOLS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettools)) +#define NM_DHCP_NETTOOLS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass)) +#define NM_IS_DHCP_NETTOOLS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_NETTOOLS)) +#define NM_IS_DHCP_NETTOOLS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_NETTOOLS)) +#define NM_DHCP_NETTOOLS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass)) + +typedef struct _NMDhcpNettools NMDhcpNettools; +typedef struct _NMDhcpNettoolsClass NMDhcpNettoolsClass; + +/*****************************************************************************/ + +typedef struct { + NDhcp4Client * client; + NDhcp4ClientProbe *probe; + NDhcp4ClientLease *lease; + GSource * event_source; + char * lease_file; +} NMDhcpNettoolsPrivate; + +struct _NMDhcpNettools { + NMDhcpClient parent; + NMDhcpNettoolsPrivate _priv; +}; + +struct _NMDhcpNettoolsClass { + NMDhcpClientClass parent; +}; + +G_DEFINE_TYPE(NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT) + +#define NM_DHCP_NETTOOLS_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDhcpNettools, NM_IS_DHCP_NETTOOLS) + +/*****************************************************************************/ + +static void +set_error_nettools(GError **error, int r, const char *message) +{ + /* the error code returned from n_dhcp4_* API is either a negative + * errno, or a positive internal error code. Generate different messages + * for these. */ + if (r < 0) + nm_utils_error_set_errno(error, r, "%s: %s", message); + else + nm_utils_error_set(error, r, "%s (code %d)", message, r); +} + +/*****************************************************************************/ + +#define DHCP_MAX_FQDN_LENGTH 255 + +enum { + NM_IN_ADDR_CLASS_A, + NM_IN_ADDR_CLASS_B, + NM_IN_ADDR_CLASS_C, + NM_IN_ADDR_CLASS_INVALID, +}; + +static int +in_addr_class(struct in_addr addr) +{ + switch (ntohl(addr.s_addr) >> 24) { + case 0 ... 127: + return NM_IN_ADDR_CLASS_A; + case 128 ... 191: + return NM_IN_ADDR_CLASS_B; + case 192 ... 223: + return NM_IN_ADDR_CLASS_C; + default: + return NM_IN_ADDR_CLASS_INVALID; + } +} + +static gboolean +lease_option_consume(void *out, size_t n_out, uint8_t **datap, size_t *n_datap) +{ + if (*n_datap < n_out) + return FALSE; + + memcpy(out, *datap, n_out); + *datap += n_out; + *n_datap -= n_out; + return TRUE; +} + +static gboolean +lease_option_next_in_addr(struct in_addr *addrp, uint8_t **datap, size_t *n_datap) +{ + return lease_option_consume(addrp, sizeof(struct in_addr), datap, n_datap); +} + +static gboolean +lease_option_next_route(struct in_addr *destp, + uint8_t * plenp, + struct in_addr *gatewayp, + gboolean classless, + uint8_t ** datap, + size_t * n_datap) +{ + struct in_addr dest = {}, gateway; + uint8_t * data = *datap; + size_t n_data = *n_datap; + uint8_t plen; + uint8_t bytes; + + if (classless) { + if (!lease_option_consume(&plen, sizeof(plen), &data, &n_data)) + return FALSE; + + if (plen > 32) + return FALSE; + + bytes = plen == 0 ? 0 : ((plen - 1) / 8) + 1; + + if (!lease_option_consume(&dest, bytes, &data, &n_data)) + return FALSE; + } else { + if (!lease_option_next_in_addr(&dest, &data, &n_data)) + return FALSE; + + switch (in_addr_class(dest)) { + case NM_IN_ADDR_CLASS_A: + plen = 8; + break; + case NM_IN_ADDR_CLASS_B: + plen = 16; + break; + case NM_IN_ADDR_CLASS_C: + plen = 24; + break; + case NM_IN_ADDR_CLASS_INVALID: + return FALSE; + } + } + + dest.s_addr = nm_utils_ip4_address_clear_host_address(dest.s_addr, plen); + + if (!lease_option_next_in_addr(&gateway, &data, &n_data)) + return FALSE; + + *destp = dest; + *plenp = plen; + *gatewayp = gateway; + *datap = data; + *n_datap = n_data; + return TRUE; +} + +static gboolean +lease_option_print_label(GString *str, size_t n_label, uint8_t **datap, size_t *n_datap) +{ + for (size_t i = 0; i < n_label; ++i) { + uint8_t c; + + if (!lease_option_consume(&c, sizeof(c), datap, n_datap)) + return FALSE; + + switch (c) { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '-': + case '_': + g_string_append_c(str, c); + break; + case '.': + case '\\': + g_string_append_printf(str, "\\%c", c); + break; + default: + g_string_append_printf(str, "\\%3d", c); + } + } + + return TRUE; +} + +static gboolean +lease_option_print_domain_name(GString * str, + uint8_t * cache, + size_t * n_cachep, + uint8_t **datap, + size_t * n_datap) +{ + uint8_t * domain; + size_t n_domain, n_cache = *n_cachep; + uint8_t **domainp = datap; + size_t * n_domainp = n_datap; + gboolean first = TRUE; + uint8_t c; + + /* + * We are given two adjacent memory regions. The @cache contains alreday parsed + * domain names, and the @datap contains the remaining data to parse. + * + * A domain name is formed from a sequence of labels. Each label start with + * a length byte, where the two most significant bits are unset. A zero-length + * label indicates the end of the domain name. + * + * Alternatively, a label can be followed by an offset (indicated by the two + * most significant bits being set in the next byte that is read). The offset + * is an offset into the cache, where the next label of the domain name can + * be found. + * + * Note, that each time a jump to an offset is performed, the size of the + * cache shrinks, so this is guaranteed to terminate. + */ + if (cache + n_cache != *datap) + return FALSE; + + for (;;) { + if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp)) + return FALSE; + + switch (c & 0xC0) { + case 0x00: /* label length */ + { + size_t n_label = c; + + if (n_label == 0) { + /* + * We reached the final label of the domain name. Adjust + * the cache to include the consumed data, and return. + */ + *n_cachep = *datap - cache; + return TRUE; + } + + if (!first) + g_string_append_c(str, '.'); + else + first = FALSE; + + if (!lease_option_print_label(str, n_label, domainp, n_domainp)) + return FALSE; + + break; + } + case 0xC0: /* back pointer */ + { + size_t offset = (c & 0x3F) << 16; + + /* + * The offset is given as two bytes (in big endian), where the + * two high bits are masked out. + */ + + if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp)) + return FALSE; + + offset += c; + + if (offset >= n_cache) + return FALSE; + + domain = cache + offset; + n_domain = n_cache - offset; + n_cache = offset; + + domainp = &domain; + n_domainp = &n_domain; + + break; + } + default: + return FALSE; + } + } +} + +static gboolean +lease_get_in_addr(NDhcp4ClientLease *lease, guint8 option, struct in_addr *addrp) +{ + struct in_addr addr; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); + if (r) + return FALSE; + + if (!lease_option_next_in_addr(&addr, &data, &n_data)) + return FALSE; + + if (n_data != 0) + return FALSE; + + *addrp = addr; + return TRUE; +} + +static gboolean +lease_get_u16(NDhcp4ClientLease *lease, uint8_t option, uint16_t *u16p) +{ + uint8_t *data; + size_t n_data; + uint16_t be16; + int r; + + r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); + if (r) + return FALSE; + + if (n_data != sizeof(be16)) + return FALSE; + + memcpy(&be16, data, sizeof(be16)); + + *u16p = ntohs(be16); + return TRUE; +} + +static gboolean +lease_parse_address(NDhcp4ClientLease *lease, + NMIP4Config * ip4_config, + GHashTable * options, + GError ** error) +{ + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + struct in_addr a_address; + struct in_addr a_netmask; + struct in_addr a_next_server; + guint32 a_plen; + guint64 nettools_lifetime; + guint32 a_lifetime; + guint32 a_timestamp; + guint64 a_expiry; + + n_dhcp4_client_lease_get_yiaddr(lease, &a_address); + if (a_address.s_addr == INADDR_ANY) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "could not get address from lease"); + return FALSE; + } + + n_dhcp4_client_lease_get_lifetime(lease, &nettools_lifetime); + + if (nettools_lifetime == G_MAXUINT64) { + a_timestamp = 0; + a_lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + a_expiry = G_MAXUINT64; + } else { + guint64 nettools_basetime; + guint64 lifetime; + gint64 ts; + + n_dhcp4_client_lease_get_basetime(lease, &nettools_basetime); + + /* usually we shouldn't assert against external libraries like n-dhcp4. + * Here we still do it... it seems safe enough. */ + nm_assert(nettools_basetime > 0); + nm_assert(nettools_lifetime >= nettools_basetime); + nm_assert(((nettools_lifetime - nettools_basetime) % NM_UTILS_NSEC_PER_SEC) == 0); + nm_assert((nettools_lifetime - nettools_basetime) / NM_UTILS_NSEC_PER_SEC <= G_MAXUINT32); + + if (nettools_lifetime <= nettools_basetime) { + /* A lease time of 0 is allowed on some dhcp servers, so, let's accept it. */ + lifetime = 0; + } else { + lifetime = nettools_lifetime - nettools_basetime; + + /* we "ceil" the value to the next second. In practice, we don't expect any sub-second values + * from n-dhcp4 anyway, so this should have no effect. */ + lifetime += NM_UTILS_NSEC_PER_SEC - 1; + } + + ts = nm_utils_monotonic_timestamp_from_boottime(nettools_basetime, 1); + + /* the timestamp must be positive, because we only started nettools DHCP client + * after obtaining the first monotonic timestamp. Hence, the lease must have been + * received afterwards. */ + nm_assert(ts >= NM_UTILS_NSEC_PER_SEC); + + a_timestamp = ts / NM_UTILS_NSEC_PER_SEC; + a_lifetime = NM_MIN(lifetime / NM_UTILS_NSEC_PER_SEC, NM_PLATFORM_LIFETIME_PERMANENT - 1); + a_expiry = time(NULL) + + ((lifetime - (nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME) - nettools_basetime)) + / NM_UTILS_NSEC_PER_SEC); + } + + if (!lease_get_in_addr(lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "could not get netmask from lease"); + return FALSE; + } + + _nm_utils_inet4_ntop(a_address.s_addr, addr_str); + a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr); + + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, + addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_SUBNET_MASK, + _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str)); + + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, + (guint64) a_lifetime); + + if (a_expiry != G_MAXUINT64) { + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_EXPIRY, + a_expiry); + } + + n_dhcp4_client_lease_get_siaddr(lease, &a_next_server); + if (a_next_server.s_addr != INADDR_ANY) { + _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, + addr_str); + } + + nm_ip4_config_add_address(ip4_config, + &((const NMPlatformIP4Address){ + .address = a_address.s_addr, + .peer_address = a_address.s_addr, + .plen = a_plen, + .addr_source = NM_IP_CONFIG_SOURCE_DHCP, + .timestamp = a_timestamp, + .lifetime = a_lifetime, + .preferred = a_lifetime, + })); + + return TRUE; +} + +static void +lease_parse_address_list(NDhcp4ClientLease * lease, + NMIP4Config * ip4_config, + NMDhcpOptionDhcp4Options option, + GHashTable * options) +{ + nm_auto_free_gstring GString *str = NULL; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + struct in_addr addr; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); + if (r) + return; + + nm_gstring_prepare(&str); + + while (lease_option_next_in_addr(&addr, &data, &n_data)) { + _nm_utils_inet4_ntop(addr.s_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + + switch (option) { + case NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER: + if (addr.s_addr == 0 || nm_ip4_addr_is_localhost(addr.s_addr)) { + /* Skip localhost addresses, like also networkd does. + * See https://github.com/systemd/systemd/issues/4524. */ + continue; + } + nm_ip4_config_add_nameserver(ip4_config, addr.s_addr); + break; + case NM_DHCP_OPTION_DHCP4_NIS_SERVERS: + nm_ip4_config_add_nis_server(ip4_config, addr.s_addr); + break; + case NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER: + nm_ip4_config_add_wins(ip4_config, addr.s_addr); + break; + default: + nm_assert_not_reached(); + } + } + + nm_dhcp_option_add_option(options, _nm_dhcp_option_dhcp4_options, option, str->str); +} + +static void +lease_parse_routes(NDhcp4ClientLease *lease, + NMIP4Config * ip4_config, + GHashTable * options, + guint32 route_table, + guint32 route_metric) +{ + nm_auto_free_gstring GString *str = NULL; + char dest_str[NM_UTILS_INET_ADDRSTRLEN]; + char gateway_str[NM_UTILS_INET_ADDRSTRLEN]; + const char * s; + struct in_addr dest, gateway; + uint8_t plen; + guint32 m; + gboolean has_router_from_classless = FALSE, has_classless = FALSE; + guint32 default_route_metric = route_metric; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, + &data, + &n_data); + if (!r) { + nm_gstring_prepare(&str); + + has_classless = TRUE; + + while (lease_option_next_route(&dest, &plen, &gateway, TRUE, &data, &n_data)) { + _nm_utils_inet4_ntop(dest.s_addr, dest_str); + _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); + + g_string_append_printf(nm_gstring_add_space_delimiter(str), + "%s/%d %s", + dest_str, + (int) plen, + gateway_str); + + if (plen == 0) { + /* if there are multiple default routes, we add them with differing + * metrics. */ + m = default_route_metric; + if (default_route_metric < G_MAXUINT32) + default_route_metric++; + + has_router_from_classless = TRUE; + } else { + m = route_metric; + } + + nm_ip4_config_add_route( + ip4_config, + &((const NMPlatformIP4Route){ + .network = dest.s_addr, + .plen = plen, + .gateway = gateway.s_addr, + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .metric = m, + .table_coerced = nm_platform_route_table_coerce(route_table), + }), + NULL); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, + str->str); + } + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data); + if (!r) { + nm_gstring_prepare(&str); + + while (lease_option_next_route(&dest, &plen, &gateway, FALSE, &data, &n_data)) { + _nm_utils_inet4_ntop(dest.s_addr, dest_str); + _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); + + g_string_append_printf(nm_gstring_add_space_delimiter(str), + "%s/%d %s", + dest_str, + (int) plen, + gateway_str); + + if (has_classless) { + /* RFC 3443: if the DHCP server returns both a Classless Static Routes + * option and a Static Routes option, the DHCP client MUST ignore the + * Static Routes option. */ + continue; + } + + if (plen == 0) { + /* for option 33 (static route), RFC 2132 says: + * + * The default route (0.0.0.0) is an illegal destination for a static + * route. */ + continue; + } + + nm_ip4_config_add_route( + ip4_config, + &((const NMPlatformIP4Route){ + .network = dest.s_addr, + .plen = plen, + .gateway = gateway.s_addr, + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .metric = route_metric, + .table_coerced = nm_platform_route_table_coerce(route_table), + }), + NULL); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, + str->str); + } + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data); + if (!r) { + nm_gstring_prepare(&str); + + while (lease_option_next_in_addr(&gateway, &data, &n_data)) { + s = _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); + g_string_append(nm_gstring_add_space_delimiter(str), s); + + if (gateway.s_addr == 0) { + /* silently skip 0.0.0.0 */ + continue; + } + + if (has_router_from_classless) { + /* If the DHCP server returns both a Classless Static Routes option and a + * Router option, the DHCP client MUST ignore the Router option [RFC 3442]. + * + * Be more lenient and ignore the Router option only if Classless Static + * Routes contain a default gateway (as other DHCP backends do). + */ + continue; + } + + /* if there are multiple default routes, we add them with differing + * metrics. */ + m = default_route_metric; + if (default_route_metric < G_MAXUINT32) + default_route_metric++; + + nm_ip4_config_add_route( + ip4_config, + &((const NMPlatformIP4Route){ + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .gateway = gateway.s_addr, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = m, + }), + NULL); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_ROUTER, + str->str); + } +} + +static void +lease_parse_mtu(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) +{ + uint16_t mtu; + + if (!lease_get_u16(lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu)) + return; + + if (mtu < 68) + return; + + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, + mtu); + nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP); +} + +static void +lease_parse_metered(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) +{ + gboolean metered = FALSE; + uint8_t *data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data); + if (r) + metered = FALSE; + else + metered = !!memmem(data, n_data, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED")); + + /* TODO: expose the vendor specific option when present */ + nm_ip4_config_set_metered(ip4_config, metered); +} + +static void +lease_parse_ntps(NDhcp4ClientLease *lease, GHashTable *options) +{ + nm_auto_free_gstring GString *str = NULL; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + struct in_addr addr; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data); + if (r) + return; + + nm_gstring_prepare(&str); + + while (lease_option_next_in_addr(&addr, &data, &n_data)) { + _nm_utils_inet4_ntop(addr.s_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + } + + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NTP_SERVER, + str->str); +} + +static void +lease_parse_hostname(NDhcp4ClientLease *lease, GHashTable *options) +{ + nm_auto_free_gstring GString *str = NULL; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data); + if (r) + return; + + str = g_string_new_len((char *) data, n_data); + + if (nm_utils_is_localhost(str->str)) + return; + + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_HOST_NAME, + str->str); +} + +static void +lease_parse_domainname(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) +{ + nm_auto_free_gstring GString *str = NULL; + gs_strfreev char ** domains = NULL; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data); + if (r) + return; + + str = g_string_new_len((char *) data, n_data); + + /* Multiple domains sometimes stuffed into option 15 "Domain Name". */ + domains = g_strsplit(str->str, " ", 0); + nm_gstring_prepare(&str); + + for (char **d = domains; *d; d++) { + if (nm_utils_is_localhost(*d)) + return; + + g_string_append(nm_gstring_add_space_delimiter(str), *d); + nm_ip4_config_add_domain(ip4_config, *d); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, + str->str); +} + +char ** +nm_dhcp_parse_search_list(guint8 *data, size_t n_data) +{ + GPtrArray *array = NULL; + guint8 * cache = data; + size_t n_cache = 0; + + for (;;) { + nm_auto_free_gstring GString *domain = NULL; + + nm_gstring_prepare(&domain); + + if (!lease_option_print_domain_name(domain, cache, &n_cache, &data, &n_data)) + break; + + if (!array) + array = g_ptr_array_new(); + + g_ptr_array_add(array, g_string_free(domain, FALSE)); + domain = NULL; + } + + if (array) { + g_ptr_array_add(array, NULL); + return (char **) g_ptr_array_free(array, FALSE); + } else + return NULL; +} + +static void +lease_parse_search_domains(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) +{ + nm_auto_free_gstring GString *str = NULL; + uint8_t * data; + size_t n_data; + gs_strfreev char ** domains = NULL; + guint i; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data); + if (r) + return; + + domains = nm_dhcp_parse_search_list(data, n_data); + nm_gstring_prepare(&str); + + for (i = 0; domains && domains[i]; i++) { + g_string_append(nm_gstring_add_space_delimiter(str), domains[i]); + nm_ip4_config_add_search(ip4_config, domains[i]); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, + str->str); +} + +static void +lease_parse_root_path(NDhcp4ClientLease *lease, GHashTable *options) +{ + nm_auto_free_gstring GString *str = NULL; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data); + if (r) + return; + + str = g_string_new_len((char *) data, n_data); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_ROOT_PATH, + str->str); +} + +static void +lease_parse_wpad(NDhcp4ClientLease *lease, GHashTable *options) +{ + gs_free char *wpad = NULL; + uint8_t * data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, + &data, + &n_data); + if (r) + return; + + nm_utils_buf_utf8safe_escape((char *) data, n_data, 0, &wpad); + if (wpad == NULL) + wpad = g_strndup((char *) data, n_data); + + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, + wpad); +} + +static void +lease_parse_nis_domain(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) +{ + gs_free char *str_free = NULL; + const char * str; + uint8_t * data; + size_t n_data; + guint i; + int r; + + r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, &data, &n_data); + if (r) + return; + + for (i = 0; i < n_data; i++) { + if (!nm_is_ascii((char) data[i])) + return; + } + + str = nm_strndup_a(300, (const char *) data, n_data, &str_free); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, + str); + nm_ip4_config_set_nis_domain(ip4_config, str); +} + +static void +lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options) +{ + int i; + + for (i = NM_DHCP_OPTION_DHCP4_PRIVATE_224; i <= NM_DHCP_OPTION_DHCP4_PRIVATE_254; i++) { + gs_free char *option_string = NULL; + guint8 * data; + gsize n_data; + int r; + + /* We manage private options 249 (private classless static route) and 252 (wpad) in a special + * way, so skip them as we here just manage all (the other) private options as raw data */ + if (NM_IN_SET(i, + NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY)) + continue; + + r = n_dhcp4_client_lease_query(lease, i, &data, &n_data); + if (r) + continue; + + option_string = nm_utils_bin2hexstr_full(data, n_data, ':', FALSE, NULL); + nm_dhcp_option_take_option(options, + _nm_dhcp_option_dhcp4_options, + i, + g_steal_pointer(&option_string)); + } +} + +static NMIP4Config * +lease_to_ip4_config(NMDedupMultiIndex *multi_idx, + const char * iface, + int ifindex, + NDhcp4ClientLease *lease, + guint32 route_table, + guint32 route_metric, + GHashTable ** out_options, + GError ** error) +{ + gs_unref_object NMIP4Config *ip4_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + + g_return_val_if_fail(lease != NULL, NULL); + + ip4_config = nm_ip4_config_new(multi_idx, ifindex); + options = nm_dhcp_option_create_options_dict(); + + if (!lease_parse_address(lease, ip4_config, options, error)) + return NULL; + + lease_parse_routes(lease, ip4_config, options, route_table, route_metric); + lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, options); + lease_parse_domainname(lease, ip4_config, options); + lease_parse_search_domains(lease, ip4_config, options); + lease_parse_mtu(lease, ip4_config, options); + lease_parse_metered(lease, ip4_config, options); + + lease_parse_hostname(lease, options); + lease_parse_ntps(lease, options); + lease_parse_root_path(lease, options); + lease_parse_wpad(lease, options); + lease_parse_nis_domain(lease, ip4_config, options); + lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options); + lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, options); + lease_parse_private_options(lease, options); + + NM_SET_OUT(out_options, g_steal_pointer(&options)); + return g_steal_pointer(&ip4_config); +} + +/*****************************************************************************/ + +static void +lease_save(NMDhcpNettools *self, NDhcp4ClientLease *lease, const char *lease_file) +{ + struct in_addr a_address; + nm_auto_free_gstring GString *new_contents = NULL; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + gs_free_error GError *error = NULL; + + nm_assert(lease); + nm_assert(lease_file); + + new_contents = g_string_new("# This is private data. Do not parse.\n"); + + n_dhcp4_client_lease_get_yiaddr(lease, &a_address); + if (a_address.s_addr == INADDR_ANY) + return; + + g_string_append_printf(new_contents, + "ADDRESS=%s\n", + _nm_utils_inet4_ntop(a_address.s_addr, sbuf)); + + if (!g_file_set_contents(lease_file, new_contents->str, -1, &error)) + _LOGW("error saving lease to %s: %s", lease_file, error->message); +} + +static void +bound4_handle(NMDhcpNettools *self, NDhcp4ClientLease *lease, gboolean extended) +{ + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + const char * iface = nm_dhcp_client_get_iface(NM_DHCP_CLIENT(self)); + gs_unref_object NMIP4Config *ip4_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + GError * error = NULL; + + _LOGT("lease available (%s)", extended ? "extended" : "new"); + + ip4_config = lease_to_ip4_config(nm_dhcp_client_get_multi_idx(NM_DHCP_CLIENT(self)), + iface, + nm_dhcp_client_get_ifindex(NM_DHCP_CLIENT(self)), + lease, + nm_dhcp_client_get_route_table(NM_DHCP_CLIENT(self)), + nm_dhcp_client_get_route_metric(NM_DHCP_CLIENT(self)), + &options, + &error); + if (!ip4_config) { + _LOGW("%s", error->message); + g_clear_error(&error); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return; + } + + nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options); + lease_save(self, lease, priv->lease_file); + + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), + extended ? NM_DHCP_STATE_EXTENDED : NM_DHCP_STATE_BOUND, + NM_IP_CONFIG_CAST(ip4_config), + options); +} + +static void +dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event) +{ + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + struct in_addr server_id; + char addr_str[INET_ADDRSTRLEN]; + int r; + + _LOGT("client event %d", event->event); + + switch (event->event) { + case N_DHCP4_CLIENT_EVENT_OFFER: + r = n_dhcp4_client_lease_get_server_identifier(event->offer.lease, &server_id); + if (r) { + _LOGW("selecting lease failed: %d", r); + return; + } + + if (nm_dhcp_client_server_id_is_rejected(NM_DHCP_CLIENT(self), &server_id)) { + _LOGD("server-id %s is in the reject-list, ignoring", + nm_utils_inet_ntop(AF_INET, &server_id, addr_str)); + return; + } + + r = n_dhcp4_client_lease_select(event->offer.lease); + if (r) { + _LOGW("selecting lease failed: %d", r); + return; + } + break; + case N_DHCP4_CLIENT_EVENT_RETRACTED: + case N_DHCP4_CLIENT_EVENT_EXPIRED: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_EXPIRE, NULL, NULL); + break; + case N_DHCP4_CLIENT_EVENT_CANCELLED: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + break; + case N_DHCP4_CLIENT_EVENT_GRANTED: + priv->lease = n_dhcp4_client_lease_ref(event->granted.lease); + bound4_handle(self, event->granted.lease, FALSE); + break; + case N_DHCP4_CLIENT_EVENT_EXTENDED: + bound4_handle(self, event->extended.lease, TRUE); + break; + case N_DHCP4_CLIENT_EVENT_DOWN: + /* ignore down events, they are purely informational */ + break; + case N_DHCP4_CLIENT_EVENT_LOG: + { + NMLogLevel nm_level; + + nm_level = nm_log_level_from_syslog(event->log.level); + if (nm_logging_enabled(nm_level, LOGD_DHCP4)) { + nm_log(nm_level, + LOGD_DHCP4, + NULL, + NULL, + "dhcp4 (%s): %s", + nm_dhcp_client_get_iface(NM_DHCP_CLIENT(self)), + event->log.message); + } + } break; + default: + _LOGW("unhandled DHCP event %d", event->event); + break; + } +} + +static gboolean +dhcp4_event_cb(int fd, GIOCondition condition, gpointer data) +{ + NMDhcpNettools * self = data; + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + NDhcp4ClientEvent * event; + int r; + + r = n_dhcp4_client_dispatch(priv->client); + if (r < 0) { + /* FIXME: if any operation (e.g. send()) fails during the + * dispatch, n-dhcp4 returns an error without arming timers + * or progressing state, so the only reasonable thing to do + * is to move to failed state so that the client will be + * restarted. Ideally n-dhcp4 should retry failed operations + * a predefined number of times (possibly infinite). + */ + _LOGE("error %d dispatching events", r); + nm_clear_g_source_inst(&priv->event_source); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return G_SOURCE_REMOVE; + } + + while (!n_dhcp4_client_pop_event(priv->client, &event) && event) { + dhcp4_event_handle(self, event); + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +nettools_create(NMDhcpNettools *self, const char *dhcp_anycast_addr, GError **error) +{ + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + nm_auto(n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL; + nm_auto(n_dhcp4_client_unrefp) NDhcp4Client * client = NULL; + GBytes * hwaddr; + GBytes * bcast_hwaddr; + const uint8_t * hwaddr_arr; + const uint8_t * bcast_hwaddr_arr; + gsize hwaddr_len; + gsize bcast_hwaddr_len; + GBytes * client_id; + gs_unref_bytes GBytes *client_id_new = NULL; + const uint8_t * client_id_arr; + size_t client_id_len; + int r, fd, arp_type, transport; + + g_return_val_if_fail(!priv->client, FALSE); + + hwaddr = nm_dhcp_client_get_hw_addr(NM_DHCP_CLIENT(self)); + if (!hwaddr || !(hwaddr_arr = g_bytes_get_data(hwaddr, &hwaddr_len)) + || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len)) < 0) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address"); + return FALSE; + } + + bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr(NM_DHCP_CLIENT(self)); + bcast_hwaddr_arr = g_bytes_get_data(bcast_hwaddr, &bcast_hwaddr_len); + + switch (arp_type) { + case ARPHRD_ETHER: + transport = N_DHCP4_TRANSPORT_ETHERNET; + break; + case ARPHRD_INFINIBAND: + transport = N_DHCP4_TRANSPORT_INFINIBAND; + break; + default: + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "unsupported ARP type"); + return FALSE; + } + + /* Note that we always set a client-id. In particular for infiniband that is necessary, + * see https://tools.ietf.org/html/rfc4390#section-2.1 . */ + client_id = nm_dhcp_client_get_client_id(NM_DHCP_CLIENT(self)); + if (!client_id) { + client_id_new = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_arr, hwaddr_len); + client_id = client_id_new; + } + + if (!(client_id_arr = g_bytes_get_data(client_id, &client_id_len)) || client_id_len < 2) { + /* invalid client-ids are not expected. */ + nm_assert_not_reached(); + + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id"); + return FALSE; + } + + r = n_dhcp4_client_config_new(&config); + if (r) { + set_error_nettools(error, r, "failed to create client-config"); + return FALSE; + } + + n_dhcp4_client_config_set_ifindex(config, nm_dhcp_client_get_ifindex(NM_DHCP_CLIENT(self))); + n_dhcp4_client_config_set_transport(config, transport); + n_dhcp4_client_config_set_mac(config, hwaddr_arr, hwaddr_len); + n_dhcp4_client_config_set_broadcast_mac(config, bcast_hwaddr_arr, bcast_hwaddr_len); + r = n_dhcp4_client_config_set_client_id(config, + client_id_arr, + NM_MIN(client_id_len, 1 + _NM_SD_MAX_CLIENT_ID_LEN)); + if (r) { + set_error_nettools(error, r, "failed to set client-id"); + return FALSE; + } + + r = n_dhcp4_client_new(&client, config); + if (r) { + set_error_nettools(error, r, "failed to create client"); + return FALSE; + } + + priv->client = client; + client = NULL; + + n_dhcp4_client_set_log_level(priv->client, + nm_log_level_to_syslog(nm_logging_get_level(LOGD_DHCP4))); + + n_dhcp4_client_get_fd(priv->client, &fd); + + priv->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, dhcp4_event_cb, self, NULL); + g_source_attach(priv->event_source, NULL); + + return TRUE; +} + +static gboolean +_accept(NMDhcpClient *client, GError **error) +{ + NMDhcpNettools * self = NM_DHCP_NETTOOLS(client); + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + int r; + + g_return_val_if_fail(priv->lease, FALSE); + + _LOGT("accept"); + + r = n_dhcp4_client_lease_accept(priv->lease); + if (r) { + set_error_nettools(error, r, "failed to accept lease"); + return FALSE; + } + + priv->lease = n_dhcp4_client_lease_unref(priv->lease); + + return TRUE; +} + +static gboolean +decline(NMDhcpClient *client, const char *error_message, GError **error) +{ + NMDhcpNettools * self = NM_DHCP_NETTOOLS(client); + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + int r; + + g_return_val_if_fail(priv->lease, FALSE); + + _LOGT("dhcp4-client: decline"); + + r = n_dhcp4_client_lease_decline(priv->lease, error_message); + if (r) { + set_error_nettools(error, r, "failed to decline lease"); + return FALSE; + } + + priv->lease = n_dhcp4_client_lease_unref(priv->lease); + + return TRUE; +} + +static guint8 +fqdn_flags_to_wire(NMDhcpHostnameFlags flags) +{ + guint r = 0; + + /* RFC 4702 section 2.1 */ + if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) + r |= (1 << 0); + if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) + r |= (1 << 2); + if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) + r |= (1 << 3); + + return r; +} + +static gboolean +ip4_start(NMDhcpClient *client, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + nm_auto(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL; + NMDhcpNettools * self = NM_DHCP_NETTOOLS(client); + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + gs_free char * lease_file = NULL; + struct in_addr last_addr = {0}; + const char * hostname; + const char * mud_url; + GBytes * vendor_class_identifier; + int r, i; + + g_return_val_if_fail(!priv->probe, FALSE); + + if (!nettools_create(self, dhcp_anycast_addr, error)) + return FALSE; + + r = n_dhcp4_client_probe_config_new(&config); + if (r) { + set_error_nettools(error, r, "failed to create dhcp-client-probe-config"); + return FALSE; + } + + /* + * FIXME: + * Select, or configure, a reasonable start delay, to protect poor servers being flooded. + */ + n_dhcp4_client_probe_config_set_start_delay(config, 1); + + nm_dhcp_utils_get_leasefile_path(AF_INET, + "internal", + nm_dhcp_client_get_iface(client), + nm_dhcp_client_get_uuid(client), + &lease_file); + + if (last_ip4_address) + inet_pton(AF_INET, last_ip4_address, &last_addr); + else { + /* + * TODO: we stick to the systemd-networkd lease file format. Quite easy for now to + * just use the functions in systemd code. Anyway, as in the end we just use the + * ip address from all the options found in the lease, write a function that parses + * the lease file just for the assigned address and returns it in &last_address. + * Then drop reference to systemd-networkd structures and functions. + */ + nm_auto(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + + dhcp_lease_load(&lease, lease_file); + if (lease) + sd_dhcp_lease_get_address(lease, &last_addr); + } + + if (last_addr.s_addr) { + n_dhcp4_client_probe_config_set_requested_ip(config, last_addr); + n_dhcp4_client_probe_config_set_init_reboot(config, TRUE); + } + + /* Add requested options */ + for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) { + if (_nm_dhcp_option_dhcp4_options[i].include) { + nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255); + n_dhcp4_client_probe_config_request_option(config, + _nm_dhcp_option_dhcp4_options[i].option_num); + } + } + + mud_url = nm_dhcp_client_get_mud_url(client); + if (mud_url) { + r = n_dhcp4_client_probe_config_append_option(config, + NM_DHCP_OPTION_DHCP4_MUD_URL, + mud_url, + strlen(mud_url)); + if (r) { + set_error_nettools(error, r, "failed to set MUD URL"); + return FALSE; + } + } + hostname = nm_dhcp_client_get_hostname(client); + if (hostname) { + if (nm_dhcp_client_get_use_fqdn(client)) { + uint8_t buffer[255]; + NMDhcpHostnameFlags flags; + size_t fqdn_len; + + flags = nm_dhcp_client_get_hostname_flags(client); + buffer[0] = fqdn_flags_to_wire(flags); + buffer[1] = 0; /* RCODE1 (deprecated) */ + buffer[2] = 0; /* RCODE2 (deprecated) */ + + if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) { + r = nm_sd_dns_name_to_wire_format(hostname, buffer + 3, sizeof(buffer) - 3, FALSE); + if (r <= 0) { + if (r < 0) + nm_utils_error_set_errno(error, r, "failed to convert DHCP FQDN: %s"); + else + nm_utils_error_set(error, r, "failed to convert DHCP FQDN"); + return FALSE; + } + fqdn_len = r; + } else { + fqdn_len = strlen(hostname); + if (fqdn_len > sizeof(buffer) - 3) { + nm_utils_error_set(error, r, "failed to set DHCP FQDN: name too long"); + return FALSE; + } + memcpy(buffer + 3, hostname, fqdn_len); + } + + r = n_dhcp4_client_probe_config_append_option(config, + NM_DHCP_OPTION_DHCP4_CLIENT_FQDN, + buffer, + 3 + fqdn_len); + if (r) { + set_error_nettools(error, r, "failed to set DHCP FQDN"); + return FALSE; + } + } else { + r = n_dhcp4_client_probe_config_append_option(config, + NM_DHCP_OPTION_DHCP4_HOST_NAME, + hostname, + strlen(hostname)); + if (r) { + set_error_nettools(error, r, "failed to set DHCP hostname"); + return FALSE; + } + } + } + + vendor_class_identifier = nm_dhcp_client_get_vendor_class_identifier(client); + if (vendor_class_identifier) { + const void *option_data; + gsize option_size; + + option_data = g_bytes_get_data(vendor_class_identifier, &option_size); + nm_assert(option_data); + nm_assert(option_size <= 255); + + r = n_dhcp4_client_probe_config_append_option(config, + NM_DHCP_OPTION_DHCP4_VENDOR_CLASS_IDENTIFIER, + option_data, + option_size); + if (r) { + set_error_nettools(error, r, "failed to set vendor class identifier"); + return FALSE; + } + } + + g_free(priv->lease_file); + priv->lease_file = g_steal_pointer(&lease_file); + + r = n_dhcp4_client_probe(priv->client, &priv->probe, config); + if (r) { + set_error_nettools(error, r, "failed to start DHCP client"); + return FALSE; + } + + _LOGT("dhcp-client4: start %p", (gpointer) priv->client); + + nm_dhcp_client_start_timeout(client); + return TRUE; +} + +static void +stop(NMDhcpClient *client, gboolean release) +{ + NMDhcpNettools * self = NM_DHCP_NETTOOLS(client); + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); + + NM_DHCP_CLIENT_CLASS(nm_dhcp_nettools_parent_class)->stop(client, release); + + _LOGT("dhcp-client4: stop %p", (gpointer) priv->client); + + priv->probe = n_dhcp4_client_probe_free(priv->probe); +} + +/*****************************************************************************/ + +static void +nm_dhcp_nettools_init(NMDhcpNettools *self) +{} + +static void +dispose(GObject *object) +{ + NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(object); + + nm_clear_g_free(&priv->lease_file); + nm_clear_g_source_inst(&priv->event_source); + nm_clear_pointer(&priv->lease, n_dhcp4_client_lease_unref); + nm_clear_pointer(&priv->probe, n_dhcp4_client_probe_free); + nm_clear_pointer(&priv->client, n_dhcp4_client_unref); + + G_OBJECT_CLASS(nm_dhcp_nettools_parent_class)->dispose(object); +} + +static void +nm_dhcp_nettools_class_init(NMDhcpNettoolsClass *class) +{ + NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(class); + GObjectClass * object_class = G_OBJECT_CLASS(class); + + object_class->dispose = dispose; + + client_class->ip4_start = ip4_start; + client_class->accept = _accept; + client_class->decline = decline; + client_class->stop = stop; +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = { + .name = "nettools", + .get_type = nm_dhcp_nettools_get_type, + .experimental = TRUE, +}; diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c new file mode 100644 index 0000000..13d8501 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-options.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-options.h" + +#define REQPREFIX "requested_" + +#define REQ(_num, _name, _include) \ + { \ + .name = REQPREFIX ""_name, .option_num = _num, .include = _include, \ + } + +const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = { + REQ(NM_DHCP_OPTION_DHCP4_SUBNET_MASK, "subnet_mask", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_TIME_OFFSET, "time_offset", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, "domain_name_servers", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_HOST_NAME, "host_name", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, "domain_name", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, "interface_mtu", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_BROADCAST, "broadcast_address", TRUE), + /* RFC 3442: The Classless Static Routes option code MUST appear in the parameter + * request list prior to both the Router option code and the Static + * Routes option code, if present. */ + REQ(NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, "rfc3442_classless_static_routes", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_ROUTER, "routers", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, "static_routes", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, "nis_domain", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_NIS_SERVERS, "nis_servers", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_NTP_SERVER, "ntp_servers", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_SERVER_ID, "dhcp_server_identifier", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, "domain_search", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, "wpad", TRUE), + REQ(NM_DHCP_OPTION_DHCP4_ROOT_PATH, "root_path", TRUE), + + REQ(NM_DHCP_OPTION_DHCP4_TIME_SERVERS, "time_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS, "ien116_name_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_LOG_SERVERS, "log_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS, "cookie_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_LPR_SERVERS, "lpr_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS, "impress_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS, "resource_location_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE, "boot_size", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_MERIT_DUMP, "merit_dump", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SWAP_SERVER, "swap_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH, "extensions_path", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING, "ip_forwarding", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING, "non_local_source_routing", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_POLICY_FILTER, "policy_filter", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MDR, "max_dgram_reassembly", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_TTL, "default_ip_ttl", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT, "path_mtu_aging_timeout", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE, "path_mtu_plateau_table", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL, "all_subnets_local", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY, "perform_mask_discovery", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER, "mask_supplier", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY, "router_discovery", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR, "router_solicitation_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION, "trailer_encapsulation", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT, "arp_cache_timeout", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION, "ieee802_3_encapsulation", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL, "default_tcp_ttl", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_internal", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE, "tcp_keepalive_garbage", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, "vendor_encapsulated_options", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, "netbios_name_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER, "netbios_dd_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_FONT_SERVERS, "font_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER, "x_display_manager", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, "dhcp_lease_time", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME, "dhcp_renewal_time", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME, "dhcp_rebinding_time", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_CLIENT_ID, "dhcp_client_identifier", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, "tcode", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN, "nwip_domain", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS, "nwip_suboptions", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN, "nisplus_domain", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS, "nisplus_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME, "tftp_server_name", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME, "bootfile_name", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT, "mobile_ip_home_agent", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SMTP_SERVER, "smtp_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_POP_SERVER, "pop_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NNTP_SERVER, "nntp_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_WWW_SERVER, "www_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_FINGER_SERVER, "finger_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IRC_SERVER, "irc_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER, "streettalk_server", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER, + "streettalk_directory_assistance_server", + FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT, "slp_directory_agent", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE, "slp_service_scope", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_CLIENT_FQDN, "fqdn", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION, "relay_agent_information", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NDS_SERVERS, "nds_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME, "nds_tree_name", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NDS_CONTEXT, "nds_context", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES, "bcms_controller_names", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS, "bcms_controller_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION, "client_last_transaction_time", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP, "associated_ip", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE, "pxe_system_type", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID, "pxe_interface_id", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID, "pxe_client_id", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_UAP_SERVERS, "uap_servers", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC, "geoconf_civic", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS, "netinfo_server_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG, "netinfo_server_tag", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_URL, "default_url", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_AUTO_CONFIG, "auto_config", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH, "name_service_search", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION, "subnet_selection", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_VIVCO, "vivco", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_VIVSO, "vivso", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PANA_AGENT, "pana_agent", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_V4_LOST, "v4_lost", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS, "sip_ua_cs_domains", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF, "ipv4_address_andsf", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION, "rndss_selection", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS, "tftp_server_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS, "v4_portparams", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL, "v4_captive_portal", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_MUD_URL, "mud_url", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE, "loader_configfile", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX, "loader_pathprefix", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME, "loader_reboottime", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_OPTION_6RD, "option_6rd", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN, "v4_access_domain", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_224, "private_224", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_225, "private_225", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_226, "private_226", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_227, "private_227", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_228, "private_228", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_229, "private_229", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_230, "private_230", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_231, "private_231", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_232, "private_232", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_233, "private_233", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_234, "private_234", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_235, "private_235", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_236, "private_236", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_237, "private_237", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_238, "private_238", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_239, "private_239", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_240, "private_240", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_241, "private_241", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_242, "private_242", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_243, "private_243", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_244, "private_244", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_245, "private_245", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_246, "private_246", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_247, "private_247", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_248, "private_248", FALSE), + /* NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE */ + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_250, "private_250", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_251, "private_251", FALSE), + /* NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY */ + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_253, "private_253", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_254, "private_254", FALSE), + + /* Internal values */ + REQ(NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, "ip_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NM_EXPIRY, "expiry", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, "next_server", FALSE), + + {0}}; + +const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = { + REQ(NM_DHCP_OPTION_DHCP6_CLIENTID, "dhcp6_client_id", FALSE), + + /* Don't request server ID by default; some servers don't reply to + * Information Requests that request the Server ID. + */ + REQ(NM_DHCP_OPTION_DHCP6_SERVERID, "dhcp6_server_id", FALSE), + + REQ(NM_DHCP_OPTION_DHCP6_DNS_SERVERS, "dhcp6_name_servers", TRUE), + REQ(NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, "dhcp6_domain_search", TRUE), + REQ(NM_DHCP_OPTION_DHCP6_SNTP_SERVERS, "dhcp6_sntp_servers", TRUE), + REQ(NM_DHCP_OPTION_DHCP6_FQDN, "fqdn_fqdn", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_MUD_URL, "dhcp6_mud_url", FALSE), + + /* Internal values */ + REQ(NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, "ip6_address", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN, "ip6_prefixlen", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE, "preferred_life", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE, "max_life", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_STARTS, "starts", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS, "life_starts", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_RENEW, "renew", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_REBIND, "rebind", FALSE), + REQ(NM_DHCP_OPTION_DHCP6_NM_IAID, "iaid", FALSE), + + {0}}; + +const char * +nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option) +{ + guint i = 0; + + while (requests[i].name) { + if (requests[i].option_num == option) + return requests[i].name + NM_STRLEN(REQPREFIX); + i++; + } + + /* Option should always be found */ + nm_assert_not_reached(); + return NULL; +} + +void +nm_dhcp_option_take_option(GHashTable * options, + const NMDhcpOption *requests, + guint option, + char * value) +{ + nm_assert(options); + nm_assert(requests); + nm_assert(value); + nm_assert(g_utf8_validate(value, -1, NULL)); + + g_hash_table_insert(options, (gpointer) nm_dhcp_option_request_string(requests, option), value); +} + +void +nm_dhcp_option_add_option(GHashTable * options, + const NMDhcpOption *requests, + guint option, + const char * value) +{ + if (options) + nm_dhcp_option_take_option(options, requests, option, g_strdup(value)); +} + +void +nm_dhcp_option_add_option_u64(GHashTable * options, + const NMDhcpOption *requests, + guint option, + guint64 value) +{ + if (options) + nm_dhcp_option_take_option(options, + requests, + option, + g_strdup_printf("%" G_GUINT64_FORMAT, value)); +} + +void +nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests) +{ + guint i; + + if (!options) + return; + + for (i = 0; requests[i].name; i++) { + if (requests[i].include) + g_hash_table_insert(options, (gpointer) requests[i].name, g_strdup("1")); + } +} + +GHashTable * +nm_dhcp_option_create_options_dict(void) +{ + return g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); +} diff --git a/src/core/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h new file mode 100644 index 0000000..c2a403a --- /dev/null +++ b/src/core/dhcp/nm-dhcp-options.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_DHCP_OPTIONS_H__ +#define __NM_DHCP_OPTIONS_H__ + +typedef enum { + NM_DHCP_OPTION_DHCP4_PAD = 0, + NM_DHCP_OPTION_DHCP4_SUBNET_MASK = 1, + NM_DHCP_OPTION_DHCP4_TIME_OFFSET = 2, + NM_DHCP_OPTION_DHCP4_ROUTER = 3, + NM_DHCP_OPTION_DHCP4_TIME_SERVERS = 4, + NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS = 5, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER = 6, + NM_DHCP_OPTION_DHCP4_LOG_SERVERS = 7, + NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS = 8, + NM_DHCP_OPTION_DHCP4_LPR_SERVERS = 9, + NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS = 10, + NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS = 11, + NM_DHCP_OPTION_DHCP4_HOST_NAME = 12, + NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE = 13, + NM_DHCP_OPTION_DHCP4_MERIT_DUMP = 14, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME = 15, + NM_DHCP_OPTION_DHCP4_SWAP_SERVER = 16, + NM_DHCP_OPTION_DHCP4_ROOT_PATH = 17, + NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH = 18, + NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING = 19, + NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING = 20, + NM_DHCP_OPTION_DHCP4_POLICY_FILTER = 21, + NM_DHCP_OPTION_DHCP4_INTERFACE_MDR = 22, + NM_DHCP_OPTION_DHCP4_INTERFACE_TTL = 23, + NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT = 24, + NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE = 25, + NM_DHCP_OPTION_DHCP4_INTERFACE_MTU = 26, + NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL = 27, + NM_DHCP_OPTION_DHCP4_BROADCAST = 28, + NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY = 29, + NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER = 30, + NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY = 31, + NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR = 32, + NM_DHCP_OPTION_DHCP4_STATIC_ROUTE = 33, + NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION = 34, + NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT = 35, + NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION = 36, + NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL = 37, + NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL = 38, + NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE = 39, + NM_DHCP_OPTION_DHCP4_NIS_DOMAIN = 40, + NM_DHCP_OPTION_DHCP4_NIS_SERVERS = 41, + NM_DHCP_OPTION_DHCP4_NTP_SERVER = 42, + NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC = 43, + NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER = 44, + NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER = 45, + NM_DHCP_OPTION_DHCP4_FONT_SERVERS = 48, + NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER = 49, + NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME = 51, + NM_DHCP_OPTION_DHCP4_SERVER_ID = 54, + NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME = 58, + NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME = 59, + NM_DHCP_OPTION_DHCP4_VENDOR_CLASS_IDENTIFIER = 60, + NM_DHCP_OPTION_DHCP4_CLIENT_ID = 61, + NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN = 62, + NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS = 63, + NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN = 64, + NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS = 65, + NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME = 66, + NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME = 67, + NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT = 68, + NM_DHCP_OPTION_DHCP4_SMTP_SERVER = 69, + NM_DHCP_OPTION_DHCP4_POP_SERVER = 70, + NM_DHCP_OPTION_DHCP4_NNTP_SERVER = 71, + NM_DHCP_OPTION_DHCP4_WWW_SERVER = 72, + NM_DHCP_OPTION_DHCP4_FINGER_SERVER = 73, + NM_DHCP_OPTION_DHCP4_IRC_SERVER = 74, + NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER = 75, + NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER = 76, + NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT = 78, + NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE = 79, + NM_DHCP_OPTION_DHCP4_CLIENT_FQDN = 81, + NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION = 82, + NM_DHCP_OPTION_DHCP4_NDS_SERVERS = 85, + NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME = 86, + NM_DHCP_OPTION_DHCP4_NDS_CONTEXT = 87, + NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES = 88, + NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS = 89, + NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION = 91, + NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP = 92, + NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE = 93, + NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID = 94, + NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID = 97, + NM_DHCP_OPTION_DHCP4_UAP_SERVERS = 98, + NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC = 99, + NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE = 101, + NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS = 112, + NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG = 113, + NM_DHCP_OPTION_DHCP4_DEFAULT_URL = 114, + NM_DHCP_OPTION_DHCP4_AUTO_CONFIG = 116, + NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH = 117, + NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION = 118, + NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST = 119, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE = 121, + NM_DHCP_OPTION_DHCP4_VIVCO = 124, + NM_DHCP_OPTION_DHCP4_VIVSO = 125, + NM_DHCP_OPTION_DHCP4_PANA_AGENT = 136, + NM_DHCP_OPTION_DHCP4_V4_LOST = 137, + NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS = 141, + NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF = 142, + NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION = 146, + NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS = 150, + NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS = 159, + NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL = 160, + NM_DHCP_OPTION_DHCP4_MUD_URL = 161, + NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE = 209, + NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX = 210, + NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME = 211, + NM_DHCP_OPTION_DHCP4_OPTION_6RD = 212, + NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN = 213, + NM_DHCP_OPTION_DHCP4_PRIVATE_224 = 224, + NM_DHCP_OPTION_DHCP4_PRIVATE_225 = 225, + NM_DHCP_OPTION_DHCP4_PRIVATE_226 = 226, + NM_DHCP_OPTION_DHCP4_PRIVATE_227 = 227, + NM_DHCP_OPTION_DHCP4_PRIVATE_228 = 228, + NM_DHCP_OPTION_DHCP4_PRIVATE_229 = 229, + NM_DHCP_OPTION_DHCP4_PRIVATE_230 = 230, + NM_DHCP_OPTION_DHCP4_PRIVATE_231 = 231, + NM_DHCP_OPTION_DHCP4_PRIVATE_232 = 232, + NM_DHCP_OPTION_DHCP4_PRIVATE_233 = 233, + NM_DHCP_OPTION_DHCP4_PRIVATE_234 = 234, + NM_DHCP_OPTION_DHCP4_PRIVATE_235 = 235, + NM_DHCP_OPTION_DHCP4_PRIVATE_236 = 236, + NM_DHCP_OPTION_DHCP4_PRIVATE_237 = 237, + NM_DHCP_OPTION_DHCP4_PRIVATE_238 = 238, + NM_DHCP_OPTION_DHCP4_PRIVATE_239 = 239, + NM_DHCP_OPTION_DHCP4_PRIVATE_240 = 240, + NM_DHCP_OPTION_DHCP4_PRIVATE_241 = 241, + NM_DHCP_OPTION_DHCP4_PRIVATE_242 = 242, + NM_DHCP_OPTION_DHCP4_PRIVATE_243 = 243, + NM_DHCP_OPTION_DHCP4_PRIVATE_244 = 244, + NM_DHCP_OPTION_DHCP4_PRIVATE_245 = 245, + NM_DHCP_OPTION_DHCP4_PRIVATE_246 = 246, + NM_DHCP_OPTION_DHCP4_PRIVATE_247 = 247, + NM_DHCP_OPTION_DHCP4_PRIVATE_248 = 248, + NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, + NM_DHCP_OPTION_DHCP4_PRIVATE_250 = 250, + NM_DHCP_OPTION_DHCP4_PRIVATE_251 = 251, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY = 252, + NM_DHCP_OPTION_DHCP4_PRIVATE_253 = 253, + NM_DHCP_OPTION_DHCP4_PRIVATE_254 = 254, + NM_DHCP_OPTION_DHCP4_END = 255, + /* Internal values */ + NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS = 1024, + NM_DHCP_OPTION_DHCP4_NM_EXPIRY = 1025, + NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER = 1026, +} NMDhcpOptionDhcp4Options; + +typedef enum { + NM_DHCP_OPTION_DHCP6_CLIENTID = 1, + NM_DHCP_OPTION_DHCP6_SERVERID = 2, + NM_DHCP_OPTION_DHCP6_DNS_SERVERS = 23, + NM_DHCP_OPTION_DHCP6_DOMAIN_LIST = 24, + NM_DHCP_OPTION_DHCP6_SNTP_SERVERS = 31, + NM_DHCP_OPTION_DHCP6_FQDN = 39, + NM_DHCP_OPTION_DHCP6_MUD_URL = 112, + + /* Internal values */ + NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS = 1026, + NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN = 1027, + NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE = 1028, + NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE = 1029, + NM_DHCP_OPTION_DHCP6_NM_STARTS = 1030, + NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS = 1031, + NM_DHCP_OPTION_DHCP6_NM_RENEW = 1032, + NM_DHCP_OPTION_DHCP6_NM_REBIND = 1033, + NM_DHCP_OPTION_DHCP6_NM_IAID = 1034, + +} NMDhcpOptionDhcp6Options; + +typedef struct { + const char *name; + uint16_t option_num; + bool include; +} NMDhcpOption; + +extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[]; +extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[]; + +const char *nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option); +void nm_dhcp_option_take_option(GHashTable * options, + const NMDhcpOption *requests, + guint option, + char * value); +void nm_dhcp_option_add_option(GHashTable * options, + const NMDhcpOption *requests, + guint option, + const char * value); +void nm_dhcp_option_add_option_u64(GHashTable * options, + const NMDhcpOption *requests, + guint option, + guint64 value); +void nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests); +GHashTable *nm_dhcp_option_create_options_dict(void); + +#endif /* __NM_DHCP_OPTIONS_H__ */ diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c new file mode 100644 index 0000000..ea50511 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-systemd.c @@ -0,0 +1,1169 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-std-aux/unaligned.h" + +#include "nm-utils.h" +#include "nm-dhcp-utils.h" +#include "nm-dhcp-options.h" +#include "nm-core-utils.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-dhcp-client-logging.h" +#include "systemd/nm-sd.h" +#include "systemd/nm-sd-utils-dhcp.h" + +/*****************************************************************************/ + +#define NM_TYPE_DHCP_SYSTEMD (nm_dhcp_systemd_get_type()) +#define NM_DHCP_SYSTEMD(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemd)) +#define NM_DHCP_SYSTEMD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass)) +#define NM_IS_DHCP_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_SYSTEMD)) +#define NM_IS_DHCP_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_SYSTEMD)) +#define NM_DHCP_SYSTEMD_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass)) + +typedef struct _NMDhcpSystemd NMDhcpSystemd; +typedef struct _NMDhcpSystemdClass NMDhcpSystemdClass; + +static GType nm_dhcp_systemd_get_type(void); + +/*****************************************************************************/ + +typedef struct { + sd_dhcp_client * client4; + sd_dhcp6_client *client6; + char * lease_file; + + guint request_count; + + bool privacy : 1; +} NMDhcpSystemdPrivate; + +struct _NMDhcpSystemd { + NMDhcpClient parent; + NMDhcpSystemdPrivate _priv; +}; + +struct _NMDhcpSystemdClass { + NMDhcpClientClass parent; +}; + +G_DEFINE_TYPE(NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT) + +#define NM_DHCP_SYSTEMD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpSystemd, NM_IS_DHCP_SYSTEMD) + +/*****************************************************************************/ + +static NMIP4Config * +lease_to_ip4_config(NMDedupMultiIndex *multi_idx, + const char * iface, + int ifindex, + sd_dhcp_lease * lease, + guint32 route_table, + guint32 route_metric, + GHashTable ** out_options, + GError ** error) +{ + gs_unref_object NMIP4Config *ip4_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + const struct in_addr * addr_list; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + const char * s; + nm_auto_free_gstring GString *str = NULL; + gs_free sd_dhcp_route **routes = NULL; + const char *const * search_domains = NULL; + guint16 mtu; + int i, num; + const void * data; + gsize data_len; + gboolean metered = FALSE; + gboolean has_router_from_classless = FALSE; + gboolean has_classless_route = FALSE; + gboolean has_static_route = FALSE; + const gint32 ts = nm_utils_get_monotonic_timestamp_sec(); + gint64 ts_time = time(NULL); + struct in_addr a_address; + struct in_addr a_netmask; + struct in_addr a_next_server; + struct in_addr server_id; + struct in_addr broadcast; + const struct in_addr * a_router; + guint32 a_plen; + guint32 a_lifetime; + guint32 renewal; + guint32 rebinding; + gs_free nm_sd_dhcp_option *private_options = NULL; + + nm_assert(lease != NULL); + + if (sd_dhcp_lease_get_address(lease, &a_address) < 0) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "could not get address from lease"); + return NULL; + } + + if (sd_dhcp_lease_get_netmask(lease, &a_netmask) < 0) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "could not get netmask from lease"); + return NULL; + } + + if (sd_dhcp_lease_get_lifetime(lease, &a_lifetime) < 0) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "could not get lifetime from lease"); + return NULL; + } + + ip4_config = nm_ip4_config_new(multi_idx, ifindex); + + options = out_options ? nm_dhcp_option_create_options_dict() : NULL; + + _nm_utils_inet4_ntop(a_address.s_addr, addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, + addr_str); + + a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_SUBNET_MASK, + _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str)); + + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, + a_lifetime); + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_EXPIRY, + (guint64)(ts_time + a_lifetime)); + + if (sd_dhcp_lease_get_next_server(lease, &a_next_server) == 0) { + _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, + addr_str); + } + + nm_ip4_config_add_address(ip4_config, + &((const NMPlatformIP4Address){ + .address = a_address.s_addr, + .peer_address = a_address.s_addr, + .plen = a_plen, + .addr_source = NM_IP_CONFIG_SOURCE_DHCP, + .timestamp = ts, + .lifetime = a_lifetime, + .preferred = a_lifetime, + })); + + if (sd_dhcp_lease_get_server_identifier(lease, &server_id) >= 0) { + _nm_utils_inet4_ntop(server_id.s_addr, addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_SERVER_ID, + addr_str); + } + + if (sd_dhcp_lease_get_broadcast(lease, &broadcast) >= 0) { + _nm_utils_inet4_ntop(broadcast.s_addr, addr_str); + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_BROADCAST, + addr_str); + } + + num = sd_dhcp_lease_get_dns(lease, &addr_list); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + _nm_utils_inet4_ntop(addr_list[i].s_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + + if (addr_list[i].s_addr == 0 || nm_ip4_addr_is_localhost(addr_list[i].s_addr)) { + /* Skip localhost addresses, like also networkd does. + * See https://github.com/systemd/systemd/issues/4524. */ + continue; + } + nm_ip4_config_add_nameserver(ip4_config, addr_list[i].s_addr); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, + str->str); + } + + num = sd_dhcp_lease_get_search_domains(lease, (char ***) &search_domains); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + g_string_append(nm_gstring_add_space_delimiter(str), search_domains[i]); + nm_ip4_config_add_search(ip4_config, search_domains[i]); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, + str->str); + } + + if (sd_dhcp_lease_get_domainname(lease, &s) >= 0) { + gs_strfreev char **domains = NULL; + char ** d; + + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, + s); + + /* Multiple domains sometimes stuffed into option 15 "Domain Name". + * As systemd escapes such characters, split them at \\032. */ + domains = g_strsplit(s, "\\032", 0); + for (d = domains; *d; d++) + nm_ip4_config_add_domain(ip4_config, *d); + } + + if (sd_dhcp_lease_get_hostname(lease, &s) >= 0) { + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_HOST_NAME, + s); + } + + num = sd_dhcp_lease_get_routes(lease, &routes); + if (num > 0) { + nm_auto_free_gstring GString *str_classless = NULL; + nm_auto_free_gstring GString *str_static = NULL; + guint32 default_route_metric = route_metric; + + for (i = 0; i < num; i++) { + switch (sd_dhcp_route_get_option(routes[i])) { + case NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE: + has_classless_route = TRUE; + break; + case NM_DHCP_OPTION_DHCP4_STATIC_ROUTE: + has_static_route = TRUE; + break; + } + } + + if (has_classless_route) + str_classless = g_string_sized_new(30); + if (has_static_route) + str_static = g_string_sized_new(30); + + for (i = 0; i < num; i++) { + char network_net_str[NM_UTILS_INET_ADDRSTRLEN]; + char gateway_str[NM_UTILS_INET_ADDRSTRLEN]; + guint8 r_plen; + struct in_addr r_network; + struct in_addr r_gateway; + in_addr_t network_net; + int option; + guint32 m; + + option = sd_dhcp_route_get_option(routes[i]); + if (!NM_IN_SET(option, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, + NM_DHCP_OPTION_DHCP4_STATIC_ROUTE)) + continue; + + if (sd_dhcp_route_get_destination(routes[i], &r_network) < 0) + continue; + if (sd_dhcp_route_get_destination_prefix_length(routes[i], &r_plen) < 0 || r_plen > 32) + continue; + if (sd_dhcp_route_get_gateway(routes[i], &r_gateway) < 0) + continue; + + network_net = nm_utils_ip4_address_clear_host_address(r_network.s_addr, r_plen); + _nm_utils_inet4_ntop(network_net, network_net_str); + _nm_utils_inet4_ntop(r_gateway.s_addr, gateway_str); + + g_string_append_printf( + nm_gstring_add_space_delimiter(option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE + ? str_classless + : str_static), + "%s/%d %s", + network_net_str, + (int) r_plen, + gateway_str); + + if (option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE && has_classless_route) { + /* RFC 3443: if the DHCP server returns both a Classless Static Routes + * option and a Static Routes option, the DHCP client MUST ignore the + * Static Routes option. */ + continue; + } + + if (r_plen == 0 && option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE) { + /* for option 33 (static route), RFC 2132 says: + * + * The default route (0.0.0.0) is an illegal destination for a static + * route. */ + continue; + } + + if (r_plen == 0) { + /* if there are multiple default routes, we add them with differing + * metrics. */ + m = default_route_metric; + if (default_route_metric < G_MAXUINT32) + default_route_metric++; + + has_router_from_classless = TRUE; + } else + m = route_metric; + + nm_ip4_config_add_route( + ip4_config, + &((const NMPlatformIP4Route){ + .network = network_net, + .plen = r_plen, + .gateway = r_gateway.s_addr, + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .metric = m, + .table_coerced = nm_platform_route_table_coerce(route_table), + }), + NULL); + } + + if (str_classless && str_classless->len > 0) + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, + str_classless->str); + if (str_static && str_static->len > 0) + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, + str_static->str); + } + + num = sd_dhcp_lease_get_router(lease, &a_router); + if (num > 0) { + guint32 default_route_metric = route_metric; + + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + guint32 m; + + s = _nm_utils_inet4_ntop(a_router[i].s_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), s); + + if (a_router[i].s_addr == 0) { + /* silently skip 0.0.0.0 */ + continue; + } + + if (has_router_from_classless) { + /* If the DHCP server returns both a Classless Static Routes option and a + * Router option, the DHCP client MUST ignore the Router option [RFC 3442]. + * + * Be more lenient and ignore the Router option only if Classless Static + * Routes contain a default gateway (as other DHCP backends do). + */ + continue; + } + + /* if there are multiple default routes, we add them with differing + * metrics. */ + m = default_route_metric; + if (default_route_metric < G_MAXUINT32) + default_route_metric++; + + nm_ip4_config_add_route( + ip4_config, + &((const NMPlatformIP4Route){ + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .gateway = a_router[i].s_addr, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = m, + }), + NULL); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_ROUTER, + str->str); + } + + if (sd_dhcp_lease_get_mtu(lease, &mtu) >= 0 && mtu) { + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, + mtu); + nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP); + } + + num = sd_dhcp_lease_get_ntp(lease, &addr_list); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + _nm_utils_inet4_ntop(addr_list[i].s_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NTP_SERVER, + str->str); + } + + if (sd_dhcp_lease_get_root_path(lease, &s) >= 0) { + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_ROOT_PATH, + s); + } + + if (sd_dhcp_lease_get_t1(lease, &renewal) >= 0) { + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME, + renewal); + } + + if (sd_dhcp_lease_get_t2(lease, &rebinding) >= 0) { + nm_dhcp_option_add_option_u64(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME, + rebinding); + } + + if (sd_dhcp_lease_get_timezone(lease, &s) >= 0) { + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp4_options, + NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, + s); + } + + if (sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len) >= 0) + metered = !!memmem(data, data_len, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED")); + nm_ip4_config_set_metered(ip4_config, metered); + + num = nm_sd_dhcp_lease_get_private_options(lease, &private_options); + if (num > 0) { + for (i = 0; i < num; i++) { + char *option_string; + + option_string = nm_utils_bin2hexstr_full(private_options[i].data, + private_options[i].data_len, + ':', + FALSE, + NULL); + if (!options) { + g_free(option_string); + continue; + } + nm_dhcp_option_take_option(options, + _nm_dhcp_option_dhcp4_options, + private_options[i].code, + option_string); + } + } + NM_SET_OUT(out_options, g_steal_pointer(&options)); + return g_steal_pointer(&ip4_config); +} + +/*****************************************************************************/ + +static void +bound4_handle(NMDhcpSystemd *self, gboolean extended) +{ + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + const char * iface = nm_dhcp_client_get_iface(NM_DHCP_CLIENT(self)); + gs_unref_object NMIP4Config *ip4_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + sd_dhcp_lease * lease = NULL; + GError * error = NULL; + + if (sd_dhcp_client_get_lease(priv->client4, &lease) < 0 || !lease) { + _LOGW("no lease!"); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return; + } + + _LOGD("lease available"); + + ip4_config = lease_to_ip4_config(nm_dhcp_client_get_multi_idx(NM_DHCP_CLIENT(self)), + iface, + nm_dhcp_client_get_ifindex(NM_DHCP_CLIENT(self)), + lease, + nm_dhcp_client_get_route_table(NM_DHCP_CLIENT(self)), + nm_dhcp_client_get_route_metric(NM_DHCP_CLIENT(self)), + &options, + &error); + if (!ip4_config) { + _LOGW("%s", error->message); + g_clear_error(&error); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return; + } + + nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options); + dhcp_lease_save(lease, priv->lease_file); + + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), + extended ? NM_DHCP_STATE_EXTENDED : NM_DHCP_STATE_BOUND, + NM_IP_CONFIG_CAST(ip4_config), + options); +} + +static int +dhcp_event_cb(sd_dhcp_client *client, int event, gpointer user_data) +{ + NMDhcpSystemd * self = NM_DHCP_SYSTEMD(user_data); + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + char addr_str[INET_ADDRSTRLEN]; + sd_dhcp_lease * lease = NULL; + struct in_addr addr; + int r; + + nm_assert(priv->client4 == client); + + _LOGD("client event %d", event); + + switch (event) { + case SD_DHCP_CLIENT_EVENT_EXPIRED: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(user_data), NM_DHCP_STATE_EXPIRE, NULL, NULL); + break; + case SD_DHCP_CLIENT_EVENT_STOP: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(user_data), NM_DHCP_STATE_FAIL, NULL, NULL); + break; + case SD_DHCP_CLIENT_EVENT_RENEW: + case SD_DHCP_CLIENT_EVENT_IP_CHANGE: + bound4_handle(self, TRUE); + break; + case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE: + bound4_handle(self, FALSE); + break; + case SD_DHCP_CLIENT_EVENT_SELECTING: + r = sd_dhcp_client_get_lease(priv->client4, &lease); + if (r < 0) + return r; + r = sd_dhcp_lease_get_server_identifier(lease, &addr); + if (r < 0) + return r; + if (nm_dhcp_client_server_id_is_rejected(NM_DHCP_CLIENT(user_data), &addr)) { + _LOGD("server-id %s is in the reject-list, ignoring", + nm_utils_inet_ntop(AF_INET, &addr, addr_str)); + return -ENOMSG; + } + break; + case SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE: + break; + default: + _LOGW("unhandled DHCP event %d", event); + break; + } + + return 0; +} + +static gboolean +ip4_start(NMDhcpClient *client, + const char * dhcp_anycast_addr, + const char * last_ip4_address, + GError ** error) +{ + nm_auto(sd_dhcp_client_unrefp) sd_dhcp_client *sd_client = NULL; + NMDhcpSystemd * self = NM_DHCP_SYSTEMD(client); + NMDhcpSystemdPrivate * priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + gs_free char * lease_file = NULL; + GBytes * hwaddr; + const uint8_t * hwaddr_arr; + gsize hwaddr_len; + int arp_type; + GBytes * client_id; + gs_unref_bytes GBytes *client_id_new = NULL; + GBytes * vendor_class_identifier; + const uint8_t * client_id_arr; + size_t client_id_len; + struct in_addr last_addr = {0}; + const char * hostname; + const char * mud_url; + int r, i; + GBytes * bcast_hwaddr; + const uint8_t * bcast_hwaddr_arr; + gsize bcast_hwaddr_len; + + g_return_val_if_fail(!priv->client4, FALSE); + g_return_val_if_fail(!priv->client6, FALSE); + + r = sd_dhcp_client_new(&sd_client, FALSE); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to create dhcp-client: %s"); + return FALSE; + } + + _LOGT("dhcp-client4: set %p", sd_client); + + r = sd_dhcp_client_attach_event(sd_client, NULL, 0); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to attach event: %s"); + return FALSE; + } + + hwaddr = nm_dhcp_client_get_hw_addr(client); + if (!hwaddr || !(hwaddr_arr = g_bytes_get_data(hwaddr, &hwaddr_len)) + || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len)) < 0) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address"); + return FALSE; + } + + bcast_hwaddr_arr = NULL; + if ((bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr(NM_DHCP_CLIENT(self)))) { + bcast_hwaddr_arr = g_bytes_get_data(bcast_hwaddr, &bcast_hwaddr_len); + if (bcast_hwaddr_len != hwaddr_len) + bcast_hwaddr_arr = NULL; + } + + r = sd_dhcp_client_set_mac(sd_client, + hwaddr_arr, + bcast_hwaddr_arr, + hwaddr_len, + (guint16) arp_type); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set MAC address: %s"); + return FALSE; + } + + r = sd_dhcp_client_set_ifindex(sd_client, nm_dhcp_client_get_ifindex(client)); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set ifindex: %s"); + return FALSE; + } + + nm_dhcp_utils_get_leasefile_path(AF_INET, + "internal", + nm_dhcp_client_get_iface(client), + nm_dhcp_client_get_uuid(client), + &lease_file); + + if (last_ip4_address) + inet_pton(AF_INET, last_ip4_address, &last_addr); + else { + nm_auto(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + + dhcp_lease_load(&lease, lease_file); + if (lease) + sd_dhcp_lease_get_address(lease, &last_addr); + } + + if (last_addr.s_addr) { + r = sd_dhcp_client_set_request_address(sd_client, &last_addr); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set last IPv4 address: %s"); + return FALSE; + } + } + + client_id = nm_dhcp_client_get_client_id(client); + if (!client_id) { + client_id_new = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_arr, hwaddr_len); + client_id = client_id_new; + } + + if (!(client_id_arr = g_bytes_get_data(client_id, &client_id_len)) || client_id_len < 2) { + /* invalid client-ids are not expected. */ + nm_assert_not_reached(); + + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id"); + return FALSE; + } + + /* Note that we always set a client-id. In particular for infiniband that is necessary, + * see https://tools.ietf.org/html/rfc4390#section-2.1 . */ + r = sd_dhcp_client_set_client_id(sd_client, + client_id_arr[0], + client_id_arr + 1, + NM_MIN(client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN)); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set IPv4 client-id: %s"); + return FALSE; + } + + /* Add requested options */ + for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) { + if (_nm_dhcp_option_dhcp4_options[i].include) { + nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255); + r = sd_dhcp_client_set_request_option(sd_client, + _nm_dhcp_option_dhcp4_options[i].option_num); + nm_assert(r >= 0 || r == -EEXIST); + } + } + + hostname = nm_dhcp_client_get_hostname(client); + if (hostname) { + /* FIXME: sd-dhcp decides which hostname/FQDN option to send (12 or 81) + * only based on whether the hostname has a domain part or not. At the + * moment there is no way to force one or another. + */ + r = sd_dhcp_client_set_hostname(sd_client, hostname); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set DHCP hostname: %s"); + return FALSE; + } + } + + mud_url = nm_dhcp_client_get_mud_url(client); + if (mud_url) { + r = sd_dhcp_client_set_mud_url(sd_client, mud_url); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set DHCP MUDURL: %s"); + return FALSE; + } + } + + vendor_class_identifier = nm_dhcp_client_get_vendor_class_identifier(client); + if (vendor_class_identifier) { + const char *option_data; + gsize len; + + option_data = g_bytes_get_data(vendor_class_identifier, &len); + nm_assert(option_data); + nm_assert(len <= 255); + + option_data = nm_strndup_a(300, option_data, len, NULL); + + r = sd_dhcp_client_set_vendor_class_identifier(sd_client, option_data); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set DHCP vendor class identifier: %s"); + return FALSE; + } + } + + r = sd_dhcp_client_set_callback(sd_client, dhcp_event_cb, client); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set callback: %s"); + return FALSE; + } + + priv->client4 = g_steal_pointer(&sd_client); + + g_free(priv->lease_file); + priv->lease_file = g_steal_pointer(&lease_file); + + nm_dhcp_client_set_client_id(client, client_id); + + r = sd_dhcp_client_start(priv->client4); + if (r < 0) { + sd_dhcp_client_set_callback(priv->client4, NULL, NULL); + nm_clear_pointer(&priv->client4, sd_dhcp_client_unref); + nm_utils_error_set_errno(error, r, "failed to start DHCP client: %s"); + return FALSE; + } + + nm_dhcp_client_start_timeout(client); + return TRUE; +} + +static NMIP6Config * +lease_to_ip6_config(NMDedupMultiIndex *multi_idx, + const char * iface, + int ifindex, + sd_dhcp6_lease * lease, + gboolean info_only, + GHashTable ** out_options, + gint32 ts, + GError ** error) +{ + gs_unref_object NMIP6Config *ip6_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + struct in6_addr tmp_addr; + const struct in6_addr * dns; + uint32_t lft_pref, lft_valid; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + char ** domains; + const char * s; + nm_auto_free_gstring GString *str = NULL; + int num, i; + + nm_assert(lease); + + ip6_config = nm_ip6_config_new(multi_idx, ifindex); + + options = out_options ? nm_dhcp_option_create_options_dict() : NULL; + + sd_dhcp6_lease_reset_address_iter(lease); + nm_gstring_prepare(&str); + while (sd_dhcp6_lease_get_address(lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) { + const NMPlatformIP6Address address = { + .plen = 128, + .address = tmp_addr, + .timestamp = ts, + .lifetime = lft_valid, + .preferred = lft_pref, + .addr_source = NM_IP_CONFIG_SOURCE_DHCP, + }; + + nm_ip6_config_add_address(ip6_config, &address); + + _nm_utils_inet6_ntop(&tmp_addr, addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + }; + if (str->len) + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp6_options, + NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, + str->str); + + if (!info_only && nm_ip6_config_get_num_addresses(ip6_config) == 0) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "no address received in managed mode"); + return NULL; + } + + num = sd_dhcp6_lease_get_dns(lease, &dns); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + _nm_utils_inet6_ntop(&dns[i], addr_str); + g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + nm_ip6_config_add_nameserver(ip6_config, &dns[i]); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp6_options, + NM_DHCP_OPTION_DHCP6_DNS_SERVERS, + str->str); + } + + num = sd_dhcp6_lease_get_domains(lease, &domains); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + g_string_append(nm_gstring_add_space_delimiter(str), domains[i]); + nm_ip6_config_add_search(ip6_config, domains[i]); + } + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp6_options, + NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, + str->str); + } + + if (sd_dhcp6_lease_get_fqdn(lease, &s) >= 0) { + nm_dhcp_option_add_option(options, + _nm_dhcp_option_dhcp6_options, + NM_DHCP_OPTION_DHCP6_FQDN, + s); + } + + NM_SET_OUT(out_options, g_steal_pointer(&options)); + return g_steal_pointer(&ip6_config); +} + +static void +bound6_handle(NMDhcpSystemd *self) +{ + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + const gint32 ts = nm_utils_get_monotonic_timestamp_sec(); + const char * iface = nm_dhcp_client_get_iface(NM_DHCP_CLIENT(self)); + gs_unref_object NMIP6Config *ip6_config = NULL; + gs_unref_hashtable GHashTable *options = NULL; + gs_free_error GError *error = NULL; + NMPlatformIP6Address prefix = {0}; + sd_dhcp6_lease * lease = NULL; + + if (sd_dhcp6_client_get_lease(priv->client6, &lease) < 0 || !lease) { + _LOGW(" no lease!"); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return; + } + + _LOGD("lease available"); + + ip6_config = lease_to_ip6_config(nm_dhcp_client_get_multi_idx(NM_DHCP_CLIENT(self)), + iface, + nm_dhcp_client_get_ifindex(NM_DHCP_CLIENT(self)), + lease, + nm_dhcp_client_get_info_only(NM_DHCP_CLIENT(self)), + &options, + ts, + &error); + + if (!ip6_config) { + _LOGW("%s", error->message); + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), NM_DHCP_STATE_FAIL, NULL, NULL); + return; + } + + nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), + NM_DHCP_STATE_BOUND, + NM_IP_CONFIG_CAST(ip6_config), + options); + + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + while (!sd_dhcp6_lease_get_pd(lease, + &prefix.address, + &prefix.plen, + &prefix.preferred, + &prefix.lifetime)) { + prefix.timestamp = ts; + nm_dhcp_client_emit_ipv6_prefix_delegated(NM_DHCP_CLIENT(self), &prefix); + } +} + +static void +dhcp6_event_cb(sd_dhcp6_client *client, int event, gpointer user_data) +{ + NMDhcpSystemd * self = NM_DHCP_SYSTEMD(user_data); + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + + nm_assert(priv->client6 == client); + + _LOGD("client event %d", event); + + switch (event) { + case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(user_data), NM_DHCP_STATE_TIMEOUT, NULL, NULL); + break; + case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE: + case SD_DHCP6_CLIENT_EVENT_STOP: + nm_dhcp_client_set_state(NM_DHCP_CLIENT(user_data), NM_DHCP_STATE_FAIL, NULL, NULL); + break; + case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: + case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: + bound6_handle(self); + break; + default: + _LOGW("unhandled event %d", event); + break; + } +} + +static gboolean +ip6_start(NMDhcpClient * client, + const char * dhcp_anycast_addr, + const struct in6_addr * ll_addr, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error) +{ + NMDhcpSystemd * self = NM_DHCP_SYSTEMD(client); + NMDhcpSystemdPrivate * priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + nm_auto(sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL; + const char * hostname; + const char * mud_url; + int r, i; + const guint8 * duid_arr; + gsize duid_len; + GBytes * duid; + + g_return_val_if_fail(!priv->client4, FALSE); + g_return_val_if_fail(!priv->client6, FALSE); + + if (!(duid = nm_dhcp_client_get_client_id(client)) + || !(duid_arr = g_bytes_get_data(duid, &duid_len)) || duid_len < 2) { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing DUID"); + g_return_val_if_reached(FALSE); + } + + r = sd_dhcp6_client_new(&sd_client); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to create dhcp-client: %s"); + return FALSE; + } + + _LOGT("dhcp-client6: set %p", sd_client); + + if (nm_dhcp_client_get_info_only(client)) + sd_dhcp6_client_set_information_request(sd_client, 1); + + r = sd_dhcp6_client_set_iaid(sd_client, nm_dhcp_client_get_iaid(client)); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set IAID: %s"); + return FALSE; + } + + r = sd_dhcp6_client_set_duid(sd_client, + unaligned_read_be16(&duid_arr[0]), + &duid_arr[2], + duid_len - 2); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set DUID: %s"); + return FALSE; + } + + r = sd_dhcp6_client_attach_event(sd_client, NULL, 0); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to attach event: %s"); + return FALSE; + } + + r = sd_dhcp6_client_set_ifindex(sd_client, nm_dhcp_client_get_ifindex(client)); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set ifindex: %s"); + return FALSE; + } + + /* Add requested options */ + for (i = 0; _nm_dhcp_option_dhcp6_options[i].name; i++) { + if (_nm_dhcp_option_dhcp6_options[i].include) { + r = sd_dhcp6_client_set_request_option(sd_client, + _nm_dhcp_option_dhcp6_options[i].option_num); + nm_assert(r >= 0 || r == -EEXIST); + } + } + + mud_url = nm_dhcp_client_get_mud_url(client); + if (mud_url) { + r = sd_dhcp6_client_set_request_mud_url(sd_client, mud_url); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set mud-url: %s"); + return FALSE; + } + } + + if (needed_prefixes > 0) { + if (needed_prefixes > 1) + _LOGW("dhcp-client6: only one prefix request is supported"); + /* FIXME: systemd-networkd API only allows to request a + * single prefix */ + r = sd_dhcp6_client_set_prefix_delegation(sd_client, TRUE); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to enable prefix delegation: %s"); + return FALSE; + } + } + + r = sd_dhcp6_client_set_local_address(sd_client, ll_addr); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set local address: %s"); + return FALSE; + } + + hostname = nm_dhcp_client_get_hostname(client); + r = sd_dhcp6_client_set_fqdn(sd_client, hostname); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set DHCP hostname: %s"); + return FALSE; + } + + r = sd_dhcp6_client_set_callback(sd_client, dhcp6_event_cb, client); + if (r < 0) { + nm_utils_error_set_errno(error, r, "failed to set callback: %s"); + return FALSE; + } + + priv->client6 = g_steal_pointer(&sd_client); + + r = sd_dhcp6_client_start(priv->client6); + if (r < 0) { + sd_dhcp6_client_set_callback(priv->client6, NULL, NULL); + nm_clear_pointer(&priv->client6, sd_dhcp6_client_unref); + nm_utils_error_set_errno(error, r, "failed to start client: %s"); + return FALSE; + } + + nm_dhcp_client_start_timeout(client); + return TRUE; +} + +static void +stop(NMDhcpClient *client, gboolean release) +{ + NMDhcpSystemd * self = NM_DHCP_SYSTEMD(client); + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self); + int r = 0; + + NM_DHCP_CLIENT_CLASS(nm_dhcp_systemd_parent_class)->stop(client, release); + + _LOGT("dhcp-client%d: stop %p", + priv->client4 ? '4' : '6', + priv->client4 ? (gpointer) priv->client4 : (gpointer) priv->client6); + + if (priv->client4) { + sd_dhcp_client_set_callback(priv->client4, NULL, NULL); + r = sd_dhcp_client_stop(priv->client4); + } else if (priv->client6) { + sd_dhcp6_client_set_callback(priv->client6, NULL, NULL); + r = sd_dhcp6_client_stop(priv->client6); + } + + if (r) + _LOGW("failed to stop client (%d)", r); +} + +/*****************************************************************************/ + +static void +nm_dhcp_systemd_init(NMDhcpSystemd *self) +{} + +static void +dispose(GObject *object) +{ + NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(object); + + nm_clear_g_free(&priv->lease_file); + + if (priv->client4) { + sd_dhcp_client_stop(priv->client4); + sd_dhcp_client_unref(priv->client4); + priv->client4 = NULL; + } + + if (priv->client6) { + sd_dhcp6_client_stop(priv->client6); + sd_dhcp6_client_unref(priv->client6); + priv->client6 = NULL; + } + + G_OBJECT_CLASS(nm_dhcp_systemd_parent_class)->dispose(object); +} + +static void +nm_dhcp_systemd_class_init(NMDhcpSystemdClass *sdhcp_class) +{ + NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(sdhcp_class); + GObjectClass * object_class = G_OBJECT_CLASS(sdhcp_class); + + object_class->dispose = dispose; + + client_class->ip4_start = ip4_start; + client_class->ip6_start = ip6_start; + client_class->stop = stop; +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_systemd = { + .name = "systemd", + .get_type = nm_dhcp_systemd_get_type, + .experimental = TRUE, +}; + +/*****************************************************************************/ + +static GType +_get_type_per_addr_family(int addr_family) +{ + nm_assert_addr_family(addr_family); + + if (addr_family == AF_INET) + return nm_dhcp_nettools_get_type(); + return nm_dhcp_systemd_get_type(); +} + +const NMDhcpClientFactory _nm_dhcp_client_factory_internal = { + .name = "internal", + .get_type_per_addr_family = _get_type_per_addr_family, +}; diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c new file mode 100644 index 0000000..ed11a6e --- /dev/null +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -0,0 +1,836 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" + +#include "nm-dhcp-utils.h" +#include "nm-utils.h" +#include "nm-config.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-dhcp-client-logging.h" +#include "nm-core-internal.h" + +/*****************************************************************************/ + +static gboolean +ip4_process_dhcpcd_rfc3442_routes(const char * iface, + const char * str, + guint32 route_table, + guint32 route_metric, + NMIP4Config *ip4_config, + guint32 * gwaddr) +{ + gs_free const char **routes = NULL; + const char ** r; + gboolean have_routes = FALSE; + + routes = nm_utils_strsplit_set(str, " "); + if (!routes) + return FALSE; + + if ((NM_PTRARRAY_LEN(routes) % 2) != 0) { + _LOG2W(LOGD_DHCP4, iface, " classless static routes provided, but invalid"); + return FALSE; + } + + for (r = routes; *r; r += 2) { + char * slash; + NMPlatformIP4Route route; + int rt_cidr = 32; + guint32 rt_addr, rt_route; + + slash = strchr(*r, '/'); + if (slash) { + *slash = '\0'; + errno = 0; + rt_cidr = strtol(slash + 1, NULL, 10); + if (errno || rt_cidr > 32) { + _LOG2W(LOGD_DHCP4, + iface, + "DHCP provided invalid classless static route cidr: '%s'", + slash + 1); + continue; + } + } + if (inet_pton(AF_INET, *r, &rt_addr) <= 0) { + _LOG2W(LOGD_DHCP4, + iface, + "DHCP provided invalid classless static route address: '%s'", + *r); + continue; + } + if (inet_pton(AF_INET, *(r + 1), &rt_route) <= 0) { + _LOG2W(LOGD_DHCP4, + iface, + "DHCP provided invalid classless static route gateway: '%s'", + *(r + 1)); + continue; + } + + have_routes = TRUE; + if (rt_cidr == 0 && rt_addr == 0) { + /* FIXME: how to handle multiple routers? */ + *gwaddr = rt_route; + } else { + _LOG2I(LOGD_DHCP4, + iface, + " classless static route %s/%d gw %s", + *r, + rt_cidr, + *(r + 1)); + memset(&route, 0, sizeof(route)); + route.network = nm_utils_ip4_address_clear_host_address(rt_addr, rt_cidr); + route.plen = rt_cidr; + route.gateway = rt_route; + route.rt_source = NM_IP_CONFIG_SOURCE_DHCP; + route.metric = route_metric; + route.table_coerced = nm_platform_route_table_coerce(route_table); + nm_ip4_config_add_route(ip4_config, &route, NULL); + } + } + + return have_routes; +} + +static gboolean +process_dhclient_rfc3442_route(const char *const **p_octets, NMPlatformIP4Route *route) +{ + const char *const *o = *p_octets; + gs_free char * next_hop = NULL; + int addr_len; + int v_plen; + in_addr_t tmp_addr; + in_addr_t v_network = 0; + + v_plen = _nm_utils_ascii_str_to_int64(*o, 10, 0, 32, -1); + if (v_plen == -1) + return FALSE; + o++; + + addr_len = v_plen > 0 ? ((v_plen - 1) / 8) + 1 : 0; + + /* ensure there's at least the address + next hop left */ + if (NM_PTRARRAY_LEN(o) < addr_len + 4) + return FALSE; + + if (v_plen > 0) { + const char * addr[4] = {"0", "0", "0", "0"}; + gs_free char *str_addr = NULL; + int i; + + for (i = 0; i < addr_len; i++) + addr[i] = *o++; + + str_addr = g_strjoin(".", addr[0], addr[1], addr[2], addr[3], NULL); + if (inet_pton(AF_INET, str_addr, &tmp_addr) <= 0) + return FALSE; + v_network = nm_utils_ip4_address_clear_host_address(tmp_addr, v_plen); + } + + next_hop = g_strjoin(".", o[0], o[1], o[2], o[3], NULL); + o += 4; + if (inet_pton(AF_INET, next_hop, &tmp_addr) <= 0) + return FALSE; + + *route = (NMPlatformIP4Route){ + .network = v_network, + .plen = v_plen, + .gateway = tmp_addr, + }; + *p_octets = o; + return TRUE; +} + +static gboolean +ip4_process_dhclient_rfc3442_routes(const char * iface, + const char * str, + guint32 route_table, + guint32 route_metric, + NMIP4Config *ip4_config, + guint32 * gwaddr) +{ + gs_free const char **octets = NULL; + const char *const * o; + gboolean have_routes = FALSE; + + octets = nm_utils_strsplit_set_with_empty(str, " ."); + if (NM_PTRARRAY_LEN(octets) < 5) { + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str); + return FALSE; + } + + o = octets; + while (*o) { + NMPlatformIP4Route route; + + if (!process_dhclient_rfc3442_route(&o, &route)) { + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid classless static routes"); + return have_routes; + } + + have_routes = TRUE; + if (!route.plen) { + /* gateway passed as classless static route */ + *gwaddr = route.gateway; + } else { + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + + /* normal route */ + route.rt_source = NM_IP_CONFIG_SOURCE_DHCP; + route.metric = route_metric; + route.table_coerced = nm_platform_route_table_coerce(route_table); + nm_ip4_config_add_route(ip4_config, &route, NULL); + + _LOG2I(LOGD_DHCP4, + iface, + " classless static route %s/%d gw %s", + _nm_utils_inet4_ntop(route.network, b1), + route.plen, + _nm_utils_inet4_ntop(route.gateway, b2)); + } + } + + return have_routes; +} + +static gboolean +ip4_process_classless_routes(const char * iface, + GHashTable * options, + guint32 route_table, + guint32 route_metric, + NMIP4Config *ip4_config, + guint32 * gwaddr) +{ + const char *str, *p; + + g_return_val_if_fail(options != NULL, FALSE); + g_return_val_if_fail(ip4_config != NULL, FALSE); + + *gwaddr = 0; + + /* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a + * slightly different format: + * + * option classless-static-routes = array of (destination-descriptor ip-address); + * + * which results in: + * + * 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6 + * + * dhcpcd supports classless static routes natively and uses this same + * option identifier with the following format: + * + * 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41 + */ + str = g_hash_table_lookup(options, "classless_static_routes"); + + /* dhclient doesn't have actual support for rfc3442 classless static routes + * upstream. Thus, people resort to defining the option in dhclient.conf + * and using arbitrary formats like so: + * + * option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; + * + * See https://lists.isc.org/pipermail/dhcp-users/2008-December/007629.html + */ + if (!str) + str = g_hash_table_lookup(options, "rfc3442_classless_static_routes"); + + /* Microsoft version; same as rfc3442 but with a different option # (249) */ + if (!str) + str = g_hash_table_lookup(options, "ms_classless_static_routes"); + + if (!str || !strlen(str)) + return FALSE; + + p = str; + while (*p) { + if (!g_ascii_isdigit(*p) && (*p != ' ') && (*p != '.') && (*p != '/')) { + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str); + return FALSE; + } + p++; + }; + + if (strchr(str, '/')) { + /* dhcpcd format */ + return ip4_process_dhcpcd_rfc3442_routes(iface, + str, + route_table, + route_metric, + ip4_config, + gwaddr); + } + + return ip4_process_dhclient_rfc3442_routes(iface, + str, + route_table, + route_metric, + ip4_config, + gwaddr); +} + +static void +process_classful_routes(const char * iface, + GHashTable * options, + guint32 route_table, + guint32 route_metric, + NMIP4Config *ip4_config) +{ + gs_free const char **searches = NULL; + const char ** s; + const char * str; + + str = g_hash_table_lookup(options, "static_routes"); + if (!str) + return; + + searches = nm_utils_strsplit_set(str, " "); + if (!searches) + return; + + if ((NM_PTRARRAY_LEN(searches) % 2) != 0) { + _LOG2I(LOGD_DHCP, iface, " static routes provided, but invalid"); + return; + } + + for (s = searches; *s; s += 2) { + NMPlatformIP4Route route; + guint32 rt_addr, rt_route; + + if (inet_pton(AF_INET, *s, &rt_addr) <= 0) { + _LOG2W(LOGD_DHCP, iface, "DHCP provided invalid static route address: '%s'", *s); + continue; + } + if (inet_pton(AF_INET, *(s + 1), &rt_route) <= 0) { + _LOG2W(LOGD_DHCP, iface, "DHCP provided invalid static route gateway: '%s'", *(s + 1)); + continue; + } + + // FIXME: ensure the IP address and route are sane + + memset(&route, 0, sizeof(route)); + route.network = rt_addr; + /* RFC 2132, updated by RFC 3442: + * The Static Routes option (option 33) does not provide a subnet mask + * for each route - it is assumed that the subnet mask is implicit in + * whatever network number is specified in each route entry */ + route.plen = _nm_utils_ip4_get_default_prefix(rt_addr); + if (rt_addr & ~_nm_utils_ip4_prefix_to_netmask(route.plen)) { + /* RFC 943: target not "this network"; using host routing */ + route.plen = 32; + } + route.gateway = rt_route; + route.rt_source = NM_IP_CONFIG_SOURCE_DHCP; + route.metric = route_metric; + route.table_coerced = nm_platform_route_table_coerce(route_table); + + route.network = nm_utils_ip4_address_clear_host_address(route.network, route.plen); + + nm_ip4_config_add_route(ip4_config, &route, NULL); + _LOG2I(LOGD_DHCP, + iface, + " static route %s", + nm_platform_ip4_route_to_string(&route, NULL, 0)); + } +} + +static void +process_domain_search(const char *iface, const char *str, GFunc add_func, gpointer user_data) +{ + gs_free const char **searches = NULL; + gs_free char * unescaped = NULL; + const char ** s; + char * p; + int i; + + g_return_if_fail(str != NULL); + g_return_if_fail(add_func != NULL); + + unescaped = g_strdup(str); + + p = unescaped; + do { + p = strstr(p, "\\032"); + if (!p) + break; + + /* Clear the escaped space with real spaces */ + for (i = 0; i < 4; i++) + *p++ = ' '; + } while (*p++); + + if (strchr(unescaped, '\\')) { + _LOG2W(LOGD_DHCP, iface, " invalid domain search: '%s'", unescaped); + return; + } + + searches = nm_utils_strsplit_set(unescaped, " "); + for (s = searches; searches && *s; s++) { + _LOG2I(LOGD_DHCP, iface, " domain search '%s'", *s); + add_func((gpointer) *s, user_data); + } +} + +static void +ip4_add_domain_search(gpointer data, gpointer user_data) +{ + nm_ip4_config_add_search(NM_IP4_CONFIG(user_data), (const char *) data); +} + +NMIP4Config * +nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx, + int ifindex, + const char * iface, + GHashTable * options, + guint32 route_table, + guint32 route_metric) +{ + gs_unref_object NMIP4Config *ip4_config = NULL; + guint32 tmp_addr; + in_addr_t addr; + NMPlatformIP4Address address; + char * str = NULL; + gboolean gateway_has = FALSE; + guint32 gateway = 0; + guint8 plen = 0; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_val_if_fail(options != NULL, NULL); + + ip4_config = nm_ip4_config_new(multi_idx, ifindex); + memset(&address, 0, sizeof(address)); + address.timestamp = nm_utils_get_monotonic_timestamp_sec(); + + str = g_hash_table_lookup(options, "ip_address"); + if (str && (inet_pton(AF_INET, str, &addr) > 0)) + _LOG2I(LOGD_DHCP4, iface, " address %s", str); + else + return NULL; + + str = g_hash_table_lookup(options, "subnet_mask"); + if (str && (inet_pton(AF_INET, str, &tmp_addr) > 0)) { + plen = nm_utils_ip4_netmask_to_prefix(tmp_addr); + _LOG2I(LOGD_DHCP4, iface, " plen %d (%s)", plen, str); + } else { + /* Get default netmask for the IP according to appropriate class. */ + plen = _nm_utils_ip4_get_default_prefix(addr); + _LOG2I(LOGD_DHCP4, iface, " plen %d (default)", plen); + } + nm_platform_ip4_address_set_addr(&address, addr, plen); + + /* Routes: if the server returns classless static routes, we MUST ignore + * the 'static_routes' option. + */ + if (!ip4_process_classless_routes(iface, + options, + route_table, + route_metric, + ip4_config, + &gateway)) + process_classful_routes(iface, options, route_table, route_metric, ip4_config); + + if (gateway) { + _LOG2I(LOGD_DHCP4, iface, " gateway %s", _nm_utils_inet4_ntop(gateway, sbuf)); + gateway_has = TRUE; + } else { + /* If the gateway wasn't provided as a classless static route with a + * subnet length of 0, try to find it using the old-style 'routers' option. + */ + str = g_hash_table_lookup(options, "routers"); + if (str) { + gs_free const char **routers = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = routers; routers && *s; s++) { + /* FIXME: how to handle multiple routers? */ + if (inet_pton(AF_INET, *s, &gateway) > 0) { + _LOG2I(LOGD_DHCP4, iface, " gateway %s", *s); + gateway_has = TRUE; + break; + } else + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid gateway '%s'", *s); + } + } + } + + if (gateway_has) { + const NMPlatformIP4Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .gateway = gateway, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + + nm_ip4_config_add_route(ip4_config, &r, NULL); + } + + str = g_hash_table_lookup(options, "dhcp_lease_time"); + if (str) { + address.lifetime = address.preferred = strtoul(str, NULL, 10); + _LOG2I(LOGD_DHCP4, iface, " lease time %u", address.lifetime); + } + + address.addr_source = NM_IP_CONFIG_SOURCE_DHCP; + nm_ip4_config_add_address(ip4_config, &address); + + str = g_hash_table_lookup(options, "host_name"); + if (str) + _LOG2I(LOGD_DHCP4, iface, " hostname '%s'", str); + + str = g_hash_table_lookup(options, "domain_name_servers"); + if (str) { + gs_free const char **dns = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = dns; dns && *s; s++) { + if (inet_pton(AF_INET, *s, &tmp_addr) > 0) { + if (tmp_addr) { + nm_ip4_config_add_nameserver(ip4_config, tmp_addr); + _LOG2I(LOGD_DHCP4, iface, " nameserver '%s'", *s); + } + } else + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid nameserver '%s'", *s); + } + } + + str = g_hash_table_lookup(options, "domain_name"); + if (str) { + gs_free const char **domains = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = domains; domains && *s; s++) { + _LOG2I(LOGD_DHCP4, iface, " domain name '%s'", *s); + nm_ip4_config_add_domain(ip4_config, *s); + } + } + + str = g_hash_table_lookup(options, "domain_search"); + if (str) + process_domain_search(iface, str, ip4_add_domain_search, ip4_config); + + str = g_hash_table_lookup(options, "netbios_name_servers"); + if (str) { + gs_free const char **nbns = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = nbns; nbns && *s; s++) { + if (inet_pton(AF_INET, *s, &tmp_addr) > 0) { + if (tmp_addr) { + nm_ip4_config_add_wins(ip4_config, tmp_addr); + _LOG2I(LOGD_DHCP4, iface, " wins '%s'", *s); + } + } else + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid WINS server '%s'", *s); + } + } + + str = g_hash_table_lookup(options, "interface_mtu"); + if (str) { + int int_mtu; + + errno = 0; + int_mtu = strtol(str, NULL, 10); + if (NM_IN_SET(errno, EINVAL, ERANGE)) + return NULL; + + if (int_mtu > 576) + nm_ip4_config_set_mtu(ip4_config, int_mtu, NM_IP_CONFIG_SOURCE_DHCP); + } + + str = g_hash_table_lookup(options, "nis_domain"); + if (str) { + _LOG2I(LOGD_DHCP4, iface, " NIS domain '%s'", str); + nm_ip4_config_set_nis_domain(ip4_config, str); + } + + str = g_hash_table_lookup(options, "nis_servers"); + if (str) { + gs_free const char **nis = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = nis; nis && *s; s++) { + if (inet_pton(AF_INET, *s, &tmp_addr) > 0) { + if (tmp_addr) { + nm_ip4_config_add_nis_server(ip4_config, tmp_addr); + _LOG2I(LOGD_DHCP4, iface, " nis '%s'", *s); + } + } else + _LOG2W(LOGD_DHCP4, iface, "ignoring invalid NIS server '%s'", *s); + } + } + + str = g_hash_table_lookup(options, "vendor_encapsulated_options"); + nm_ip4_config_set_metered(ip4_config, str && strstr(str, "ANDROID_METERED")); + + return g_steal_pointer(&ip4_config); +} + +/*****************************************************************************/ + +static void +ip6_add_domain_search(gpointer data, gpointer user_data) +{ + nm_ip6_config_add_search(NM_IP6_CONFIG(user_data), (const char *) data); +} + +NMPlatformIP6Address +nm_dhcp_utils_ip6_prefix_from_options(GHashTable *options) +{ + gs_strfreev char ** split_addr = NULL; + NMPlatformIP6Address address = { + 0, + }; + struct in6_addr tmp_addr; + char * str = NULL; + int prefix; + + g_return_val_if_fail(options != NULL, address); + + str = g_hash_table_lookup(options, "ip6_prefix"); + if (!str) + return address; + + split_addr = g_strsplit(str, "/", 2); + if (split_addr[0] == NULL && split_addr[1] == NULL) { + nm_log_warn(LOGD_DHCP6, "DHCP returned prefix without length '%s'", str); + return address; + } + + if (!inet_pton(AF_INET6, split_addr[0], &tmp_addr)) { + nm_log_warn(LOGD_DHCP6, "DHCP returned invalid prefix '%s'", str); + return address; + } + + prefix = _nm_utils_ascii_str_to_int64(split_addr[1], 10, 0, 128, -1); + if (prefix < 0) { + nm_log_warn(LOGD_DHCP6, "DHCP returned prefix with invalid length '%s'", str); + return address; + } + + address.address = tmp_addr; + address.addr_source = NM_IP_CONFIG_SOURCE_DHCP; + address.plen = prefix; + address.timestamp = nm_utils_get_monotonic_timestamp_sec(); + + str = g_hash_table_lookup(options, "max_life"); + if (str) + address.lifetime = strtoul(str, NULL, 10); + + str = g_hash_table_lookup(options, "preferred_life"); + if (str) + address.preferred = strtoul(str, NULL, 10); + + return address; +} + +NMIP6Config * +nm_dhcp_utils_ip6_config_from_options(NMDedupMultiIndex *multi_idx, + int ifindex, + const char * iface, + GHashTable * options, + gboolean info_only) +{ + gs_unref_object NMIP6Config *ip6_config = NULL; + struct in6_addr tmp_addr; + NMPlatformIP6Address address; + char * str = NULL; + + g_return_val_if_fail(options != NULL, NULL); + + memset(&address, 0, sizeof(address)); + address.plen = 128; + address.timestamp = nm_utils_get_monotonic_timestamp_sec(); + + ip6_config = nm_ip6_config_new(multi_idx, ifindex); + + str = g_hash_table_lookup(options, "max_life"); + if (str) { + address.lifetime = strtoul(str, NULL, 10); + _LOG2I(LOGD_DHCP6, iface, " valid_lft %u", address.lifetime); + } + + str = g_hash_table_lookup(options, "preferred_life"); + if (str) { + address.preferred = strtoul(str, NULL, 10); + _LOG2I(LOGD_DHCP6, iface, " preferred_lft %u", address.preferred); + } + + str = g_hash_table_lookup(options, "ip6_address"); + if (str) { + if (!inet_pton(AF_INET6, str, &tmp_addr)) { + _LOG2W(LOGD_DHCP6, iface, "(%s): DHCP returned invalid address '%s'", iface, str); + return NULL; + } + + address.address = tmp_addr; + address.addr_source = NM_IP_CONFIG_SOURCE_DHCP; + nm_ip6_config_add_address(ip6_config, &address); + _LOG2I(LOGD_DHCP6, iface, " address %s", str); + } else if (info_only == FALSE) { + /* No address in Managed mode is a hard error */ + return NULL; + } + + str = g_hash_table_lookup(options, "host_name"); + if (str) + _LOG2I(LOGD_DHCP6, iface, " hostname '%s'", str); + + str = g_hash_table_lookup(options, "dhcp6_name_servers"); + if (str) { + gs_free const char **dns = nm_utils_strsplit_set(str, " "); + const char ** s; + + for (s = dns; dns && *s; s++) { + if (inet_pton(AF_INET6, *s, &tmp_addr) > 0) { + if (!IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) { + nm_ip6_config_add_nameserver(ip6_config, &tmp_addr); + _LOG2I(LOGD_DHCP6, iface, " nameserver '%s'", *s); + } + } else + _LOG2W(LOGD_DHCP6, iface, "ignoring invalid nameserver '%s'", *s); + } + } + + str = g_hash_table_lookup(options, "dhcp6_domain_search"); + if (str) + process_domain_search(iface, str, ip6_add_domain_search, ip6_config); + + return g_steal_pointer(&ip6_config); +} + +char * +nm_dhcp_utils_duid_to_string(GBytes *duid) +{ + gconstpointer data; + gsize len; + + g_return_val_if_fail(duid, NULL); + + data = g_bytes_get_data(duid, &len); + return nm_utils_bin2hexstr_full(data, len, ':', FALSE, NULL); +} + +/** + * nm_dhcp_utils_client_id_string_to_bytes: + * @client_id: the client ID string + * + * Accepts either a hex string ("aa:bb:cc") representing a binary client ID + * (the first byte is assumed to be the 'type' field per RFC 2132 section 9.14), + * or a string representing a non-hardware-address client ID, in which case + * the 'type' field is set to 0. + * + * Returns: the binary client ID suitable for sending over the wire + * to the DHCP server. + */ +GBytes * +nm_dhcp_utils_client_id_string_to_bytes(const char *client_id) +{ + GBytes *bytes = NULL; + guint len; + char * c; + + g_return_val_if_fail(client_id && client_id[0], NULL); + + /* Try as hex encoded */ + if (strchr(client_id, ':')) { + bytes = nm_utils_hexstr2bin(client_id); + + /* the result must be at least two bytes long, + * because @client_id contains a delimiter + * but nm_utils_hexstr2bin() does not allow + * leading nor trailing delimiters. */ + nm_assert(!bytes || g_bytes_get_size(bytes) >= 2); + } + if (!bytes) { + /* Fall back to string */ + len = strlen(client_id); + c = g_malloc(len + 1); + c[0] = 0; /* type: non-hardware address per RFC 2132 section 9.14 */ + memcpy(c + 1, client_id, len); + bytes = g_bytes_new_take(c, len + 1); + } + + return bytes; +} + +/** + * nm_dhcp_utils_get_leasefile_path: + * @addr_family: the IP address family + * @plugin_name: the name of the plugin part of the lease file name + * @iface: the interface name to which the lease relates to + * @uuid: uuid of the connection to which the lease relates to + * @out_leasefile_path: will store the computed lease file path + * + * Constructs the lease file name on the basis of the calling plugin, + * interface name and connection uuid. Then returns in @out_leasefile_path + * the full path of the lease filename. + * + * Returns: TRUE if the lease file already exists, FALSE otherwise. + */ +gboolean +nm_dhcp_utils_get_leasefile_path(int addr_family, + const char *plugin_name, + const char *iface, + const char *uuid, + char ** out_leasefile_path) +{ + gs_free char *rundir_path = NULL; + gs_free char *statedir_path = NULL; + + rundir_path = g_strdup_printf(NMRUNDIR "/%s%s-%s-%s.lease", + plugin_name, + addr_family == AF_INET6 ? "6" : "", + uuid, + iface); + + if (g_file_test(rundir_path, G_FILE_TEST_EXISTS)) { + *out_leasefile_path = g_steal_pointer(&rundir_path); + return TRUE; + } + + statedir_path = g_strdup_printf(NMSTATEDIR "/%s%s-%s-%s.lease", + plugin_name, + addr_family == AF_INET6 ? "6" : "", + uuid, + iface); + + if (g_file_test(statedir_path, G_FILE_TEST_EXISTS)) { + *out_leasefile_path = g_steal_pointer(&statedir_path); + return TRUE; + } + + if (nm_config_get_configure_and_quit(nm_config_get()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) + *out_leasefile_path = g_steal_pointer(&rundir_path); + else + *out_leasefile_path = g_steal_pointer(&statedir_path); + return FALSE; +} + +char * +nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease) +{ + const char *start; + const char *iaid; + + if (!lease) + return NULL; + + iaid = g_hash_table_lookup(lease, "iaid"); + if (!iaid) + return NULL; + + start = g_hash_table_lookup(lease, "life_starts"); + if (!start) + return NULL; + + return g_strdup_printf("%s|%s", iaid, start); +} diff --git a/src/core/dhcp/nm-dhcp-utils.h b/src/core/dhcp/nm-dhcp-utils.h new file mode 100644 index 0000000..f5bc53a --- /dev/null +++ b/src/core/dhcp/nm-dhcp-utils.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_UTILS_H__ +#define __NETWORKMANAGER_DHCP_UTILS_H__ + +#include + +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" + +NMIP4Config *nm_dhcp_utils_ip4_config_from_options(struct _NMDedupMultiIndex *multi_idx, + int ifindex, + const char * iface, + GHashTable * options, + guint32 route_table, + guint32 route_metric); + +NMIP6Config *nm_dhcp_utils_ip6_config_from_options(struct _NMDedupMultiIndex *multi_idx, + int ifindex, + const char * iface, + GHashTable * options, + gboolean info_only); + +NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options(GHashTable *options); + +char *nm_dhcp_utils_duid_to_string(GBytes *duid); + +GBytes *nm_dhcp_utils_client_id_string_to_bytes(const char *client_id); + +gboolean nm_dhcp_utils_get_leasefile_path(int addr_family, + const char *plugin_name, + const char *iface, + const char *uuid, + char ** out_leasefile_path); + +char **nm_dhcp_parse_search_list(guint8 *data, size_t n_data); + +char *nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease); + +#endif /* __NETWORKMANAGER_DHCP_UTILS_H__ */ diff --git a/src/core/dhcp/tests/meson.build b/src/core/dhcp/tests/meson.build new file mode 100644 index 0000000..e43c8ca --- /dev/null +++ b/src/core/dhcp/tests/meson.build @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_units = [ + 'test-dhcp-dhclient', + 'test-dhcp-utils', +] + +foreach test_unit: test_units + exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + ) + + test( + 'dhcp/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endforeach diff --git a/src/core/dhcp/tests/test-dhclient-commented-duid.leases b/src/core/dhcp/tests/test-dhclient-commented-duid.leases new file mode 100644 index 0000000..3e46ae7 --- /dev/null +++ b/src/core/dhcp/tests/test-dhclient-commented-duid.leases @@ -0,0 +1,2 @@ +#default-duid "\000\001\000\001\030y\246\023`g \354Lp"; + diff --git a/src/core/dhcp/tests/test-dhclient-duid.leases b/src/core/dhcp/tests/test-dhclient-duid.leases new file mode 100644 index 0000000..229331d --- /dev/null +++ b/src/core/dhcp/tests/test-dhclient-duid.leases @@ -0,0 +1,2 @@ +default-duid "\000\001\000\001\030y\246\023`g \354Lp"; + diff --git a/src/core/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c new file mode 100644 index 0000000..6b3ec7a --- /dev/null +++ b/src/core/dhcp/tests/test-dhcp-dhclient.c @@ -0,0 +1,1341 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" + +#include "NetworkManagerUtils.h" +#include "dhcp/nm-dhcp-dhclient-utils.h" +#include "dhcp/nm-dhcp-utils.h" +#include "nm-utils.h" +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" + +#include "nm-test-utils-core.h" + +#define TEST_DIR NM_BUILD_SRCDIR "/src/core/dhcp/tests" +#define TEST_MUDURL "https://example.com/mud.json" + +static void +test_config(const char * orig, + const char * expected, + int addr_family, + const char * hostname, + guint32 timeout, + gboolean use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * dhcp_client_id, + GBytes * expected_new_client_id, + const char * iface, + const char * anycast_addr, + const char * mud_url) +{ + gs_free char *new = NULL; + gs_unref_bytes GBytes *client_id = NULL; + gs_unref_bytes GBytes *new_client_id = NULL; + + if (dhcp_client_id) { + client_id = nm_dhcp_utils_client_id_string_to_bytes(dhcp_client_id); + g_assert(client_id); + } + + new = nm_dhcp_dhclient_create_config(iface, + addr_family, + client_id, + anycast_addr, + hostname, + timeout, + use_fqdn, + hostname_flags, + mud_url, + NULL, + "/path/to/dhclient.conf", + orig, + &new_client_id); + g_assert(new != NULL); + + if (!nm_streq(new, expected)) { + g_message("\n* OLD ---------------------------------\n" + "%s" + "\n- NEW -----------------------------------\n" + "%s" + "\n+ EXPECTED ++++++++++++++++++++++++++++++\n" + "%s" + "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", + orig, + new, + expected); + } + g_assert_cmpstr(new, ==, expected); + + if (expected_new_client_id) { + g_assert(new_client_id); + g_assert(g_bytes_equal(new_client_id, expected_new_client_id)); + } else + g_assert(new_client_id == NULL); +} + +/*****************************************************************************/ + +static const char *orig_missing_expected = + "# Created by NetworkManager\n" + "\n\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_orig_missing(void) +{ + test_config(NULL, + orig_missing_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *orig_missing_add_mud_url_expected = + "# Created by NetworkManager\n" + "\n" + "option mudurl code 161 = text;\n" + "send mudurl \"https://example.com/mud.json\";\n\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_orig_missing_add_mud_url(void) +{ + test_config(NULL, + orig_missing_add_mud_url_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + TEST_MUDURL); +} + +/*****************************************************************************/ + +static const char *override_client_id_orig = "send dhcp-client-identifier 00:30:04:20:7A:08;\n"; + +static const char *override_client_id_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send dhcp-client-identifier 11:22:33:44:55:66; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_override_client_id(void) +{ + test_config(override_client_id_orig, + override_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "11:22:33:44:55:66", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *quote_client_id_expected = + "# Created by NetworkManager\n" + "\n" + "send dhcp-client-identifier \"\\x00abcd\"; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_quote_client_id(void) +{ + test_config(NULL, + quote_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "abcd", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *quote_client_id_expected_2 = + "# Created by NetworkManager\n" + "\n" + "send dhcp-client-identifier 00:61:5c:62:63; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_quote_client_id_2(void) +{ + test_config(NULL, + quote_client_id_expected_2, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "a\\bc", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *hex_zero_client_id_expected = + "# Created by NetworkManager\n" + "\n" + "send dhcp-client-identifier 00:11:22:33; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_hex_zero_client_id(void) +{ + test_config(NULL, + hex_zero_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "00:11:22:33", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *ascii_client_id_expected = + "# Created by NetworkManager\n" + "\n" + "send dhcp-client-identifier \"\\x00qb:cd:ef:12:34:56\"; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_ascii_client_id(void) +{ + test_config(NULL, + ascii_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "qb:cd:ef:12:34:56", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *hex_single_client_id_expected = + "# Created by NetworkManager\n" + "\n" + "send dhcp-client-identifier ab:cd:0e:12:34:56; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_hex_single_client_id(void) +{ + test_config(NULL, + hex_single_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + "ab:cd:e:12:34:56", + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *existing_hex_client_id_orig = "send dhcp-client-identifier 10:30:04:20:7A:08;\n"; + +static const char *existing_hex_client_id_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send dhcp-client-identifier 10:30:04:20:7A:08;\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_hex_client_id(void) +{ + gs_unref_bytes GBytes *new_client_id = NULL; + const guint8 bytes[] = {0x10, 0x30, 0x04, 0x20, 0x7A, 0x08}; + + new_client_id = g_bytes_new(bytes, sizeof(bytes)); + test_config(existing_hex_client_id_orig, + existing_hex_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + new_client_id, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *existing_escaped_client_id_orig = + "send dhcp-client-identifier \"\\044test\\xfe\";\n"; + +static const char *existing_escaped_client_id_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send dhcp-client-identifier \"\\044test\\xfe\";\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_escaped_client_id(void) +{ + gs_unref_bytes GBytes *new_client_id = NULL; + + new_client_id = g_bytes_new("$test\xfe", 6); + test_config(existing_escaped_client_id_orig, + existing_escaped_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + new_client_id, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +#define EACID "qb:cd:ef:12:34:56" + +static const char *existing_ascii_client_id_orig = + "send dhcp-client-identifier \"\\x00" EACID "\";\n"; + +static const char *existing_ascii_client_id_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send dhcp-client-identifier \"\\x00" EACID "\";\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_ascii_client_id(void) +{ + gs_unref_bytes GBytes *new_client_id = NULL; + char buf[NM_STRLEN(EACID) + 1] = {0}; + + memcpy(buf + 1, EACID, NM_STRLEN(EACID)); + new_client_id = g_bytes_new(buf, sizeof(buf)); + test_config(existing_ascii_client_id_orig, + existing_ascii_client_id_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + new_client_id, + "eth0", + NULL, + NULL); +} +/*****************************************************************************/ + +static const char *fqdn_expected = + "# Created by NetworkManager\n" + "\n" + "send fqdn.fqdn \"foo.bar.com\"; # added by NetworkManager\n" + "send fqdn.encoded on;\n" + "send fqdn.server-update off;\n" + "send fqdn.no-client-update on;\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n\n"; + +static void +test_fqdn(void) +{ + test_config(NULL, + fqdn_expected, + AF_INET, + "foo.bar.com", + 0, + TRUE, + NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +static const char *fqdn_options_override_orig = + "\n" + "send fqdn.fqdn \"foobar.com\"\n" /* NM must ignore this ... */ + "send fqdn.encoded off;\n" /* ... and honor these */ + "send fqdn.server-update off;\n"; + +static const char *fqdn_options_override_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send fqdn.fqdn \"example2.com\"; # added by NetworkManager\n" + "send fqdn.encoded off;\n" + "send fqdn.server-update on;\n" + "send fqdn.no-client-update off;\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n" + "# FQDN options from /path/to/dhclient.conf\n" + "send fqdn.encoded off;\n" + "send fqdn.server-update off;\n\n"; + +static void +test_fqdn_options_override(void) +{ + test_config(fqdn_options_override_orig, + fqdn_options_override_expected, + AF_INET, + "example2.com", + 0, + NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, + TRUE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *override_hostname_orig = "send host-name \"foobar\";\n"; + +static const char *override_hostname_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send host-name \"blahblah\"; # added by NetworkManager\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_override_hostname(void) +{ + test_config(override_hostname_orig, + override_hostname_expected, + AF_INET, + "blahblah", + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *override_hostname6_orig = "send fqdn.fqdn \"foobar\";\n"; + +static const char *override_hostname6_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n" + "send fqdn.server-update on;\n" + "\n" + "also request dhcp6.name-servers;\n" + "also request dhcp6.domain-search;\n" + "also request dhcp6.client-id;\n" + "\n"; + +static void +test_override_hostname6(void) +{ + test_config(override_hostname6_orig, + override_hostname6_expected, + AF_INET6, + "blahblah.local", + 0, + TRUE, + NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *nonfqdn_hostname6_expected = + "# Created by NetworkManager\n" + "\n" + "send fqdn.fqdn \"blahblah\"; # added by NetworkManager\n" + "send fqdn.no-client-update on;\n" + "\n" + "also request dhcp6.name-servers;\n" + "also request dhcp6.domain-search;\n" + "also request dhcp6.client-id;\n" + "\n"; + +static void +test_nonfqdn_hostname6(void) +{ + /* Non-FQDN hostname can now be used with dhclient */ + test_config(NULL, + nonfqdn_hostname6_expected, + AF_INET6, + "blahblah", + 0, + TRUE, + NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *existing_alsoreq_orig = "also request something;\n" + "also request another-thing;\n"; + +static const char *existing_alsoreq_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request something;\n" + "also request another-thing;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_alsoreq(void) +{ + test_config(existing_alsoreq_orig, + existing_alsoreq_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *existing_req_orig = "request something;\n" + "also request some-other-thing;\n" + "request another-thing;\n" + "also request yet-another-thing;\n"; + +static const char *existing_req_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "request; # override dhclient defaults\n" + "also request another-thing;\n" + "also request yet-another-thing;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_req(void) +{ + test_config(existing_req_orig, + existing_req_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *existing_multiline_alsoreq_orig = + "also request something another-thing yet-another-thing\n" + " foobar baz blah;\n"; + +static const char *existing_multiline_alsoreq_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request something;\n" + "also request another-thing;\n" + "also request yet-another-thing;\n" + "also request foobar;\n" + "also request baz;\n" + "also request blah;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_existing_multiline_alsoreq(void) +{ + test_config(existing_multiline_alsoreq_orig, + existing_multiline_alsoreq_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static void +test_one_duid(const char *escaped, const guint8 *unescaped, guint len) +{ + gs_unref_bytes GBytes *t1 = NULL; + gs_unref_bytes GBytes *t2 = NULL; + gs_free char * w = NULL; + + t1 = nm_dhcp_dhclient_unescape_duid(escaped); + g_assert(t1); + g_assert(nm_utils_gbytes_equal_mem(t1, unescaped, len)); + + t2 = g_bytes_new(unescaped, len); + w = nm_dhcp_dhclient_escape_duid(t2); + g_assert(w); + g_assert_cmpstr(escaped, ==, w); +} + +static void +test_duids(void) +{ + const guint8 test1_u[] = + {0x00, 0x01, 0x00, 0x01, 0x13, 0x6f, 0x13, 0x6e, 0x00, 0x22, 0xfa, 0x8c, 0xd6, 0xc2}; + const char *test1_s = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"; + + const guint8 test2_u[] = + {0x00, 0x01, 0x00, 0x01, 0x17, 0x57, 0xee, 0x39, 0x00, 0x23, 0x15, 0x08, 0x7E, 0xac}; + const char *test2_s = "\\000\\001\\000\\001\\027W\\3569\\000#\\025\\010~\\254"; + + const guint8 test3_u[] = + {0x00, 0x01, 0x00, 0x01, 0x17, 0x58, 0xe8, 0x58, 0x00, 0x23, 0x15, 0x08, 0x7e, 0xac}; + const char *test3_s = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254"; + + const guint8 test4_u[] = + {0x00, 0x01, 0x00, 0x01, 0x15, 0xd5, 0x31, 0x97, 0x00, 0x16, 0xeb, 0x04, 0x45, 0x18}; + const char *test4_s = "\\000\\001\\000\\001\\025\\3251\\227\\000\\026\\353\\004E\\030"; + + const char *bad_s = "\\000\\001\\000\\001\\425\\3251\\227\\000\\026\\353\\004E\\030"; + + test_one_duid(test1_s, test1_u, sizeof(test1_u)); + test_one_duid(test2_s, test2_u, sizeof(test2_u)); + test_one_duid(test3_s, test3_u, sizeof(test3_u)); + test_one_duid(test4_s, test4_u, sizeof(test4_u)); + + /* Invalid octal digit */ + g_assert(nm_dhcp_dhclient_unescape_duid(bad_s) == NULL); +} + +static void +test_read_duid_from_leasefile(void) +{ + const guint8 expected[] = + {0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6, 0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70}; + gs_unref_bytes GBytes *duid = NULL; + GError * error = NULL; + + duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-duid.leases", &error); + nmtst_assert_success(duid, error); + + g_assert(nm_utils_gbytes_equal_mem(duid, expected, G_N_ELEMENTS(expected))); +} + +static void +test_read_commented_duid_from_leasefile(void) +{ + GBytes *duid; + GError *error = NULL; + + duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-commented-duid.leases", &error); + g_assert_no_error(error); + g_assert(duid == NULL); +} + +/*****************************************************************************/ + +static void +_save_duid(const char *path, const guint8 *duid_bin, gsize duid_len) +{ + gs_unref_bytes GBytes *duid = NULL; + GError * error = NULL; + gboolean success; + + g_assert(path); + g_assert(duid_bin); + g_assert(duid_len > 0); + + duid = g_bytes_new(duid_bin, duid_len); + success = nm_dhcp_dhclient_save_duid(path, duid, &error); + nmtst_assert_success(success, error); +} + +static void +test_write_duid(void) +{ + const guint8 duid[] = {000, 001, 000, 001, 027, 'X', 0350, 'X', 0, '#', 025, 010, '~', 0254}; + const char * expected_contents = + "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"; + GError * error = NULL; + gs_free char *contents = NULL; + gboolean success; + const char * path = "test-dhclient-write-duid.leases"; + + _save_duid(path, duid, G_N_ELEMENTS(duid)); + + success = g_file_get_contents(path, &contents, NULL, &error); + nmtst_assert_success(success, error); + + unlink(path); + + g_assert_cmpstr(expected_contents, ==, contents); +} + +static void +test_write_existing_duid(void) +{ + const guint8 duid[] = + {000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302}; + const char *original_contents = + "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"; + const char *expected_contents = + "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n"; + GError * error = NULL; + gs_free char *contents = NULL; + gboolean success; + const char * path = "test-dhclient-write-existing-duid.leases"; + + success = g_file_set_contents(path, original_contents, -1, &error); + nmtst_assert_success(success, error); + + /* Save other DUID; should be overwritten */ + _save_duid(path, duid, G_N_ELEMENTS(duid)); + + /* reread original contents */ + success = g_file_get_contents(path, &contents, NULL, &error); + nmtst_assert_success(success, error); + + unlink(path); + g_assert_cmpstr(expected_contents, ==, contents); +} + +static const guint8 DUID_BIN[] = + {000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302}; +#define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302" + +static void +test_write_existing_commented_duid(void) +{ +#define ORIG_CONTENTS "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n" + const char * expected_contents = "default-duid \"" DUID "\";\n" ORIG_CONTENTS; + GError * error = NULL; + gs_free char *contents = NULL; + gboolean success; + const char * path = "test-dhclient-write-existing-commented-duid.leases"; + + success = g_file_set_contents(path, ORIG_CONTENTS, -1, &error); + nmtst_assert_success(success, error); + + /* Save other DUID; should be saved on top */ + _save_duid(path, DUID_BIN, G_N_ELEMENTS(DUID_BIN)); + + /* reread original contents */ + success = g_file_get_contents(path, &contents, NULL, &error); + nmtst_assert_success(success, error); + + unlink(path); + g_assert_cmpstr(expected_contents, ==, contents); +#undef ORIG_CONTENTS +} + +static void +test_write_existing_multiline_duid(void) +{ +#define ORIG_CONTENTS \ + "### Commented old DUID ###\n" \ + "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n" + const char * expected_contents = "default-duid \"" DUID "\";\n" ORIG_CONTENTS; + GError * error = NULL; + gs_free char * contents = NULL; + gboolean success; + nmtst_auto_unlinkfile char *path = + g_strdup("test-dhclient-write-existing-multiline-duid.leases"); + + success = g_file_set_contents(path, ORIG_CONTENTS, -1, &error); + nmtst_assert_success(success, error); + + _save_duid(path, DUID_BIN, G_N_ELEMENTS(DUID_BIN)); + + success = g_file_get_contents(path, &contents, NULL, &error); + nmtst_assert_success(success, error); + + g_assert_cmpstr(expected_contents, ==, contents); +#undef ORIG_CONTENTS +} + +/*****************************************************************************/ + +static const char *interface1_orig = "interface \"eth0\" {\n" + "\talso request my-option;\n" + "\tinitial-delay 5;\n" + "}\n" + "interface \"eth1\" {\n" + "\talso request another-option;\n" + "\tinitial-delay 0;\n" + "}\n" + "\n" + "also request yet-another-option;\n"; + +static const char *interface1_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "initial-delay 5;\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "also request my-option;\n" + "also request yet-another-option;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_interface1(void) +{ + test_config(interface1_orig, + interface1_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +static const char *interface2_orig = "interface eth0 {\n" + "\talso request my-option;\n" + "\tinitial-delay 5;\n" + " }\n" + "interface eth1 {\n" + "\tinitial-delay 0;\n" + "\trequest another-option;\n" + " } \n" + "\n" + "also request yet-another-option;\n"; + +static const char *interface2_expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "initial-delay 0;\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "request; # override dhclient defaults\n" + "also request another-option;\n" + "also request yet-another-option;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + +static void +test_interface2(void) +{ + test_config(interface2_orig, + interface2_expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth1", + NULL, + NULL); +} + +static void +test_structured(void) +{ + gs_unref_bytes GBytes *new_client_id = NULL; + const guint8 bytes[] = "sad-and-useless"; + + static const char *const orig = + "interface \"eth0\" { \n" + " send host-name \"useless.example.com\";\n" + " hardware ethernet de:ad:80:86:ba:be;\n" + " send dhcp-client-identifier \"sad-and-useless\";\n" + " script \"/bin/useless\";\n" + " send dhcp-lease-time 8086;\n" + " request subnet-mask, broadcast-address, time-offset, routers,\n" + " domain-search, domain-name, host-name;\n" + " require subnet-mask;\n" + "} \n" + "\n" + " interface \"eth1\" { \n" + " send host-name \"sad.example.com\";\n" + " hardware ethernet de:ca:f6:66:ca:fe;\n" + " send dhcp-client-identifier \"useless-and-miserable\";\n" + " script \"/bin/miserable\";\n" + " send dhcp-lease-time 1337;\n" + " request subnet-mask, broadcast-address, time-offset, routers,\n" + " domain-search, domain-name, domain-name-servers, host-name;\n" + " require subnet-mask, domain-name-servers;\n" + " if not option domain-name = \"example.org\" {\n" + " prepend domain-name-servers 127.0.0.1;\n" + " } else {\n" + " prepend domain-name-servers 127.0.0.2;\n" + " } \n" + " } \n" + "\n" + "pseudo \"secondary\" \"eth0\" { \n" + " send dhcp-client-identifier \"sad-useless-and-secondary\";\n" + " script \"/bin/secondary\";\n" + " send host-name \"secondary.useless.example.com\";\n" + " send dhcp-lease-time 666;\n" + " request routers;\n" + " require routers;\n" + " } \n" + "\n" + " pseudo \"tertiary\" \"eth0\" { \n" + " send dhcp-client-identifier \"sad-useless-and-tertiary\";\n" + " script \"/bin/tertiary\";\n" + " send host-name \"tertiary.useless.example.com\";\n" + "} \n" + "\n" + " alias{ \n" + " interface \"eth0\";\n" + " fixed-address 192.0.2.1;\n" + " option subnet-mask 255.255.255.0;\n" + " } \n" + " lease { \n" + " interface \"eth0\";\n" + " fixed-address 192.0.2.2;\n" + " option subnet-mask 255.255.255.0;\n" + " } \n" + "if not option domain-name = \"example.org\" {\n" + " prepend domain-name-servers 127.0.0.1;\n" + " if not option domain-name = \"useless.example.com\" {\n" + " prepend domain-name-servers 127.0.0.2;\n" + " }\n" + "}\n"; + + static const char *const expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "send host-name \"useless.example.com\";\n" + "hardware ethernet de:ad:80:86:ba:be;\n" + "send dhcp-client-identifier \"sad-and-useless\";\n" + "send dhcp-lease-time 8086;\n" + "require subnet-mask;\n" + "if not option domain-name = \"example.org\" {\n" + "prepend domain-name-servers 127.0.0.1;\n" + "if not option domain-name = \"useless.example.com\" {\n" + "prepend domain-name-servers 127.0.0.2;\n" + "}\n" + "}\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "request; # override dhclient defaults\n" + "also request subnet-mask;\n" + "also request broadcast-address;\n" + "also request time-offset;\n" + "also request routers;\n" + "also request domain-search;\n" + "also request domain-name;\n" + "also request host-name;\n" + "also request rfc3442-classless-static-routes;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request ntp-servers;\n" + "also request root-path;\n" + "\n"; + + new_client_id = g_bytes_new(bytes, sizeof(bytes) - 1); + test_config(orig, + expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + new_client_id, + "eth0", + NULL, + NULL); +} + +static void +test_config_req_intf(void) +{ + static const char *const orig = "request subnet-mask, broadcast-address, routers,\n" + "\trfc3442-classless-static-routes,\n" + "\tinterface-mtu, host-name, domain-name, domain-search,\n" + "\tdomain-name-servers, nis-domain, nis-servers,\n" + "\tnds-context, nds-servers, nds-tree-name,\n" + "\tnetbios-name-servers, netbios-dd-server,\n" + "\tnetbios-node-type, netbios-scope, ntp-servers;\n" + ""; + static const char *const expected = + "# Created by NetworkManager\n" + "# Merged from /path/to/dhclient.conf\n" + "\n" + "\n" + "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" + "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" + "option wpad code 252 = string;\n" + "\n" + "request; # override dhclient defaults\n" + "also request subnet-mask;\n" + "also request broadcast-address;\n" + "also request routers;\n" + "also request rfc3442-classless-static-routes;\n" + "also request interface-mtu;\n" + "also request host-name;\n" + "also request domain-name;\n" + "also request domain-search;\n" + "also request domain-name-servers;\n" + "also request nis-domain;\n" + "also request nis-servers;\n" + "also request nds-context;\n" + "also request nds-servers;\n" + "also request nds-tree-name;\n" + "also request netbios-name-servers;\n" + "also request netbios-dd-server;\n" + "also request netbios-node-type;\n" + "also request netbios-scope;\n" + "also request ntp-servers;\n" + "also request ms-classless-static-routes;\n" + "also request static-routes;\n" + "also request wpad;\n" + "also request root-path;\n" + "\n"; + + test_config(orig, + expected, + AF_INET, + NULL, + 0, + FALSE, + NM_DHCP_HOSTNAME_FLAG_NONE, + NULL, + NULL, + "eth0", + NULL, + NULL); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT"); + + g_test_add_func("/dhcp/dhclient/orig_missing", test_orig_missing); + g_test_add_func("/dhcp/dhclient/orig_missing_add_mud_url", test_orig_missing_add_mud_url); + g_test_add_func("/dhcp/dhclient/override_client_id", test_override_client_id); + g_test_add_func("/dhcp/dhclient/quote_client_id/1", test_quote_client_id); + g_test_add_func("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2); + g_test_add_func("/dhcp/dhclient/hex_zero_client_id", test_hex_zero_client_id); + g_test_add_func("/dhcp/dhclient/ascii_client_id", test_ascii_client_id); + g_test_add_func("/dhcp/dhclient/hex_single_client_id", test_hex_single_client_id); + g_test_add_func("/dhcp/dhclient/existing-hex-client-id", test_existing_hex_client_id); + g_test_add_func("/dhcp/dhclient/existing-client-id", test_existing_escaped_client_id); + g_test_add_func("/dhcp/dhclient/existing-ascii-client-id", test_existing_ascii_client_id); + g_test_add_func("/dhcp/dhclient/fqdn", test_fqdn); + g_test_add_func("/dhcp/dhclient/fqdn_options_override", test_fqdn_options_override); + g_test_add_func("/dhcp/dhclient/override_hostname", test_override_hostname); + g_test_add_func("/dhcp/dhclient/override_hostname6", test_override_hostname6); + g_test_add_func("/dhcp/dhclient/nonfqdn_hostname6", test_nonfqdn_hostname6); + g_test_add_func("/dhcp/dhclient/existing_req", test_existing_req); + g_test_add_func("/dhcp/dhclient/existing_alsoreq", test_existing_alsoreq); + g_test_add_func("/dhcp/dhclient/existing_multiline_alsoreq", test_existing_multiline_alsoreq); + g_test_add_func("/dhcp/dhclient/duids", test_duids); + g_test_add_func("/dhcp/dhclient/interface/1", test_interface1); + g_test_add_func("/dhcp/dhclient/interface/2", test_interface2); + g_test_add_func("/dhcp/dhclient/config/req_intf", test_config_req_intf); + g_test_add_func("/dhcp/dhclient/structured", test_structured); + + g_test_add_func("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile); + g_test_add_func("/dhcp/dhclient/read_commented_duid_from_leasefile", + test_read_commented_duid_from_leasefile); + + g_test_add_func("/dhcp/dhclient/write_duid", test_write_duid); + g_test_add_func("/dhcp/dhclient/write_existing_duid", test_write_existing_duid); + g_test_add_func("/dhcp/dhclient/write_existing_commented_duid", + test_write_existing_commented_duid); + g_test_add_func("/dhcp/dhclient/write_existing_multiline_duid", + test_write_existing_multiline_duid); + + return g_test_run(); +} diff --git a/src/core/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c new file mode 100644 index 0000000..dc58688 --- /dev/null +++ b/src/core/dhcp/tests/test-dhcp-utils.c @@ -0,0 +1,781 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-utils.h" + +#include "dhcp/nm-dhcp-utils.h" +#include "platform/nm-platform.h" + +#include "nm-test-utils-core.h" + +static NMIP4Config * +_ip4_config_from_options(int ifindex, const char *iface, GHashTable *options, guint32 route_metric) +{ + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new(); + NMIP4Config * config; + + config = nm_dhcp_utils_ip4_config_from_options(multi_idx, + ifindex, + iface, + options, + RT_TABLE_MAIN, + route_metric); + g_assert(config); + return config; +} + +typedef struct { + const char *name; + const char *value; +} Option; + +static GHashTable * +fill_table(const Option *test_options, GHashTable *table) +{ + const Option *opt; + + if (!table) + table = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL); + for (opt = test_options; opt->name; opt++) + g_hash_table_insert(table, (gpointer) opt->name, (gpointer) opt->value); + return table; +} + +static const Option generic_options[] = { + {"subnet_mask", "255.255.255.0"}, + {"ip_address", "192.168.1.106"}, + {"network_number", "192.168.1.0"}, + {"expiry", "1232324877"}, + {"dhcp_lease_time", "3600"}, + {"dhcp_server_identifier", "192.168.1.1"}, + {"routers", "192.168.1.1"}, + {"domain_name_servers", "216.254.95.2 216.231.41.2"}, + {"dhcp_message_type", "5"}, + {"broadcast_address", "192.168.1.255"}, + {"domain_search", "foobar.com blah.foobar.com"}, + {"host_name", "nmreallywhipsthe"}, + {"domain_name", "lamasass.com"}, + {"interface_mtu", "987"}, + {"static_routes", "10.1.1.5 10.1.1.1 100.99.88.56 10.1.1.1"}, + {NULL, NULL}}; + +static void +test_generic_options(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const NMPlatformIP4Address * address; + const NMPlatformIP4Route * route; + guint32 tmp; + const char * expected_addr = "192.168.1.106"; + const char * expected_gw = "192.168.1.1"; + const char * expected_dns1 = "216.254.95.2"; + const char * expected_dns2 = "216.231.41.2"; + const char * expected_search1 = "foobar.com"; + const char * expected_search2 = "blah.foobar.com"; + const char * expected_route1_dest = "10.1.1.5"; + const char * expected_route1_gw = "10.1.1.1"; + const char * expected_route2_dest = "100.99.88.56"; + const char * expected_route2_gw = "10.1.1.1"; + + options = fill_table(generic_options, NULL); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 address */ + g_assert_cmpint(nm_ip4_config_get_num_addresses(ip4_config), ==, 1); + address = _nmtst_ip4_config_get_address(ip4_config, 0); + g_assert(inet_pton(AF_INET, expected_addr, &tmp) > 0); + g_assert(address->address == tmp); + g_assert(address->peer_address == tmp); + g_assert_cmpint(address->plen, ==, 24); + + /* Gateway */ + g_assert(inet_pton(AF_INET, expected_gw, &tmp) > 0); + g_assert(nmtst_ip4_config_get_gateway(ip4_config) == tmp); + + g_assert_cmpint(nm_ip4_config_get_num_wins(ip4_config), ==, 0); + + g_assert_cmpint(nm_ip4_config_get_mtu(ip4_config), ==, 987); + + /* Domain searches */ + g_assert_cmpint(nm_ip4_config_get_num_searches(ip4_config), ==, 2); + g_assert_cmpstr(nm_ip4_config_get_search(ip4_config, 0), ==, expected_search1); + g_assert_cmpstr(nm_ip4_config_get_search(ip4_config, 1), ==, expected_search2); + + /* DNS servers */ + g_assert_cmpint(nm_ip4_config_get_num_nameservers(ip4_config), ==, 2); + g_assert(inet_pton(AF_INET, expected_dns1, &tmp) > 0); + g_assert(nm_ip4_config_get_nameserver(ip4_config, 0) == tmp); + g_assert(inet_pton(AF_INET, expected_dns2, &tmp) > 0); + g_assert(nm_ip4_config_get_nameserver(ip4_config, 1) == tmp); + + /* Routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + + /* Route #1 */ + route = _nmtst_ip4_config_get_route(ip4_config, 0); + g_assert(inet_pton(AF_INET, expected_route1_dest, &tmp) > 0); + g_assert(route->network == tmp); + g_assert(inet_pton(AF_INET, expected_route1_gw, &tmp) > 0); + g_assert(route->gateway == tmp); + g_assert_cmpint(route->plen, ==, 32); + g_assert_cmpint(route->metric, ==, 0); + + /* Route #2 */ + route = _nmtst_ip4_config_get_route(ip4_config, 1); + g_assert(route->network == nmtst_inet4_from_string(expected_route2_dest)); + g_assert(route->gateway == nmtst_inet4_from_string(expected_route2_gw)); + g_assert_cmpint(route->plen, ==, 32); + g_assert_cmpint(route->metric, ==, 0); + + route = _nmtst_ip4_config_get_route(ip4_config, 2); + g_assert(route->network == nmtst_inet4_from_string("0.0.0.0")); + g_assert(route->gateway == nmtst_inet4_from_string("192.168.1.1")); + g_assert_cmpint(route->plen, ==, 0); + g_assert_cmpint(route->metric, ==, 0); + + g_hash_table_destroy(options); +} + +static void +test_wins_options(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const NMPlatformIP4Address * address; + guint32 tmp; + const char * expected_wins1 = "63.12.199.5"; + const char * expected_wins2 = "150.4.88.120"; + static const Option data[] = {{"netbios_name_servers", "63.12.199.5 150.4.88.120"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 address */ + g_assert_cmpint(nm_ip4_config_get_num_addresses(ip4_config), ==, 1); + address = _nmtst_ip4_config_get_address(ip4_config, 0); + g_assert(address); + g_assert_cmpint(nm_ip4_config_get_num_wins(ip4_config), ==, 2); + g_assert(inet_pton(AF_INET, expected_wins1, &tmp) > 0); + g_assert(nm_ip4_config_get_wins(ip4_config, 0) == tmp); + g_assert(inet_pton(AF_INET, expected_wins2, &tmp) > 0); + g_assert(nm_ip4_config_get_wins(ip4_config, 1) == tmp); + + g_hash_table_destroy(options); +} + +static void +test_vendor_option_metered(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + static const Option data[] = {{"vendor_encapsulated_options", "ANDROID_METERED"}, {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_assert(nm_ip4_config_get_metered(ip4_config) == FALSE); + g_hash_table_destroy(options); + g_clear_object(&ip4_config); + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_assert(nm_ip4_config_get_metered(ip4_config) == TRUE); + g_hash_table_destroy(options); +} + +static void +test_parse_search_list(void) +{ + guint8 *data; + char ** domains; + + data = (guint8[]){0x05, 'l', 'o', 'c', 'a', 'l', 0x00}; + domains = nm_dhcp_parse_search_list(data, 7); + g_assert(domains); + g_assert_cmpint(g_strv_length(domains), ==, 1); + g_assert_cmpstr(domains[0], ==, "local"); + g_strfreev(domains); + + data = (guint8[]){0x04, 't', 'e', 's', 't', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', + 'e', 0x03, 'c', 'o', 'm', 0x00, 0xc0, 0x05, 0x03, 'a', 'b', 'c', + 0xc0, 0x0d, 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x00}; + domains = nm_dhcp_parse_search_list(data, 34); + g_assert(domains); + g_assert_cmpint(g_strv_length(domains), ==, 4); + g_assert_cmpstr(domains[0], ==, "test.example.com"); + g_assert_cmpstr(domains[1], ==, "example.com"); + g_assert_cmpstr(domains[2], ==, "abc.com"); + g_assert_cmpstr(domains[3], ==, "foobar"); + g_strfreev(domains); + + data = (guint8[]){ + 0x40, + 'b', + 'a', + 'd', + }; + domains = nm_dhcp_parse_search_list(data, 4); + g_assert(!domains); + + data = (guint8[]){ + 0x04, + 'o', + 'k', + 'a', + 'y', + 0x00, + 0x40, + 'b', + 'a', + 'd', + }; + domains = nm_dhcp_parse_search_list(data, 10); + g_assert(domains); + g_assert_cmpint(g_strv_length(domains), ==, 1); + g_assert_cmpstr(domains[0], ==, "okay"); + g_strfreev(domains); +} + +static void +ip4_test_route(NMIP4Config *ip4_config, + guint route_num, + const char * expected_dest, + const char * expected_gw, + guint expected_prefix) +{ + const NMPlatformIP4Route *route; + guint32 tmp; + + g_assert(expected_prefix <= 32); + + route = _nmtst_ip4_config_get_route(ip4_config, route_num); + g_assert(inet_pton(AF_INET, expected_dest, &tmp) > 0); + g_assert(route->network == tmp); + g_assert(inet_pton(AF_INET, expected_gw, &tmp) > 0); + g_assert(route->gateway == tmp); + g_assert_cmpint(route->plen, ==, expected_prefix); + g_assert_cmpint(route->metric, ==, 0); +} + +static void +ip4_test_gateway(NMIP4Config *ip4_config, const char *expected_gw) +{ + guint32 tmp; + + g_assert_cmpint(nm_ip4_config_get_num_addresses(ip4_config), ==, 1); + g_assert(inet_pton(AF_INET, expected_gw, &tmp) > 0); + g_assert(nmtst_ip4_config_get_gateway(ip4_config) == tmp); +} + +static void +test_classless_static_routes_1(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + const char * expected_route2_dest = "10.0.0.0"; + const char * expected_route2_gw = "10.17.66.41"; + static const Option data[] = { + /* dhclient custom format */ + {"rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 8); + ip4_test_route(ip4_config, 2, "0.0.0.0", "192.168.1.1", 0); + + g_hash_table_destroy(options); +} + +static void +test_classless_static_routes_2(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + const char * expected_route2_dest = "10.0.0.0"; + const char * expected_route2_gw = "10.17.66.41"; + static const Option data[] = { + /* dhcpcd format */ + {"classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 8); + ip4_test_route(ip4_config, 2, "0.0.0.0", expected_route1_gw, 0); + + g_hash_table_destroy(options); +} + +static void +test_fedora_dhclient_classless_static_routes(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "129.210.177.128"; + const char * expected_route1_gw = "192.168.0.113"; + const char * expected_route2_dest = "2.0.0.0"; + const char * expected_route2_gw = "10.34.255.6"; + const char * expected_gateway = "192.168.0.113"; + static const Option data[] = { + /* Fedora dhclient format */ + {"classless_static_routes", + "0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 25); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 7); + ip4_test_route(ip4_config, 2, "0.0.0.0", expected_route1_gw, 0); + + /* Gateway */ + ip4_test_gateway(ip4_config, expected_gateway); + + g_hash_table_destroy(options); +} + +static void +test_dhclient_invalid_classless_routes_1(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + static const Option data[] = { + /* dhclient format */ + {"rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*ignoring invalid classless static routes*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 2); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, "0.0.0.0", expected_route1_gw, 0); + + g_hash_table_destroy(options); +} + +static void +test_dhcpcd_invalid_classless_routes_1(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "10.1.1.5"; + const char * expected_route1_gw = "10.1.1.1"; + const char * expected_route2_dest = "100.99.88.56"; + const char * expected_route2_gw = "10.1.1.1"; + static const Option data[] = { + /* dhcpcd format */ + {"classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*ignoring invalid classless static routes*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + ip4_test_route(ip4_config, 2, "0.0.0.0", "192.168.1.1", 0); + + g_hash_table_destroy(options); +} + +static void +test_dhclient_invalid_classless_routes_2(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "10.1.1.5"; + const char * expected_route1_gw = "10.1.1.1"; + const char * expected_route2_dest = "100.99.88.56"; + const char * expected_route2_gw = "10.1.1.1"; + static const Option data[] = { + {"rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*ignoring invalid classless static routes*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + ip4_test_route(ip4_config, 2, "0.0.0.0", "192.168.1.1", 0); + + g_hash_table_destroy(options); +} + +static void +test_dhcpcd_invalid_classless_routes_2(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "10.1.1.5"; + const char * expected_route1_gw = "10.1.1.1"; + const char * expected_route2_dest = "100.99.88.56"; + const char * expected_route2_gw = "10.1.1.1"; + static const Option data[] = { + {"classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*ignoring invalid classless static routes*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + + /* Routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 3); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route(ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + ip4_test_route(ip4_config, 2, "0.0.0.0", "192.168.1.1", 0); + + g_hash_table_destroy(options); +} + +static void +test_dhclient_invalid_classless_routes_3(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + static const Option data[] = { + {"rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*ignoring invalid classless static routes*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 2); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, "0.0.0.0", expected_route1_gw, 0); + + g_hash_table_destroy(options); +} + +static void +test_dhcpcd_invalid_classless_routes_3(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + static Option data[] = { + {"classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*DHCP provided invalid classless static route*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 2); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, "0.0.0.0", expected_route1_gw, 0); + + g_hash_table_destroy(options); +} + +static void +test_dhclient_gw_in_classless_routes(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + const char * expected_gateway = "192.2.3.4"; + static Option data[] = { + {"rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 2); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, "0.0.0.0", "192.2.3.4", 0); + + /* Gateway */ + ip4_test_gateway(ip4_config, expected_gateway); + + g_hash_table_destroy(options); +} + +static void +test_dhcpcd_gw_in_classless_routes(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_route1_dest = "192.168.10.0"; + const char * expected_route1_gw = "192.168.1.1"; + const char * expected_gateway = "192.2.3.4"; + static Option data[] = { + {"classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4"}, + {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* IP4 routes */ + g_assert_cmpint(nm_ip4_config_get_num_routes(ip4_config), ==, 2); + ip4_test_route(ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route(ip4_config, 1, "0.0.0.0", "192.2.3.4", 0); + + /* Gateway */ + ip4_test_gateway(ip4_config, expected_gateway); + + g_hash_table_destroy(options); +} + +static void +test_escaped_domain_searches(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const char * expected_search0 = "host1"; + const char * expected_search1 = "host2"; + const char * expected_search2 = "host3"; + static const Option data[] = {{"domain_search", "host1\\032host2\\032host3"}, {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + /* domain searches */ + g_assert_cmpint(nm_ip4_config_get_num_searches(ip4_config), ==, 3); + g_assert_cmpstr(nm_ip4_config_get_search(ip4_config, 0), ==, expected_search0); + g_assert_cmpstr(nm_ip4_config_get_search(ip4_config, 1), ==, expected_search1); + g_assert_cmpstr(nm_ip4_config_get_search(ip4_config, 2), ==, expected_search2); + + g_hash_table_destroy(options); +} + +static void +test_invalid_escaped_domain_searches(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + static const Option data[] = {{"domain_search", "host1\\aahost2\\032host3"}, {NULL, NULL}}; + + options = fill_table(generic_options, NULL); + options = fill_table(data, options); + + NMTST_EXPECT_NM_WARN("*invalid domain search*"); + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + g_test_assert_expected_messages(); + + /* domain searches */ + g_assert_cmpint(nm_ip4_config_get_num_searches(ip4_config), ==, 0); + + g_hash_table_destroy(options); +} + +static void +test_ip4_missing_prefix(const char *ip, guint32 expected_prefix) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const NMPlatformIP4Address * address; + + options = fill_table(generic_options, NULL); + g_hash_table_insert(options, "ip_address", (gpointer) ip); + g_hash_table_remove(options, "subnet_mask"); + + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + g_assert_cmpint(nm_ip4_config_get_num_addresses(ip4_config), ==, 1); + address = _nmtst_ip4_config_get_address(ip4_config, 0); + g_assert(address); + g_assert_cmpint(address->plen, ==, expected_prefix); + + g_hash_table_destroy(options); +} + +static void +test_ip4_missing_prefix_24(void) +{ + test_ip4_missing_prefix("192.168.1.10", 24); +} + +static void +test_ip4_missing_prefix_16(void) +{ + test_ip4_missing_prefix("172.16.54.50", 16); +} + +static void +test_ip4_missing_prefix_8(void) +{ + test_ip4_missing_prefix("10.1.2.3", 8); +} + +static void +test_ip4_prefix_classless(void) +{ + GHashTable * options; + gs_unref_object NMIP4Config *ip4_config = NULL; + const NMPlatformIP4Address * address; + + /* Ensure that the missing-subnet-mask handler doesn't mangle classless + * subnet masks at all. The handler should trigger only if the server + * doesn't send the subnet mask. + */ + + options = fill_table(generic_options, NULL); + g_hash_table_insert(options, "ip_address", "172.16.54.22"); + g_hash_table_insert(options, "subnet_mask", "255.255.252.0"); + + ip4_config = _ip4_config_from_options(1, "eth0", options, 0); + + g_assert_cmpint(nm_ip4_config_get_num_addresses(ip4_config), ==, 1); + address = _nmtst_ip4_config_get_address(ip4_config, 0); + g_assert(address); + g_assert_cmpint(address->plen, ==, 22); + + g_hash_table_destroy(options); +} + +#define COMPARE_ID(src, is_str, expected, expected_len) \ + G_STMT_START \ + { \ + gs_unref_bytes GBytes *b = NULL; \ + const char * p; \ + gsize l; \ + \ + b = nm_dhcp_utils_client_id_string_to_bytes(src); \ + g_assert(b); \ + p = g_bytes_get_data(b, &l); \ + if (is_str) { \ + g_assert_cmpint(l, ==, expected_len + 1); \ + g_assert_cmpint(((const char *) p)[0], ==, 0); \ + g_assert(memcmp(p + 1, expected, expected_len) == 0); \ + } else { \ + g_assert_cmpint(l, ==, expected_len); \ + g_assert(memcmp(p, expected, expected_len) == 0); \ + } \ + } \ + G_STMT_END + +static void +test_client_id_from_string(void) +{ + const char * nothex = "asdfasdfasdfasdfasdfasdfasdf"; + const char * allhex = "00:11:22:33:4:55:66:77:88"; + const guint8 allhex_bin[] = {0x00, 0x11, 0x22, 0x33, 0x04, 0x55, 0x66, 0x77, 0x88}; + const char * somehex = "00:11:22:33:44:55:asdfasdfasdf:99:10"; + const char * nocolons = "0011223344559910"; + const char * endcolon = "00:11:22:33:44:55:"; + + COMPARE_ID(nothex, TRUE, nothex, strlen(nothex)); + COMPARE_ID(allhex, FALSE, allhex_bin, sizeof(allhex_bin)); + COMPARE_ID(somehex, TRUE, somehex, strlen(somehex)); + COMPARE_ID(nocolons, TRUE, nocolons, strlen(nocolons)); + COMPARE_ID(endcolon, TRUE, endcolon, strlen(endcolon)); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT"); + + g_test_add_func("/dhcp/generic-options", test_generic_options); + g_test_add_func("/dhcp/wins-options", test_wins_options); + g_test_add_func("/dhcp/classless-static-routes-1", test_classless_static_routes_1); + g_test_add_func("/dhcp/classless-static-routes-2", test_classless_static_routes_2); + g_test_add_func("/dhcp/fedora-dhclient-classless-static-routes", + test_fedora_dhclient_classless_static_routes); + g_test_add_func("/dhcp/dhclient-invalid-classless-routes-1", + test_dhclient_invalid_classless_routes_1); + g_test_add_func("/dhcp/dhcpcd-invalid-classless-routes-1", + test_dhcpcd_invalid_classless_routes_1); + g_test_add_func("/dhcp/dhclient-invalid-classless-routes-2", + test_dhclient_invalid_classless_routes_2); + g_test_add_func("/dhcp/dhcpcd-invalid-classless-routes-2", + test_dhcpcd_invalid_classless_routes_2); + g_test_add_func("/dhcp/dhclient-invalid-classless-routes-3", + test_dhclient_invalid_classless_routes_3); + g_test_add_func("/dhcp/dhcpcd-invalid-classless-routes-3", + test_dhcpcd_invalid_classless_routes_3); + g_test_add_func("/dhcp/dhclient-gw-in-classless-routes", test_dhclient_gw_in_classless_routes); + g_test_add_func("/dhcp/dhcpcd-gw-in-classless-routes", test_dhcpcd_gw_in_classless_routes); + g_test_add_func("/dhcp/escaped-domain-searches", test_escaped_domain_searches); + g_test_add_func("/dhcp/invalid-escaped-domain-searches", test_invalid_escaped_domain_searches); + g_test_add_func("/dhcp/ip4-missing-prefix-24", test_ip4_missing_prefix_24); + g_test_add_func("/dhcp/ip4-missing-prefix-16", test_ip4_missing_prefix_16); + g_test_add_func("/dhcp/ip4-missing-prefix-8", test_ip4_missing_prefix_8); + g_test_add_func("/dhcp/ip4-prefix-classless", test_ip4_prefix_classless); + g_test_add_func("/dhcp/client-id-from-string", test_client_id_from_string); + g_test_add_func("/dhcp/vendor-option-metered", test_vendor_option_metered); + g_test_add_func("/dhcp/parse-search-list", test_parse_search_list); + + return g_test_run(); +} diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c new file mode 100644 index 0000000..82470e0 --- /dev/null +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -0,0 +1,1203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Dan Williams + */ + +#include "nm-default.h" + +#include "nm-dns-dnsmasq.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" + +#define PIDFILE NMRUNDIR "/dnsmasq.pid" +#define CONFDIR NMCONFDIR "/dnsmasq.d" + +#define DNSMASQ_DBUS_SERVICE "org.freedesktop.NetworkManager.dnsmasq" +#define DNSMASQ_DBUS_PATH "/uk/org/thekelleys/dnsmasq" + +#define RATELIMIT_INTERVAL_MSEC 30000 +#define RATELIMIT_BURST 5 + +#define _NMLOG_DOMAIN LOGD_DNS + +/*****************************************************************************/ + +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "dnsmasq", __VA_ARGS__) + +#define WAIT_MSEC_AFTER_SIGTERM 1000 +G_STATIC_ASSERT(WAIT_MSEC_AFTER_SIGTERM <= NM_SHUTDOWN_TIMEOUT_MS); + +#define WAIT_MSEC_AFTER_SIGKILL 400 +G_STATIC_ASSERT(WAIT_MSEC_AFTER_SIGKILL + 100 <= NM_SHUTDOWN_TIMEOUT_MS_WATCHDOG); + +typedef void (*GlPidSpawnAsyncNotify)(GCancellable *cancellable, + GPid pid, + const int * p_exit_code, + GError * error, + gpointer notify_user_data); + +typedef struct { + NMShutdownWaitObjHandle *shutdown_wait_handle; + guint64 p_start_time; + gint64 started_at; + GPid pid; + bool sigkilled : 1; +} GlPidKillExternalData; + +typedef struct { + const char * dm_binary; + GlPidSpawnAsyncNotify notify; + gpointer notify_user_data; + GCancellable * cancellable; +} GlPidSpawnAsyncData; + +static struct { + GlPidKillExternalData *kill_external_data; + + GlPidSpawnAsyncData *spawn_data; + + NMShutdownWaitObjHandle *terminate_handle; + + GPid pid; + + guint terminate_timeout_id; + + guint watch_id; + + /* whether the external process (with the pid from PIDFILE) was already killed. + * This only happens once, once we do that, we remember to not do it again. + * The reason is that later one, when we want to kill the process it's a + * child process. So, we wait for the exit code. */ + bool kill_external_done : 1; + + bool terminate_sigkill : 1; +} gl_pid; + +/*****************************************************************************/ + +static void _gl_pid_spawn_next_step(void); +static void _gl_pid_spawn_cancelled_cb(GCancellable *cancellable, GlPidSpawnAsyncData *sdata); + +/*****************************************************************************/ + +static gboolean +_gl_pid_unlink_pidfile(gboolean do_unlink) +{ + int errsv; + + if (do_unlink) { + if (unlink(PIDFILE) == 0) + _LOGD("spawn: delete PID file %s", PIDFILE); + else { + errsv = errno; + if (errsv != ENOENT) + _LOGD("spawn: delete PID file %s failed: %s (%d)", + PIDFILE, + nm_strerror_native(errsv), + errsv); + } + } + return TRUE; +} + +static gboolean +_gl_pid_kill_external_timeout_cb(gpointer user_data) +{ + guint64 p_start_time; + char p_state = '\0'; + gint64 now; + + p_start_time = nm_utils_get_start_time_for_pid(gl_pid.kill_external_data->pid, &p_state, NULL); + if (p_start_time == 0 || p_start_time != gl_pid.kill_external_data->p_start_time + || nm_utils_process_state_is_dead(p_state)) { + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s is gone", + gl_pid.kill_external_data->pid, + PIDFILE); + goto process_gone; + } + + now = nm_utils_get_monotonic_timestamp_msec(); + + if (gl_pid.kill_external_data->started_at + WAIT_MSEC_AFTER_SIGTERM < now) { + if (!gl_pid.kill_external_data->sigkilled) { + _LOGD("spawn: send SIGKILL to process %" G_PID_FORMAT " from pidfile %s", + gl_pid.kill_external_data->pid, + PIDFILE); + gl_pid.kill_external_data->sigkilled = TRUE; + kill(gl_pid.kill_external_data->pid, SIGKILL); + } else if (gl_pid.kill_external_data->started_at + WAIT_MSEC_AFTER_SIGTERM + + WAIT_MSEC_AFTER_SIGKILL + < now) { + _LOGW("spawn: process %" G_PID_FORMAT + " from pidfile %s is still here after trying to kill it. Wait no longer", + gl_pid.kill_external_data->pid, + PIDFILE); + goto process_gone; + } + } + + return G_SOURCE_CONTINUE; + +process_gone: + nm_shutdown_wait_obj_unregister(gl_pid.kill_external_data->shutdown_wait_handle); + g_slice_free(GlPidKillExternalData, g_steal_pointer(&gl_pid.kill_external_data)); + + _gl_pid_unlink_pidfile(TRUE); + + _gl_pid_spawn_next_step(); + + return G_SOURCE_REMOVE; +} + +static gboolean +_gl_pid_kill_external(void) +{ + gs_free char *contents = NULL; + gs_free char *cmdline_contents = NULL; + gs_free_error GError *error = NULL; + gint64 pid64; + GPid pid = 0; + guint64 p_start_time = 0; + char proc_path[256]; + gboolean do_kill = FALSE; + char p_state = '\0'; + gboolean do_unlink = TRUE; + int errsv; + + if (gl_pid.kill_external_done) { + if (gl_pid.kill_external_data) { + _LOGD("spawn: waiting for external process %" G_PID_FORMAT " from pidfile %s quit", + gl_pid.kill_external_data->pid, + PIDFILE); + return FALSE; + } + return TRUE; + } + + if (!g_file_get_contents(PIDFILE, &contents, NULL, &error)) { + if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + do_unlink = FALSE; + _LOGD("spawn: failure to read pidfile %s: %s", PIDFILE, error->message); + g_clear_error(&error); + goto handle_kill; + } + + pid64 = _nm_utils_ascii_str_to_int64(contents, 10, 2, G_MAXINT64, -1); + if (pid64 == -1 || (pid = (GPid) pid64) != pid64) { + _LOGD("spawn: pidfile %s does not contain a valid process identifier", PIDFILE); + goto handle_kill; + } + + G_STATIC_ASSERT_EXPR(sizeof(pid) == sizeof(pid_t)); + + p_start_time = nm_utils_get_start_time_for_pid(pid, &p_state, NULL); + if (p_start_time == 0) { + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s seems to no longer exist", + pid, + PIDFILE); + goto handle_kill; + } + + nm_sprintf_buf(proc_path, "/proc/%" G_PID_FORMAT "/cmdline", pid); + if (!g_file_get_contents(proc_path, &cmdline_contents, NULL, NULL)) { + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s seems to no longer exist", + pid, + PIDFILE); + goto handle_kill; + } + + if (!strstr(cmdline_contents, "/dnsmasq")) { + _LOGD("spawn: process %" G_PID_FORMAT + " from pidfile %s seems to no longer to be a dnsmasq process", + pid, + PIDFILE); + goto handle_kill; + } + + do_kill = TRUE; + +handle_kill: + + gl_pid.kill_external_done = TRUE; + + if (!do_kill) + return _gl_pid_unlink_pidfile(do_unlink); + + if (nm_utils_process_state_is_dead(p_state)) { + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s is already a zombie", pid, PIDFILE); + return _gl_pid_unlink_pidfile(do_unlink); + } + + if (kill(pid, SIGTERM) != 0) { + errsv = errno; + if (errsv == ESRCH) + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s no longer exists", + pid, + PIDFILE); + else + _LOGD("spawn: process %" G_PID_FORMAT " from pidfile %s failed with \"%s\" (%d)", + pid, + PIDFILE, + nm_strerror_native(errsv), + errsv); + return _gl_pid_unlink_pidfile(do_unlink); + } + + _LOGD("spawn: waiting for process %" G_PID_FORMAT " from pidfile %s to terminate after SIGTERM", + pid, + PIDFILE); + + gl_pid.kill_external_data = g_slice_new(GlPidKillExternalData); + *gl_pid.kill_external_data = (GlPidKillExternalData){ + .shutdown_wait_handle = nm_shutdown_wait_obj_register_handle_full( + g_strdup_printf("kill-external-dnsmasq-process-%" G_PID_FORMAT, pid), + TRUE), + .started_at = nm_utils_get_monotonic_timestamp_msec(), + .pid = pid, + .p_start_time = p_start_time, + }; + g_timeout_add(50, _gl_pid_kill_external_timeout_cb, NULL); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +_gl_pid_spawn_clear_pid(void) +{ + gboolean was_stopping = !!gl_pid.terminate_handle; + + gl_pid.pid = 0; + gl_pid.terminate_sigkill = FALSE; + nm_clear_g_source(&gl_pid.watch_id); + nm_clear_g_source(&gl_pid.terminate_timeout_id); + nm_clear_pointer(&gl_pid.terminate_handle, nm_shutdown_wait_obj_unregister); + return was_stopping; +} + +static void +_gl_pid_spawn_register_for_termination(void) +{ + if (gl_pid.pid > 0 && !gl_pid.terminate_handle) { + /* Create a shutdown handle as a reminder that the currently running process must be terminated + * first. This also happens to block shutdown... */ + gl_pid.terminate_handle = nm_shutdown_wait_obj_register_handle_full( + g_strdup_printf("kill-dnsmasq-process-%" G_PID_FORMAT, gl_pid.pid), + TRUE); + } +} + +/** + * _gl_pid_spawn_notify: + * @sdata: the notify data. @sdata might be destroyed by the function, + * depending on the other arguments (which indicate whether the + * task is complete). + * @pid: the PID to notify (argument for GlPidSpawnAsyncNotify) + * @p_exit_code: the exit code to notify (argument for GlPidSpawnAsyncNotify) + * @error: error reason to notify (argument for GlPidSpawnAsyncNotify) + * + * The GlPidSpawnAsyncNotify callback passed to _gl_pid_spawn() is used + * for two purposes: + * + * - signal that the dnsmasq process was spawned (or failed to be spawned). + * - signal that the dnsmasq process quit (if it was spawned successfully before). + * + * Depending on the arguments, the callee can see what's the case. + */ +static void +_gl_pid_spawn_notify(GlPidSpawnAsyncData *sdata, GPid pid, const int *p_exit_code, GError *error) +{ + gboolean destroy = TRUE; + + nm_assert(sdata); + + if (error) { + nm_assert(pid == 0); + nm_assert(!p_exit_code); + if (!nm_utils_error_is_cancelled(error)) + _LOGD("spawn: dnsmasq failed: %s", error->message); + } else if (p_exit_code) { + /* the only caller already logged about this condition extensively. */ + nm_assert(pid > 0); + } else { + nm_assert(pid > 0); + _LOGD("spawn: dnsmasq started with pid %" G_PID_FORMAT, pid); + destroy = FALSE; + } + + nm_assert((!!destroy) == (sdata != gl_pid.spawn_data)); + + if (destroy) + g_signal_handlers_disconnect_by_func(sdata->cancellable, _gl_pid_spawn_cancelled_cb, sdata); + + sdata->notify(sdata->cancellable, pid, p_exit_code, error, sdata->notify_user_data); + + if (destroy) { + g_clear_object(&sdata->cancellable); + nm_g_slice_free(sdata); + } +} + +static void +_gl_pid_spawn_cancelled_cb(GCancellable *cancellable, GlPidSpawnAsyncData *sdata) +{ + gs_free_error GError *error = NULL; + + if (sdata == gl_pid.spawn_data) { + gl_pid.spawn_data = NULL; + + /* When the cancellable gets cancelled, we terminate the current dnsmasq instance + * in the background. The only way for keeping dnsmasq running while unregistering + * the callback is by calling _gl_pid_spawn() without a new callback. */ + _gl_pid_spawn_register_for_termination(); + } else + nm_assert_not_reached(); + + if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) + nm_assert_not_reached(); + + _gl_pid_spawn_notify(sdata, 0, NULL, error); + + _gl_pid_spawn_next_step(); +} + +static gboolean +_gl_pid_spawn_terminate_timeout_cb(gpointer user_data) +{ + nm_assert(gl_pid.terminate_timeout_id != 0); + nm_assert(gl_pid.pid > 0); + nm_assert(gl_pid.terminate_handle); + nm_assert(gl_pid.watch_id != 0); + + gl_pid.terminate_timeout_id = 0; + + if (!gl_pid.terminate_sigkill) { + gl_pid.terminate_sigkill = TRUE; + _LOGD("spawn: send SIGKILL signal to dnsmasq process %" G_PID_FORMAT + " as it did not exit yet", + gl_pid.pid); + kill(gl_pid.pid, SIGKILL); + gl_pid.terminate_timeout_id = + g_timeout_add(WAIT_MSEC_AFTER_SIGKILL, _gl_pid_spawn_terminate_timeout_cb, NULL); + } else { + _LOGE("spawn: process %" G_PID_FORMAT " did not exit even after SIGTERM and SIGKILL", + gl_pid.pid); + + /* we don't unregister the watch. Just forget about it. We still want to reap the child eventually. */ + gl_pid.watch_id = 0; + + _gl_pid_spawn_clear_pid(); + _gl_pid_spawn_next_step(); + } + + return G_SOURCE_REMOVE; +} + +static void +_gl_pid_spawn_watch_cb(GPid pid, int status, gpointer user_data) +{ + int err; + gboolean was_stopping; + + nm_assert(pid > 0); + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + if (err) { + char sbuf[100]; + + _LOGW("spawn: dnsmasq process %" G_PID_FORMAT " exited with error: %s", + pid, + nm_utils_dnsmasq_status_to_string(err, sbuf, sizeof(sbuf))); + } else + _LOGD("spawn: dnsmasq process %" G_PID_FORMAT " exited normally", pid); + } else if (WIFSTOPPED(status)) + _LOGW("spawn: dnsmasq process %" G_PID_FORMAT " stopped unexpectedly with signal %d", + pid, + WSTOPSIG(status)); + else if (WIFSIGNALED(status)) + _LOGW("spawn: dnsmasq process %" G_PID_FORMAT " died with signal %d", + pid, + WTERMSIG(status)); + else + _LOGW("spawn: dnsmasq process %" G_PID_FORMAT " died from an unknown cause (status %d)", + pid, + status); + + if (gl_pid.pid != pid) { + /* this can only happen, if we timed out and no longer care about this PID. + * We still kept the watch-id active, to reap the process. Nothing to do. */ + return; + } + + nm_assert(gl_pid.watch_id != 0); + + gl_pid.watch_id = 0; + + _gl_pid_unlink_pidfile(TRUE); + + was_stopping = _gl_pid_spawn_clear_pid(); + + if (gl_pid.spawn_data) { + if (was_stopping) { + /* The current process was scheduled to be terminated. That means the pending + * spawn_data is not for that former instance, but for starting a new one. + * This spawn-request is not yet complete, instead it's just about to start. */ + } else + _gl_pid_spawn_notify(g_steal_pointer(&gl_pid.spawn_data), pid, &status, NULL); + } + + _gl_pid_spawn_next_step(); +} + +/** + * _gl_pid_spawn_next_step: + * + * The state about a running dnsmasq process is tracked in @gl_pid. There are + * various things that can happen + * + * - user calls _gl_pid_spawn() -- which might terminate an existing run first. + * - user might cancel the GCancellable -- which would abort the spawning or + * kill the current instance. + * - the child process might exit. + * + * In all these cases, we call _gl_pid_spawn_next_step() to check what to do next. + */ +static void +_gl_pid_spawn_next_step(void) +{ + gs_free_error GError *error = NULL; + const char * argv[15]; + GPid pid = 0; + guint argv_idx; + + if (!_gl_pid_kill_external()) { + /* we need to wait to kill the instance from the PID file first. */ + return; + } + + if (gl_pid.terminate_handle) { + nm_assert(gl_pid.pid > 0); + + if (gl_pid.terminate_timeout_id == 0) { + _LOGD("spawn: send SIGTERM signal to process %" G_PID_FORMAT, gl_pid.pid); + gl_pid.terminate_timeout_id = + g_timeout_add(WAIT_MSEC_AFTER_SIGTERM, _gl_pid_spawn_terminate_timeout_cb, NULL); + kill(gl_pid.pid, SIGTERM); + } + + /* we can only wait for the process to exit. */ + return; + } + + if (!gl_pid.spawn_data) { + /* we are not requested to spawn another process. */ + nm_assert(gl_pid.pid == 0); + return; + } + + if (gl_pid.pid > 0) { + /* the process we desire is already running. All good. */ + return; + } + + argv_idx = 0; + argv[argv_idx++] = gl_pid.spawn_data->dm_binary; + argv[argv_idx++] = "--no-resolv"; /* Use only commandline */ + argv[argv_idx++] = "--keep-in-foreground"; + argv[argv_idx++] = "--no-hosts"; /* don't use /etc/hosts to resolve */ + argv[argv_idx++] = "--bind-interfaces"; + argv[argv_idx++] = "--pid-file=" PIDFILE; + argv[argv_idx++] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */ + argv[argv_idx++] = "--cache-size=400"; + argv[argv_idx++] = "--clear-on-reload"; /* clear cache when dns server changes */ + argv[argv_idx++] = "--conf-file=/dev/null"; /* avoid loading /etc/dnsmasq.conf */ + argv[argv_idx++] = "--proxy-dnssec"; /* Allow DNSSEC to pass through */ + argv[argv_idx++] = "--enable-dbus=" DNSMASQ_DBUS_SERVICE; + + /* dnsmasq exits if the conf dir is not present */ + if (g_file_test(CONFDIR, G_FILE_TEST_IS_DIR)) + argv[argv_idx++] = "--conf-dir=" CONFDIR; + + argv[argv_idx++] = NULL; + nm_assert(argv_idx <= G_N_ELEMENTS(argv)); + + if (!_LOGD_ENABLED()) + _LOGI("starting %s", gl_pid.spawn_data->dm_binary); + else { + gs_free char *cmdline = NULL; + + _LOGD("spawn: starting dnsmasq: %s", (cmdline = g_strjoinv(" ", (char **) argv))); + } + + if (!g_spawn_async(NULL, + (char **) argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + nm_utils_setpgid, + NULL, + &pid, + &error)) { + _gl_pid_spawn_notify(g_steal_pointer(&gl_pid.spawn_data), 0, NULL, error); + return; + } + + gl_pid.pid = pid; + gl_pid.watch_id = g_child_watch_add(pid, _gl_pid_spawn_watch_cb, NULL); + + _gl_pid_spawn_notify(gl_pid.spawn_data, pid, NULL, NULL); +} + +/** + * _gl_pid_spawn: + * @dm_binary: the binary name for dnsmasq to spawn. We could + * detect it ad-hoc right when needing it. But that would be + * asynchronously, and if dnsmasq is not in $PATH, we want to + * fail right away (synchronously). Hence, @dm_binary is + * an argument. + * @cancellable: abort the operation. This will invoke the callback + * a last time. Also, if the dnsmasq process is currently running, + * it will be terminated in the background. To unregister a notify + * call without killing the dnsmasq process, call _gl_pid_spawn() + * again with all arguments %NULL. + * @notify: the callback when the process is started successfully + * and when the process terminates. + * @notify_user_data: user-data for callback. + * + * If a dnsmasq process is already running (from a previous call of + * _gl_pid_spawn()), that one will be replaced. Meaning, the other notify + * callback will be invoked with NM_UTILS_ERROR/NM_UTILS_ERROR_CANCELLED_DISPOSING. + * If you the @dm_binary argument, the previously running process will + * also be terminated first, before spawning a new instance. + * However, you may also pass all arguments as %NULL. In that case, the + * previous @notify will be completed (and forgotten), but the dnsmasq + * process will be left running in the background. + * + * So, you can: + * + * - call _gl_pid_spawn() with a @dm_binary argument. The previous + * notify() completes with NM_UTILS_ERROR_CANCELLED_DISPOSING and + * the dnsmasq process gets killed. + * - cancel the GCancellable, in this case the notify() completes + * with G_IO_ERROR_CANCELLED and the dnsmasq process gets killed. + * - call _gl_pid_spawn() with all arguments %NULL. In that case + * the previous notify() completes with NM_UTILS_ERROR_CANCELLED_DISPOSING + * but the dnsmasq process keeps running in the background. + * + * The callback is used in two cases. + * - When spawning the process it will be invoked always exactly once. + * In this case the callback might be invoked synchronously or + * asynchronously. + * This either provides a PID or a failure reason. In case of a + * failure, that's the end and the process is not running. + * - if the process could be spawned, the child process with the + * provided PID gets monitored. When the process exits, the callback + * will be invoked again, with a failure reason. This is always done + * asynchronously. + */ +static void +_gl_pid_spawn(const char * dm_binary, + GCancellable * cancellable, + GlPidSpawnAsyncNotify notify, + gpointer notify_user_data) +{ + GlPidSpawnAsyncData *sdata_replace; + + sdata_replace = g_steal_pointer(&gl_pid.spawn_data); + + if (dm_binary) { + nm_assert(notify); + nm_assert(G_IS_CANCELLABLE(cancellable)); + gl_pid.spawn_data = g_slice_new(GlPidSpawnAsyncData); + *gl_pid.spawn_data = (GlPidSpawnAsyncData){ + .dm_binary = dm_binary, + .notify = notify, + .notify_user_data = notify_user_data, + .cancellable = g_object_ref(cancellable), + }; + g_signal_connect(cancellable, + "cancelled", + G_CALLBACK(_gl_pid_spawn_cancelled_cb), + gl_pid.spawn_data); + + /* If dnsmasq is running, we terminate it and start a new instance. + * + * If the user would not provide a new callback, this would mean to fail/abort + * the currently subscribed notification (below). But it would leave the dnsmasq + * instance running in the background. + * This allows the user to say to not care about the current instance + * anymore, but still leave it running. + * + * To kill the dnsmasq process without scheduling a new one, cancel the cancellable + * instead. */ + _gl_pid_spawn_register_for_termination(); + } else { + nm_assert(!notify); + nm_assert(!cancellable); + nm_assert(!notify_user_data); + } + + if (sdata_replace) { + gs_free_error GError *error = NULL; + + /* we don't mark the error as G_IO_ERROR/G_IO_ERROR_CANCELLED. That + * is reserved for cancelling the cancellable. However, the current + * request was obsoleted/replaced by a new one, so we fail it with + * NM_UTILS_ERROR/NM_UTILS_ERROR_CANCELLED_DISPOSING. */ + nm_utils_error_set_cancelled(&error, TRUE, NULL); + _gl_pid_spawn_notify(sdata_replace, 0, NULL, error); + } + + _gl_pid_spawn_next_step(); +} + +/*****************************************************************************/ + +typedef struct { + GDBusConnection *dbus_connection; + + GVariant *set_server_ex_args; + + GCancellable *update_cancellable; + + GCancellable *main_cancellable; + + char *name_owner; + + gint64 burst_start_at; + + GPid process_pid; + + guint name_owner_changed_id; + guint main_timeout_id; + + guint burst_retry_timeout_id; + + guint8 burst_count; + + bool is_stopped : 1; + +} NMDnsDnsmasqPrivate; + +struct _NMDnsDnsmasq { + NMDnsPlugin parent; + NMDnsDnsmasqPrivate _priv; +}; + +struct _NMDnsDnsmasqClass { + NMDnsPluginClass parent; +}; + +G_DEFINE_TYPE(NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN) + +#define NM_DNS_DNSMASQ_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsDnsmasq, NM_IS_DNS_DNSMASQ) + +/*****************************************************************************/ + +#undef _NMLOG +#define _NMLOG(level, ...) __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG_DOMAIN, "dnsmasq", __VA_ARGS__) + +/*****************************************************************************/ + +static gboolean start_dnsmasq(NMDnsDnsmasq *self, gboolean force_start, GError **error); + +/*****************************************************************************/ + +static void +add_dnsmasq_nameserver(NMDnsDnsmasq * self, + GVariantBuilder *servers, + const char * ip, + const char * domain) +{ + g_return_if_fail(ip); + + _LOGD("adding nameserver '%s'%s%s%s", + ip, + NM_PRINT_FMT_QUOTED(domain, " for domain \"", domain, "\"", "")); + + g_variant_builder_open(servers, G_VARIANT_TYPE("as")); + + g_variant_builder_add(servers, "s", ip); + if (domain) + g_variant_builder_add(servers, "s", domain); + + g_variant_builder_close(servers); +} + +#define IP_ADDR_TO_STRING_BUFLEN (NM_UTILS_INET_ADDRSTRLEN + 1 + IFNAMSIZ) + +static const char * +ip_addr_to_string(int addr_family, gconstpointer addr, const char *iface, char *out_buf) +{ + int n_written; + char buf2[NM_UTILS_INET_ADDRSTRLEN]; + const char *separator; + + nm_assert_addr_family(addr_family); + nm_assert(addr); + nm_assert(out_buf); + + if (addr_family == AF_INET) { + nm_utils_inet_ntop(addr_family, addr, buf2); + separator = "@"; + } else { + if (IN6_IS_ADDR_V4MAPPED(addr)) + _nm_utils_inet4_ntop(((const struct in6_addr *) addr)->s6_addr32[3], buf2); + else + _nm_utils_inet6_ntop(addr, buf2); + /* Need to scope link-local addresses with %. Before dnsmasq 2.58, + * only '@' was supported as delimiter. Since 2.58, '@' and '%' are + * supported. Due to a bug, since 2.73 only '%' works properly as "server" + * address. + */ + separator = IN6_IS_ADDR_LINKLOCAL(addr) ? "%" : "@"; + } + + n_written = g_snprintf(out_buf, + IP_ADDR_TO_STRING_BUFLEN, + "%s%s%s", + buf2, + iface ? separator : "", + iface ?: ""); + nm_assert(n_written < IP_ADDR_TO_STRING_BUFLEN); + return out_buf; +} + +static void +add_global_config(NMDnsDnsmasq * self, + GVariantBuilder * dnsmasq_servers, + const NMGlobalDnsConfig *config) +{ + guint i, j; + + g_return_if_fail(config); + + for (i = 0; i < nm_global_dns_config_get_num_domains(config); i++) { + NMGlobalDnsDomain *domain = nm_global_dns_config_get_domain(config, i); + const char *const *servers = nm_global_dns_domain_get_servers(domain); + const char * name = nm_global_dns_domain_get_name(domain); + + g_return_if_fail(name); + + for (j = 0; servers && servers[j]; j++) { + if (!strcmp(name, "*")) + add_dnsmasq_nameserver(self, dnsmasq_servers, servers[j], NULL); + else + add_dnsmasq_nameserver(self, dnsmasq_servers, servers[j], name); + } + } +} + +static void +add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsConfigIPData *ip_data) +{ + NMIPConfig * ip_config = ip_data->ip_config; + gconstpointer addr; + const char * iface, *domain; + char ip_addr_to_string_buf[IP_ADDR_TO_STRING_BUFLEN]; + int addr_family; + guint i, j, num; + + iface = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex); + addr_family = nm_ip_config_get_addr_family(ip_config); + + num = nm_ip_config_get_num_nameservers(ip_config); + for (i = 0; i < num; i++) { + addr = nm_ip_config_get_nameserver(ip_config, i); + ip_addr_to_string(addr_family, addr, iface, ip_addr_to_string_buf); + + if (!ip_data->domains.has_default_route_explicit && ip_data->domains.has_default_route) + add_dnsmasq_nameserver(self, servers, ip_addr_to_string_buf, NULL); + if (ip_data->domains.search) { + for (j = 0; ip_data->domains.search[j]; j++) { + domain = nm_utils_parse_dns_domain(ip_data->domains.search[j], NULL); + add_dnsmasq_nameserver(self, + servers, + ip_addr_to_string_buf, + domain[0] ? domain : NULL); + } + } + if (ip_data->domains.reverse) { + for (j = 0; ip_data->domains.reverse[j]; j++) { + add_dnsmasq_nameserver(self, + servers, + ip_addr_to_string_buf, + ip_data->domains.reverse[j]); + } + } + } +} + +static GVariant * +create_update_args(NMDnsDnsmasq * self, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname) +{ + GVariantBuilder servers; + const NMDnsConfigIPData *ip_data; + + g_variant_builder_init(&servers, G_VARIANT_TYPE("aas")); + + if (global_config) + add_global_config(self, &servers, global_config); + else { + c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) + add_ip_config(self, &servers, ip_data); + } + + return g_variant_new("(aas)", &servers); +} + +/*****************************************************************************/ + +static void +dnsmasq_update_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMDnsDnsmasq *self; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *response = NULL; + + response = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + if (!response) + _LOGW("dnsmasq update failed: %s", error->message); + else + _LOGD("dnsmasq update successful"); +} + +static void +send_dnsmasq_update(NMDnsDnsmasq *self) +{ + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + if (!priv->name_owner || !priv->set_server_ex_args) + return; + + _LOGD("trying to update dnsmasq nameservers"); + + nm_clear_g_cancellable(&priv->update_cancellable); + priv->update_cancellable = g_cancellable_new(); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + DNSMASQ_DBUS_PATH, + DNSMASQ_DBUS_SERVICE, + "SetServersEx", + priv->set_server_ex_args, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 20000, + priv->update_cancellable, + dnsmasq_update_done, + self); +} + +/*****************************************************************************/ + +static void +_main_cleanup(NMDnsDnsmasq *self, gboolean emit_failed) +{ + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + if (!priv->main_cancellable) + return; + + priv->process_pid = 0; + nm_clear_g_free(&priv->name_owner); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_source(&priv->main_timeout_id); + nm_clear_g_cancellable(&priv->update_cancellable); + + /* cancelling the main_cancellable will also cause _gl_pid_spawn*() to terminate the + * process in the background. */ + nm_clear_g_cancellable(&priv->main_cancellable); + + if (!priv->is_stopped && priv->burst_retry_timeout_id == 0) { + start_dnsmasq(self, FALSE, NULL); + send_dnsmasq_update(self); + } +} + +static void +name_owner_changed(NMDnsDnsmasq *self, const char *name_owner) +{ + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + name_owner = nm_str_not_empty(name_owner); + + if (nm_streq0(priv->name_owner, name_owner)) + return; + + g_free(priv->name_owner); + priv->name_owner = g_strdup(name_owner); + + if (!name_owner) { + _LOGT("D-Bus name for dnsmasq disappeared"); + _main_cleanup(self, TRUE); + return; + } + + _LOGT("D-Bus name for dnsmasq got owner %s", name_owner); + nm_clear_g_source(&priv->main_timeout_id); + send_dnsmasq_update(self); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMDnsDnsmasq *self = user_data; + const char * new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(self, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + name_owner_changed(user_data, name_owner); +} + +static gboolean +spawn_timeout_cb(gpointer user_data) +{ + NMDnsDnsmasq *self = user_data; + + _LOGW("timeout waiting for dnsmasq to appear on D-Bus"); + _main_cleanup(self, TRUE); + return G_SOURCE_REMOVE; +} + +static void +spawn_notify(GCancellable *cancellable, + GPid pid, + const int * p_exit_code, + GError * error, + gpointer notify_user_data) +{ + NMDnsDnsmasq * self; + NMDnsDnsmasqPrivate *priv; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = notify_user_data; + priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + if (error || p_exit_code) { + _main_cleanup(self, TRUE); + return; + } + + nm_assert(pid > 0); + priv->process_pid = pid; + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + DNSMASQ_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + DNSMASQ_DBUS_SERVICE, + -1, + priv->main_cancellable, + get_name_owner_cb, + self); +} + +static gboolean +_burst_retry_timeout_cb(gpointer user_data) +{ + NMDnsDnsmasq * self = user_data; + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + priv->burst_retry_timeout_id = 0; + + start_dnsmasq(self, TRUE, NULL); + send_dnsmasq_update(self); + return G_SOURCE_REMOVE; +} + +static gboolean +start_dnsmasq(NMDnsDnsmasq *self, gboolean force_start, GError **error) +{ + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + const char * dm_binary; + gint64 now; + + if (G_LIKELY(priv->main_cancellable)) { + /* The process is already running or about to be started. Nothing to do. */ + return TRUE; + } + + dm_binary = nm_utils_find_helper("dnsmasq", DNSMASQ_PATH, NULL); + if (!dm_binary) { + /* We resolve the binary name before trying to start it asynchronously. + * The reason is, that if dnsmasq is not installed, we want to fail early, + * so that NMDnsManager can fallback to a non-caching implementation. */ + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "could not find dnsmasq binary"); + return FALSE; + } + + if (!priv->dbus_connection) { + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + if (!priv->dbus_connection) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "no D-Bus connection available to talk to dnsmasq"); + return FALSE; + } + } + + now = nm_utils_get_monotonic_timestamp_msec(); + if (force_start || priv->burst_start_at == 0 + || priv->burst_start_at + RATELIMIT_INTERVAL_MSEC <= now) { + priv->burst_start_at = now; + priv->burst_count = 1; + nm_clear_g_source(&priv->burst_retry_timeout_id); + _LOGT("rate-limit: start burst interval of %d seconds %s", + RATELIMIT_INTERVAL_MSEC / 1000, + force_start ? " (force)" : ""); + } else if (priv->burst_count < RATELIMIT_BURST) { + nm_assert(priv->burst_retry_timeout_id == 0); + priv->burst_count++; + _LOGT("rate-limit: %u try within burst interval of %d seconds", + (guint) priv->burst_count, + RATELIMIT_INTERVAL_MSEC / 1000); + } else { + if (priv->burst_retry_timeout_id == 0) { + _LOGW("dnsmasq dies and gets respawned too quickly. Back off. Something is very wrong"); + priv->burst_retry_timeout_id = + g_timeout_add_seconds((2 * RATELIMIT_INTERVAL_MSEC) / 1000, + _burst_retry_timeout_cb, + self); + } else + _LOGT("rate-limit: currently rate-limited from restart"); + return TRUE; + } + + priv->main_timeout_id = g_timeout_add(10000, spawn_timeout_cb, self); + + priv->main_cancellable = g_cancellable_new(); + + _gl_pid_spawn(dm_binary, priv->main_cancellable, spawn_notify, self); + return TRUE; +} + +static gboolean +update(NMDnsPlugin * plugin, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error) +{ + NMDnsDnsmasq * self = NM_DNS_DNSMASQ(plugin); + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + if (!start_dnsmasq(self, TRUE, error)) + return FALSE; + + nm_clear_pointer(&priv->set_server_ex_args, g_variant_unref); + priv->set_server_ex_args = + g_variant_ref_sink(create_update_args(self, global_config, ip_config_lst_head, hostname)); + + send_dnsmasq_update(self); + return TRUE; +} + +/*****************************************************************************/ + +static void +stop(NMDnsPlugin *plugin) +{ + NMDnsDnsmasq * self = NM_DNS_DNSMASQ(plugin); + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + priv->is_stopped = TRUE; + priv->burst_start_at = 0; + nm_clear_g_source(&priv->burst_retry_timeout_id); + + /* Cancelling the cancellable will also terminate the + * process (in the background). */ + _main_cleanup(self, FALSE); +} + +/*****************************************************************************/ + +static void +nm_dns_dnsmasq_init(NMDnsDnsmasq *self) +{} + +NMDnsPlugin * +nm_dns_dnsmasq_new(void) +{ + return g_object_new(NM_TYPE_DNS_DNSMASQ, NULL); +} + +static void +dispose(GObject *object) +{ + NMDnsDnsmasq * self = NM_DNS_DNSMASQ(object); + NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); + + priv->is_stopped = TRUE; + + nm_clear_g_source(&priv->burst_retry_timeout_id); + + _main_cleanup(self, FALSE); + + nm_clear_pointer(&priv->set_server_ex_args, g_variant_unref); + + G_OBJECT_CLASS(nm_dns_dnsmasq_parent_class)->dispose(object); + + g_clear_object(&priv->dbus_connection); +} + +static void +nm_dns_dnsmasq_class_init(NMDnsDnsmasqClass *dns_class) +{ + NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS(dns_class); + GObjectClass * object_class = G_OBJECT_CLASS(dns_class); + + object_class->dispose = dispose; + + plugin_class->plugin_name = "dnsmasq"; + plugin_class->is_caching = TRUE; + plugin_class->stop = stop; + plugin_class->update = update; +} diff --git a/src/core/dns/nm-dns-dnsmasq.h b/src/core/dns/nm-dns-dnsmasq.h new file mode 100644 index 0000000..bd6d4c6 --- /dev/null +++ b/src/core/dns/nm-dns-dnsmasq.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DNS_DNSMASQ_H__ +#define __NETWORKMANAGER_DNS_DNSMASQ_H__ + +#include "nm-dns-plugin.h" + +#define NM_TYPE_DNS_DNSMASQ (nm_dns_dnsmasq_get_type()) +#define NM_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq)) +#define NM_DNS_DNSMASQ_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass)) +#define NM_IS_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_DNSMASQ)) +#define NM_IS_DNS_DNSMASQ_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_DNSMASQ)) +#define NM_DNS_DNSMASQ_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass)) + +typedef struct _NMDnsDnsmasq NMDnsDnsmasq; +typedef struct _NMDnsDnsmasqClass NMDnsDnsmasqClass; + +GType nm_dns_dnsmasq_get_type(void); + +NMDnsPlugin *nm_dns_dnsmasq_new(void); + +#endif /* __NETWORKMANAGER_DNS_DNSMASQ_H__ */ diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c new file mode 100644 index 0000000..92f0ef3 --- /dev/null +++ b/src/core/dns/nm-dns-manager.c @@ -0,0 +1,2676 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2005 Colin Walters + * Copyright (C) 2004 - 2017 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if WITH_LIBPSL + #include +#endif + +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-dns-manager.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "NetworkManagerUtils.h" +#include "nm-config.h" +#include "nm-dbus-object.h" +#include "devices/nm-device.h" +#include "nm-manager.h" + +#include "nm-dns-plugin.h" +#include "nm-dns-dnsmasq.h" +#include "nm-dns-systemd-resolved.h" +#include "nm-dns-unbound.h" + +#define HASH_LEN NM_UTILS_CHECKSUM_LENGTH_SHA1 + +#ifndef RESOLVCONF_PATH + #define RESOLVCONF_PATH "/sbin/resolvconf" + #define HAS_RESOLVCONF 0 +#else + #define HAS_RESOLVCONF 1 +#endif + +#ifndef NETCONFIG_PATH + #define NETCONFIG_PATH "/sbin/netconfig" + #define HAS_NETCONFIG 0 +#else + #define HAS_NETCONFIG 1 +#endif + +/*****************************************************************************/ + +typedef enum { SR_SUCCESS, SR_NOTFOUND, SR_ERROR } SpawnResult; + +typedef struct { + GPtrArray * nameservers; + GPtrArray * searches; + GPtrArray * options; + const char *nis_domain; + GPtrArray * nis_servers; + NMTernary has_trust_ad; +} NMResolvConfData; + +/*****************************************************************************/ + +enum { + CONFIG_CHANGED, + + LAST_SIGNAL +}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMDnsManager, PROP_MODE, PROP_RC_MANAGER, PROP_CONFIGURATION, ); + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GHashTable *configs_dict; + CList configs_lst_head; + + CList ip_configs_lst_head; + GVariant *config_variant; + + NMDnsConfigIPData *best_ip_config_4; + NMDnsConfigIPData *best_ip_config_6; + + bool ip_configs_lst_need_sort : 1; + + bool configs_lst_need_sort : 1; + + bool dns_touched : 1; + bool is_stopped : 1; + + char *hostname; + guint updates_queue; + + guint8 hash[HASH_LEN]; /* SHA1 hash of current DNS config */ + guint8 prev_hash[HASH_LEN]; /* Hash when begin_updates() was called */ + + NMDnsManagerResolvConfManager rc_manager; + char * mode; + NMDnsPlugin * sd_resolve_plugin; + NMDnsPlugin * plugin; + + NMConfig *config; + + struct { + guint64 ts; + guint num_restarts; + guint timer; + } plugin_ratelimit; +} NMDnsManagerPrivate; + +struct _NMDnsManager { + NMDBusObject parent; + NMDnsManagerPrivate _priv; +}; + +struct _NMDnsManagerClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMDnsManager, nm_dns_manager, NM_TYPE_DBUS_OBJECT) + +#define NM_DNS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsManager, NM_IS_DNS_MANAGER) + +NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "dns-mgr" +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + char __prefix[20]; \ + const NMDnsManager *const __self = (self); \ + \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((!__self || __self == singleton_instance) \ + ? "" \ + : nm_sprintf_buf(__prefix, "[%p]", __self)) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data); + +/*****************************************************************************/ + +static gboolean +domain_is_valid(const char *domain, gboolean check_public_suffix) +{ + if (*domain == '\0') + return FALSE; +#if WITH_LIBPSL + if (check_public_suffix && psl_is_public_suffix(psl_builtin(), domain)) + return FALSE; +#endif + return TRUE; +} + +static gboolean +domain_is_routing(const char *domain) +{ + return domain[0] == '~'; +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _rc_manager_to_string, + NMDnsManagerResolvConfManager, + NM_UTILS_LOOKUP_DEFAULT_WARN(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO, "auto"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, "unmanaged"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE, "immutable"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK, "symlink"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE, "file"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF, "resolvconf"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG, "netconfig"), ); + +static NM_UTILS_LOOKUP_STR_DEFINE( + _config_type_to_string, + NMDnsIPConfigType, + NM_UTILS_LOOKUP_DEFAULT_WARN(""), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"), ); + +/*****************************************************************************/ + +static void +_ASSERT_dns_config_data(const NMDnsConfigData *data) +{ + nm_assert(data); + nm_assert(NM_IS_DNS_MANAGER(data->self)); + nm_assert(data->ifindex > 0); +} + +static void +_ASSERT_dns_config_ip_data(const NMDnsConfigIPData *ip_data) +{ + nm_assert(ip_data); + _ASSERT_dns_config_data(ip_data->data); + nm_assert(NM_IS_IP_CONFIG(ip_data->ip_config)); + nm_assert(c_list_contains(&ip_data->data->data_lst_head, &ip_data->data_lst)); + nm_assert(ip_data->data->ifindex == nm_ip_config_get_ifindex(ip_data->ip_config)); +#if NM_MORE_ASSERTS > 5 + { + gboolean has_default = FALSE; + gsize i; + + for (i = 0; ip_data->domains.search && ip_data->domains.search; i++) { + const char *d = ip_data->domains.search[i]; + + d = nm_utils_parse_dns_domain(d, NULL); + nm_assert(d); + if (d[0] == '\0') + has_default = TRUE; + } + nm_assert(has_default == ip_data->domains.has_default_route_explicit); + if (ip_data->domains.has_default_route_explicit) + nm_assert(ip_data->domains.has_default_route_exclusive); + if (ip_data->domains.has_default_route_exclusive) + nm_assert(ip_data->domains.has_default_route); + } +#endif +} + +static NMDnsConfigIPData * +_dns_config_ip_data_new(NMDnsConfigData * data, + NMIPConfig * ip_config, + NMDnsIPConfigType ip_config_type) +{ + NMDnsConfigIPData *ip_data; + + _ASSERT_dns_config_data(data); + nm_assert(NM_IS_IP_CONFIG(ip_config)); + nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED); + + ip_data = g_slice_new(NMDnsConfigIPData); + *ip_data = (NMDnsConfigIPData){ + .data = data, + .ip_config = g_object_ref(ip_config), + .ip_config_type = ip_config_type, + }; + c_list_link_tail(&data->data_lst_head, &ip_data->data_lst); + c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_configs_lst_head, + &ip_data->ip_config_lst); + + g_signal_connect(ip_config, + NM_IS_IP4_CONFIG(ip_config) ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY + : "notify::" NM_IP6_CONFIG_DNS_PRIORITY, + (GCallback) _ip_config_dns_priority_changed, + ip_data); + + _ASSERT_dns_config_ip_data(ip_data); + return ip_data; +} + +static void +_dns_config_ip_data_free(NMDnsConfigIPData *ip_data) +{ + _ASSERT_dns_config_ip_data(ip_data); + + c_list_unlink_stale(&ip_data->data_lst); + c_list_unlink_stale(&ip_data->ip_config_lst); + + g_free(ip_data->domains.search); + g_strfreev(ip_data->domains.reverse); + + g_signal_handlers_disconnect_by_func(ip_data->ip_config, + _ip_config_dns_priority_changed, + ip_data); + + g_object_unref(ip_data->ip_config); + nm_g_slice_free(ip_data); +} + +static NMDnsConfigIPData * +_dns_config_data_find_ip_config(NMDnsConfigData *data, NMIPConfig *ip_config) +{ + NMDnsConfigIPData *ip_data; + + _ASSERT_dns_config_data(data); + + c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) { + _ASSERT_dns_config_ip_data(ip_data); + + if (ip_data->ip_config == ip_config) + return ip_data; + } + return NULL; +} + +static void +_dns_config_data_free(NMDnsConfigData *data) +{ + _ASSERT_dns_config_data(data); + + nm_assert(c_list_is_empty(&data->data_lst_head)); + c_list_unlink_stale(&data->configs_lst); + nm_g_slice_free(data); +} + +static int +_mgr_get_ip_configs_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data) +{ + const NMDnsConfigIPData *a = c_list_entry(a_lst, NMDnsConfigIPData, ip_config_lst); + const NMDnsConfigIPData *b = c_list_entry(b_lst, NMDnsConfigIPData, ip_config_lst); + + /* Configurations with lower priority value first */ + NM_CMP_DIRECT(nm_ip_config_get_dns_priority(a->ip_config), + nm_ip_config_get_dns_priority(b->ip_config)); + + /* Sort according to type (descendingly) */ + NM_CMP_FIELD(b, a, ip_config_type); + + return 0; +} + +static CList * +_mgr_get_ip_configs_lst_head(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(priv->ip_configs_lst_need_sort)) { + priv->ip_configs_lst_need_sort = FALSE; + c_list_sort(&priv->ip_configs_lst_head, _mgr_get_ip_configs_lst_cmp, NULL); + } + + return &priv->ip_configs_lst_head; +} + +static int +_mgr_get_configs_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data) +{ + const NMDnsConfigData *a = c_list_entry(a_lst, NMDnsConfigData, configs_lst); + const NMDnsConfigData *b = c_list_entry(b_lst, NMDnsConfigData, configs_lst); + + NM_CMP_FIELD(b, a, ifindex); + return nm_assert_unreachable_val(0); +} + +_nm_unused static CList * +_mgr_get_configs_lst_head(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(priv->configs_lst_need_sort)) { + priv->configs_lst_need_sort = FALSE; + c_list_sort(&priv->configs_lst_head, _mgr_get_configs_lst_cmp, NULL); + } + + return &priv->configs_lst_head; +} + +/*****************************************************************************/ + +gboolean +nm_dns_manager_has_systemd_resolved(NMDnsManager *self) +{ + NMDnsManagerPrivate * priv; + NMDnsSystemdResolved *plugin = NULL; + + g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); + + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + if (priv->sd_resolve_plugin) { + nm_assert(!NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)); + plugin = NM_DNS_SYSTEMD_RESOLVED(priv->sd_resolve_plugin); + } else if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) + plugin = NM_DNS_SYSTEMD_RESOLVED(priv->plugin); + + return plugin && nm_dns_systemd_resolved_is_running(plugin); +} + +/*****************************************************************************/ + +static void +add_string_item(GPtrArray *array, const char *str, gboolean dup) +{ + int i; + + g_return_if_fail(array != NULL); + g_return_if_fail(str != NULL); + + /* Check for dupes before adding */ + for (i = 0; i < array->len; i++) { + const char *candidate = g_ptr_array_index(array, i); + + if (candidate && !strcmp(candidate, str)) + return; + } + + /* No dupes, add the new item */ + g_ptr_array_add(array, dup ? g_strdup(str) : (gpointer) str); +} + +static void +add_dns_option_item(GPtrArray *array, const char *str) +{ + if (_nm_utils_dns_option_find_idx(array, str) < 0) + g_ptr_array_add(array, g_strdup(str)); +} + +static void +add_dns_domains(GPtrArray * array, + const NMIPConfig *ip_config, + gboolean include_routing, + gboolean dup) +{ + guint num_domains, num_searches, i; + const char *str; + + num_domains = nm_ip_config_get_num_domains(ip_config); + num_searches = nm_ip_config_get_num_searches(ip_config); + + for (i = 0; i < num_searches; i++) { + str = nm_ip_config_get_search(ip_config, i); + if (!include_routing && domain_is_routing(str)) + continue; + if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE)) + continue; + add_string_item(array, str, dup); + } + if (num_domains > 1 || !num_searches) { + for (i = 0; i < num_domains; i++) { + str = nm_ip_config_get_domain(ip_config, i); + if (!include_routing && domain_is_routing(str)) + continue; + if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE)) + continue; + add_string_item(array, str, dup); + } + } +} + +static void +merge_one_ip_config(NMResolvConfData *rc, int ifindex, const NMIPConfig *ip_config) +{ + int addr_family; + char buf[NM_UTILS_INET_ADDRSTRLEN + 50]; + gboolean has_trust_ad; + guint num_nameservers; + guint num; + guint i; + + addr_family = nm_ip_config_get_addr_family(ip_config); + + nm_assert_addr_family(addr_family); + nm_assert(ifindex > 0); + nm_assert(ifindex == nm_ip_config_get_ifindex(ip_config)); + + num_nameservers = nm_ip_config_get_num_nameservers(ip_config); + for (i = 0; i < num_nameservers; i++) { + const NMIPAddr *addr; + + addr = nm_ip_config_get_nameserver(ip_config, i); + if (addr_family == AF_INET) + nm_utils_inet_ntop(addr_family, addr, buf); + else if (IN6_IS_ADDR_V4MAPPED(addr)) + _nm_utils_inet4_ntop(addr->addr6.s6_addr32[3], buf); + else { + _nm_utils_inet6_ntop(&addr->addr6, buf); + if (IN6_IS_ADDR_LINKLOCAL(addr)) { + const char *ifname; + + ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex); + if (ifname) { + g_strlcat(buf, "%", sizeof(buf)); + g_strlcat(buf, ifname, sizeof(buf)); + } + } + } + + add_string_item(rc->nameservers, buf, TRUE); + } + + add_dns_domains(rc->searches, ip_config, FALSE, TRUE); + + has_trust_ad = FALSE; + num = nm_ip_config_get_num_dns_options(ip_config); + for (i = 0; i < num; i++) { + const char *option = nm_ip_config_get_dns_option(ip_config, i); + + if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) { + has_trust_ad = TRUE; + continue; + } + add_dns_option_item(rc->options, nm_ip_config_get_dns_option(ip_config, i)); + } + if (num_nameservers == 0) { + /* If the @ip_config contributes no DNS servers, ignore whether trust-ad is set or unset + * for this @ip_config. */ + } else if (has_trust_ad) { + /* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute). + * Once set to FALSE, it doesn't get reset. */ + if (rc->has_trust_ad == NM_TERNARY_DEFAULT) + rc->has_trust_ad = NM_TERNARY_TRUE; + } else + rc->has_trust_ad = NM_TERNARY_FALSE; + + if (addr_family == AF_INET) { + const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config; + + /* NIS stuff */ + num = nm_ip4_config_get_num_nis_servers(ip4_config); + for (i = 0; i < num; i++) { + add_string_item(rc->nis_servers, + _nm_utils_inet4_ntop(nm_ip4_config_get_nis_server(ip4_config, i), buf), + TRUE); + } + + if (nm_ip4_config_get_nis_domain(ip4_config)) { + /* FIXME: handle multiple domains */ + if (!rc->nis_domain) + rc->nis_domain = nm_ip4_config_get_nis_domain(ip4_config); + } + } +} + +static GPid +run_netconfig(NMDnsManager *self, GError **error, int *stdin_fd) +{ + char * argv[5]; + gs_free char *tmp = NULL; + GPid pid = -1; + + argv[0] = NETCONFIG_PATH; + argv[1] = "modify"; + argv[2] = "--service"; + argv[3] = "NetworkManager"; + argv[4] = NULL; + + _LOGD("spawning '%s'", (tmp = g_strjoinv(" ", argv))); + + if (!g_spawn_async_with_pipes(NULL, + argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &pid, + stdin_fd, + NULL, + NULL, + error)) + return -1; + + return pid; +} + +static void +netconfig_construct_str(NMDnsManager *self, GString *str, const char *key, const char *value) +{ + if (value) { + _LOGD("writing to netconfig: %s='%s'", key, value); + g_string_append_printf(str, "%s='%s'\n", key, value); + } +} + +static void +netconfig_construct_strv(NMDnsManager * self, + GString * str, + const char * key, + const char *const *values) +{ + if (values) { + gs_free char *value = NULL; + + value = g_strjoinv(" ", (char **) values); + netconfig_construct_str(self, str, key, value); + } +} + +static SpawnResult +dispatch_netconfig(NMDnsManager * self, + const char *const *searches, + const char *const *nameservers, + const char * nis_domain, + const char *const *nis_servers, + GError ** error) +{ + GPid pid; + int fd; + int errsv; + int status; + gssize l; + nm_auto_free_gstring GString *str = NULL; + + pid = run_netconfig(self, error, &fd); + if (pid <= 0) + return SR_NOTFOUND; + + str = g_string_new(""); + + /* NM is writing already-merged DNS information to netconfig, so it + * does not apply to a specific network interface. + */ + netconfig_construct_str(self, str, "INTERFACE", "NetworkManager"); + netconfig_construct_strv(self, str, "DNSSEARCH", searches); + netconfig_construct_strv(self, str, "DNSSERVERS", nameservers); + netconfig_construct_str(self, str, "NISDOMAIN", nis_domain); + netconfig_construct_strv(self, str, "NISSERVERS", nis_servers); + +again: + l = write(fd, str->str, str->len); + if (l == -1) { + if (errno == EINTR) + goto again; + } + + nm_close(fd); + + /* FIXME: don't write to netconfig synchronously. */ + + /* Wait until the process exits */ + if (!nm_utils_kill_child_sync(pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Error waiting for netconfig to exit: %s", + nm_strerror_native(errsv)); + return SR_ERROR; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Error calling netconfig: %s %d", + WIFEXITED(status) ? "exited with status" + : (WIFSIGNALED(status) ? "exited with signal" + : "exited with unknown reason"), + WIFEXITED(status) ? WEXITSTATUS(status) + : (WIFSIGNALED(status) ? WTERMSIG(status) : status)); + return SR_ERROR; + } + return SR_SUCCESS; +} + +static char * +create_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + GString *str; + gsize i; + + str = g_string_new_len(NULL, 245); + + g_string_append(str, "# Generated by NetworkManager\n"); + + if (searches && searches[0]) { + gsize search_base_idx; + + g_string_append(str, "search"); + search_base_idx = str->len; + + for (i = 0; searches[i]; i++) { + const char *s = searches[i]; + gsize l = strlen(s); + + if (l == 0 || NM_STRCHAR_ANY(s, ch, NM_IN_SET(ch, ' ', '\t', '\n'))) { + /* there should be no such characters in the search entry. Also, + * because glibc parser would treat them as line/word separator. + * + * Skip the value silently. */ + continue; + } + + if (search_base_idx > 0) { + if (str->len - search_base_idx + 1 + l > 254) { + /* this entry crosses the 256 character boundary. Older glibc versions + * would truncate the entry at this point. + * + * Fill the line with spaces to cross the 256 char boundary and continue + * afterwards. This way, the truncation happens between two search entries. */ + while (str->len - search_base_idx < 257) + g_string_append_c(str, ' '); + search_base_idx = 0; + } + } + + g_string_append_c(str, ' '); + g_string_append_len(str, s, l); + } + g_string_append_c(str, '\n'); + } + + if (nameservers && nameservers[0]) { + for (i = 0; nameservers[i]; i++) { + if (i == 3) { + g_string_append( + str, + "# NOTE: the libc resolver may not support more than 3 nameservers.\n"); + g_string_append(str, "# The nameservers listed below may not be recognized.\n"); + } + g_string_append(str, "nameserver "); + g_string_append(str, nameservers[i]); + g_string_append_c(str, '\n'); + } + } + + if (options && options[0]) { + g_string_append(str, "options"); + for (i = 0; options[i]; i++) { + g_string_append_c(str, ' '); + g_string_append(str, options[i]); + } + g_string_append_c(str, '\n'); + } + + return g_string_free(str, FALSE); +} + +char * +nmtst_dns_create_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + return create_resolv_conf(searches, nameservers, options); +} + +static gboolean +write_resolv_conf_contents(FILE *f, const char *content, GError **error) +{ + int errsv; + + if (fprintf(f, "%s", content) < 0) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not write " _PATH_RESCONF ": %s", + nm_strerror_native(errsv)); + errno = errsv; + return FALSE; + } + + return TRUE; +} + +static gboolean +write_resolv_conf(FILE * f, + const char *const *searches, + const char *const *nameservers, + const char *const *options, + GError ** error) +{ + gs_free char *content = NULL; + + content = create_resolv_conf(searches, nameservers, options); + return write_resolv_conf_contents(f, content, error); +} + +static SpawnResult +dispatch_resolvconf(NMDnsManager *self, + char ** searches, + char ** nameservers, + char ** options, + GError ** error) +{ + gs_free char *cmd = NULL; + FILE * f; + gboolean success = FALSE; + int errsv; + int err; + char * argv[] = {RESOLVCONF_PATH, "-d", "NetworkManager", NULL}; + int status; + + if (!g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + RESOLVCONF_PATH " is not executable"); + return SR_NOTFOUND; + } + + if (!searches && !nameservers) { + _LOGI("Removing DNS information from %s", RESOLVCONF_PATH); + + if (!g_spawn_sync("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, error)) + return SR_ERROR; + + if (status != 0) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "%s returned error code", + RESOLVCONF_PATH); + return SR_ERROR; + } + + return SR_SUCCESS; + } + + _LOGI("Writing DNS information to %s", RESOLVCONF_PATH); + + /* FIXME: don't write to resolvconf synchronously. */ + + cmd = g_strconcat(RESOLVCONF_PATH, " -a ", "NetworkManager", NULL); + if ((f = popen(cmd, "w")) == NULL) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not write to %s: %s", + RESOLVCONF_PATH, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + success = write_resolv_conf(f, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options), + error); + err = pclose(f); + if (err < 0) { + errsv = errno; + g_clear_error(error); + g_set_error(error, + G_IO_ERROR, + g_io_error_from_errno(errsv), + "Failed to close pipe to resolvconf: %d", + errsv); + return SR_ERROR; + } else if (err > 0) { + _LOGW("resolvconf failed with status %d", err); + g_clear_error(error); + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "resolvconf failed with status %d", err); + return SR_ERROR; + } + + return success ? SR_SUCCESS : SR_ERROR; +} + +static const char * +_read_link_cached(const char *path, gboolean *is_cached, char **cached) +{ + nm_assert(is_cached); + nm_assert(cached); + + if (*is_cached) + return *cached; + + nm_assert(!*cached); + *is_cached = TRUE; + return (*cached = g_file_read_link(path, NULL)); +} + +#define MY_RESOLV_CONF NMRUNDIR "/resolv.conf" +#define MY_RESOLV_CONF_TMP MY_RESOLV_CONF ".tmp" +#define RESOLV_CONF_TMP "/etc/.resolv.conf.NetworkManager" + +#define NO_STUB_RESOLV_CONF NMRUNDIR "/no-stub-resolv.conf" + +static void +update_resolv_conf_no_stub(NMDnsManager * self, + const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + gs_free char *content = NULL; + GError * local = NULL; + + content = create_resolv_conf(searches, nameservers, options); + + if (!g_file_set_contents(NO_STUB_RESOLV_CONF, content, -1, &local)) { + _LOGD("update-resolv-no-stub: failure to write file: %s", local->message); + g_error_free(local); + return; + } + + _LOGT("update-resolv-no-stub: '%s' successfully written", NO_STUB_RESOLV_CONF); +} + +static SpawnResult +update_resolv_conf(NMDnsManager * self, + const char *const * searches, + const char *const * nameservers, + const char *const * options, + GError ** error, + NMDnsManagerResolvConfManager rc_manager) +{ + FILE * f; + gboolean success; + gs_free char *content = NULL; + SpawnResult write_file_result = SR_SUCCESS; + int errsv; + gboolean resconf_link_cached = FALSE; + gs_free char *resconf_link = NULL; + + content = create_resolv_conf(searches, nameservers, options); + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE + || (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK + && !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link))) { + gs_free char * rc_path_syml = NULL; + nm_auto_free char *rc_path_real = NULL; + const char * rc_path = _PATH_RESCONF; + GError * local = NULL; + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { + rc_path_real = realpath(_PATH_RESCONF, NULL); + if (rc_path_real) + rc_path = rc_path_real; + else { + /* realpath did not resolve a path-name. That either means, + * _PATH_RESCONF: + * - does not exist + * - is a plain file + * - is a dangling symlink + * + * Handle the case, where it is a dangling symlink... */ + rc_path_syml = nm_utils_read_link_absolute(_PATH_RESCONF, NULL); + if (rc_path_syml) + rc_path = rc_path_syml; + } + } + + /* we first write to /etc/resolv.conf directly. If that fails, + * we still continue to write to runstatedir but remember the + * error. */ + if (!g_file_set_contents(rc_path, content, -1, &local)) { + _LOGT("update-resolv-conf: write to %s failed (rc-manager=%s, %s)", + rc_path, + _rc_manager_to_string(rc_manager), + local->message); + g_propagate_error(error, local); + /* clear @error, so that we don't try reset it. This is the error + * we want to propagate to the caller. */ + error = NULL; + write_file_result = SR_ERROR; + } else { + _LOGT("update-resolv-conf: write to %s succeeded (rc-manager=%s)", + rc_path, + _rc_manager_to_string(rc_manager)); + } + } + + if ((f = fopen(MY_RESOLV_CONF_TMP, "we")) == NULL) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not open %s: %s", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: open temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + success = write_resolv_conf_contents(f, content, error); + if (!success) { + errsv = errno; + _LOGT("update-resolv-conf: write temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + } + + if (fclose(f) < 0) { + if (success) { + errsv = errno; + /* only set an error here if write_resolv_conf() was successful, + * since its error is more important. + */ + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not close %s: %s", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: close temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + } + return SR_ERROR; + } else if (!success) + return SR_ERROR; + + if (rename(MY_RESOLV_CONF_TMP, MY_RESOLV_CONF) < 0) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not replace %s: %s", + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: failed to rename temporary file %s to %s (%s)", + MY_RESOLV_CONF_TMP, + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { + _LOGT("update-resolv-conf: write internal file %s succeeded (rc-manager=%s)", + MY_RESOLV_CONF, + _rc_manager_to_string(rc_manager)); + return write_file_result; + } + + if (rc_manager != NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK + || !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)) { + _LOGT("update-resolv-conf: write internal file %s succeeded", MY_RESOLV_CONF); + return write_file_result; + } + + if (!nm_streq0(_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link), + MY_RESOLV_CONF)) { + _LOGT("update-resolv-conf: write internal file %s succeeded (don't touch symlink %s " + "linking to %s)", + MY_RESOLV_CONF, + _PATH_RESCONF, + _read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)); + return write_file_result; + } + + /* By this point, /etc/resolv.conf exists and is a symlink to our internal + * resolv.conf. We update the symlink so that applications get an inotify + * notification. + */ + if (unlink(RESOLV_CONF_TMP) != 0 && ((errsv = errno) != ENOENT)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not unlink %s: %s", + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but cannot delete temporary file %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (symlink(MY_RESOLV_CONF, RESOLV_CONF_TMP) == -1) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not create symlink %s pointing to %s: %s", + RESOLV_CONF_TMP, + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but failed to symlink %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (rename(RESOLV_CONF_TMP, _PATH_RESCONF) == -1) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not rename %s to %s: %s", + RESOLV_CONF_TMP, + _PATH_RESCONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but failed to rename temporary symlink %s to %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + _PATH_RESCONF, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + _LOGT("update-resolv-conf: write internal file %s succeeded and update symlink %s", + MY_RESOLV_CONF, + _PATH_RESCONF); + return write_file_result; +} + +static void +compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN]) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + NMDnsConfigIPData * ip_data; + + sum = g_checksum_new(G_CHECKSUM_SHA1); + nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1)); + + if (global) + nm_global_dns_config_update_checksum(global, sum); + else { + const CList *head; + + /* FIXME(ip-config-checksum): this relies on the fact that an IP + * configuration without DNS parameters gives a zero checksum. */ + head = _mgr_get_ip_configs_lst_head(self); + c_list_for_each_entry (ip_data, head, ip_config_lst) + nm_ip_config_hash(ip_data->ip_config, sum, TRUE); + } + + nm_utils_checksum_get_digest_len(sum, buffer, HASH_LEN); +} + +static gboolean +merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) +{ + NMGlobalDnsDomain *default_domain; + const char *const *searches; + const char *const *options; + const char *const *servers; + guint i; + + if (!global_conf) + return FALSE; + + searches = nm_global_dns_config_get_searches(global_conf); + if (searches) { + for (i = 0; searches[i]; i++) { + if (domain_is_routing(searches[i])) + continue; + if (!domain_is_valid(searches[i], FALSE)) + continue; + add_string_item(rc->searches, searches[i], TRUE); + } + } + + options = nm_global_dns_config_get_options(global_conf); + if (options) { + for (i = 0; options[i]; i++) + add_string_item(rc->options, options[i], TRUE); + } + + default_domain = nm_global_dns_config_lookup_domain(global_conf, "*"); + nm_assert(default_domain); + + servers = nm_global_dns_domain_get_servers(default_domain); + if (servers) { + for (i = 0; servers[i]; i++) + add_string_item(rc->nameservers, servers[i], TRUE); + } + + return TRUE; +} + +static const char * +get_nameserver_list(const NMIPConfig *config, GString **str) +{ + guint num, i; + char buf[NM_UTILS_INET_ADDRSTRLEN]; + int addr_family; + + if (*str) + g_string_truncate(*str, 0); + else + *str = g_string_sized_new(64); + + addr_family = nm_ip_config_get_addr_family(config); + num = nm_ip_config_get_num_nameservers(config); + for (i = 0; i < num; i++) { + nm_utils_inet_ntop(addr_family, nm_ip_config_get_nameserver(config, i), buf); + if (i > 0) + g_string_append_c(*str, ' '); + g_string_append(*str, buf); + } + + return (*str)->str; +} + +static char ** +_ptrarray_to_strv(GPtrArray *parray) +{ + if (parray->len > 0) + g_ptr_array_add(parray, NULL); + return (char **) g_ptr_array_free(parray, parray->len == 0); +} + +static void +_collect_resolv_conf_data(NMDnsManager * self, + NMGlobalDnsConfig *global_config, + char *** out_searches, + char *** out_options, + char *** out_nameservers, + char *** out_nis_servers, + const char ** out_nis_domain) +{ + NMDnsManagerPrivate *priv; + NMResolvConfData rc = { + .nameservers = g_ptr_array_new(), + .searches = g_ptr_array_new(), + .options = g_ptr_array_new(), + .nis_domain = NULL, + .nis_servers = g_ptr_array_new(), + .has_trust_ad = NM_TERNARY_DEFAULT, + }; + + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + if (global_config) + merge_global_dns_config(&rc, global_config); + else { + nm_auto_free_gstring GString *tmp_gstring = NULL; + int prio, first_prio = 0; + const NMDnsConfigIPData * ip_data; + const CList * head; + gboolean is_first = TRUE; + + head = _mgr_get_ip_configs_lst_head(self); + c_list_for_each_entry (ip_data, head, ip_config_lst) { + gboolean skip = FALSE; + + _ASSERT_dns_config_ip_data(ip_data); + + prio = nm_ip_config_get_dns_priority(ip_data->ip_config); + + if (is_first) { + is_first = FALSE; + first_prio = prio; + } else if (first_prio < 0 && first_prio != prio) + skip = TRUE; + + if (nm_ip_config_get_num_nameservers(ip_data->ip_config)) { + _LOGT( + "config: %8d %-7s v%c %-5d %s: %s", + prio, + _config_type_to_string(ip_data->ip_config_type), + nm_utils_addr_family_to_char(nm_ip_config_get_addr_family(ip_data->ip_config)), + ip_data->data->ifindex, + skip ? "" : "", + get_nameserver_list(ip_data->ip_config, &tmp_gstring)); + } + + if (!skip) + merge_one_ip_config(&rc, ip_data->data->ifindex, ip_data->ip_config); + } + } + + /* If the hostname is a FQDN ("dcbw.example.com"), then add the domain part of it + * ("example.com") to the searches list, to ensure that we can still resolve its + * non-FQ form ("dcbw") too. (Also, if there are no other search domains specified, + * this makes a good default.) However, if the hostname is the top level of a domain + * (eg, "example.com"), then use the hostname itself as the search (since the user is + * unlikely to want "com" as a search domain). + */ + if (priv->hostname) { + const char *hostdomain = strchr(priv->hostname, '.'); + + if (hostdomain && !nm_utils_ipaddr_is_valid(AF_UNSPEC, priv->hostname)) { + hostdomain++; + if (domain_is_valid(hostdomain, TRUE)) + add_string_item(rc.searches, hostdomain, TRUE); + else if (domain_is_valid(priv->hostname, TRUE)) + add_string_item(rc.searches, priv->hostname, TRUE); + } + } + + if (rc.has_trust_ad == NM_TERNARY_TRUE) + g_ptr_array_add(rc.options, g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD)); + + *out_searches = _ptrarray_to_strv(rc.searches); + *out_options = _ptrarray_to_strv(rc.options); + *out_nameservers = _ptrarray_to_strv(rc.nameservers); + *out_nis_servers = _ptrarray_to_strv(rc.nis_servers); + *out_nis_domain = rc.nis_domain; +} + +/*****************************************************************************/ + +static char ** +get_ip_rdns_domains(NMIPConfig *ip_config) +{ + int addr_family = nm_ip_config_get_addr_family(ip_config); + char ** strv; + GPtrArray * domains; + NMDedupMultiIter ipconf_iter; + const NMPlatformIPAddress *address; + const NMPlatformIPRoute * route; + + nm_assert_addr_family(addr_family); + + domains = g_ptr_array_sized_new(5); + + nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) { + nm_utils_get_reverse_dns_domains_ip(addr_family, + address->address_ptr, + address->plen, + domains); + } + + nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &route) { + if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) { + nm_utils_get_reverse_dns_domains_ip(addr_family, + route->network_ptr, + route->plen, + domains); + } + } + + /* Terminating NULL so we can use g_strfreev() to free it */ + g_ptr_array_add(domains, NULL); + + /* Free the array and return NULL if the only element was the ending NULL */ + strv = (char **) g_ptr_array_free(domains, (domains->len == 1)); + + return _nm_utils_strv_cleanup(strv, FALSE, FALSE, TRUE); +} + +static gboolean +_domain_track_get_priority(GHashTable *ht, const char *domain, int *out_priority) +{ + gpointer ptr; + + if (!ht || !g_hash_table_lookup_extended(ht, domain, NULL, &ptr)) { + *out_priority = 0; + return FALSE; + } + *out_priority = GPOINTER_TO_INT(ptr); + return TRUE; +} + +/* Check if the domain is shadowed by a parent domain with more negative priority */ +static gboolean +_domain_track_is_shadowed(GHashTable * ht, + const char * domain, + int priority, + const char **out_parent, + int * out_parent_priority) +{ + char *parent; + int parent_priority; + + if (!ht) + return FALSE; + + nm_assert(!g_hash_table_contains(ht, domain)); + + if (_domain_track_get_priority(ht, "", &parent_priority)) { + nm_assert(parent_priority <= priority); + if (parent_priority < 0 && parent_priority < priority) { + *out_parent = ""; + *out_parent_priority = parent_priority; + return TRUE; + } + } + + parent = strchr(domain, '.'); + while (parent && parent[1]) { + parent++; + if (_domain_track_get_priority(ht, parent, &parent_priority)) { + nm_assert(parent_priority <= priority); + if (parent_priority < 0 && parent_priority < priority) { + *out_parent = parent; + *out_parent_priority = parent_priority; + return TRUE; + } + } + parent = strchr(parent, '.'); + } + + return FALSE; +} + +static void +_mgr_configs_data_construct(NMDnsManager *self) +{ + NMDnsConfigIPData *ip_data; + gs_unref_hashtable GHashTable *ht = NULL; + gs_unref_hashtable GHashTable *wildcard_entries = NULL; + CList * head; + int prev_priority = G_MININT; + + head = _mgr_get_ip_configs_lst_head(self); + +#if NM_MORE_ASSERTS + /* we call _mgr_configs_data_clear() at the end of update. We + * don't expect any domain settings here. */ + c_list_for_each_entry (ip_data, head, ip_config_lst) { + nm_assert(!ip_data->domains.search); + nm_assert(!ip_data->domains.reverse); + nm_assert(!ip_data->domains.has_default_route_explicit); + nm_assert(!ip_data->domains.has_default_route_exclusive); + nm_assert(!ip_data->domains.has_default_route); + } +#endif + + c_list_for_each_entry (ip_data, head, ip_config_lst) { + NMIPConfig *ip_config = ip_data->ip_config; + gboolean add_wildcard = FALSE; + + if (!nm_ip_config_get_num_nameservers(ip_config)) + continue; + if (nm_ip_config_best_default_route_get(ip_config)) + add_wildcard = TRUE; + else { + /* If a VPN has never-default=no but doesn't get a default + * route (this can happen for example when the server + * pushes routes with openconnect), and there are no + * search or routing domains, then the name servers pushed + * by the server would be unused. It is preferable in this + * case to use the VPN DNS server for all queries. */ + if (ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN + && !nm_ip_config_get_never_default(ip_data->ip_config) + && nm_ip_config_get_num_searches(ip_data->ip_config) == 0 + && nm_ip_config_get_num_domains(ip_data->ip_config) == 0) + add_wildcard = TRUE; + } + + if (add_wildcard) { + if (!wildcard_entries) + wildcard_entries = g_hash_table_new(nm_direct_hash, NULL); + g_hash_table_add(wildcard_entries, ip_data); + } + } + + c_list_for_each_entry (ip_data, head, ip_config_lst) { + NMIPConfig * ip_config = ip_data->ip_config; + int priority; + const char **domains; + guint n_searches; + guint n_domains; + guint num_dom1; + guint num_dom2; + guint n_domains_allocated; + guint i; + gboolean has_default_route_maybe = FALSE; + gboolean has_default_route_explicit = FALSE; + gboolean has_default_route_auto = FALSE; + + if (!nm_ip_config_get_num_nameservers(ip_config)) + continue; + + n_searches = nm_ip_config_get_num_searches(ip_config); + n_domains = nm_ip_config_get_num_domains(ip_config); + + priority = nm_ip_config_get_dns_priority(ip_config); + + nm_assert(priority != 0); + nm_assert(prev_priority <= priority); + prev_priority = priority; + + /* Add wildcard lookup domain to connections with the default route. + * If there is no default route, add the wildcard domain to all non-VPN + * connections */ + if (wildcard_entries) { + /* FIXME: this heuristic of which device has a default route does + * not work with policy routing (as used by default with WireGuard). + * We should have a more stable mechanism where an NMIPConfig indicates + * whether it is suitable for certain operations (like having an automatically + * added "~" domain). */ + if (g_hash_table_contains(wildcard_entries, ip_data)) + has_default_route_maybe = TRUE; + } else { + if (ip_data->ip_config_type != NM_DNS_IP_CONFIG_TYPE_VPN) + has_default_route_maybe = TRUE; + } + + n_domains_allocated = (n_searches > 0 ? n_searches : n_domains) + 1u; + domains = g_new(const char *, n_domains_allocated); + + num_dom1 = 0; + + /* searches are preferred over domains */ + if (n_searches > 0) { + for (i = 0; i < n_searches; i++) + domains[num_dom1++] = nm_ip_config_get_search(ip_config, i); + } else { + for (i = 0; i < n_domains; i++) + domains[num_dom1++] = nm_ip_config_get_domain(ip_config, i); + } + + nm_assert(num_dom1 < n_domains_allocated); + + num_dom2 = 0; + for (i = 0; TRUE; i++) { + const char *domain_full; + const char *domain_clean; + const char *parent; + int old_priority; + int parent_priority; + gboolean check_default_route; + + if (i < num_dom1) { + check_default_route = FALSE; + domain_full = domains[i]; + domain_clean = nm_utils_parse_dns_domain(domains[i], NULL); + } else if (i == num_dom1) { + if (!has_default_route_maybe) + continue; + if (has_default_route_explicit) + continue; + check_default_route = TRUE; + domain_full = "~"; + domain_clean = ""; + } else + break; + + /* Remove domains with lower priority */ + if (_domain_track_get_priority(ht, domain_clean, &old_priority)) { + nm_assert(old_priority <= priority); + if (old_priority < priority) { + _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) because it already exists " + "with p=%d", + NM_PRINT_FMT_QUOTED(!check_default_route, + "'", + domain_full, + "'", + ""), + ip_data->data->ifindex, + priority, + old_priority); + continue; + } + } else if (_domain_track_is_shadowed(ht, + domain_clean, + priority, + &parent, + &parent_priority)) { + _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) shadowed by '%s' (p=%d)", + NM_PRINT_FMT_QUOTED(!check_default_route, + "'", + domain_full, + "'", + ""), + ip_data->data->ifindex, + priority, + parent, + parent_priority); + continue; + } + + _LOGT( + "plugin: add domain %s%s%s (i=%d, p=%d)", + NM_PRINT_FMT_QUOTED(!check_default_route, "'", domain_full, "'", ""), + ip_data->data->ifindex, + priority); + + if (!ht) + ht = g_hash_table_new(nm_str_hash, g_str_equal); + g_hash_table_insert(ht, (gpointer) domain_clean, GINT_TO_POINTER(priority)); + + if (check_default_route) + has_default_route_auto = TRUE; + else { + nm_assert(num_dom2 <= num_dom1); + nm_assert(num_dom2 < n_domains_allocated); + domains[num_dom2++] = domain_full; + if (domain_clean[0] == '\0') + has_default_route_explicit = TRUE; + } + } + nm_assert(num_dom2 < n_domains_allocated); + domains[num_dom2] = NULL; + + nm_assert(!ip_data->domains.search); + nm_assert(!ip_data->domains.reverse); + ip_data->domains.search = domains; + ip_data->domains.reverse = get_ip_rdns_domains(ip_config); + ip_data->domains.has_default_route_explicit = has_default_route_explicit; + ip_data->domains.has_default_route_exclusive = + has_default_route_explicit || (priority < 0 && has_default_route_auto); + ip_data->domains.has_default_route = + ip_data->domains.has_default_route_exclusive || has_default_route_auto; + + { + gs_free char *str1 = NULL; + gs_free char *str2 = NULL; + + _LOGT("plugin: settings: ifindex=%d, priority=%d, default-route=%d%s, search=%s, " + "reverse=%s", + ip_data->data->ifindex, + priority, + ip_data->domains.has_default_route, + ip_data->domains.has_default_route_explicit + ? " (explicit)" + : (ip_data->domains.has_default_route_exclusive ? " (exclusive)" : ""), + (str1 = g_strjoinv(",", (char **) ip_data->domains.search)), + (ip_data->domains.reverse ? (str2 = g_strjoinv(",", ip_data->domains.reverse)) + : "")); + } + } +} + +static void +_mgr_configs_data_clear(NMDnsManager *self) +{ + NMDnsConfigIPData *ip_data; + CList * head; + + head = _mgr_get_ip_configs_lst_head(self); + c_list_for_each_entry (ip_data, head, ip_config_lst) { + nm_clear_g_free(&ip_data->domains.search); + nm_clear_pointer(&ip_data->domains.reverse, g_strfreev); + ip_data->domains.has_default_route_explicit = FALSE; + ip_data->domains.has_default_route_exclusive = FALSE; + ip_data->domains.has_default_route = FALSE; + } +} + +/*****************************************************************************/ + +static gboolean +update_dns(NMDnsManager *self, gboolean no_caching, GError **error) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + const char * nis_domain = NULL; + gs_strfreev char ** searches = NULL; + gs_strfreev char ** options = NULL; + gs_strfreev char ** nameservers = NULL; + gs_strfreev char ** nis_servers = NULL; + gboolean caching = FALSE; + gboolean do_update = TRUE; + gboolean resolv_conf_updated = FALSE; + SpawnResult result = SR_SUCCESS; + NMConfigData * data; + NMGlobalDnsConfig * global_config; + gs_free_error GError *local_error = NULL; + GError **const p_local_error = error ? &local_error : NULL; + + nm_assert(!error || !*error); + + if (priv->is_stopped) { + _LOGD("update-dns: not updating resolv.conf (is stopped)"); + return TRUE; + } + + nm_clear_g_source(&priv->plugin_ratelimit.timer); + + if (NM_IN_SET(priv->rc_manager, + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, + NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)) { + do_update = FALSE; + _LOGD("update-dns: not updating resolv.conf"); + } else { + priv->dns_touched = TRUE; + _LOGD("update-dns: updating resolv.conf"); + } + + data = nm_config_get_data(priv->config); + global_config = nm_config_data_get_global_dns_config(data); + + /* Update hash with config we're applying */ + compute_hash(self, global_config, priv->hash); + + _collect_resolv_conf_data(self, + global_config, + &searches, + &options, + &nameservers, + &nis_servers, + &nis_domain); + + if (priv->plugin || priv->sd_resolve_plugin) + _mgr_configs_data_construct(self); + + if (priv->sd_resolve_plugin) { + nm_dns_plugin_update(priv->sd_resolve_plugin, + global_config, + _mgr_get_ip_configs_lst_head(self), + priv->hostname, + NULL); + } + + /* Let any plugins do their thing first */ + if (priv->plugin) { + NMDnsPlugin * plugin = priv->plugin; + const char * plugin_name = nm_dns_plugin_get_name(plugin); + gs_free_error GError *plugin_error = NULL; + + if (nm_dns_plugin_is_caching(plugin)) { + if (no_caching) { + _LOGD("update-dns: plugin %s ignored (caching disabled)", plugin_name); + goto plugin_skip; + } + caching = TRUE; + } + + _LOGD("update-dns: updating plugin %s", plugin_name); + if (!nm_dns_plugin_update(plugin, + global_config, + _mgr_get_ip_configs_lst_head(self), + priv->hostname, + &plugin_error)) { + _LOGW("update-dns: plugin %s update failed: %s", plugin_name, plugin_error->message); + + /* If the plugin failed to update, we shouldn't write out a local + * caching DNS configuration to resolv.conf. + */ + caching = FALSE; + } + +plugin_skip:; + } + + /* Clear the generated search list as it points to + * strings owned by IP configurations and we can't + * guarantee they stay alive. */ + _mgr_configs_data_clear(self); + + update_resolv_conf_no_stub(self, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options)); + + /* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf + * to ensure that the glibc resolver doesn't try to round-robin nameservers, + * but only uses the local caching nameserver. + */ + if (caching) { + const char *lladdr = "127.0.0.1"; + gboolean need_edns0; + gboolean need_trust; + + if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) { + /* systemd-resolved uses a different link-local address */ + lladdr = "127.0.0.53"; + } + + g_strfreev(nameservers); + nameservers = g_new0(char *, 2); + nameservers[0] = g_strdup(lladdr); + + need_edns0 = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_EDNS0) < 0; + need_trust = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) < 0; + + if (need_edns0 || need_trust) { + gsize len; + + len = NM_PTRARRAY_LEN(options); + options = g_realloc(options, sizeof(char *) * (len + 3u)); + if (need_edns0) + options[len++] = g_strdup(NM_SETTING_DNS_OPTION_EDNS0); + if (need_trust) + options[len++] = g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD); + options[len] = NULL; + } + } + + if (do_update) { + switch (priv->rc_manager) { + case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: + result = update_resolv_conf(self, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options), + p_local_error, + priv->rc_manager); + resolv_conf_updated = TRUE; + /* If we have ended with no nameservers avoid updating again resolv.conf + * on stop, as some external changes may be applied to it in the meanwhile */ + if (!nameservers && !options) + priv->dns_touched = FALSE; + break; + case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: + result = dispatch_resolvconf(self, searches, nameservers, options, p_local_error); + break; + case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: + result = dispatch_netconfig(self, + (const char *const *) searches, + (const char *const *) nameservers, + nis_domain, + (const char *const *) nis_servers, + p_local_error); + break; + default: + nm_assert_not_reached(); + } + + if (result == SR_NOTFOUND) { + _LOGD("update-dns: program not available, writing to resolv.conf"); + g_clear_error(&local_error); + result = update_resolv_conf(self, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options), + p_local_error, + NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK); + resolv_conf_updated = TRUE; + } + } + + /* Unless we've already done it, update private resolv.conf in NMRUNDIR + * ignoring any errors */ + if (!resolv_conf_updated) { + update_resolv_conf(self, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options), + NULL, + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED); + } + + /* signal that resolv.conf was changed */ + if (do_update && result == SR_SUCCESS) + g_signal_emit(self, signals[CONFIG_CHANGED], 0); + + nm_clear_pointer(&priv->config_variant, g_variant_unref); + _notify(self, PROP_CONFIGURATION); + + if (result != SR_SUCCESS) { + if (error) + g_propagate_error(error, g_steal_pointer(&local_error)); + return FALSE; + } + + nm_assert(!local_error); + return TRUE; +} + +/*****************************************************************************/ + +static void +_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data) +{ + _ASSERT_dns_config_ip_data(ip_data); + + NM_DNS_MANAGER_GET_PRIVATE(ip_data->data->self)->ip_configs_lst_need_sort = TRUE; +} + +gboolean +nm_dns_manager_set_ip_config(NMDnsManager * self, + NMIPConfig * ip_config, + NMDnsIPConfigType ip_config_type) +{ + NMDnsManagerPrivate *priv; + NMDnsConfigIPData * ip_data; + NMDnsConfigData * data; + int ifindex; + NMDnsConfigIPData ** p_best; + + g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_IP_CONFIG(ip_config), FALSE); + + ifindex = nm_ip_config_get_ifindex(ip_config); + g_return_val_if_fail(ifindex > 0, FALSE); + + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + data = g_hash_table_lookup(priv->configs_dict, &ifindex); + if (!data) + ip_data = NULL; + else + ip_data = _dns_config_data_find_ip_config(data, ip_config); + + if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED) { + if (!ip_data) + return FALSE; + if (priv->best_ip_config_4 == ip_data) + priv->best_ip_config_4 = NULL; + if (priv->best_ip_config_6 == ip_data) + priv->best_ip_config_6 = NULL; + /* deleting a config doesn't invalidate the configs' sort order. */ + _dns_config_ip_data_free(ip_data); + if (c_list_is_empty(&data->data_lst_head)) + g_hash_table_remove(priv->configs_dict, &ifindex); + goto changed; + } + + if (ip_data && ip_data->ip_config_type == ip_config_type) { + /* nothing to do. */ + return FALSE; + } + + if (!data) { + data = g_slice_new(NMDnsConfigData); + *data = (NMDnsConfigData){ + .ifindex = ifindex, + .self = self, + .data_lst_head = C_LIST_INIT(data->data_lst_head), + }; + _ASSERT_dns_config_data(data); + g_hash_table_add(priv->configs_dict, data); + c_list_link_tail(&priv->configs_lst_head, &data->configs_lst); + priv->configs_lst_need_sort = TRUE; + } + + if (!ip_data) + ip_data = _dns_config_ip_data_new(data, ip_config, ip_config_type); + else + ip_data->ip_config_type = ip_config_type; + + priv->ip_configs_lst_need_sort = TRUE; + + p_best = NM_IS_IP4_CONFIG(ip_config) ? &priv->best_ip_config_4 : &priv->best_ip_config_6; + + if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) { + /* Only one best-device per IP version is allowed */ + if (*p_best != ip_data) { + if (*p_best) + (*p_best)->ip_config_type = NM_DNS_IP_CONFIG_TYPE_DEFAULT; + *p_best = ip_data; + } + } else { + if (*p_best == ip_data) + *p_best = NULL; + } + +changed: + if (!priv->updates_queue) { + gs_free_error GError *error = NULL; + + if (!update_dns(self, FALSE, &error)) + _LOGW("could not commit DNS changes: %s", error->message); + } + + return TRUE; +} + +void +nm_dns_manager_set_initial_hostname(NMDnsManager *self, const char *hostname) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + g_free(priv->hostname); + priv->hostname = g_strdup(hostname); +} + +void +nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean skip_update) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + const char * filtered = NULL; + + /* Certain hostnames we don't want to include in resolv.conf 'searches' */ + if (hostname && nm_utils_is_specific_hostname(hostname) && !strstr(hostname, ".in-addr.arpa") + && strchr(hostname, '.')) { + filtered = hostname; + } + + if ((!priv->hostname && !filtered) + || (priv->hostname && filtered && !strcmp(priv->hostname, filtered))) + return; + + g_free(priv->hostname); + priv->hostname = g_strdup(filtered); + + if (skip_update) + return; + + if (!priv->updates_queue) { + gs_free_error GError *error = NULL; + + if (!update_dns(self, FALSE, &error)) + _LOGW("could not commit DNS changes: %s", error->message); + } +} + +void +nm_dns_manager_begin_updates(NMDnsManager *self, const char *func) +{ + NMDnsManagerPrivate *priv; + + g_return_if_fail(self != NULL); + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + /* Save current hash when starting a new batch */ + if (priv->updates_queue == 0) + memcpy(priv->prev_hash, priv->hash, sizeof(priv->hash)); + + priv->updates_queue++; + + _LOGD("(%s): queueing DNS updates (%d)", func, priv->updates_queue); +} + +void +nm_dns_manager_end_updates(NMDnsManager *self, const char *func) +{ + NMDnsManagerPrivate *priv; + gs_free_error GError *error = NULL; + gboolean changed; + guint8 new[HASH_LEN]; + + g_return_if_fail(self != NULL); + + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + g_return_if_fail(priv->updates_queue > 0); + + compute_hash(self, nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)), new); + changed = (memcmp(new, priv->prev_hash, sizeof(new)) != 0) ? TRUE : FALSE; + _LOGD("(%s): DNS configuration %s", func, changed ? "changed" : "did not change"); + + priv->updates_queue--; + if ((priv->updates_queue > 0) || (changed == FALSE)) { + _LOGD("(%s): no DNS changes to commit (%d)", func, priv->updates_queue); + return; + } + + /* Commit all the outstanding changes */ + _LOGD("(%s): committing DNS changes (%d)", func, priv->updates_queue); + if (!update_dns(self, FALSE, &error)) + _LOGW("could not commit DNS changes: %s", error->message); + + memset(priv->prev_hash, 0, sizeof(priv->prev_hash)); +} + +void +nm_dns_manager_stop(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv; + + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + if (priv->is_stopped) + g_return_if_reached(); + + _LOGT("stopping..."); + + /* If we're quitting, leave a valid resolv.conf in place, not one + * pointing to 127.0.0.1 if dnsmasq was active. But if we haven't + * done any DNS updates yet, there's no reason to touch resolv.conf + * on shutdown. + */ + if (priv->dns_touched && priv->plugin && NM_IS_DNS_DNSMASQ(priv->plugin)) { + gs_free_error GError *error = NULL; + + if (!update_dns(self, TRUE, &error)) + _LOGW("could not commit DNS changes on shutdown: %s", error->message); + + priv->dns_touched = FALSE; + } + + priv->is_stopped = TRUE; +} + +/*****************************************************************************/ + +static gboolean +_clear_plugin(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + priv->plugin_ratelimit.ts = 0; + nm_clear_g_source(&priv->plugin_ratelimit.timer); + + if (priv->plugin) { + nm_dns_plugin_stop(priv->plugin); + g_clear_object(&priv->plugin); + return TRUE; + } + return FALSE; +} + +static NMDnsManagerResolvConfManager +_check_resconf_immutable(NMDnsManagerResolvConfManager rc_manager) +{ + struct stat st; + int fd, flags; + bool immutable = FALSE; + + switch (rc_manager) { + case NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE: + nm_assert_not_reached(); + /* fall-through */ + case NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED: + return NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED; + default: + + if (lstat(_PATH_RESCONF, &st) != 0) + return rc_manager; + + if (S_ISLNK(st.st_mode)) { + /* only regular files and directories can have extended file attributes. */ + switch (rc_manager) { + case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: + /* we don't care whether the link-target is immutable. + * If the symlink points to another file, rc-manager=symlink anyway backs off. + * Otherwise, we would only check whether our internal resolv.conf is immutable. */ + return NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK; + case NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE: + nm_assert_not_reached(); + /* fall-through */ + case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO: + break; + } + } + + fd = open(_PATH_RESCONF, O_RDONLY | O_CLOEXEC); + if (fd != -1) { + if (ioctl(fd, FS_IOC_GETFLAGS, &flags) != -1) + immutable = NM_FLAGS_HAS(flags, FS_IMMUTABLE_FL); + nm_close(fd); + } + return immutable ? NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE : rc_manager; + } +} + +static gboolean +_resolvconf_resolved_managed(void) +{ + static const char *const RESOLVED_PATHS[] = { + "../run/systemd/resolve/stub-resolv.conf", + "../run/systemd/resolve/resolv.conf", + "../lib/systemd/resolv.conf", + "../usr/lib/systemd/resolv.conf", + "/run/systemd/resolve/stub-resolv.conf", + "/run/systemd/resolve/resolv.conf", + "/lib/systemd/resolv.conf", + "/usr/lib/systemd/resolv.conf", + }; + struct stat st, st_test; + guint i; + + if (lstat(_PATH_RESCONF, &st) != 0) + return FALSE; + + if (S_ISLNK(st.st_mode)) { + gs_free char * full_path = NULL; + nm_auto_free char *real_path = NULL; + + /* see if resolv.conf is a symlink with a target that is + * exactly like one of the candidates. + * + * This check will work for symlinks, even if the target + * does not exist and realpath() cannot resolve anything. + * + * We want to handle that, because systemd-resolved might not + * have started yet. */ + full_path = g_file_read_link(_PATH_RESCONF, NULL); + if (nm_utils_strv_find_first((char **) RESOLVED_PATHS, + G_N_ELEMENTS(RESOLVED_PATHS), + full_path) + >= 0) + return TRUE; + + /* see if resolv.conf is a symlink that resolves exactly one + * of the candidate paths. + * + * This check will work for symlinks that can be resolved + * to a realpath, but the actual file might not exist. + * + * We want to handle that, because systemd-resolved might not + * have started yet. */ + real_path = realpath(_PATH_RESCONF, NULL); + if (nm_utils_strv_find_first((char **) RESOLVED_PATHS, + G_N_ELEMENTS(RESOLVED_PATHS), + real_path) + >= 0) + return TRUE; + + /* fall-through and resolve the symlink, to check the file + * it points to (below). + * + * This check is the most reliable, but it only works if + * systemd-resolved already started and created the file. */ + if (stat(_PATH_RESCONF, &st) != 0) + return FALSE; + } + + /* see if resolv.conf resolves to one of the candidate + * paths (or whether it is hard-linked). */ + for (i = 0; i < G_N_ELEMENTS(RESOLVED_PATHS); i++) { + const char *p = RESOLVED_PATHS[i]; + + if (p[0] == '/' && stat(p, &st_test) == 0 && st.st_dev == st_test.st_dev + && st.st_ino == st_test.st_ino) + return TRUE; + } + + return FALSE; +} + +static void +init_resolv_conf_mode(NMDnsManager *self, gboolean force_reload_plugin) +{ + NMDnsManagerPrivate * priv = NM_DNS_MANAGER_GET_PRIVATE(self); + NMDnsManagerResolvConfManager rc_manager; + const char * mode; + gboolean systemd_resolved; + gboolean param_changed = FALSE; + gboolean plugin_changed = FALSE; + gboolean systemd_resolved_changed = FALSE; + gboolean rc_manager_was_auto = FALSE; + + mode = nm_config_data_get_dns_mode(nm_config_get_data(priv->config)); + systemd_resolved = nm_config_data_get_systemd_resolved(nm_config_get_data(priv->config)); + + if (nm_streq0(mode, "none")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED; + else { + const char *man; + + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN; + man = nm_config_data_get_rc_manager(nm_config_get_data(priv->config)); + +again: + if (!man) { + /* nop */ + } else if (nm_streq(man, "auto")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO; + else if (NM_IN_STRSET(man, "symlink", "none")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK; + else if (nm_streq(man, "file")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE; + else if (nm_streq(man, "resolvconf")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF; + else if (nm_streq(man, "netconfig")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG; + else if (nm_streq(man, "unmanaged")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED; + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN) { + if (man) { + _LOGW("init: unknown resolv.conf manager \"%s\", fallback to \"%s\"", + man, + "" NM_CONFIG_DEFAULT_MAIN_RC_MANAGER); + } + man = "" NM_CONFIG_DEFAULT_MAIN_RC_MANAGER; + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO; + goto again; + } + } + + rc_manager = _check_resconf_immutable(rc_manager); + + if ((!mode && _resolvconf_resolved_managed()) || nm_streq0(mode, "systemd-resolved")) { + if (force_reload_plugin || !NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) { + _clear_plugin(self); + priv->plugin = nm_dns_systemd_resolved_new(); + plugin_changed = TRUE; + } + mode = "systemd-resolved"; + systemd_resolved = FALSE; + } else if (nm_streq0(mode, "dnsmasq")) { + if (force_reload_plugin || !NM_IS_DNS_DNSMASQ(priv->plugin)) { + _clear_plugin(self); + priv->plugin = nm_dns_dnsmasq_new(); + plugin_changed = TRUE; + } + } else if (nm_streq0(mode, "unbound")) { + if (force_reload_plugin || !NM_IS_DNS_UNBOUND(priv->plugin)) { + _clear_plugin(self); + priv->plugin = nm_dns_unbound_new(); + plugin_changed = TRUE; + } + } else { + if (!NM_IN_STRSET(mode, "none", "default")) { + if (mode) + _LOGW("init: unknown dns mode '%s'", mode); + mode = "default"; + } + if (_clear_plugin(self)) + plugin_changed = TRUE; + } + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO) { + rc_manager_was_auto = TRUE; + if (nm_streq(mode, "systemd-resolved")) + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED; + else if (HAS_RESOLVCONF && g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) { + /* We detect /sbin/resolvconf only at this stage. That means, if you install + * or uninstall openresolv afterwards, you need to reload the DNS settings + * (with SIGHUP or `systemctl reload NetworkManager.service`). + * + * We only accept resolvconf if NetworkManager was built with --with-resolvconf. + * For example, on Fedora the systemd package provides a compat resolvconf + * implementation for systemd-resolved. But using that never makes sense, because + * there we either use full systemd-resolved mode or not. In no case does it + * make sense to call that resolvconf implementation. */ + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF; + } else if (HAS_NETCONFIG && g_file_test(NETCONFIG_PATH, G_FILE_TEST_IS_EXECUTABLE)) { + /* Like for resolvconf, we detect only once. We only autoenable this + * option, if NetworkManager was built with netconfig explicitly enabled. */ + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG; + } else + rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK; + } + + /* The systemd-resolved plugin is special. We typically always want to keep + * systemd-resolved up to date even if the configured plugin is different. */ + if (systemd_resolved) { + if (!priv->sd_resolve_plugin) { + priv->sd_resolve_plugin = nm_dns_systemd_resolved_new(); + systemd_resolved_changed = TRUE; + } + } else if (nm_clear_g_object(&priv->sd_resolve_plugin)) + systemd_resolved_changed = TRUE; + + g_object_freeze_notify(G_OBJECT(self)); + + if (!nm_streq0(priv->mode, mode)) { + g_free(priv->mode); + priv->mode = g_strdup(mode); + param_changed = TRUE; + _notify(self, PROP_MODE); + } + + if (priv->rc_manager != rc_manager) { + priv->rc_manager = rc_manager; + param_changed = TRUE; + _notify(self, PROP_RC_MANAGER); + } + + if (param_changed || plugin_changed || systemd_resolved_changed) { + _LOGI("init: dns=%s%s rc-manager=%s%s%s%s%s", + mode, + (systemd_resolved ? ",systemd-resolved" : ""), + _rc_manager_to_string(rc_manager), + rc_manager_was_auto ? " (auto)" : "", + NM_PRINT_FMT_QUOTED(priv->plugin, + ", plugin=", + nm_dns_plugin_get_name(priv->plugin), + "", + "")); + } + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMDnsManager * self) +{ + if (NM_FLAGS_ANY(changes, + NM_CONFIG_CHANGE_DNS_MODE | NM_CONFIG_CHANGE_RC_MANAGER + | NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_DNS_FULL)) { + /* reload the resolv-conf mode also on SIGHUP (when DNS_MODE didn't change). + * The reason is, that the configuration also depends on whether resolv.conf + * is immutable, thus, without the configuration changing, we always want to + * re-configure the mode. */ + init_resolv_conf_mode( + self, + NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_DNS_FULL)); + } + + if (NM_FLAGS_ANY(changes, + NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_SIGUSR1 + | NM_CONFIG_CHANGE_CAUSE_DNS_RC | NM_CONFIG_CHANGE_CAUSE_DNS_FULL + | NM_CONFIG_CHANGE_DNS_MODE | NM_CONFIG_CHANGE_RC_MANAGER + | NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) { + gs_free_error GError *error = NULL; + + if (!update_dns(self, FALSE, &error)) + _LOGW("could not commit DNS changes: %s", error->message); + } +} + +static GVariant * +_get_global_config_variant(NMGlobalDnsConfig *global) +{ + NMGlobalDnsDomain *domain; + GVariantBuilder builder; + guint i, num; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + num = nm_global_dns_config_get_num_domains(global); + for (i = 0; i < num; i++) { + GVariantBuilder conf_builder; + GVariantBuilder item_builder; + const char * domain_name; + const char *const *servers; + + g_variant_builder_init(&conf_builder, G_VARIANT_TYPE("a{sv}")); + + domain = nm_global_dns_config_get_domain(global, i); + domain_name = nm_global_dns_domain_get_name(domain); + + if (domain_name && !nm_streq0(domain_name, "*")) { + g_variant_builder_init(&item_builder, G_VARIANT_TYPE("as")); + g_variant_builder_add(&item_builder, "s", domain_name); + g_variant_builder_add(&conf_builder, + "{sv}", + "domains", + g_variant_builder_end(&item_builder)); + } + + g_variant_builder_init(&item_builder, G_VARIANT_TYPE("as")); + for (servers = nm_global_dns_domain_get_servers(domain); *servers; servers++) { + g_variant_builder_add(&item_builder, "s", *servers); + } + g_variant_builder_add(&conf_builder, + "{sv}", + "nameservers", + g_variant_builder_end(&item_builder)); + + g_variant_builder_add(&conf_builder, + "{sv}", + "priority", + g_variant_new_int32(NM_DNS_PRIORITY_DEFAULT_NORMAL)); + + g_variant_builder_add(&builder, "a{sv}", &conf_builder); + } + + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} + +static GVariant * +_get_config_variant(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + NMGlobalDnsConfig * global_config; + gs_free char * str = NULL; + GVariantBuilder builder; + NMDnsConfigIPData * ip_data; + const CList * head; + gs_unref_ptrarray GPtrArray *array_domains = NULL; + + if (priv->config_variant) + return priv->config_variant; + + global_config = nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)); + if (global_config) { + priv->config_variant = _get_global_config_variant(global_config); + _LOGT("current configuration: %s", (str = g_variant_print(priv->config_variant, TRUE))); + return priv->config_variant; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + head = _mgr_get_ip_configs_lst_head(self); + c_list_for_each_entry (ip_data, head, ip_config_lst) { + const NMIPConfig *ip_config = ip_data->ip_config; + GVariantBuilder entry_builder; + GVariantBuilder strv_builder; + guint i, num; + const int addr_family = nm_ip_config_get_addr_family(ip_config); + char buf[NM_UTILS_INET_ADDRSTRLEN]; + const NMIPAddr * addr; + const char * ifname; + + num = nm_ip_config_get_num_nameservers(ip_config); + if (!num) + continue; + + g_variant_builder_init(&entry_builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as")); + for (i = 0; i < num; i++) { + addr = nm_ip_config_get_nameserver(ip_config, i); + g_variant_builder_add(&strv_builder, "s", nm_utils_inet_ntop(addr_family, addr, buf)); + } + g_variant_builder_add(&entry_builder, + "{sv}", + "nameservers", + g_variant_builder_end(&strv_builder)); + + num = nm_ip_config_get_num_domains(ip_config); + num += nm_ip_config_get_num_searches(ip_config); + if (num > 0) { + if (!array_domains) + array_domains = g_ptr_array_sized_new(num); + else + g_ptr_array_set_size(array_domains, 0); + + add_dns_domains(array_domains, ip_config, TRUE, FALSE); + if (array_domains->len) { + g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as")); + for (i = 0; i < array_domains->len; i++) { + g_variant_builder_add(&strv_builder, "s", array_domains->pdata[i]); + } + g_variant_builder_add(&entry_builder, + "{sv}", + "domains", + g_variant_builder_end(&strv_builder)); + } + } + + ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex); + if (ifname) { + g_variant_builder_add(&entry_builder, + "{sv}", + "interface", + g_variant_new_string(ifname)); + } + + g_variant_builder_add(&entry_builder, + "{sv}", + "priority", + g_variant_new_int32(nm_ip_config_get_dns_priority(ip_config))); + + g_variant_builder_add( + &entry_builder, + "{sv}", + "vpn", + g_variant_new_boolean(ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN)); + + g_variant_builder_add(&builder, "a{sv}", &entry_builder); + } + + priv->config_variant = g_variant_ref_sink(g_variant_builder_end(&builder)); + _LOGT("current configuration: %s", (str = g_variant_print(priv->config_variant, TRUE))); + + return priv->config_variant; +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDnsManager * self = NM_DNS_MANAGER(object); + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MODE: + g_value_set_string(value, priv->mode); + break; + case PROP_RC_MANAGER: + g_value_set_string(value, _rc_manager_to_string(priv->rc_manager)); + break; + case PROP_CONFIGURATION: + g_value_set_variant(value, _get_config_variant(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_dns_manager_init(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + _LOGT("creating..."); + + c_list_init(&priv->configs_lst_head); + c_list_init(&priv->ip_configs_lst_head); + + priv->config = g_object_ref(nm_config_get()); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMDnsConfigData, ifindex) == 0); + priv->configs_dict = g_hash_table_new_full(nm_pint_hash, + nm_pint_equals, + (GDestroyNotify) _dns_config_data_free, + NULL); + + /* Set the initial hash */ + compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash); + + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed_cb), + self); + init_resolv_conf_mode(self, TRUE); +} + +static void +dispose(GObject *object) +{ + NMDnsManager * self = NM_DNS_MANAGER(object); + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + NMDnsConfigIPData * ip_data, *ip_data_safe; + + _LOGT("disposing"); + + if (!priv->is_stopped) + nm_dns_manager_stop(self); + + if (priv->config) + g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); + + g_clear_object(&priv->sd_resolve_plugin); + _clear_plugin(self); + + priv->best_ip_config_4 = NULL; + priv->best_ip_config_6 = NULL; + + c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_configs_lst_head, ip_config_lst) + _dns_config_ip_data_free(ip_data); + + nm_clear_pointer(&priv->configs_dict, g_hash_table_destroy); + nm_assert(c_list_is_empty(&priv->configs_lst_head)); + + nm_clear_g_source(&priv->plugin_ratelimit.timer); + + g_clear_object(&priv->config); + + G_OBJECT_CLASS(nm_dns_manager_parent_class)->dispose(object); + + nm_clear_pointer(&priv->config_variant, g_variant_unref); +} + +static void +finalize(GObject *object) +{ + NMDnsManager * self = NM_DNS_MANAGER(object); + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + g_free(priv->hostname); + g_free(priv->mode); + + G_OBJECT_CLASS(nm_dns_manager_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_dns_manager = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DNS_MANAGER, + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "s", NM_DNS_MANAGER_MODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RcManager", + "s", + NM_DNS_MANAGER_RC_MANAGER), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Configuration", + "aa{sv}", + NM_DNS_MANAGER_CONFIGURATION), ), ), +}; + +static void +nm_dns_manager_class_init(NMDnsManagerClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH "/DnsManager"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_dns_manager); + dbus_object_class->export_on_construction = TRUE; + + obj_properties[PROP_MODE] = g_param_spec_string(NM_DNS_MANAGER_MODE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_RC_MANAGER] = + g_param_spec_string(NM_DNS_MANAGER_RC_MANAGER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONFIGURATION] = + g_param_spec_variant(NM_DNS_MANAGER_CONFIGURATION, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[CONFIG_CHANGED] = g_signal_new(NM_DNS_MANAGER_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h new file mode 100644 index 0000000..937ba62 --- /dev/null +++ b/src/core/dns/nm-dns-manager.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2005 Colin Walters + * Copyright (C) 2004 - 2013 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_DNS_MANAGER_H__ +#define __NETWORKMANAGER_DNS_MANAGER_H__ + +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-setting-connection.h" + +typedef enum { + NM_DNS_IP_CONFIG_TYPE_REMOVED = -1, + + NM_DNS_IP_CONFIG_TYPE_DEFAULT = 0, + NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, + NM_DNS_IP_CONFIG_TYPE_VPN, +} NMDnsIPConfigType; + +enum { + NM_DNS_PRIORITY_DEFAULT_NORMAL = 100, + NM_DNS_PRIORITY_DEFAULT_VPN = 50, +}; + +/*****************************************************************************/ + +struct _NMDnsConfigData; +struct _NMDnsManager; + +typedef struct { + struct _NMDnsConfigData *data; + NMIPConfig * ip_config; + CList data_lst; + CList ip_config_lst; + NMDnsIPConfigType ip_config_type; + struct { + const char **search; + char ** reverse; + + /* Whether "search" explicitly contains a default route "~" + * or "". It is redundant information, but for faster lookup. */ + bool has_default_route_explicit : 1; + + /* Whether an explicit "~" search domain should be added. + * For systemd-resolved, this configured an explicit wildcard + * search domain, and should be used for profiles with negative + * DNS priority. + * + * If "has_default_route_explicit", this is always TRUE and implied. + * + * With systemd-resolved, if TRUE we will set a "." search domain. + */ + bool has_default_route_exclusive : 1; + + /* Whether the device should be used for any domains "~". + * + * If "has_default_route_exclusive", this is always TRUE and implied. + * + * With systemd-resolved, this is the value for SetLinkDefaultRoute(). */ + bool has_default_route : 1; + } domains; +} NMDnsConfigIPData; + +typedef struct _NMDnsConfigData { + int ifindex; + struct _NMDnsManager *self; + CList data_lst_head; + CList configs_lst; +} NMDnsConfigData; + +/*****************************************************************************/ + +#define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type()) +#define NM_DNS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_DNS_MANAGER, NMDnsManager)) +#define NM_DNS_MANAGER_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DNS_MANAGER, NMDnsManagerClass)) +#define NM_IS_DNS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_DNS_MANAGER)) +#define NM_IS_DNS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), NM_TYPE_DNS_MANAGER)) +#define NM_DNS_MANAGER_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_DNS_MANAGER, NMDnsManagerClass)) + +/* properties */ +#define NM_DNS_MANAGER_MODE "mode" +#define NM_DNS_MANAGER_RC_MANAGER "rc-manager" +#define NM_DNS_MANAGER_CONFIGURATION "configuration" + +/* internal signals */ +#define NM_DNS_MANAGER_CONFIG_CHANGED "config-changed" + +typedef struct _NMDnsManager NMDnsManager; +typedef struct _NMDnsManagerClass NMDnsManagerClass; + +GType nm_dns_manager_get_type(void); + +NMDnsManager *nm_dns_manager_get(void); + +/* Allow changes to be batched together */ +void nm_dns_manager_begin_updates(NMDnsManager *self, const char *func); +void nm_dns_manager_end_updates(NMDnsManager *self, const char *func); + +gboolean nm_dns_manager_set_ip_config(NMDnsManager * self, + NMIPConfig * ip_config, + NMDnsIPConfigType ip_config_type); + +void nm_dns_manager_set_initial_hostname(NMDnsManager *self, const char *hostname); +void nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean skip_update); + +/** + * NMDnsManagerResolvConfManager + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN: unspecified rc-manager. + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED: do not touch /etc/resolv.conf + * (but still write the internal copy -- unless it is symlinked by + * /etc/resolv.conf) + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO: if /etc/resolv.conf is marked + * as an immutable file, use "unmanaged" and don't touch /etc/resolv.conf. + * Otherwise, if "systemd-resolved" is enabled (or detected), configure systemd-resolved via D-Bus + * and don't touch /etc/resolv.conf. + * Otherwise, if "resolvconf" application is found, use it. + * As last resort, fallback to "symlink" which writes to /etc/resolv.conf + * if (and only if) the file is missing or not a symlink. + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE: similar to "unmanaged", + * but indicates that resolv.conf cannot be modified. + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: NM writes /etc/resolv.conf + * if the file is missing or not a symlink. An existing symlink is + * left untouched. + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: Write to /etc/resolv.conf directly. + * If it is a file, write it as file, otherwise follow symlinks. + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: NM is managing resolv.conf + through resolvconf + * @NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: NM is managing resolv.conf + through netconfig + * + * NMDnsManager's management of resolv.conf + */ +typedef enum { + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN, + NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO, + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, + NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE, + NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK, + NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE, + NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF, + NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG, +} NMDnsManagerResolvConfManager; + +void nm_dns_manager_stop(NMDnsManager *self); + +gboolean nm_dns_manager_has_systemd_resolved(NMDnsManager *self); + +/*****************************************************************************/ + +char *nmtst_dns_create_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options); + +#endif /* __NETWORKMANAGER_DNS_MANAGER_H__ */ diff --git a/src/core/dns/nm-dns-plugin.c b/src/core/dns/nm-dns-plugin.c new file mode 100644 index 0000000..3233b6f --- /dev/null +++ b/src/core/dns/nm-dns-plugin.c @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dns-plugin.h" + +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +typedef struct _NMDnsPluginPrivate { + GPid pid; + guint watch_id; + char *progname; + char *pidfile; +} NMDnsPluginPrivate; + +G_DEFINE_ABSTRACT_TYPE(NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT) + +#define NM_DNS_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDnsPlugin, NM_IS_DNS_PLUGIN) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "dns-plugin" +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + char __prefix[20]; \ + const NMDnsPlugin *const __self = (self); \ + \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (!__self ? "" : nm_sprintf_buf(__prefix, "[%p]", __self)) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +gboolean +nm_dns_plugin_update(NMDnsPlugin * self, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error) +{ + g_return_val_if_fail(NM_DNS_PLUGIN_GET_CLASS(self)->update != NULL, FALSE); + + return NM_DNS_PLUGIN_GET_CLASS(self)->update(self, + global_config, + ip_config_lst_head, + hostname, + error); +} + +gboolean +nm_dns_plugin_is_caching(NMDnsPlugin *self) +{ + return NM_DNS_PLUGIN_GET_CLASS(self)->is_caching; +} + +const char * +nm_dns_plugin_get_name(NMDnsPlugin *self) +{ + NMDnsPluginClass *klass; + + g_return_val_if_fail(NM_IS_DNS_PLUGIN(self), NULL); + + klass = NM_DNS_PLUGIN_GET_CLASS(self); + nm_assert(klass->plugin_name); + return klass->plugin_name; +} + +void +nm_dns_plugin_stop(NMDnsPlugin *self) +{ + NMDnsPluginClass *klass; + + g_return_if_fail(NM_IS_DNS_PLUGIN(self)); + + klass = NM_DNS_PLUGIN_GET_CLASS(self); + if (klass->stop) + klass->stop(self); +} + +/*****************************************************************************/ + +static void +nm_dns_plugin_init(NMDnsPlugin *self) +{} + +static void +nm_dns_plugin_class_init(NMDnsPluginClass *plugin_class) +{} diff --git a/src/core/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h new file mode 100644 index 0000000..644d01e --- /dev/null +++ b/src/core/dns/nm-dns-plugin.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef __NM_DNS_PLUGIN_H__ +#define __NM_DNS_PLUGIN_H__ + +#include "nm-dns-manager.h" +#include "nm-config-data.h" + +#define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type()) +#define NM_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin)) +#define NM_DNS_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) +#define NM_IS_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_PLUGIN)) +#define NM_IS_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_PLUGIN)) +#define NM_DNS_PLUGIN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) + +typedef struct { + GObject parent; +} NMDnsPlugin; + +typedef struct { + GObjectClass parent; + + /* Called when DNS information is changed. 'configs' is an array + * of pointers to NMDnsConfigIPData sorted by priority. + * 'global_config' is the optional global DNS + * configuration. + */ + gboolean (*update)(NMDnsPlugin * self, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error); + + void (*stop)(NMDnsPlugin *self); + + const char *plugin_name; + + /* Types should set to TRUE if they start a local caching nameserver + * that listens on localhost and would block any other local caching + * nameserver from operating. + */ + bool is_caching : 1; + +} NMDnsPluginClass; + +GType nm_dns_plugin_get_type(void); + +gboolean nm_dns_plugin_is_caching(NMDnsPlugin *self); + +const char *nm_dns_plugin_get_name(NMDnsPlugin *self); + +gboolean nm_dns_plugin_update(NMDnsPlugin * self, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error); + +void nm_dns_plugin_stop(NMDnsPlugin *self); + +#endif /* __NM_DNS_PLUGIN_H__ */ diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c new file mode 100644 index 0000000..3535d3d --- /dev/null +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -0,0 +1,640 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Dan Williams + * Copyright (C) 2016 Sjoerd Simons + */ + +#include "nm-default.h" + +#include "nm-dns-systemd-resolved.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-dbus-manager.h" +#include "nm-manager.h" +#include "nm-setting-connection.h" +#include "devices/nm-device.h" +#include "NetworkManagerUtils.h" +#include "nm-std-aux/nm-dbus-compat.h" + +#define SYSTEMD_RESOLVED_DBUS_SERVICE "org.freedesktop.resolve1" +#define SYSTEMD_RESOLVED_MANAGER_IFACE "org.freedesktop.resolve1.Manager" +#define SYSTEMD_RESOLVED_DBUS_PATH "/org/freedesktop/resolve1" + +/* define a variable, so that we can compare the operation with pointer equality. */ +static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute"; + +/*****************************************************************************/ + +typedef struct { + int ifindex; + CList configs_lst_head; +} InterfaceConfig; + +typedef struct { + CList request_queue_lst; + const char * operation; + GVariant * argument; + NMDnsSystemdResolved *self; + int ifindex; +} RequestItem; + +/*****************************************************************************/ + +typedef struct { + GDBusConnection *dbus_connection; + GHashTable * dirty_interfaces; + GCancellable * cancellable; + CList request_queue_lst_head; + guint name_owner_changed_id; + bool send_updates_warn_ratelimited : 1; + bool try_start_blocked : 1; + bool dbus_has_owner : 1; + bool dbus_initied : 1; + bool request_queue_to_send : 1; + NMTernary has_link_default_route : 3; +} NMDnsSystemdResolvedPrivate; + +struct _NMDnsSystemdResolved { + NMDnsPlugin parent; + NMDnsSystemdResolvedPrivate _priv; +}; + +struct _NMDnsSystemdResolvedClass { + NMDnsPluginClass parent; +}; + +G_DEFINE_TYPE(NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN) + +#define NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG(level, ...) \ + __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG_DOMAIN, "dns-sd-resolved", __VA_ARGS__) + +/*****************************************************************************/ + +static void +_request_item_free(RequestItem *request_item) +{ + c_list_unlink_stale(&request_item->request_queue_lst); + g_variant_unref(request_item->argument); + nm_g_slice_free(request_item); +} + +static void +_request_item_append(NMDnsSystemdResolved *self, + const char * operation, + int ifindex, + GVariant * argument) +{ + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + RequestItem * request_item; + + request_item = g_slice_new(RequestItem); + *request_item = (RequestItem){ + .operation = operation, + .argument = g_variant_ref_sink(argument), + .self = self, + .ifindex = ifindex, + }; + c_list_link_tail(&priv->request_queue_lst_head, &request_item->request_queue_lst); +} + +/*****************************************************************************/ + +static void +_interface_config_free(InterfaceConfig *config) +{ + nm_c_list_elem_free_all(&config->configs_lst_head, NULL); + g_slice_free(InterfaceConfig, config); +} + +static void +call_done(GObject *source, GAsyncResult *r, gpointer user_data) +{ + gs_unref_variant GVariant *v = NULL; + gs_free_error GError * error = NULL; + NMDnsSystemdResolved * self; + NMDnsSystemdResolvedPrivate *priv; + RequestItem * request_item; + NMLogLevel log_level; + + v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + request_item = user_data; + self = request_item->self; + priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + if (v) { + if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE + && priv->has_link_default_route == NM_TERNARY_DEFAULT) { + priv->has_link_default_route = NM_TERNARY_TRUE; + _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported"); + } + priv->send_updates_warn_ratelimited = FALSE; + return; + } + + if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE + && nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { + if (priv->has_link_default_route == NM_TERNARY_DEFAULT) { + priv->has_link_default_route = NM_TERNARY_FALSE; + _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported"); + } + return; + } + + log_level = LOGL_DEBUG; + if (!priv->send_updates_warn_ratelimited) { + priv->send_updates_warn_ratelimited = TRUE; + log_level = LOGL_WARN; + } + _NMLOG(log_level, + "send-updates %s@%d failed: %s", + request_item->operation, + request_item->ifindex, + error->message); +} + +static gboolean +update_add_ip_config(NMDnsSystemdResolved *self, + GVariantBuilder * dns, + GVariantBuilder * domains, + NMDnsConfigIPData * data) +{ + int addr_family; + gsize addr_size; + guint i, n; + gboolean is_routing; + const char *domain; + gboolean has_config = FALSE; + + addr_family = nm_ip_config_get_addr_family(data->ip_config); + addr_size = nm_utils_addr_family_to_size(addr_family); + + if ((!data->domains.search || !data->domains.search[0]) + && !data->domains.has_default_route_exclusive && !data->domains.has_default_route) + return FALSE; + + n = nm_ip_config_get_num_nameservers(data->ip_config); + for (i = 0; i < n; i++) { + g_variant_builder_open(dns, G_VARIANT_TYPE("(iay)")); + g_variant_builder_add(dns, "i", addr_family); + g_variant_builder_add_value( + dns, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + nm_ip_config_get_nameserver(data->ip_config, i), + addr_size, + 1)); + g_variant_builder_close(dns); + has_config = TRUE; + } + + if (!data->domains.has_default_route_explicit && data->domains.has_default_route_exclusive) { + g_variant_builder_add(domains, "(sb)", ".", TRUE); + has_config = TRUE; + } + if (data->domains.search) { + for (i = 0; data->domains.search[i]; i++) { + domain = nm_utils_parse_dns_domain(data->domains.search[i], &is_routing); + g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing); + has_config = TRUE; + } + } + + return has_config; +} + +static void +free_pending_updates(NMDnsSystemdResolved *self) +{ + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + RequestItem * request_item; + + while ((request_item = + c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) + _request_item_free(request_item); +} + +static gboolean +prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic) +{ + GVariantBuilder dns; + GVariantBuilder domains; + NMCListElem * elem; + NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; + NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT; + const char * mdns_arg = NULL, *llmnr_arg = NULL; + gboolean has_config = FALSE; + gboolean has_default_route = FALSE; + + g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))")); + g_variant_builder_add(&dns, "i", ic->ifindex); + g_variant_builder_open(&dns, G_VARIANT_TYPE("a(iay)")); + + g_variant_builder_init(&domains, G_VARIANT_TYPE("(ia(sb))")); + g_variant_builder_add(&domains, "i", ic->ifindex); + g_variant_builder_open(&domains, G_VARIANT_TYPE("a(sb)")); + + c_list_for_each_entry (elem, &ic->configs_lst_head, lst) { + NMDnsConfigIPData *data = elem->data; + NMIPConfig * ip_config = data->ip_config; + + has_config |= update_add_ip_config(self, &dns, &domains, data); + + if (data->domains.has_default_route) + has_default_route = TRUE; + + if (NM_IS_IP4_CONFIG(ip_config)) { + mdns = NM_MAX(mdns, nm_ip4_config_mdns_get(NM_IP4_CONFIG(ip_config))); + llmnr = NM_MAX(llmnr, nm_ip4_config_llmnr_get(NM_IP4_CONFIG(ip_config))); + } + } + + g_variant_builder_close(&dns); + g_variant_builder_close(&domains); + + switch (mdns) { + case NM_SETTING_CONNECTION_MDNS_NO: + mdns_arg = "no"; + break; + case NM_SETTING_CONNECTION_MDNS_RESOLVE: + mdns_arg = "resolve"; + break; + case NM_SETTING_CONNECTION_MDNS_YES: + mdns_arg = "yes"; + break; + case NM_SETTING_CONNECTION_MDNS_DEFAULT: + mdns_arg = ""; + break; + } + nm_assert(mdns_arg); + + switch (llmnr) { + case NM_SETTING_CONNECTION_LLMNR_NO: + llmnr_arg = "no"; + break; + case NM_SETTING_CONNECTION_LLMNR_RESOLVE: + llmnr_arg = "resolve"; + break; + case NM_SETTING_CONNECTION_LLMNR_YES: + llmnr_arg = "yes"; + break; + case NM_SETTING_CONNECTION_LLMNR_DEFAULT: + llmnr_arg = ""; + break; + } + nm_assert(llmnr_arg); + + if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg)) + has_config = TRUE; + + _request_item_append(self, "SetLinkDomains", ic->ifindex, g_variant_builder_end(&domains)); + _request_item_append(self, + DBUS_OP_SET_LINK_DEFAULT_ROUTE, + ic->ifindex, + g_variant_new("(ib)", ic->ifindex, has_default_route)); + _request_item_append(self, + "SetLinkMulticastDNS", + ic->ifindex, + g_variant_new("(is)", ic->ifindex, mdns_arg ?: "")); + _request_item_append(self, + "SetLinkLLMNR", + ic->ifindex, + g_variant_new("(is)", ic->ifindex, llmnr_arg ?: "")); + _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns)); + + return has_config; +} + +static void +send_updates(NMDnsSystemdResolved *self) +{ + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + RequestItem * request_item; + + if (!priv->request_queue_to_send) { + /* nothing to do. */ + return; + } + + if (!priv->dbus_initied) { + _LOGT("send-updates: D-Bus connection not ready"); + return; + } + + if (!priv->dbus_has_owner) { + if (priv->try_start_blocked) { + /* we have no name owner and we already tried poking the service to + * autostart. */ + _LOGT("send-updates: no name owner"); + return; + } + + _LOGT("send-updates: no name owner. Try start service..."); + priv->try_start_blocked = TRUE; + + nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, + SYSTEMD_RESOLVED_DBUS_SERVICE, + -1, + NULL, + NULL, + NULL); + return; + } + + nm_clear_g_cancellable(&priv->cancellable); + + if (c_list_is_empty(&priv->request_queue_lst_head)) { + _LOGT("send-updates: no requests to send"); + priv->request_queue_to_send = FALSE; + return; + } + + _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head)); + + priv->cancellable = g_cancellable_new(); + + priv->request_queue_to_send = FALSE; + + c_list_for_each_entry (request_item, &priv->request_queue_lst_head, request_queue_lst) { + if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE + && priv->has_link_default_route == NM_TERNARY_FALSE) { + /* The "SetLinkDefaultRoute" API is only supported since v240. + * We detected that it is not supported, and skip the call. There + * is no special workaround, because in this case we rely on systemd-resolved + * to do the right thing automatically. */ + continue; + } + + /* Above we explicitly call "StartServiceByName" trying to avoid D-Bus activating systmd-resolved + * multiple times. There is still a race, were we might hit this line although actually + * the service just quit this very moment. In that case, we would try to D-Bus activate the + * service multiple times during each call (something we wanted to avoid). + * + * But this is hard to avoid, because we'd have to check the error failure to detect the reason + * and retry. The race is not critical, because at worst it results in logging a warning + * about failure to start systemd.resolved. */ + g_dbus_connection_call(priv->dbus_connection, + SYSTEMD_RESOLVED_DBUS_SERVICE, + SYSTEMD_RESOLVED_DBUS_PATH, + SYSTEMD_RESOLVED_MANAGER_IFACE, + request_item->operation, + request_item->argument, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + call_done, + request_item); + } +} + +static gboolean +update(NMDnsPlugin * plugin, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error) +{ + NMDnsSystemdResolved * self = NM_DNS_SYSTEMD_RESOLVED(plugin); + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *interfaces = NULL; + gs_free gpointer * interfaces_keys = NULL; + guint interfaces_len; + int ifindex; + gpointer pointer; + NMDnsConfigIPData *ip_data; + GHashTableIter iter; + guint i; + + interfaces = + g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free); + + c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) { + InterfaceConfig *ic = NULL; + + ifindex = ip_data->data->ifindex; + nm_assert(ifindex == nm_ip_config_get_ifindex(ip_data->ip_config)); + + ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex)); + if (!ic) { + ic = g_slice_new(InterfaceConfig); + ic->ifindex = ifindex; + c_list_init(&ic->configs_lst_head); + g_hash_table_insert(interfaces, GINT_TO_POINTER(ifindex), ic); + } + + c_list_link_tail(&ic->configs_lst_head, &nm_c_list_elem_new_stale(ip_data)->lst); + } + + free_pending_updates(self); + + interfaces_keys = + nm_utils_hash_keys_to_array(interfaces, nm_cmp_int2ptr_p_with_data, NULL, &interfaces_len); + for (i = 0; i < interfaces_len; i++) { + InterfaceConfig *ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(interfaces_keys[i])); + + if (prepare_one_interface(self, ic)) + g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex)); + else + g_hash_table_remove(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex)); + } + + /* If we previously configured an ifindex with non-empty values in + * resolved, and the current update doesn't contain that interface, + * reset the resolved configuration for that ifindex. */ + g_hash_table_iter_init(&iter, priv->dirty_interfaces); + while (g_hash_table_iter_next(&iter, (gpointer *) &pointer, NULL)) { + ifindex = GPOINTER_TO_INT(pointer); + if (!g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) { + InterfaceConfig ic; + + _LOGT("clear previously configured ifindex %d", ifindex); + ic = (InterfaceConfig){ + .ifindex = ifindex, + .configs_lst_head = C_LIST_INIT(ic.configs_lst_head), + }; + prepare_one_interface(self, &ic); + g_hash_table_iter_remove(&iter); + } + } + + priv->request_queue_to_send = TRUE; + send_updates(self); + return TRUE; +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMDnsSystemdResolved *self, const char *owner) +{ + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + owner = nm_str_not_empty(owner); + + if (!owner) + _LOGT("D-Bus name for systemd-resolved has no owner"); + else + _LOGT("D-Bus name for systemd-resolved has owner %s", owner); + + priv->dbus_has_owner = !!owner; + if (owner) { + priv->try_start_blocked = FALSE; + priv->request_queue_to_send = TRUE; + } else + priv->has_link_default_route = NM_TERNARY_DEFAULT; + + send_updates(self); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMDnsSystemdResolved * self = user_data; + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + const char * new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + if (!priv->dbus_initied) { + /* There was a race and we got a NameOwnerChanged signal before GetNameOwner + * returns. */ + priv->dbus_initied = TRUE; + nm_clear_g_cancellable(&priv->cancellable); + } + + name_owner_changed(user_data, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + NMDnsSystemdResolved * self; + NMDnsSystemdResolvedPrivate *priv; + + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + g_clear_object(&priv->cancellable); + + priv->dbus_initied = TRUE; + + name_owner_changed(self, name_owner); +} + +/*****************************************************************************/ + +gboolean +nm_dns_systemd_resolved_is_running(NMDnsSystemdResolved *self) +{ + NMDnsSystemdResolvedPrivate *priv; + + g_return_val_if_fail(NM_IS_DNS_SYSTEMD_RESOLVED(self), FALSE); + + priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + return priv->dbus_initied && (priv->dbus_has_owner || !priv->try_start_blocked); +} + +/*****************************************************************************/ + +static void +nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self) +{ + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + priv->has_link_default_route = NM_TERNARY_DEFAULT; + + c_list_init(&priv->request_queue_lst_head); + priv->dirty_interfaces = g_hash_table_new(nm_direct_hash, NULL); + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + if (!priv->dbus_connection) { + _LOGD("no D-Bus connection"); + return; + } + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + SYSTEMD_RESOLVED_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + priv->cancellable = g_cancellable_new(); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + SYSTEMD_RESOLVED_DBUS_SERVICE, + -1, + priv->cancellable, + get_name_owner_cb, + self); +} + +NMDnsPlugin * +nm_dns_systemd_resolved_new(void) +{ + return g_object_new(NM_TYPE_DNS_SYSTEMD_RESOLVED, NULL); +} + +static void +dispose(GObject *object) +{ + NMDnsSystemdResolved * self = NM_DNS_SYSTEMD_RESOLVED(object); + NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + + free_pending_updates(self); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_cancellable(&priv->cancellable); + + g_clear_object(&priv->dbus_connection); + nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref); + + G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object); +} + +static void +nm_dns_systemd_resolved_class_init(NMDnsSystemdResolvedClass *dns_class) +{ + NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS(dns_class); + GObjectClass * object_class = G_OBJECT_CLASS(dns_class); + + object_class->dispose = dispose; + + plugin_class->plugin_name = "systemd-resolved"; + plugin_class->is_caching = TRUE; + plugin_class->update = update; +} diff --git a/src/core/dns/nm-dns-systemd-resolved.h b/src/core/dns/nm-dns-systemd-resolved.h new file mode 100644 index 0000000..4ab04ab --- /dev/null +++ b/src/core/dns/nm-dns-systemd-resolved.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2016 Sjoerd Simons + */ + +#ifndef __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ +#define __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ + +#include "nm-dns-plugin.h" + +#define NM_TYPE_DNS_SYSTEMD_RESOLVED (nm_dns_systemd_resolved_get_type()) +#define NM_DNS_SYSTEMD_RESOLVED(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolved)) +#define NM_DNS_SYSTEMD_RESOLVED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolvedClass)) +#define NM_IS_DNS_SYSTEMD_RESOLVED(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_SYSTEMD_RESOLVED)) +#define NM_IS_DNS_SYSTEMD_RESOLVED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_SYSTEMD_RESOLVED)) +#define NM_DNS_SYSTEMD_RESOLVED_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolvedClass)) + +typedef struct _NMDnsSystemdResolved NMDnsSystemdResolved; +typedef struct _NMDnsSystemdResolvedClass NMDnsSystemdResolvedClass; + +GType nm_dns_systemd_resolved_get_type(void); + +NMDnsPlugin *nm_dns_systemd_resolved_new(void); + +gboolean nm_dns_systemd_resolved_is_running(NMDnsSystemdResolved *self); + +#endif /* __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ */ diff --git a/src/core/dns/nm-dns-unbound.c b/src/core/dns/nm-dns-unbound.c new file mode 100644 index 0000000..ee162c3 --- /dev/null +++ b/src/core/dns/nm-dns-unbound.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + * Author: Pavel Šimerda + */ + +#include "nm-default.h" + +#include "nm-dns-unbound.h" + +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +struct _NMDnsUnbound { + NMDnsPlugin parent; +}; + +struct _NMDnsUnboundClass { + NMDnsPluginClass parent; +}; + +G_DEFINE_TYPE(NMDnsUnbound, nm_dns_unbound, NM_TYPE_DNS_PLUGIN) + +/*****************************************************************************/ + +static gboolean +update(NMDnsPlugin * plugin, + const NMGlobalDnsConfig *global_config, + const CList * ip_config_lst_head, + const char * hostname, + GError ** error) +{ + char * argv[] = {DNSSEC_TRIGGER_PATH, "--async", "--update", NULL}; + gs_free_error GError *local = NULL; + int status; + + /* TODO: We currently call a script installed with the dnssec-trigger + * package that queries all information itself. Later, the dependency + * on that package will be optional and the only hard dependency will + * be unbound. + * + * Unbound configuration should be later handled by this plugin directly, + * without calling custom scripts. The dnssec-trigger functionality + * may be eventually merged into NetworkManager. + */ + if (!g_spawn_sync("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "error spawning dns-trigger: %s", + local->message); + return FALSE; + } + if (status != 0) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "dns-trigger exited with error code %d", + status); + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_dns_unbound_init(NMDnsUnbound *unbound) +{} + +NMDnsPlugin * +nm_dns_unbound_new(void) +{ + return g_object_new(NM_TYPE_DNS_UNBOUND, NULL); +} + +static void +nm_dns_unbound_class_init(NMDnsUnboundClass *klass) +{ + NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS(klass); + + plugin_class->plugin_name = "unbound"; + plugin_class->is_caching = TRUE; + plugin_class->update = update; +} diff --git a/src/core/dns/nm-dns-unbound.h b/src/core/dns/nm-dns-unbound.h new file mode 100644 index 0000000..feb3309 --- /dev/null +++ b/src/core/dns/nm-dns-unbound.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DNS_UNBOUND_H__ +#define __NETWORKMANAGER_DNS_UNBOUND_H__ + +#include "nm-dns-plugin.h" + +#define NM_TYPE_DNS_UNBOUND (nm_dns_unbound_get_type()) +#define NM_DNS_UNBOUND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_UNBOUND, NMDnsUnbound)) +#define NM_DNS_UNBOUND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_UNBOUND, NMDnsUnboundClass)) +#define NM_IS_DNS_UNBOUND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_UNBOUND)) +#define NM_IS_DNS_UNBOUND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_UNBOUND)) +#define NM_DNS_UNBOUND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_UNBOUND, NMDnsUnboundClass)) + +typedef struct _NMDnsUnbound NMDnsUnbound; +typedef struct _NMDnsUnboundClass NMDnsUnboundClass; + +GType nm_dns_unbound_get_type(void); + +NMDnsPlugin *nm_dns_unbound_new(void); + +#endif /* __NETWORKMANAGER_DNS_UNBOUND_H__ */ diff --git a/src/core/dnsmasq/nm-dnsmasq-manager.c b/src/core/dnsmasq/nm-dnsmasq-manager.c new file mode 100644 index 0000000..00b02a0 --- /dev/null +++ b/src/core/dnsmasq/nm-dnsmasq-manager.c @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dnsmasq-manager.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-dnsmasq-utils.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" + +#define CONFDIR NMCONFDIR "/dnsmasq-shared.d" + +/*****************************************************************************/ + +enum { + STATE_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + char *iface; + char *pidfile; + GPid pid; + guint dm_watch_id; +} NMDnsMasqManagerPrivate; + +struct _NMDnsMasqManager { + GObject parent; + NMDnsMasqManagerPrivate _priv; +}; + +struct _NMDnsMasqManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMDnsMasqManager, nm_dnsmasq_manager, G_TYPE_OBJECT) + +#define NM_DNSMASQ_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDnsMasqManager, NM_IS_DNSMASQ_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SHARING +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "dnsmasq-manager", __VA_ARGS__) + +/*****************************************************************************/ + +static void +dm_watch_cb(GPid pid, int status, gpointer user_data) +{ + NMDnsMasqManager * manager = NM_DNSMASQ_MANAGER(user_data); + NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE(manager); + guint err; + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + if (err != 0) { + _LOGW("dnsmasq exited with error: %s", nm_utils_dnsmasq_status_to_string(err, NULL, 0)); + } + } else if (WIFSTOPPED(status)) { + _LOGW("dnsmasq stopped unexpectedly with signal %d", WSTOPSIG(status)); + } else if (WIFSIGNALED(status)) { + _LOGW("dnsmasq died with signal %d", WTERMSIG(status)); + } else { + _LOGW("dnsmasq died from an unknown cause"); + } + + priv->pid = 0; + priv->dm_watch_id = 0; + + g_signal_emit(manager, signals[STATE_CHANGED], 0, NM_DNSMASQ_STATUS_DEAD); +} + +static GPtrArray * +create_dm_cmd_line(const char * iface, + const NMIP4Config *ip4_config, + const char * pidfile, + gboolean announce_android_metered, + GError ** error) +{ + gs_unref_ptrarray GPtrArray *cmd = NULL; + nm_auto_free_gstring GString *s = NULL; + char first[INET_ADDRSTRLEN]; + char last[INET_ADDRSTRLEN]; + char listen_address_s[INET_ADDRSTRLEN]; + char tmpaddr[INET_ADDRSTRLEN]; + gs_free char * error_desc = NULL; + const char * dm_binary; + const NMPlatformIP4Address * listen_address; + guint i, n; + + listen_address = nm_ip4_config_get_first_address(ip4_config); + + g_return_val_if_fail(listen_address, NULL); + + dm_binary = nm_utils_find_helper("dnsmasq", DNSMASQ_PATH, error); + if (!dm_binary) + return NULL; + + cmd = g_ptr_array_new_with_free_func(g_free); + + nm_strv_ptrarray_add_string_dup(cmd, dm_binary); + + if (nm_logging_enabled(LOGL_TRACE, LOGD_SHARING) || getenv("NM_DNSMASQ_DEBUG")) { + nm_strv_ptrarray_add_string_dup(cmd, "--log-dhcp"); + nm_strv_ptrarray_add_string_dup(cmd, "--log-queries"); + } + + /* dnsmasq may read from its default config file location, which if that + * location is a valid config file, it will combine with the options here + * and cause undesirable side-effects. Like sending bogus IP addresses + * as the gateway or whatever. So tell dnsmasq not to use any config file + * at all. + */ + nm_strv_ptrarray_add_string_dup(cmd, "--conf-file=/dev/null"); + + nm_strv_ptrarray_add_string_dup(cmd, "--no-hosts"); + nm_strv_ptrarray_add_string_dup(cmd, "--keep-in-foreground"); + nm_strv_ptrarray_add_string_dup(cmd, "--bind-interfaces"); + nm_strv_ptrarray_add_string_dup(cmd, "--except-interface=lo"); + nm_strv_ptrarray_add_string_dup(cmd, "--clear-on-reload"); + + /* Use strict order since in the case of VPN connections, the VPN's + * nameservers will be first in resolv.conf, and those need to be tried + * first by dnsmasq to successfully resolve names from the VPN. + */ + nm_strv_ptrarray_add_string_dup(cmd, "--strict-order"); + + _nm_utils_inet4_ntop(listen_address->address, listen_address_s); + + nm_strv_ptrarray_add_string_concat(cmd, "--listen-address=", listen_address_s); + + if (!nm_dnsmasq_utils_get_range(listen_address, first, last, &error_desc)) { + g_set_error_literal(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, error_desc); + _LOGW("failed to find DHCP address ranges: %s", error_desc); + return NULL; + } + + nm_strv_ptrarray_add_string_printf(cmd, "--dhcp-range=%s,%s,60m", first, last); + + if (nm_ip4_config_best_default_route_get(ip4_config)) { + nm_strv_ptrarray_add_string_concat(cmd, "--dhcp-option=option:router,", listen_address_s); + } + + if ((n = nm_ip4_config_get_num_nameservers(ip4_config))) { + nm_gstring_prepare(&s); + g_string_append(s, "--dhcp-option=option:dns-server"); + for (i = 0; i < n; i++) { + g_string_append_c(s, ','); + g_string_append( + s, + _nm_utils_inet4_ntop(nm_ip4_config_get_nameserver(ip4_config, i), tmpaddr)); + } + nm_strv_ptrarray_take_gstring(cmd, &s); + } + + if ((n = nm_ip4_config_get_num_searches(ip4_config))) { + nm_gstring_prepare(&s); + g_string_append(s, "--dhcp-option=option:domain-search"); + for (i = 0; i < n; i++) { + g_string_append_c(s, ','); + g_string_append(s, nm_ip4_config_get_search(ip4_config, i)); + } + nm_strv_ptrarray_take_gstring(cmd, &s); + } + + if (announce_android_metered) { + /* force option 43 to announce ANDROID_METERED. Do this, even if the client + * did not ask for this option. See https://www.lorier.net/docs/android-metered.html */ + nm_strv_ptrarray_add_string_dup(cmd, "--dhcp-option-force=43,ANDROID_METERED"); + } + + nm_strv_ptrarray_add_string_dup(cmd, "--dhcp-lease-max=50"); + + nm_strv_ptrarray_add_string_printf(cmd, + "--dhcp-leasefile=%s/dnsmasq-%s.leases", + NMSTATEDIR, + iface); + + nm_strv_ptrarray_add_string_concat(cmd, "--pid-file=", pidfile); + + /* dnsmasq exits if the conf dir is not present */ + if (g_file_test(CONFDIR, G_FILE_TEST_IS_DIR)) + nm_strv_ptrarray_add_string_dup(cmd, "--conf-dir=" CONFDIR); + + g_ptr_array_add(cmd, NULL); + return g_steal_pointer(&cmd); +} + +static void +kill_existing_by_pidfile(const char *pidfile) +{ + char * contents = NULL; + pid_t pid; + char proc_path[250]; + char * cmdline_contents = NULL; + guint64 start_time; + const char *exe; + + if (!pidfile || !g_file_get_contents(pidfile, &contents, NULL, NULL)) + return; + + pid = _nm_utils_ascii_str_to_int64(contents, 10, 1, G_MAXUINT64, 0); + if (pid == 0) + goto out; + + start_time = nm_utils_get_start_time_for_pid(pid, NULL, NULL); + if (start_time == 0) + goto out; + + nm_sprintf_buf(proc_path, "/proc/%lld/cmdline", (long long) pid); + if (!g_file_get_contents(proc_path, &cmdline_contents, NULL, NULL)) + goto out; + + exe = strrchr(cmdline_contents, '/'); + if ((exe && strcmp(&exe[1], "dnsmasq") == 0) || (strcmp(cmdline_contents, DNSMASQ_PATH) == 0)) { + nm_utils_kill_process_sync(pid, start_time, SIGKILL, LOGD_SHARING, "dnsmasq", 0, 0, 500); + } + +out: + unlink(pidfile); + g_free(cmdline_contents); + g_free(contents); +} + +gboolean +nm_dnsmasq_manager_start(NMDnsMasqManager *manager, + NMIP4Config * ip4_config, + gboolean announce_android_metered, + GError ** error) +{ + NMDnsMasqManagerPrivate *priv; + gs_unref_ptrarray GPtrArray *dm_cmd = NULL; + gs_free char * cmd_str = NULL; + + g_return_val_if_fail(NM_IS_DNSMASQ_MANAGER(manager), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + g_return_val_if_fail(nm_ip4_config_get_num_addresses(ip4_config) > 0, FALSE); + + priv = NM_DNSMASQ_MANAGER_GET_PRIVATE(manager); + + kill_existing_by_pidfile(priv->pidfile); + + dm_cmd = + create_dm_cmd_line(priv->iface, ip4_config, priv->pidfile, announce_android_metered, error); + if (!dm_cmd) + return FALSE; + + _LOGI("starting dnsmasq..."); + _LOGD("command line: %s", (cmd_str = g_strjoinv(" ", (char **) dm_cmd->pdata))); + + priv->pid = 0; + if (!g_spawn_async(NULL, + (char **) dm_cmd->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + nm_utils_setpgid, + NULL, + &priv->pid, + error)) + return FALSE; + + nm_assert(priv->pid > 0); + + _LOGD("dnsmasq started with pid %d", priv->pid); + + priv->dm_watch_id = g_child_watch_add(priv->pid, (GChildWatchFunc) dm_watch_cb, manager); + + return TRUE; +} + +void +nm_dnsmasq_manager_stop(NMDnsMasqManager *manager) +{ + NMDnsMasqManagerPrivate *priv; + + g_return_if_fail(NM_IS_DNSMASQ_MANAGER(manager)); + + priv = NM_DNSMASQ_MANAGER_GET_PRIVATE(manager); + + nm_clear_g_source(&priv->dm_watch_id); + + if (priv->pid) { + nm_utils_kill_child_async(priv->pid, SIGTERM, LOGD_SHARING, "dnsmasq", 2000, NULL, NULL); + priv->pid = 0; + } + + unlink(priv->pidfile); +} + +/*****************************************************************************/ + +static void +nm_dnsmasq_manager_init(NMDnsMasqManager *manager) +{} + +NMDnsMasqManager * +nm_dnsmasq_manager_new(const char *iface) +{ + NMDnsMasqManager * manager; + NMDnsMasqManagerPrivate *priv; + + manager = g_object_new(NM_TYPE_DNSMASQ_MANAGER, NULL); + + priv = NM_DNSMASQ_MANAGER_GET_PRIVATE(manager); + priv->iface = g_strdup(iface); + priv->pidfile = g_strdup_printf(RUNSTATEDIR "/nm-dnsmasq-%s.pid", iface); + + return manager; +} + +static void +finalize(GObject *object) +{ + NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE(object); + + nm_dnsmasq_manager_stop(NM_DNSMASQ_MANAGER(object)); + + g_free(priv->iface); + g_free(priv->pidfile); + + G_OBJECT_CLASS(nm_dnsmasq_manager_parent_class)->finalize(object); +} + +static void +nm_dnsmasq_manager_class_init(NMDnsMasqManagerClass *manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(manager_class); + + object_class->finalize = finalize; + + signals[STATE_CHANGED] = g_signal_new(NM_DNS_MASQ_MANAGER_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); +} diff --git a/src/core/dnsmasq/nm-dnsmasq-manager.h b/src/core/dnsmasq/nm-dnsmasq-manager.h new file mode 100644 index 0000000..272d1ce --- /dev/null +++ b/src/core/dnsmasq/nm-dnsmasq-manager.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DNSMASQ_MANAGER_H__ +#define __NETWORKMANAGER_DNSMASQ_MANAGER_H__ + +#include "nm-ip4-config.h" + +#define NM_TYPE_DNSMASQ_MANAGER (nm_dnsmasq_manager_get_type()) +#define NM_DNSMASQ_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManager)) +#define NM_DNSMASQ_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManagerClass)) +#define NM_IS_DNSMASQ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNSMASQ_MANAGER)) +#define NM_IS_DNSMASQ_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNSMASQ_MANAGER)) +#define NM_DNSMASQ_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManagerClass)) + +/* signals */ +#define NM_DNS_MASQ_MANAGER_STATE_CHANGED "state-changed" + +typedef enum { + NM_DNSMASQ_STATUS_UNKNOWN, + NM_DNSMASQ_STATUS_DEAD, + NM_DNSMASQ_STATUS_RUNNING, +} NMDnsMasqStatus; + +typedef struct _NMDnsMasqManager NMDnsMasqManager; +typedef struct _NMDnsMasqManagerClass NMDnsMasqManagerClass; + +GType nm_dnsmasq_manager_get_type(void); + +NMDnsMasqManager *nm_dnsmasq_manager_new(const char *iface); + +gboolean nm_dnsmasq_manager_start(NMDnsMasqManager *manager, + NMIP4Config * ip4_config, + gboolean announce_android_metered, + GError ** error); + +void nm_dnsmasq_manager_stop(NMDnsMasqManager *manager); + +#endif /* __NETWORKMANAGER_DNSMASQ_MANAGER_H__ */ diff --git a/src/core/dnsmasq/nm-dnsmasq-utils.c b/src/core/dnsmasq/nm-dnsmasq-utils.c new file mode 100644 index 0000000..3bd7c93 --- /dev/null +++ b/src/core/dnsmasq/nm-dnsmasq-utils.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-dnsmasq-utils.h" +#include "platform/nm-platform.h" +#include "nm-utils.h" + +gboolean +nm_dnsmasq_utils_get_range(const NMPlatformIP4Address *addr, + char * out_first, + char * out_last, + char ** out_error_desc) +{ + guint32 host = addr->address; + guint8 prefix = addr->plen; + guint32 netmask; + guint32 first, last, mid, reserved; + const guint32 NUM = 256; + + g_return_val_if_fail(out_first, FALSE); + g_return_val_if_fail(out_last, FALSE); + + if (prefix > 30) { + if (out_error_desc) + *out_error_desc = g_strdup_printf("Address prefix %d is too small for DHCP.", prefix); + return FALSE; + } + + if (prefix < 24) { + /* if the subnet is larger then /24, we partition it and treat it + * like it would be a /24. + * + * Hence, the resulting range will always be between x.x.x.1/24 + * and x.x.x.254/24, with x.x.x.0 being the network address of the + * host. + * + * In this case, only a /24 portion of the subnet is used. + * No particular reason for that, but it's unlikely that a user + * would use NetworkManager's shared method when having hundred + * of DHCP clients. So, restrict the range to the same /24 in + * which the host address lies. + */ + prefix = 24; + } + + netmask = _nm_utils_ip4_prefix_to_netmask(prefix); + + /* treat addresses in host-order from here on. */ + netmask = ntohl(netmask); + host = ntohl(host); + + /* if host is the network or broadcast address, coerce it to + * one above or below. Usually, we wouldn't expect the user + * to pick such an address. */ + if (host == (host & netmask)) + host++; + else if (host == (host | ~netmask)) + host--; + + /* Exclude the network and broadcast address. */ + first = (host & netmask) + 1; + last = (host | ~netmask) - 1; + + /* Depending on whether host is above or below the middle of + * the subnet, the larger part if handed out. + * + * If the host is in the lower half, the range starts + * at the lower end with the host (plus reserved), until the + * broadcast address + * + * If the host is in the upper half, the range starts above + * the network-address and goes up until the host (except reserved). + * + * reserved is up to 8 addresses, 10% of the determined range. + */ + mid = (host & netmask) | (((first + last) / 2) & ~netmask); + if (host > mid) { + /* use lower range */ + reserved = NM_MIN(((host - first) / 10), 8); + last = host - 1 - reserved; + first = NM_MAX(first, last > NUM ? last - NUM : 0); + } else { + /* use upper range */ + reserved = NM_MIN(((last - host) / 10), 8); + first = host + 1 + reserved; + last = NM_MIN(last, first < 0xFFFFFFFF - NUM ? first + NUM : 0xFFFFFFFF); + } + + first = htonl(first); + last = htonl(last); + + _nm_utils_inet4_ntop(first, out_first); + _nm_utils_inet4_ntop(last, out_last); + + return TRUE; +} diff --git a/src/core/dnsmasq/nm-dnsmasq-utils.h b/src/core/dnsmasq/nm-dnsmasq-utils.h new file mode 100644 index 0000000..57bbc4c --- /dev/null +++ b/src/core/dnsmasq/nm-dnsmasq-utils.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DNSMASQ_UTILS_H__ +#define __NETWORKMANAGER_DNSMASQ_UTILS_H__ + +#include "platform/nm-platform.h" + +gboolean nm_dnsmasq_utils_get_range(const NMPlatformIP4Address *addr, + char * out_first, + char * out_last, + char ** out_error_desc); + +#endif /* __NETWORKMANAGER_DNSMASQ_UTILS_H__ */ diff --git a/src/core/dnsmasq/tests/meson.build b/src/core/dnsmasq/tests/meson.build new file mode 100644 index 0000000..565c18a --- /dev/null +++ b/src/core/dnsmasq/tests/meson.build @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_unit = 'test-dnsmasq-utils' + +exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) + +test( + 'dnsmasq/' + test_unit, + test_script, + args: test_args + [exe.full_path()], +) diff --git a/src/core/dnsmasq/tests/test-dnsmasq-utils.c b/src/core/dnsmasq/tests/test-dnsmasq-utils.c new file mode 100644 index 0000000..6eadf29 --- /dev/null +++ b/src/core/dnsmasq/tests/test-dnsmasq-utils.c @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "dnsmasq/nm-dnsmasq-utils.h" + +#include "nm-test-utils-core.h" + +static void +test_address_ranges(void) +{ +#define _test_address_range(addr, plen, expected_first, expected_last) \ + G_STMT_START \ + { \ + char *_error_desc = NULL; \ + char _first[INET_ADDRSTRLEN]; \ + char _last[INET_ADDRSTRLEN]; \ + \ + if (!nm_dnsmasq_utils_get_range(nmtst_platform_ip4_address((addr ""), NULL, (plen)), \ + _first, \ + _last, \ + &_error_desc)) \ + g_assert_not_reached(); \ + g_assert(!_error_desc); \ + g_assert_cmpstr(_first, ==, (expected_first "")); \ + g_assert_cmpstr(_last, ==, (expected_last "")); \ + g_assert_cmpint( \ + (ntohl(nmtst_inet4_from_string(_last)) - ntohl(nmtst_inet4_from_string(_first))), \ + <=, \ + 244); \ + } \ + G_STMT_END + +#define _test_address_range_fail(addr, plen) \ + G_STMT_START \ + { \ + char *_error_desc = NULL; \ + char _first[INET_ADDRSTRLEN]; \ + char _last[INET_ADDRSTRLEN]; \ + \ + if (nm_dnsmasq_utils_get_range(nmtst_platform_ip4_address((addr ""), NULL, (plen)), \ + _first, \ + _last, \ + &_error_desc)) \ + g_assert_not_reached(); \ + g_assert(_error_desc); \ + g_free(_error_desc); \ + } \ + G_STMT_END + + _test_address_range_fail("1.2.3.1", 31); + + _test_address_range("0.0.0.0", 30, "0.0.0.2", "0.0.0.2"); + _test_address_range("0.0.0.1", 30, "0.0.0.2", "0.0.0.2"); + _test_address_range("0.0.0.2", 30, "0.0.0.1", "0.0.0.1"); + _test_address_range("0.0.0.3", 30, "0.0.0.1", "0.0.0.1"); + _test_address_range("1.2.3.0", 30, "1.2.3.2", "1.2.3.2"); + _test_address_range("1.2.3.1", 30, "1.2.3.2", "1.2.3.2"); + _test_address_range("1.2.3.2", 30, "1.2.3.1", "1.2.3.1"); + _test_address_range("1.2.3.3", 30, "1.2.3.1", "1.2.3.1"); + _test_address_range("1.2.3.4", 30, "1.2.3.6", "1.2.3.6"); + _test_address_range("1.2.3.5", 30, "1.2.3.6", "1.2.3.6"); + _test_address_range("1.2.3.6", 30, "1.2.3.5", "1.2.3.5"); + _test_address_range("1.2.3.7", 30, "1.2.3.5", "1.2.3.5"); + _test_address_range("1.2.3.8", 30, "1.2.3.10", "1.2.3.10"); + _test_address_range("1.2.3.9", 30, "1.2.3.10", "1.2.3.10"); + _test_address_range("255.255.255.0", 30, "255.255.255.2", "255.255.255.2"); + _test_address_range("255.255.255.1", 30, "255.255.255.2", "255.255.255.2"); + _test_address_range("255.255.255.2", 30, "255.255.255.1", "255.255.255.1"); + _test_address_range("255.255.255.3", 30, "255.255.255.1", "255.255.255.1"); + _test_address_range("255.255.255.248", 30, "255.255.255.250", "255.255.255.250"); + _test_address_range("255.255.255.249", 30, "255.255.255.250", "255.255.255.250"); + _test_address_range("255.255.255.250", 30, "255.255.255.249", "255.255.255.249"); + _test_address_range("255.255.255.251", 30, "255.255.255.249", "255.255.255.249"); + _test_address_range("255.255.255.252", 30, "255.255.255.254", "255.255.255.254"); + _test_address_range("255.255.255.253", 30, "255.255.255.254", "255.255.255.254"); + _test_address_range("255.255.255.254", 30, "255.255.255.253", "255.255.255.253"); + _test_address_range("255.255.255.255", 30, "255.255.255.253", "255.255.255.253"); + + _test_address_range("0.0.0.0", 29, "0.0.0.2", "0.0.0.6"); + _test_address_range("0.0.0.1", 29, "0.0.0.2", "0.0.0.6"); + _test_address_range("0.0.0.2", 29, "0.0.0.3", "0.0.0.6"); + _test_address_range("0.0.0.3", 29, "0.0.0.4", "0.0.0.6"); + _test_address_range("0.0.0.4", 29, "0.0.0.1", "0.0.0.3"); + _test_address_range("0.0.0.5", 29, "0.0.0.1", "0.0.0.4"); + _test_address_range("0.0.0.6", 29, "0.0.0.1", "0.0.0.5"); + _test_address_range("0.0.0.7", 29, "0.0.0.1", "0.0.0.5"); + _test_address_range("0.0.0.8", 29, "0.0.0.10", "0.0.0.14"); + _test_address_range("0.0.0.9", 29, "0.0.0.10", "0.0.0.14"); + _test_address_range("1.2.3.0", 29, "1.2.3.2", "1.2.3.6"); + _test_address_range("1.2.3.1", 29, "1.2.3.2", "1.2.3.6"); + _test_address_range("1.2.3.2", 29, "1.2.3.3", "1.2.3.6"); + _test_address_range("1.2.3.3", 29, "1.2.3.4", "1.2.3.6"); + _test_address_range("1.2.3.4", 29, "1.2.3.1", "1.2.3.3"); + _test_address_range("1.2.3.5", 29, "1.2.3.1", "1.2.3.4"); + _test_address_range("1.2.3.6", 29, "1.2.3.1", "1.2.3.5"); + _test_address_range("1.2.3.7", 29, "1.2.3.1", "1.2.3.5"); + _test_address_range("1.2.3.8", 29, "1.2.3.10", "1.2.3.14"); + _test_address_range("1.2.3.9", 29, "1.2.3.10", "1.2.3.14"); + _test_address_range("255.255.255.248", 29, "255.255.255.250", "255.255.255.254"); + _test_address_range("255.255.255.249", 29, "255.255.255.250", "255.255.255.254"); + _test_address_range("255.255.255.250", 29, "255.255.255.251", "255.255.255.254"); + _test_address_range("255.255.255.251", 29, "255.255.255.252", "255.255.255.254"); + _test_address_range("255.255.255.252", 29, "255.255.255.249", "255.255.255.251"); + _test_address_range("255.255.255.253", 29, "255.255.255.249", "255.255.255.252"); + _test_address_range("255.255.255.254", 29, "255.255.255.249", "255.255.255.253"); + _test_address_range("255.255.255.255", 29, "255.255.255.249", "255.255.255.253"); + + _test_address_range("1.2.3.1", 29, "1.2.3.2", "1.2.3.6"); + _test_address_range("1.2.3.1", 28, "1.2.3.3", "1.2.3.14"); + _test_address_range("1.2.3.1", 26, "1.2.3.8", "1.2.3.62"); + + _test_address_range("192.167.255.255", 24, "192.167.255.1", "192.167.255.245"); + _test_address_range("192.168.0.0", 24, "192.168.0.10", "192.168.0.254"); + _test_address_range("192.168.0.1", 24, "192.168.0.10", "192.168.0.254"); + _test_address_range("192.168.0.2", 24, "192.168.0.11", "192.168.0.254"); + _test_address_range("192.168.0.99", 24, "192.168.0.108", "192.168.0.254"); + _test_address_range("192.168.0.126", 24, "192.168.0.135", "192.168.0.254"); + _test_address_range("192.168.0.127", 24, "192.168.0.136", "192.168.0.254"); + _test_address_range("192.168.0.128", 24, "192.168.0.1", "192.168.0.119"); + _test_address_range("192.168.0.129", 24, "192.168.0.1", "192.168.0.120"); + _test_address_range("192.168.0.130", 24, "192.168.0.1", "192.168.0.121"); + _test_address_range("192.168.0.254", 24, "192.168.0.1", "192.168.0.245"); + _test_address_range("192.168.0.255", 24, "192.168.0.1", "192.168.0.245"); + _test_address_range("192.168.1.0", 24, "192.168.1.10", "192.168.1.254"); + _test_address_range("192.168.1.1", 24, "192.168.1.10", "192.168.1.254"); + _test_address_range("192.168.1.2", 24, "192.168.1.11", "192.168.1.254"); + _test_address_range("192.168.1.10", 24, "192.168.1.19", "192.168.1.254"); + _test_address_range("192.168.15.253", 24, "192.168.15.1", "192.168.15.244"); + _test_address_range("192.168.15.254", 24, "192.168.15.1", "192.168.15.245"); + _test_address_range("192.168.15.255", 24, "192.168.15.1", "192.168.15.245"); + _test_address_range("192.168.16.0", 24, "192.168.16.10", "192.168.16.254"); + _test_address_range("192.168.16.1", 24, "192.168.16.10", "192.168.16.254"); + + _test_address_range("192.167.255.255", 20, "192.167.255.1", "192.167.255.245"); + _test_address_range("192.168.0.0", 20, "192.168.0.10", "192.168.0.254"); + _test_address_range("192.168.0.1", 20, "192.168.0.10", "192.168.0.254"); + _test_address_range("192.168.0.2", 20, "192.168.0.11", "192.168.0.254"); + _test_address_range("192.168.0.126", 20, "192.168.0.135", "192.168.0.254"); + _test_address_range("192.168.0.127", 20, "192.168.0.136", "192.168.0.254"); + _test_address_range("192.168.0.128", 20, "192.168.0.1", "192.168.0.119"); + _test_address_range("192.168.0.129", 20, "192.168.0.1", "192.168.0.120"); + _test_address_range("192.168.0.130", 20, "192.168.0.1", "192.168.0.121"); + _test_address_range("192.168.0.254", 20, "192.168.0.1", "192.168.0.245"); + _test_address_range("192.168.0.255", 20, "192.168.0.1", "192.168.0.245"); + _test_address_range("192.168.1.0", 20, "192.168.1.10", "192.168.1.254"); + _test_address_range("192.168.1.1", 20, "192.168.1.10", "192.168.1.254"); + _test_address_range("192.168.1.2", 20, "192.168.1.11", "192.168.1.254"); + _test_address_range("192.168.1.10", 20, "192.168.1.19", "192.168.1.254"); + _test_address_range("192.168.15.253", 20, "192.168.15.1", "192.168.15.244"); + _test_address_range("192.168.15.254", 20, "192.168.15.1", "192.168.15.245"); + _test_address_range("192.168.15.255", 20, "192.168.15.1", "192.168.15.245"); + _test_address_range("192.168.16.0", 20, "192.168.16.10", "192.168.16.254"); + _test_address_range("192.168.16.1", 20, "192.168.16.10", "192.168.16.254"); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/dnsmasq/address-ranges", test_address_ranges); + + return g_test_run(); +} diff --git a/src/core/initrd/meson.build b/src/core/initrd/meson.build new file mode 100644 index 0000000..bd590e8 --- /dev/null +++ b/src/core/initrd/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnmi_core = static_library( + 'nmi-core', + sources: files( + 'nmi-cmdline-reader.c', + 'nmi-dt-reader.c', + 'nmi-ibft-reader.c', + ), + dependencies: core_default_dep, + c_args: daemon_c_flags, +) + +executable( + 'nm-initrd-generator', + 'nm-initrd-generator.c', + dependencies: core_default_dep, + c_args: daemon_c_flags, + link_with: [ + libNetworkManagerBase, + libnmi_core, + ], + link_args: ldflags_linker_script_binary, + link_depends: linker_script_binary, + install: true, + install_dir: nm_libexecdir, +) + +if enable_tests + subdir('tests') +endif diff --git a/src/core/initrd/nm-initrd-generator.c b/src/core/initrd/nm-initrd-generator.c new file mode 100644 index 0000000..654adf9 --- /dev/null +++ b/src/core/initrd/nm-initrd-generator.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" +#include "nm-core-utils.h" +#include "nm-core-internal.h" +#include "nm-keyfile/nm-keyfile-internal.h" +#include "nm-initrd-generator.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-config.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "initrd-generator: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static void +output_conn(gpointer key, gpointer value, gpointer user_data) +{ + const char * basename = key; + NMConnection * connection = value; + char * connections_dir = user_data; + nm_auto_unref_keyfile GKeyFile *file = NULL; + gs_free char * data = NULL; + gs_free_error GError *error = NULL; + gsize len; + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) + goto err_out; + + file = nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + if (file == NULL) + goto err_out; + + data = g_key_file_to_data(file, &len, &error); + if (!data) + goto err_out; + + if (connections_dir) { + gs_free char *filename = NULL; + gs_free char *full_filename = NULL; + + filename = nm_keyfile_utils_create_filename(basename, TRUE); + full_filename = g_build_filename(connections_dir, filename, NULL); + + if (!nm_utils_file_set_contents(full_filename, data, len, 0600, NULL, &error)) + goto err_out; + } else + g_print("\n*** Connection '%s' ***\n\n%s", basename, data); + + return; +err_out: + g_print("%s\n", error->message); +} + +#define DEFAULT_SYSFS_DIR "/sys" +#define DEFAULT_INITRD_DATA_DIR NMRUNDIR "/initrd" +#define DEFAULT_RUN_CONFIG_DIR NMRUNDIR "/conf.d" + +int +main(int argc, char *argv[]) +{ + GHashTable * connections; + gs_free char * connections_dir = NULL; + gs_free char * initrd_dir = NULL; + gs_free char * sysfs_dir = NULL; + gs_free char * run_config_dir = NULL; + gboolean dump_to_stdout = FALSE; + gs_strfreev char **remaining = NULL; + GOptionEntry option_entries[] = { + {"connections-dir", + 'c', + 0, + G_OPTION_ARG_FILENAME, + &connections_dir, + "Output connection directory", + NM_KEYFILE_PATH_NAME_RUN}, + {"initrd-data-dir", + 'i', + 0, + G_OPTION_ARG_FILENAME, + &initrd_dir, + "Output initrd data directory", + DEFAULT_INITRD_DATA_DIR}, + {"sysfs-dir", + 'd', + 0, + G_OPTION_ARG_FILENAME, + &sysfs_dir, + "The sysfs mount point", + DEFAULT_SYSFS_DIR}, + {"run-config-dir", + 'r', + 0, + G_OPTION_ARG_FILENAME, + &run_config_dir, + "Output config directory", + DEFAULT_RUN_CONFIG_DIR}, + {"stdout", + 's', + 0, + G_OPTION_ARG_NONE, + &dump_to_stdout, + "Dump connections to standard output", + NULL}, + {G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &remaining, NULL, NULL}, + {NULL}}; + nm_auto_free_option_context GOptionContext *option_context = NULL; + gs_free_error GError *error = NULL; + gs_free char * hostname = NULL; + int errsv; + gint64 carrier_timeout_sec = 0; + + option_context = g_option_context_new( + "-- [ip=...] [rd.route=...] [bridge=...] [bond=...] [team=...] [vlan=...] " + "[bootdev=...] [nameserver=...] [rd.peerdns=...] [rd.bootif=...] [BOOTIF=...] " + "[rd.znet=...] [rd.net.timeout.carrier=...] ... "); + + g_option_context_set_summary(option_context, "Generate early NetworkManager configuration."); + g_option_context_set_description( + option_context, + "This tool scans the command line for options relevant to network\n" + "configuration and creates configuration files for an early instance\n" + "of NetworkManager run from the initial ramdisk during early boot."); + g_option_context_add_main_entries(option_context, option_entries, GETTEXT_PACKAGE); + + if (!g_option_context_parse(option_context, &argc, &argv, &error)) { + _LOGW(LOGD_CORE, "%s", error->message); + return 1; + } + + if (!remaining) { + /* No arguments, no networking. Don't bother. */ + return 0; + } + + if (!connections_dir) + connections_dir = g_strdup(NM_KEYFILE_PATH_NAME_RUN); + if (!sysfs_dir) + sysfs_dir = g_strdup(DEFAULT_SYSFS_DIR); + if (!initrd_dir) + initrd_dir = g_strdup(DEFAULT_INITRD_DATA_DIR); + if (!run_config_dir) + run_config_dir = g_strdup(DEFAULT_RUN_CONFIG_DIR); + + connections = nmi_cmdline_reader_parse(sysfs_dir, + (const char *const *) remaining, + &hostname, + &carrier_timeout_sec); + + if (dump_to_stdout) { + nm_clear_g_free(&connections_dir); + nm_clear_g_free(&initrd_dir); + nm_clear_g_free(&run_config_dir); + if (hostname) + g_print("\n*** Hostname '%s' ***\n", hostname); + if (carrier_timeout_sec != 0) + g_print("\n*** Carrier Wait Timeout %" G_GINT64_FORMAT " sec ***\n", + carrier_timeout_sec); + } else { + if (g_mkdir_with_parents(connections_dir, 0755) != 0) { + errsv = errno; + _LOGW(LOGD_CORE, "%s: %s", connections_dir, nm_strerror_native(errsv)); + return 1; + } + if (g_mkdir_with_parents(initrd_dir, 0755) != 0) { + errsv = errno; + _LOGW(LOGD_CORE, "%s: %s", initrd_dir, nm_strerror_native(errsv)); + return 1; + } + if (g_mkdir_with_parents(run_config_dir, 0755) != 0) { + errsv = errno; + _LOGW(LOGD_CORE, "%s: %s", run_config_dir, nm_strerror_native(errsv)); + return 1; + } + + if (hostname) { + gs_free char *hostname_file = NULL; + gs_free char *data = NULL; + + hostname_file = g_strdup_printf("%s/hostname", initrd_dir); + data = g_strdup_printf("%s\n", hostname); + + if (!g_file_set_contents(hostname_file, data, strlen(data), &error)) { + _LOGW(LOGD_CORE, "%s: %s", hostname_file, error->message); + return 1; + } + } + if (carrier_timeout_sec != 0) { + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_free char * filename = NULL; + + keyfile = g_key_file_new(); + g_key_file_set_list_separator(keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR); + filename = g_strdup_printf("%s/15-carrier-timeout.conf", run_config_dir); + + g_key_file_set_value(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE "-15-carrier-timeout", + NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, + "*"); + g_key_file_set_int64(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE "-15-carrier-timeout", + NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, + carrier_timeout_sec * 1000); + + if (!g_key_file_save_to_file(keyfile, filename, &error)) { + _LOGW(LOGD_CORE, "%s: %s", filename, error->message); + return 1; + } + } + } + + g_hash_table_foreach(connections, output_conn, connections_dir); + g_hash_table_destroy(connections); + + return 0; +} diff --git a/src/core/initrd/nm-initrd-generator.h b/src/core/initrd/nm-initrd-generator.h new file mode 100644 index 0000000..56dcfd6 --- /dev/null +++ b/src/core/initrd/nm-initrd-generator.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014, 2018 Red Hat, Inc. + */ + +#ifndef __NM_INITRD_GENERATOR_H__ +#define __NM_INITRD_GENERATOR_H__ + +#include "nm-connection.h" +#include "nm-utils.h" + +#define NMI_WAIT_DEVICE_TIMEOUT_MS 60000 + +static inline int +get_ip_address_family(const char *str, gboolean with_prefix) +{ + int addr_family; + + if (!str) + return AF_UNSPEC; + + if (with_prefix) { + if (nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, str, &addr_family, NULL, NULL)) + return addr_family; + } else { + if (nm_utils_parse_inaddr_bin(AF_UNSPEC, str, &addr_family, NULL)) + return addr_family; + } + + return AF_UNSPEC; +} + +GHashTable *nmi_ibft_read(const char *sysfs_dir); + +gboolean +nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, GError **error); + +NMConnection *nmi_dt_reader_parse(const char *sysfs_dir); + +GHashTable *nmi_cmdline_reader_parse(const char * sysfs_dir, + const char *const *argv, + char ** hostname, + gint64 * carrier_timeout_sec); + +#endif /* __NM_INITRD_GENERATOR_H__ */ diff --git a/src/core/initrd/nmi-cmdline-reader.c b/src/core/initrd/nmi-cmdline-reader.c new file mode 100644 index 0000000..662b980 --- /dev/null +++ b/src/core/initrd/nmi-cmdline-reader.c @@ -0,0 +1,1255 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-core-internal.h" +#include "nm-initrd-generator.h" +#include "systemd/nm-sd-utils-shared.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "cmdline-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +typedef struct { + GHashTable * hash; + GPtrArray * array; + GPtrArray * vlan_parents; + GHashTable * explicit_ip_connections; + NMConnection *bootdev_connection; /* connection for bootdev=$ifname */ + NMConnection *default_connection; /* connection not bound to any ifname */ + char * hostname; + + /* Parameters to be set for all connections */ + gboolean ignore_auto_dns; + int dhcp_timeout; + char * dhcp4_vci; + + gint64 carrier_timeout_sec; +} Reader; + +static Reader * +reader_new(void) +{ + Reader *reader; + + reader = g_slice_new(Reader); + *reader = (Reader){ + .hash = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref), + .explicit_ip_connections = + g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL), + .vlan_parents = g_ptr_array_new_with_free_func(g_free), + .array = g_ptr_array_new(), + }; + + return reader; +} + +static GHashTable * +reader_destroy(Reader *reader, gboolean free_hash) +{ + gs_unref_hashtable GHashTable *hash = NULL; + + g_ptr_array_unref(reader->array); + g_ptr_array_unref(reader->vlan_parents); + g_hash_table_unref(reader->explicit_ip_connections); + hash = g_steal_pointer(&reader->hash); + nm_clear_g_free(&reader->hostname); + nm_clear_g_free(&reader->dhcp4_vci); + nm_g_slice_free(reader); + if (!free_hash) + return g_steal_pointer(&hash); + return NULL; +} + +static NMConnection * +reader_add_connection(Reader *reader, const char *name, NMConnection *connection_take) +{ + char *name_dup; + + name_dup = g_strdup(name); + if (g_hash_table_insert(reader->hash, name_dup, connection_take)) + g_ptr_array_add(reader->array, name_dup); + + return connection_take; +} + +/* Returns a new connection owned by the reader */ +static NMConnection * +reader_create_connection(Reader * reader, + const char * basename, + const char * id, + const char * ifname, + const char * mac, + const char * type_name, + NMConnectionMultiConnect multi_connect) +{ + NMConnection *connection; + NMSetting * setting; + + connection = reader_add_connection(reader, basename, nm_simple_connection_new()); + + /* Start off assuming dynamic IP configurations. */ + + setting = nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, setting); + g_object_set(setting, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + reader->ignore_auto_dns, + NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, + reader->dhcp_timeout, + NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, + reader->dhcp4_vci, + NULL); + + setting = nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, setting); + g_object_set(setting, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + reader->ignore_auto_dns, + NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, + reader->dhcp_timeout, + NULL); + + setting = nm_setting_connection_new(); + nm_connection_add_setting(connection, setting); + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_INTERFACE_NAME, + ifname, + NM_SETTING_CONNECTION_TYPE, + type_name, + NM_SETTING_CONNECTION_MULTI_CONNECT, + multi_connect, + NULL); + + if (nm_streq0(type_name, NM_SETTING_INFINIBAND_SETTING_NAME)) { + setting = nm_setting_infiniband_new(); + nm_connection_add_setting(connection, setting); + g_object_set(setting, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL); + } + + if (mac) { + if (nm_streq0(type_name, NM_SETTING_INFINIBAND_SETTING_NAME)) { + setting = (NMSetting *) nm_connection_get_setting_infiniband(connection); + g_object_set(setting, NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL); + } else { + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + g_object_set(setting, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL); + } + } + + return connection; +} + +static NMConnection * +reader_get_default_connection(Reader *reader) +{ + NMConnection *con; + + if (!reader->default_connection) { + con = reader_create_connection(reader, + "default_connection", + "Wired Connection", + NULL, + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE); + nm_connection_add_setting(con, nm_setting_wired_new()); + reader->default_connection = con; + } + return reader->default_connection; +} + +static NMConnection * +reader_get_connection(Reader * reader, + const char *iface_spec, + const char *type_name, + gboolean create_if_missing) +{ + NMConnection *connection = NULL; + NMSetting * setting; + const char * ifname = NULL; + gs_free char *mac = NULL; + + if (iface_spec) { + if (nm_utils_is_valid_iface_name(iface_spec, NULL)) + ifname = iface_spec; + else { + mac = nm_utils_hwaddr_canonical(iface_spec, -1); + if (!mac) + _LOGW(LOGD_CORE, "invalid interface '%s'", iface_spec); + } + } + + if (!ifname && !mac) { + NMConnection * candidate; + NMSettingConnection *s_con; + guint i; + + /* + * If ifname was not given, we'll match the connection by type. + * If the type was not given either, then we're happy with any connection but slaves. + * This is so that things like "bond=bond0:eth1,eth2 nameserver=1.3.3.7 end up + * slapping the nameserver to the most reasonable connection (bond0). + */ + for (i = 0; i < reader->array->len; i++) { + candidate = g_hash_table_lookup(reader->hash, reader->array->pdata[i]); + s_con = nm_connection_get_setting_connection(candidate); + + if (type_name == NULL && nm_setting_connection_get_master(s_con) == NULL) { + connection = candidate; + break; + } + + if (type_name != NULL + && nm_streq(nm_setting_connection_get_connection_type(s_con), type_name)) { + connection = candidate; + break; + } + } + } else + connection = g_hash_table_lookup(reader->hash, (gpointer) ifname ?: mac); + + if (!connection) { + if (!create_if_missing) + return NULL; + + if (!type_name) { + if (NM_STR_HAS_PREFIX(ifname, "ib") + || (mac && nm_utils_hwaddr_valid(mac, INFINIBAND_ALEN))) + type_name = NM_SETTING_INFINIBAND_SETTING_NAME; + else + type_name = NM_SETTING_WIRED_SETTING_NAME; + } + + connection = reader_create_connection(reader, + ifname ?: mac, + ifname ?: (mac ?: "Wired Connection"), + ifname, + mac, + type_name, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + } + setting = (NMSetting *) nm_connection_get_setting_connection(connection); + + if (type_name) { + g_object_set(setting, NM_SETTING_CONNECTION_TYPE, type_name, NULL); + if (!nm_connection_get_setting_by_name(connection, type_name)) { + setting = g_object_new(nm_setting_lookup_type(type_name), NULL); + nm_connection_add_setting(connection, setting); + } + } + + return connection; +} + +static char * +get_word(char **argument, const char separator) +{ + char *word; + int nest = 0; + + if (*argument == NULL) + return NULL; + + if (**argument == '[') { + nest++; + (*argument)++; + } + + word = *argument; + + while (**argument != '\0') { + if (nest && **argument == ']') { + **argument = '\0'; + (*argument)++; + nest--; + continue; + } + + if (nest == 0 && **argument == separator) { + **argument = '\0'; + (*argument)++; + break; + } + (*argument)++; + } + + return *word ? word : NULL; +} + +static void +connection_set(NMConnection *connection, + const char * setting_name, + const char * property, + const char * value) +{ + NMSetting * setting; + GType setting_type; + nm_auto_unref_gtypeclass GObjectClass *object_class = NULL; + GParamSpec * spec; + + setting_type = nm_setting_lookup_type(setting_name); + object_class = g_type_class_ref(setting_type); + spec = g_object_class_find_property(object_class, property); + nm_assert(spec); + + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (!setting) { + setting = g_object_new(setting_type, NULL); + nm_connection_add_setting(connection, setting); + } + + if (G_IS_PARAM_SPEC_UINT(spec)) { + guint v; + + v = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT, 0); + if (errno || !nm_g_object_set_property_uint(G_OBJECT(setting), property, v, NULL)) { + _LOGW(LOGD_CORE, + "Could not set property '%s.%s' to '%s'", + setting_name, + property, + value); + } + } else if (G_IS_PARAM_SPEC_STRING(spec)) + g_object_set(setting, property, value, NULL); + else + _LOGW(LOGD_CORE, "Don't know how to set '%s' of %s", property, setting_name); +} + +static void +reader_read_all_connections_from_fw(Reader *reader, const char *sysfs_dir) +{ + gs_unref_hashtable GHashTable *ibft = NULL; + NMConnection * dt_connection; + const char * mac; + GHashTable * nic; + const char * index; + GError * error = NULL; + guint i, length; + gs_free const char ** keys = NULL; + + ibft = nmi_ibft_read(sysfs_dir); + keys = nm_utils_strdict_get_keys(ibft, TRUE, &length); + + for (i = 0; i < length; i++) { + gs_unref_object NMConnection *connection = NULL; + gs_free char * name = NULL; + + mac = keys[i]; + nic = g_hash_table_lookup(ibft, mac); + connection = nm_simple_connection_new(); + index = g_hash_table_lookup(nic, "index"); + if (!index) { + _LOGW(LOGD_CORE, "Ignoring an iBFT entry without an index"); + continue; + } + + if (!nmi_ibft_update_connection_from_nic(connection, nic, &error)) { + _LOGW(LOGD_CORE, "Unable to merge iBFT configuration: %s", error->message); + g_error_free(error); + continue; + } + + name = g_strdup_printf("ibft%s", index); + reader_add_connection(reader, name, g_steal_pointer(&connection)); + } + + dt_connection = nmi_dt_reader_parse(sysfs_dir); + if (dt_connection) + reader_add_connection(reader, "ofw", dt_connection); +} + +static void +reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL; + gs_unref_hashtable GHashTable *ibft = NULL; + const char * tmp; + const char * tmp2; + const char * kind = NULL; + const char * client_ip = NULL; + const char * peer = NULL; + const char * gateway_ip = NULL; + const char * netmask = NULL; + const char * client_hostname = NULL; + const char * iface_spec = NULL; + const char * mtu = NULL; + const char * macaddr = NULL; + int client_ip_family = AF_UNSPEC; + int client_ip_prefix = -1; + const char * dns[2] = { + 0, + }; + int dns_addr_family[2] = { + 0, + }; + int i; + GError *error = NULL; + + if (!*argument) + return; + + tmp = get_word(&argument, ':'); + if (!*argument) { + /* ip={dhcp|on|any|dhcp6|auto6|link6|ibft} */ + kind = tmp; + } else { + tmp2 = get_word(&argument, ':'); + if (NM_IN_STRSET(tmp2, + "none", + "off", + "dhcp", + "on" + "any", + "dhcp6", + "auto", + "auto6", + "link6", + "ibft")) { + /* :{none|off|dhcp|on|any|dhcp6|auto|auto6|link6|ibft} */ + iface_spec = tmp; + kind = tmp2; + } else { + /* :[]:::: */ + client_ip = tmp; + if (client_ip) { + client_ip_family = get_ip_address_family(client_ip, TRUE); + if (client_ip_family == AF_UNSPEC) { + _LOGW(LOGD_CORE, "Invalid IP address '%s'.", client_ip); + return; + } + } + + peer = tmp2; + gateway_ip = get_word(&argument, ':'); + netmask = get_word(&argument, ':'); + client_hostname = get_word(&argument, ':'); + iface_spec = get_word(&argument, ':'); + kind = get_word(&argument, ':'); + } + + if (client_hostname && !nm_sd_hostname_is_valid(client_hostname, FALSE)) + client_hostname = NULL; + + if (client_hostname) { + g_free(reader->hostname); + reader->hostname = g_strdup(client_hostname); + } + + tmp = get_word(&argument, ':'); + dns_addr_family[0] = get_ip_address_family(tmp, FALSE); + if (dns_addr_family[0] != AF_UNSPEC) { + dns[0] = tmp; + dns[1] = get_word(&argument, ':'); + dns_addr_family[1] = get_ip_address_family(dns[1], FALSE); + if (*argument) + _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument); + } else { + mtu = tmp; + macaddr = argument; + } + } + + if (iface_spec == NULL && NM_IN_STRSET(kind, "fw", "ibft")) { + reader_read_all_connections_from_fw(reader, sysfs_dir); + return; + } + + /* Parsing done, construct the NMConnection. */ + if (iface_spec) + connection = reader_get_connection(reader, iface_spec, NULL, TRUE); + else + connection = reader_get_default_connection(reader); + + g_hash_table_add(reader->explicit_ip_connections, g_object_ref(connection)); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + s_ip6 = nm_connection_get_setting_ip6_config(connection); + + if (netmask && *netmask) { + gboolean is_ipv4 = client_ip_family == AF_INET; + NMIPAddr addr; + + if (is_ipv4 && nm_utils_parse_inaddr_bin(AF_INET, netmask, NULL, &addr)) + client_ip_prefix = nm_utils_ip4_netmask_to_prefix(addr.addr4); + else + client_ip_prefix = _nm_utils_ascii_str_to_int64(netmask, 10, 0, is_ipv4 ? 32 : 128, -1); + + if (client_ip_prefix == -1) + _LOGW(LOGD_CORE, "Invalid IP mask: %s", netmask); + } + + /* Static IP configuration might be present. */ + if (client_ip && *client_ip) { + NMIPAddress *address = NULL; + NMIPAddr addr; + + if (nm_utils_parse_inaddr_prefix_bin(client_ip_family, + client_ip, + NULL, + &addr, + client_ip_prefix == -1 ? &client_ip_prefix : NULL)) { + if (client_ip_prefix == -1) { + switch (client_ip_family) { + case AF_INET: + client_ip_prefix = _nm_utils_ip4_get_default_prefix(addr.addr4); + break; + case AF_INET6: + client_ip_prefix = 64; + break; + } + } + + address = nm_ip_address_new_binary(client_ip_family, + &addr.addr_ptr, + client_ip_prefix, + &error); + if (!address) { + _LOGW(LOGD_CORE, "Invalid address '%s': %s", client_ip, error->message); + g_clear_error(&error); + } + } else + nm_assert_not_reached(); + + if (address) { + switch (client_ip_family) { + case AF_INET: + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + nm_setting_ip_config_add_address(s_ip4, address); + break; + case AF_INET6: + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + nm_setting_ip_config_add_address(s_ip6, address); + break; + default: + nm_assert_not_reached(); + break; + } + nm_ip_address_unref(address); + } + } + + /* Dynamic IP configuration configured explicitly. */ + if (NM_IN_STRSET(kind, "none", "off")) { + if (nm_setting_ip_config_get_num_addresses(s_ip6) == 0) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + } + if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + } + } else if (nm_streq0(kind, "dhcp")) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + if (nm_setting_ip_config_get_num_addresses(s_ip6) == 0) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + } + } else if (NM_IN_STRSET(kind, "auto6", "dhcp6")) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE, NULL); + if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + } + } else if (nm_streq0(kind, "link6")) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + } + } else if (nm_streq0(kind, "ibft")) { + NMSettingWired *s_wired; + const char * mac = NULL; + const char * ifname; + gs_free char * mac_free = NULL; + gs_free char * address_path = NULL; + GHashTable * nic = NULL; + + if ((s_wired = nm_connection_get_setting_wired(connection)) + && (mac = nm_setting_wired_get_mac_address(s_wired))) { + /* got mac from the connection */ + } else if ((ifname = nm_connection_get_interface_name(connection))) { + /* read it from sysfs */ + address_path = g_build_filename(sysfs_dir, "class", "net", ifname, "address", NULL); + if (g_file_get_contents(address_path, &mac_free, NULL, &error)) { + g_strchomp(mac_free); + mac = mac_free; + } else { + _LOGW(LOGD_CORE, "Can't get a MAC address for %s: %s", ifname, error->message); + g_clear_error(&error); + } + } + + if (mac) { + gs_free char *mac_up = NULL; + + mac_up = g_ascii_strup(mac, -1); + ibft = nmi_ibft_read(sysfs_dir); + nic = g_hash_table_lookup(ibft, mac_up); + if (!nic) + _LOGW(LOGD_CORE, "No iBFT NIC for %s (%s)", iface_spec, mac_up); + } + + if (nic) { + if (!nmi_ibft_update_connection_from_nic(connection, nic, &error)) { + _LOGW(LOGD_CORE, "Unable to merge iBFT configuration: %s", error->message); + g_clear_error(&error); + } + } + } + + if (peer && *peer) + _LOGW(LOGD_CORE, "Ignoring peer: %s (not implemented)\n", peer); + + if (gateway_ip && *gateway_ip) { + switch (get_ip_address_family(gateway_ip, FALSE)) { + case AF_INET: + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL); + break; + case AF_INET6: + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL); + break; + default: + _LOGW(LOGD_CORE, "Invalid gateway: %s", gateway_ip); + break; + } + } + + if (client_hostname && *client_hostname) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, client_hostname, NULL); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, client_hostname, NULL); + } + + for (i = 0; i < 2; i++) { + if (dns_addr_family[i] == AF_UNSPEC) + break; + if (nm_utils_ipaddr_is_valid(dns_addr_family[i], dns[i])) { + switch (dns_addr_family[i]) { + case AF_INET: + nm_setting_ip_config_add_dns(s_ip4, dns[i]); + break; + case AF_INET6: + nm_setting_ip_config_add_dns(s_ip6, dns[i]); + break; + default: + _LOGW(LOGD_CORE, "Unknown address family: %s", dns[i]); + break; + } + } else { + _LOGW(LOGD_CORE, "Invalid name server: %s", dns[i]); + } + } + + if (mtu && *mtu) + connection_set(connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu); + + if (macaddr && *macaddr) + connection_set(connection, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + macaddr); +} + +static void +reader_parse_master(Reader *reader, char *argument, const char *type_name, const char *default_name) +{ + NMConnection * connection; + NMSettingConnection *s_con; + gs_free char * master_to_free = NULL; + const char * master; + char * slaves; + const char * slave; + char * opts; + const char * mtu = NULL; + + master = get_word(&argument, ':'); + if (!master) + master = master_to_free = g_strdup_printf("%s0", default_name ?: type_name); + slaves = get_word(&argument, ':'); + + connection = reader_get_connection(reader, master, type_name, TRUE); + s_con = nm_connection_get_setting_connection(connection); + master = nm_setting_connection_get_uuid(s_con); + + if (nm_streq(type_name, NM_SETTING_BRIDGE_SETTING_NAME)) { + NMSettingBridge *s_bridge = nm_connection_get_setting_bridge(connection); + + /* Avoid the forwarding delay */ + g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL); + } else if (nm_streq(type_name, NM_SETTING_BOND_SETTING_NAME)) { + NMSettingBond *s_bond = nm_connection_get_setting_bond(connection); + + opts = get_word(&argument, ':'); + while (opts && *opts) { + gs_free_error GError *error = NULL; + char * opt; + const char * opt_name; + + opt = get_word(&opts, ','); + opt_name = get_word(&opt, '='); + + if (!_nm_setting_bond_validate_option(opt_name, opt, &error)) { + _LOGW(LOGD_CORE, + "Ignoring invalid bond option: %s%s%s = %s%s%s: %s", + NM_PRINT_FMT_QUOTE_STRING(opt_name), + NM_PRINT_FMT_QUOTE_STRING(opt), + error->message); + continue; + } + nm_setting_bond_add_option(s_bond, opt_name, opt); + } + + mtu = get_word(&argument, ':'); + } + + do { + slave = get_word(&slaves, ','); + if (slave == NULL) + slave = "eth0"; + + connection = reader_get_connection(reader, slave, NULL, TRUE); + s_con = nm_connection_get_setting_connection(connection); + g_object_set(s_con, + NM_SETTING_CONNECTION_SLAVE_TYPE, + type_name, + NM_SETTING_CONNECTION_MASTER, + master, + NULL); + if (mtu) + connection_set(connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu); + } while (slaves && *slaves != '\0'); + + if (argument && *argument) + _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument); +} + +static void +reader_add_routes(Reader *reader, GPtrArray *array) +{ + guint i; + + for (i = 0; i < array->len; i++) { + NMConnection * connection = NULL; + const char * net; + const char * gateway; + const char * interface; + int family = AF_UNSPEC; + NMIPAddr net_addr = {}; + NMIPAddr gateway_addr = {}; + int net_prefix = -1; + NMIPRoute * route; + NMSettingIPConfig *s_ip; + char * argument; + gs_free_error GError *error = NULL; + + argument = array->pdata[i]; + net = get_word(&argument, ':'); + gateway = get_word(&argument, ':'); + interface = get_word(&argument, ':'); + + if (interface) + connection = reader_get_connection(reader, interface, NULL, TRUE); + if (!connection) + connection = reader->bootdev_connection; + if (!connection) + connection = reader_get_connection(reader, interface, NULL, FALSE); + if (!connection) + connection = reader_get_default_connection(reader); + + if (net && *net) { + if (!nm_utils_parse_inaddr_prefix_bin(family, net, &family, &net_addr, &net_prefix)) { + _LOGW(LOGD_CORE, "Unrecognized address: %s", net); + continue; + } + } + + if (gateway && *gateway) { + if (!nm_utils_parse_inaddr_bin(family, gateway, &family, &gateway_addr)) { + _LOGW(LOGD_CORE, "Unrecognized address: %s", gateway); + continue; + } + } + + switch (family) { + case AF_INET: + s_ip = nm_connection_get_setting_ip4_config(connection); + if (net_prefix == -1) + net_prefix = 32; + break; + case AF_INET6: + s_ip = nm_connection_get_setting_ip6_config(connection); + if (net_prefix == -1) + net_prefix = 128; + break; + default: + _LOGW(LOGD_CORE, "Unknown address family: %s", net); + continue; + } + + route = nm_ip_route_new_binary(family, + &net_addr.addr_ptr, + net_prefix, + &gateway_addr.addr_ptr, + -1, + &error); + if (!route) { + g_warning("Invalid route '%s via %s': %s\n", net, gateway, error->message); + continue; + } + + nm_setting_ip_config_add_route(s_ip, route); + nm_ip_route_unref(route); + } +} + +static void +reader_parse_vlan(Reader *reader, char *argument) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + const char * vlan; + const char * phy; + const char * vlanid; + + vlan = get_word(&argument, ':'); + phy = get_word(&argument, ':'); + + for (vlanid = vlan + strlen(vlan); vlanid > vlan; vlanid--) { + if (!g_ascii_isdigit(*(vlanid - 1))) + break; + } + + connection = reader_get_connection(reader, vlan, NM_SETTING_VLAN_SETTING_NAME, TRUE); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_object_set(s_vlan, + NM_SETTING_VLAN_PARENT, + phy, + NM_SETTING_VLAN_ID, + (guint) _nm_utils_ascii_str_to_int64(vlanid, 10, 0, G_MAXUINT, G_MAXUINT), + NULL); + + if (argument && *argument) + _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument); + + if (!nm_strv_ptrarray_contains(reader->vlan_parents, phy)) + g_ptr_array_add(reader->vlan_parents, g_strdup(phy)); +} + +static void +reader_parse_rd_znet(Reader *reader, char *argument, gboolean net_ifnames) +{ + const char * nettype; + const char * subchannels[4] = {0, 0, 0, 0}; + const char * tmp; + gs_free char * ifname = NULL; + const char * prefix; + NMConnection * connection; + NMSettingWired *s_wired; + static int count_ctc = 0; + static int count_eth = 0; + int index; + + nettype = get_word(&argument, ','); + subchannels[0] = get_word(&argument, ','); + subchannels[1] = get_word(&argument, ','); + + if (nm_streq0(nettype, "ctc")) { + if (net_ifnames == TRUE) { + prefix = "sl"; + } else { + prefix = "ctc"; + index = count_ctc++; + } + } else { + subchannels[2] = get_word(&argument, ','); + if (net_ifnames == TRUE) { + prefix = "en"; + } else { + prefix = "eth"; + index = count_eth++; + } + } + + if (net_ifnames == TRUE) { + const char *bus_id; + size_t bus_id_len; + size_t bus_id_start; + + /* The following logic is taken from names_ccw() in systemd/src/udev/udev-builtin-net_id.c */ + bus_id = subchannels[0]; + bus_id_len = strlen(bus_id); + bus_id_start = strspn(bus_id, ".0"); + bus_id += bus_id_start < bus_id_len ? bus_id_start : bus_id_len - 1; + + ifname = g_strdup_printf("%sc%s", prefix, bus_id); + } else { + ifname = g_strdup_printf("%s%d", prefix, index); + } + + connection = reader_get_connection(reader, ifname, NM_SETTING_WIRED_SETTING_NAME, FALSE); + if (!connection) + return; + s_wired = nm_connection_get_setting_wired(connection); + g_object_set(s_wired, + NM_SETTING_WIRED_S390_NETTYPE, + nettype, + NM_SETTING_WIRED_S390_SUBCHANNELS, + &subchannels, + NULL); + + while ((tmp = get_word(&argument, ',')) != NULL) { + char *val; + + val = strchr(tmp, '='); + if (val) { + gs_free char *key = NULL; + + key = g_strndup(tmp, val - tmp); + val[0] = '\0'; + val++; + nm_setting_wired_add_s390_option(s_wired, key, val); + } + } +} + +static void +_normalize_conn(gpointer key, gpointer value, gpointer user_data) +{ + NMConnection *connection = value; + + nm_connection_normalize(connection, NULL, NULL, NULL); +} + +static void +reader_add_nameservers(Reader *reader, GPtrArray *nameservers) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip; + GHashTableIter iter; + int addr_family; + const char * ns; + guint i; + + for (i = 0; i < nameservers->len; i++) { + ns = nameservers->pdata[i]; + addr_family = get_ip_address_family(ns, FALSE); + if (addr_family == AF_UNSPEC) { + _LOGW(LOGD_CORE, "Unknown address family: %s", ns); + continue; + } + + g_hash_table_iter_init(&iter, reader->hash); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &connection)) { + switch (addr_family) { + case AF_INET: + s_ip = nm_connection_get_setting_ip4_config(connection); + if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + continue; + break; + case AF_INET6: + s_ip = nm_connection_get_setting_ip6_config(connection); + if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + continue; + break; + default: + nm_assert_not_reached(); + continue; + } + + nm_setting_ip_config_add_dns(s_ip, ns); + } + } +} + +static void +connection_set_needed(NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + if (!nm_streq0(nm_setting_connection_get_connection_type(s_con), NM_SETTING_WIRED_SETTING_NAME)) + return; + + g_object_set(s_con, + NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, + (int) NMI_WAIT_DEVICE_TIMEOUT_MS, + NULL); +} + +static void +connection_set_needed_cb(gpointer key, gpointer value, gpointer user_data) +{ + connection_set_needed(value); +} + +GHashTable * +nmi_cmdline_reader_parse(const char * sysfs_dir, + const char *const *argv, + char ** hostname, + gint64 * carrier_timeout_sec) +{ + Reader * reader; + const char * tag; + gboolean ignore_bootif = FALSE; + gboolean neednet = FALSE; + gs_free char * bootif_val = NULL; + gs_free char * bootdev = NULL; + gboolean net_ifnames = TRUE; + gs_unref_ptrarray GPtrArray *nameservers = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *znets = NULL; + int i; + + reader = reader_new(); + + for (i = 0; argv[i]; i++) { + gs_free char *argument_clone = NULL; + char * argument; + + argument_clone = g_strdup(argv[i]); + argument = argument_clone; + + tag = get_word(&argument, '='); + + if (nm_streq(tag, "net.ifnames")) + net_ifnames = !nm_streq(argument, "0"); + else if (nm_streq(tag, "rd.peerdns")) + reader->ignore_auto_dns = !_nm_utils_ascii_str_to_bool(argument, TRUE); + else if (nm_streq(tag, "rd.net.timeout.dhcp")) { + reader->dhcp_timeout = _nm_utils_ascii_str_to_int64(argument, 10, 0, G_MAXINT32, 0); + } else if (nm_streq(tag, "rd.net.dhcp.vendor-class")) { + if (nm_utils_validate_dhcp4_vendor_class_id(argument, NULL)) + nm_utils_strdup_reset(&reader->dhcp4_vci, argument); + } else if (nm_streq(tag, "rd.net.timeout.carrier")) { + reader->carrier_timeout_sec = + _nm_utils_ascii_str_to_int64(argument, 10, 0, G_MAXINT32, 0); + } + } + + for (i = 0; argv[i]; i++) { + gs_free char *argument_clone = NULL; + char * argument; + char * word; + + argument_clone = g_strdup(argv[i]); + argument = argument_clone; + + tag = get_word(&argument, '='); + if (nm_streq(tag, "ip")) + reader_parse_ip(reader, sysfs_dir, argument); + else if (nm_streq(tag, "rd.route")) { + if (!routes) + routes = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(routes, g_strdup(argument)); + } else if (nm_streq(tag, "bridge")) + reader_parse_master(reader, argument, NM_SETTING_BRIDGE_SETTING_NAME, "br"); + else if (nm_streq(tag, "bond")) + reader_parse_master(reader, argument, NM_SETTING_BOND_SETTING_NAME, NULL); + else if (nm_streq(tag, "team")) + reader_parse_master(reader, argument, NM_SETTING_TEAM_SETTING_NAME, NULL); + else if (nm_streq(tag, "vlan")) + reader_parse_vlan(reader, argument); + else if (nm_streq(tag, "bootdev")) { + g_free(bootdev); + bootdev = g_strdup(argument); + } else if (nm_streq(tag, "nameserver")) { + word = get_word(&argument, '\0'); + if (word) { + if (!nameservers) + nameservers = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(nameservers, g_strdup(word)); + } + if (argument && *argument) + _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument); + } else if (nm_streq(tag, "rd.iscsi.ibft") && _nm_utils_ascii_str_to_bool(argument, TRUE)) + reader_read_all_connections_from_fw(reader, sysfs_dir); + else if (nm_streq(tag, "rd.bootif")) + ignore_bootif = !_nm_utils_ascii_str_to_bool(argument, TRUE); + else if (nm_streq(tag, "rd.neednet")) + neednet = _nm_utils_ascii_str_to_bool(argument, TRUE); + else if (nm_streq(tag, "rd.znet")) { + if (!znets) + znets = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(znets, g_strdup(argument)); + } else if (g_ascii_strcasecmp(tag, "BOOTIF") == 0) { + nm_clear_g_free(&bootif_val); + bootif_val = g_strdup(argument); + } + } + + for (i = 0; i < reader->vlan_parents->len; i++) { + NMConnection * connection; + NMSettingIPConfig *s_ip; + + /* Disable IP configuration for parent connections of VLANs, + * unless those interfaces were explicitly configured otherwise. */ + + connection = reader_get_connection(reader, reader->vlan_parents->pdata[i], NULL, TRUE); + if (!g_hash_table_contains(reader->explicit_ip_connections, connection)) { + s_ip = nm_connection_get_setting_ip4_config(connection); + if (s_ip) { + g_object_set(s_ip, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + } + + s_ip = nm_connection_get_setting_ip6_config(connection); + if (s_ip) { + g_object_set(s_ip, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + } + } + } + + if (ignore_bootif) + nm_clear_g_free(&bootif_val); + if (bootif_val) { + NMConnection * connection; + NMSettingWired *s_wired; + const char * bootif = bootif_val; + char prefix[4]; + + if (!nm_utils_hwaddr_valid(bootif, ETH_ALEN)) { + strncpy(prefix, bootif, 3); + prefix[3] = '\0'; + + if (NM_IN_STRSET(prefix, "01-", "01:", "00-", "00:") + && nm_utils_hwaddr_valid(&bootif[3], ETH_ALEN)) { + /* + * BOOTIF MAC address can be prefixed with a hardware type identifier. + * "01" stays for "wired", "00" is also accepted as it means "undefined". + * No others are known. + */ + bootif += 3; + } + } + + connection = reader_get_connection(reader, NULL, NM_SETTING_WIRED_SETTING_NAME, FALSE); + if (!connection) + connection = reader_get_default_connection(reader); + + s_wired = nm_connection_get_setting_wired(connection); + + if (nm_connection_get_interface_name(connection) + || (nm_setting_wired_get_mac_address(s_wired) + && !nm_utils_hwaddr_matches(nm_setting_wired_get_mac_address(s_wired), + -1, + bootif, + -1))) { + connection = reader_create_connection(reader, + "bootif_connection", + "BOOTIF Connection", + NULL, + bootif, + NM_SETTING_WIRED_SETTING_NAME, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + } else { + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, bootif, NULL); + } + } + + if (bootdev) { + NMConnection *connection; + + connection = reader_get_connection(reader, bootdev, NULL, TRUE); + reader->bootdev_connection = connection; + connection_set_needed(connection); + } + + if (neednet) { + if (g_hash_table_size(reader->hash) == 0) { + /* Make sure there's some connection. */ + reader_get_default_connection(reader); + } + + g_hash_table_foreach(reader->hash, connection_set_needed_cb, NULL); + } + + if (routes) + reader_add_routes(reader, routes); + + if (nameservers) + reader_add_nameservers(reader, nameservers); + + if (znets) { + for (i = 0; i < znets->len; i++) + reader_parse_rd_znet(reader, znets->pdata[i], net_ifnames); + } + + g_hash_table_foreach(reader->hash, _normalize_conn, NULL); + + NM_SET_OUT(hostname, g_steal_pointer(&reader->hostname)); + + NM_SET_OUT(carrier_timeout_sec, reader->carrier_timeout_sec); + + return reader_destroy(reader, FALSE); +} diff --git a/src/core/initrd/nmi-dt-reader.c b/src/core/initrd/nmi-dt-reader.c new file mode 100644 index 0000000..5c406a4 --- /dev/null +++ b/src/core/initrd/nmi-dt-reader.c @@ -0,0 +1,371 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-initrd-generator.h" + +#include +#include + +#include "nm-core-internal.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "dt-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static gboolean +dt_get_property(const char *base, + const char *dev, + const char *prop, + char ** contents, + size_t * length) +{ + gs_free char *filename = g_build_filename(base, dev, prop, NULL); + gs_free_error GError *error = NULL; + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) + return FALSE; + + if (!contents) + return TRUE; + + if (!g_file_get_contents(filename, contents, length, &error)) { + _LOGW(LOGD_CORE, "%s: Can not read the %s property: %s", dev, prop, error->message); + return FALSE; + } + + return TRUE; +} + +static NMIPAddress * +dt_get_ipaddr_property(const char *base, const char *dev, const char *prop, int *family) +{ + gs_free char *buf = NULL; + size_t len; + int f; + + if (!dt_get_property(base, dev, prop, &buf, &len)) + return NULL; + + f = nm_utils_addr_family_from_size(len); + if (f == AF_UNSPEC || (*family != AF_UNSPEC && *family != f)) { + _LOGW(LOGD_CORE, "%s: Address %s has unrecognized length (%zd)", dev, prop, len); + return NULL; + } + + *family = f; + return nm_ip_address_new_binary(f, buf, 0, NULL); +} + +static char * +dt_get_hwaddr_property(const char *base, const char *dev, const char *prop) +{ + gs_free guint8 *buf = NULL; + size_t len; + + if (!dt_get_property(base, dev, prop, (char **) &buf, &len)) + return NULL; + + if (len != ETH_ALEN) { + _LOGW(LOGD_CORE, "%s: MAC address %s has unrecognized length (%zd)", dev, prop, len); + return NULL; + } + + return g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x", + buf[0], + buf[1], + buf[2], + buf[3], + buf[4], + buf[4]); +} + +static NMIPAddress * +str_addr(const char *str, int *family) +{ + NMIPAddr addr_bin; + + if (!nm_utils_parse_inaddr_bin_full(*family, TRUE, str, family, &addr_bin)) { + _LOGW(LOGD_CORE, "Malformed IP address: '%s'", str); + return NULL; + } + return nm_ip_address_new_binary(*family, &addr_bin, 0, NULL); +} + +NMConnection * +nmi_dt_reader_parse(const char *sysfs_dir) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free char * base = NULL; + gs_free char * bootpath = NULL; + gs_strfreev char ** tokens = NULL; + char * path = NULL; + gboolean bootp = FALSE; + const char * s_ipaddr = NULL; + const char * s_netmask = NULL; + const char * s_gateway = NULL; + nm_auto_unref_ip_address NMIPAddress *ipaddr = NULL; + nm_auto_unref_ip_address NMIPAddress *gateway = NULL; + const char * duplex = NULL; + gs_free char * hwaddr = NULL; + gs_free char * local_hwaddr = NULL; + gs_free char * hostname = NULL; + guint32 speed = 0; + int prefix = -1; + NMSettingIPConfig * s_ip = NULL; + NMSetting * s_ip4 = NULL; + NMSetting * s_ip6 = NULL; + NMSetting * s_wired = NULL; + int family = AF_UNSPEC; + int i = 0; + char * c; + gs_free_error GError *error = NULL; + + base = g_build_filename(sysfs_dir, "firmware", "devicetree", "base", NULL); + + if (!dt_get_property(base, "chosen", "bootpath", &bootpath, NULL)) + return NULL; + + c = strchr(bootpath, ':'); + if (c) { + *c = '\0'; + path = c + 1; + } else { + path = ""; + } + + dt_get_property(base, "chosen", "client-name", &hostname, NULL); + + local_hwaddr = dt_get_hwaddr_property(base, bootpath, "local-mac-address"); + hwaddr = dt_get_hwaddr_property(base, bootpath, "mac-address"); + if (nm_streq0(local_hwaddr, hwaddr)) + nm_clear_g_free(&local_hwaddr); + + tokens = g_strsplit(path, ",", 0); + + /* + * Ethernet device settings. Defined by "Open Firmware, + * Recommended Practice: Device Support Extensions, Version 1.0 [1] + * [1] https://www.devicetree.org/open-firmware/practice/devicex/dse1_0a.ps + */ + + for (i = 0; tokens[i]; i++) { + /* Skip these. They have magical meaning for OpenFirmware. */ + if (NM_IN_STRSET(tokens[i], "nfs", "last")) + continue; + if (nm_streq(tokens[i], "promiscuous")) { + /* Ignore. */ + continue; + } + + if (g_str_has_prefix(tokens[i], "speed=")) { + speed = _nm_utils_ascii_str_to_int64(tokens[i] + 6, 10, 0, G_MAXUINT32, 0); + continue; + } + + if (g_str_has_prefix(tokens[i], "duplex=auto")) { + continue; + } else if (g_str_has_prefix(tokens[i], "duplex=half") + || g_str_has_prefix(tokens[i], "duplex=full")) { + duplex = tokens[i] + 7; + continue; + } + + break; + } + + /* + * Network boot configuration. Defined by "Open Firmware, + * Recommended Practice: TFTP Booting Extension, Version 1.0 [1] + * [1] https://www.devicetree.org/open-firmware/practice/obp-tftp/tftp1_0.pdf + */ + + for (; tokens[i]; i++) { + if (NM_IN_STRSET(tokens[i], "bootp", "dhcp", "rarp")) { + bootp = TRUE; + continue; + } + break; + } + + /* s-iaddr, or perhaps a raw absolute filename */ + if (tokens[i] && tokens[i][0] != '/') + i++; + + /* filename */ + if (tokens[i]) + i++; + + /* c-iaddr */ + if (tokens[i]) { + s_ipaddr = tokens[i]; + i++; + } + + /* g-iaddr */ + if (tokens[i]) { + s_gateway = tokens[i]; + i++; + } + + if (tokens[i] && (strchr(tokens[i], '.') || strchr(tokens[i], ':'))) { + /* yaboot claims the mask can be specified here, + * though it doesn't support it. */ + s_netmask = tokens[i]; + i++; + } + + /* bootp-retries */ + if (tokens[i]) + i++; + + /* tftp-retries */ + if (tokens[i]) + i++; + + if (tokens[i]) { + /* yaboot accepts a mask here */ + s_netmask = tokens[i]; + i++; + } + + connection = nm_simple_connection_new(); + + nm_connection_add_setting(connection, + g_object_new(NM_TYPE_SETTING_CONNECTION, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_ID, + "OpenFirmware Connection", + NULL)); + + s_ip4 = nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, s_ip4); + + s_ip6 = nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, s_ip6); + + g_object_set(s_ip6, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NULL); + + if (!bootp && dt_get_property(base, "chosen", "bootp-response", NULL, NULL)) + bootp = TRUE; + + if (!bootp) { + nm_auto_unref_ip_address NMIPAddress *netmask = NULL; + + netmask = dt_get_ipaddr_property(base, "chosen", "netmask-ip", &family); + gateway = dt_get_ipaddr_property(base, "chosen", "gateway-ip", &family); + if (gateway) + s_gateway = nm_ip_address_get_address(gateway); + ipaddr = dt_get_ipaddr_property(base, "chosen", "client-ip", &family); + + if (family == AF_UNSPEC) { + nm_assert(netmask == NULL); + nm_assert(gateway == NULL); + nm_assert(ipaddr == NULL); + + netmask = str_addr(s_netmask, &family); + ipaddr = str_addr(s_ipaddr, &family); + + prefix = _nm_utils_ascii_str_to_int64(s_netmask, 10, 0, 128, -1); + } + + if (prefix == -1 && family == AF_INET && netmask) { + guint32 netmask_v4; + + nm_ip_address_get_address_binary(netmask, &netmask_v4); + prefix = nm_utils_ip4_netmask_to_prefix(netmask_v4); + } + + if (prefix == -1) + _LOGW(LOGD_CORE, "Unable to determine the network prefix"); + else + nm_ip_address_set_prefix(ipaddr, prefix); + } + + if (!ipaddr) { + family = AF_UNSPEC; + bootp = TRUE; + } + + if (bootp) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hostname, + NULL); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hostname, + NULL); + } else { + switch (family) { + case AF_INET: + s_ip = (NMSettingIPConfig *) s_ip4; + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + break; + case AF_INET6: + s_ip = (NMSettingIPConfig *) s_ip6; + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NULL); + break; + default: + g_return_val_if_reached(NULL); + } + + nm_setting_ip_config_add_address(s_ip, ipaddr); + g_object_set(s_ip, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL); + } + + if (duplex || speed || hwaddr || local_hwaddr) { + s_wired = nm_setting_wired_new(); + nm_connection_add_setting(connection, s_wired); + + g_object_set(s_wired, + NM_SETTING_WIRED_SPEED, + speed, + NM_SETTING_WIRED_DUPLEX, + duplex, + NM_SETTING_WIRED_MAC_ADDRESS, + hwaddr, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + local_hwaddr, + NULL); + } + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) { + _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); + nm_clear_pointer(&connection, g_object_unref); + } + + return g_steal_pointer(&connection); +} diff --git a/src/core/initrd/nmi-ibft-reader.c b/src/core/initrd/nmi-ibft-reader.c new file mode 100644 index 0000000..4080ede --- /dev/null +++ b/src/core/initrd/nmi-ibft-reader.c @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-initrd-generator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "ibft-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static GHashTable * +load_one_nic(const char *sysfs_dir, const char *dir_name) +{ + gs_free char *nic_path = g_build_filename(sysfs_dir, dir_name, NULL); + GDir * nic_dir; + const char * entry_name; + char * content; + gs_free_error GError *error = NULL; + GHashTable * nic; + + g_return_val_if_fail(sysfs_dir != NULL, FALSE); + + nic_dir = g_dir_open(nic_path, 0, &error); + if (!nic_dir) { + _LOGW(LOGD_CORE, "Can't open %s: %s", nic_path, error->message); + return NULL; + } + + nic = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + while ((entry_name = g_dir_read_name(nic_dir))) { + gs_free char *entry_path = g_build_filename(nic_path, entry_name, NULL); + + if (!g_file_test(entry_path, G_FILE_TEST_IS_REGULAR)) + continue; + + if (!g_file_get_contents(entry_path, &content, NULL, &error)) { + _LOGW(LOGD_CORE, "Can't read %s: %s", entry_path, error->message); + g_clear_error(&error); + continue; + } + + g_strchomp(content); + if (!g_hash_table_insert(nic, g_strdup(entry_name), content)) + _LOGW(LOGD_CORE, "Duplicate iBFT entry: %s", entry_name); + } + + g_dir_close(nic_dir); + + return nic; +} + +GHashTable * +nmi_ibft_read(const char *sysfs_dir) +{ + gs_free char *ibft_path = NULL; + GDir * ibft_dir; + const char * dir_name; + GHashTable * ibft, *nic; + char * mac; + gs_free_error GError *error = NULL; + + g_return_val_if_fail(sysfs_dir != NULL, FALSE); + + ibft_path = g_build_filename(sysfs_dir, "firmware", "ibft", NULL); + + ibft = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_hash_table_unref); + + if (!g_file_test(ibft_path, G_FILE_TEST_IS_DIR)) + nm_utils_modprobe(NULL, FALSE, "iscsi_ibft", NULL); + if (!g_file_test(ibft_path, G_FILE_TEST_IS_DIR)) + return ibft; + + ibft_dir = g_dir_open(ibft_path, 0, &error); + if (!ibft_dir) { + _LOGW(LOGD_CORE, "Unable to open iBFT firmware directory: %s", error->message); + return ibft; + } + + while ((dir_name = g_dir_read_name(ibft_dir))) { + if (!g_str_has_prefix(dir_name, "ethernet")) + continue; + + nic = load_one_nic(ibft_path, dir_name); + mac = g_hash_table_lookup(nic, "mac"); + + if (!mac) { + _LOGW(LOGD_CORE, "Ignoring an iBFT record without a MAC address"); + g_hash_table_unref(nic); + continue; + } + + mac = g_ascii_strup(mac, -1); + if (!g_hash_table_insert(ibft, mac, nic)) + _LOGW(LOGD_CORE, "Duplicate iBFT record for %s", mac); + } + + g_dir_close(ibft_dir); + + return ibft; +} + +static gboolean +ip_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSettingIPConfig *s_ip = NULL; + NMSettingIPConfig *s_ip4 = NULL; + NMSettingIPConfig *s_ip6 = NULL; + NMIPAddress * addr; + const char * s_ipaddr = NULL; + const char * s_prefix = NULL; + const char * s_gateway = NULL; + const char * s_dns1 = NULL; + const char * s_dns2 = NULL; + const char * s_origin = NULL; + const char * method = NULL; + int family; + gint64 prefix; + + s_ipaddr = (const char *) g_hash_table_lookup(nic, "ip-addr"); + s_prefix = (const char *) g_hash_table_lookup(nic, "prefix-len"); + s_gateway = (const char *) g_hash_table_lookup(nic, "gateway"); + s_dns1 = (const char *) g_hash_table_lookup(nic, "primary-dns"); + s_dns2 = (const char *) g_hash_table_lookup(nic, "secondary-dns"); + s_origin = (const char *) g_hash_table_lookup(nic, "origin"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (!s_ip4) { + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip4); + } + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (!s_ip6) { + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip6); + + g_object_set(s_ip6, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NULL); + } + + family = get_ip_address_family(s_ipaddr, FALSE); + if (family == AF_UNSPEC) + family = get_ip_address_family(s_gateway, FALSE); + + switch (family) { + case AF_INET: + s_ip = s_ip4; + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + break; + case AF_INET6: + s_ip = s_ip6; + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + break; + default: + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP address '%s'.", + s_ipaddr); + return FALSE; + } + + if ((nm_streq0(s_origin, "3") && family == AF_INET) + || (nm_streq0(s_origin, "4") && family == AF_INET)) { + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + } else if (nm_streq0(s_origin, "3") && family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_DHCP; + } else if (nm_streq0(s_origin, "4") && family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + } else if (family == AF_INET) { + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + } else if (family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + } else { + g_return_val_if_reached(FALSE); + } + g_object_set(s_ip, + NM_SETTING_IP_CONFIG_METHOD, + method, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + + if (s_gateway && !nm_utils_ipaddr_is_valid(family, s_gateway)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP gateway '%s'.", + s_gateway); + return FALSE; + } + + if (s_dns1 && !nm_utils_ipaddr_is_valid(family, s_dns1)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid DNS1 address '%s'.", + s_dns1); + return FALSE; + } + + if (s_dns2 && !nm_utils_ipaddr_is_valid(family, s_dns2)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid DNS2 address '%s'.", + s_dns2); + return FALSE; + } + + if (s_ipaddr) { + prefix = _nm_utils_ascii_str_to_int64(s_prefix, 10, 0, 128, -1); + if (prefix == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP prefix '%s'.", + s_prefix); + return FALSE; + } + + addr = nm_ip_address_new(family, s_ipaddr, prefix, error); + if (!addr) { + g_prefix_error(error, "iBFT: "); + return FALSE; + } + + nm_setting_ip_config_add_address(s_ip, addr); + nm_ip_address_unref(addr); + + g_object_set(s_ip, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL); + } + + if (s_dns1) + nm_setting_ip_config_add_dns(s_ip, s_dns1); + if (s_dns2) + nm_setting_ip_config_add_dns(s_ip, s_dns2); + + return TRUE; +} + +static gboolean +connection_setting_add(GHashTable * nic, + NMConnection *connection, + const char * type, + const char * prefix, + GError ** error) +{ + NMSetting * s_con; + char * id, *uuid; + const char *s_index, *s_hwaddr, *s_ipaddr, *s_vlanid; + + s_index = (const char *) g_hash_table_lookup(nic, "index"); + s_hwaddr = (const char *) g_hash_table_lookup(nic, "mac"); + s_ipaddr = (const char *) g_hash_table_lookup(nic, "ip-addr"); + s_vlanid = (const char *) g_hash_table_lookup(nic, "vlan"); + + if (!s_hwaddr) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: missing MAC address"); + return FALSE; + } + + id = g_strdup_printf("iBFT%s%s Connection%s%s", + prefix ? " " : "", + prefix ? prefix : "", + s_index ? " " : "", + s_index ? s_index : ""); + + uuid = _nm_utils_uuid_generate_from_strings("ibft", + s_hwaddr, + s_vlanid ? "V" : "v", + s_vlanid ? s_vlanid : "", + s_ipaddr ? "A" : "DHCP", + s_ipaddr ? s_ipaddr : "", + NULL); + + s_con = (NMSetting *) nm_connection_get_setting_connection(connection); + if (!s_con) { + s_con = nm_setting_connection_new(); + nm_connection_add_setting(connection, s_con); + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_TYPE, + type, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_INTERFACE_NAME, + NULL, + NULL); + + g_free(uuid); + g_free(id); + + return TRUE; +} + +static gboolean +is_ibft_vlan_device(GHashTable *nic) +{ + const char *s_vlan_id; + + g_assert(nic); + + s_vlan_id = (const char *) g_hash_table_lookup(nic, "vlan"); + + if (s_vlan_id) { + /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it + * means "no VLAN". + */ + if (_nm_utils_ascii_str_to_int64(s_vlan_id, 10, 1, 4095, -1) != -1) + return TRUE; + } + + return FALSE; +} + +static gboolean +vlan_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSetting * s_vlan = NULL; + const char *vlan_id_str = NULL; + gint64 vlan_id = -1; + + g_assert(nic); + g_assert(connection); + + /* This won't fail since this function shouldn't be called unless the + * iBFT VLAN ID exists and is > 0. + */ + vlan_id_str = (const char *) g_hash_table_lookup(nic, "vlan"); + g_assert(vlan_id_str); + + /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */ + vlan_id = _nm_utils_ascii_str_to_int64(vlan_id_str, 10, 1, 4095, -1); + if (vlan_id == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid VLAN_ID '%s'", + vlan_id_str); + return FALSE; + } + + s_vlan = (NMSetting *) nm_connection_get_setting_vlan(connection); + if (!s_vlan) { + s_vlan = nm_setting_vlan_new(); + nm_connection_add_setting(connection, s_vlan); + } + + g_object_set(s_vlan, NM_SETTING_VLAN_ID, (guint32) vlan_id, NULL); + + return TRUE; +} + +static gboolean +wired_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSetting * s_wired = NULL; + const char *hwaddr = NULL; + + g_assert(nic); + g_assert(connection); + + hwaddr = (const char *) g_hash_table_lookup(nic, "mac"); + if (!hwaddr) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: missing MAC address"); + return FALSE; + } + + if (!nm_utils_hwaddr_valid(hwaddr, ETH_ALEN)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid MAC address '%s'.", + hwaddr); + return FALSE; + } + + s_wired = (NMSetting *) nm_connection_get_setting_wired(connection); + if (!s_wired) { + s_wired = nm_setting_wired_new(); + nm_connection_add_setting(connection, s_wired); + } + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL); + + return TRUE; +} + +gboolean +nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, GError **error) +{ + gboolean is_vlan = FALSE; + + g_assert(nic); + + is_vlan = is_ibft_vlan_device(nic); + if (is_vlan && !vlan_setting_add_from_block(nic, connection, error)) + return FALSE; + + /* Always have a wired setting; for VLAN it defines the parent */ + if (!wired_setting_add_from_block(nic, connection, error)) + return FALSE; + + if (!ip_setting_add_from_block(nic, connection, error)) + return FALSE; + + if (!connection_setting_add(nic, + connection, + is_vlan ? NM_SETTING_VLAN_SETTING_NAME + : NM_SETTING_WIRED_SETTING_NAME, + is_vlan ? "VLAN" : NULL, + error)) + return FALSE; + + if (!nm_connection_normalize(connection, NULL, NULL, error)) + return FALSE; + + return TRUE; +} diff --git a/src/core/initrd/tests/meson.build b/src/core/initrd/tests/meson.build new file mode 100644 index 0000000..51650d0 --- /dev/null +++ b/src/core/initrd/tests/meson.build @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +c_flags = test_c_flags + ['-DTEST_INITRD_DIR="@0@"'.format(meson.current_source_dir())] + +test_units = [ + 'test-dt-reader', + 'test-ibft-reader', + 'test-cmdline-reader', +] + +foreach test_unit : test_units + exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: c_flags, + link_with: libnmi_core, + ) + + test( + 'initrd/' + test_unit, + test_script, + timeout: default_test_timeout, + args: test_args + [exe.full_path()], + ) +endforeach diff --git a/src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..bf6cf45 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +192.168.35.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..944c8d4 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +192.168.32.72 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2303dbd --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10000.500.250.1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..7cb9d0a --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.252.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..bf6cf45 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +192.168.35.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..944c8d4 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +192.168.32.72 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..5ed7b71 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +blah.foo.bar.baz \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..7cb9d0a --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.252.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..1970856 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +bb.cc.dd.ee \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..944c8d4 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +192.168.32.72 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..7cb9d0a --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.252.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..bf6cf45 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +192.168.35.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..d526f32 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +aa.bb.cc.dd \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..7cb9d0a --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.252.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address b/src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address b/src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address new file mode 100644 index 0000000..befa785 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address @@ -0,0 +1 @@ +00:33:21:98:b9:f1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..59ea5b8 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +10.16.52.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway new file mode 100644 index 0000000..59ea5b8 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway @@ -0,0 +1 @@ +10.16.52.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac new file mode 100644 index 0000000..befa785 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name new file mode 100644 index 0000000..ac3a6a2 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name @@ -0,0 +1 @@ +iqn.1.2008-11.com.blahblah:iscsi1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath b/src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath new file mode 100644 index 0000000..6f069ae Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request new file mode 100644 index 0000000..034d423 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response new file mode 100644 index 0000000..25982d6 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath new file mode 100644 index 0000000..db88070 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip new file mode 100644 index 0000000..7bde864 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip @@ -0,0 +1 @@ +���� \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip new file mode 100644 index 0000000..f108ba9 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip @@ -0,0 +1,2 @@ + ++ \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name new file mode 100644 index 0000000..fe0f76b Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name new file mode 100644 index 0000000..b34f9f9 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip new file mode 100644 index 0000000..f036183 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip @@ -0,0 +1,2 @@ + ++� \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name new file mode 100644 index 0000000..f3e5805 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip new file mode 100644 index 0000000..d441cd8 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip new file mode 100644 index 0000000..a2a4b2e --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip @@ -0,0 +1,2 @@ + +& \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file new file mode 100644 index 0000000..c0e0e33 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type new file mode 100644 index 0000000..df3c9d9 Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type differ diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address new file mode 100644 index 0000000..c983e75 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address @@ -0,0 +1 @@ +�>�� \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address new file mode 100644 index 0000000..c983e75 --- /dev/null +++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address @@ -0,0 +1 @@ +�>�� \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name new file mode 100644 index 0000000..88eaddf Binary files /dev/null and b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name differ diff --git a/src/core/initrd/tests/sysfs-static/class/net/eth0/address b/src/core/initrd/tests/sysfs-static/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/class/net/eth1/address b/src/core/initrd/tests/sysfs-static/class/net/eth1/address new file mode 100644 index 0000000..befa785 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/class/net/eth1/address @@ -0,0 +1 @@ +00:33:21:98:b9:f1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..bf6cf45 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +192.168.35.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..944c8d4 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +192.168.32.72 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..7cb9d0a --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.252.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway new file mode 100644 index 0000000..59ea5b8 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway @@ -0,0 +1 @@ +10.16.52.254 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac new file mode 100644 index 0000000..befa785 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns new file mode 100644 index 0000000..2e152cb --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns @@ -0,0 +1 @@ +10.16.255.2 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns new file mode 100644 index 0000000..2eb9e1d --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns @@ -0,0 +1 @@ +10.16.255.3 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..509a7ab --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.pjones6 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name new file mode 100644 index 0000000..32c9450 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.0.2008-11.com.blahblah:iscsi0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr new file mode 100644 index 0000000..7450fdf --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr @@ -0,0 +1 @@ +10.16.52.16 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name new file mode 100644 index 0000000..ac3a6a2 --- /dev/null +++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name @@ -0,0 +1 @@ +iqn.1.2008-11.com.blahblah:iscsi1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/class/net/eth0/address b/src/core/initrd/tests/sysfs-vlan/class/net/eth0/address new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/class/net/eth0/address @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..4923230 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +192.168.6.200 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..20659db --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:33:21:98:b9:f0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..cabf43b --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +24 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..5a279b4 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.255.0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..d800886 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..8e9cd2c --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.2010-04.org.ipxe:d05faa97-c4be-44f6-a723-efde9aa399a0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..a32ce01 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +192.168.6.32 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun new file mode 100644 index 0000000..f309ca6 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun @@ -0,0 +1 @@ +01000000 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port new file mode 100644 index 0000000..58f0d91 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name new file mode 100644 index 0000000..f0e6115 --- /dev/null +++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name @@ -0,0 +1 @@ +iqn.2003-01.org.x:disk1 \ No newline at end of file diff --git a/src/core/initrd/tests/sysfs/class/net/eth0/address b/src/core/initrd/tests/sysfs/class/net/eth0/address new file mode 100644 index 0000000..1f2f610 --- /dev/null +++ b/src/core/initrd/tests/sysfs/class/net/eth0/address @@ -0,0 +1 @@ +00:53:00:AB:00:01 diff --git a/src/core/initrd/tests/sysfs/class/net/eth2/address b/src/core/initrd/tests/sysfs/class/net/eth2/address new file mode 100644 index 0000000..6ec1078 --- /dev/null +++ b/src/core/initrd/tests/sysfs/class/net/eth2/address @@ -0,0 +1 @@ +00:53:06:66:ab:01 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id new file mode 100644 index 0000000..590d9cd --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id @@ -0,0 +1 @@ +NMTst diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id new file mode 100644 index 0000000..35d4d09 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id @@ -0,0 +1 @@ +Whatevs diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature new file mode 100644 index 0000000..e3a5d7e --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature @@ -0,0 +1 @@ +IBFT diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp new file mode 100644 index 0000000..98a0d79 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:0002 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway new file mode 100644 index 0000000..7f378f8 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:0001 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname new file mode 100644 index 0000000..3ed5163 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname @@ -0,0 +1 @@ +host0.example.com diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr new file mode 100644 index 0000000..98a0d79 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:0002 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac new file mode 100644 index 0000000..f244131 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac @@ -0,0 +1 @@ +00:53:00:ab:00:01 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin @@ -0,0 +1 @@ +4 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len new file mode 100644 index 0000000..900731f --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len @@ -0,0 +1 @@ +64 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns new file mode 100644 index 0000000..e7511d5 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:0053 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns new file mode 100644 index 0000000..1f6c5c9 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:5353 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask new file mode 100644 index 0000000..5df9345 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask @@ -0,0 +1 @@ +255.255.255.255 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan new file mode 100644 index 0000000..7cc86ad --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan @@ -0,0 +1 @@ +666 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp new file mode 100644 index 0000000..61ef558 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp @@ -0,0 +1 @@ +192.0.2.2 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway new file mode 100644 index 0000000..2096f19 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway @@ -0,0 +1 @@ +192.0.2.1 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname new file mode 100644 index 0000000..5dc539d --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname @@ -0,0 +1 @@ +host2.example.com diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index @@ -0,0 +1 @@ +2 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr new file mode 100644 index 0000000..61ef558 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr @@ -0,0 +1 @@ +192.0.2.2 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac new file mode 100644 index 0000000..2fc3d52 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac @@ -0,0 +1 @@ +00:53:06:66:AB:01 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len @@ -0,0 +1 @@ +24 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns new file mode 100644 index 0000000..6fa8c74 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns @@ -0,0 +1 @@ +192.0.2.53 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns new file mode 100644 index 0000000..a7269ff --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns @@ -0,0 +1 @@ +192.0.2.54 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask new file mode 100644 index 0000000..d30f9e9 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask @@ -0,0 +1 @@ +255.255.255.0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/index b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/index new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/index @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name new file mode 100644 index 0000000..337b5a0 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name @@ -0,0 +1 @@ +iqn.1994-05.com.example.initiator:48b055856417 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server new file mode 100644 index 0000000..4aff939 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:3205 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server new file mode 100644 index 0000000..a26fb64 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:1812 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server new file mode 100644 index 0000000..908be27 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server @@ -0,0 +1 @@ +192.0.2.181 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server new file mode 100644 index 0000000..fca9e24 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:0427 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type b/src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/flags b/src/core/initrd/tests/sysfs/firmware/ibft/target0/flags new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/flags @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs/firmware/ibft/target0/index new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/index @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr new file mode 100644 index 0000000..994b6e9 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0000:3260 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs/firmware/ibft/target0/lun new file mode 100644 index 0000000..7c295bd --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/lun @@ -0,0 +1 @@ +01000000 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc b/src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs/firmware/ibft/target0/port new file mode 100644 index 0000000..53f0295 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/port @@ -0,0 +1 @@ +3260 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type b/src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type new file mode 100644 index 0000000..573541a --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type @@ -0,0 +1 @@ +0 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/flags b/src/core/initrd/tests/sysfs/firmware/ibft/target2/flags new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/flags @@ -0,0 +1 @@ +3 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/index b/src/core/initrd/tests/sysfs/firmware/ibft/target2/index new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/index @@ -0,0 +1 @@ +2 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr new file mode 100644 index 0000000..a210477 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr @@ -0,0 +1 @@ +2001:0db8:0000:0000:0000:0000:0002:3260 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/lun b/src/core/initrd/tests/sysfs/firmware/ibft/target2/lun new file mode 100644 index 0000000..7c295bd --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/lun @@ -0,0 +1 @@ +01000000 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc b/src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc @@ -0,0 +1 @@ +2 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/port b/src/core/initrd/tests/sysfs/firmware/ibft/target2/port new file mode 100644 index 0000000..53f0295 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/port @@ -0,0 +1 @@ +3260 diff --git a/src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name b/src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name new file mode 100644 index 0000000..f409432 --- /dev/null +++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name @@ -0,0 +1 @@ +iqn.1994-05.com.example.target:48b055851337 diff --git a/src/core/initrd/tests/test-cmdline-reader.c b/src/core/initrd/tests/test-cmdline-reader.c new file mode 100644 index 0000000..1dc0988 --- /dev/null +++ b/src/core/initrd/tests/test-cmdline-reader.c @@ -0,0 +1,2116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +#include "../nm-initrd-generator.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +#define _parse(ARGV, out_hostname, out_carrier_timeout_sec) \ + ({ \ + const char *const *const _ARGV = (ARGV); \ + char **const _out_hostname = (out_hostname); \ + gint64 *const _out_carrier_timeout_sec = (out_carrier_timeout_sec); \ + GHashTable * _connections; \ + \ + _connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", \ + _ARGV, \ + _out_hostname, \ + _out_carrier_timeout_sec); \ + \ + g_assert(_connections); \ + \ + _connections; \ + }) + +#define _parse_cons(ARGV) \ + ({ \ + GHashTable * _con_connections; \ + gs_free char *_con_hostname = NULL; \ + gint64 _con_carrier_timeout_sec = 0; \ + \ + _con_connections = _parse((ARGV), \ + nmtst_get_rand_bool() ? &_con_hostname : NULL, \ + nmtst_get_rand_bool() ? &_con_carrier_timeout_sec : NULL); \ + g_assert_cmpstr(_con_hostname, ==, NULL); \ + g_assert_cmpint(_con_carrier_timeout_sec, ==, 0); \ + \ + _con_connections; \ + }) + +#define _parse_con(ARGV, connection_name) \ + ({ \ + gs_unref_hashtable GHashTable *_1_connections = NULL; \ + NMConnection * _1_connection; \ + const char *const _1_connection_name = (connection_name); \ + \ + g_assert(_1_connection_name); \ + \ + _1_connections = _parse_cons((ARGV)); \ + \ + g_assert_cmpint(g_hash_table_size(_1_connections), ==, 1); \ + \ + _1_connection = g_hash_table_lookup(_1_connections, _1_connection_name); \ + g_assert(NM_IS_CONNECTION(_1_connection)); \ + \ + nmtst_assert_connection_verifies_without_normalization(_1_connection); \ + \ + NM_CONNECTION(g_object_ref(_1_connection)); \ + }) + +/*****************************************************************************/ + +static void +test_auto(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=auto"); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "default_connection"); + + g_assert(!nm_connection_get_setting_vlan(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Wired Connection"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); +} + +static void +test_dhcp_with_hostname(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("ip=::::host1::dhcp"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 1); + g_assert_cmpstr(hostname, ==, "host1"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "default_connection"); + + nmtst_assert_connection_verifies_without_normalization(connection); + + g_assert(!nm_connection_get_setting_vlan(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Wired Connection"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); +} + +static void +test_dhcp_with_mtu(void) +{ + const char *const *ARGV0 = NM_MAKE_STRV("ip=:dhcp:1499"); + const char *const *ARGV1 = NM_MAKE_STRV("ip=::::::dhcp:1499"); + const char *const *ARGV[] = {ARGV0, ARGV1}; + guint i; + + for (i = 0; i < G_N_ELEMENTS(ARGV); i++) { + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV[i], "default_connection"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Wired Connection"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1499); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + } +} + +static void +test_if_auto_with_mtu(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=eth0:auto:1666"); + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "eth0"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1666); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); +} + +static void +test_if_dhcp6(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=eth1:dhcp6"); + gs_unref_object NMConnection *connection = NULL; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "eth1"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); +} + +static void +test_if_auto_with_mtu_and_mac(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=eth2:auto6:2048:00:53:ef:12:34:56"); + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "eth2"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth2"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 2048); + g_assert_cmpstr(nm_setting_wired_get_cloned_mac_address(s_wired), ==, "00:53:EF:12:34:56"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); +} + +static void +test_if_ip4_manual(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("ip=192.0.2.2::192.0.2.1:255.255.255.0:" + "hostname0.example.com:eth3:none:192.0.2.53", + "ip=203.0.113.2::203.0.113.1:26:" + "hostname1.example.com:eth4"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * ip_addr; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + g_assert_cmpstr(hostname, ==, "hostname1.example.com"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "eth3"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth3"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "192.0.2.53"); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.0.2.2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 24); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.0.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname0.example.com"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); + + connection = g_hash_table_lookup(connections, "eth4"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth4"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "203.0.113.2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 26); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "203.0.113.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname1.example.com"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); +} + +static void +test_if_ip6_manual(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("ip=[2001:0db8::02]/64::[2001:0db8::01]::" + "hostname0.example.com:eth4::[2001:0db8::53]"); + NMConnection * connection; + NMSettingIPConfig * s_ip6; + NMIPAddress * ip_addr; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 1); + g_assert_cmpstr(hostname, ==, "hostname0.example.com"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "eth4"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth4"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8::53"); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 64); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1"); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "hostname0.example.com"); +} + +static void +test_if_off(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("ip=off", + "ip=ens3:off", + "ip=10.0.0.8:::::ens4:off", + "ip=[2001:DB8::8]:::::ens5:off"); + NMConnection * connection; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + struct { + const char name[32]; + const char ipv4_method[32]; + const char ipv6_method[32]; + + } conn_expected[] = { + {"default_connection", + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED}, + {"ens3", NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_DISABLED}, + {"ens4", NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NM_SETTING_IP6_CONFIG_METHOD_DISABLED}, + {"ens5", NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_MANUAL}, + }; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, G_N_ELEMENTS(conn_expected)); + + for (int i = 0; i < G_N_ELEMENTS(conn_expected); ++i) { + connection = g_hash_table_lookup(connections, conn_expected[i].name); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, conn_expected[i].ipv4_method); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, conn_expected[i].ipv6_method); + } +} + +static void +test_if_mac_ifname(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("ip=[2001:0db8::42]/64::[2001:0db8::01]::" + "hostname0:00-11-22-33-44-55::[2001:0db8::53]"); + NMConnection * connection; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMIPAddress * ip_addr; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 1); + g_assert_cmpstr(hostname, ==, "hostname0"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "00:11:22:33:44:55"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "00:11:22:33:44:55"); + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:11:22:33:44:55"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8::53"); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::42"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 64); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1"); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "hostname0"); +} + +static void +test_multiple_merge(void) +{ + const char *const *ARGV = + NM_MAKE_STRV("ip=192.0.2.2/16:::::eth0", "ip=[2001:db8::2]:::56::eth0"); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * ip_addr; + + connection = _parse_con(ARGV, "eth0"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.0.2.2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 16); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 56); +} + +static void +test_multiple_bootdev(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + + const char *const *ARGV = NM_MAKE_STRV("nameserver=1.2.3.4", + "ip=eth3:auto6", + "ip=eth4:dhcp", + "ip=eth5:link6", + "bootdev=eth4"); + + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "eth3"); + g_assert(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + connection = g_hash_table_lookup(connections, "eth4"); + g_assert(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), + ==, + NMI_WAIT_DEVICE_TIMEOUT_MS); + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "1.2.3.4"); + + connection = g_hash_table_lookup(connections, "eth5"); + g_assert(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL); +} + +static void +test_bootdev(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("vlan=vlan2:ens5", "bootdev=ens3"); + NMConnection * connection; + NMSettingConnection * s_con; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "ens3"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "ens3"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "ens3"); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), + ==, + NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup(connections, "vlan2"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "vlan2"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "vlan2"); + + connection = g_hash_table_lookup(connections, "ens5"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "ens5"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "ens5"); +} + +static void +test_some_more(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("bootdev=eth1", + "hail", + "nameserver=[2001:DB8:3::53]", + "satan", + "nameserver=192.0.2.53", + "worship", + "doom", + "rd.peerdns=0", + "rd.route=[2001:DB8:3::/48]:[2001:DB8:2::1]:ens10"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPRoute * ip_route; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth1"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "eth1"); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "192.0.2.53"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8:3::53"); + + connection = g_hash_table_lookup(connections, "ens10"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "ens10"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "ens10"); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8:3::53"); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 1); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + ip_route = nm_setting_ip_config_get_route(s_ip6, 0); + g_assert_cmpstr(nm_ip_route_get_dest(ip_route), ==, "2001:db8:3::"); + g_assert_cmpint(nm_ip_route_get_family(ip_route), ==, AF_INET6); + g_assert_cmpint(nm_ip_route_get_metric(ip_route), ==, -1); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip_route), ==, "2001:db8:2::1"); + g_assert_cmpint(nm_ip_route_get_prefix(ip_route), ==, 48); +} + +static void +test_bond(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("rd.route=192.0.2.53::bong0", + "bond=bong0:eth0,eth1:mode=balance-rr", + "nameserver=203.0.113.53"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingBond * s_bond; + NMIPRoute * ip_route; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "bong0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bong0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "203.0.113.53"); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 1); + ip_route = nm_setting_ip_config_get_route(s_ip4, 0); + g_assert_cmpstr(nm_ip_route_get_dest(ip_route), ==, "192.0.2.53"); + g_assert_cmpint(nm_ip_route_get_family(ip_route), ==, AF_INET); + g_assert_cmpint(nm_ip_route_get_metric(ip_route), ==, -1); + g_assert(!nm_ip_route_get_next_hop(ip_route)); + g_assert_cmpint(nm_ip_route_get_prefix(ip_route), ==, 32); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 1); + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr"); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth1"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_bond_ip(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = + NM_MAKE_STRV("bond=bond0:eth0,eth1", + "ip=192.168.1.1::192.168.1.254:24::bond0:none:1480:01:02:03:04:05:06", + "nameserver=4.8.15.16"); + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMSettingBond * s_bond; + NMIPAddress * ip_addr; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "bond0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bond0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1480); + g_assert_cmpstr(nm_setting_wired_get_cloned_mac_address(s_wired), ==, "01:02:03:04:05:06"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.168.1.1"); + g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 24); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.254"); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.8.15.16"); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 1); + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr"); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth1"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_bond_default(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("bond"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingBond * s_bond; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + connection = g_hash_table_lookup(connections, "bond0"); + + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bond0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 1); + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr"); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_bridge(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("bridge=bridge0:eth0,eth1", + "rd.route=192.0.2.53::bridge0", + "rd.net.timeout.dhcp=10"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingBridge * s_bridge; + NMIPRoute * ip_route; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "bridge0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bridge0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 1); + g_assert_cmpint(nm_setting_ip_config_get_dhcp_timeout(s_ip4), ==, 10); + ip_route = nm_setting_ip_config_get_route(s_ip4, 0); + g_assert_cmpstr(nm_ip_route_get_dest(ip_route), ==, "192.0.2.53"); + g_assert_cmpint(nm_ip_route_get_family(ip_route), ==, AF_INET); + g_assert_cmpint(nm_ip_route_get_metric(ip_route), ==, -1); + g_assert(!nm_ip_route_get_next_hop(ip_route)); + g_assert_cmpint(nm_ip_route_get_prefix(ip_route), ==, 32); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + g_assert_cmpint(nm_setting_ip_config_get_dhcp_timeout(s_ip6), ==, 10); + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + g_assert_cmpint(nm_setting_bridge_get_stp(s_bridge), ==, FALSE); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth1"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_bridge_default(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("bridge"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingBridge * s_bridge; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + connection = g_hash_table_lookup(connections, "br0"); + + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "br0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_bridge_ip(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = + NM_MAKE_STRV("ip=bridge123:auto:1280:00:11:22:33:CA:fe", + "bridge=bridge123:eth0,eth1,eth2,eth3,eth4,eth5,eth6,eth7,eth8,eth9"); + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMSettingBridge * s_bridge; + const char * master_uuid; + guint i; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 11); + + connection = g_hash_table_lookup(connections, "bridge123"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bridge123"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1280); + g_assert_cmpstr(nm_setting_wired_get_cloned_mac_address(s_wired), ==, "00:11:22:33:CA:FE"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + + for (i = 0; i < 10; i++) { + char ifname[16]; + + nm_sprintf_buf(ifname, "eth%u", i); + + connection = g_hash_table_lookup(connections, ifname); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, ifname); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, ifname); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + } +} + +static void +test_team(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("team=team0:eth0,eth1", "ip=team0:dhcp6"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingTeam * s_team; + const char * master_uuid; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + + connection = g_hash_table_lookup(connections, "team0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_TEAM_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "team0"); + master_uuid = nm_connection_get_uuid(connection); + g_assert(master_uuid); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0); + g_assert(!nm_setting_ip_config_get_gateway(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0); + + s_team = nm_connection_get_setting_team(connection); + g_assert(s_team); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_TEAM_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth1"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_TEAM_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, master_uuid); + g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con), + ==, + NM_CONNECTION_MULTI_CONNECT_SINGLE); +} + +static void +test_vlan(void) +{ + const char *const *ARGV0 = NM_MAKE_STRV("ip=eth0.100:dhcp", "vlan=eth0.100:eth0"); + const char *const *ARGV1 = NM_MAKE_STRV("vlan=eth0.100:eth0", "ip=eth0.100:dhcp"); + const char *const *ARGV[] = {ARGV0, ARGV1}; + guint i; + + for (i = 0; i < G_N_ELEMENTS(ARGV); i++) { + gs_unref_hashtable GHashTable *connections = NULL; + NMConnection * connection; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingVlan * s_vlan; + + connections = _parse_cons(ARGV[i]); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + /* VLAN eth0.100 */ + connection = g_hash_table_lookup(connections, "eth0.100"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0.100"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 100); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + /* Ethernet eth0 */ + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + } +} + +static void +test_vlan_with_dhcp_on_parent(void) +{ + const char *const *ARGV0 = NM_MAKE_STRV("vlan=eth0.100:eth0", "ip=eth0:dhcp"); + const char *const *ARGV1 = NM_MAKE_STRV("ip=eth0:dhcp", "vlan=eth0.100:eth0"); + const char *const *ARGV[] = {ARGV0, ARGV1}; + guint i; + + for (i = 0; i < G_N_ELEMENTS(ARGV); i++) { + gs_unref_hashtable GHashTable *connections = NULL; + NMConnection * connection; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingVlan * s_vlan; + + connections = _parse_cons(ARGV[i]); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + /* VLAN eth0.100 */ + connection = g_hash_table_lookup(connections, "eth0.100"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0.100"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 100); + + /* Ethernet eth0 */ + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + } +} + +static void +test_vlan_over_bond(void) +{ + const char *const *ARGV0 = NM_MAKE_STRV("ip=1.2.3.4:::24::vlan1:none", + "bond=bond2:ens3,ens4:mode=active-backup", + "vlan=vlan1:bond2"); + const char *const *ARGV1 = NM_MAKE_STRV("vlan=vlan1:bond2", + "ip=1.2.3.4:::24::vlan1:none", + "bond=bond2:ens3,ens4:mode=active-backup"); + const char *const *ARGV2 = NM_MAKE_STRV("bond=bond2:ens3,ens4:mode=active-backup", + "ip=1.2.3.4:::24::vlan1:none", + "vlan=vlan1:bond2"); + const char *const *ARGV[] = {ARGV0, ARGV1, ARGV2}; + guint i; + + for (i = 0; i < G_N_ELEMENTS(ARGV); i++) { + gs_unref_hashtable GHashTable *connections = NULL; + NMConnection * connection; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingVlan * s_vlan; + + connections = _parse_cons(ARGV[i]); + g_assert_cmpint(g_hash_table_size(connections), ==, 4); + + /* VLAN vlan1 */ + connection = g_hash_table_lookup(connections, "vlan1"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "vlan1"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "bond2"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 1); + + /* Bond bond2 */ + connection = g_hash_table_lookup(connections, "bond2"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_BOND_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bond2"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + + /* Ethernet ens3 and ens4 */ + connection = g_hash_table_lookup(connections, "ens3"); + g_assert(connection); + connection = g_hash_table_lookup(connections, "ens4"); + g_assert(connection); + } +} + +static void +test_ibft_ip_dev(void) +{ + const char *const * ARGV = NM_MAKE_STRV("ip=eth0:ibft"); + NMSettingConnection *s_con; + gs_unref_object NMConnection *connection = NULL; + + connection = _parse_con(ARGV, "eth0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, NULL); +} + +static void +test_ibft_ip_dev_mac(void) +{ + const char *const * ARGV = NM_MAKE_STRV("ip=00-53-06-66-ab-01:ibft"); + NMSettingConnection *s_con; + gs_unref_object NMConnection *connection = NULL; + + connection = _parse_con(ARGV, "00:53:06:66:AB:01"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, NULL); +} + +static void +_test_ibft_ip(const char *const *ARGV) +{ + gs_unref_hashtable GHashTable *connections = NULL; + NMConnection * connection; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + connection = g_hash_table_lookup(connections, "ibft0"); + nmtst_assert_connection_verifies_without_normalization(connection); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "iBFT VLAN Connection 0"); + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL); + + connection = g_hash_table_lookup(connections, "ibft2"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "iBFT Connection 2"); + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL); +} + +static void +test_ibft_ip(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=ibft"); + + _test_ibft_ip(ARGV); +} + +static void +test_ibft_rd_iscsi_ibft(void) +{ + const char *const *ARGV = NM_MAKE_STRV("rd.iscsi.ibft"); + + _test_ibft_ip(ARGV); +} + +static void +test_ignore_extra(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("blabla", "extra", "lalala"); + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); +} + +static void +test_rd_znet(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const *const ARGV = + NM_MAKE_STRV("ip=10.11.12.13::10.11.12.1:24:foo.example.com:enc800:none", + "ip=slc600:dhcp", + "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1", + "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0"); + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + const char *const * v_subchannels; + const NMUtilsNamedValue s390_options[] = { + {.name = "layer2", .value_str = "0"}, + {.name = "portno", .value_str = "1"}, + }; + int i_s390_options_keys; + gs_free char *hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + g_assert_cmpstr(hostname, ==, "foo.example.com"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "enc800"); + g_assert(NM_IS_CONNECTION(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(NM_IS_SETTING_CONNECTION(s_con)); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "enc800"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "enc800"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(NM_IS_SETTING_WIRED(s_wired)); + + v_subchannels = nm_setting_wired_get_s390_subchannels(s_wired); + g_assert(v_subchannels); + g_assert_cmpstr(v_subchannels[0], ==, "0.0.0800"); + g_assert_cmpstr(v_subchannels[1], ==, "0.0.0801"); + g_assert_cmpstr(v_subchannels[2], ==, "0.0.0802"); + g_assert_cmpstr(v_subchannels[3], ==, NULL); + + g_assert_cmpint(nm_setting_wired_get_num_s390_options(s_wired), ==, G_N_ELEMENTS(s390_options)); + for (i_s390_options_keys = 0; i_s390_options_keys < G_N_ELEMENTS(s390_options); + i_s390_options_keys++) { + const NMUtilsNamedValue *s390_option = &s390_options[i_s390_options_keys]; + const char * k; + const char * v; + const char * v2; + + g_assert(s390_option->name); + g_assert(s390_option->value_str); + v = nm_setting_wired_get_s390_option_by_key(s_wired, s390_option->name); + g_assert(v); + g_assert_cmpstr(v, ==, s390_option->value_str); + + if (!nm_setting_wired_get_s390_option(s_wired, i_s390_options_keys, &k, &v2)) + g_assert_not_reached(); + g_assert_cmpstr(k, ==, s390_option->name); + g_assert(v == v2); + g_assert_cmpstr(v2, ==, s390_option->value_str); + } + + nmtst_assert_connection_verifies_without_normalization(connection); + + connection = g_hash_table_lookup(connections, "slc600"); + g_assert(NM_IS_CONNECTION(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(NM_IS_SETTING_CONNECTION(s_con)); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "slc600"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "slc600"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(NM_IS_SETTING_WIRED(s_wired)); + + v_subchannels = nm_setting_wired_get_s390_subchannels(s_wired); + g_assert(v_subchannels); + g_assert_cmpstr(v_subchannels[0], ==, "0.0.0600"); + g_assert_cmpstr(v_subchannels[1], ==, "0.0.0601"); + g_assert_cmpstr(v_subchannels[2], ==, NULL); + + nmtst_assert_connection_verifies_without_normalization(connection); +} + +static void +test_rd_znet_legacy(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const *const ARGV = + NM_MAKE_STRV("ip=10.11.12.13::10.11.12.1:24:foo.example.com:eth0:none", + "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1", + "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0", + "ip=ctc0:dhcp", + "net.ifnames=0"); + NMConnection * connection; + NMSettingConnection *s_con; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + g_assert_cmpstr(hostname, ==, "foo.example.com"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "eth0"); + g_assert(NM_IS_CONNECTION(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(NM_IS_SETTING_CONNECTION(s_con)); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "eth0"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "eth0"); + + nmtst_assert_connection_verifies_without_normalization(connection); + + connection = g_hash_table_lookup(connections, "ctc0"); + g_assert(NM_IS_CONNECTION(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(NM_IS_SETTING_CONNECTION(s_con)); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "ctc0"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "ctc0"); + + nmtst_assert_connection_verifies_without_normalization(connection); +} + +static void +test_rd_znet_no_ip(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const *const ARGV = + NM_MAKE_STRV("rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1"); + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); +} + +static void +test_bootif_ip(void) +{ + const char *const *ARGV = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03", "ip=dhcp"); + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "default_connection"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "Wired Connection"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:53:AB:CD:02:03"); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert(!nm_setting_ip_config_get_may_fail(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); +} + +static void +test_neednet(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("rd.neednet", + "ip=eno1:dhcp", + "ip=172.25.1.100::172.25.1.1:24::eno2", + "bridge=br0:eno3"); + NMConnection * connection; + NMSettingConnection * s_con; + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 4); + + connection = g_hash_table_lookup(connections, "eno1"); + nmtst_assert_connection_verifies_without_normalization(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "eno1"); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), + ==, + NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup(connections, "eno2"); + nmtst_assert_connection_verifies_without_normalization(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "eno2"); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), + ==, + NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup(connections, "eno3"); + nmtst_assert_connection_verifies_without_normalization(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "eno3"); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), + ==, + NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup(connections, "br0"); + nmtst_assert_connection_verifies_without_normalization(connection); + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "br0"); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1); +} + +static void +test_bootif_no_ip(void) +{ + const char *const *ARGV = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03"); + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = _parse_con(ARGV, "default_connection"); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "Wired Connection"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:53:AB:CD:02:03"); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_may_fail(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); +} + +static void +test_bootif_hwtype(void) +{ + const char *const *ARGV0 = NM_MAKE_STRV("ip=eth0:dhcp", "BOOTIF=01-00-53-AB-cd-02-03"); + const char *const *ARGV1 = NM_MAKE_STRV("ip=eth0:dhcp", "BOOTIF=00-00-53-Ab-cD-02-03"); + const char *const *ARGV[] = {ARGV0, ARGV1}; + guint i; + + for (i = 0; i < G_N_ELEMENTS(ARGV); i++) { + gs_unref_hashtable GHashTable *connections = NULL; + NMConnection * connection; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connections = _parse_cons(ARGV[i]); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert(!nm_setting_ip_config_get_may_fail(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + + connection = g_hash_table_lookup(connections, "bootif_connection"); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "BOOTIF Connection"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:53:AB:CD:02:03"); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert(nm_setting_ip_config_get_may_fail(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6)); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); + } +} + +/* Check that nameservers are assigned to all existing + * connections that support the specific IPv4/IPv6 address + * family. + */ +static void +test_nameserver(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = + NM_MAKE_STRV("nameserver=1.1.1.1", + "ip=eth0:dhcp", + "ip=eth1:auto6", + "ip=10.11.12.13::10.11.12.1:24:foo.example.com:eth2:none", + "nameserver=1.0.0.1", + "nameserver=[2606:4700:4700::1111]"); + NMConnection * connection; + NMSettingIPConfig *s_ip; + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 3); + g_assert_cmpstr(hostname, ==, "foo.example.com"); + g_assert_cmpint(carrier_timeout_sec, ==, 0); + + connection = g_hash_table_lookup(connections, "eth0"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_ip = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 0), ==, "1.1.1.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 1), ==, "1.0.0.1"); + + connection = g_hash_table_lookup(connections, "eth1"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_ip = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 0), ==, "2606:4700:4700::1111"); + + connection = g_hash_table_lookup(connections, "eth2"); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_ip = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 0), ==, "1.1.1.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 1), ==, "1.0.0.1"); +} + +static void +test_bootif_off(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const *ARGV = NM_MAKE_STRV("BOOTIF=01-00-53-AB-cd-02-03", "rd.bootif=0"); + + connections = _parse_cons(ARGV); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); +} + +static void +test_dhcp_vendor_class_id(void) +{ + const char *const *ARGV; + gs_unref_object NMConnection *connection = NULL; + NMSettingIP4Config * s_ip4; + gs_free char * vci_long = NULL; + char vci_arg_long[512] = {0}; + + ARGV = NM_MAKE_STRV("rd.net.dhcp.vendor-class=testvci", "ip=eno1:dhcp"); + connection = _parse_con(ARGV, "eno1"); + s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection)); + g_assert_cmpstr(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4), ==, "testvci"); + + g_clear_object(&connection); + + ARGV = NM_MAKE_STRV("rd.net.dhcp.vendor-class", "ip=eno1:dhcp"); + connection = _parse_con(ARGV, "eno1"); + s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection)); + g_assert(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4) == NULL); + + g_clear_object(&connection); + + memset(vci_arg_long, 'A', 400); + vci_long = g_strdup_printf("rd.net.dhcp.vendor-class=%s", vci_arg_long); + ARGV = NM_MAKE_STRV(vci_long, "ip=eno1:dhcp"); + connection = _parse_con(ARGV, "eno1"); + s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection)); + g_assert(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4) == NULL); +} + +static void +test_infiniband_iface(void) +{ + const char *const *ARGV = NM_MAKE_STRV("ip=ib1:dhcp"); + gs_unref_object NMConnection *connection = NULL; + NMSettingInfiniband * s_ib; + + connection = _parse_con(ARGV, "ib1"); + + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_INFINIBAND_SETTING_NAME); + s_ib = nm_connection_get_setting_infiniband(connection); + g_assert(s_ib); +} + +static void +test_infiniband_mac(void) +{ + const char *const *ARGV = + NM_MAKE_STRV("ip=00-11-22-33-44-55-66-77-88-99-aa-bb-cc-dd-ee-ff-00-11-22-33:dhcp"); + gs_unref_object NMConnection *connection = NULL; + NMSettingInfiniband * s_ib; + + connection = _parse_con(ARGV, "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33"); + + g_assert_cmpstr(nm_connection_get_connection_type(connection), + ==, + NM_SETTING_INFINIBAND_SETTING_NAME); + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL); + s_ib = nm_connection_get_setting_infiniband(connection); + g_assert(s_ib); + g_assert_cmpstr(nm_setting_infiniband_get_mac_address(s_ib), + ==, + "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33"); +} + +static void +test_carrier_timeout(void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const * ARGV = NM_MAKE_STRV("rd.net.timeout.carrier=20"); + gs_free char * hostname = NULL; + gint64 carrier_timeout_sec = 0; + + connections = _parse(ARGV, &hostname, &carrier_timeout_sec); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); + g_assert_cmpstr(hostname, ==, NULL); + g_assert_cmpint(carrier_timeout_sec, ==, 20); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/initrd/cmdline/auto", test_auto); + g_test_add_func("/initrd/cmdline/dhcp_with_hostname", test_dhcp_with_hostname); + g_test_add_func("/initrd/cmdline/dhcp_with_mtu", test_dhcp_with_mtu); + g_test_add_func("/initrd/cmdline/if_auto_with_mtu", test_if_auto_with_mtu); + g_test_add_func("/initrd/cmdline/if_dhcp6", test_if_dhcp6); + g_test_add_func("/initrd/cmdline/if_auto_with_mtu_and_mac", test_if_auto_with_mtu_and_mac); + g_test_add_func("/initrd/cmdline/if_ip4_manual", test_if_ip4_manual); + g_test_add_func("/initrd/cmdline/if_ip6_manual", test_if_ip6_manual); + g_test_add_func("/initrd/cmdline/if_mac_ifname", test_if_mac_ifname); + g_test_add_func("/initrd/cmdline/if_off", test_if_off); + g_test_add_func("/initrd/cmdline/multiple/merge", test_multiple_merge); + g_test_add_func("/initrd/cmdline/multiple/bootdev", test_multiple_bootdev); + g_test_add_func("/initrd/cmdline/nameserver", test_nameserver); + g_test_add_func("/initrd/cmdline/some_more", test_some_more); + g_test_add_func("/initrd/cmdline/bootdev", test_bootdev); + g_test_add_func("/initrd/cmdline/bond", test_bond); + g_test_add_func("/initrd/cmdline/bond/ip", test_bond_ip); + g_test_add_func("/initrd/cmdline/bond/default", test_bond_default); + g_test_add_func("/initrd/cmdline/team", test_team); + g_test_add_func("/initrd/cmdline/vlan", test_vlan); + g_test_add_func("/initrd/cmdline/vlan/dhcp-on-parent", test_vlan_with_dhcp_on_parent); + g_test_add_func("/initrd/cmdline/vlan/over-bond", test_vlan_over_bond); + g_test_add_func("/initrd/cmdline/bridge", test_bridge); + g_test_add_func("/initrd/cmdline/bridge/default", test_bridge_default); + g_test_add_func("/initrd/cmdline/bridge/ip", test_bridge_ip); + g_test_add_func("/initrd/cmdline/ibft/ip_dev", test_ibft_ip_dev); + g_test_add_func("/initrd/cmdline/ibft/ip_dev_mac", test_ibft_ip_dev_mac); + g_test_add_func("/initrd/cmdline/ibft/ip", test_ibft_ip); + g_test_add_func("/initrd/cmdline/ibft/rd_iscsi_ibft", test_ibft_rd_iscsi_ibft); + g_test_add_func("/initrd/cmdline/ignore_extra", test_ignore_extra); + g_test_add_func("/initrd/cmdline/rd_znet", test_rd_znet); + g_test_add_func("/initrd/cmdline/rd_znet/legacy", test_rd_znet_legacy); + g_test_add_func("/initrd/cmdline/rd_znet/no_ip", test_rd_znet_no_ip); + g_test_add_func("/initrd/cmdline/bootif/ip", test_bootif_ip); + g_test_add_func("/initrd/cmdline/bootif/no_ip", test_bootif_no_ip); + g_test_add_func("/initrd/cmdline/bootif/hwtype", test_bootif_hwtype); + g_test_add_func("/initrd/cmdline/bootif/off", test_bootif_off); + g_test_add_func("/initrd/cmdline/neednet", test_neednet); + g_test_add_func("/initrd/cmdline/dhcp/vendor_class_id", test_dhcp_vendor_class_id); + g_test_add_func("/initrd/cmdline/infiniband/iface", test_infiniband_iface); + g_test_add_func("/initrd/cmdline/infiniband/mac", test_infiniband_mac); + g_test_add_func("/initrd/cmdline/carrier_timeout", test_carrier_timeout); + + return g_test_run(); +} diff --git a/src/core/initrd/tests/test-dt-reader.c b/src/core/initrd/tests/test-dt-reader.c new file mode 100644 index 0000000..982ccc3 --- /dev/null +++ b/src/core/initrd/tests/test-dt-reader.c @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +#include "../nm-initrd-generator.h" + +#include "nm-test-utils-core.h" + +static void +test_read_dt_ofw(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * mac_address; + + connection = nmi_dt_reader_parse(TEST_INITRD_DIR "/sysfs-dt"); + g_assert(connection); + nmtst_assert_connection_verifies(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "OpenFirmware Connection"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac_address = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac_address); + g_assert(nm_utils_hwaddr_matches(mac_address, -1, "ac:7f:3e:e5:d8:d8", -1)); + g_assert(!nm_setting_wired_get_duplex(s_wired)); + g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 0); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "demiurge"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + g_object_unref(connection); +} + +static void +test_read_dt_slof(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * ip4_addr; + + connection = nmi_dt_reader_parse(TEST_INITRD_DIR "/sysfs-dt-tftp"); + g_assert(connection); + nmtst_assert_connection_verifies(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "OpenFirmware Connection"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + g_assert_cmpstr(nm_setting_wired_get_duplex(s_wired), ==, "half"); + g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 10); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.32.2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16); + + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.32.1"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + + g_object_unref(connection); +} + +static void +test_read_dt_none(void) +{ + NMConnection *connection; + + connection = nmi_dt_reader_parse(TEST_INITRD_DIR "/sysfs"); + g_assert(!connection); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/initrd/dt/ofw", test_read_dt_ofw); + g_test_add_func("/initrd/dt/slof", test_read_dt_slof); + g_test_add_func("/initrd/dt/none", test_read_dt_none); + + return g_test_run(); +} diff --git a/src/core/initrd/tests/test-ibft-reader.c b/src/core/initrd/tests/test-ibft-reader.c new file mode 100644 index 0000000..320153f --- /dev/null +++ b/src/core/initrd/tests/test-ibft-reader.c @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +#include "../nm-initrd-generator.h" + +#include "nm-test-utils-core.h" + +static NMConnection * +read_connection(const char *sysfs_dir, const char *expected_mac, GError **error) +{ + NMConnection * connection = NULL; + gs_unref_hashtable GHashTable *ibft = NULL; + gs_free char * mac = NULL; + GHashTable * nic = NULL; + + ibft = nmi_ibft_read(sysfs_dir); + + mac = g_ascii_strup(expected_mac, -1); + nic = g_hash_table_lookup(ibft, mac); + if (!nic) + return NULL; + + connection = nm_simple_connection_new(); + + if (!nmi_ibft_update_connection_from_nic(connection, nic, error)) + g_clear_object(&connection); + + return connection; +} + +static void +test_read_ibft_dhcp(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GError * error = NULL; + const char * mac_address; + const char * expected_mac_address = "00:33:21:98:b9:f1"; + + connection = read_connection(TEST_INITRD_DIR "/sysfs-dhcp", expected_mac_address, &error); + g_assert_no_error(error); + nmtst_assert_connection_verifies_without_normalization(connection); + + g_assert(!nm_connection_get_setting_vlan(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "iBFT Connection 1"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, NULL); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac_address = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac_address); + g_assert(nm_utils_hwaddr_matches(mac_address, -1, expected_mac_address, -1)); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + + g_object_unref(connection); +} + +static void +test_read_ibft_static(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GError * error = NULL; + const char * mac_address; + const char * expected_mac_address = "00:33:21:98:b9:f0"; + NMIPAddress * ip4_addr; + + connection = read_connection(TEST_INITRD_DIR "/sysfs-static", expected_mac_address, &error); + g_assert_no_error(error); + nmtst_assert_connection_verifies_without_normalization(connection); + + g_assert(!nm_connection_get_setting_vlan(connection)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "iBFT Connection 0"); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, NULL); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac_address = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac_address); + g_assert(nm_utils_hwaddr_matches(mac_address, -1, expected_mac_address, -1)); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "10.16.255.2"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "10.16.255.3"); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.32.72"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 22); + + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.35.254"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + + g_object_unref(connection); +} + +static void +test_read_ibft_bad_address(gconstpointer user_data) +{ + const char * sysfs_dir = user_data; + NMConnection *connection; + GError * error = NULL; + + g_assert(g_file_test(sysfs_dir, G_FILE_TEST_EXISTS)); + + connection = read_connection(sysfs_dir, "00:33:21:98:b9:f0", &error); + g_assert(connection == NULL); + g_assert(error); + g_clear_error(&error); +} + +static void +test_read_ibft_vlan(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingVlan * s_vlan; + NMSettingIPConfig * s_ip4; + const char * mac_address; + const char * expected_mac_address = "00:33:21:98:b9:f0"; + NMIPAddress * ip4_addr; + GError * error = NULL; + + connection = read_connection(TEST_INITRD_DIR "/sysfs-vlan", expected_mac_address, &error); + g_assert_no_error(error); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_VLAN_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, NULL); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac_address = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac_address); + g_assert(nm_utils_hwaddr_matches(mac_address, -1, expected_mac_address, -1)); + + /* ===== VLAN SETTING ===== */ + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 123); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, NULL); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.6.200"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, NULL); + + g_object_unref(connection); +} + +static void +test_read_ibft(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + GError * error = NULL; + + /* This test doesn't actually test too much (apart from the presence of + * IPv6 that is not covered by other tests), but the test fixture is a good + * example of about everything that can be included in iBFT table (as of + * ACPI 3.0b). */ + + connection = read_connection(TEST_INITRD_DIR "/sysfs", "00:53:00:AB:00:01", &error); + g_assert(connection); + g_assert_no_error(error); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + nmtst_assert_connection_verifies_without_normalization(connection); + g_assert(nm_setting_ip_config_get_num_addresses(s_ip4) == 0); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(nm_setting_ip_config_get_num_addresses(s_ip6) == 1); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_object_unref(connection); + + connection = read_connection(TEST_INITRD_DIR "/sysfs", "00:53:06:66:AB:01", &error); + g_assert(connection); + g_assert_no_error(error); + nmtst_assert_connection_verifies_without_normalization(connection); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(nm_setting_ip_config_get_num_addresses(s_ip4) == 1); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(nm_setting_ip_config_get_num_addresses(s_ip6) == 0); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED); + g_object_unref(connection); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/initrd/ibft", test_read_ibft); + g_test_add_func("/initrd/ibft/dhcp", test_read_ibft_dhcp); + g_test_add_func("/initrd/ibft/static", test_read_ibft_static); + g_test_add_func("/initrd/ibft/vlan", test_read_ibft_vlan); + g_test_add_data_func("/initrd/ibft/bad-ipaddr-read", + TEST_INITRD_DIR "/sysfs-bad-ipaddr", + test_read_ibft_bad_address); + g_test_add_data_func("/initrd/ibft/bad-gateway-read", + TEST_INITRD_DIR "/sysfs-bad-gateway", + test_read_ibft_bad_address); + g_test_add_data_func("/initrd/ibft/bad-dns1-read", + TEST_INITRD_DIR "/sysfs-bad-dns1", + test_read_ibft_bad_address); + g_test_add_data_func("/initrd/ibft/bad-dns2-read", + TEST_INITRD_DIR "/sysfs-bad-dns2", + test_read_ibft_bad_address); + + return g_test_run(); +} diff --git a/src/core/main-utils.c b/src/core/main-utils.c new file mode 100644 index 0000000..357f20f --- /dev/null +++ b/src/core/main-utils.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2012 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "main-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-config.h" + +static gboolean +sighup_handler(gpointer user_data) +{ + nm_main_config_reload(GPOINTER_TO_INT(user_data)); + return G_SOURCE_CONTINUE; +} + +static gboolean +sigint_handler(gpointer user_data) +{ + GMainLoop *main_loop = user_data; + + nm_log_info(LOGD_CORE, "caught SIGINT, shutting down normally."); + g_main_loop_quit(main_loop); + + return G_SOURCE_REMOVE; +} + +static gboolean +sigterm_handler(gpointer user_data) +{ + GMainLoop *main_loop = user_data; + + nm_log_info(LOGD_CORE, "caught SIGTERM, shutting down normally."); + g_main_loop_quit(main_loop); + + return G_SOURCE_REMOVE; +} + +/** + * nm_main_utils_setup_signals: + * @main_loop: the #GMainLoop to quit when SIGINT or SIGTERM is received + * + * Sets up signal handling for NetworkManager. + */ +void +nm_main_utils_setup_signals(GMainLoop *main_loop) +{ + g_return_if_fail(main_loop != NULL); + + signal(SIGPIPE, SIG_IGN); + + g_unix_signal_add(SIGHUP, sighup_handler, GINT_TO_POINTER(SIGHUP)); + if (nm_glib_check_version(2, 36, 0)) { + g_unix_signal_add(SIGUSR1, sighup_handler, GINT_TO_POINTER(SIGUSR1)); + g_unix_signal_add(SIGUSR2, sighup_handler, GINT_TO_POINTER(SIGUSR2)); + } else + nm_log_warn(LOGD_CORE, + "glib-version: cannot handle SIGUSR1 and SIGUSR2 signals. Consider upgrading " + "glib to 2.36.0 or newer"); + g_unix_signal_add(SIGINT, sigint_handler, main_loop); + g_unix_signal_add(SIGTERM, sigterm_handler, main_loop); +} + +gboolean +nm_main_utils_write_pidfile(const char *pidfile) +{ + char pid[16]; + int fd; + int errsv; + gboolean success = FALSE; + + if ((fd = open(pidfile, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 00644)) < 0) { + errsv = errno; + fprintf(stderr, _("Opening %s failed: %s\n"), pidfile, nm_strerror_native(errsv)); + return FALSE; + } + + g_snprintf(pid, sizeof(pid), "%d", getpid()); + if (write(fd, pid, strlen(pid)) < 0) { + errsv = errno; + fprintf(stderr, _("Writing to %s failed: %s\n"), pidfile, nm_strerror_native(errsv)); + } else + success = TRUE; + + if (nm_close(fd)) { + errsv = errno; + fprintf(stderr, _("Closing %s failed: %s\n"), pidfile, nm_strerror_native(errsv)); + } + + return success; +} + +void +nm_main_utils_ensure_statedir() +{ + gs_free char *parent = NULL; + int errsv; + + parent = g_path_get_dirname(NMSTATEDIR); + + /* Ensure parent state directories exists */ + if (parent && parent[0] == '/' && parent[1] != '\0' + && g_mkdir_with_parents(parent, 0755) != 0) { + errsv = errno; + fprintf(stderr, + "Cannot create parents for '%s': %s", + NMSTATEDIR, + nm_strerror_native(errsv)); + exit(1); + } + /* Ensure state directory exists */ + if (g_mkdir_with_parents(NMSTATEDIR, 0700) != 0) { + errsv = errno; + fprintf(stderr, "Cannot create '%s': %s", NMSTATEDIR, nm_strerror_native(errsv)); + exit(1); + } +} + +void +nm_main_utils_ensure_rundir() +{ + int errsv; + + /* Setup runtime directory */ + if (g_mkdir_with_parents(NMRUNDIR, 0755) != 0) { + errsv = errno; + fprintf(stderr, _("Cannot create '%s': %s"), NMRUNDIR, nm_strerror_native(errsv)); + exit(1); + } + + /* NM_CONFIG_DEVICE_STATE_DIR is used to determine whether NM is restarted or not. + * It is important to set NMConfigCmdLineOptions.first_start before creating + * the directory. */ + nm_assert(g_str_has_prefix(NM_CONFIG_DEVICE_STATE_DIR, NMRUNDIR "/")); + if (g_mkdir(NM_CONFIG_DEVICE_STATE_DIR, 0755) != 0) { + errsv = errno; + if (errsv != EEXIST) { + fprintf(stderr, + _("Cannot create '%s': %s"), + NM_CONFIG_DEVICE_STATE_DIR, + nm_strerror_native(errsv)); + exit(1); + } + } +} + +/** + * nm_main_utils_ensure_not_running_pidfile: + * @pidfile: the pid file + * + * Checks whether the pidfile already exists and contains PID of a running + * process. + * + * Exits with code 1 if a conflicting process is running. + */ +void +nm_main_utils_ensure_not_running_pidfile(const char *pidfile) +{ + gs_free char *contents = NULL; + gs_free char *proc_cmdline = NULL; + gsize len = 0; + long pid; + const char * process_name; + const char * prgname = g_get_prgname(); + + g_return_if_fail(prgname); + + if (!pidfile || !*pidfile) + return; + + if (!g_file_get_contents(pidfile, &contents, &len, NULL)) + return; + if (len <= 0) + return; + + errno = 0; + pid = strtol(contents, NULL, 10); + if (pid <= 0 || pid > 65536 || errno) + return; + + nm_clear_g_free(&contents); + proc_cmdline = g_strdup_printf("/proc/%ld/cmdline", pid); + if (!g_file_get_contents(proc_cmdline, &contents, &len, NULL)) + return; + + process_name = strrchr(contents, '/'); + if (process_name) + process_name++; + else + process_name = contents; + if (strcmp(process_name, prgname) == 0) { + /* Check that the process exists */ + if (kill(pid, 0) == 0) { + fprintf(stderr, _("%s is already running (pid %ld)\n"), prgname, pid); + exit(1); + } + } +} + +void +nm_main_utils_ensure_root() +{ + if (getuid() != 0) { + fprintf(stderr, _("You must be root to run %s!\n"), g_get_prgname() ?: ""); + exit(1); + } +} + +gboolean +nm_main_utils_early_setup(const char * progname, + int * argc, + char ** argv[], + GOptionEntry *options, + void (*option_context_hook)(gpointer user_data, GOptionContext *opt_ctx), + gpointer option_context_hook_data, + const char *summary) +{ + GOptionContext *opt_ctx = NULL; + GError * error = NULL; + gboolean success = FALSE; + int i; + const char * opt_fmt_log_level = NULL, *opt_fmt_log_domains = NULL; + const char ** opt_loc_log_level = NULL, **opt_loc_log_domains = NULL; + + /* Make GIO ignore the remote VFS service; otherwise it tries to use the + * session bus to contact the remote service, and NM shouldn't ever be + * talking on the session bus. See rh #588745 + */ + setenv("GIO_USE_VFS", "local", 1); + + /* + * Set the umask to 0022, which results in 0666 & ~0022 = 0644. + * Otherwise, if root (or an su'ing user) has a wacky umask, we could + * write out an unreadable resolv.conf. + */ + umask(022); + + /* Ensure gettext() gets the right environment (bgo #666516) */ + setlocale(LC_ALL, ""); + textdomain(GETTEXT_PACKAGE); + + for (i = 0; options[i].long_name; i++) { + NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") + if (!strcmp(options[i].long_name, "log-level")) { + opt_fmt_log_level = options[i].description; + opt_loc_log_level = &options[i].description; + options[i].description = + g_strdup_printf(options[i].description, nm_logging_all_levels_to_string()); + } else if (!strcmp(options[i].long_name, "log-domains")) { + opt_fmt_log_domains = options[i].description; + opt_loc_log_domains = &options[i].description; + options[i].description = + g_strdup_printf(options[i].description, nm_logging_all_domains_to_string()); + } + NM_PRAGMA_WARNING_REENABLE + } + + /* Parse options */ + opt_ctx = g_option_context_new(NULL); + g_option_context_set_translation_domain(opt_ctx, GETTEXT_PACKAGE); + g_option_context_set_ignore_unknown_options(opt_ctx, FALSE); + g_option_context_set_help_enabled(opt_ctx, TRUE); + g_option_context_add_main_entries(opt_ctx, options, NULL); + g_option_context_set_summary(opt_ctx, summary); + if (option_context_hook) + option_context_hook(option_context_hook_data, opt_ctx); + + success = g_option_context_parse(opt_ctx, argc, argv, &error); + if (!success) { + fprintf(stderr, + _("%s. Please use --help to see a list of valid options.\n"), + error->message); + g_clear_error(&error); + } + g_option_context_free(opt_ctx); + + if (opt_loc_log_level) { + g_free((char *) *opt_loc_log_level); + *opt_loc_log_level = opt_fmt_log_level; + } + if (opt_loc_log_domains) { + g_free((char *) *opt_loc_log_domains); + *opt_loc_log_domains = opt_fmt_log_domains; + } + + return success; +} diff --git a/src/core/main-utils.h b/src/core/main-utils.h new file mode 100644 index 0000000..2eb731a --- /dev/null +++ b/src/core/main-utils.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __MAIN_UTILS_H__ +#define __MAIN_UTILS_H__ + +void nm_main_utils_ensure_root(void); + +void nm_main_utils_setup_signals(GMainLoop *main_loop); + +void nm_main_utils_ensure_statedir(void); +void nm_main_utils_ensure_rundir(void); + +gboolean nm_main_utils_write_pidfile(const char *pidfile); + +void nm_main_utils_ensure_not_running_pidfile(const char *pidfile); + +gboolean nm_main_utils_early_setup(const char * progname, + int * argc, + char ** argv[], + GOptionEntry *options, + void (*option_context_hook)(gpointer user_data, + GOptionContext *opt_ctx), + gpointer option_context_hook_data, + const char *summary); + +/* The following functions are not implemented inside nm-main-utils.c, instead + * main.c and nm-iface-helper.c */ + +void nm_main_config_reload(int signal); + +#endif /* __MAIN_UTILS_H__ */ diff --git a/src/core/main.c b/src/core/main.c new file mode 100644 index 0000000..18a6333 --- /dev/null +++ b/src/core/main.c @@ -0,0 +1,546 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2017 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main-utils.h" +#include "nm-dbus-interface.h" +#include "NetworkManagerUtils.h" +#include "nm-manager.h" +#include "platform/nm-linux-platform.h" +#include "nm-dbus-manager.h" +#include "devices/nm-device.h" +#include "dhcp/nm-dhcp-manager.h" +#include "nm-config.h" +#include "nm-session-monitor.h" +#include "nm-dispatcher.h" +#include "settings/nm-settings.h" +#include "nm-auth-manager.h" +#include "nm-core-internal.h" +#include "nm-dbus-object.h" +#include "nm-connectivity.h" +#include "dns/nm-dns-manager.h" +#include "systemd/nm-sd.h" +#include "nm-netns.h" + +#if !defined(NM_DIST_VERSION) + #define NM_DIST_VERSION VERSION +#endif + +#define NM_DEFAULT_PID_FILE NMRUNDIR "/NetworkManager.pid" + +#define CONFIG_ATOMIC_SECTION_PREFIXES ((char **) NULL) + +static GMainLoop *main_loop = NULL; +static gboolean configure_and_quit = FALSE; + +static struct { + gboolean show_version; + gboolean print_config; + gboolean become_daemon; + gboolean g_fatal_warnings; + gboolean run_from_build_dir; + char * opt_log_level; + char * opt_log_domains; + char * pidfile; +} global_opt = { + .become_daemon = TRUE, +}; + +static void +_set_g_fatal_warnings(void) +{ + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal(G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal(fatal_mask); +} + +static void +_init_nm_debug(NMConfig *config) +{ + gs_free char *debug = NULL; + enum { + D_RLIMIT_CORE = (1 << 0), + D_FATAL_WARNINGS = (1 << 1), + }; + GDebugKey keys[] = { + {"RLIMIT_CORE", D_RLIMIT_CORE}, + {"fatal-warnings", D_FATAL_WARNINGS}, + }; + guint flags; + const char *env = getenv("NM_DEBUG"); + + debug = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG, + NM_CONFIG_GET_VALUE_NONE); + + flags = nm_utils_parse_debug_string(env, keys, G_N_ELEMENTS(keys)); + flags |= nm_utils_parse_debug_string(debug, keys, G_N_ELEMENTS(keys)); + +#if !defined(__SANITIZE_ADDRESS__) + if (NM_FLAGS_HAS(flags, D_RLIMIT_CORE)) { + /* only enable this, if explicitly requested, because it might + * expose sensitive data. */ + + struct rlimit limit = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + setrlimit(RLIMIT_CORE, &limit); + } +#endif + + if (NM_FLAGS_HAS(flags, D_FATAL_WARNINGS)) + _set_g_fatal_warnings(); +} + +void +nm_main_config_reload(int signal) +{ + NMConfigChangeFlags reload_flags; + + switch (signal) { + case SIGHUP: + reload_flags = NM_CONFIG_CHANGE_CAUSE_SIGHUP; + break; + case SIGUSR1: + reload_flags = NM_CONFIG_CHANGE_CAUSE_SIGUSR1; + break; + case SIGUSR2: + reload_flags = NM_CONFIG_CHANGE_CAUSE_SIGUSR2; + break; + default: + g_return_if_reached(); + } + + nm_log_info(LOGD_CORE, "reload configuration (signal %s)...", strsignal(signal)); + + /* The signal handler thread is only installed after + * creating NMConfig instance, and on shut down we + * no longer run the mainloop (to reach this point). + * + * Hence, a NMConfig singleton instance must always be + * available. */ + nm_config_reload(nm_config_get(), reload_flags, TRUE); +} + +static void +manager_configure_quit(NMManager *manager, gpointer user_data) +{ + nm_log_info(LOGD_CORE, "quitting now that startup is complete"); + g_main_loop_quit(main_loop); + configure_and_quit = TRUE; +} + +static int +print_config(NMConfigCmdLineOptions *config_cli) +{ + gs_unref_object NMConfig *config = NULL; + gs_free_error GError *error = NULL; + NMConfigData * config_data; + const char *const * warnings; + + nm_logging_setup("OFF", "ALL", NULL, NULL); + + config = nm_config_new(config_cli, CONFIG_ATOMIC_SECTION_PREFIXES, &error); + if (config == NULL) { + fprintf(stderr, _("Failed to read configuration: %s\n"), error->message); + return 7; + } + + config_data = nm_config_get_data(config); + fprintf(stdout, + "# NetworkManager configuration: %s\n", + nm_config_data_get_config_description(config_data)); + nm_config_data_log(config_data, "", "", nm_config_get_no_auto_default_file(config), stdout); + + warnings = nm_config_get_warnings(config); + if (warnings && warnings[0]) + fprintf(stdout, "\n"); + for (; warnings && warnings[0]; warnings++) + fprintf(stdout, "# WARNING: %s\n", warnings[0]); + + return 0; +} + +static void +do_early_setup(int *argc, char **argv[], NMConfigCmdLineOptions *config_cli) +{ + GOptionEntry options[] = {{"version", + 'V', + 0, + G_OPTION_ARG_NONE, + &global_opt.show_version, + N_("Print NetworkManager version and exit"), + NULL}, + {"no-daemon", + 'n', + G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, + &global_opt.become_daemon, + N_("Don't become a daemon"), + NULL}, + {"log-level", + 0, + 0, + G_OPTION_ARG_STRING, + &global_opt.opt_log_level, + N_("Log level: one of [%s]"), + "INFO"}, + {"log-domains", + 0, + 0, + G_OPTION_ARG_STRING, + &global_opt.opt_log_domains, + N_("Log domains separated by ',': any combination of [%s]"), + "PLATFORM,RFKILL,WIFI"}, + {"g-fatal-warnings", + 0, + 0, + G_OPTION_ARG_NONE, + &global_opt.g_fatal_warnings, + N_("Make all warnings fatal"), + NULL}, + {"pid-file", + 'p', + 0, + G_OPTION_ARG_FILENAME, + &global_opt.pidfile, + N_("Specify the location of a PID file"), + NM_DEFAULT_PID_FILE}, + {"run-from-build-dir", + 0, + 0, + G_OPTION_ARG_NONE, + &global_opt.run_from_build_dir, + "Run from build directory", + NULL}, + {"print-config", + 0, + 0, + G_OPTION_ARG_NONE, + &global_opt.print_config, + N_("Print NetworkManager configuration and exit"), + NULL}, + {NULL}}; + + if (!nm_main_utils_early_setup( + "NetworkManager", + argc, + argv, + options, + (void (*)(gpointer, GOptionContext *)) nm_config_cmd_line_options_add_to_entries, + config_cli, + _("NetworkManager monitors all network connections and automatically\nchooses the best " + "connection to use. It also allows the user to\nspecify wireless access points " + "which wireless cards in the computer\nshould associate with."))) + exit(1); + + global_opt.pidfile = global_opt.pidfile ?: g_strdup(NM_DEFAULT_PID_FILE); +} + +static gboolean +_dbus_manager_init(NMConfig *config) +{ + NMDBusManager * busmgr; + NMConfigConfigureAndQuitType c_a_q_type; + + busmgr = nm_dbus_manager_get(); + + c_a_q_type = nm_config_get_configure_and_quit(config); + + if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED) + return nm_dbus_manager_acquire_bus(busmgr, TRUE); + + if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { + /* D-Bus is useless in configure and quit mode -- we're eventually dropping + * off and potential clients would have no way of knowing whether we're + * finished already or didn't start yet. + * + * But we still create a nm_dbus_manager_get_dbus_connection() D-Bus connection + * so that we can talk to other services like firewalld. */ + return nm_dbus_manager_acquire_bus(busmgr, FALSE); + } + + nm_assert(c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD); + /* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM + * connection. And of course don't claim the D-Bus name. */ + return TRUE; +} + +/* + * main + * + */ +int +main(int argc, char *argv[]) +{ + gboolean success = FALSE; + NMManager * manager = NULL; + NMConfig * config; + gs_free_error GError * error = NULL; + gboolean wrote_pidfile = FALSE; + char * bad_domains = NULL; + NMConfigCmdLineOptions *config_cli; + guint sd_id = 0; + GError * error_invalid_logging_config = NULL; + const char *const * warnings; + int errsv; + + /* Known to cause a possible deadlock upon GDBus initialization: + * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */ + g_type_ensure(G_TYPE_SOCKET); + g_type_ensure(G_TYPE_DBUS_CONNECTION); + g_type_ensure(NM_TYPE_DBUS_MANAGER); + + _nm_utils_is_manager_process = TRUE; + + main_loop = g_main_loop_new(NULL, FALSE); + + /* we determine a first-start (contrary to a restart during the same boot) + * based on the existence of NM_CONFIG_DEVICE_STATE_DIR directory. */ + config_cli = nm_config_cmd_line_options_new( + !g_file_test(NM_CONFIG_DEVICE_STATE_DIR, G_FILE_TEST_IS_DIR)); + + do_early_setup(&argc, &argv, config_cli); + + if (global_opt.g_fatal_warnings) + _set_g_fatal_warnings(); + + if (global_opt.show_version) { + fprintf(stdout, NM_DIST_VERSION "\n"); + exit(0); + } + + if (global_opt.print_config) { + int result; + + result = print_config(config_cli); + nm_config_cmd_line_options_free(config_cli); + exit(result); + } + + nm_main_utils_ensure_root(); + + nm_main_utils_ensure_not_running_pidfile(global_opt.pidfile); + + nm_main_utils_ensure_statedir(); + nm_main_utils_ensure_rundir(); + + /* When running from the build directory, determine our build directory + * base and set helper paths in the build tree */ + if (global_opt.run_from_build_dir) { + char *path, *slash; + int g; + + /* exe is /src/.libs/lt-NetworkManager, so chop off + * the last three components */ + path = realpath("/proc/self/exe", NULL); + g_assert(path != NULL); + for (g = 0; g < 3; ++g) { + slash = strrchr(path, '/'); + g_assert(slash != NULL); + *slash = '\0'; + } + + /* don't free these strings, we need them for the entire + * process lifetime */ + nm_dhcp_helper_path = g_strdup_printf("%s/src/dhcp/nm-dhcp-helper", path); + + g_free(path); + } + + if (!nm_logging_setup(global_opt.opt_log_level, + global_opt.opt_log_domains, + &bad_domains, + &error)) { + fprintf(stderr, + _("%s. Please use --help to see a list of valid options.\n"), + error->message); + exit(1); + } + + /* Read the config file and CLI overrides */ + config = nm_config_setup(config_cli, CONFIG_ATOMIC_SECTION_PREFIXES, &error); + nm_config_cmd_line_options_free(config_cli); + config_cli = NULL; + if (config == NULL) { + fprintf(stderr, _("Failed to read configuration: %s\n"), error->message); + exit(1); + } + + _init_nm_debug(config); + + /* Initialize logging from config file *only* if not explicitly + * specified by commandline. + */ + if (global_opt.opt_log_level == NULL && global_opt.opt_log_domains == NULL) { + if (!nm_logging_setup(nm_config_get_log_level(config), + nm_config_get_log_domains(config), + &bad_domains, + &error_invalid_logging_config)) { + /* ignore error, and print the failure reason below. + * Likewise, print about bad_domains below. */ + } + } + + if (global_opt.become_daemon && !nm_config_get_is_debug(config)) { + if (daemon(0, 0) < 0) { + errsv = errno; + fprintf(stderr, + _("Could not daemonize: %s [error %u]\n"), + nm_strerror_native(errsv), + errsv); + exit(1); + } + wrote_pidfile = nm_main_utils_write_pidfile(global_opt.pidfile); + } + + /* Set up unix signal handling - before creating threads, but after daemonizing! */ + nm_main_utils_setup_signals(main_loop); + + { + gs_free char *v = NULL; + + v = nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_LOGGING, + NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + nm_logging_init(v, nm_config_get_is_debug(config)); + } + + nm_log_info(LOGD_CORE, + "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s%s)", + nm_config_get_first_start(config) ? "for the first time" : "after a restart", + NM_MORE_ASSERTS != 0 ? ", asserts:" G_STRINGIFY(NM_MORE_ASSERTS) : ""); + + nm_log_info(LOGD_CORE, + "Read config: %s", + nm_config_data_get_config_description(nm_config_get_data(config))); + nm_config_data_log(nm_config_get_data(config), + "CONFIG: ", + " ", + nm_config_get_no_auto_default_file(config), + NULL); + + if (error_invalid_logging_config) { + nm_log_warn(LOGD_CORE, + "config: invalid logging configuration: %s", + error_invalid_logging_config->message); + g_clear_error(&error_invalid_logging_config); + } + if (bad_domains) { + nm_log_warn(LOGD_CORE, + "config: invalid logging domains '%s' from %s", + bad_domains, + (global_opt.opt_log_level == NULL && global_opt.opt_log_domains == NULL) + ? "config file" + : "command line"); + nm_clear_g_free(&bad_domains); + } + + warnings = nm_config_get_warnings(config); + for (; warnings && *warnings; warnings++) + nm_log_warn(LOGD_CORE, "config: %s", *warnings); + nm_config_clear_warnings(config); + + /* the first access to State causes the file to be read (and possibly print a warning) */ + nm_config_state_get(config); + + nm_log_dbg(LOGD_CORE, + "WEXT support is %s", +#if HAVE_WEXT + "enabled" +#else + "disabled" +#endif + ); + + if (!_dbus_manager_init(config)) + goto done_no_manager; + + nm_linux_platform_setup(); + + NM_UTILS_KEEP_ALIVE(config, nm_netns_get(), "NMConfig-depends-on-NMNetns"); + + nm_auth_manager_setup(nm_config_data_get_main_auth_polkit(nm_config_get_data_orig(config))); + + manager = nm_manager_setup(); + + nm_dbus_manager_start(nm_dbus_manager_get(), nm_manager_dbus_set_property_handle, manager); + + g_signal_connect(manager, + NM_MANAGER_CONFIGURE_QUIT, + G_CALLBACK(manager_configure_quit), + config); + + if (!nm_manager_start(manager, &error)) { + nm_log_err(LOGD_CORE, "failed to initialize: %s", error->message); + goto done; + } + + nm_platform_process_events(NM_PLATFORM_GET); + + /* Make sure the loopback interface is up. If interface is down, we bring + * it up and kernel will assign it link-local IPv4 and IPv6 addresses. If + * it was already up, we assume is in clean state. + * + * TODO: it might be desirable to check the list of addresses and compare + * it with a list of expected addresses (one of the protocol families + * could be disabled). The 'lo' interface is sometimes used for assigning + * global addresses so their availability doesn't depend on the state of + * physical interfaces. + */ + nm_log_dbg(LOGD_CORE, "setting up local loopback"); + nm_platform_link_set_up(NM_PLATFORM_GET, 1, NULL); + + success = TRUE; + + if (configure_and_quit == FALSE) { + sd_id = nm_sd_event_attach_default(); + + g_main_loop_run(main_loop); + } + +done: + + /* write the device-state to file. Note that we only persist the + * state here. We don't bother updating the state as devices + * change during regular operation. If NM is killed with SIGKILL, + * it misses to update the state. */ + nm_manager_write_device_state_all(manager); + + nm_manager_stop(manager); + + nm_config_state_set(config, TRUE, TRUE); + + nm_dns_manager_stop(nm_dns_manager_get()); + + nm_settings_kf_db_write(NM_SETTINGS_GET); + +done_no_manager: + if (global_opt.pidfile && wrote_pidfile) + unlink(global_opt.pidfile); + + nm_log_info(LOGD_CORE, "exiting (%s)", success ? "success" : "error"); + + nm_clear_g_source(&sd_id); + + exit(success ? 0 : 1); +} diff --git a/src/core/meson.build b/src/core/meson.build new file mode 100644 index 0000000..ada4d7a --- /dev/null +++ b/src/core/meson.build @@ -0,0 +1,332 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +src_inc = include_directories('.') + +core_plugin_dep = declare_dependency( + sources: libnm_core_enum_sources[1], + include_directories: [ + src_inc, + top_inc, + shared_inc, + libnm_core_inc, + ], + dependencies: [ + glib_dep, + ], +) + +core_default_dep = declare_dependency( + sources: libnm_core_enum_sources[1], + include_directories: src_inc, + dependencies: [ + libnm_core_nm_default_dep, + libnm_log_core_dep, + libnm_platform_dep, + ], +) + +install_data( + 'org.freedesktop.NetworkManager.conf', + install_dir: dbus_conf_dir, +) + +subdir('systemd') + +core_plugins = [] + +daemon_c_flags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON'] + +platform_wifi_wext_source = files() +if enable_wext + platform_wifi_wext_source += files('platform/wifi/nm-wifi-utils-wext.c') +endif + +libNetworkManagerBase = static_library( + 'NetworkManagerBase', + sources: files( + 'dhcp/nm-dhcp-client.c', + 'dhcp/nm-dhcp-manager.c', + 'dhcp/nm-dhcp-nettools.c', + 'dhcp/nm-dhcp-systemd.c', + 'dhcp/nm-dhcp-utils.c', + 'dhcp/nm-dhcp-options.c', + 'ndisc/nm-lndp-ndisc.c', + 'ndisc/nm-ndisc.c', + 'platform/wifi/nm-wifi-utils-nl80211.c', + 'platform/wifi/nm-wifi-utils.c', + 'platform/wpan/nm-wpan-utils.c', + 'platform/nm-linux-platform.c', + 'platform/nm-platform.c', + 'platform/nmp-object.c', + 'platform/nmp-rules-manager.c', + 'main-utils.c', + 'NetworkManagerUtils.c', + 'nm-core-utils.c', + 'nm-dbus-object.c', + 'nm-dbus-utils.c', + 'nm-netns.c', + 'nm-l3-config-data.c', + 'nm-l3-ipv4ll.c', + 'nm-l3cfg.c', + 'nm-ip-config.c', + 'nm-ip4-config.c', + 'nm-ip6-config.c', + ) + platform_wifi_wext_source, + dependencies: [ + core_default_dep, + libn_dhcp4_dep, + libnm_keyfile_dep, + libnm_core_dep, + libnm_systemd_shared_dep, + libnm_udev_aux_dep, + libsystemd_dep, + libudev_dep, + ], + c_args: daemon_c_flags, +) + +nm_deps = [ + core_default_dep, + dl_dep, + libn_acd_dep, + libndp_dep, + libudev_dep, + logind_dep, +] + +if enable_concheck + nm_deps += libcurl_dep +endif + +if enable_libaudit + nm_deps += libaudit_dep +endif + +if enable_libpsl + nm_deps += libpsl_dep +endif + +if enable_selinux + nm_deps += selinux_dep +endif + +libNetworkManager = static_library( + 'NetworkManager', + sources: files( + 'devices/nm-acd-manager.c', + 'devices/nm-device-6lowpan.c', + 'devices/nm-device-bond.c', + 'devices/nm-device-bridge.c', + 'devices/nm-device.c', + 'devices/nm-device-dummy.c', + 'devices/nm-device-ethernet.c', + 'devices/nm-device-ethernet-utils.c', + 'devices/nm-device-factory.c', + 'devices/nm-device-generic.c', + 'devices/nm-device-infiniband.c', + 'devices/nm-device-ip-tunnel.c', + 'devices/nm-device-macsec.c', + 'devices/nm-device-macvlan.c', + 'devices/nm-device-ppp.c', + 'devices/nm-device-tun.c', + 'devices/nm-device-veth.c', + 'devices/nm-device-vlan.c', + 'devices/nm-device-vrf.c', + 'devices/nm-device-vxlan.c', + 'devices/nm-device-wireguard.c', + 'devices/nm-device-wpan.c', + 'devices/nm-lldp-listener.c', + 'dhcp/nm-dhcp-dhclient.c', + 'dhcp/nm-dhcp-dhclient-utils.c', + 'dhcp/nm-dhcp-dhcpcanon.c', + 'dhcp/nm-dhcp-dhcpcd.c', + 'dhcp/nm-dhcp-listener.c', + 'dns/nm-dns-dnsmasq.c', + 'dns/nm-dns-manager.c', + 'dns/nm-dns-plugin.c', + 'dns/nm-dns-systemd-resolved.c', + 'dns/nm-dns-unbound.c', + 'dnsmasq/nm-dnsmasq-manager.c', + 'dnsmasq/nm-dnsmasq-utils.c', + 'ppp/nm-ppp-manager-call.c', + 'settings/plugins/keyfile/nms-keyfile-storage.c', + 'settings/plugins/keyfile/nms-keyfile-plugin.c', + 'settings/plugins/keyfile/nms-keyfile-reader.c', + 'settings/plugins/keyfile/nms-keyfile-utils.c', + 'settings/plugins/keyfile/nms-keyfile-writer.c', + 'settings/nm-agent-manager.c', + 'settings/nm-secret-agent.c', + 'settings/nm-settings.c', + 'settings/nm-settings-connection.c', + 'settings/nm-settings-plugin.c', + 'settings/nm-settings-storage.c', + 'settings/nm-settings-utils.c', + 'supplicant/nm-supplicant-config.c', + 'supplicant/nm-supplicant-interface.c', + 'supplicant/nm-supplicant-manager.c', + 'supplicant/nm-supplicant-settings-verify.c', + 'vpn/nm-vpn-connection.c', + 'vpn/nm-vpn-manager.c', + 'nm-active-connection.c', + 'nm-act-request.c', + 'nm-audit-manager.c', + 'nm-auth-manager.c', + 'nm-auth-utils.c', + 'nm-dbus-manager.c', + 'nm-checkpoint.c', + 'nm-checkpoint-manager.c', + 'nm-config.c', + 'nm-config-data.c', + 'nm-connectivity.c', + 'nm-dcb.c', + 'nm-dhcp-config.c', + 'nm-dispatcher.c', + 'nm-firewall-manager.c', + 'nm-hostname-manager.c', + 'nm-keep-alive.c', + 'nm-manager.c', + 'nm-pacrunner-manager.c', + 'nm-policy.c', + 'nm-proxy-config.c', + 'nm-rfkill-manager.c', + 'nm-session-monitor.c', + 'nm-sleep-monitor.c', + ), + dependencies: nm_deps, + c_args: daemon_c_flags, + link_with: [ + libNetworkManagerBase, + libnm_systemd_core, + libnm_systemd_shared, + ], +) + +executable( + 'nm-iface-helper', + 'nm-iface-helper.c', + dependencies: [ + core_default_dep, + dl_dep, + libndp_dep, + libudev_dep, + libn_acd_dep, + ], + c_args: daemon_c_flags, + link_with: [ + libNetworkManagerBase, + libnm_systemd_core, + libnm_systemd_shared, + ], + link_args: ldflags_linker_script_binary, + link_depends: linker_script_binary, + install: true, + install_dir: nm_libexecdir, +) + +if enable_tests + test_c_flags = daemon_c_flags + ['-DNETWORKMANAGER_COMPILATION_TEST'] + if require_root_tests + test_c_flags += ['-DREQUIRE_ROOT_TESTS=1'] + endif + + libNetworkManagerTest = static_library( + 'NetworkManagerTest', + sources: files( + 'ndisc/nm-fake-ndisc.c', + 'platform/tests/test-common.c', + 'platform/nm-fake-platform.c', + ), + dependencies: core_default_dep, + c_args: test_c_flags, + link_with: libNetworkManager, + ) + + libNetworkManagerTest_dep = declare_dependency( + dependencies: core_default_dep, + link_with: libNetworkManagerTest, + ) + + subdir('dnsmasq/tests') + subdir('ndisc/tests') + subdir('platform/tests') + subdir('supplicant/tests') + subdir('tests') +endif + +subdir('dhcp') + +if enable_ppp + subdir('ppp') +endif + +subdir('devices') +subdir('initrd') +subdir('settings/plugins') + +# NetworkManager binary + +# libNetworkManager.a, as built by meson doesn't contain all symbols +# from libNetworkManagerBase.a and other static libraries, unless we +# add dependencies with link_whole, only supported in meson >= 0.46. +# Create an executable with full symbols that we use in place of the +# library to enumerate the symbols. +NetworkManager_all_sym = executable( + 'NetworkManager-all-sym', + 'main.c', + dependencies: nm_deps, + c_args: daemon_c_flags, + link_args: '-Wl,--no-gc-sections', + link_whole: [ + libNetworkManager, + libNetworkManagerBase, + libnm_core, + ], +) + +ver_script = custom_target( + 'NetworkManager.ver', + output: 'NetworkManager.ver', + depends: [ + NetworkManager_all_sym, + core_plugins, + ], + command: [ + join_paths(source_root, 'tools', 'create-exports-NetworkManager.sh'), + '--called-from-build', + source_root, + ], +) + +NetworkManager = executable( + 'NetworkManager', + 'main.c', + dependencies: nm_deps, + c_args: daemon_c_flags, + link_with: libNetworkManager, + link_args: [ + '-rdynamic', + '-Wl,--version-script,@0@'.format(ver_script.full_path()), + ], + link_depends: ver_script, + install: true, + install_dir: nm_sbindir, +) + +if enable_tests + foreach plugin: core_plugins + plugin_path = plugin.full_path() + + test( + 'sym/' + plugin_path.split('/')[-1], + NetworkManager, + args: '--version', + env: ['LD_BIND_NOW=1', 'LD_PRELOAD=' + plugin_path], + ) + endforeach +endif + +test( + 'check-config-options', + find_program(join_paths(source_root, 'tools', 'check-config-options.sh')), + args: source_root, +) diff --git a/src/core/ndisc/nm-fake-ndisc.c b/src/core/ndisc/nm-fake-ndisc.c new file mode 100644 index 0000000..5bcdb0d --- /dev/null +++ b/src/core/ndisc/nm-fake-ndisc.c @@ -0,0 +1,415 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-fake-ndisc.h" + +#include + +#include "nm-ndisc-private.h" + +#define _NMLOG_PREFIX_NAME "ndisc-fake" + +/*****************************************************************************/ + +typedef struct { + guint id; + guint when; + + NMNDiscDHCPLevel dhcp_level; + GArray * gateways; + GArray * prefixes; + GArray * dns_servers; + GArray * dns_domains; + int hop_limit; + guint32 mtu; +} FakeRa; + +typedef struct { + struct in6_addr network; + struct in6_addr gateway; + gint64 expiry_msec; + gint64 expiry_preferred_msec; + int plen; + NMIcmpv6RouterPref preference; +} FakePrefix; + +/*****************************************************************************/ + +enum { + RS_SENT, + LAST_SIGNAL, +}; +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + guint receive_ra_id; + GSList *ras; +} NMFakeNDiscPrivate; + +struct _NMFakeRNDisc { + NMNDisc parent; + NMFakeNDiscPrivate _priv; +}; + +struct _NMFakeRNDiscClass { + NMNDiscClass parent; +}; + +G_DEFINE_TYPE(NMFakeNDisc, nm_fake_ndisc, NM_TYPE_NDISC) + +#define NM_FAKE_NDISC_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMFakeNDisc, NM_IS_FAKE_NDISC, NMNDisc) + +/*****************************************************************************/ + +static void +fake_ra_free(gpointer data) +{ + FakeRa *ra = data; + + g_array_free(ra->gateways, TRUE); + g_array_free(ra->prefixes, TRUE); + g_array_free(ra->dns_servers, TRUE); + g_array_free(ra->dns_domains, TRUE); + g_free(ra); +} + +static void +ra_dns_domain_free(gpointer data) +{ + g_free(((NMNDiscDNSDomain *) (data))->domain); +} + +static FakeRa * +find_ra(GSList *ras, guint id) +{ + GSList *iter; + + for (iter = ras; iter; iter = iter->next) { + if (((FakeRa *) iter->data)->id == id) + return iter->data; + } + return NULL; +} + +guint +nm_fake_ndisc_add_ra(NMFakeNDisc * self, + guint seconds_after_previous, + NMNDiscDHCPLevel dhcp_level, + int hop_limit, + guint32 mtu) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); + static guint counter = 1; + FakeRa * ra; + + ra = g_malloc0(sizeof(*ra)); + ra->id = counter++; + ra->when = seconds_after_previous; + ra->dhcp_level = dhcp_level; + ra->hop_limit = hop_limit; + ra->mtu = mtu; + ra->gateways = g_array_new(FALSE, FALSE, sizeof(NMNDiscGateway)); + ra->prefixes = g_array_new(FALSE, FALSE, sizeof(FakePrefix)); + ra->dns_servers = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSServer)); + ra->dns_domains = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSDomain)); + g_array_set_clear_func(ra->dns_domains, ra_dns_domain_free); + + priv->ras = g_slist_append(priv->ras, ra); + return ra->id; +} + +void +nm_fake_ndisc_add_gateway(NMFakeNDisc * self, + guint ra_id, + const char * addr, + gint64 expiry_msec, + NMIcmpv6RouterPref preference) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); + FakeRa * ra = find_ra(priv->ras, ra_id); + NMNDiscGateway * gw; + + g_assert(ra); + + gw = nm_g_array_append_new(ra->gateways, NMNDiscGateway); + if (inet_pton(AF_INET6, addr, &gw->address) != 1) + g_assert_not_reached(); + gw->expiry_msec = expiry_msec; + gw->preference = preference; +} + +void +nm_fake_ndisc_add_prefix(NMFakeNDisc * self, + guint ra_id, + const char * network, + guint plen, + const char * gateway, + gint64 expiry_msec, + gint64 expiry_preferred_msec, + NMIcmpv6RouterPref preference) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); + FakeRa * ra = find_ra(priv->ras, ra_id); + FakePrefix * prefix; + + g_assert(ra); + + prefix = nm_g_array_append_new(ra->prefixes, FakePrefix); + *prefix = (FakePrefix){ + .plen = plen, + .expiry_msec = expiry_msec, + .expiry_preferred_msec = expiry_preferred_msec, + .preference = preference, + }; + if (inet_pton(AF_INET6, network, &prefix->network) != 1) + g_assert_not_reached(); + if (inet_pton(AF_INET6, gateway, &prefix->gateway) != 1) + g_assert_not_reached(); +} + +void +nm_fake_ndisc_add_dns_server(NMFakeNDisc *self, + guint ra_id, + const char * address, + gint64 expiry_msec) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); + FakeRa * ra = find_ra(priv->ras, ra_id); + NMNDiscDNSServer * dns; + + g_assert(ra); + + dns = nm_g_array_append_new(ra->dns_servers, NMNDiscDNSServer); + + dns->expiry_msec = expiry_msec; + if (inet_pton(AF_INET6, address, &dns->address) != 1) + g_assert_not_reached(); +} + +void +nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, guint ra_id, const char *domain, gint64 expiry_msec) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); + FakeRa * ra = find_ra(priv->ras, ra_id); + NMNDiscDNSDomain * dns; + + g_assert(ra); + + dns = nm_g_array_append_new(ra->dns_domains, NMNDiscDNSDomain); + + dns->domain = g_strdup(domain); + dns->expiry_msec = expiry_msec; +} + +gboolean +nm_fake_ndisc_done(NMFakeNDisc *self) +{ + return !NM_FAKE_NDISC_GET_PRIVATE(self)->ras; +} + +/*****************************************************************************/ + +static gboolean +send_rs(NMNDisc *ndisc, GError **error) +{ + _LOGT("send_rs()"); + g_signal_emit(ndisc, signals[RS_SENT], 0); + return TRUE; +} + +static gboolean +receive_ra(gpointer user_data) +{ + NMFakeNDisc * self = user_data; + NMFakeNDiscPrivate * priv = NM_FAKE_NDISC_GET_PRIVATE(self); + NMNDisc * ndisc = NM_NDISC(self); + NMNDiscDataInternal *rdata = ndisc->rdata; + FakeRa * ra = priv->ras->data; + NMNDiscConfigMap changed = 0; + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + guint i; + NMNDiscDHCPLevel dhcp_level; + + priv->receive_ra_id = 0; + + /* preserve the "most managed" level on updates. */ + dhcp_level = MAX(rdata->public.dhcp_level, ra->dhcp_level); + + if (rdata->public.dhcp_level != dhcp_level) { + rdata->public.dhcp_level = dhcp_level; + changed |= NM_NDISC_CONFIG_DHCP_LEVEL; + } + + for (i = 0; i < ra->gateways->len; i++) { + const NMNDiscGateway *item = &g_array_index(ra->gateways, NMNDiscGateway, i); + + if (nm_ndisc_add_gateway(ndisc, item, now_msec)) + changed |= NM_NDISC_CONFIG_GATEWAYS; + } + + for (i = 0; i < ra->prefixes->len; i++) { + FakePrefix * item = &g_array_index(ra->prefixes, FakePrefix, i); + const NMNDiscRoute route = { + .network = item->network, + .plen = item->plen, + .gateway = item->gateway, + .expiry_msec = item->expiry_msec, + .preference = item->preference, + }; + + g_assert(route.plen > 0 && route.plen <= 128); + + if (nm_ndisc_add_route(ndisc, &route, now_msec)) + changed |= NM_NDISC_CONFIG_ROUTES; + + if (item->plen == 64) { + const NMNDiscAddress address = { + .address = item->network, + .expiry_msec = item->expiry_msec, + .expiry_preferred_msec = item->expiry_preferred_msec, + .dad_counter = 0, + }; + + if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec)) + changed |= NM_NDISC_CONFIG_ADDRESSES; + } + } + + for (i = 0; i < ra->dns_servers->len; i++) { + const NMNDiscDNSServer *item = &g_array_index(ra->dns_servers, NMNDiscDNSServer, i); + + if (nm_ndisc_add_dns_server(ndisc, item, now_msec)) + changed |= NM_NDISC_CONFIG_DNS_SERVERS; + } + + for (i = 0; i < ra->dns_domains->len; i++) { + const NMNDiscDNSDomain *item = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, i); + + if (nm_ndisc_add_dns_domain(ndisc, item, now_msec)) + changed |= NM_NDISC_CONFIG_DNS_DOMAINS; + } + + if (rdata->public.mtu != ra->mtu) { + rdata->public.mtu = ra->mtu; + changed |= NM_NDISC_CONFIG_MTU; + } + + if (rdata->public.hop_limit != ra->hop_limit) { + rdata->public.hop_limit = ra->hop_limit; + changed |= NM_NDISC_CONFIG_HOP_LIMIT; + } + + priv->ras = g_slist_remove(priv->ras, priv->ras->data); + fake_ra_free(ra); + + nm_ndisc_ra_received(NM_NDISC(self), now_msec, changed); + + /* Schedule next RA */ + if (priv->ras) { + ra = priv->ras->data; + priv->receive_ra_id = g_timeout_add_seconds(ra->when, receive_ra, self); + } + + return G_SOURCE_REMOVE; +} + +static void +start(NMNDisc *ndisc) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(ndisc); + FakeRa * ra; + + /* Queue up the first fake RA */ + g_assert(priv->ras); + ra = priv->ras->data; + + g_assert(!priv->receive_ra_id); + priv->receive_ra_id = g_timeout_add_seconds(ra->when, receive_ra, ndisc); +} + +static void +stop(NMNDisc *ndisc) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(ndisc); + + nm_clear_g_source(&priv->receive_ra_id); +} + +void +nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self) +{ + if (!NM_FAKE_NDISC_GET_PRIVATE(self)->receive_ra_id) + start(NM_NDISC(self)); +} + +/*****************************************************************************/ + +static void +nm_fake_ndisc_init(NMFakeNDisc *fake_ndisc) +{} + +NMNDisc * +nm_fake_ndisc_new(int ifindex, const char *ifname) +{ + return g_object_new(NM_TYPE_FAKE_NDISC, + NM_NDISC_IFINDEX, + ifindex, + NM_NDISC_IFNAME, + ifname, + NM_NDISC_NODE_TYPE, + (int) NM_NDISC_NODE_TYPE_HOST, + NM_NDISC_STABLE_TYPE, + (int) NM_UTILS_STABLE_TYPE_UUID, + NM_NDISC_NETWORK_ID, + "fake", + NM_NDISC_MAX_ADDRESSES, + NM_NDISC_MAX_ADDRESSES_DEFAULT, + NM_NDISC_ROUTER_SOLICITATIONS, + NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT, + NM_NDISC_ROUTER_SOLICITATION_INTERVAL, + NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL, + NM_NDISC_RA_TIMEOUT, + 30u, + NULL); +} + +static void +dispose(GObject *object) +{ + NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(object); + + nm_clear_g_source(&priv->receive_ra_id); + + g_slist_free_full(priv->ras, fake_ra_free); + priv->ras = NULL; + + G_OBJECT_CLASS(nm_fake_ndisc_parent_class)->dispose(object); +} + +static void +nm_fake_ndisc_class_init(NMFakeNDiscClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMNDiscClass *ndisc_class = NM_NDISC_CLASS(klass); + + object_class->dispose = dispose; + + ndisc_class->start = start; + ndisc_class->stop = stop; + ndisc_class->send_rs = send_rs; + + signals[RS_SENT] = g_signal_new(NM_FAKE_NDISC_RS_SENT, + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/core/ndisc/nm-fake-ndisc.h b/src/core/ndisc/nm-fake-ndisc.h new file mode 100644 index 0000000..677f15f --- /dev/null +++ b/src/core/ndisc/nm-fake-ndisc.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_FAKE_NDISC_H__ +#define __NETWORKMANAGER_FAKE_NDISC_H__ + +#include "nm-ndisc.h" + +#define NM_TYPE_FAKE_NDISC (nm_fake_ndisc_get_type()) +#define NM_FAKE_NDISC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_FAKE_NDISC, NMFakeNDisc)) +#define NM_FAKE_NDISC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_FAKE_NDISC, NMFakeNDiscClass)) +#define NM_IS_FAKE_NDISC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_FAKE_NDISC)) +#define NM_IS_FAKE_NDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_FAKE_NDISC)) +#define NM_FAKE_NDISC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_FAKE_NDISC, NMFakeNDiscClass)) + +#define NM_FAKE_NDISC_RS_SENT "rs-sent" + +typedef struct _NMFakeRNDisc NMFakeNDisc; +typedef struct _NMFakeRNDiscClass NMFakeNDiscClass; + +GType nm_fake_ndisc_get_type(void); + +NMNDisc *nm_fake_ndisc_new(int ifindex, const char *ifname); + +guint nm_fake_ndisc_add_ra(NMFakeNDisc * self, + guint seconds, + NMNDiscDHCPLevel dhcp_level, + int hop_limit, + guint32 mtu); + +void nm_fake_ndisc_add_gateway(NMFakeNDisc * self, + guint ra_id, + const char * addr, + gint64 expiry_msec, + NMIcmpv6RouterPref preference); + +void nm_fake_ndisc_add_prefix(NMFakeNDisc * self, + guint ra_id, + const char * network, + guint plen, + const char * gateway, + gint64 expiry_msec, + gint64 expiry_preferred_msec, + NMIcmpv6RouterPref preference); + +void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self, + guint ra_id, + const char * address, + gint64 expiry_msec); + +void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, + guint ra_id, + const char * domain, + gint64 expiry_msec); + +void nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self); + +gboolean nm_fake_ndisc_done(NMFakeNDisc *self); + +#endif /* __NETWORKMANAGER_FAKE_NDISC_H__ */ diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c new file mode 100644 index 0000000..7abd197 --- /dev/null +++ b/src/core/ndisc/nm-lndp-ndisc.c @@ -0,0 +1,826 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-lndp-ndisc.h" + +#include +#include +/* stdarg.h included because of a bug in ndp.h */ +#include +#include + +#include "nm-glib-aux/nm-str-buf.h" +#include "systemd/nm-sd-utils-shared.h" +#include "nm-ndisc-private.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-platform/nmp-netns.h" + +#define _NMLOG_PREFIX_NAME "ndisc-lndp" + +/*****************************************************************************/ + +typedef struct { + struct ndp *ndp; + GSource * event_source; +} NMLndpNDiscPrivate; + +/*****************************************************************************/ + +struct _NMLndpNDisc { + NMNDisc parent; + NMLndpNDiscPrivate _priv; +}; + +struct _NMLndpNDiscClass { + NMNDiscClass parent; +}; + +/*****************************************************************************/ + +G_DEFINE_TYPE(NMLndpNDisc, nm_lndp_ndisc, NM_TYPE_NDISC) + +#define NM_LNDP_NDISC_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMLndpNDisc, NM_IS_LNDP_NDISC, NMNDisc) + +/*****************************************************************************/ + +static gboolean +send_rs(NMNDisc *ndisc, GError **error) +{ + NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + struct ndp_msg * msg; + int errsv; + + errsv = ndp_msg_new(&msg, NDP_MSG_RS); + if (errsv) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot create router solicitation"); + return FALSE; + } + ndp_msg_ifindex_set(msg, nm_ndisc_get_ifindex(ndisc)); + + errsv = ndp_msg_send(priv->ndp, msg); + ndp_msg_destroy(msg); + if (errsv) { + errsv = nm_errno_native(errsv); + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "%s (%d)", + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + return TRUE; +} + +static NMIcmpv6RouterPref +_route_preference_coerce(enum ndp_route_preference pref) +{ +#define _ASSERT_ENUM(v1, v2) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT((NMIcmpv6RouterPref)(v1) == (v2)); \ + G_STATIC_ASSERT((enum ndp_route_preference)(v2) == (v1)); \ + G_STATIC_ASSERT((gint64)(v1) == (v2)); \ + G_STATIC_ASSERT((gint64)(v2) == (v1)); \ + } \ + G_STMT_END + + switch (pref) { + case NDP_ROUTE_PREF_LOW: + case NDP_ROUTE_PREF_MEDIUM: + case NDP_ROUTE_PREF_HIGH: + _ASSERT_ENUM(NDP_ROUTE_PREF_LOW, NM_ICMPV6_ROUTER_PREF_LOW); + _ASSERT_ENUM(NDP_ROUTE_PREF_MEDIUM, NM_ICMPV6_ROUTER_PREF_MEDIUM); + _ASSERT_ENUM(NDP_ROUTE_PREF_HIGH, NM_ICMPV6_ROUTER_PREF_HIGH); + return (NMIcmpv6RouterPref) pref; + } + + /* unexpected value must be treated as MEDIUM (RFC 4191). */ + return NM_ICMPV6_ROUTER_PREF_MEDIUM; +} + +static int +receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) +{ + NMNDisc * ndisc = (NMNDisc *) user_data; + NMNDiscDataInternal *rdata = ndisc->rdata; + NMNDiscConfigMap changed = 0; + struct ndp_msgra * msgra = ndp_msgra(msg); + struct in6_addr gateway_addr; + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + int offset; + int hop_limit; + guint32 val; + + /* Router discovery is subject to the following RFC documents: + * + * http://tools.ietf.org/html/rfc4861 + * http://tools.ietf.org/html/rfc4862 + * + * The biggest difference from good old DHCP is that all configuration + * items have their own lifetimes and they are merged from various + * sources. Router discovery is *not* contract-based, so there is *no* + * single time when the configuration is finished and updates can + * come at any time. + */ + _LOGD("received router advertisement at timestamp %" G_GINT64_FORMAT ".%03d seconds", + now_msec / 1000, + (int) (now_msec % 1000)); + + gateway_addr = *ndp_msg_addrto(msg); + if (IN6_IS_ADDR_UNSPECIFIED(&gateway_addr)) + g_return_val_if_reached(0); + + /* DHCP level: + * + * The problem with DHCP level is what to do if subsequent + * router advertisements carry different flags. Currently, we just + * rewrite the flag with every inbound RA. + */ + { + NMNDiscDHCPLevel dhcp_level; + + if (ndp_msgra_flag_managed(msgra)) + dhcp_level = NM_NDISC_DHCP_LEVEL_MANAGED; + else if (ndp_msgra_flag_other(msgra)) + dhcp_level = NM_NDISC_DHCP_LEVEL_OTHERCONF; + else + dhcp_level = NM_NDISC_DHCP_LEVEL_NONE; + + /* when receiving multiple RA (possibly from different routers), + * let's keep the "most managed" level. */ + G_STATIC_ASSERT_EXPR(NM_NDISC_DHCP_LEVEL_MANAGED > NM_NDISC_DHCP_LEVEL_OTHERCONF); + G_STATIC_ASSERT_EXPR(NM_NDISC_DHCP_LEVEL_OTHERCONF > NM_NDISC_DHCP_LEVEL_NONE); + dhcp_level = MAX(dhcp_level, rdata->public.dhcp_level); + + if (dhcp_level != rdata->public.dhcp_level) { + rdata->public.dhcp_level = dhcp_level; + changed |= NM_NDISC_CONFIG_DHCP_LEVEL; + } + } + + /* Default gateway: + * + * Subsequent router advertisements can represent new default gateways + * on the network. We should present all of them in router preference + * order. + */ + { + const NMNDiscGateway gateway = { + .address = gateway_addr, + .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msgra_router_lifetime(msgra)), + .preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)), + }; + + /* https://tools.ietf.org/html/rfc2461#section-4.2 + * > A Lifetime of 0 indicates that the router is not a + * > default router and SHOULD NOT appear on the default + * > router list. + * We handle that by tracking a gateway that expires right now. */ + + if (nm_ndisc_add_gateway(ndisc, &gateway, now_msec)) + changed |= NM_NDISC_CONFIG_GATEWAYS; + } + + /* Addresses & Routes */ + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) { + guint8 r_plen; + struct in6_addr r_network; + + /* Device route */ + + r_plen = ndp_msg_opt_prefix_len(msg, offset); + if (r_plen == 0 || r_plen > 128) + continue; + nm_utils_ip6_address_clear_host_address(&r_network, + ndp_msg_opt_prefix(msg, offset), + r_plen); + + if (IN6_IS_ADDR_UNSPECIFIED(&r_network) || IN6_IS_ADDR_LINKLOCAL(&r_network)) + continue; + + if (ndp_msg_opt_prefix_flag_on_link(msg, offset)) { + const NMNDiscRoute route = { + .network = r_network, + .plen = r_plen, + .expiry_msec = + _nm_ndisc_lifetime_to_expiry(now_msec, + ndp_msg_opt_prefix_valid_time(msg, offset)), + }; + + if (nm_ndisc_add_route(ndisc, &route, now_msec)) + changed |= NM_NDISC_CONFIG_ROUTES; + } + + /* Address */ + if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) { + const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset); + const guint32 preferred_time = + NM_MIN(ndp_msg_opt_prefix_preferred_time(msg, offset), valid_time); + const NMNDiscAddress address = { + .address = r_network, + .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, valid_time), + .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred_time), + }; + + if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec)) + changed |= NM_NDISC_CONFIG_ADDRESSES; + } + } + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) { + guint8 plen = ndp_msg_opt_route_prefix_len(msg, offset); + struct in6_addr network; + + if (plen == 0 || plen > 128) + continue; + + nm_utils_ip6_address_clear_host_address(&network, + ndp_msg_opt_route_prefix(msg, offset), + plen); + + { + const NMNDiscRoute route = { + .network = network, + .gateway = gateway_addr, + .plen = plen, + .expiry_msec = + _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_route_lifetime(msg, offset)), + .preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)), + }; + + /* Routers through this particular gateway */ + if (nm_ndisc_add_route(ndisc, &route, now_msec)) + changed |= NM_NDISC_CONFIG_ROUTES; + } + } + + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_RDNSS) { + struct in6_addr *addr; + int addr_index; + + ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) { + const NMNDiscDNSServer dns_server = { + .address = *addr, + .expiry_msec = + _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_rdnss_lifetime(msg, offset)), + }; + + if (nm_ndisc_add_dns_server(ndisc, &dns_server, now_msec)) + changed |= NM_NDISC_CONFIG_DNS_SERVERS; + } + } + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_DNSSL) { + char *domain; + int domain_index; + + ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) { + const NMNDiscDNSDomain dns_domain = { + .domain = domain, + .expiry_msec = + _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_dnssl_lifetime(msg, offset)), + }; + + if (nm_ndisc_add_dns_domain(ndisc, &dns_domain, now_msec)) + changed |= NM_NDISC_CONFIG_DNS_DOMAINS; + } + } + + hop_limit = ndp_msgra_curhoplimit(msgra); + if (rdata->public.hop_limit != hop_limit) { + rdata->public.hop_limit = hop_limit; + changed |= NM_NDISC_CONFIG_HOP_LIMIT; + } + + val = ndp_msgra_reachable_time(msgra); + if (val && rdata->public.reachable_time_ms != val) { + rdata->public.reachable_time_ms = val; + changed |= NM_NDISC_CONFIG_REACHABLE_TIME; + } + + val = ndp_msgra_retransmit_time(msgra); + if (val && rdata->public.retrans_timer_ms != val) { + rdata->public.retrans_timer_ms = val; + changed |= NM_NDISC_CONFIG_RETRANS_TIMER; + } + + /* MTU */ + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_MTU) { + guint32 mtu = ndp_msg_opt_mtu(msg, offset); + if (mtu >= 1280) { + if (rdata->public.mtu != mtu) { + rdata->public.mtu = mtu; + changed |= NM_NDISC_CONFIG_MTU; + } + } else { + /* All sorts of bad things would happen if we accepted this. + * Kernel would set it, but would flush out all IPv6 addresses away + * from the link, even the link-local, and we wouldn't be able to + * listen for further RAs that could fix the MTU. */ + _LOGW("MTU too small for IPv6 ignored: %d", mtu); + } + } + + nm_ndisc_ra_received(ndisc, now_msec, changed); + return 0; +} + +static void * +_ndp_msg_add_option(struct ndp_msg *msg, gsize len) +{ + gsize payload_len = ndp_msg_payload_len(msg); + void *ret = &((uint8_t *) msg)[payload_len]; + + nm_assert(len <= G_MAXSIZE - payload_len); + len += payload_len; + + if (len > ndp_msg_payload_maxlen(msg)) + return NULL; + + ndp_msg_payload_len_set(msg, len); + nm_assert(len == ndp_msg_payload_len(msg)); + return ret; +} + +/*****************************************************************************/ + +/* "Recursive DNS Server Option" at https://tools.ietf.org/html/rfc8106#section-5.1 */ + +#define NM_ND_OPT_RDNSS 25 + +typedef struct _nm_packed { + struct nd_opt_hdr header; + uint16_t reserved; + uint32_t lifetime; + struct in6_addr addrs[0]; +} NMLndpRdnssOption; + +G_STATIC_ASSERT(sizeof(NMLndpRdnssOption) == 8u); + +/*****************************************************************************/ + +/* "DNS Search List Option" at https://tools.ietf.org/html/rfc8106#section-5.2 */ + +#define NM_ND_OPT_DNSSL 31 + +typedef struct _nm_packed { + struct nd_opt_hdr header; + uint16_t reserved; + uint32_t lifetime; + uint8_t search_list[0]; +} NMLndpDnsslOption; + +G_STATIC_ASSERT(sizeof(NMLndpDnsslOption) == 8u); + +/*****************************************************************************/ + +static gboolean +send_ra(NMNDisc *ndisc, GError **error) +{ + NMLndpNDiscPrivate * priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + NMNDiscDataInternal * rdata = ndisc->rdata; + int errsv; + struct in6_addr * addr; + struct ndp_msg * msg; + guint i; + nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); + + errsv = ndp_msg_new(&msg, NDP_MSG_RA); + if (errsv) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot create a router advertisement"); + return FALSE; + } + + ndp_msg_ifindex_set(msg, nm_ndisc_get_ifindex(ndisc)); + + /* Multicast to all nodes. */ + addr = ndp_msg_addrto(msg); + addr->s6_addr32[0] = htonl(0xff020000); + addr->s6_addr32[1] = 0; + addr->s6_addr32[2] = 0; + addr->s6_addr32[3] = htonl(0x1); + + ndp_msgra_router_lifetime_set(ndp_msgra(msg), NM_NDISC_ROUTER_LIFETIME); + + /* The device let us know about all addresses that the device got + * whose prefixes are suitable for delegating. Let's announce them. */ + for (i = 0; i < rdata->addresses->len; i++) { + const NMNDiscAddress * address = &g_array_index(rdata->addresses, NMNDiscAddress, i); + struct nd_opt_prefix_info *prefix; + + prefix = _ndp_msg_add_option(msg, sizeof(*prefix)); + if (!prefix) { + /* Maybe we could sent separate RAs, but why bother... */ + _LOGW("The RA is too big, had to omit some some prefixes."); + break; + } + + prefix->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; + prefix->nd_opt_pi_len = 4; + prefix->nd_opt_pi_prefix_len = 64; + prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; + prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; + prefix->nd_opt_pi_valid_time = + htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, + address->expiry_msec, + TRUE)); + prefix->nd_opt_pi_preferred_time = + htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, + address->expiry_preferred_msec, + TRUE)); + prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0]; + prefix->nd_opt_pi_prefix.s6_addr32[1] = address->address.s6_addr32[1]; + prefix->nd_opt_pi_prefix.s6_addr32[2] = 0; + prefix->nd_opt_pi_prefix.s6_addr32[3] = 0; + } + + if (rdata->dns_servers->len > 0u) { + NMLndpRdnssOption *option; + gsize len = sizeof(*option) + (sizeof(option->addrs[0]) * rdata->dns_servers->len); + + option = _ndp_msg_add_option(msg, len); + if (!option) { + _LOGW("The RA is too big, had to omit DNS information."); + goto dns_servers_done; + } + + option->header.nd_opt_type = NM_ND_OPT_RDNSS; + option->header.nd_opt_len = len / 8; + option->lifetime = htonl(900); + + for (i = 0; i < rdata->dns_servers->len; i++) { + const NMNDiscDNSServer *dns_server = + &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); + + option->addrs[i] = dns_server->address; + } + } +dns_servers_done: + + if (rdata->dns_domains->len > 0u) { + NMLndpDnsslOption *option; + gsize padding; + gsize len; + + nm_str_buf_reset(&sbuf, NULL); + + for (i = 0; i < rdata->dns_domains->len; i++) { + const NMNDiscDNSDomain *dns_domain = + &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); + const char *domain = dns_domain->domain; + gsize domain_l; + gsize n_reserved; + int r; + + if (nm_str_is_empty(domain)) { + nm_assert_not_reached(); + continue; + } + + domain_l = strlen(domain); + + nm_str_buf_maybe_expand(&sbuf, domain_l + 2u, FALSE); + n_reserved = sbuf.allocated - sbuf.len; + + r = nm_sd_dns_name_to_wire_format( + domain, + (guint8 *) (&nm_str_buf_get_str_unsafe(&sbuf)[sbuf.len]), + n_reserved, + FALSE); + + if (r < 0 || ((gsize) r) > n_reserved) { + nm_assert(r != -ENOBUFS); + nm_assert(r < 0); + /* we don't expect errors here, unless the domain name is invalid. + * That should have been caught (and rejected) by upper layers, but + * at this point it seems dangerous to assert (as it's hard to review + * that all callers got it correct). So instead silently ignore the error. */ + continue; + } + + nm_str_buf_set_size(&sbuf, sbuf.len + ((gsize) r), TRUE, FALSE); + } + + if (sbuf.len == 0) { + /* no valid domains? */ + goto dns_domains_done; + } + + len = sizeof(*option) + sbuf.len; + padding = len % 8u; + if (padding != 0u) { + padding = 8u - padding; + len += padding; + } + + nm_assert(len % 8u == 0u); + nm_assert(len > 0u); + nm_assert(len / 8u >= 2u); + + if (len / 8u >= 256u || !(option = _ndp_msg_add_option(msg, len))) { + _LOGW("The RA is too big, had to omit DNS search list."); + goto dns_domains_done; + } + + nm_str_buf_append_c_len(&sbuf, '\0', padding); + + option->header.nd_opt_type = NM_ND_OPT_DNSSL; + option->header.nd_opt_len = len / 8u; + option->reserved = 0; + option->lifetime = htonl(900); + memcpy(option->search_list, nm_str_buf_get_str_unsafe(&sbuf), sbuf.len); + } +dns_domains_done: + + errsv = ndp_msg_send(priv->ndp, msg); + + ndp_msg_destroy(msg); + if (errsv) { + errsv = nm_errno_native(errsv); + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "%s (%d)", + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + return TRUE; +} + +static int +receive_rs(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) +{ + NMNDisc *ndisc = user_data; + + nm_ndisc_rs_received(ndisc); + return 0; +} + +static gboolean +event_ready(int fd, GIOCondition condition, gpointer user_data) +{ + gs_unref_object NMNDisc *ndisc = g_object_ref(NM_NDISC(user_data)); + nm_auto_pop_netns NMPNetns *netns = NULL; + NMLndpNDiscPrivate * priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + + _LOGD("processing libndp events"); + + if (!nm_ndisc_netns_push(ndisc, &netns)) { + /* something is very wrong. Stop handling events. */ + nm_clear_g_source_inst(&priv->event_source); + return G_SOURCE_REMOVE; + } + + ndp_callall_eventfd_handler(priv->ndp); + return G_SOURCE_CONTINUE; +} + +static void +start(NMNDisc *ndisc) +{ + NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + int fd; + + g_return_if_fail(!priv->event_source); + + fd = ndp_get_eventfd(priv->ndp); + + priv->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, event_ready, ndisc, NULL); + g_source_attach(priv->event_source, NULL); + + /* Flush any pending messages to avoid using obsolete information */ + event_ready(fd, 0, ndisc); + + switch (nm_ndisc_get_node_type(ndisc)) { + case NM_NDISC_NODE_TYPE_HOST: + ndp_msgrcv_handler_register(priv->ndp, + receive_ra, + NDP_MSG_RA, + nm_ndisc_get_ifindex(ndisc), + ndisc); + break; + case NM_NDISC_NODE_TYPE_ROUTER: + ndp_msgrcv_handler_register(priv->ndp, + receive_rs, + NDP_MSG_RS, + nm_ndisc_get_ifindex(ndisc), + ndisc); + break; + default: + g_assert_not_reached(); + } +} + +static void +_cleanup(NMNDisc *ndisc) +{ + NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + + nm_clear_g_source_inst(&priv->event_source); + + if (priv->ndp) { + switch (nm_ndisc_get_node_type(ndisc)) { + case NM_NDISC_NODE_TYPE_HOST: + ndp_msgrcv_handler_unregister(priv->ndp, + receive_ra, + NDP_MSG_RA, + nm_ndisc_get_ifindex(ndisc), + ndisc); + break; + case NM_NDISC_NODE_TYPE_ROUTER: + ndp_msgrcv_handler_unregister(priv->ndp, + receive_rs, + NDP_MSG_RS, + nm_ndisc_get_ifindex(ndisc), + ndisc); + break; + default: + nm_assert_not_reached(); + break; + } + ndp_close(priv->ndp); + priv->ndp = NULL; + } +} + +static void +stop(NMNDisc *ndisc) +{ + _cleanup(ndisc); +} + +/*****************************************************************************/ + +static int +ipv6_sysctl_get(NMPlatform *platform, + const char *ifname, + const char *property, + int min, + int max, + int defval) +{ + return nm_platform_sysctl_ip_conf_get_int_checked(platform, + AF_INET6, + ifname, + property, + 10, + min, + max, + defval); +} + +void +nm_lndp_ndisc_get_sysctl(NMPlatform *platform, + const char *ifname, + int * out_max_addresses, + int * out_router_solicitations, + int * out_router_solicitation_interval, + guint32 * out_default_ra_timeout) +{ + int router_solicitation_interval = 0; + int router_solicitations = 0; + + if (out_max_addresses) { + *out_max_addresses = ipv6_sysctl_get(platform, + ifname, + "max_addresses", + 0, + G_MAXINT32, + NM_NDISC_MAX_ADDRESSES_DEFAULT); + } + if (out_router_solicitations || out_default_ra_timeout) { + router_solicitations = ipv6_sysctl_get(platform, + ifname, + "router_solicitations", + 1, + G_MAXINT32, + NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT); + NM_SET_OUT(out_router_solicitations, router_solicitations); + } + if (out_router_solicitation_interval || out_default_ra_timeout) { + router_solicitation_interval = ipv6_sysctl_get(platform, + ifname, + "router_solicitation_interval", + 1, + G_MAXINT32, + NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL); + NM_SET_OUT(out_router_solicitation_interval, router_solicitation_interval); + } + if (out_default_ra_timeout) { + *out_default_ra_timeout = + NM_MAX((((gint64) router_solicitations) * router_solicitation_interval) + 1, 30); + } +} + +/*****************************************************************************/ + +static void +nm_lndp_ndisc_init(NMLndpNDisc *lndp_ndisc) +{} + +NMNDisc * +nm_lndp_ndisc_new(NMPlatform * platform, + int ifindex, + const char * ifname, + NMUtilsStableType stable_type, + const char * network_id, + NMSettingIP6ConfigAddrGenMode addr_gen_mode, + NMNDiscNodeType node_type, + int max_addresses, + int router_solicitations, + int router_solicitation_interval, + guint32 ra_timeout, + GError ** error) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMNDisc * ndisc; + NMLndpNDiscPrivate * priv; + int errsv; + + g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL); + g_return_val_if_fail(!error || !*error, NULL); + g_return_val_if_fail(network_id, NULL); + + if (!nm_platform_netns_push(platform, &netns)) + return NULL; + + ndisc = g_object_new(NM_TYPE_LNDP_NDISC, + NM_NDISC_PLATFORM, + platform, + NM_NDISC_STABLE_TYPE, + (int) stable_type, + NM_NDISC_IFINDEX, + ifindex, + NM_NDISC_IFNAME, + ifname, + NM_NDISC_NETWORK_ID, + network_id, + NM_NDISC_ADDR_GEN_MODE, + (int) addr_gen_mode, + NM_NDISC_NODE_TYPE, + (int) node_type, + NM_NDISC_MAX_ADDRESSES, + max_addresses, + NM_NDISC_ROUTER_SOLICITATIONS, + router_solicitations, + NM_NDISC_ROUTER_SOLICITATION_INTERVAL, + router_solicitation_interval, + NM_NDISC_RA_TIMEOUT, + (guint) ra_timeout, + NULL); + + priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); + + errsv = ndp_open(&priv->ndp); + + if (errsv != 0) { + errsv = nm_errno_native(errsv); + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "failure creating libndp socket: %s (%d)", + nm_strerror_native(errsv), + errsv); + g_object_unref(ndisc); + return NULL; + } + return ndisc; +} + +static void +dispose(GObject *object) +{ + NMNDisc *ndisc = NM_NDISC(object); + + _cleanup(ndisc); + + G_OBJECT_CLASS(nm_lndp_ndisc_parent_class)->dispose(object); +} + +static void +nm_lndp_ndisc_class_init(NMLndpNDiscClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMNDiscClass *ndisc_class = NM_NDISC_CLASS(klass); + + object_class->dispose = dispose; + ndisc_class->start = start; + ndisc_class->stop = stop; + ndisc_class->send_rs = send_rs; + ndisc_class->send_ra = send_ra; +} diff --git a/src/core/ndisc/nm-lndp-ndisc.h b/src/core/ndisc/nm-lndp-ndisc.h new file mode 100644 index 0000000..96fc973 --- /dev/null +++ b/src/core/ndisc/nm-lndp-ndisc.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_LNDP_NDISC_H__ +#define __NETWORKMANAGER_LNDP_NDISC_H__ + +#include "nm-ndisc.h" +#include "nm-core-utils.h" + +#define NM_TYPE_LNDP_NDISC (nm_lndp_ndisc_get_type()) +#define NM_LNDP_NDISC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LNDP_NDISC, NMLndpNDisc)) +#define NM_LNDP_NDISC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LNDP_NDISC, NMLndpNDiscClass)) +#define NM_IS_LNDP_NDISC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LNDP_NDISC)) +#define NM_IS_LNDP_NDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LNDP_NDISC)) +#define NM_LNDP_NDISC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LNDP_NDISC, NMLndpNDiscClass)) + +typedef struct _NMLndpNDisc NMLndpNDisc; +typedef struct _NMLndpNDiscClass NMLndpNDiscClass; + +GType nm_lndp_ndisc_get_type(void); + +NMNDisc *nm_lndp_ndisc_new(NMPlatform * platform, + int ifindex, + const char * ifname, + NMUtilsStableType stable_type, + const char * network_id, + NMSettingIP6ConfigAddrGenMode addr_gen_mode, + NMNDiscNodeType node_type, + int max_addresses, + int router_solicitations, + int router_solicitation_interval, + guint32 ra_timeout, + GError ** error); + +void nm_lndp_ndisc_get_sysctl(NMPlatform *platform, + const char *ifname, + int * out_max_addresses, + int * out_router_solicitations, + int * out_router_solicitation_interval, + guint32 * out_default_ra_timeout); + +#endif /* __NETWORKMANAGER_LNDP_NDISC_H__ */ diff --git a/src/core/ndisc/nm-ndisc-private.h b/src/core/ndisc/nm-ndisc-private.h new file mode 100644 index 0000000..0473912 --- /dev/null +++ b/src/core/ndisc/nm-ndisc-private.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_NDISC_PRIVATE_H__ +#define __NETWORKMANAGER_NDISC_PRIVATE_H__ + +#include "nm-ndisc.h" + +/* Functions only used by ndisc implementations */ + +struct _NMNDiscDataInternal { + NMNDiscData public; + GArray *gateways; + GArray *addresses; + GArray *routes; + GArray *dns_servers; + GArray *dns_domains; +}; + +typedef struct _NMNDiscDataInternal NMNDiscDataInternal; + +void nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed); +void nm_ndisc_rs_received(NMNDisc *ndisc); + +gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec); +gboolean +nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec); +gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec); +gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec); +gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_IP6 +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, ndisc, __VA_ARGS__) + +#define _LOG(level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + NMNDisc *const __self = (self); \ + char __prefix[64]; \ + const char * __ifname = __self ? nm_ndisc_get_ifname(__self) : NULL; \ + \ + _nm_log(__level, \ + __domain, \ + 0, \ + __ifname, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (__self ? nm_sprintf_buf(__prefix, \ + "%s[%p,%s%s%s]", \ + _NMLOG_PREFIX_NAME, \ + __self, \ + NM_PRINT_FMT_QUOTE_STRING(__ifname)) \ + : _NMLOG_PREFIX_NAME) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +#endif /* __NETWORKMANAGER_NDISC_PRIVATE_H__ */ diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c new file mode 100644 index 0000000..05da8eb --- /dev/null +++ b/src/core/ndisc/nm-ndisc.c @@ -0,0 +1,1830 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-ndisc.h" + +#include +#include + +#include "nm-setting-ip6-config.h" + +#include "nm-ndisc-private.h" +#include "nm-utils.h" +#include "platform/nm-platform.h" +#include "nm-platform/nmp-netns.h" +#include "nm-l3-config-data.h" + +#define _NMLOG_PREFIX_NAME "ndisc" + +#define RFC7559_IRT ((gint32) 4) /* RFC7559, Initial Retransmission Time, in seconds */ +#define RFC7559_MRT ((gint32) 3600) /* RFC7559, Maximum Retransmission Time, in seconds */ + +#define _SIZE_MAX_GATEWAYS 100u +#define _SIZE_MAX_ADDRESSES 100u +#define _SIZE_MAX_ROUTES 1000u +#define _SIZE_MAX_DNS_SERVERS 64u +#define _SIZE_MAX_DNS_DOMAINS 64u + +/*****************************************************************************/ + +struct _NMNDiscPrivate { + /* this *must* be the first field. */ + NMNDiscDataInternal rdata; + + char *last_error; + + GSource *ra_timeout_source; + + gint32 announcements_left; + guint send_ra_id; + gint32 last_ra; + + gint32 solicit_retransmit_time_msec; + + GSource *solicit_timer_source; + + GSource *timeout_expire_source; + + NMUtilsIPv6IfaceId iid; + + /* immutable values: */ + int ifindex; + char * ifname; + char * network_id; + guint max_addresses; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + NMUtilsStableType stable_type; + guint32 ra_timeout; + gint32 router_solicitations; + gint32 router_solicitation_interval; + NMNDiscNodeType node_type; + + NMPlatform *platform; + NMPNetns * netns; +}; + +typedef struct _NMNDiscPrivate NMNDiscPrivate; + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, + PROP_IFINDEX, + PROP_IFNAME, + PROP_STABLE_TYPE, + PROP_NETWORK_ID, + PROP_ADDR_GEN_MODE, + PROP_MAX_ADDRESSES, + PROP_RA_TIMEOUT, + PROP_ROUTER_SOLICITATIONS, + PROP_ROUTER_SOLICITATION_INTERVAL, + PROP_NODE_TYPE, ); + +enum { CONFIG_RECEIVED, RA_TIMEOUT_SIGNAL, LAST_SIGNAL }; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE(NMNDisc, nm_ndisc, G_TYPE_OBJECT) + +#define NM_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMNDisc, NM_IS_NDISC) + +/*****************************************************************************/ + +static void _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed); +static gboolean timeout_expire_cb(gpointer user_data); + +/*****************************************************************************/ + +NML3ConfigData * +nm_ndisc_data_to_l3cd(NMDedupMultiIndex * multi_idx, + int ifindex, + const NMNDiscData * rdata, + NMSettingIP6ConfigPrivacy ip6_privacy, + guint32 route_table, + guint32 route_metric, + gboolean kernel_support_rta_pref, + gboolean kernel_support_extended_ifa_flags) +{ + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + guint32 ifa_flags; + guint8 plen; + guint i; + const gint32 now_sec = nm_utils_get_monotonic_timestamp_sec(); + + l3cd = nm_l3_config_data_new(multi_idx, ifindex); + + nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_NDISC); + + nm_l3_config_data_set_ip6_privacy(l3cd, ip6_privacy); + + /* Check, whether kernel is recent enough to help user space handling RA. + * If it's not supported, we have no ipv6-privacy and must add autoconf + * addresses as /128. The reason for the /128 is to prevent the kernel + * from adding a prefix route for this address. */ + ifa_flags = 0; + if (kernel_support_extended_ifa_flags) { + ifa_flags |= IFA_F_NOPREFIXROUTE; + if (NM_IN_SET(ip6_privacy, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)) + ifa_flags |= IFA_F_MANAGETEMPADDR; + plen = 64; + } else + plen = 128; + + for (i = 0; i < rdata->addresses_n; i++) { + const NMNDiscAddress *ndisc_addr = &rdata->addresses[i]; + NMPlatformIP6Address a; + + a = (NMPlatformIP6Address){ + .ifindex = ifindex, + .address = ndisc_addr->address, + .plen = plen, + .timestamp = now_sec, + .lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) now_sec) * 1000, + ndisc_addr->expiry_msec, + TRUE), + .preferred = _nm_ndisc_lifetime_from_expiry( + ((gint64) now_sec) * 1000, + NM_MIN(ndisc_addr->expiry_msec, ndisc_addr->expiry_preferred_msec), + TRUE), + .addr_source = NM_IP_CONFIG_SOURCE_NDISC, + .n_ifa_flags = ifa_flags, + }; + + nm_l3_config_data_add_address_6(l3cd, &a); + } + + for (i = 0; i < rdata->routes_n; i++) { + const NMNDiscRoute *ndisc_route = &rdata->routes[i]; + NMPlatformIP6Route r; + + r = (NMPlatformIP6Route){ + .ifindex = ifindex, + .network = ndisc_route->network, + .plen = ndisc_route->plen, + .gateway = ndisc_route->gateway, + .rt_source = NM_IP_CONFIG_SOURCE_NDISC, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .rt_pref = ndisc_route->preference, + }; + nm_assert((NMIcmpv6RouterPref) r.rt_pref == ndisc_route->preference); + + nm_l3_config_data_add_route_6(l3cd, &r); + } + + if (rdata->gateways_n > 0) { + const NMIcmpv6RouterPref first_pref = rdata->gateways[0].preference; + NMPlatformIP6Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_NDISC, + .ifindex = ifindex, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + + for (i = 0; i < rdata->gateways_n; i++) { + r.gateway = rdata->gateways[i].address; + r.rt_pref = rdata->gateways[i].preference; + nm_assert((NMIcmpv6RouterPref) r.rt_pref == rdata->gateways[i].preference); + nm_l3_config_data_add_route_6(l3cd, &r); + + if (first_pref != rdata->gateways[i].preference && !kernel_support_rta_pref) { + /* We are unable to configure a router preference. Hence, we skip all gateways + * with a different preference from the first gateway. Note, that the gateways + * are sorted in order of highest to lowest preference. */ + break; + } + } + } + + for (i = 0; i < rdata->dns_servers_n; i++) + nm_l3_config_data_add_nameserver(l3cd, AF_INET6, &rdata->dns_servers[i].address); + + for (i = 0; i < rdata->dns_domains_n; i++) + nm_l3_config_data_add_search(l3cd, AF_INET6, rdata->dns_domains[i].domain); + + nm_l3_config_data_set_ndisc_hop_limit(l3cd, rdata->hop_limit); + nm_l3_config_data_set_ndisc_reachable_time_msec(l3cd, rdata->reachable_time_ms); + nm_l3_config_data_set_ndisc_retrans_timer_msec(l3cd, rdata->retrans_timer_ms); + + nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu); + + return g_steal_pointer(&l3cd); +} + +/*****************************************************************************/ + +static guint8 +_preference_to_priority(NMIcmpv6RouterPref pref) +{ + switch (pref) { + case NM_ICMPV6_ROUTER_PREF_LOW: + return 1; + case NM_ICMPV6_ROUTER_PREF_MEDIUM: + return 2; + case NM_ICMPV6_ROUTER_PREF_HIGH: + return 3; + case NM_ICMPV6_ROUTER_PREF_INVALID: + break; + } + return 0; +} + +/*****************************************************************************/ + +static gboolean +expiry_next(gint64 now_msec, gint64 expiry_msec, gint64 *next_msec) +{ + if (expiry_msec == NM_NDISC_EXPIRY_INFINITY) + return TRUE; + + if (expiry_msec <= now_msec) { + /* expired. */ + return FALSE; + } + + if (next_msec) { + if (*next_msec > expiry_msec) + *next_msec = expiry_msec; + } + + /* the timestamp is good (not yet expired) */ + return TRUE; +} + +static const char * +_get_exp(char *buf, gsize buf_size, gint64 now_msec, gint64 expiry_time) +{ + int l; + + if (expiry_time == NM_NDISC_EXPIRY_INFINITY) + return "permanent"; + l = g_snprintf(buf, buf_size, "%.3f", ((double) (expiry_time - now_msec)) / 1000); + nm_assert(l < buf_size); + return buf; +} + +#define get_exp(buf, now_msec, item) \ + _get_exp((buf), G_N_ELEMENTS(buf), (now_msec), (item)->expiry_msec) + +/*****************************************************************************/ + +NMPNetns * +nm_ndisc_netns_get(NMNDisc *self) +{ + g_return_val_if_fail(NM_IS_NDISC(self), NULL); + + return NM_NDISC_GET_PRIVATE(self)->netns; +} + +gboolean +nm_ndisc_netns_push(NMNDisc *self, NMPNetns **netns) +{ + NMNDiscPrivate *priv; + + g_return_val_if_fail(NM_IS_NDISC(self), FALSE); + + priv = NM_NDISC_GET_PRIVATE(self); + if (priv->netns && !nmp_netns_push(priv->netns)) { + NM_SET_OUT(netns, NULL); + return FALSE; + } + + NM_SET_OUT(netns, priv->netns); + return TRUE; +} + +/*****************************************************************************/ + +int +nm_ndisc_get_ifindex(NMNDisc *self) +{ + g_return_val_if_fail(NM_IS_NDISC(self), 0); + + return NM_NDISC_GET_PRIVATE(self)->ifindex; +} + +const char * +nm_ndisc_get_ifname(NMNDisc *self) +{ + g_return_val_if_fail(NM_IS_NDISC(self), NULL); + + return NM_NDISC_GET_PRIVATE(self)->ifname; +} + +NMNDiscNodeType +nm_ndisc_get_node_type(NMNDisc *self) +{ + g_return_val_if_fail(NM_IS_NDISC(self), NM_NDISC_NODE_TYPE_INVALID); + + return NM_NDISC_GET_PRIVATE(self)->node_type; +} + +/*****************************************************************************/ + +static void +_ASSERT_data_gateways(const NMNDiscDataInternal *data) +{ +#if NM_MORE_ASSERTS > 10 + guint i, j; + const NMNDiscGateway *item_prev = NULL; + + if (!data->gateways->len) + return; + + for (i = 0; i < data->gateways->len; i++) { + const NMNDiscGateway *item = &g_array_index(data->gateways, NMNDiscGateway, i); + + nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&item->address)); + for (j = 0; j < i; j++) { + const NMNDiscGateway *item2 = &g_array_index(data->gateways, NMNDiscGateway, j); + + nm_assert(!IN6_ARE_ADDR_EQUAL(&item->address, &item2->address)); + } + + if (i > 0) { + nm_assert(_preference_to_priority(item_prev->preference) + >= _preference_to_priority(item->preference)); + } + + item_prev = item; + } +#endif +} + +/*****************************************************************************/ + +static const NMNDiscData * +_data_complete(NMNDiscDataInternal *data) +{ + _ASSERT_data_gateways(data); + +#define _SET(data, field) \ + G_STMT_START \ + { \ + if ((data->public.field##_n = data->field->len) > 0) \ + data->public.field = (gpointer) data->field->data; \ + else \ + data->public.field = NULL; \ + } \ + G_STMT_END + _SET(data, gateways); + _SET(data, addresses); + _SET(data, routes); + _SET(data, dns_servers); + _SET(data, dns_domains); +#undef _SET + return &data->public; +} + +void +nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed) +{ + _config_changed_log(self, changed); + g_signal_emit(self, + signals[CONFIG_RECEIVED], + 0, + _data_complete(&NM_NDISC_GET_PRIVATE(self)->rdata), + (guint) changed); +} + +/*****************************************************************************/ + +gboolean +nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + guint i; + guint insert_idx = G_MAXUINT; + + for (i = 0; i < rdata->gateways->len;) { + NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i); + + if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->gateways, i); + _ASSERT_data_gateways(rdata); + return TRUE; + } + + if (item->preference != new_item->preference) { + g_array_remove_index(rdata->gateways, i); + continue; + } + + if (item->expiry_msec == new_item->expiry_msec) + return FALSE; + + item->expiry_msec = new_item->expiry_msec; + _ASSERT_data_gateways(rdata); + return TRUE; + } + + /* Put before less preferable gateways. */ + if (_preference_to_priority(item->preference) + < _preference_to_priority(new_item->preference) + && insert_idx == G_MAXUINT) + insert_idx = i; + + i++; + } + + if (rdata->gateways->len >= _SIZE_MAX_GATEWAYS) + return FALSE; + + if (new_item->expiry_msec <= now_msec) + return FALSE; + + g_array_insert_val(rdata->gateways, + insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx, + *new_item); + _ASSERT_data_gateways(rdata); + return TRUE; +} + +/** + * complete_address: + * @ndisc: the #NMNDisc + * @addr: the #NMNDiscAddress + * + * Adds the host part to the address that has network part set. + * If the address already has a host part, add a different host part + * if possible (this is useful in case DAD failed). + * + * Can fail if a different address can not be generated (DAD failure + * for an EUI-64 address or DAD counter overflow). + * + * Returns: %TRUE if the address could be completed, %FALSE otherwise. + **/ +static gboolean +complete_address(NMNDisc *ndisc, NMNDiscAddress *addr) +{ + NMNDiscPrivate *priv; + GError * error = NULL; + + g_return_val_if_fail(NM_IS_NDISC(ndisc), FALSE); + + priv = NM_NDISC_GET_PRIVATE(ndisc); + if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) { + if (!nm_utils_ipv6_addr_set_stable_privacy(priv->stable_type, + &addr->address, + priv->ifname, + priv->network_id, + addr->dad_counter++, + &error)) { + _LOGW("complete-address: failed to generate an stable-privacy address: %s", + error->message); + g_clear_error(&error); + return FALSE; + } + _LOGD("complete-address: using an stable-privacy address"); + return TRUE; + } + + if (!priv->iid.id) { + _LOGW("complete-address: can't generate an EUI-64 address: no interface identifier"); + return FALSE; + } + + if (addr->address.s6_addr32[2] == 0x0 && addr->address.s6_addr32[3] == 0x0) { + _LOGD("complete-address: adding an EUI-64 address"); + nm_utils_ipv6_addr_set_interface_identifier(&addr->address, priv->iid); + return TRUE; + } + + _LOGW("complete-address: can't generate a new_item EUI-64 address"); + return FALSE; +} + +static gboolean +nm_ndisc_add_address(NMNDisc * ndisc, + const NMNDiscAddress *new_item, + gint64 now_msec, + gboolean from_ra) +{ + NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); + NMNDiscDataInternal *rdata = &priv->rdata; + NMNDiscAddress * new2; + NMNDiscAddress * existing = NULL; + guint i; + + nm_assert(new_item); + nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new_item->address)); + nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new_item->address)); + nm_assert(new_item->expiry_preferred_msec <= new_item->expiry_msec); + nm_assert((!!from_ra) == (now_msec > 0)); + + for (i = 0; i < rdata->addresses->len; i++) { + NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i); + + if (from_ra) { + /* RFC4862 5.5.3.d, we find an existing address with the same prefix. + * (note that all prefixes at this point have implicitly length /64). */ + if (memcmp(&item->address, &new_item->address, 8) == 0) { + existing = item; + break; + } + } else { + if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) { + existing = item; + break; + } + } + } + + if (existing) { + gint64 new_expiry_preferred_msec; + gint64 new_expiry_msec; + + if (from_ra) { + if (new_item->expiry_msec == NM_NDISC_EXPIRY_INFINITY) + new_expiry_msec = NM_NDISC_EXPIRY_INFINITY; + else { + const gint64 NDISC_PREFIX_LFT_MIN_MSEC = 7200 * 1000; /* RFC4862 5.5.3.e */ + gint64 new_lifetime; + gint64 existing_lifetime; + + new_lifetime = new_item->expiry_msec - now_msec; + if (existing->expiry_msec == NM_NDISC_EXPIRY_INFINITY) + existing_lifetime = G_MAXINT64; + else + existing_lifetime = existing->expiry_msec - now_msec; + + /* see RFC4862 5.5.3.e */ + if (new_lifetime >= NDISC_PREFIX_LFT_MIN_MSEC + || new_lifetime >= existing_lifetime) { + /* either extend the lifetime of the new_item lifetime is longer than + * NDISC_PREFIX_LFT_MIN_MSEC. */ + new_expiry_msec = new_item->expiry_msec; + } else if (existing_lifetime <= NDISC_PREFIX_LFT_MIN_MSEC) { + /* keep the current lifetime. */ + new_expiry_msec = existing->expiry_msec; + } else { + /* trim the current lifetime to NDISC_PREFIX_LFT_MIN_MSEC. */ + new_expiry_msec = now_msec + NDISC_PREFIX_LFT_MIN_MSEC; + } + } + + new_expiry_preferred_msec = + NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec); + new_expiry_preferred_msec = NM_MIN(new_expiry_preferred_msec, new_expiry_msec); + } else { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->addresses, i); + return TRUE; + } + + new_expiry_msec = new_item->expiry_msec; + new_expiry_preferred_msec = + NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec); + } + + /* the dad_counter does not get modified. */ + if (new_expiry_msec == existing->expiry_msec + && new_expiry_preferred_msec == existing->expiry_preferred_msec) { + return FALSE; + } + + existing->expiry_msec = new_expiry_msec; + existing->expiry_preferred_msec = new_expiry_preferred_msec; + return TRUE; + } + + /* we create at most max_addresses autoconf addresses. This is different from + * what the kernel does, because it considers *all* addresses (including + * static and other temporary addresses). + **/ + if (rdata->addresses->len >= priv->max_addresses) + return FALSE; + + if (new_item->expiry_msec <= now_msec) + return FALSE; + + new2 = nm_g_array_append_new(rdata->addresses, NMNDiscAddress); + + *new2 = *new_item; + + new2->expiry_preferred_msec = NM_MIN(new2->expiry_preferred_msec, new2->expiry_msec); + + if (from_ra) { + new2->dad_counter = 0; + if (!complete_address(ndisc, new2)) { + g_array_set_size(rdata->addresses, rdata->addresses->len - 1); + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec) +{ + return nm_ndisc_add_address(ndisc, new_item, now_msec, TRUE); +} + +gboolean +nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + guint i; + guint insert_idx = G_MAXUINT; + gboolean changed = FALSE; + + if (new_item->plen == 0 || new_item->plen > 128) { + /* Only expect non-default routes. The router has no idea what the + * local configuration or user preferences are, so sending routes + * with a prefix length of 0 must be ignored by NMNDisc. + * + * Also, upper layers also don't expect that NMNDisc exposes routes + * with a plen or zero or larger then 128. + */ + g_return_val_if_reached(FALSE); + } + + priv = NM_NDISC_GET_PRIVATE(ndisc); + rdata = &priv->rdata; + + for (i = 0; i < rdata->routes->len;) { + NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i); + + if (IN6_ARE_ADDR_EQUAL(&item->network, &new_item->network) + && item->plen == new_item->plen) { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->routes, i); + return TRUE; + } + + if (item->preference != new_item->preference) { + g_array_remove_index(rdata->routes, i); + changed = TRUE; + continue; + } + + if (item->expiry_msec == new_item->expiry_msec + && IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway)) + return FALSE; + + item->expiry_msec = new_item->expiry_msec; + item->gateway = new_item->gateway; + return TRUE; + } + + /* Put before less preferable routes. */ + if (_preference_to_priority(item->preference) + < _preference_to_priority(new_item->preference) + && insert_idx == G_MAXUINT) + insert_idx = i; + + i++; + } + + if (rdata->routes->len >= _SIZE_MAX_ROUTES) + return FALSE; + + if (new_item->expiry_msec <= now_msec) { + nm_assert(!changed); + return FALSE; + } + + g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new_item); + return TRUE; +} + +gboolean +nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + guint i; + + priv = NM_NDISC_GET_PRIVATE(ndisc); + rdata = &priv->rdata; + + for (i = 0; i < rdata->dns_servers->len; i++) { + NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); + + if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->dns_servers, i); + return TRUE; + } + + if (item->expiry_msec == new_item->expiry_msec) + return FALSE; + + item->expiry_msec = new_item->expiry_msec; + return TRUE; + } + } + + if (rdata->dns_servers->len >= _SIZE_MAX_DNS_SERVERS) + return FALSE; + + if (new_item->expiry_msec <= now_msec) + return FALSE; + + g_array_append_val(rdata->dns_servers, *new_item); + return TRUE; +} + +/* Copies new_item->domain if 'new_item' is added to the dns_domains list */ +gboolean +nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + NMNDiscDNSDomain * item; + guint i; + + priv = NM_NDISC_GET_PRIVATE(ndisc); + rdata = &priv->rdata; + + for (i = 0; i < rdata->dns_domains->len; i++) { + item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); + + if (nm_streq(item->domain, new_item->domain)) { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->dns_domains, i); + return TRUE; + } + + if (item->expiry_msec == new_item->expiry_msec) + return FALSE; + + item->expiry_msec = new_item->expiry_msec; + return TRUE; + } + } + + if (rdata->dns_domains->len >= _SIZE_MAX_DNS_DOMAINS) + return FALSE; + + if (new_item->expiry_msec <= now_msec) + return FALSE; + + item = nm_g_array_append_new(rdata->dns_domains, NMNDiscDNSDomain); + *item = (NMNDiscDNSDomain){ + .domain = g_strdup(new_item->domain), + .expiry_msec = new_item->expiry_msec, + }; + return TRUE; +} + +/*****************************************************************************/ + +#define _MAYBE_WARN(...) \ + G_STMT_START \ + { \ + gboolean _different_message; \ + \ + _different_message = !nm_streq0(priv->last_error, error->message); \ + _NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \ + if (_different_message) { \ + nm_clear_g_free(&priv->last_error); \ + priv->last_error = g_strdup(error->message); \ + } \ + } \ + G_STMT_END + +static gint32 +solicit_retransmit_time_jitter(gint32 solicit_retransmit_time_msec) +{ + gint32 ten_percent; + + nm_assert(solicit_retransmit_time_msec > 0); + nm_assert(solicit_retransmit_time_msec < 3 * RFC7559_MRT * 1000); + + /* Add a ±10% jitter. + * + * This is the "RAND" parameter from https://tools.ietf.org/html/rfc3315#section-14 + * as requested by RFC7559. */ + + ten_percent = NM_MAX(1, solicit_retransmit_time_msec / 10); + + return solicit_retransmit_time_msec - ten_percent + + ((gint32)(g_random_int() % (2u * ((guint32) ten_percent)))); +} + +static gboolean +solicit_timer_cb(gpointer user_data) +{ + const gint32 TIMEOUT_APPROX_THRESHOLD_SEC = 10000; + nm_auto_pop_netns NMPNetns *netns = NULL; + NMNDisc * ndisc = user_data; + NMNDiscClass * klass = NM_NDISC_GET_CLASS(ndisc); + NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); + gs_free_error GError *error = NULL; + gint32 timeout_msec; + + if (!nm_ndisc_netns_push(ndisc, &netns)) { + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "failure to switch netns for soliciting routers"); + } else + klass->send_rs(ndisc, &error); + + if (error) + _MAYBE_WARN("solicit: failure sending router solicitation: %s", error->message); + else { + _LOGT("solicit: router solicitation sent"); + nm_clear_g_free(&priv->last_error); + } + + /* https://tools.ietf.org/html/rfc4861#section-6.3.7 describes how to send solicitations: + * + * > ... a host SHOULD transmit up to MAX_RTR_SOLICITATIONS Router Solicitation messages, + * > each separated by at least RTR_SOLICITATION_INTERVAL seconds. + * + * but this was extended by https://tools.ietf.org/html/rfc7559#section-2 to send continuously + * and with exponential backoff (detailed the algorithm in https://tools.ietf.org/html/rfc3315#section-14). + * We do RFC7559. + */ + if (priv->solicit_retransmit_time_msec == 0) { + G_STATIC_ASSERT(RFC7559_IRT == NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL); + priv->solicit_retransmit_time_msec = solicit_retransmit_time_jitter(RFC7559_IRT * 1000); + timeout_msec = priv->solicit_retransmit_time_msec; + } else { + priv->solicit_retransmit_time_msec += + solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec); + timeout_msec = priv->solicit_retransmit_time_msec; + if (priv->solicit_retransmit_time_msec > RFC7559_MRT * 1000) { + priv->solicit_retransmit_time_msec = RFC7559_MRT * 1000; + timeout_msec = solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec); + } + } + + _LOGD("solicit: schedule sending next solicitation in%s %.3f seconds", + timeout_msec / 1000 >= TIMEOUT_APPROX_THRESHOLD_SEC ? " about" : "", + ((double) timeout_msec) / 1000); + + nm_clear_g_source_inst(&priv->solicit_timer_source); + priv->solicit_timer_source = nm_g_timeout_add_source_approx(timeout_msec, + TIMEOUT_APPROX_THRESHOLD_SEC, + solicit_timer_cb, + ndisc); + return G_SOURCE_CONTINUE; +} + +static void +solicit_timer_start(NMNDisc *ndisc) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + gint32 delay_msec; + + /* rfc4861, Section 6.3.7: + * + * We should randomly wait up to NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY (1 second) + * before sending the first RS. RFC4861 is from 2007, I don't think 1 second is + * a suitable delay in 2021. Wait only up to 250 msec instead. */ + + delay_msec = + g_random_int() % ((guint32)(NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY * 1000 / 4)); + + _LOGD("solicit: schedule sending first solicitation (of %d) in %.3f seconds", + priv->router_solicitations, + ((double) delay_msec) / 1000); + + priv->solicit_retransmit_time_msec = 0; + nm_clear_g_source_inst(&priv->solicit_timer_source); + priv->solicit_timer_source = nm_g_timeout_add_source(delay_msec, solicit_timer_cb, ndisc); +} + +/*****************************************************************************/ + +static gboolean +announce_router(NMNDisc *ndisc) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMNDiscClass * klass = NM_NDISC_GET_CLASS(ndisc); + NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); + GError * error = NULL; + + if (!nm_ndisc_netns_push(ndisc, &netns)) + return G_SOURCE_REMOVE; + + priv->last_ra = nm_utils_get_monotonic_timestamp_sec(); + if (klass->send_ra(ndisc, &error)) { + _LOGD("router advertisement sent"); + nm_clear_g_free(&priv->last_error); + } else { + _MAYBE_WARN("failure sending router advertisement: %s", error->message); + g_clear_error(&error); + } + + if (--priv->announcements_left) { + _LOGD("will resend an initial router advertisement"); + + /* Schedule next initial announcement retransmit. */ + priv->send_ra_id = + g_timeout_add_seconds(g_random_int_range(NM_NDISC_ROUTER_ADVERT_DELAY, + NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL), + (GSourceFunc) announce_router, + ndisc); + } else { + _LOGD("will send an unsolicited router advertisement"); + + /* Schedule next unsolicited announcement. */ + priv->announcements_left = 1; + priv->send_ra_id = g_timeout_add_seconds(NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL, + (GSourceFunc) announce_router, + ndisc); + } + + return G_SOURCE_REMOVE; +} + +static void +announce_router_initial(NMNDisc *ndisc) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + + _LOGD("will send an initial router advertisement"); + + /* Retry three more times. */ + priv->announcements_left = NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT; + + /* Unschedule an unsolicited resend if we are allowed to send now. */ + if (G_LIKELY(nm_utils_get_monotonic_timestamp_sec() - priv->last_ra + > NM_NDISC_ROUTER_ADVERT_DELAY)) + nm_clear_g_source(&priv->send_ra_id); + + /* Schedule the initial send rather early. Clamp the delay by minimal + * delay and not the initial advert internal so that we start fast. */ + if (G_LIKELY(!priv->send_ra_id)) { + priv->send_ra_id = + g_timeout_add_seconds(g_random_int_range(0, NM_NDISC_ROUTER_ADVERT_DELAY), + (GSourceFunc) announce_router, + ndisc); + } +} + +static void +announce_router_solicited(NMNDisc *ndisc) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + + _LOGD("will send an solicited router advertisement"); + + /* Unschedule an unsolicited resend if we are allowed to send now. */ + if (nm_utils_get_monotonic_timestamp_sec() - priv->last_ra > NM_NDISC_ROUTER_ADVERT_DELAY) + nm_clear_g_source(&priv->send_ra_id); + + if (!priv->send_ra_id) { + priv->send_ra_id = g_timeout_add(g_random_int_range(0, NM_NDISC_ROUTER_ADVERT_DELAY_MS), + (GSourceFunc) announce_router, + ndisc); + } +} + +/*****************************************************************************/ + +void +nm_ndisc_set_config(NMNDisc * ndisc, + const GArray *addresses, + const GArray *dns_servers, + const GArray *dns_domains) +{ + gboolean changed = FALSE; + guint i; + + for (i = 0; i < addresses->len; i++) { + if (nm_ndisc_add_address(ndisc, &g_array_index(addresses, NMNDiscAddress, i), 0, FALSE)) + changed = TRUE; + } + + for (i = 0; i < dns_servers->len; i++) { + if (nm_ndisc_add_dns_server(ndisc, + &g_array_index(dns_servers, NMNDiscDNSServer, i), + G_MININT64)) + changed = TRUE; + } + + for (i = 0; i < dns_domains->len; i++) { + if (nm_ndisc_add_dns_domain(ndisc, + &g_array_index(dns_domains, NMNDiscDNSDomain, i), + G_MININT64)) + changed = TRUE; + } + + if (changed) + announce_router_initial(ndisc); +} + +/** + * nm_ndisc_set_iid: + * @ndisc: the #NMNDisc + * @iid: the new interface ID + * + * Sets the "Modified EUI-64" interface ID to be used when generating + * IPv6 addresses using received prefixes. Identifiers are either generated + * from the hardware addresses or manually set by the operator with + * "ip token" command. + * + * Upon token change (or initial setting) all addresses generated using + * the old identifier are removed. The caller should ensure the addresses + * will be reset by soliciting router advertisements. + * + * In case the stable privacy addressing is used %FALSE is returned and + * addresses are left untouched. + * + * Returns: %TRUE if addresses need to be regenerated, %FALSE otherwise. + **/ +gboolean +nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + + g_return_val_if_fail(NM_IS_NDISC(ndisc), FALSE); + + priv = NM_NDISC_GET_PRIVATE(ndisc); + rdata = &priv->rdata; + + if (priv->iid.id != iid.id) { + priv->iid = iid; + + if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) + return FALSE; + + if (rdata->addresses->len) { + _LOGD("IPv6 interface identifier changed, flushing addresses"); + g_array_remove_range(rdata->addresses, 0, rdata->addresses->len); + nm_ndisc_emit_config_change(ndisc, NM_NDISC_CONFIG_ADDRESSES); + solicit_timer_start(ndisc); + } + return TRUE; + } + + return FALSE; +} + +static gboolean +ra_timeout_cb(gpointer user_data) +{ + NMNDisc *ndisc = NM_NDISC(user_data); + + nm_clear_g_source_inst(&NM_NDISC_GET_PRIVATE(ndisc)->ra_timeout_source); + g_signal_emit(ndisc, signals[RA_TIMEOUT_SIGNAL], 0); + return G_SOURCE_CONTINUE; +} + +void +nm_ndisc_start(NMNDisc *ndisc) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMNDiscPrivate * priv; + + g_return_if_fail(NM_IS_NDISC(ndisc)); + + priv = NM_NDISC_GET_PRIVATE(ndisc); + + nm_assert(NM_NDISC_GET_CLASS(ndisc)->start); + nm_assert(!priv->ra_timeout_source); + + _LOGD("starting neighbor discovery for ifindex %d%s", + priv->ifindex, + priv->node_type == NM_NDISC_NODE_TYPE_HOST ? " (solicit)" : " (announce)"); + + if (!nm_ndisc_netns_push(ndisc, &netns)) + return; + + NM_NDISC_GET_CLASS(ndisc)->start(ndisc); + + if (priv->node_type == NM_NDISC_NODE_TYPE_HOST) { + G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_DEFAULT == 0); + G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_INFINITY == G_MAXINT32); + nm_assert(priv->ra_timeout > 0u); + nm_assert(priv->ra_timeout <= NM_RA_TIMEOUT_INFINITY); + + if (priv->ra_timeout < NM_RA_TIMEOUT_INFINITY) { + guint timeout_msec; + + _LOGD("scheduling RA timeout in %u seconds", priv->ra_timeout); + if (priv->ra_timeout < G_MAXUINT / 1000u) + timeout_msec = priv->ra_timeout * 1000u; + else + timeout_msec = G_MAXUINT; + priv->ra_timeout_source = nm_g_timeout_add_source(timeout_msec, ra_timeout_cb, ndisc); + } + + solicit_timer_start(ndisc); + return; + } + + nm_assert(priv->ra_timeout == 0u); + nm_assert(priv->node_type == NM_NDISC_NODE_TYPE_ROUTER); + announce_router_initial(ndisc); +} + +void +nm_ndisc_stop(NMNDisc *ndisc) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMNDiscDataInternal * rdata; + NMNDiscPrivate * priv; + + g_return_if_fail(NM_IS_NDISC(ndisc)); + + priv = NM_NDISC_GET_PRIVATE(ndisc); + + nm_assert(NM_NDISC_GET_CLASS(ndisc)->stop); + + _LOGD("stopping neighbor discovery for ifindex %d", priv->ifindex); + + if (!nm_ndisc_netns_push(ndisc, &netns)) + return; + + NM_NDISC_GET_CLASS(ndisc)->stop(ndisc); + + rdata = &priv->rdata; + + g_array_set_size(rdata->gateways, 0); + g_array_set_size(rdata->addresses, 0); + g_array_set_size(rdata->routes, 0); + g_array_set_size(rdata->dns_servers, 0); + g_array_set_size(rdata->dns_domains, 0); + priv->rdata.public.hop_limit = 64; + + nm_clear_g_source_inst(&priv->ra_timeout_source); + nm_clear_g_source(&priv->send_ra_id); + nm_clear_g_free(&priv->last_error); + nm_clear_g_source_inst(&priv->timeout_expire_source); + + priv->solicit_retransmit_time_msec = 0; + nm_clear_g_source_inst(&priv->solicit_timer_source); + + priv->announcements_left = 0; + + priv->last_ra = G_MININT32; +} + +NMNDiscConfigMap +nm_ndisc_dad_failed(NMNDisc *ndisc, const struct in6_addr *address, gboolean emit_changed_signal) +{ + NMNDiscDataInternal *rdata; + guint i; + gboolean changed = FALSE; + + rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + + for (i = 0; i < rdata->addresses->len;) { + NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i); + + if (IN6_ARE_ADDR_EQUAL(&item->address, address)) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD("DAD failed for discovered address %s", _nm_utils_inet6_ntop(address, sbuf)); + changed = TRUE; + if (!complete_address(ndisc, item)) { + g_array_remove_index(rdata->addresses, i); + continue; + } + } + i++; + } + + if (emit_changed_signal && changed) + nm_ndisc_emit_config_change(ndisc, NM_NDISC_CONFIG_ADDRESSES); + + return changed ? NM_NDISC_CONFIG_ADDRESSES : NM_NDISC_CONFIG_NONE; +} + +#define CONFIG_MAP_MAX_STR 7 + +static void +config_map_to_string(NMNDiscConfigMap map, char *p) +{ + if (map & NM_NDISC_CONFIG_DHCP_LEVEL) + *p++ = 'd'; + if (map & NM_NDISC_CONFIG_GATEWAYS) + *p++ = 'G'; + if (map & NM_NDISC_CONFIG_ADDRESSES) + *p++ = 'A'; + if (map & NM_NDISC_CONFIG_ROUTES) + *p++ = 'R'; + if (map & NM_NDISC_CONFIG_DNS_SERVERS) + *p++ = 'S'; + if (map & NM_NDISC_CONFIG_DNS_DOMAINS) + *p++ = 'D'; + *p = '\0'; +} + +static const char * +dhcp_level_to_string(NMNDiscDHCPLevel dhcp_level) +{ + switch (dhcp_level) { + case NM_NDISC_DHCP_LEVEL_NONE: + return "none"; + case NM_NDISC_DHCP_LEVEL_OTHERCONF: + return "otherconf"; + case NM_NDISC_DHCP_LEVEL_MANAGED: + return "managed"; + default: + return "INVALID"; + } +} + +static void +_config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + guint i; + char changedstr[CONFIG_MAP_MAX_STR]; + char addrstr[NM_UTILS_INET_ADDRSTRLEN]; + char str_pref[35]; + char str_exp[100]; + gint64 now_msec; + + if (!_LOGD_ENABLED()) + return; + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + priv = NM_NDISC_GET_PRIVATE(ndisc); + rdata = &priv->rdata; + + config_map_to_string(changed, changedstr); + _LOGD("neighbor discovery configuration changed [%s]:", changedstr); + _LOGD(" dhcp-level %s", dhcp_level_to_string(priv->rdata.public.dhcp_level)); + + if (rdata->public.hop_limit) + _LOGD(" hop limit : %d", rdata->public.hop_limit); + if (rdata->public.reachable_time_ms) + _LOGD(" reachable time : %u", (guint) rdata->public.reachable_time_ms); + if (rdata->public.retrans_timer_ms) + _LOGD(" retrans timer : %u", (guint) rdata->public.retrans_timer_ms); + + for (i = 0; i < rdata->gateways->len; i++) { + const NMNDiscGateway *gateway = &g_array_index(rdata->gateways, NMNDiscGateway, i); + + _LOGD(" gateway %s pref %s exp %s", + _nm_utils_inet6_ntop(&gateway->address, addrstr), + nm_icmpv6_router_pref_to_string(gateway->preference, str_pref, sizeof(str_pref)), + get_exp(str_exp, now_msec, gateway)); + } + for (i = 0; i < rdata->addresses->len; i++) { + const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i); + + _LOGD(" address %s exp %s", + _nm_utils_inet6_ntop(&address->address, addrstr), + get_exp(str_exp, now_msec, address)); + } + for (i = 0; i < rdata->routes->len; i++) { + const NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i); + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD(" route %s/%u via %s pref %s exp %s", + _nm_utils_inet6_ntop(&route->network, addrstr), + (guint) route->plen, + _nm_utils_inet6_ntop(&route->gateway, sbuf), + nm_icmpv6_router_pref_to_string(route->preference, str_pref, sizeof(str_pref)), + get_exp(str_exp, now_msec, route)); + } + for (i = 0; i < rdata->dns_servers->len; i++) { + const NMNDiscDNSServer *dns_server = + &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); + + _LOGD(" dns_server %s exp %s", + _nm_utils_inet6_ntop(&dns_server->address, addrstr), + get_exp(str_exp, now_msec, dns_server)); + } + for (i = 0; i < rdata->dns_domains->len; i++) { + const NMNDiscDNSDomain *dns_domain = + &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); + + _LOGD(" dns_domain %s exp %s", dns_domain->domain, get_exp(str_exp, now_msec, dns_domain)); + } +} + +/*****************************************************************************/ + +static gboolean +_array_set_size_max(GArray *array, guint size_max) +{ + nm_assert(array); + nm_assert(size_max > 0u); + + if (array->len <= size_max) + return FALSE; + + g_array_set_size(array, size_max); + return TRUE; +} + +static void +clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscGateway * arr; + guint i; + guint j; + + if (rdata->gateways->len == 0) + return; + + arr = &g_array_index(rdata->gateways, NMNDiscGateway, 0); + + for (i = 0, j = 0; i < rdata->gateways->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + continue; + if (i != j) + arr[j] = arr[i]; + j++; + } + + if (i != j) { + *changed |= NM_NDISC_CONFIG_GATEWAYS; + g_array_set_size(rdata->gateways, j); + } + + if (_array_set_size_max(rdata->gateways, _SIZE_MAX_GATEWAYS)) + *changed |= NM_NDISC_CONFIG_GATEWAYS; + + _ASSERT_data_gateways(rdata); +} + +static void +clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscAddress * arr; + guint i; + guint j; + + if (rdata->addresses->len == 0) + return; + + arr = &g_array_index(rdata->addresses, NMNDiscAddress, 0); + + for (i = 0, j = 0; i < rdata->addresses->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + continue; + if (i != j) + arr[j] = arr[i]; + j++; + } + + if (i != j) { + *changed = NM_NDISC_CONFIG_ADDRESSES; + g_array_set_size(rdata->addresses, j); + } + + if (_array_set_size_max(rdata->gateways, priv->max_addresses)) + *changed |= NM_NDISC_CONFIG_ADDRESSES; +} + +static void +clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscRoute * arr; + guint i; + guint j; + + if (rdata->routes->len == 0) + return; + + arr = &g_array_index(rdata->routes, NMNDiscRoute, 0); + + for (i = 0, j = 0; i < rdata->routes->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + continue; + if (i != j) + arr[j] = arr[i]; + j++; + } + + if (i != j) { + *changed |= NM_NDISC_CONFIG_ROUTES; + g_array_set_size(rdata->routes, j); + } + + if (_array_set_size_max(rdata->gateways, _SIZE_MAX_ROUTES)) + *changed |= NM_NDISC_CONFIG_ROUTES; +} + +static void +clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscDNSServer * arr; + guint i; + guint j; + + if (rdata->dns_servers->len == 0) + return; + + arr = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, 0); + + for (i = 0, j = 0; i < rdata->dns_servers->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + continue; + if (i != j) + arr[j] = arr[i]; + j++; + } + + if (i != j) { + *changed |= NM_NDISC_CONFIG_DNS_SERVERS; + g_array_set_size(rdata->dns_servers, j); + } + + if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_SERVERS)) + *changed |= NM_NDISC_CONFIG_DNS_SERVERS; +} + +static void +clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscDNSDomain * arr; + guint i; + guint j; + + if (rdata->dns_domains->len == 0) + return; + + arr = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, 0); + + for (i = 0, j = 0; i < rdata->dns_domains->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + continue; + + if (i != j) { + g_free(arr[j].domain); + arr[j] = arr[i]; + arr[i].domain = NULL; + } + + j++; + } + + if (i != 0) { + *changed |= NM_NDISC_CONFIG_DNS_DOMAINS; + g_array_set_size(rdata->dns_domains, j); + } + + if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_DOMAINS)) + *changed |= NM_NDISC_CONFIG_DNS_DOMAINS; +} + +static void +check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + gint64 next_msec = G_MAXINT64; + + _LOGT("router-data: check for changed router advertisement data"); + + clean_gateways(ndisc, now_msec, &changed, &next_msec); + clean_addresses(ndisc, now_msec, &changed, &next_msec); + clean_routes(ndisc, now_msec, &changed, &next_msec); + clean_dns_servers(ndisc, now_msec, &changed, &next_msec); + clean_dns_domains(ndisc, now_msec, &changed, &next_msec); + + nm_assert(next_msec > now_msec); + + nm_clear_g_source_inst(&priv->timeout_expire_source); + + if (next_msec == NM_NDISC_EXPIRY_INFINITY) + _LOGD("router-data: next lifetime expiration will happen: never"); + else { + const gint64 timeout_msec = NM_MIN(next_msec - now_msec, ((gint64) G_MAXINT32)); + const guint TIMEOUT_APPROX_THRESHOLD_SEC = 10000; + + _LOGD("router-data: next lifetime expiration will happen: in %s%.3f seconds", + (timeout_msec / 1000) >= TIMEOUT_APPROX_THRESHOLD_SEC ? " about" : "", + ((double) timeout_msec) / 1000); + + priv->timeout_expire_source = nm_g_timeout_add_source_approx(timeout_msec, + TIMEOUT_APPROX_THRESHOLD_SEC, + timeout_expire_cb, + ndisc); + } + + /* When we receive an RA, we don't disable solicitations entirely. Instead, + * we set the interval the maximum (RFC7559_MRT). + * + * This contradicts https://tools.ietf.org/html/rfc7559#section-2.1, which says + * that we SHOULD stop sending RS if we receive an RA -- but only on a multicast + * capable link and if the RA has a valid router lifetime. + * + * But we really want to recover from a dead router on the network, so we + * don't want to cease sending RS entirely. + * + * But we only re-schedule the timer if the current interval is not already + * "RFC7559_MRT * 1000". Otherwise, we already have a slow interval counter + * pending. */ + if (priv->solicit_retransmit_time_msec != RFC7559_MRT * 1000) { + gint32 timeout_msec; + + priv->solicit_retransmit_time_msec = RFC7559_MRT * 1000; + timeout_msec = solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec); + + _LOGD("solicit: schedule sending next (slow) solicitation in about %.3f seconds", + ((double) timeout_msec) / 1000); + + nm_clear_g_source_inst(&priv->solicit_timer_source); + priv->solicit_timer_source = + nm_g_timeout_add_source_approx(timeout_msec, 0, solicit_timer_cb, ndisc); + } + + if (changed != NM_NDISC_CONFIG_NONE) + nm_ndisc_emit_config_change(ndisc, changed); +} + +static gboolean +timeout_expire_cb(gpointer user_data) +{ + check_timestamps(user_data, nm_utils_get_monotonic_timestamp_msec(), NM_NDISC_CONFIG_NONE); + return G_SOURCE_CONTINUE; +} + +void +nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + + nm_clear_g_source_inst(&priv->ra_timeout_source); + nm_clear_g_free(&priv->last_error); + check_timestamps(ndisc, now_msec, changed); +} + +void +nm_ndisc_rs_received(NMNDisc *ndisc) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + + nm_clear_g_free(&priv->last_error); + announce_router_solicited(ndisc); +} + +/*****************************************************************************/ + +static void +dns_domain_free(gpointer data) +{ + g_free(((NMNDiscDNSDomain *) (data))->domain); +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMNDisc * self = NM_NDISC(object); + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(self); + int i; + + switch (prop_id) { + case PROP_PLATFORM: + /* construct-only */ + priv->platform = g_value_get_object(value) ?: NM_PLATFORM_GET; + if (!priv->platform) + g_return_if_reached(); + + g_object_ref(priv->platform); + + priv->netns = nm_platform_netns_get(priv->platform); + if (priv->netns) + g_object_ref(priv->netns); + + g_return_if_fail(!priv->netns || priv->netns == nmp_netns_get_current()); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + g_return_if_fail(priv->ifindex > 0); + break; + case PROP_IFNAME: + /* construct-only */ + priv->ifname = g_value_dup_string(value); + g_return_if_fail(priv->ifname && priv->ifname[0]); + break; + case PROP_STABLE_TYPE: + /* construct-only */ + priv->stable_type = g_value_get_int(value); + break; + case PROP_NETWORK_ID: + /* construct-only */ + priv->network_id = g_value_dup_string(value); + g_return_if_fail(priv->network_id); + break; + case PROP_ADDR_GEN_MODE: + /* construct-only */ + priv->addr_gen_mode = g_value_get_int(value); + break; + case PROP_MAX_ADDRESSES: + /* construct-only */ + i = g_value_get_int(value); + nm_assert(i >= 0); + priv->max_addresses = i; + + if (priv->max_addresses <= 0) + priv->max_addresses = _SIZE_MAX_ADDRESSES; + else if (priv->max_addresses > 3u * _SIZE_MAX_ADDRESSES) + priv->max_addresses = 3u * _SIZE_MAX_ADDRESSES; + break; + case PROP_RA_TIMEOUT: + /* construct-only */ + priv->ra_timeout = g_value_get_uint(value); + nm_assert(priv->ra_timeout <= NM_RA_TIMEOUT_INFINITY); + break; + case PROP_ROUTER_SOLICITATIONS: + /* construct-only */ + priv->router_solicitations = g_value_get_int(value); + break; + case PROP_ROUTER_SOLICITATION_INTERVAL: + /* construct-only */ + priv->router_solicitation_interval = g_value_get_int(value); + break; + case PROP_NODE_TYPE: + /* construct-only */ + priv->node_type = g_value_get_int(value); + nm_assert(NM_IN_SET(priv->node_type, NM_NDISC_NODE_TYPE_HOST, NM_NDISC_NODE_TYPE_ROUTER)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_ndisc_init(NMNDisc *ndisc) +{ + NMNDiscPrivate * priv; + NMNDiscDataInternal *rdata; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(ndisc, NM_TYPE_NDISC, NMNDiscPrivate); + ndisc->_priv = priv; + + rdata = &priv->rdata; + + rdata->gateways = g_array_new(FALSE, FALSE, sizeof(NMNDiscGateway)); + rdata->addresses = g_array_new(FALSE, FALSE, sizeof(NMNDiscAddress)); + rdata->routes = g_array_new(FALSE, FALSE, sizeof(NMNDiscRoute)); + rdata->dns_servers = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSServer)); + rdata->dns_domains = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSDomain)); + g_array_set_clear_func(rdata->dns_domains, dns_domain_free); + priv->rdata.public.hop_limit = 64; +} + +static void +dispose(GObject *object) +{ + NMNDisc * ndisc = NM_NDISC(object); + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + + nm_clear_g_source_inst(&priv->ra_timeout_source); + nm_clear_g_source_inst(&priv->solicit_timer_source); + nm_clear_g_source(&priv->send_ra_id); + nm_clear_g_free(&priv->last_error); + + nm_clear_g_source_inst(&priv->timeout_expire_source); + + G_OBJECT_CLASS(nm_ndisc_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMNDisc * ndisc = NM_NDISC(object); + NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); + NMNDiscDataInternal *rdata = &priv->rdata; + + g_free(priv->ifname); + g_free(priv->network_id); + + g_array_unref(rdata->gateways); + g_array_unref(rdata->addresses); + g_array_unref(rdata->routes); + g_array_unref(rdata->dns_servers); + g_array_unref(rdata->dns_domains); + + g_clear_object(&priv->netns); + g_clear_object(&priv->platform); + + G_OBJECT_CLASS(nm_ndisc_parent_class)->finalize(object); +} + +static void +nm_ndisc_class_init(NMNDiscClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMNDiscPrivate)); + + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_PLATFORM] = + g_param_spec_object(NM_NDISC_PLATFORM, + "", + "", + NM_TYPE_PLATFORM, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_NDISC_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFNAME] = + g_param_spec_string(NM_NDISC_IFNAME, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_STABLE_TYPE] = + g_param_spec_int(NM_NDISC_STABLE_TYPE, + "", + "", + NM_UTILS_STABLE_TYPE_UUID, + NM_UTILS_STABLE_TYPE_RANDOM, + NM_UTILS_STABLE_TYPE_UUID, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NETWORK_ID] = + g_param_spec_string(NM_NDISC_NETWORK_ID, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ADDR_GEN_MODE] = + g_param_spec_int(NM_NDISC_ADDR_GEN_MODE, + "", + "", + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_MAX_ADDRESSES] = + g_param_spec_int(NM_NDISC_MAX_ADDRESSES, + "", + "", + 0, + G_MAXINT32, + NM_NDISC_MAX_ADDRESSES_DEFAULT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_RA_TIMEOUT_INFINITY); + obj_properties[PROP_RA_TIMEOUT] = + g_param_spec_uint(NM_NDISC_RA_TIMEOUT, + "", + "", + 0, + G_MAXINT32, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTER_SOLICITATIONS] = + g_param_spec_int(NM_NDISC_ROUTER_SOLICITATIONS, + "", + "", + 1, + G_MAXINT32, + NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTER_SOLICITATION_INTERVAL] = + g_param_spec_int(NM_NDISC_ROUTER_SOLICITATION_INTERVAL, + "", + "", + 1, + G_MAXINT32, + NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NODE_TYPE] = + g_param_spec_int(NM_NDISC_NODE_TYPE, + "", + "", + NM_NDISC_NODE_TYPE_INVALID, + NM_NDISC_NODE_TYPE_ROUTER, + NM_NDISC_NODE_TYPE_INVALID, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[CONFIG_RECEIVED] = g_signal_new(NM_NDISC_CONFIG_RECEIVED, + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_UINT); + signals[RA_TIMEOUT_SIGNAL] = g_signal_new(NM_NDISC_RA_TIMEOUT_SIGNAL, + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h new file mode 100644 index 0000000..93aee4d --- /dev/null +++ b/src/core/ndisc/nm-ndisc.h @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_NDISC_H__ +#define __NETWORKMANAGER_NDISC_H__ + +#include +#include +#include + +#include "nm-setting-ip6-config.h" +#include "NetworkManagerUtils.h" + +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" + +#define NM_RA_TIMEOUT_DEFAULT ((guint32) 0) +#define NM_RA_TIMEOUT_INFINITY ((guint32) G_MAXINT32) + +#define NM_TYPE_NDISC (nm_ndisc_get_type()) +#define NM_NDISC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_NDISC, NMNDisc)) +#define NM_NDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_NDISC, NMNDiscClass)) +#define NM_IS_NDISC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_NDISC)) +#define NM_IS_NDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_NDISC)) +#define NM_NDISC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_NDISC, NMNDiscClass)) + +#define NM_NDISC_PLATFORM "platform" +#define NM_NDISC_IFINDEX "ifindex" +#define NM_NDISC_IFNAME "ifname" +#define NM_NDISC_NETWORK_ID "network-id" +#define NM_NDISC_ADDR_GEN_MODE "addr-gen-mode" +#define NM_NDISC_STABLE_TYPE "stable-type" +#define NM_NDISC_NODE_TYPE "node-type" +#define NM_NDISC_MAX_ADDRESSES "max-addresses" +#define NM_NDISC_RA_TIMEOUT "ra-timeout" +#define NM_NDISC_ROUTER_SOLICITATIONS "router-solicitations" +#define NM_NDISC_ROUTER_SOLICITATION_INTERVAL "router-solicitation-interval" + +#define NM_NDISC_CONFIG_RECEIVED "config-received" +#define NM_NDISC_RA_TIMEOUT_SIGNAL "ra-timeout-signal" + +typedef enum { + NM_NDISC_DHCP_LEVEL_UNKNOWN, + NM_NDISC_DHCP_LEVEL_NONE, + NM_NDISC_DHCP_LEVEL_OTHERCONF, + NM_NDISC_DHCP_LEVEL_MANAGED +} NMNDiscDHCPLevel; + +#define NM_NDISC_INFINITY_U32 ((uint32_t) -1) + +/* It's important that this is G_MAXINT64, so that we can meaningfully do + * MIN(e1, e2) to find the minimum expiry time (and properly handle if any + * of them is infinity). + * + * While usually you assign this to "expiry_msec", you might say the + * unit of it is milliseconds. But of course, infinity has not really a unit. */ +#define NM_NDISC_EXPIRY_INFINITY G_MAXINT64 + +/* in common cases, the expiry_msec tracks the timestamp in nm_utils_get_monotonic_timestamp_mses() + * timestamp when the item expires. + * + * When we configure an NMNDiscAddress to be announced via the router advertisement, + * then that address does not have a fixed expiry point in time, instead, the expiry + * really contains the lifetime from the moment when we send the router advertisement. + * In that case, the expiry_msec is more a "lifetime" that starts counting at timestamp + * zero. + * + * The unit is milliseconds (but of course, the timestamp is zero, so it doesn't really matter). */ +#define NM_NDISC_EXPIRY_BASE_TIMESTAMP ((gint64) 0) + +static inline gint64 +_nm_ndisc_lifetime_to_expiry(gint64 now_msec, guint32 lifetime) +{ + if (lifetime == NM_NDISC_INFINITY_U32) + return NM_NDISC_EXPIRY_INFINITY; + return now_msec + (((gint64) lifetime) * 1000); +} + +static inline gint64 +_nm_ndisc_lifetime_from_expiry(gint64 now_msec, gint64 expiry_msec, gboolean ceil) +{ + gint64 diff; + + if (expiry_msec == NM_NDISC_EXPIRY_INFINITY) + return NM_NDISC_INFINITY_U32; + + /* we don't expect nor handle integer overflow. The time stamp and expiry + * should be reasonably small so that it cannot happen. */ + + diff = expiry_msec - now_msec; + + if (diff <= 0) + return 0; + + if (ceil) { + /* we ceil() towards the next full second (instead of floor()). */ + diff += 999; + } + + return NM_MIN(diff / 1000, (gint64)(G_MAXUINT32 - 1)); +} + +/*****************************************************************************/ + +typedef struct _NMNDiscGateway { + struct in6_addr address; + gint64 expiry_msec; + NMIcmpv6RouterPref preference; +} NMNDiscGateway; + +typedef struct _NMNDiscAddress { + struct in6_addr address; + gint64 expiry_msec; + gint64 expiry_preferred_msec; + guint8 dad_counter; +} NMNDiscAddress; + +typedef struct _NMNDiscRoute { + struct in6_addr network; + struct in6_addr gateway; + gint64 expiry_msec; + NMIcmpv6RouterPref preference; + guint8 plen; +} NMNDiscRoute; + +typedef struct { + struct in6_addr address; + gint64 expiry_msec; +} NMNDiscDNSServer; + +typedef struct { + char * domain; + gint64 expiry_msec; +} NMNDiscDNSDomain; + +typedef enum { + NM_NDISC_CONFIG_NONE = 0, + NM_NDISC_CONFIG_DHCP_LEVEL = 1 << 0, + NM_NDISC_CONFIG_GATEWAYS = 1 << 1, + NM_NDISC_CONFIG_ADDRESSES = 1 << 2, + NM_NDISC_CONFIG_ROUTES = 1 << 3, + NM_NDISC_CONFIG_DNS_SERVERS = 1 << 4, + NM_NDISC_CONFIG_DNS_DOMAINS = 1 << 5, + NM_NDISC_CONFIG_HOP_LIMIT = 1 << 6, + NM_NDISC_CONFIG_MTU = 1 << 7, + NM_NDISC_CONFIG_REACHABLE_TIME = 1 << 8, + NM_NDISC_CONFIG_RETRANS_TIMER = 1 << 9, +} NMNDiscConfigMap; + +typedef enum { + NM_NDISC_NODE_TYPE_INVALID, + NM_NDISC_NODE_TYPE_HOST, + NM_NDISC_NODE_TYPE_ROUTER, +} NMNDiscNodeType; + +#define NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL 4 /* seconds */ +#define NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY 1 /* seconds */ + +#define NM_NDISC_MAX_ADDRESSES_DEFAULT 16 +#define NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT 3 /* RFC4861, MAX_RTR_SOLICITATIONS */ +#define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT 3 /* RFC4861, MAX_INITIAL_RTR_ADVERTISEMENTS */ +#define NM_NDISC_ROUTER_ADVERT_DELAY 3 /* RFC4861, MIN_DELAY_BETWEEN_RAS */ +#define NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL 16 /* RFC4861, MAX_INITIAL_RTR_ADVERT_INTERVAL */ +#define NM_NDISC_ROUTER_ADVERT_DELAY_MS 500 /* RFC4861, MAX_RA_DELAY_TIME */ +#define NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL 600 /* RFC4861, MaxRtrAdvInterval default */ +#define NM_NDISC_ROUTER_LIFETIME 900 /* 1.5 * NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL */ + +struct _NMNDiscPrivate; +struct _NMNDiscDataInternal; + +typedef struct { + NMNDiscDHCPLevel dhcp_level; + guint32 mtu; + int hop_limit; + guint32 reachable_time_ms; + guint32 retrans_timer_ms; + + guint gateways_n; + guint addresses_n; + guint routes_n; + guint dns_servers_n; + guint dns_domains_n; + + const NMNDiscGateway * gateways; + const NMNDiscAddress * addresses; + const NMNDiscRoute * routes; + const NMNDiscDNSServer *dns_servers; + const NMNDiscDNSDomain *dns_domains; +} NMNDiscData; + +/** + * NMNDisc: + * + * Interface-specific structure that handles incoming router advertisements, + * caches advertised items and removes them when they are obsolete. + */ +typedef struct { + GObject parent; + union { + struct _NMNDiscPrivate * _priv; + struct _NMNDiscDataInternal *rdata; + }; +} NMNDisc; + +typedef struct { + GObjectClass parent; + + void (*start)(NMNDisc *ndisc); + void (*stop)(NMNDisc *ndisc); + gboolean (*send_rs)(NMNDisc *ndisc, GError **error); + gboolean (*send_ra)(NMNDisc *ndisc, GError **error); +} NMNDiscClass; + +GType nm_ndisc_get_type(void); + +void nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed); + +int nm_ndisc_get_ifindex(NMNDisc *self); +const char * nm_ndisc_get_ifname(NMNDisc *self); +NMNDiscNodeType nm_ndisc_get_node_type(NMNDisc *self); + +gboolean nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid); +void nm_ndisc_start(NMNDisc *ndisc); +void nm_ndisc_stop(NMNDisc *ndisc); +NMNDiscConfigMap +nm_ndisc_dad_failed(NMNDisc *ndisc, const struct in6_addr *address, gboolean emit_changed_signal); +void nm_ndisc_set_config(NMNDisc * ndisc, + const GArray *addresses, + const GArray *dns_servers, + const GArray *dns_domains); + +NMPlatform *nm_ndisc_get_platform(NMNDisc *self); +NMPNetns * nm_ndisc_netns_get(NMNDisc *self); +gboolean nm_ndisc_netns_push(NMNDisc *self, NMPNetns **netns); + +static inline gboolean +nm_ndisc_dad_addr_is_fail_candidate_event(NMPlatformSignalChangeType change_type, + const NMPlatformIP6Address *addr) +{ + return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY) + && ((change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED) + || (change_type == NM_PLATFORM_SIGNAL_REMOVED + && addr->n_ifa_flags & IFA_F_TENTATIVE)); +} + +static inline gboolean +nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj) +{ + const NMPlatformIP6Address *addr; + + addr = NMP_OBJECT_CAST_IP6_ADDRESS( + nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)); + if (addr + && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY) + || !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED))) { + /* the address still/again exists and is not in DADFAILED state. Skip it. */ + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +struct _NML3ConfigData; + +struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex * multi_idx, + int ifindex, + const NMNDiscData * rdata, + NMSettingIP6ConfigPrivacy ip6_privacy, + guint32 route_table, + guint32 route_metric, + gboolean kernel_support_rta_pref, + gboolean kernel_support_extended_ifa_flags); + +#endif /* __NETWORKMANAGER_NDISC_H__ */ diff --git a/src/core/ndisc/tests/meson.build b/src/core/ndisc/tests/meson.build new file mode 100644 index 0000000..6a2e70f --- /dev/null +++ b/src/core/ndisc/tests/meson.build @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_unit = 'test-ndisc-fake' + +exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) + +test( + 'ndisc/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) + +test = 'test-ndisc-linux' + +exe = executable( + test, + test + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) diff --git a/src/core/ndisc/tests/test-ndisc-fake.c b/src/core/ndisc/tests/test-ndisc-fake.c new file mode 100644 index 0000000..3c4bc1f --- /dev/null +++ b/src/core/ndisc/tests/test-ndisc-fake.c @@ -0,0 +1,657 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "ndisc/nm-ndisc.h" +#include "ndisc/nm-fake-ndisc.h" + +#include "platform/nm-fake-platform.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +static NMFakeNDisc * +ndisc_new(void) +{ + NMNDisc * ndisc; + const int ifindex = 1; + const char * ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex); + NMUtilsIPv6IfaceId iid = {}; + + ndisc = nm_fake_ndisc_new(ifindex, ifname); + iid.id_u8[7] = 1; + nm_ndisc_set_iid(ndisc, iid); + g_assert(ndisc); + + return NM_FAKE_NDISC(ndisc); +} + +/*****************************************************************************/ + +static void +match_gateway(const NMNDiscData *rdata, + guint idx, + const char * addr, + gint64 expiry_msec, + NMIcmpv6RouterPref pref) +{ + const NMNDiscGateway *gw; + + g_assert(rdata); + g_assert_cmpint(idx, <, rdata->gateways_n); + g_assert(rdata->gateways); + + gw = &rdata->gateways[idx]; + + nmtst_assert_ip6_address(&gw->address, addr); + g_assert_cmpint(gw->expiry_msec, ==, expiry_msec); + g_assert_cmpint(gw->preference, ==, pref); +} + +#define match_address(rdata, idx, addr, _expiry_msec, _expiry_preferred_msec) \ + G_STMT_START \ + { \ + const NMNDiscData * _rdata = (rdata); \ + guint _idx = (idx); \ + const NMNDiscAddress *_a; \ + \ + g_assert(_rdata); \ + g_assert_cmpint(_idx, <, _rdata->addresses_n); \ + g_assert(_rdata->addresses); \ + \ + _a = &_rdata->addresses[_idx]; \ + \ + nmtst_assert_ip6_address(&_a->address, (addr)); \ + g_assert_cmpint(_a->expiry_msec, ==, (_expiry_msec)); \ + g_assert_cmpint(_a->expiry_preferred_msec, ==, (_expiry_preferred_msec)); \ + } \ + G_STMT_END + +#define match_route(rdata, idx, nw, pl, gw, _expiry_msec, pref) \ + G_STMT_START \ + { \ + const NMNDiscData * _rdata = (rdata); \ + guint _idx = (idx); \ + const NMNDiscRoute *_r; \ + int _plen = (pl); \ + \ + g_assert(_rdata); \ + g_assert_cmpint(_idx, <, _rdata->routes_n); \ + g_assert(_rdata->routes); \ + g_assert(_plen > 0 && _plen <= 128); \ + \ + _r = &_rdata->routes[idx]; \ + \ + nmtst_assert_ip6_address(&_r->network, (nw)); \ + g_assert_cmpint((int) _r->plen, ==, _plen); \ + nmtst_assert_ip6_address(&_r->gateway, (gw)); \ + g_assert_cmpint(_r->expiry_msec, ==, (_expiry_msec)); \ + g_assert_cmpint(_r->preference, ==, (pref)); \ + } \ + G_STMT_END + +static void +match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, gint64 expiry_msec) +{ + const NMNDiscDNSServer *dns; + + g_assert(rdata); + g_assert_cmpint(idx, <, rdata->dns_servers_n); + g_assert(rdata->dns_servers); + + dns = &rdata->dns_servers[idx]; + + nmtst_assert_ip6_address(&dns->address, addr); + g_assert_cmpint(dns->expiry_msec, ==, expiry_msec); +} + +static void +match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, gint64 expiry_msec) +{ + const NMNDiscDNSDomain *dns; + + g_assert(rdata); + g_assert_cmpint(idx, <, rdata->dns_domains_n); + g_assert(rdata->dns_domains); + + dns = &rdata->dns_domains[idx]; + + g_assert_cmpstr(dns->domain, ==, domain); + g_assert_cmpint(dns->expiry_msec, ==, expiry_msec); +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *loop; + gint64 timestamp_msec_1; + guint counter; + guint rs_counter; + gint64 first_solicit_msec; + guint32 timeout_id; +} TestData; + +/*****************************************************************************/ + +static void +test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data) +{ + NMNDiscConfigMap changed = changed_int; + + switch (data->counter++) { + case 0: + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS + | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES + | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS + | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU); + g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF); + match_gateway(rdata, + 0, + "fe80::1", + data->timestamp_msec_1 + 10000, + NM_ICMPV6_ROUTER_PREF_MEDIUM); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1 + 10000); + match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10); + match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000); + match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 3500); + + g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); + break; + case 1: + g_main_loop_quit(data->loop); + break; + default: + g_assert_not_reached(); + } +} + +static void +test_simple(void) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE); + gs_unref_object NMFakeNDisc *ndisc = ndisc_new(); + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + TestData data = { + .loop = loop, + .timestamp_msec_1 = now_msec, + }; + guint id; + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500); + g_assert(id); + + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:a::", + 64, + "fe80::1", + now_msec + 10000, + now_msec + 10000, + 10); + nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000); + nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 3500); + + g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data); + + nm_ndisc_start(NM_NDISC(ndisc)); + nmtst_main_loop_run_assert(data.loop, 15000); + g_assert_cmpint(data.counter, ==, 2); +} + +/*****************************************************************************/ + +static void +test_everything_rs_sent(NMNDisc *ndisc, TestData *data) +{ + g_assert_cmpint(data->rs_counter, ==, 0); + data->rs_counter++; +} + +static void +test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data) +{ + NMNDiscConfigMap changed = changed_int; + + if (data->counter == 0) { + g_assert_cmpint(data->rs_counter, ==, 1); + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS + | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES + | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS + | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU); + match_gateway(rdata, + 0, + "fe80::1", + data->timestamp_msec_1 + 10000, + NM_ICMPV6_ROUTER_PREF_MEDIUM); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1 + 10000); + match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10); + match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000); + match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 10000); + } else if (data->counter == 1) { + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES + | NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_DNS_SERVERS + | NM_NDISC_CONFIG_DNS_DOMAINS); + + g_assert_cmpint(rdata->gateways_n, ==, 1); + match_gateway(rdata, + 0, + "fe80::2", + data->timestamp_msec_1 + 10000, + NM_ICMPV6_ROUTER_PREF_MEDIUM); + g_assert_cmpint(rdata->addresses_n, ==, 2); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1); + match_address(rdata, + 1, + "2001:db8:a:b::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1 + 10000); + g_assert_cmpint(rdata->routes_n, ==, 1); + match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 10000, 10); + g_assert_cmpint(rdata->dns_servers_n, ==, 1); + match_dns_server(rdata, 0, "2001:db8:c:c::2", data->timestamp_msec_1 + 10000); + g_assert_cmpint(rdata->dns_domains_n, ==, 1); + match_dns_domain(rdata, 0, "foobar2.com", data->timestamp_msec_1 + 10000); + + g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); + g_main_loop_quit(data->loop); + } else + g_assert_not_reached(); + + data->counter++; +} + +static void +test_everything(void) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE); + gs_unref_object NMFakeNDisc *ndisc = ndisc_new(); + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + TestData data = { + .loop = loop, + .timestamp_msec_1 = now_msec, + }; + guint id; + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:a::", + 64, + "fe80::1", + now_msec + 10000, + now_msec + 10000, + 10); + nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000); + nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 10000); + + /* expire everything from the first RA in the second */ + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec, NM_ICMPV6_ROUTER_PREF_MEDIUM); + nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now_msec, now_msec, 0); + nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec); + nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec); + + /* and add some new stuff */ + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:b::", + 64, + "fe80::2", + now_msec + 10000, + now_msec + 10000, + 10); + nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now_msec + 10000); + nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now_msec + 10000); + + g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_everything_changed), &data); + g_signal_connect(ndisc, NM_FAKE_NDISC_RS_SENT, G_CALLBACK(test_everything_rs_sent), &data); + + nm_ndisc_start(NM_NDISC(ndisc)); + nmtst_main_loop_run_assert(data.loop, 15000); + g_assert_cmpint(data.counter, ==, 2); + g_assert_cmpint(data.rs_counter, ==, 1); +} + +static void +test_preference_order_cb(NMNDisc * ndisc, + const NMNDiscData *rdata, + guint changed_int, + TestData * data) +{ + NMNDiscConfigMap changed = changed_int; + + if (data->counter == 1) { + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES + | NM_NDISC_CONFIG_ROUTES); + + g_assert_cmpint(rdata->gateways_n, ==, 2); + match_gateway(rdata, + 0, + "fe80::1", + data->timestamp_msec_1 + 10000, + NM_ICMPV6_ROUTER_PREF_HIGH); + match_gateway(rdata, + 1, + "fe80::2", + data->timestamp_msec_1 + 11000, + NM_ICMPV6_ROUTER_PREF_LOW); + g_assert_cmpint(rdata->addresses_n, ==, 2); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1 + 10000); + match_address(rdata, + 1, + "2001:db8:a:b::1", + data->timestamp_msec_1 + 11000, + data->timestamp_msec_1 + 10000); + g_assert_cmpint(rdata->routes_n, ==, 2); + match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10); + match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5); + + g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); + g_main_loop_quit(data->loop); + } + + data->counter++; +} + +static void +test_preference_order(void) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE); + gs_unref_object NMFakeNDisc *ndisc = ndisc_new(); + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + TestData data = { + .loop = loop, + .timestamp_msec_1 = now_msec, + }; + guint id; + + /* Test insertion order of gateways */ + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_HIGH); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:a::", + 64, + "fe80::1", + now_msec + 10000, + now_msec + 10000, + 5); + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_LOW); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:b::", + 64, + "fe80::2", + now_msec + 11000, + now_msec + 10000, + 10); + + g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data); + + nm_ndisc_start(NM_NDISC(ndisc)); + nmtst_main_loop_run_assert(data.loop, 15000); + g_assert_cmpint(data.counter, ==, 2); +} + +static void +test_preference_changed_cb(NMNDisc * ndisc, + const NMNDiscData *rdata, + guint changed_int, + TestData * data) +{ + NMNDiscConfigMap changed = changed_int; + + if (data->counter == 1) { + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES + | NM_NDISC_CONFIG_ROUTES); + g_assert_cmpint(rdata->gateways_n, ==, 2); + match_gateway(rdata, + 0, + "fe80::2", + data->timestamp_msec_1 + 11000, + NM_ICMPV6_ROUTER_PREF_MEDIUM); + match_gateway(rdata, + 1, + "fe80::1", + data->timestamp_msec_1 + 10000, + NM_ICMPV6_ROUTER_PREF_LOW); + g_assert_cmpint(rdata->addresses_n, ==, 2); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 10000, + data->timestamp_msec_1 + 10000); + match_address(rdata, + 1, + "2001:db8:a:b::1", + data->timestamp_msec_1 + 11000, + data->timestamp_msec_1 + 11000); + g_assert_cmpint(rdata->routes_n, ==, 2); + match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10); + match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5); + } else if (data->counter == 2) { + g_assert_cmpint(changed, + ==, + NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES + | NM_NDISC_CONFIG_ROUTES); + + g_assert_cmpint(rdata->gateways_n, ==, 2); + match_gateway(rdata, + 0, + "fe80::1", + data->timestamp_msec_1 + 12000, + NM_ICMPV6_ROUTER_PREF_HIGH); + match_gateway(rdata, + 1, + "fe80::2", + data->timestamp_msec_1 + 11000, + NM_ICMPV6_ROUTER_PREF_MEDIUM); + g_assert_cmpint(rdata->addresses_n, ==, 2); + match_address(rdata, + 0, + "2001:db8:a:a::1", + data->timestamp_msec_1 + 12000, + data->timestamp_msec_1 + 12000); + match_address(rdata, + 1, + "2001:db8:a:b::1", + data->timestamp_msec_1 + 11000, + data->timestamp_msec_1 + 11000); + g_assert_cmpint(rdata->routes_n, ==, 2); + match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 12000, 15); + match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10); + + g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); + g_main_loop_quit(data->loop); + } + + data->counter++; +} + +static void +test_preference_changed(void) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE); + gs_unref_object NMFakeNDisc *ndisc = ndisc_new(); + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + TestData data = { + .loop = loop, + .timestamp_msec_1 = now_msec, + }; + guint id; + + /* Test that when a low-preference and medium gateway send advertisements, + * that if the low-preference gateway switches to high-preference, we do + * not get duplicates in the gateway list. + */ + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:a::", + 64, + "fe80::1", + now_msec + 10000, + now_msec + 10000, + 5); + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_MEDIUM); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:b::", + 64, + "fe80::2", + now_msec + 11000, + now_msec + 11000, + 10); + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 12000, NM_ICMPV6_ROUTER_PREF_HIGH); + nm_fake_ndisc_add_prefix(ndisc, + id, + "2001:db8:a:a::", + 64, + "fe80::1", + now_msec + 12000, + now_msec + 12000, + 15); + + g_signal_connect(ndisc, + NM_NDISC_CONFIG_RECEIVED, + G_CALLBACK(test_preference_changed_cb), + &data); + + nm_ndisc_start(NM_NDISC(ndisc)); + nmtst_main_loop_run_assert(data.loop, 15000); + g_assert_cmpint(data.counter, ==, 3); +} + +/*****************************************************************************/ + +static void +_test_dns_solicit_loop_changed(NMNDisc * ndisc, + const NMNDiscData *rdata, + guint changed_int, + TestData * data) +{ + data->counter++; +} + +static void +_test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data) +{ + data->rs_counter++; +} + +static void +test_dns_solicit_loop(void) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE); + gs_unref_object NMFakeNDisc *ndisc = ndisc_new(); + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + TestData data = { + .loop = loop, + .timestamp_msec_1 = now_msec, + }; + guint id; + + /* Ensure that no solicitation loop happens when DNS servers or domains + * stop being sent in advertisements. This can happen if two routers + * send RAs, but the one sending DNS info stops responding, or if one + * router removes the DNS info from the RA without zero-lifetiming them + * first. + */ + + id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert(id); + nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW); + nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 6000); + + g_signal_connect(ndisc, + NM_NDISC_CONFIG_RECEIVED, + G_CALLBACK(_test_dns_solicit_loop_changed), + &data); + g_signal_connect(ndisc, + NM_FAKE_NDISC_RS_SENT, + G_CALLBACK(_test_dns_solicit_loop_rs_sent), + &data); + + nm_ndisc_start(NM_NDISC(ndisc)); + if (nmtst_main_loop_run(data.loop, 10000)) + g_error("we expect to run the loop until timeout. What is wrong?"); + g_assert_cmpint(data.counter, ==, 3); + g_assert_cmpint(data.rs_counter, ==, 1); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT"); + + if (nmtst_test_quick()) { + g_print("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-ndisc-fake"); + return g_test_run(); + } + + nm_fake_platform_setup(); + + g_test_add_func("/ndisc/simple", test_simple); + g_test_add_func("/ndisc/everything-changed", test_everything); + g_test_add_func("/ndisc/preference-order", test_preference_order); + g_test_add_func("/ndisc/preference-changed", test_preference_changed); + g_test_add_func("/ndisc/dns-solicit-loop", test_dns_solicit_loop); + + return g_test_run(); +} diff --git a/src/core/ndisc/tests/test-ndisc-linux.c b/src/core/ndisc/tests/test-ndisc-linux.c new file mode 100644 index 0000000..2517e2b --- /dev/null +++ b/src/core/ndisc/tests/test-ndisc-linux.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "ndisc/nm-ndisc.h" +#include "ndisc/nm-lndp-ndisc.h" + +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + GMainLoop * loop; + NMNDisc * ndisc; + int ifindex = 1; + const char * ifname; + NMUtilsIPv6IfaceId iid = {}; + GError * error = NULL; + int max_addresses; + int router_solicitations; + int router_solicitation_interval; + guint32 ra_timeout; + + nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT"); + + if (getuid() != 0) { + g_print("Missing permission: must run as root\n"); + return EXIT_FAILURE; + } + + loop = g_main_loop_new(NULL, FALSE); + + nm_linux_platform_setup(); + + if (argv[1]) { + ifname = argv[1]; + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, ifname); + } else { + g_print("Missing command line argument \"interface-name\"\n"); + return EXIT_FAILURE; + } + + nm_lndp_ndisc_get_sysctl(NM_PLATFORM_GET, + ifname, + &max_addresses, + &router_solicitations, + &router_solicitation_interval, + &ra_timeout); + + ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET, + ifindex, + ifname, + NM_UTILS_STABLE_TYPE_UUID, + "8ce666e8-d34d-4fb1-b858-f15a7al28086", + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NM_NDISC_NODE_TYPE_HOST, + max_addresses, + router_solicitations, + router_solicitation_interval, + ra_timeout, + &error); + if (!ndisc) { + g_print("Failed to create NMNDisc instance: %s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + iid.id_u8[7] = 1; + nm_ndisc_set_iid(ndisc, iid); + nm_ndisc_start(ndisc); + g_main_loop_run(loop); + + g_clear_object(&ndisc); + + return EXIT_SUCCESS; +} diff --git a/src/core/nm-act-request.c b/src/core/nm-act-request.c new file mode 100644 index 0000000..248b57b --- /dev/null +++ b/src/core/nm-act-request.c @@ -0,0 +1,542 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2012 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-act-request.h" + +#include +#include +#include + +#include "c-list/src/c-list.h" + +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" +#include "devices/nm-device.h" +#include "nm-active-connection.h" +#include "settings/nm-settings-connection.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" + +typedef struct { + CList call_ids_lst_head; + NMUtilsShareRules *share_rules; +} NMActRequestPrivate; + +struct _NMActRequest { + NMActiveConnection parent; + NMActRequestPrivate _priv; +}; + +typedef struct { + NMActiveConnectionClass parent; +} NMActRequestClass; + +enum { + PROP_0, + PROP_IP4_CONFIG, + PROP_DHCP4_CONFIG, + PROP_IP6_CONFIG, + PROP_DHCP6_CONFIG, + + LAST_PROP +}; + +G_DEFINE_TYPE(NMActRequest, nm_act_request, NM_TYPE_ACTIVE_CONNECTION) + +#define NM_ACT_REQUEST_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMActRequest, NM_IS_ACT_REQUEST) + +/*****************************************************************************/ + +NMSettingsConnection * +nm_act_request_get_settings_connection(NMActRequest *req) +{ + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NULL); + + return nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req)); +} + +NMConnection * +nm_act_request_get_applied_connection(NMActRequest *req) +{ + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NULL); + + return nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req)); +} + +/*****************************************************************************/ + +struct _NMActRequestGetSecretsCallId { + CList call_ids_lst; + NMActRequest * self; + NMActRequestSecretsFunc callback; + gpointer callback_data; + NMSettingsConnectionCallId *call_id; + bool has_ref; +}; + +static void +_get_secrets_call_id_free(NMActRequestGetSecretsCallId *call_id) +{ + nm_assert(call_id); + nm_assert(!c_list_is_linked(&call_id->call_ids_lst)); + + if (call_id->has_ref) + g_object_unref(call_id->self); + g_slice_free(NMActRequestGetSecretsCallId, call_id); +} + +static void +get_secrets_cb(NMSettingsConnection * connection, + NMSettingsConnectionCallId *call_id_s, + const char * agent_username, + const char * setting_name, + GError * error, + gpointer user_data) +{ + NMActRequestGetSecretsCallId *call_id = user_data; + NMActRequestPrivate * priv; + + g_return_if_fail(call_id && call_id->call_id == call_id_s); + g_return_if_fail(NM_IS_ACT_REQUEST(call_id->self)); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + priv = NM_ACT_REQUEST_GET_PRIVATE(call_id->self); + + nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst)); + + c_list_unlink(&call_id->call_ids_lst); + + if (call_id->callback) + call_id->callback(call_id->self, call_id, connection, error, call_id->callback_data); + + _get_secrets_call_id_free(call_id); +} + +/** + * nm_act_request_get_secrets: + * @self: + * @ref_self: if %TRUE, the pending call take a reference on @self. + * It also allows you to omit the @self argument in nm_act_request_cancel_secrets(). + * @setting_name: + * @flags: + * @hint: + * @callback: + * @callback_data: + * + * Asynchronously starts the request for secrets. This function cannot + * fail. + * + * The return call-id can be used to cancel the request. You are + * only allowed to cancel a still pending operation (once). + * The callback will always be invoked once, even for canceling + * or disposing of NMActRequest. + * + * Returns: a call-id. + */ +NMActRequestGetSecretsCallId * +nm_act_request_get_secrets(NMActRequest * self, + gboolean ref_self, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMActRequestSecretsFunc callback, + gpointer callback_data) +{ + NMActRequestPrivate * priv; + NMActRequestGetSecretsCallId *call_id; + NMSettingsConnectionCallId * call_id_s; + NMSettingsConnection * settings_connection; + NMConnection * applied_connection; + + g_return_val_if_fail(NM_IS_ACT_REQUEST(self), NULL); + + priv = NM_ACT_REQUEST_GET_PRIVATE(self); + + settings_connection = nm_act_request_get_settings_connection(self); + applied_connection = nm_act_request_get_applied_connection(self); + + call_id = g_slice_new0(NMActRequestGetSecretsCallId); + call_id->has_ref = ref_self; + call_id->self = ref_self ? g_object_ref(self) : self; + call_id->callback = callback; + call_id->callback_data = callback_data; + c_list_link_tail(&priv->call_ids_lst_head, &call_id->call_ids_lst); + + if (nm_active_connection_get_user_requested(NM_ACTIVE_CONNECTION(self))) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED; + + call_id_s = nm_settings_connection_get_secrets( + settings_connection, + applied_connection, + nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(self)), + setting_name, + flags, + hints, + get_secrets_cb, + call_id); + call_id->call_id = call_id_s; + g_return_val_if_fail(call_id_s, NULL); + return call_id; +} + +static void +_do_cancel_secrets(NMActRequest *self, NMActRequestGetSecretsCallId *call_id, gboolean is_disposing) +{ + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(self); + + nm_assert(call_id && call_id->self == self); + nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst)); + + c_list_unlink(&call_id->call_ids_lst); + + nm_settings_connection_cancel_secrets(nm_act_request_get_settings_connection(self), + call_id->call_id); + + if (call_id->callback) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, is_disposing, "NMActRequest"); + call_id->callback(self, call_id, NULL, error, call_id->callback_data); + } + + _get_secrets_call_id_free(call_id); +} + +/** + * nm_act_request_cancel_secrets: + * @self: The #NMActRequest. Note that this argument can be %NULL if, and only if + * the call_id was created with @take_ref. + * @call_id: + * + * You are only allowed to cancel the call once, and only before the callback + * is already invoked. Note that cancelling causes the callback to be invoked + * synchronously. + */ +void +nm_act_request_cancel_secrets(NMActRequest *self, NMActRequestGetSecretsCallId *call_id) +{ + g_return_if_fail(call_id); + + if (self) { + g_return_if_fail(NM_IS_ACT_REQUEST(self)); + g_return_if_fail(self == call_id->self); + } else { + g_return_if_fail(call_id->has_ref); + g_return_if_fail(NM_IS_ACT_REQUEST(call_id->self)); + self = call_id->self; + } + + if (!c_list_is_linked(&call_id->call_ids_lst)) + g_return_if_reached(); + + _do_cancel_secrets(self, call_id, FALSE); +} + +void +nm_act_request_clear_secrets(NMActRequest *self) +{ + g_return_if_fail(NM_IS_ACT_REQUEST(self)); + + nm_active_connection_clear_secrets((NMActiveConnection *) self); +} + +/*****************************************************************************/ + +NMUtilsShareRules * +nm_act_request_get_shared(NMActRequest *req) +{ + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE); + + return NM_ACT_REQUEST_GET_PRIVATE(req)->share_rules; +} + +void +nm_act_request_set_shared(NMActRequest *req, NMUtilsShareRules *rules) +{ + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(req); + + g_return_if_fail(NM_IS_ACT_REQUEST(req)); + + if (priv->share_rules == rules) + return; + + if (priv->share_rules) { + nm_utils_share_rules_apply(priv->share_rules, FALSE); + priv->share_rules = NULL; + } + if (rules) { + priv->share_rules = rules; + nm_utils_share_rules_apply(priv->share_rules, TRUE); + } +} + +/*****************************************************************************/ + +static void +device_notify(GObject *object, GParamSpec *pspec, gpointer self) +{ + g_object_notify(self, pspec->name); +} + +static void +device_state_changed(NMActiveConnection *active, + NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state) +{ + NMActiveConnectionState cur_ac_state = nm_active_connection_get_state(active); + NMActiveConnectionState ac_state = NM_ACTIVE_CONNECTION_STATE_UNKNOWN; + NMActiveConnectionStateReason ac_state_reason = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN; + + /* Decide which device state changes to handle when this active connection + * is not the device's current request. Two cases here: (a) the AC is + * pending and not yet active, and (b) the AC was active but the device is + * entering DISCONNECTED state (which clears the device's current AC before + * emitting the state change signal). + */ + if (NM_ACTIVE_CONNECTION(nm_device_get_act_request(device)) != active) { + /* Some other request is activating; this one must be pending */ + if (new_state >= NM_DEVICE_STATE_PREPARE) + return; + else if (new_state == NM_DEVICE_STATE_DISCONNECTED) { + /* This request hasn't started activating yet; the device is + * disconnecting and cleaning up a previous activation request. + */ + if (cur_ac_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATING) + return; + + /* Catch device disconnections after this request has been active */ + } + + /* All states < DISCONNECTED are fatal and handled */ + } + + /* Set NMActiveConnection state based on the device's state */ + switch (new_state) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + ac_state = NM_ACTIVE_CONNECTION_STATE_ACTIVATING; + break; + case NM_DEVICE_STATE_ACTIVATED: + ac_state = NM_ACTIVE_CONNECTION_STATE_ACTIVATED; + + g_signal_connect(device, + "notify::" NM_DEVICE_IP4_CONFIG, + G_CALLBACK(device_notify), + active); + g_signal_connect(device, + "notify::" NM_DEVICE_DHCP4_CONFIG, + G_CALLBACK(device_notify), + active); + g_signal_connect(device, + "notify::" NM_DEVICE_IP6_CONFIG, + G_CALLBACK(device_notify), + active); + g_signal_connect(device, + "notify::" NM_DEVICE_DHCP6_CONFIG, + G_CALLBACK(device_notify), + active); + break; + case NM_DEVICE_STATE_DEACTIVATING: + ac_state = NM_ACTIVE_CONNECTION_STATE_DEACTIVATING; + break; + case NM_DEVICE_STATE_FAILED: + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + ac_state = NM_ACTIVE_CONNECTION_STATE_DEACTIVATED; + ac_state_reason = NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED; + + g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_notify), active); + break; + default: + break; + } + + if (ac_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED + || ac_state == NM_ACTIVE_CONNECTION_STATE_UNKNOWN) + nm_active_connection_set_default(active, AF_UNSPEC, FALSE); + + nm_active_connection_set_state(active, ac_state, ac_state_reason); +} + +static void +master_failed(NMActiveConnection *self) +{ + NMDevice * device; + NMDeviceState device_state; + + /* If the connection has an active device, fail it */ + device = nm_active_connection_get_device(self); + if (device) { + device_state = nm_device_get_state(device); + if (nm_device_is_activating(device) || (device_state == NM_DEVICE_STATE_ACTIVATED)) { + nm_device_queue_state(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED); + return; + } + } + + /* If no device, or the device wasn't active, just move to deactivated state */ + nm_active_connection_set_state(self, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NM_ACTIVE_CONNECTION_STATE_REASON_DEPENDENCY_FAILED); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMActiveConnection *active; + NMDevice * device; + char * name; + + switch (prop_id) { + case PROP_IP4_CONFIG: + name = NM_DEVICE_IP4_CONFIG; + break; + case PROP_DHCP4_CONFIG: + name = NM_DEVICE_DHCP4_CONFIG; + break; + case PROP_IP6_CONFIG: + name = NM_DEVICE_IP6_CONFIG; + break; + case PROP_DHCP6_CONFIG: + name = NM_DEVICE_DHCP6_CONFIG; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + + active = NM_ACTIVE_CONNECTION(object); + device = nm_active_connection_get_device(active); + if (!device + || !NM_IN_SET(nm_active_connection_get_state(active), + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)) { + g_value_set_string(value, NULL); + return; + } + + g_object_get_property(G_OBJECT(device), name, value); +} + +static void +nm_act_request_init(NMActRequest *req) +{ + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(req); + + c_list_init(&priv->call_ids_lst_head); +} + +/** + * nm_act_request_new: + * + * @settings_connection: (allow-none): the connection to activate @device with + * @applied_connection: (allow-none): the applied connection + * @specific_object: the object path of the specific object (ie, Wi-Fi access point, + * etc) that will be used to activate @connection and @device + * @subject: the #NMAuthSubject representing the requestor of the activation + * @activation_type: the #NMActivationType + * @activation_reason: the reason for activation + * @initial_state_flags: the initial state flags. + * @device: the device/interface to configure according to @connection + * + * Creates a new device-based activation request. If an applied connection is + * supplied, it shall not be modified by the caller afterwards. + * + * Returns: the new activation request on success, %NULL on error. + */ +NMActRequest * +nm_act_request_new(NMSettingsConnection * settings_connection, + NMConnection * applied_connection, + const char * specific_object, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + NMDevice * device) +{ + g_return_val_if_fail(!settings_connection || NM_IS_SETTINGS_CONNECTION(settings_connection), + NULL); + g_return_val_if_fail(NM_IS_DEVICE(device), NULL); + g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL); + + return g_object_new(NM_TYPE_ACT_REQUEST, + NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION, + applied_connection, + NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, + settings_connection, + NM_ACTIVE_CONNECTION_INT_DEVICE, + device, + NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, + specific_object, + NM_ACTIVE_CONNECTION_INT_SUBJECT, + subject, + NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE, + (int) activation_type, + NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, + (int) activation_reason, + NM_ACTIVE_CONNECTION_STATE_FLAGS, + (guint) initial_state_flags, + NULL); +} + +static void +dispose(GObject *object) +{ + NMActRequest * self = NM_ACT_REQUEST(object); + NMActRequestPrivate * priv = NM_ACT_REQUEST_GET_PRIVATE(self); + NMActRequestGetSecretsCallId *call_id, *call_id_safe; + + /* Kill any in-progress secrets requests */ + c_list_for_each_entry_safe (call_id, call_id_safe, &priv->call_ids_lst_head, call_ids_lst) + _do_cancel_secrets(self, call_id, TRUE); + + if (priv->share_rules) { + nm_utils_share_rules_apply(priv->share_rules, FALSE); + nm_clear_pointer(&priv->share_rules, nm_utils_share_rules_free); + } + + G_OBJECT_CLASS(nm_act_request_parent_class)->dispose(object); +} + +static void +nm_act_request_class_init(NMActRequestClass *req_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(req_class); + NMActiveConnectionClass *active_class = NM_ACTIVE_CONNECTION_CLASS(req_class); + + /* virtual methods */ + object_class->dispose = dispose; + object_class->get_property = get_property; + active_class->master_failed = master_failed; + active_class->device_state_changed = device_state_changed; + + /* properties */ + g_object_class_override_property(object_class, + PROP_IP4_CONFIG, + NM_ACTIVE_CONNECTION_IP4_CONFIG); + g_object_class_override_property(object_class, + PROP_DHCP4_CONFIG, + NM_ACTIVE_CONNECTION_DHCP4_CONFIG); + g_object_class_override_property(object_class, + PROP_IP6_CONFIG, + NM_ACTIVE_CONNECTION_IP6_CONFIG); + g_object_class_override_property(object_class, + PROP_DHCP6_CONFIG, + NM_ACTIVE_CONNECTION_DHCP6_CONFIG); +} diff --git a/src/core/nm-act-request.h b/src/core/nm-act-request.h new file mode 100644 index 0000000..2a56843 --- /dev/null +++ b/src/core/nm-act-request.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2012 Red Hat, Inc. + */ + +#ifndef __NM_ACT_REQUEST_H__ +#define __NM_ACT_REQUEST_H__ + +#include "nm-connection.h" +#include "nm-active-connection.h" + +#define NM_TYPE_ACT_REQUEST (nm_act_request_get_type()) +#define NM_ACT_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_ACT_REQUEST, NMActRequest)) +#define NM_ACT_REQUEST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_ACT_REQUEST, NMActRequestClass)) +#define NM_IS_ACT_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_ACT_REQUEST)) +#define NM_IS_ACT_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_ACT_REQUEST)) +#define NM_ACT_REQUEST_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_ACT_REQUEST, NMActRequestClass)) + +struct _NMActRequestGetSecretsCallId; +typedef struct _NMActRequestGetSecretsCallId NMActRequestGetSecretsCallId; + +GType nm_act_request_get_type(void); + +NMActRequest *nm_act_request_new(NMSettingsConnection * settings_connection, + NMConnection * applied_connection, + const char * specific_object, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + NMDevice * device); + +NMSettingsConnection *nm_act_request_get_settings_connection(NMActRequest *req); + +NMConnection *nm_act_request_get_applied_connection(NMActRequest *req); + +/*****************************************************************************/ + +struct _NMUtilsShareRules; + +struct _NMUtilsShareRules *nm_act_request_get_shared(NMActRequest *req); + +void nm_act_request_set_shared(NMActRequest *req, struct _NMUtilsShareRules *rules); + +/*****************************************************************************/ + +/* Secrets handling */ + +typedef void (*NMActRequestSecretsFunc)(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data); + +NMActRequestGetSecretsCallId *nm_act_request_get_secrets(NMActRequest * req, + gboolean take_ref, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMActRequestSecretsFunc callback, + gpointer callback_data); + +void nm_act_request_cancel_secrets(NMActRequest *req, NMActRequestGetSecretsCallId *call_id); +void nm_act_request_clear_secrets(NMActRequest *self); + +#endif /* __NM_ACT_REQUEST_H__ */ diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c new file mode 100644 index 0000000..9049eea --- /dev/null +++ b/src/core/nm-active-connection.c @@ -0,0 +1,1848 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-active-connection.h" + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-dbus-interface.h" +#include "devices/nm-device.h" +#include "settings/nm-settings-connection.h" +#include "nm-simple-connection.h" +#include "nm-auth-utils.h" +#include "nm-auth-manager.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-keep-alive.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" + +#define AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED ((NMAuthManagerCallId *) GINT_TO_POINTER(1)) + +typedef struct _NMActiveConnectionPrivate { + NMDBusTrackObjPath settings_connection; + NMConnection * applied_connection; + char * specific_object; + NMDevice * device; + + guint64 version_id; + + char *pending_activation_id; + + NMActivationStateFlags state_flags; + + NMActiveConnectionState state; + bool is_default : 1; + bool is_default6 : 1; + bool state_set : 1; + bool vpn : 1; + bool master_ready : 1; + + NMActivationType activation_type : 3; + + /* capture the original reason why the connection was activated. + * For example with NM_ACTIVATION_REASON_ASSUME, the connection + * will later change to become fully managed. But the original + * reason never changes. */ + NMActivationReason activation_reason : 4; + + NMAuthSubject * subject; + NMActiveConnection *master; + + NMActiveConnection *parent; + + struct { + NMAuthManagerCallId *call_id_network_control; + NMAuthManagerCallId *call_id_wifi_shared_permission; + + NMActiveConnectionAuthResultFunc result_func; + gpointer user_data; + } auth; + + NMKeepAlive *keep_alive; +} NMActiveConnectionPrivate; + +NM_GOBJECT_PROPERTIES_DEFINE(NMActiveConnection, + PROP_CONNECTION, + PROP_ID, + PROP_UUID, + PROP_TYPE, + PROP_SPECIFIC_OBJECT, + PROP_DEVICES, + PROP_STATE, + PROP_STATE_FLAGS, + PROP_DEFAULT, + PROP_IP4_CONFIG, + PROP_DHCP4_CONFIG, + PROP_DEFAULT6, + PROP_IP6_CONFIG, + PROP_DHCP6_CONFIG, + PROP_VPN, + PROP_MASTER, + + PROP_INT_SETTINGS_CONNECTION, + PROP_INT_APPLIED_CONNECTION, + PROP_INT_DEVICE, + PROP_INT_SUBJECT, + PROP_INT_MASTER, + PROP_INT_MASTER_READY, + PROP_INT_ACTIVATION_TYPE, + PROP_INT_ACTIVATION_REASON, ); + +enum { DEVICE_CHANGED, DEVICE_METERED_CHANGED, PARENT_ACTIVE, STATE_CHANGED, LAST_SIGNAL }; +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_ABSTRACT_TYPE(NMActiveConnection, nm_active_connection, NM_TYPE_DBUS_OBJECT) + +#define NM_ACTIVE_CONNECTION_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMActiveConnection, NM_IS_ACTIVE_CONNECTION) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_active_connection; +static const GDBusSignalInfo signal_info_state_changed; + +static void check_master_ready(NMActiveConnection *self); +static void _device_cleanup(NMActiveConnection *self); +static void _settings_connection_flags_changed(NMSettingsConnection *settings_connection, + NMActiveConnection * self); +static void _set_activation_type_managed(NMActiveConnection *self); + +static void auth_complete(NMActiveConnection *self, gboolean result, const char *message); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_DEVICE +#define _NMLOG_PREFIX_NAME "active-connection" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _sbuf[64]; \ + NMActiveConnectionPrivate *_priv = self ? NM_ACTIVE_CONNECTION_GET_PRIVATE(self) : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + (_priv && _priv->device) ? nm_device_get_iface(_priv->device) : NULL, \ + (_priv && _priv->applied_connection) \ + ? nm_connection_get_uuid(_priv->applied_connection) \ + : NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + self ? nm_sprintf_buf(_sbuf, "[%p]", self) : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _state_to_string, + NMActiveConnectionState, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVE_CONNECTION_STATE_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATING, "activating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATED, "activated"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, "deactivating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, "deactivated"), ); + +#define state_to_string_a(state) NM_UTILS_LOOKUP_STR_A(_state_to_string, state) + +/* the maximum required buffer size for _state_flags_to_string(). */ +#define _NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE (255) + +static NM_UTILS_FLAGS2STR_DEFINE( + _state_flags_to_string, + NMActivationStateFlags, + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_IS_MASTER, "is-master"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_IS_SLAVE, "is-slave"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_LAYER2_READY, "layer2-ready"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_IP4_READY, "ip4-ready"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_IP6_READY, "ip6-ready"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES, "master-has-slaves"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + "lifetime-bound-to-profile-visibility"), + NM_UTILS_FLAGS2STR(NM_ACTIVATION_STATE_FLAG_EXTERNAL, "external"), ); + +/*****************************************************************************/ + +static void +_settings_connection_updated(NMSettingsConnection *sett_conn, + guint update_reason_u, + gpointer user_data) +{ + NMActiveConnection *self = user_data; + + /* we don't know which properties actually changed. Just to be sure, + * notify about all possible properties. After all, an update of a + * connection is a rare event. */ + + _notify(self, PROP_ID); + + /* it's a bit odd to update the TYPE of an active connection. But the alternative + * is unexpected too. */ + _notify(self, PROP_TYPE); + + /* currently, the UUID and the exported CONNECTION path cannot change. Later, we might + * want to support a re-link operation, which associates an active-connection with a different + * settings-connection. */ +} + +static void +_set_settings_connection(NMActiveConnection *self, NMSettingsConnection *sett_conn) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + if (priv->settings_connection.obj == sett_conn) + return; + + if (priv->settings_connection.obj) { + g_signal_handlers_disconnect_by_func(priv->settings_connection.obj, + _settings_connection_updated, + self); + g_signal_handlers_disconnect_by_func(priv->settings_connection.obj, + _settings_connection_flags_changed, + self); + } + if (sett_conn) { + g_signal_connect(sett_conn, + NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, + (GCallback) _settings_connection_updated, + self); + if (nm_active_connection_get_activation_type(self) == NM_ACTIVATION_TYPE_EXTERNAL) + g_signal_connect(sett_conn, + NM_SETTINGS_CONNECTION_FLAGS_CHANGED, + (GCallback) _settings_connection_flags_changed, + self); + } + + nm_dbus_track_obj_path_set(&priv->settings_connection, sett_conn, TRUE); +} + +NMActiveConnectionState +nm_active_connection_get_state(NMActiveConnection *self) +{ + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->state; +} + +static void +emit_state_changed(NMActiveConnection *self, guint state, guint reason) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_active_connection, + &signal_info_state_changed, + "(uu)", + (guint32) state, + (guint32) reason); + g_signal_emit(self, signals[STATE_CHANGED], 0, state, reason); +} + +void +nm_active_connection_set_state(NMActiveConnection * self, + NMActiveConnectionState new_state, + NMActiveConnectionStateReason reason) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMActiveConnectionState old_state; + + /* DEACTIVATED is a terminal state */ + g_return_if_fail(priv->state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED + || new_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED); + + if (priv->state == new_state) + return; + + _LOGD("set state %s (was %s)", state_to_string_a(new_state), state_to_string_a(priv->state)); + + if (new_state > NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + /* once we are about to deactivate, we don't need the keep-alive instance + * anymore. Freeze/disarm it. */ + nm_keep_alive_disarm(priv->keep_alive); + } + + if (new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED + && priv->activation_type == NM_ACTIVATION_TYPE_ASSUME) { + /* assuming connections mean to gracefully take over an externally + * configured device. Once activation is complete, an assumed + * activation *is* the same as a full activation. */ + _set_activation_type_managed(self); + } + + old_state = priv->state; + priv->state = new_state; + priv->state_set = TRUE; + emit_state_changed(self, new_state, reason); + _notify(self, PROP_STATE); + + check_master_ready(self); + + if (new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED + || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + nm_settings_connection_update_timestamp(priv->settings_connection.obj, + (guint64) time(NULL)); + } + + if (priv->device) { + if (old_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED + && new_state >= NM_ACTIVE_CONNECTION_STATE_ACTIVATED && priv->pending_activation_id) { + nm_device_remove_pending_action(priv->device, priv->pending_activation_id, TRUE); + nm_clear_g_free(&priv->pending_activation_id); + } + } + + if (new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED + || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + _notify(self, PROP_IP4_CONFIG); + _notify(self, PROP_DHCP4_CONFIG); + _notify(self, PROP_IP6_CONFIG); + _notify(self, PROP_DHCP6_CONFIG); + } + + if (priv->state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + _nm_unused gs_unref_object NMActiveConnection *self_keep_alive = g_object_ref(self); + + auth_complete(self, FALSE, "Authorization request cancelled"); + + /* Device is no longer relevant when deactivated. So remove it and + * emit property change notification so clients re-read the value, + * which will be NULL due to conditions in get_property(). + */ + _device_cleanup(self); + _notify(self, PROP_DEVICES); + } +} + +void +nm_active_connection_set_state_fail(NMActiveConnection * self, + NMActiveConnectionStateReason reason, + const char * error_desc) +{ + NMActiveConnectionState s; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + + if (error_desc) { + _LOGD("Failed to activate '%s': %s", + nm_active_connection_get_settings_connection_id(self), + error_desc); + } + + s = nm_active_connection_get_state(self); + if (s >= NM_ACTIVE_CONNECTION_STATE_ACTIVATING && s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + nm_active_connection_set_state(self, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, reason); + s = nm_active_connection_get_state(self); + } + if (s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + nm_active_connection_set_state(self, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, reason); + } +} + +NMActivationStateFlags +nm_active_connection_get_state_flags(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + return priv->state_flags + | (priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL + ? NM_ACTIVATION_STATE_FLAG_EXTERNAL + : NM_ACTIVATION_STATE_FLAG_NONE); +} + +void +nm_active_connection_set_state_flags_full(NMActiveConnection * self, + NMActivationStateFlags state_flags, + NMActivationStateFlags mask) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMActivationStateFlags f; + + nm_assert(!NM_FLAGS_HAS(mask, NM_ACTIVATION_STATE_FLAG_EXTERNAL)); + + f = (priv->state_flags & ~mask) | (state_flags & mask); + if (f != priv->state_flags) { + char buf1[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE]; + char buf2[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE]; + + _LOGD("set state-flags %s (was %s)", + _state_flags_to_string(f, buf1, sizeof(buf1)), + _state_flags_to_string(priv->state_flags, buf2, sizeof(buf2))); + priv->state_flags = f; + _notify(self, PROP_STATE_FLAGS); + + nm_keep_alive_set_settings_connection_watch_visible( + priv->keep_alive, + NM_FLAGS_HAS(priv->state_flags, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY) + ? priv->settings_connection.obj + : NULL); + } +} + +const char * +nm_active_connection_get_settings_connection_id(NMActiveConnection *self) +{ + NMSettingsConnection *sett_conn; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + sett_conn = NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->settings_connection.obj; + return sett_conn ? nm_settings_connection_get_id(sett_conn) : NULL; +} + +NMSettingsConnection * +_nm_active_connection_get_settings_connection(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->settings_connection.obj; +} + +NMSettingsConnection * +nm_active_connection_get_settings_connection(NMActiveConnection *self) +{ + NMSettingsConnection *sett_conn; + + sett_conn = _nm_active_connection_get_settings_connection(self); + + /* Only call this function on an active-connection that is already + * fully set-up (i.e. that has a settings-connection). Other uses + * indicate a bug. */ + g_return_val_if_fail(sett_conn, NULL); + return sett_conn; +} + +NMConnection * +nm_active_connection_get_applied_connection(NMActiveConnection *self) +{ + NMConnection *connection; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + connection = NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->applied_connection; + + /* Only call this function on an active-connection that is already + * fully set-up (i.e. that has a settings-connection). Other uses + * indicate a bug. */ + g_return_val_if_fail(connection, NULL); + return connection; +} + +static void +_set_applied_connection_take(NMActiveConnection *self, NMConnection *applied_connection) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMSettingConnection * s_con; + NMActivationStateFlags flags_val = 0; + + nm_assert(NM_IS_CONNECTION(applied_connection)); + nm_assert(!priv->applied_connection); + + /* we take ownership of @applied_connection. Ensure to pass in a reference. */ + priv->applied_connection = applied_connection; + nm_connection_clear_secrets(priv->applied_connection); + + /* we determine whether the connection is a master/slave, based solely + * on the connection properties itself. */ + s_con = nm_connection_get_setting_connection(priv->applied_connection); + if (nm_setting_connection_get_master(s_con)) + flags_val |= NM_ACTIVATION_STATE_FLAG_IS_SLAVE; + + if (_nm_connection_type_is_master(nm_setting_connection_get_connection_type(s_con))) + flags_val |= NM_ACTIVATION_STATE_FLAG_IS_MASTER; + + nm_active_connection_set_state_flags_full(self, + flags_val, + NM_ACTIVATION_STATE_FLAG_IS_MASTER + | NM_ACTIVATION_STATE_FLAG_IS_SLAVE); +} + +void +nm_active_connection_set_settings_connection(NMActiveConnection * self, + NMSettingsConnection *sett_conn) +{ + NMActiveConnectionPrivate *priv; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn)); + g_return_if_fail(!priv->settings_connection.obj); + g_return_if_fail(!priv->applied_connection); + + /* Can't change connection after the ActiveConnection is exported over D-Bus. + * + * Later, we want to change the settings-connection of an activated connection. + * When doing that, this changes the assumption that the settings-connection + * never changes (once it's set). That has effects for NMVpnConnection and + * NMActivationRequest. + * For example, we'd have to cancel all pending seret requests. */ + g_return_if_fail(!nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))); + + _set_settings_connection(self, sett_conn); + + _set_applied_connection_take( + self, + nm_simple_connection_new_clone( + nm_settings_connection_get_connection(priv->settings_connection.obj))); +} + +gboolean +nm_active_connection_has_unmodified_applied_connection(NMActiveConnection * self, + NMSettingCompareFlags compare_flags) +{ + NMActiveConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), FALSE); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + g_return_val_if_fail(priv->settings_connection.obj, FALSE); + + return nm_settings_connection_has_unmodified_applied_connection(priv->settings_connection.obj, + priv->applied_connection, + compare_flags); +} + +/*****************************************************************************/ + +void +nm_active_connection_clear_secrets(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + if (nm_settings_connection_has_unmodified_applied_connection(priv->settings_connection.obj, + priv->applied_connection, + NM_SETTING_COMPARE_FLAG_NONE)) { + nm_settings_connection_clear_secrets(priv->settings_connection.obj, FALSE, FALSE); + } + nm_connection_clear_secrets(priv->applied_connection); +} + +/*****************************************************************************/ + +const char * +nm_active_connection_get_specific_object(NMActiveConnection *self) +{ + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->specific_object; +} + +void +nm_active_connection_set_specific_object(NMActiveConnection *self, const char *specific_object) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + /* Nothing that calls this function should be using paths from D-Bus, + * where NM uses "/" to mean NULL. + */ + nm_assert(!nm_streq0(specific_object, "/")); + + if (nm_streq0(priv->specific_object, specific_object)) + return; + + g_free(priv->specific_object); + priv->specific_object = g_strdup(specific_object); + _notify(self, PROP_SPECIFIC_OBJECT); +} + +void +nm_active_connection_set_default(NMActiveConnection *self, int addr_family, gboolean is_default) +{ + NMActiveConnectionPrivate *priv; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + is_default = !!is_default; + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + if (priv->is_default != is_default) { + priv->is_default = is_default; + _notify(self, PROP_DEFAULT); + } + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + if (priv->is_default6 != is_default) { + priv->is_default6 = is_default; + _notify(self, PROP_DEFAULT6); + } + } +} + +gboolean +nm_active_connection_get_default(NMActiveConnection *self, int addr_family) +{ + NMActiveConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), FALSE); + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + switch (addr_family) { + case AF_INET: + return priv->is_default; + case AF_INET6: + return priv->is_default6; + default: + return priv->is_default || priv->is_default6; + } +} + +NMAuthSubject * +nm_active_connection_get_subject(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->subject; +} + +gboolean +nm_active_connection_get_user_requested(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), FALSE); + + return nm_auth_subject_get_subject_type(NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->subject) + == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; +} + +NMDevice * +nm_active_connection_get_device(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->device; +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(user_data); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + /* When already deactivated or before activation, device state changes are useless */ + if (priv->state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + if (old_state < NM_DEVICE_STATE_DISCONNECTED) + return; + + /* Let subclasses handle the state change */ + if (NM_ACTIVE_CONNECTION_GET_CLASS(self)->device_state_changed) + NM_ACTIVE_CONNECTION_GET_CLASS(self)->device_state_changed(self, + device, + new_state, + old_state); +} + +static void +device_master_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(object); + NMActiveConnection * self = NM_ACTIVE_CONNECTION(user_data); + NMActiveConnection * master; + NMActiveConnectionState master_state; + + if (NM_ACTIVE_CONNECTION(nm_device_get_act_request(device)) != self) + return; + if (!nm_device_get_master(device)) + return; + if (!nm_active_connection_get_master(self)) + return; + g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_master_changed), self); + + master = nm_active_connection_get_master(self); + master_state = nm_active_connection_get_state(master); + if (master_state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + /* Master failed before attaching the slave */ + if (NM_ACTIVE_CONNECTION_GET_CLASS(self)->master_failed) + NM_ACTIVE_CONNECTION_GET_CLASS(self)->master_failed(self); + } +} + +static void +device_metered_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMActiveConnection *self = (NMActiveConnection *) user_data; + NMDevice * device = NM_DEVICE(object); + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + g_signal_emit(self, signals[DEVICE_METERED_CHANGED], 0, nm_device_get_metered(device)); +} + +gboolean +nm_active_connection_set_device(NMActiveConnection *self, NMDevice *device) +{ + NMActiveConnectionPrivate *priv; + gs_unref_object NMDevice *old_device = NULL; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), FALSE); + g_return_val_if_fail(!device || NM_IS_DEVICE(device), FALSE); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + if (device == priv->device) + return TRUE; + + _LOGD("set device %s%s%s [%p]", + NM_PRINT_FMT_QUOTED(device && nm_device_get_iface(device), + "\"", + nm_device_get_iface(device), + "\"", + device ? "(unknown)" : "(null)"), + device); + + old_device = priv->device ? g_object_ref(priv->device) : NULL; + _device_cleanup(self); + + if (device) { + /* Device obviously can't be its own master */ + g_return_val_if_fail(!priv->master + || device != nm_active_connection_get_device(priv->master), + FALSE); + + priv->device = g_object_ref(device); + + g_signal_connect(device, NM_DEVICE_STATE_CHANGED, G_CALLBACK(device_state_changed), self); + g_signal_connect(device, + "notify::" NM_DEVICE_MASTER, + G_CALLBACK(device_master_changed), + self); + g_signal_connect(device, + "notify::" NM_DEVICE_METERED, + G_CALLBACK(device_metered_changed), + self); + + if (priv->activation_type != NM_ACTIVATION_TYPE_EXTERNAL) { + priv->pending_activation_id = + g_strdup_printf(NM_PENDING_ACTIONPREFIX_ACTIVATION "%" G_GUINT64_FORMAT, + priv->version_id); + nm_device_add_pending_action(device, priv->pending_activation_id, TRUE); + } + } else { + /* The ActiveConnection's device can only be cleared after the + * connection is activated. + */ + g_warn_if_fail(priv->state > NM_ACTIVE_CONNECTION_STATE_UNKNOWN); + priv->device = NULL; + } + _notify(self, PROP_INT_DEVICE); + + g_signal_emit(self, signals[DEVICE_CHANGED], 0, priv->device, old_device); + + _notify(self, PROP_DEVICES); + + return TRUE; +} + +NMActiveConnection * +nm_active_connection_get_master(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->master; +} + +/** + * nm_active_connection_get_master_ready: + * @self: the #NMActiveConnection + * + * Returns: %TRUE if the connection has a master connection, and that + * master connection is ready to accept slaves. Otherwise, %FALSE. + */ +gboolean +nm_active_connection_get_master_ready(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), FALSE); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->master_ready; +} + +static void +check_master_ready(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + gboolean signalling = FALSE; + + /* ActiveConnetions don't enter the ACTIVATING state until they have a + * NMDevice in PREPARE or higher states, so the master active connection's + * device will be ready to accept slaves when the master is in ACTIVATING + * or higher states. + */ + if (!priv->master_ready && priv->master && priv->state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING + && NM_IN_SET(nm_active_connection_get_state(priv->master), + NM_ACTIVE_CONNECTION_STATE_ACTIVATING, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) { + signalling = TRUE; + } + + _LOGD("check-master-ready: %s (state %s, %s)", + signalling ? "signal" : (priv->master_ready ? "already signalled" : "not signalling"), + state_to_string_a(priv->state), + priv->master + ? nm_sprintf_bufa(128, + "master %p is in state %s", + priv->master, + state_to_string_a(nm_active_connection_get_state(priv->master))) + : "no master"); + + if (signalling) { + priv->master_ready = TRUE; + _notify(self, PROP_INT_MASTER_READY); + + /* Also notify clients to recheck the exported 'master' property to + * ensure that if the master connection was created without a device + * that we notify clients when the master device is known. + */ + _notify(self, PROP_MASTER); + } +} + +static void +master_state_cb(NMActiveConnection *master, GParamSpec *pspec, gpointer user_data) +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(user_data); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMActiveConnectionState master_state = nm_active_connection_get_state(master); + + check_master_ready(self); + + if (master_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATING && !priv->master_ready) { + /* Master disconnected before the slave was added */ + if (NM_ACTIVE_CONNECTION_GET_CLASS(self)->master_failed) + NM_ACTIVE_CONNECTION_GET_CLASS(self)->master_failed(self); + } +} + +/** + * nm_active_connection_set_master: + * @self: the #NMActiveConnection + * @master: if the activation depends on another device (ie, bond or bridge + * master to which this device will be enslaved) pass the #NMActiveConnection + * that this activation request is a child of + * + * Sets the master active connection of @self. + */ +void +nm_active_connection_set_master(NMActiveConnection *self, NMActiveConnection *master) +{ + NMActiveConnectionPrivate *priv; + + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(self)); + g_return_if_fail(NM_IS_ACTIVE_CONNECTION(master)); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + /* Master is write-once, and must be set before exporting the object */ + g_return_if_fail(priv->master == NULL); + g_return_if_fail(!nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))); + if (priv->device) { + /* Note, the master ActiveConnection may not yet have a device */ + g_return_if_fail(priv->device != nm_active_connection_get_device(master)); + } + + _LOGD("set master %p, %s, state %s", + master, + nm_active_connection_get_settings_connection_id(master), + state_to_string_a(nm_active_connection_get_state(master))); + + priv->master = g_object_ref(master); + g_signal_connect(priv->master, + "notify::" NM_ACTIVE_CONNECTION_STATE, + (GCallback) master_state_cb, + self); + + check_master_ready(self); +} + +NMActivationType +nm_active_connection_get_activation_type(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NM_ACTIVATION_TYPE_MANAGED); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->activation_type; +} + +static void +_set_activation_type(NMActiveConnection *self, NMActivationType activation_type) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + gboolean state_flags_changed; + + if (priv->activation_type == activation_type) + return; + + state_flags_changed = (priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL) + != (activation_type == NM_ACTIVATION_TYPE_EXTERNAL); + + priv->activation_type = activation_type; + + if (priv->settings_connection.obj) { + if (activation_type == NM_ACTIVATION_TYPE_EXTERNAL) + g_signal_connect(priv->settings_connection.obj, + NM_SETTINGS_CONNECTION_FLAGS_CHANGED, + (GCallback) _settings_connection_flags_changed, + self); + else + g_signal_handlers_disconnect_by_func(priv->settings_connection.obj, + _settings_connection_flags_changed, + self); + } + + if (state_flags_changed) + _notify(self, PROP_STATE_FLAGS); +} + +static void +_set_activation_type_managed(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + if (priv->activation_type == NM_ACTIVATION_TYPE_MANAGED) + return; + + _LOGD("update activation type from %s to %s", + nm_activation_type_to_string(priv->activation_type), + nm_activation_type_to_string(NM_ACTIVATION_TYPE_MANAGED)); + + _set_activation_type(self, NM_ACTIVATION_TYPE_MANAGED); + + if (priv->device && self == NM_ACTIVE_CONNECTION(nm_device_get_act_request(priv->device)) + && NM_IN_SET(nm_device_sys_iface_state_get(priv->device), + NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, + NM_DEVICE_SYS_IFACE_STATE_ASSUME)) + nm_device_sys_iface_state_set(priv->device, NM_DEVICE_SYS_IFACE_STATE_MANAGED); +} + +NMActivationReason +nm_active_connection_get_activation_reason(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NM_ACTIVATION_REASON_UNSET); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->activation_reason; +} + +/*****************************************************************************/ + +/** + * nm_active_connection_get_keep_alive: + * @self: the #NMActiveConnection instance + * + * Gives the #NMKeepAlive instance of the active connection. Note that + * @self is guaranteed not to swap the keep-alive instance, so it is + * in particular safe to assume that the keep-alive instance is alive + * as long as @self, and that nm_active_connection_get_keep_alive() + * will return always the same instance. + * + * In particular this means, that it is safe and encouraged, that you + * register to the notify:alive property changed signal of the returned + * instance. + * + * Returns: the #NMKeepAlive instance. + */ +NMKeepAlive * +nm_active_connection_get_keep_alive(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), NULL); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->keep_alive; +} + +/*****************************************************************************/ + +static void +_settings_connection_flags_changed(NMSettingsConnection *settings_connection, + NMActiveConnection * self) +{ + NMDevice *device; + + nm_assert(NM_IS_ACTIVE_CONNECTION(self)); + nm_assert(NM_IS_SETTINGS_CONNECTION(settings_connection)); + nm_assert(nm_active_connection_get_activation_type(self) == NM_ACTIVATION_TYPE_EXTERNAL); + nm_assert(NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->settings_connection.obj + == settings_connection); + + if (NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + + _set_activation_type_managed(self); + + device = nm_active_connection_get_device(self); + if (device) { + gs_free_error GError *error = NULL; + + if (!nm_device_reapply(device, + nm_settings_connection_get_connection( + nm_active_connection_get_settings_connection(self)), + &error)) { + _LOGW( + "failed to reapply new device settings on previously externally managed device: %s", + error->message); + } + } +} + +/*****************************************************************************/ + +static void unwatch_parent(NMActiveConnection *self, gboolean unref); + +static void +parent_destroyed(gpointer user_data, GObject *parent) +{ + NMActiveConnection *self = user_data; + + unwatch_parent(self, FALSE); + g_signal_emit(self, signals[PARENT_ACTIVE], 0, NULL); +} + +static void +parent_state_cb(NMActiveConnection *parent_ac, GParamSpec *pspec, gpointer user_data) +{ + NMActiveConnection * self = user_data; + NMActiveConnectionState parent_state = nm_active_connection_get_state(parent_ac); + + if (parent_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + return; + + unwatch_parent(self, TRUE); + + if (parent_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + g_signal_emit(self, signals[PARENT_ACTIVE], 0, parent_ac); +} + +static void +unwatch_parent(NMActiveConnection *self, gboolean unref) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + g_signal_handlers_disconnect_by_func(priv->parent, (GCallback) parent_state_cb, self); + if (unref) + g_object_weak_unref((GObject *) priv->parent, parent_destroyed, self); + priv->parent = NULL; +} + +/** + * nm_active_connection_set_parent: + * @self: the #NMActiveConnection + * @parent: The #NMActiveConnection that must be active before the manager + * can proceed progressing the device to disconnected state for us. + * + * Sets the parent connection of @self. A "parent-active" signal will be + * emitted when the parent connection becomes active. + */ +void +nm_active_connection_set_parent(NMActiveConnection *self, NMActiveConnection *parent) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + g_return_if_fail(priv->parent == NULL); + priv->parent = parent; + g_signal_connect(priv->parent, + "notify::" NM_ACTIVE_CONNECTION_STATE, + (GCallback) parent_state_cb, + self); + g_object_weak_ref((GObject *) priv->parent, parent_destroyed, self); +} + +/*****************************************************************************/ + +static void +auth_complete(NMActiveConnection *self, gboolean result, const char *message) +{ + NMActiveConnectionPrivate * priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMActiveConnectionAuthResultFunc result_func; + gpointer user_data; + + if (priv->auth.call_id_network_control) + nm_auth_manager_check_authorization_cancel(priv->auth.call_id_network_control); + if (priv->auth.call_id_wifi_shared_permission) { + if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED) + priv->auth.call_id_wifi_shared_permission = NULL; + else + nm_auth_manager_check_authorization_cancel(priv->auth.call_id_wifi_shared_permission); + } + + nm_assert(!priv->auth.call_id_network_control); + nm_assert(!priv->auth.call_id_wifi_shared_permission); + if (priv->auth.result_func) { + result_func = priv->auth.result_func; + priv->auth.result_func = NULL; + user_data = g_steal_pointer(&priv->auth.user_data); + + result_func(self, result, message, user_data); + } +} + +static void +auth_complete_keep_alive(NMActiveConnection *self, gboolean result, const char *message) +{ + _nm_unused gs_unref_object NMActiveConnection *self_keep_alive = g_object_ref(self); + + auth_complete(self, result, message); +} + +static void +auth_done(NMAuthManager * auth_mgr, + NMAuthManagerCallId *auth_call_id, + gboolean is_authorized, + gboolean is_challenge, + GError * error, + gpointer user_data) + +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(user_data); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + NMAuthCallResult result; + + nm_assert(auth_call_id); + nm_assert(priv->auth.result_func); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + if (auth_call_id == priv->auth.call_id_network_control) + priv->auth.call_id_network_control = NULL; + else { + nm_assert(auth_call_id == priv->auth.call_id_wifi_shared_permission); + priv->auth.call_id_wifi_shared_permission = NULL; + } + return; + } + + result = nm_auth_call_result_eval(is_authorized, is_challenge, error); + + if (auth_call_id == priv->auth.call_id_network_control) { + priv->auth.call_id_network_control = NULL; + if (result != NM_AUTH_CALL_RESULT_YES) { + auth_complete_keep_alive(self, FALSE, "Not authorized to control networking."); + return; + } + } else { + nm_assert(auth_call_id == priv->auth.call_id_wifi_shared_permission); + if (result != NM_AUTH_CALL_RESULT_YES) { + /* we don't fail right away. Instead, we mark that wifi-shared-permissions + * are missing. We prefer to report the failure about network-control. + * Below, we will wait longer for call_id_network_control (if it's still + * pending). */ + priv->auth.call_id_wifi_shared_permission = AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED; + } else + priv->auth.call_id_wifi_shared_permission = NULL; + } + + if (priv->auth.call_id_network_control) + return; + + if (priv->auth.call_id_wifi_shared_permission) { + if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED) + auth_complete_keep_alive(self, FALSE, "Not authorized to share connections via wifi."); + return; + } + + auth_complete_keep_alive(self, TRUE, NULL); +} + +/** + * nm_active_connection_authorize: + * @self: the #NMActiveConnection + * @initial_connection: (allow-none): for add-and-activate, there + * is no @settings_connection available when creating the active connection. + * Instead pass an alternative connection. + * @result_func: function to be called on success or error + * @user_data: pointer passed to @result_func + * + * Checks whether the subject that initiated the active connection (read from + * the #NMActiveConnection::subject property) is authorized to complete this + * activation request. + */ +void +nm_active_connection_authorize(NMActiveConnection * self, + NMConnection * initial_connection, + NMActiveConnectionAuthResultFunc result_func, + gpointer user_data) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + const char * wifi_permission = NULL; + NMConnection * connection; + + g_return_if_fail(result_func); + g_return_if_fail(!priv->auth.call_id_network_control); + nm_assert(!priv->auth.call_id_wifi_shared_permission); + + if (initial_connection) { + g_return_if_fail(NM_IS_CONNECTION(initial_connection)); + g_return_if_fail(!priv->settings_connection.obj); + g_return_if_fail(!priv->applied_connection); + connection = initial_connection; + } else { + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(priv->settings_connection.obj)); + g_return_if_fail(NM_IS_CONNECTION(priv->applied_connection)); + connection = priv->applied_connection; + } + + priv->auth.call_id_network_control = + nm_auth_manager_check_authorization(nm_auth_manager_get(), + priv->subject, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + auth_done, + self); + + /* Shared wifi connections require special permissions too */ + wifi_permission = nm_utils_get_shared_wifi_permission(connection); + if (wifi_permission) { + priv->auth.call_id_wifi_shared_permission = + nm_auth_manager_check_authorization(nm_auth_manager_get(), + priv->subject, + wifi_permission, + TRUE, + auth_done, + self); + } + + priv->auth.result_func = result_func; + priv->auth.user_data = user_data; +} + +/*****************************************************************************/ + +static guint64 +_version_id_new(void) +{ + static guint64 id = 0; + + return ++id; +} + +guint64 +nm_active_connection_version_id_get(NMActiveConnection *self) +{ + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), 0); + + return NM_ACTIVE_CONNECTION_GET_PRIVATE(self)->version_id; +} + +guint64 +nm_active_connection_version_id_bump(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(self), 0); + + priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + priv->version_id = _version_id_new(); + _LOGT("new version-id %llu", (unsigned long long) priv->version_id); + return priv->version_id; +} + +/*****************************************************************************/ + +static void +_device_cleanup(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + if (priv->device) { + g_signal_handlers_disconnect_by_func(priv->device, G_CALLBACK(device_state_changed), self); + g_signal_handlers_disconnect_by_func(priv->device, G_CALLBACK(device_master_changed), self); + g_signal_handlers_disconnect_by_func(priv->device, + G_CALLBACK(device_metered_changed), + self); + } + + if (priv->pending_activation_id) { + nm_device_remove_pending_action(priv->device, priv->pending_activation_id, TRUE); + nm_clear_g_free(&priv->pending_activation_id); + } + + g_clear_object(&priv->device); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(object); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + char ** strv; + NMDevice * master_device = NULL; + + switch (prop_id) { + /* note that while priv->settings_connection.obj might not be set initially, + * it will be set before the object is exported on D-Bus. Hence, + * nobody is calling these property getters before the object is + * exported, at which point we will have a valid settings-connection. + * + * Therefore, intentionally not check whether priv->settings_connection.obj + * is set, to get an assertion failure if somebody tries to access the + * getters at the wrong time. */ + case PROP_CONNECTION: + g_value_set_string(value, nm_dbus_track_obj_path_get(&priv->settings_connection)); + break; + case PROP_ID: + g_value_set_string(value, nm_settings_connection_get_id(priv->settings_connection.obj)); + break; + case PROP_UUID: + g_value_set_string(value, nm_settings_connection_get_uuid(priv->settings_connection.obj)); + break; + case PROP_TYPE: + g_value_set_string( + value, + nm_settings_connection_get_connection_type(priv->settings_connection.obj)); + break; + + case PROP_SPECIFIC_OBJECT: + g_value_set_string(value, priv->specific_object); + break; + case PROP_DEVICES: + strv = g_new0(char *, 2); + if (priv->device && priv->state < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + strv[0] = g_strdup(nm_dbus_object_get_path(NM_DBUS_OBJECT(priv->device))); + g_value_take_boxed(value, strv); + break; + case PROP_STATE: + if (priv->state_set) + g_value_set_uint(value, priv->state); + else { + /* When the AC has just been created, its externally-visible state should + * be "ACTIVATING", even though internally it is "UNKNOWN". + */ + g_value_set_uint(value, NM_ACTIVE_CONNECTION_STATE_ACTIVATING); + } + break; + case PROP_STATE_FLAGS: + g_value_set_uint(value, nm_active_connection_get_state_flags(self)); + break; + case PROP_DEFAULT: + g_value_set_boolean(value, priv->is_default); + break; + case PROP_IP4_CONFIG: + /* The IP and DHCP config properties may be overridden by a subclass */ + g_value_set_string(value, NULL); + break; + case PROP_DHCP4_CONFIG: + g_value_set_string(value, NULL); + break; + case PROP_DEFAULT6: + g_value_set_boolean(value, priv->is_default6); + break; + case PROP_IP6_CONFIG: + g_value_set_string(value, NULL); + break; + case PROP_DHCP6_CONFIG: + g_value_set_string(value, NULL); + break; + case PROP_VPN: + g_value_set_boolean(value, priv->vpn); + break; + case PROP_MASTER: + if (priv->master) + master_device = nm_active_connection_get_device(priv->master); + nm_dbus_utils_g_value_set_object_path(value, master_device); + break; + case PROP_INT_SUBJECT: + g_value_set_object(value, priv->subject); + break; + case PROP_INT_MASTER_READY: + g_value_set_boolean(value, priv->master_ready); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMActiveConnection * self = (NMActiveConnection *) object; + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + const char * tmp; + NMSettingsConnection * sett_conn; + NMConnection * acon; + int i; + + switch (prop_id) { + case PROP_INT_SETTINGS_CONNECTION: + /* construct-only */ + sett_conn = g_value_get_object(value); + if (sett_conn) + _set_settings_connection(self, sett_conn); + break; + case PROP_INT_APPLIED_CONNECTION: + /* construct-only */ + acon = g_value_get_object(value); + if (acon) { + /* we don't call _set_applied_connection_take() yet, because the instance + * is not yet fully initialized. We are currently in the process of setting + * the constructor properties. + * + * For now, just piggyback the connection, but call _set_applied_connection_take() + * in constructed(). */ + priv->applied_connection = g_object_ref(acon); + } + break; + case PROP_INT_DEVICE: + /* construct-only */ + nm_active_connection_set_device(self, g_value_get_object(value)); + break; + case PROP_INT_SUBJECT: + /* construct-only */ + priv->subject = g_value_dup_object(value); + break; + case PROP_INT_MASTER: + nm_active_connection_set_master(self, g_value_get_object(value)); + break; + case PROP_INT_ACTIVATION_TYPE: + /* construct-only */ + i = g_value_get_int(value); + if (!NM_IN_SET(i, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_TYPE_ASSUME, + NM_ACTIVATION_TYPE_EXTERNAL)) + g_return_if_reached(); + _set_activation_type(self, (NMActivationType) i); + break; + case PROP_STATE_FLAGS: + /* construct-only */ + priv->state_flags = g_value_get_uint(value); + nm_assert((guint) priv->state_flags == g_value_get_uint(value)); + nm_assert(!NM_FLAGS_ANY(priv->state_flags, + ~NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY)); + break; + case PROP_INT_ACTIVATION_REASON: + /* construct-only */ + i = g_value_get_int(value); + priv->activation_reason = i; + nm_assert(priv->activation_reason == ((NMActivationReason) i)); + break; + case PROP_SPECIFIC_OBJECT: + /* construct-only */ + tmp = g_value_get_string(value); + tmp = nm_dbus_path_not_empty(tmp); + priv->specific_object = g_strdup(tmp); + break; + case PROP_DEFAULT: + priv->is_default = g_value_get_boolean(value); + break; + case PROP_DEFAULT6: + priv->is_default6 = g_value_get_boolean(value); + break; + case PROP_VPN: + /* construct-only */ + priv->vpn = g_value_get_boolean(value); + break; + case PROP_MASTER: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_active_connection_init(NMActiveConnection *self) +{ + NMActiveConnectionPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_ACTIVE_CONNECTION, NMActiveConnectionPrivate); + self->_priv = priv; + + nm_dbus_track_obj_path_init(&priv->settings_connection, + G_OBJECT(self), + obj_properties[PROP_CONNECTION]); + + c_list_init(&self->active_connections_lst); + + _LOGT("creating"); + + priv->activation_type = NM_ACTIVATION_TYPE_MANAGED; + priv->version_id = _version_id_new(); + + /* the keep-alive instance must never change. Callers rely on that. */ + priv->keep_alive = nm_keep_alive_new(); + _nm_keep_alive_set_owner(priv->keep_alive, G_OBJECT(self)); +} + +static void +constructed(GObject *object) +{ + NMActiveConnection * self = (NMActiveConnection *) object; + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_active_connection_parent_class)->constructed(object); + + if (!priv->applied_connection && priv->settings_connection.obj) + priv->applied_connection = nm_simple_connection_new_clone( + nm_settings_connection_get_connection(priv->settings_connection.obj)); + + _LOGD("constructed (%s, version-id %llu, type %s)", + G_OBJECT_TYPE_NAME(self), + (unsigned long long) priv->version_id, + nm_activation_type_to_string(priv->activation_type)); + + if (priv->applied_connection) { + /* priv->applied_connection was set during the construction of the object. + * It's not yet fully initialized, so do that now. + * + * We delayed that, because we may log in _set_applied_connection_take(), and the + * first logging line should be "constructed" above). */ + _set_applied_connection_take(self, g_steal_pointer(&priv->applied_connection)); + } + + if (NM_FLAGS_HAS(priv->state_flags, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY)) + nm_keep_alive_set_settings_connection_watch_visible(priv->keep_alive, + priv->settings_connection.obj); + + g_return_if_fail(priv->subject); + g_return_if_fail(priv->activation_reason != NM_ACTIVATION_REASON_UNSET); +} + +static void +dispose(GObject *object) +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(object); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + nm_assert(!c_list_is_linked(&self->active_connections_lst)); + + _LOGD("disposing"); + + auth_complete(self, FALSE, "Authorization aborted"); + + nm_clear_g_free(&priv->specific_object); + + _set_settings_connection(self, NULL); + g_clear_object(&priv->applied_connection); + + _device_cleanup(self); + + if (priv->master) { + g_signal_handlers_disconnect_by_func(priv->master, (GCallback) master_state_cb, self); + } + g_clear_object(&priv->master); + + if (priv->parent) + unwatch_parent(self, TRUE); + + g_clear_object(&priv->subject); + + G_OBJECT_CLASS(nm_active_connection_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMActiveConnection * self = NM_ACTIVE_CONNECTION(object); + NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self); + + nm_dbus_track_obj_path_set(&priv->settings_connection, NULL, FALSE); + + nm_clear_pointer(&priv->keep_alive, nm_keep_alive_destroy); + + G_OBJECT_CLASS(nm_active_connection_parent_class)->finalize(object); +} + +static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "StateChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("state", "u"), + NM_DEFINE_GDBUS_ARG_INFO("reason", "u"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_active_connection = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_ACTIVE_CONNECTION, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_state_changed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Connection", + "o", + NM_ACTIVE_CONNECTION_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("SpecificObject", + "o", + NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Id", "s", NM_ACTIVE_CONNECTION_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Uuid", + "s", + NM_ACTIVE_CONNECTION_UUID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Type", + "s", + NM_ACTIVE_CONNECTION_TYPE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Devices", + "ao", + NM_ACTIVE_CONNECTION_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", + "u", + NM_ACTIVE_CONNECTION_STATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("StateFlags", + "u", + NM_ACTIVE_CONNECTION_STATE_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Default", + "b", + NM_ACTIVE_CONNECTION_DEFAULT), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Config", + "o", + NM_ACTIVE_CONNECTION_IP4_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp4Config", + "o", + NM_ACTIVE_CONNECTION_DHCP4_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Default6", + "b", + NM_ACTIVE_CONNECTION_DEFAULT6), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip6Config", + "o", + NM_ACTIVE_CONNECTION_IP6_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp6Config", + "o", + NM_ACTIVE_CONNECTION_DHCP6_CONFIG), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Vpn", "b", NM_ACTIVE_CONNECTION_VPN), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Master", + "o", + NM_ACTIVE_CONNECTION_MASTER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_active_connection_class_init(NMActiveConnectionClass *ac_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(ac_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(ac_class); + + g_type_class_add_private(ac_class, sizeof(NMActiveConnectionPrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/ActiveConnection"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_active_connection); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_CONNECTION] = + g_param_spec_string(NM_ACTIVE_CONNECTION_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ID] = g_param_spec_string(NM_ACTIVE_CONNECTION_ID, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_UUID] = g_param_spec_string(NM_ACTIVE_CONNECTION_UUID, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_TYPE] = g_param_spec_string(NM_ACTIVE_CONNECTION_TYPE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SPECIFIC_OBJECT] = + g_param_spec_string(NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICES] = g_param_spec_boxed(NM_ACTIVE_CONNECTION_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE] = g_param_spec_uint(NM_ACTIVE_CONNECTION_STATE, + "", + "", + NM_ACTIVE_CONNECTION_STATE_UNKNOWN, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, + NM_ACTIVE_CONNECTION_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE_FLAGS] = + g_param_spec_uint(NM_ACTIVE_CONNECTION_STATE_FLAGS, + "", + "", + 0, + G_MAXUINT32, + NM_ACTIVATION_STATE_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEFAULT] = g_param_spec_boolean(NM_ACTIVE_CONNECTION_DEFAULT, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP4_CONFIG] = + g_param_spec_string(NM_ACTIVE_CONNECTION_IP4_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DHCP4_CONFIG] = + g_param_spec_string(NM_ACTIVE_CONNECTION_DHCP4_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEFAULT6] = + g_param_spec_boolean(NM_ACTIVE_CONNECTION_DEFAULT6, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP6_CONFIG] = + g_param_spec_string(NM_ACTIVE_CONNECTION_IP6_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DHCP6_CONFIG] = + g_param_spec_string(NM_ACTIVE_CONNECTION_DHCP6_CONFIG, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_VPN] = + g_param_spec_boolean(NM_ACTIVE_CONNECTION_VPN, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_MASTER] = g_param_spec_string(NM_ACTIVE_CONNECTION_MASTER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /* Internal properties */ + obj_properties[PROP_INT_SETTINGS_CONNECTION] = + g_param_spec_object(NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, + "", + "", + NM_TYPE_SETTINGS_CONNECTION, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_APPLIED_CONNECTION] = + g_param_spec_object(NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION, + "", + "", + NM_TYPE_CONNECTION, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_DEVICE] = + g_param_spec_object(NM_ACTIVE_CONNECTION_INT_DEVICE, + "", + "", + NM_TYPE_DEVICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_SUBJECT] = + g_param_spec_object(NM_ACTIVE_CONNECTION_INT_SUBJECT, + "", + "", + NM_TYPE_AUTH_SUBJECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_MASTER] = + g_param_spec_object(NM_ACTIVE_CONNECTION_INT_MASTER, + "", + "", + NM_TYPE_ACTIVE_CONNECTION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_MASTER_READY] = + g_param_spec_boolean(NM_ACTIVE_CONNECTION_INT_MASTER_READY, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_ACTIVATION_TYPE] = + g_param_spec_int(NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE, + "", + "", + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_TYPE_EXTERNAL, + NM_ACTIVATION_TYPE_MANAGED, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_INT_ACTIVATION_REASON] = + g_param_spec_int(NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, + "", + "", + NM_ACTIVATION_REASON_UNSET, + NM_ACTIVATION_REASON_USER_REQUEST, + NM_ACTIVATION_REASON_UNSET, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[DEVICE_CHANGED] = g_signal_new(NM_ACTIVE_CONNECTION_DEVICE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMActiveConnectionClass, device_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + NM_TYPE_DEVICE, + NM_TYPE_DEVICE); + + signals[DEVICE_METERED_CHANGED] = + g_signal_new(NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMActiveConnectionClass, device_metered_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[PARENT_ACTIVE] = g_signal_new(NM_ACTIVE_CONNECTION_PARENT_ACTIVE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMActiveConnectionClass, parent_active), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_ACTIVE_CONNECTION); + + signals[STATE_CHANGED] = g_signal_new(NM_ACTIVE_CONNECTION_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT, + G_TYPE_UINT); +} diff --git a/src/core/nm-active-connection.h b/src/core/nm-active-connection.h new file mode 100644 index 0000000..15c68e6 --- /dev/null +++ b/src/core/nm-active-connection.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_ACTIVE_CONNECTION_H__ +#define __NETWORKMANAGER_ACTIVE_CONNECTION_H__ + +#include "c-list/src/c-list.h" +#include "nm-connection.h" +#include "nm-dbus-object.h" + +#define NM_TYPE_ACTIVE_CONNECTION (nm_active_connection_get_type()) +#define NM_ACTIVE_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_ACTIVE_CONNECTION, NMActiveConnection)) +#define NM_ACTIVE_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_ACTIVE_CONNECTION, NMActiveConnectionClass)) +#define NM_IS_ACTIVE_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_ACTIVE_CONNECTION)) +#define NM_IS_ACTIVE_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_ACTIVE_CONNECTION)) +#define NM_ACTIVE_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_ACTIVE_CONNECTION, NMActiveConnectionClass)) + +/* D-Bus Exported Properties */ +#define NM_ACTIVE_CONNECTION_CONNECTION "connection" +#define NM_ACTIVE_CONNECTION_ID "id" +#define NM_ACTIVE_CONNECTION_UUID "uuid" +#define NM_ACTIVE_CONNECTION_TYPE "type" +#define NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT "specific-object" +#define NM_ACTIVE_CONNECTION_DEVICES "devices" +#define NM_ACTIVE_CONNECTION_STATE "state" +#define NM_ACTIVE_CONNECTION_STATE_FLAGS "state-flags" +#define NM_ACTIVE_CONNECTION_DEFAULT "default" +#define NM_ACTIVE_CONNECTION_IP4_CONFIG "ip4-config" +#define NM_ACTIVE_CONNECTION_DHCP4_CONFIG "dhcp4-config" +#define NM_ACTIVE_CONNECTION_DEFAULT6 "default6" +#define NM_ACTIVE_CONNECTION_IP6_CONFIG "ip6-config" +#define NM_ACTIVE_CONNECTION_DHCP6_CONFIG "dhcp6-config" +#define NM_ACTIVE_CONNECTION_VPN "vpn" +#define NM_ACTIVE_CONNECTION_MASTER "master" + +/* Internal non-exported properties */ +#define NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION "int-settings-connection" +#define NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION "int-applied-connection" +#define NM_ACTIVE_CONNECTION_INT_DEVICE "int-device" +#define NM_ACTIVE_CONNECTION_INT_SUBJECT "int-subject" +#define NM_ACTIVE_CONNECTION_INT_MASTER "int-master" +#define NM_ACTIVE_CONNECTION_INT_MASTER_READY "int-master-ready" +#define NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE "int-activation-type" +#define NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON "int-activation-reason" + +/* Signals */ +#define NM_ACTIVE_CONNECTION_STATE_CHANGED "state-changed" + +/* Internal signals*/ +#define NM_ACTIVE_CONNECTION_DEVICE_CHANGED "device-changed" +#define NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED "device-metered-changed" +#define NM_ACTIVE_CONNECTION_PARENT_ACTIVE "parent-active" + +struct _NMActiveConnectionPrivate; + +struct _NMActiveConnection { + NMDBusObject parent; + struct _NMActiveConnectionPrivate *_priv; + + /* active connection can be tracked in a list by NMManager. This is + * the list node. */ + CList active_connections_lst; +}; + +typedef struct { + NMDBusObjectClass parent; + + /* re-emits device state changes as a convenience for subclasses for + * device states >= DISCONNECTED. + */ + void (*device_state_changed)(NMActiveConnection *connection, + NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state); + void (*master_failed)(NMActiveConnection *connection); + + void (*device_changed)(NMActiveConnection *connection, + NMDevice * new_device, + NMDevice * old_device); + + void (*device_metered_changed)(NMActiveConnection *connection, NMMetered new_value); + + void (*parent_active)(NMActiveConnection *connection); +} NMActiveConnectionClass; + +guint64 nm_active_connection_version_id_get(NMActiveConnection *self); +guint64 nm_active_connection_version_id_bump(NMActiveConnection *self); + +GType nm_active_connection_get_type(void); + +typedef void (*NMActiveConnectionAuthResultFunc)(NMActiveConnection *self, + gboolean success, + const char * error_desc, + gpointer user_data); + +void nm_active_connection_authorize(NMActiveConnection * self, + NMConnection * initial_connection, + NMActiveConnectionAuthResultFunc result_func, + gpointer user_data); + +NMSettingsConnection *nm_active_connection_get_settings_connection(NMActiveConnection *self); +NMConnection * nm_active_connection_get_applied_connection(NMActiveConnection *self); + +NMSettingsConnection *_nm_active_connection_get_settings_connection(NMActiveConnection *self); + +void nm_active_connection_set_settings_connection(NMActiveConnection * self, + NMSettingsConnection *connection); + +gboolean +nm_active_connection_has_unmodified_applied_connection(NMActiveConnection * self, + NMSettingCompareFlags compare_flags); + +const char *nm_active_connection_get_settings_connection_id(NMActiveConnection *self); + +const char *nm_active_connection_get_specific_object(NMActiveConnection *self); + +void nm_active_connection_set_specific_object(NMActiveConnection *self, + const char * specific_object); + +void +nm_active_connection_set_default(NMActiveConnection *self, int addr_family, gboolean is_default); + +gboolean nm_active_connection_get_default(NMActiveConnection *self, int addr_family); + +NMActiveConnectionState nm_active_connection_get_state(NMActiveConnection *self); + +void nm_active_connection_set_state(NMActiveConnection * self, + NMActiveConnectionState state, + NMActiveConnectionStateReason reason); + +void nm_active_connection_set_state_fail(NMActiveConnection * active, + NMActiveConnectionStateReason reason, + const char * error_desc); + +NMActivationStateFlags nm_active_connection_get_state_flags(NMActiveConnection *self); + +void nm_active_connection_set_state_flags_full(NMActiveConnection * self, + NMActivationStateFlags state_flags, + NMActivationStateFlags mask); + +static inline void +nm_active_connection_set_state_flags(NMActiveConnection *self, NMActivationStateFlags state_flags) +{ + nm_active_connection_set_state_flags_full(self, state_flags, state_flags); +} + +static inline void +nm_active_connection_set_state_flags_clear(NMActiveConnection * self, + NMActivationStateFlags state_flags) +{ + nm_active_connection_set_state_flags_full(self, NM_ACTIVATION_STATE_FLAG_NONE, state_flags); +} + +NMDevice *nm_active_connection_get_device(NMActiveConnection *self); + +gboolean nm_active_connection_set_device(NMActiveConnection *self, NMDevice *device); + +NMAuthSubject *nm_active_connection_get_subject(NMActiveConnection *self); + +gboolean nm_active_connection_get_user_requested(NMActiveConnection *self); + +NMActiveConnection *nm_active_connection_get_master(NMActiveConnection *self); + +gboolean nm_active_connection_get_master_ready(NMActiveConnection *self); + +void nm_active_connection_set_master(NMActiveConnection *self, NMActiveConnection *master); + +void nm_active_connection_set_parent(NMActiveConnection *self, NMActiveConnection *parent); + +NMActivationType nm_active_connection_get_activation_type(NMActiveConnection *self); + +NMActivationReason nm_active_connection_get_activation_reason(NMActiveConnection *self); + +NMKeepAlive *nm_active_connection_get_keep_alive(NMActiveConnection *self); + +void nm_active_connection_clear_secrets(NMActiveConnection *self); + +#endif /* __NETWORKMANAGER_ACTIVE_CONNECTION_H__ */ diff --git a/src/core/nm-audit-manager.c b/src/core/nm-audit-manager.c new file mode 100644 index 0000000..4a67f2e --- /dev/null +++ b/src/core/nm-audit-manager.c @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-audit-manager.h" + +#if HAVE_LIBAUDIT + #include +#endif + +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-config.h" +#include "nm-dbus-manager.h" +#include "settings/nm-settings-connection.h" + +/*****************************************************************************/ + +typedef enum { + BACKEND_LOG = (1 << 0), + BACKEND_AUDITD = (1 << 1), + _BACKEND_LAST, + BACKEND_ALL = ((_BACKEND_LAST - 1) << 1) - 1, +} AuditBackend; + +typedef struct { + const char * name; + GValue value; + gboolean need_encoding; + AuditBackend backends; +} AuditField; + +/*****************************************************************************/ + +typedef struct { + NMConfig *config; + int auditd_fd; +} NMAuditManagerPrivate; + +struct _NMAuditManager { + GObject parent; +#if HAVE_LIBAUDIT + NMAuditManagerPrivate _priv; +#endif +}; + +struct _NMAuditManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMAuditManager, nm_audit_manager, G_TYPE_OBJECT) + +#define NM_AUDIT_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMAuditManager, NM_IS_AUDIT_MANAGER) + +/*****************************************************************************/ + +#define AUDIT_LOG_LEVEL LOGL_INFO + +#define _NMLOG_PREFIX_NAME "audit" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMAuditManager, nm_audit_manager_get, NM_TYPE_AUDIT_MANAGER); + +/*****************************************************************************/ + +static void +_audit_field_init_string(AuditField * field, + const char * name, + const char * str, + gboolean need_encoding, + AuditBackend backends) +{ + field->name = name; + field->need_encoding = need_encoding; + field->backends = backends; + g_value_init(&field->value, G_TYPE_STRING); + g_value_set_static_string(&field->value, str); +} + +static void +_audit_field_init_uint(AuditField *field, const char *name, uint val, AuditBackend backends) +{ + field->name = name; + field->backends = backends; + g_value_init(&field->value, G_TYPE_UINT); + g_value_set_uint(&field->value, val); +} + +static char * +build_message(GPtrArray *fields, AuditBackend backend) +{ + GString * string; + AuditField *field; + gboolean first = TRUE; + guint i; + + string = g_string_new(NULL); + + for (i = 0; i < fields->len; i++) { + field = fields->pdata[i]; + + if (!NM_FLAGS_ANY(field->backends, backend)) + continue; + + if (first) + first = FALSE; + else + g_string_append_c(string, ' '); + + if (G_VALUE_HOLDS_STRING(&field->value)) { + const char *str = g_value_get_string(&field->value); + +#if HAVE_LIBAUDIT + if (backend == BACKEND_AUDITD) { + if (field->need_encoding) { + char *value; + + value = audit_encode_nv_string(field->name, str, 0); + g_string_append(string, value); + g_free(value); + } else + g_string_append_printf(string, "%s=%s", field->name, str); + continue; + } +#endif /* HAVE_LIBAUDIT */ + g_string_append_printf(string, "%s=\"%s\"", field->name, str); + } else if (G_VALUE_HOLDS_UINT(&field->value)) { + g_string_append_printf(string, "%s=%u", field->name, g_value_get_uint(&field->value)); + } else + g_assert_not_reached(); + } + return g_string_free(string, FALSE); +} + +static void +nm_audit_log(NMAuditManager *self, + GPtrArray * fields, + const char * file, + guint line, + const char * func, + gboolean success) +{ +#if HAVE_LIBAUDIT + NMAuditManagerPrivate *priv; +#endif + char *msg; + + g_return_if_fail(NM_IS_AUDIT_MANAGER(self)); + +#if HAVE_LIBAUDIT + priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); + + if (priv->auditd_fd >= 0) { + msg = build_message(fields, BACKEND_AUDITD); + audit_log_user_message(priv->auditd_fd, AUDIT_USYS_CONFIG, msg, NULL, NULL, NULL, success); + g_free(msg); + } +#endif + + if (nm_logging_enabled(AUDIT_LOG_LEVEL, LOGD_AUDIT)) { + msg = build_message(fields, BACKEND_LOG); + _NMLOG(AUDIT_LOG_LEVEL, LOGD_AUDIT, "%s", msg); + g_free(msg); + } +} + +static void +_audit_log_helper(NMAuditManager *self, + GPtrArray * fields, + const char * file, + guint line, + const char * func, + const char * op, + gboolean result, + gpointer subject_context, + const char * reason) +{ + AuditField op_field = {}, pid_field = {}, uid_field = {}; + AuditField result_field = {}, reason_field = {}; + gulong pid, uid; + NMAuthSubject * subject = NULL; + gs_unref_object NMAuthSubject *subject_free = NULL; + + _audit_field_init_string(&op_field, "op", op, FALSE, BACKEND_ALL); + g_ptr_array_insert(fields, 0, &op_field); + + if (subject_context) { + if (NM_IS_AUTH_SUBJECT(subject_context)) + subject = subject_context; + else if (G_IS_DBUS_METHOD_INVOCATION(subject_context)) { + GDBusMethodInvocation *context = subject_context; + + subject = subject_free = nm_dbus_manager_new_auth_subject_from_context(context); + } else + g_warn_if_reached(); + } + if (subject && nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { + pid = nm_auth_subject_get_unix_process_pid(subject); + uid = nm_auth_subject_get_unix_process_uid(subject); + if (pid != G_MAXULONG) { + _audit_field_init_uint(&pid_field, "pid", pid, BACKEND_ALL); + g_ptr_array_add(fields, &pid_field); + } + if (uid != G_MAXULONG) { + _audit_field_init_uint(&uid_field, "uid", uid, BACKEND_ALL); + g_ptr_array_add(fields, &uid_field); + } + } + + _audit_field_init_string(&result_field, + "result", + result ? "success" : "fail", + FALSE, + BACKEND_ALL); + g_ptr_array_add(fields, &result_field); + + if (reason) { + _audit_field_init_string(&reason_field, "reason", reason, FALSE, BACKEND_LOG); + g_ptr_array_add(fields, &reason_field); + } + + nm_audit_log(self, fields, file, line, func, result); +} + +gboolean +nm_audit_manager_audit_enabled(NMAuditManager *self) +{ +#if HAVE_LIBAUDIT + NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); + + if (priv->auditd_fd >= 0) + return TRUE; +#endif + + return nm_logging_enabled(AUDIT_LOG_LEVEL, LOGD_AUDIT); +} + +void +_nm_audit_manager_log_connection_op(NMAuditManager * self, + const char * file, + guint line, + const char * func, + const char * op, + NMSettingsConnection *connection, + gboolean result, + const char * args, + gpointer subject_context, + const char * reason) +{ + gs_unref_ptrarray GPtrArray *fields = NULL; + AuditField uuid_field = {}, name_field = {}, args_field = {}; + + g_return_if_fail(op); + + fields = g_ptr_array_new(); + + if (connection) { + _audit_field_init_string(&uuid_field, + "uuid", + nm_settings_connection_get_uuid(connection), + FALSE, + BACKEND_ALL); + g_ptr_array_add(fields, &uuid_field); + + _audit_field_init_string(&name_field, + "name", + nm_settings_connection_get_id(connection), + TRUE, + BACKEND_ALL); + g_ptr_array_add(fields, &name_field); + } + + if (args) { + _audit_field_init_string(&args_field, "args", args, FALSE, BACKEND_ALL); + g_ptr_array_add(fields, &args_field); + } + + _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); +} + +void +_nm_audit_manager_log_generic_op(NMAuditManager *self, + const char * file, + guint line, + const char * func, + const char * op, + const char * arg, + gboolean result, + gpointer subject_context, + const char * reason) +{ + gs_unref_ptrarray GPtrArray *fields = NULL; + AuditField arg_field = {}; + + g_return_if_fail(op); + g_return_if_fail(arg); + + fields = g_ptr_array_new(); + + _audit_field_init_string(&arg_field, "arg", arg, TRUE, BACKEND_ALL); + g_ptr_array_add(fields, &arg_field); + + _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); +} + +void +_nm_audit_manager_log_device_op(NMAuditManager *self, + const char * file, + guint line, + const char * func, + const char * op, + NMDevice * device, + gboolean result, + const char * args, + gpointer subject_context, + const char * reason) +{ + gs_unref_ptrarray GPtrArray *fields = NULL; + AuditField interface_field = {}, ifindex_field = {}, args_field = {}; + int ifindex; + + g_return_if_fail(op); + g_return_if_fail(device); + + fields = g_ptr_array_new(); + + _audit_field_init_string(&interface_field, + "interface", + nm_device_get_ip_iface(device), + TRUE, + BACKEND_ALL); + g_ptr_array_add(fields, &interface_field); + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex > 0) { + _audit_field_init_uint(&ifindex_field, "ifindex", ifindex, BACKEND_ALL); + g_ptr_array_add(fields, &ifindex_field); + } + + if (args) { + _audit_field_init_string(&args_field, "args", args, FALSE, BACKEND_ALL); + g_ptr_array_add(fields, &args_field); + } + + _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); +} + +#if HAVE_LIBAUDIT +static void +init_auditd(NMAuditManager *self) +{ + NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); + NMConfigData * data = nm_config_get_data(priv->config); + int errsv; + + if (nm_config_data_get_value_boolean(data, + NM_CONFIG_KEYFILE_GROUP_LOGGING, + NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT, + NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL)) { + if (priv->auditd_fd < 0) { + priv->auditd_fd = audit_open(); + if (priv->auditd_fd < 0) { + errsv = errno; + _LOGE(LOGD_CORE, "failed to open auditd socket: %s", nm_strerror_native(errsv)); + } else + _LOGD(LOGD_CORE, "socket created"); + } + } else { + if (priv->auditd_fd >= 0) { + audit_close(priv->auditd_fd); + priv->auditd_fd = -1; + _LOGD(LOGD_CORE, "socket closed"); + } + } +} + +static void +config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMAuditManager * self) +{ + if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES)) + init_auditd(self); +} +#endif + +/*****************************************************************************/ + +static void +nm_audit_manager_init(NMAuditManager *self) +{ +#if HAVE_LIBAUDIT + NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); + + priv->config = g_object_ref(nm_config_get()); + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed_cb), + self); + priv->auditd_fd = -1; + + init_auditd(self); +#endif +} + +static void +dispose(GObject *object) +{ +#if HAVE_LIBAUDIT + NMAuditManager * self = NM_AUDIT_MANAGER(object); + NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); + + if (priv->config) { + g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); + g_clear_object(&priv->config); + } + + if (priv->auditd_fd >= 0) { + audit_close(priv->auditd_fd); + priv->auditd_fd = -1; + } +#endif + + G_OBJECT_CLASS(nm_audit_manager_parent_class)->dispose(object); +} + +static void +nm_audit_manager_class_init(NMAuditManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; +} diff --git a/src/core/nm-audit-manager.h b/src/core/nm-audit-manager.h new file mode 100644 index 0000000..fdabbf2 --- /dev/null +++ b/src/core/nm-audit-manager.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_AUDIT_MANAGER_H__ +#define __NM_AUDIT_MANAGER_H__ + +#include "nm-connection.h" +#include "devices/nm-device.h" +#include "nm-types.h" + +#define NM_TYPE_AUDIT_MANAGER (nm_audit_manager_get_type()) +#define NM_AUDIT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_AUDIT_MANAGER, NMAuditManager)) +#define NM_AUDIT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_AUDIT_MANAGER, NMAuditManagerClass)) +#define NM_IS_AUDIT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_AUDIT_MANAGER)) +#define NM_IS_AUDIT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_AUDIT_MANAGER)) +#define NM_AUDIT_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_AUDIT_MANAGER, NMAuditManagerClass)) + +typedef struct _NMAuditManagerClass NMAuditManagerClass; + +#define NM_AUDIT_OP_CONN_ADD "connection-add" +#define NM_AUDIT_OP_CONN_DELETE "connection-delete" +#define NM_AUDIT_OP_CONN_UPDATE "connection-update" +#define NM_AUDIT_OP_CONN_ACTIVATE "connection-activate" +#define NM_AUDIT_OP_CONN_ADD_ACTIVATE "connection-add-activate" +#define NM_AUDIT_OP_CONN_DEACTIVATE "connection-deactivate" +#define NM_AUDIT_OP_CONN_CLEAR_SECRETS "connection-clear-secrets" + +#define NM_AUDIT_OP_CONNS_RELOAD "connections-reload" +#define NM_AUDIT_OP_CONNS_LOAD "connections-load" + +#define NM_AUDIT_OP_RELOAD "reload" +#define NM_AUDIT_OP_SLEEP_CONTROL "sleep-control" +#define NM_AUDIT_OP_NET_CONTROL "networking-control" +#define NM_AUDIT_OP_RADIO_CONTROL "radio-control" +#define NM_AUDIT_OP_STATISTICS "statistics" +#define NM_AUDIT_OP_HOSTNAME_SAVE "hostname-save" + +#define NM_AUDIT_OP_DEVICE_AUTOCONNECT "device-autoconnect" +#define NM_AUDIT_OP_DEVICE_DISCONNECT "device-disconnect" +#define NM_AUDIT_OP_DEVICE_DELETE "device-delete" +#define NM_AUDIT_OP_DEVICE_MANAGED "device-managed" +#define NM_AUDIT_OP_DEVICE_REAPPLY "device-reapply" + +#define NM_AUDIT_OP_CHECKPOINT_CREATE "checkpoint-create" +#define NM_AUDIT_OP_CHECKPOINT_ROLLBACK "checkpoint-rollback" +#define NM_AUDIT_OP_CHECKPOINT_DESTROY "checkpoint-destroy" +#define NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT "checkpoint-adjust-rollback-timeout" + +GType nm_audit_manager_get_type(void); +NMAuditManager *nm_audit_manager_get(void); +gboolean nm_audit_manager_audit_enabled(NMAuditManager *self); + +#define nm_audit_log_connection_op(op, connection, result, args, subject_context, reason) \ + G_STMT_START \ + { \ + NMAuditManager *_audit = nm_audit_manager_get(); \ + \ + if (nm_audit_manager_audit_enabled(_audit)) { \ + _nm_audit_manager_log_connection_op(_audit, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + (op), \ + (connection), \ + (result), \ + (args), \ + (subject_context), \ + (reason)); \ + } \ + } \ + G_STMT_END + +#define nm_audit_log_control_op(op, arg, result, subject_context, reason) \ + G_STMT_START \ + { \ + NMAuditManager *_audit = nm_audit_manager_get(); \ + \ + if (nm_audit_manager_audit_enabled(_audit)) { \ + _nm_audit_manager_log_generic_op(_audit, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + (op), \ + (arg), \ + (result), \ + (subject_context), \ + (reason)); \ + } \ + } \ + G_STMT_END + +#define nm_audit_log_device_op(op, device, result, args, subject_context, reason) \ + G_STMT_START \ + { \ + NMAuditManager *_audit = nm_audit_manager_get(); \ + \ + if (nm_audit_manager_audit_enabled(_audit)) { \ + _nm_audit_manager_log_device_op(_audit, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + (op), \ + (device), \ + (result), \ + (args), \ + (subject_context), \ + (reason)); \ + } \ + } \ + G_STMT_END + +#define nm_audit_log_checkpoint_op(op, arg, result, subject_context, reason) \ + G_STMT_START \ + { \ + NMAuditManager *_audit = nm_audit_manager_get(); \ + \ + if (nm_audit_manager_audit_enabled(_audit)) { \ + _nm_audit_manager_log_generic_op(_audit, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + (op), \ + (arg), \ + (result), \ + (subject_context), \ + (reason)); \ + } \ + } \ + G_STMT_END + +void _nm_audit_manager_log_connection_op(NMAuditManager * self, + const char * file, + guint line, + const char * func, + const char * op, + NMSettingsConnection *connection, + gboolean result, + const char * args, + gpointer subject_context, + const char * reason); + +void _nm_audit_manager_log_generic_op(NMAuditManager *self, + const char * file, + guint line, + const char * func, + const char * op, + const char * arg, + gboolean result, + gpointer subject_context, + const char * reason); + +void _nm_audit_manager_log_device_op(NMAuditManager *self, + const char * file, + guint line, + const char * func, + const char * op, + NMDevice * device, + gboolean result, + const char * args, + gpointer subject_context, + const char * reason); + +#endif /* __NM_AUDIT_MANAGER_H__ */ diff --git a/src/core/nm-auth-manager.c b/src/core/nm-auth-manager.c new file mode 100644 index 0000000..046282c --- /dev/null +++ b/src/core/nm-auth-manager.c @@ -0,0 +1,751 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-auth-manager.h" + +#include "c-list/src/c-list.h" +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-errors.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" + +#define POLKIT_SERVICE "org.freedesktop.PolicyKit1" +#define POLKIT_OBJECT_PATH "/org/freedesktop/PolicyKit1/Authority" +#define POLKIT_INTERFACE "org.freedesktop.PolicyKit1.Authority" + +#define CANCELLATION_ID_PREFIX "cancellation-id-" +#define CANCELLATION_TIMEOUT_MS 5000 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_POLKIT_ENABLED, ); + +enum { + CHANGED_SIGNAL, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + CList calls_lst_head; + GDBusConnection *dbus_connection; + GCancellable * main_cancellable; + char * name_owner; + guint64 call_numid_counter; + guint changed_id; + guint name_owner_changed_id; + bool disposing : 1; + bool shutting_down : 1; + bool got_name_owner : 1; + NMAuthPolkitMode auth_polkit_mode : 3; +} NMAuthManagerPrivate; + +struct _NMAuthManager { + GObject parent; + NMAuthManagerPrivate _priv; +}; + +struct _NMAuthManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMAuthManager, nm_auth_manager, G_TYPE_OBJECT) + +#define NM_AUTH_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthManager, NM_IS_AUTH_MANAGER) + +NM_DEFINE_SINGLETON_REGISTER(NMAuthManager); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "auth" +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + char __prefix[30] = _NMLOG_PREFIX_NAME; \ + \ + if ((self) != singleton_instance) \ + g_snprintf(__prefix, \ + sizeof(__prefix), \ + ""_NMLOG_PREFIX_NAME \ + "[%p]", \ + (self)); \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, call_id, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + NMAuthManagerCallId *_call_id = (call_id); \ + char __prefix[30] = _NMLOG_PREFIX_NAME; \ + \ + if (_call_id->self != singleton_instance) \ + g_snprintf(__prefix, \ + sizeof(__prefix), \ + ""_NMLOG_PREFIX_NAME \ + "[%p]", \ + _call_id->self); \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: call[%" G_GUINT64_FORMAT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix, \ + _call_id->call_numid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +gboolean +nm_auth_manager_get_polkit_enabled(NMAuthManager *self) +{ + g_return_val_if_fail(NM_IS_AUTH_MANAGER(self), FALSE); + + return NM_AUTH_MANAGER_GET_PRIVATE(self)->dbus_connection != NULL; +} + +/*****************************************************************************/ + +static void +_emit_changed_signal(NMAuthManager *self) +{ + g_signal_emit(self, signals[CHANGED_SIGNAL], 0); +} + +typedef enum { + POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE = 0, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1 << 0), +} PolkitCheckAuthorizationFlags; + +struct _NMAuthManagerCallId { + CList calls_lst; + NMAuthManager * self; + GCancellable * dbus_cancellable; + NMAuthManagerCheckAuthorizationCallback callback; + gpointer user_data; + guint64 call_numid; + guint idle_id; + bool idle_is_authorized : 1; +}; + +#define cancellation_id_to_str_a(call_numid) \ + nm_sprintf_bufa(NM_STRLEN(CANCELLATION_ID_PREFIX) + 60, \ + CANCELLATION_ID_PREFIX "%" G_GUINT64_FORMAT, \ + (call_numid)) + +static void +_call_id_free(NMAuthManagerCallId *call_id) +{ + c_list_unlink(&call_id->calls_lst); + nm_clear_g_source(&call_id->idle_id); + + if (call_id->dbus_cancellable) { + /* we have a pending D-Bus call. We keep the call-id instance alive + * for _call_check_authorize_cb() */ + g_cancellable_cancel(call_id->dbus_cancellable); + return; + } + + g_object_unref(call_id->self); + g_slice_free(NMAuthManagerCallId, call_id); +} + +static void +_call_id_invoke_callback(NMAuthManagerCallId *call_id, + gboolean is_authorized, + gboolean is_challenge, + GError * error) +{ + c_list_unlink(&call_id->calls_lst); + + call_id + ->callback(call_id->self, call_id, is_authorized, is_challenge, error, call_id->user_data); + _call_id_free(call_id); +} + +static void +cancel_check_authorization_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMAuthManagerCallId *call_id = user_data; + gs_unref_variant GVariant *value = NULL; + gs_free_error GError *error = NULL; + + value = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOG2T(call_id, "cancel request was cancelled"); + else if (error) + _LOG2T(call_id, "cancel request failed: %s", error->message); + else + _LOG2T(call_id, "cancel request succeeded"); + + _call_id_free(call_id); +} + +static void +_call_check_authorize_cb(GObject *proxy, GAsyncResult *res, gpointer user_data) +{ + NMAuthManagerCallId * call_id = user_data; + NMAuthManager * self; + NMAuthManagerPrivate *priv; + gs_unref_variant GVariant *value = NULL; + gs_free_error GError *error = NULL; + gboolean is_authorized = FALSE; + gboolean is_challenge = FALSE; + + /* we need to clear the cancelable, to signal for _call_id_free() that we + * are not in a pending call. + * + * Note how _call_id_free() kept call-id alive, even if the request was + * already cancelled. */ + g_clear_object(&call_id->dbus_cancellable); + + self = call_id->self; + priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + + value = g_dbus_connection_call_finish(G_DBUS_CONNECTION(proxy), res, &error); + + if (nm_utils_error_is_cancelled(error)) { + /* call_id was cancelled externally, but _call_id_free() kept call_id + * alive (and it has still the reference on @self. */ + + if (!priv->main_cancellable) { + /* we do a forced shutdown. There is no more time for cancelling... */ + _call_id_free(call_id); + + /* this shouldn't really happen, because: + * nm_auth_manager_check_authorization() only scheduled the D-Bus request at a time when + * main_cancellable was still set. It means, somebody called force-shutdown + * after call-id was schedule. + * force-shutdown should only be called after: + * - cancel all pending requests + * - give enough time to cancel the request and schedule a D-Bus call + * to CancelCheckAuthorization (below), before issuing force-shutdown. */ + g_return_if_reached(); + } + + g_dbus_connection_call(priv->dbus_connection, + POLKIT_SERVICE, + POLKIT_OBJECT_PATH, + POLKIT_INTERFACE, + "CancelCheckAuthorization", + g_variant_new("(s)", cancellation_id_to_str_a(call_id->call_numid)), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + CANCELLATION_TIMEOUT_MS, + priv->main_cancellable, + cancel_check_authorization_cb, + call_id); + return; + } + + if (!error) { + g_variant_get(value, "((bb@a{ss}))", &is_authorized, &is_challenge, NULL); + _LOG2T(call_id, "completed: authorized=%d, challenge=%d", is_authorized, is_challenge); + } else + _LOG2T(call_id, "completed: failed: %s", error->message); + + _call_id_invoke_callback(call_id, is_authorized, is_challenge, error); +} + +static gboolean +_call_on_idle(gpointer user_data) +{ + NMAuthManagerCallId *call_id = user_data; + gboolean is_authorized; + gboolean is_challenge = FALSE; + + is_authorized = call_id->idle_is_authorized; + call_id->idle_id = 0; + + _LOG2T(call_id, + "completed: authorized=%d, challenge=%d (simulated)", + is_authorized, + is_challenge); + + _call_id_invoke_callback(call_id, is_authorized, is_challenge, NULL); + return G_SOURCE_REMOVE; +} + +/* + * @callback must never be invoked synchronously. + * + * @callback is always invoked exactly once, and never synchronously. + * You may cancel the invocation with nm_auth_manager_check_authorization_cancel(), + * but: you may only do so exactly once, and only before @callback is + * invoked. Even if you cancel the request, @callback will still be invoked + * (synchronously, during the _cancel() callback). + * + * The request keeps @self alive (it needs to do so, because when cancelling a + * request we might need to do an additional CancelCheckAuthorization call, for + * which @self must be live long enough). + */ +NMAuthManagerCallId * +nm_auth_manager_check_authorization(NMAuthManager * self, + NMAuthSubject * subject, + const char * action_id, + gboolean allow_user_interaction, + NMAuthManagerCheckAuthorizationCallback callback, + gpointer user_data) +{ + NMAuthManagerPrivate * priv; + PolkitCheckAuthorizationFlags flags; + char subject_buf[64]; + NMAuthManagerCallId * call_id; + + g_return_val_if_fail(NM_IS_AUTH_MANAGER(self), NULL); + g_return_val_if_fail(NM_IN_SET(nm_auth_subject_get_subject_type(subject), + NM_AUTH_SUBJECT_TYPE_INTERNAL, + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS), + NULL); + g_return_val_if_fail(action_id, NULL); + + priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + + g_return_val_if_fail(!priv->disposing, NULL); + g_return_val_if_fail(!priv->shutting_down, NULL); + + flags = allow_user_interaction ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION + : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; + + call_id = g_slice_new(NMAuthManagerCallId); + *call_id = (NMAuthManagerCallId){ + .self = g_object_ref(self), + .callback = callback, + .user_data = user_data, + .call_numid = ++priv->call_numid_counter, + .idle_is_authorized = TRUE, + }; + c_list_link_tail(&priv->calls_lst_head, &call_id->calls_lst); + + if (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL) { + _LOG2T(call_id, + "CheckAuthorization(%s), subject=%s (succeeding for internal request)", + action_id, + nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); + call_id->idle_id = g_idle_add(_call_on_idle, call_id); + } else if (nm_auth_subject_get_unix_process_uid(subject) == 0) { + _LOG2T(call_id, + "CheckAuthorization(%s), subject=%s (succeeding for root)", + action_id, + nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); + call_id->idle_id = g_idle_add(_call_on_idle, call_id); + } else if (priv->auth_polkit_mode != NM_AUTH_POLKIT_MODE_USE_POLKIT) { + _LOG2T(call_id, + "CheckAuthorization(%s), subject=%s (PolicyKit disabled and always %s authorization " + "to non-root user)", + action_id, + nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf)), + priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ALLOW_ALL ? "grant" : "deny"); + call_id->idle_is_authorized = (priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ALLOW_ALL); + call_id->idle_id = g_idle_add(_call_on_idle, call_id); + } else { + GVariant * parameters; + GVariantBuilder builder; + GVariant * subject_value; + GVariant * details_value; + + subject_value = nm_auth_subject_unix_to_polkit_gvariant(subject); + nm_assert(g_variant_is_floating(subject_value)); + + /* ((PolkitDetails *)NULL) */ + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + details_value = g_variant_builder_end(&builder); + + parameters = g_variant_new("(@(sa{sv})s@a{ss}us)", + subject_value, + action_id, + details_value, + (guint32) flags, + cancellation_id_to_str_a(call_id->call_numid)); + + _LOG2T(call_id, + "CheckAuthorization(%s), subject=%s", + action_id, + nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); + + call_id->dbus_cancellable = g_cancellable_new(); + + nm_assert(priv->main_cancellable); + + g_dbus_connection_call(priv->dbus_connection, + POLKIT_SERVICE, + POLKIT_OBJECT_PATH, + POLKIT_INTERFACE, + "CheckAuthorization", + parameters, + G_VARIANT_TYPE("((bba{ss}))"), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, /* no timeout */ + call_id->dbus_cancellable, + _call_check_authorize_cb, + call_id); + } + + return call_id; +} + +void +nm_auth_manager_check_authorization_cancel(NMAuthManagerCallId *call_id) +{ + NMAuthManager *self; + gs_free_error GError *error = NULL; + + g_return_if_fail(call_id); + + self = call_id->self; + + g_return_if_fail(NM_IS_AUTH_MANAGER(self)); + g_return_if_fail(!c_list_is_empty(&call_id->calls_lst)); + + nm_assert( + c_list_contains(&NM_AUTH_MANAGER_GET_PRIVATE(self)->calls_lst_head, &call_id->calls_lst)); + + nm_utils_error_set_cancelled(&error, FALSE, "NMAuthManager"); + _LOG2T(call_id, "completed: failed due to call cancelled"); + _call_id_invoke_callback(call_id, FALSE, FALSE, error); +} + +/*****************************************************************************/ + +static void +changed_signal_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMAuthManager * self = user_data; + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + gboolean valid_sender; + + nm_assert(nm_streq0(signal_name, "Changed")); + + valid_sender = nm_streq0(priv->name_owner, sender_name); + + _LOGD("dbus-signal: \"Changed\" notification%s", valid_sender ? "" : " (ignore)"); + + if (valid_sender) + _emit_changed_signal(self); +} + +static void +_name_owner_changed(NMAuthManager *self, const char *name_owner, gboolean is_initial) +{ + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + gboolean is_changed; + gs_free char * old_name_owner = NULL; + + if (is_initial) + priv->got_name_owner = TRUE; + else { + if (!priv->got_name_owner) + return; + } + + name_owner = nm_str_not_empty(name_owner); + + is_changed = !nm_streq0(priv->name_owner, name_owner); + if (is_changed) { + old_name_owner = g_steal_pointer(&priv->name_owner); + priv->name_owner = g_strdup(name_owner); + } else { + if (!is_initial) + return; + } + + if (!priv->name_owner) { + if (is_initial) + _LOGT("name-owner: polkit not running"); + else + _LOGT("name-owner: polkit stopped (was %s)", old_name_owner); + } else { + if (is_initial) + _LOGT("name-owner: polkit is running (now %s)", priv->name_owner); + else if (old_name_owner) + _LOGT("name-owner: polkit restarted (now %s, was %s)", + priv->name_owner, + old_name_owner); + else + _LOGT("name-owner: polkit started (now %s)", priv->name_owner); + } + + if (priv->name_owner) + _emit_changed_signal(self); +} + +static void +_name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMAuthManager *self = user_data; + const char * new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + _name_owner_changed(self, new_owner, FALSE); +} + +static void +_name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (!nm_utils_error_is_cancelled(error)) + _name_owner_changed(user_data, name_owner, TRUE); +} + +/*****************************************************************************/ + +NMAuthManager * +nm_auth_manager_get() +{ + g_return_val_if_fail(singleton_instance, NULL); + + return singleton_instance; +} + +void +nm_auth_manager_force_shutdown(NMAuthManager *self) +{ + NMAuthManagerPrivate *priv; + + g_return_if_fail(NM_IS_AUTH_MANAGER(self)); + + priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + + /* FIXME(shutdown): ensure we properly call this API during shutdown as + * described next. */ + + /* while we have pending requests (NMAuthManagerCallId), the instance + * is kept alive. + * + * Even if the caller cancels all pending call-ids, we still need to keep + * a reference to self, in order to handle pending CancelCheckAuthorization + * requests. + * + * To do a coordinated shutdown, do the following: + * - cancel all pending NMAuthManagerCallId requests. + * - ensure everybody unrefs the NMAuthManager instance. If by that, the instance + * gets destroyed, the shutdown already completed successfully. + * - Otherwise, the object is kept alive by pending CancelCheckAuthorization requests. + * wait a certain timeout (1 second) for all requests to complete (by watching + * for destruction of NMAuthManager). + * - if that doesn't happen within timeout, issue nm_auth_manager_force_shutdown() and + * wait longer. After that, soon the instance should be destroyed and you + * did a successful shutdown. + * - if the instance was still not destroyed within a short timeout, you leaked + * resources. You cannot properly shutdown. + */ + + priv->shutting_down = TRUE; + nm_clear_g_cancellable(&priv->main_cancellable); +} + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(object); + int v_int; + + switch (prop_id) { + case PROP_POLKIT_ENABLED: + /* construct-only */ + v_int = g_value_get_int(value); + g_return_if_fail(NM_IN_SET(v_int, + NM_AUTH_POLKIT_MODE_ROOT_ONLY, + NM_AUTH_POLKIT_MODE_ALLOW_ALL, + NM_AUTH_POLKIT_MODE_USE_POLKIT)); + priv->auth_polkit_mode = v_int; + nm_assert(priv->auth_polkit_mode == v_int); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_auth_manager_init(NMAuthManager *self) +{ + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + + c_list_init(&priv->calls_lst_head); + priv->auth_polkit_mode = NM_AUTH_POLKIT_MODE_ROOT_ONLY; +} + +static void +constructed(GObject *object) +{ + NMAuthManager * self = NM_AUTH_MANAGER(object); + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + NMLogLevel logl = LOGL_DEBUG; + const char * create_message; + + G_OBJECT_CLASS(nm_auth_manager_parent_class)->constructed(object); + + if (priv->auth_polkit_mode != NM_AUTH_POLKIT_MODE_USE_POLKIT) { + if (priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ROOT_ONLY) + create_message = "polkit disabled, root-only"; + else + create_message = "polkit disabled, allow-all"; + goto out; + } + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!priv->dbus_connection) { + /* This warrants an info level message. */ + logl = LOGL_INFO; + create_message = + "D-Bus connection not available. Polkit is disabled and only root will be authorized."; + priv->auth_polkit_mode = NM_AUTH_POLKIT_MODE_ROOT_ONLY; + goto out; + } + + priv->main_cancellable = g_cancellable_new(); + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + POLKIT_SERVICE, + _name_owner_changed_cb, + self, + NULL); + + priv->changed_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + POLKIT_SERVICE, + POLKIT_INTERFACE, + "Changed", + POLKIT_OBJECT_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + changed_signal_cb, + self, + NULL); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + POLKIT_SERVICE, + -1, + priv->main_cancellable, + _name_owner_get_cb, + self); + + create_message = "polkit enabled"; + +out: + _NMLOG(logl, "create auth-manager: %s", create_message); +} + +NMAuthManager * +nm_auth_manager_setup(NMAuthPolkitMode auth_polkit_mode) +{ + NMAuthManager *self; + + g_return_val_if_fail(!singleton_instance, singleton_instance); + nm_assert(NM_IN_SET(auth_polkit_mode, + NM_AUTH_POLKIT_MODE_ROOT_ONLY, + NM_AUTH_POLKIT_MODE_ALLOW_ALL, + NM_AUTH_POLKIT_MODE_USE_POLKIT)); + + self = g_object_new(NM_TYPE_AUTH_MANAGER, + NM_AUTH_MANAGER_POLKIT_ENABLED, + (int) auth_polkit_mode, + NULL); + _LOGD("set instance"); + + singleton_instance = self; + nm_singleton_instance_register(); + + nm_log_dbg(LOGD_CORE, + "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + "NMAuthManager", + NM_HASH_OBFUSCATE_PTR(singleton_instance)); + + return self; +} + +static void +dispose(GObject *object) +{ + NMAuthManager * self = NM_AUTH_MANAGER(object); + NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); + + _LOGD("dispose"); + + nm_assert(c_list_is_empty(&priv->calls_lst_head)); + + priv->disposing = TRUE; + + nm_clear_g_cancellable(&priv->main_cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->changed_id); + + G_OBJECT_CLASS(nm_auth_manager_parent_class)->dispose(object); + + g_clear_object(&priv->dbus_connection); + + nm_clear_g_free(&priv->name_owner); +} + +static void +nm_auth_manager_class_init(NMAuthManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + + obj_properties[PROP_POLKIT_ENABLED] = + g_param_spec_int(NM_AUTH_MANAGER_POLKIT_ENABLED, + "", + "", + NM_AUTH_POLKIT_MODE_ROOT_ONLY, + NM_AUTH_POLKIT_MODE_USE_POLKIT, + NM_AUTH_POLKIT_MODE_USE_POLKIT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[CHANGED_SIGNAL] = g_signal_new(NM_AUTH_MANAGER_SIGNAL_CHANGED, + NM_TYPE_AUTH_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/nm-auth-manager.h b/src/core/nm-auth-manager.h new file mode 100644 index 0000000..991083c --- /dev/null +++ b/src/core/nm-auth-manager.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef NM_AUTH_MANAGER_H +#define NM_AUTH_MANAGER_H + +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-config-data.h" + +/*****************************************************************************/ + +typedef enum { + NM_AUTH_CALL_RESULT_UNKNOWN = NM_CLIENT_PERMISSION_RESULT_UNKNOWN, + NM_AUTH_CALL_RESULT_YES = NM_CLIENT_PERMISSION_RESULT_YES, + NM_AUTH_CALL_RESULT_AUTH = NM_CLIENT_PERMISSION_RESULT_AUTH, + NM_AUTH_CALL_RESULT_NO = NM_CLIENT_PERMISSION_RESULT_NO, +} NMAuthCallResult; + +static inline NMClientPermissionResult +nm_auth_call_result_to_client(NMAuthCallResult result) +{ + return (NMClientPermissionResult) result; +} + +static inline NMAuthCallResult +nm_auth_call_result_eval(gboolean is_authorized, gboolean is_challenge, GError *error) +{ + if (error) + return NM_AUTH_CALL_RESULT_UNKNOWN; + if (is_authorized) + return NM_AUTH_CALL_RESULT_YES; + if (is_challenge) + return NM_AUTH_CALL_RESULT_AUTH; + return NM_AUTH_CALL_RESULT_NO; +} + +/*****************************************************************************/ + +#define NM_TYPE_AUTH_MANAGER (nm_auth_manager_get_type()) +#define NM_AUTH_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_AUTH_MANAGER, NMAuthManager)) +#define NM_AUTH_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_AUTH_MANAGER, NMAuthManagerClass)) +#define NM_IS_AUTH_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_AUTH_MANAGER)) +#define NM_IS_AUTH_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_AUTH_MANAGER)) +#define NM_AUTH_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_AUTH_MANAGER, NMAuthManagerClass)) + +#define NM_AUTH_MANAGER_POLKIT_ENABLED "polkit-enabled" + +#define NM_AUTH_MANAGER_SIGNAL_CHANGED "changed" + +typedef struct _NMAuthManager NMAuthManager; +typedef struct _NMAuthManagerClass NMAuthManagerClass; + +GType nm_auth_manager_get_type(void); + +NMAuthManager *nm_auth_manager_setup(NMAuthPolkitMode auth_polkit_mode); +NMAuthManager *nm_auth_manager_get(void); + +void nm_auth_manager_force_shutdown(NMAuthManager *self); + +gboolean nm_auth_manager_get_polkit_enabled(NMAuthManager *self); + +/*****************************************************************************/ + +typedef struct _NMAuthManagerCallId NMAuthManagerCallId; + +typedef void (*NMAuthManagerCheckAuthorizationCallback)(NMAuthManager * self, + NMAuthManagerCallId *call_id, + gboolean is_authorized, + gboolean is_challenge, + GError * error, + gpointer user_data); + +NMAuthManagerCallId * +nm_auth_manager_check_authorization(NMAuthManager * self, + NMAuthSubject * subject, + const char * action_id, + gboolean allow_user_interaction, + NMAuthManagerCheckAuthorizationCallback callback, + gpointer user_data); + +void nm_auth_manager_check_authorization_cancel(NMAuthManagerCallId *call_id); + +#endif /* NM_AUTH_MANAGER_H */ diff --git a/src/core/nm-auth-utils.c b/src/core/nm-auth-utils.c new file mode 100644 index 0000000..41ddd3a --- /dev/null +++ b/src/core/nm-auth-utils.c @@ -0,0 +1,696 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-auth-utils.h" + +#include "nm-glib-aux/nm-c-list.h" +#include "nm-setting-connection.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-auth-manager.h" +#include "nm-session-monitor.h" +#include "nm-dbus-manager.h" +#include "nm-core-utils.h" + +/*****************************************************************************/ + +typedef struct { + const char * tag; + gpointer data; + GDestroyNotify destroy; +} ChainData; + +struct _NMAuthChain { + CList parent_lst; + + ChainData *data_arr; + guint data_len; + guint data_alloc; + + CList auth_call_lst_head; + + GDBusMethodInvocation *context; + NMAuthSubject * subject; + + GCancellable *cancellable; + + /* if set, it also means that the chain is already started and was cancelled. */ + GSource *cancellable_idle_source; + + NMAuthChainResultFunc done_func; + gpointer user_data; + + gulong cancellable_id; + + guint num_pending_auth_calls; + + bool is_started : 1; + bool is_destroyed : 1; + bool is_finishing : 1; +}; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMAuthChain, parent_lst) == 0); + +typedef struct { + CList auth_call_lst; + NMAuthChain * chain; + NMAuthManagerCallId *call_id; + const char * permission; + NMAuthCallResult result; +} AuthCall; + +/*****************************************************************************/ + +static void _auth_chain_destroy(NMAuthChain *self); + +/*****************************************************************************/ + +static void +_ASSERT_call(AuthCall *call) +{ + nm_assert(call); + nm_assert(call->chain); + nm_assert(call->permission && strlen(call->permission) > 0); + nm_assert(nm_c_list_contains_entry(&call->chain->auth_call_lst_head, call, auth_call_lst)); +#if NM_MORE_ASSERTS > 5 + { + AuthCall *auth_call; + guint n = 0; + + c_list_for_each_entry (auth_call, &call->chain->auth_call_lst_head, auth_call_lst) { + nm_assert(auth_call->result == NM_AUTH_CALL_RESULT_UNKNOWN || !auth_call->call_id); + if (auth_call->call_id) + n++; + } + nm_assert(n == call->chain->num_pending_auth_calls); + } +#endif +} + +/*****************************************************************************/ + +static void +_done_and_destroy(NMAuthChain *self) +{ + self->is_finishing = TRUE; + self->done_func(self, self->context, self->user_data); + nm_assert(self->is_finishing); + _auth_chain_destroy(self); +} + +static gboolean +_cancellable_idle_cb(gpointer user_data) +{ + NMAuthChain *self = user_data; + AuthCall * call; + + nm_assert(g_cancellable_is_cancelled(self->cancellable)); + nm_assert(self->cancellable_idle_source); + + c_list_for_each_entry (call, &self->auth_call_lst_head, auth_call_lst) { + if (call->call_id) { + self->num_pending_auth_calls--; + nm_auth_manager_check_authorization_cancel(g_steal_pointer(&call->call_id)); + } + } + + _done_and_destroy(self); + return G_SOURCE_REMOVE; +} + +static void +_cancellable_on_idle(NMAuthChain *self) +{ + if (self->cancellable_idle_source) + return; + + self->cancellable_idle_source = + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _cancellable_idle_cb, self, NULL); + g_source_attach(self->cancellable_idle_source, NULL); +} + +GCancellable * +nm_auth_chain_get_cancellable(NMAuthChain *self) +{ + return self->cancellable; +} + +static void +_cancellable_cancelled(GCancellable *cancellable, NMAuthChain *self) +{ + _cancellable_on_idle(self); +} + +void +nm_auth_chain_set_cancellable(NMAuthChain *self, GCancellable *cancellable) +{ + g_return_if_fail(self); + g_return_if_fail(G_IS_CANCELLABLE(cancellable)); + + /* after the chain is started, the cancellable can no longer be changed. + * No need to handle the complexity of swapping the cancellable *after* + * requests are already started. */ + g_return_if_fail(!self->is_started); + nm_assert(c_list_is_empty(&self->auth_call_lst_head)); + + /* also no need to allow setting different cancellables. */ + g_return_if_fail(!self->cancellable); + + self->cancellable = g_object_ref(cancellable); +} + +/*****************************************************************************/ + +static void +auth_call_free(AuthCall *call) +{ + _ASSERT_call(call); + + c_list_unlink_stale(&call->auth_call_lst); + if (call->call_id) { + call->chain->num_pending_auth_calls--; + nm_auth_manager_check_authorization_cancel(call->call_id); + } + nm_g_slice_free(call); +} + +static AuthCall * +_find_auth_call(NMAuthChain *self, const char *permission) +{ + AuthCall *auth_call; + + c_list_for_each_entry (auth_call, &self->auth_call_lst_head, auth_call_lst) { + if (nm_streq(auth_call->permission, permission)) + return auth_call; + } + return NULL; +} + +/*****************************************************************************/ + +static ChainData * +_get_data(NMAuthChain *self, const char *tag) +{ + guint i; + + for (i = 0; i < self->data_len; i++) { + ChainData *chain_data = &self->data_arr[i]; + + if (chain_data->tag && nm_streq(chain_data->tag, tag)) + return chain_data; + } + return NULL; +} + +gpointer +nm_auth_chain_get_data(NMAuthChain *self, const char *tag) +{ + ChainData *chain_data; + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(tag, NULL); + + chain_data = _get_data(self, tag); + return chain_data ? chain_data->data : NULL; +} + +/** + * nm_auth_chain_steal_data: + * @self: A #NMAuthChain. + * @tag: A "tag" uniquely identifying the data to steal. + * + * Removes the datum associated with @tag from the chain's data associations, + * without invoking the association's destroy handler. The caller assumes + * ownership over the returned value. + * + * Returns: the datum originally associated with @tag + */ +gpointer +nm_auth_chain_steal_data(NMAuthChain *self, const char *tag) +{ + ChainData *chain_data; + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(tag, NULL); + + chain_data = _get_data(self, tag); + if (!chain_data) + return NULL; + + /* Make sure the destroy handler isn't called when freeing. + * + * We don't bother to really remove the element from the array. + * Just mark the entry as unused by clearing the tag. */ + chain_data->destroy = NULL; + chain_data->tag = NULL; + return chain_data->data; +} + +/** + * nm_auth_chain_set_data_unsafe: + * @self: the #NMAuthChain + * @tag: the tag for referencing the attached data. + * @data: the data to attach. If %NULL, this call has no effect + * and nothing is attached. + * @data_destroy: (allow-none): the destroy function for the data pointer. + * + * @tag string is not cloned and must outlive @self. That is why + * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal + * instead. + * + * It is a bug to add the same tag more than once. + */ +void +nm_auth_chain_set_data_unsafe(NMAuthChain * self, + const char * tag, + gpointer data, + GDestroyNotify data_destroy) +{ + ChainData *chain_data; + + g_return_if_fail(self); + g_return_if_fail(tag); + + /* The tag must not yet exist. Otherwise, we'd have to first search the + * list for an existing entry. That usage pattern is not supported. */ + nm_assert(!_get_data(self, tag)); + + if (!data) { + /* we don't track user data of %NULL. + * + * In the past this had also the meaning of removing a user-data. But since + * nm_auth_chain_set_data() does not allow being called more than once + * for the same tag, we don't need to remove anything. */ + return; + } + + if (self->data_len + 1 > self->data_alloc) { + if (self->data_alloc == 0) + self->data_alloc = 8; + else + self->data_alloc *= 2; + self->data_arr = g_realloc(self->data_arr, sizeof(self->data_arr[0]) * self->data_alloc); + } + + chain_data = &self->data_arr[self->data_len++]; + *chain_data = (ChainData){ + .tag = tag, + .data = data, + .destroy = data_destroy, + }; +} + +/*****************************************************************************/ + +NMAuthCallResult +nm_auth_chain_get_result(NMAuthChain *self, const char *permission) +{ + AuthCall *auth_call; + + g_return_val_if_fail(self, NM_AUTH_CALL_RESULT_UNKNOWN); + g_return_val_if_fail(permission, NM_AUTH_CALL_RESULT_UNKNOWN); + + /* it is a bug to request the result other than from the done_func() + * callback. You are not supposed to poll for the result but request + * it upon notification. */ + nm_assert(self->is_finishing); + + auth_call = _find_auth_call(self, permission); + + /* it is a bug to request a permission result that was not + * previously requested or which did not complete yet. */ + if (!auth_call) + g_return_val_if_reached(NM_AUTH_CALL_RESULT_UNKNOWN); + + nm_assert(!auth_call->call_id); + + if (self->cancellable_idle_source) { + /* already cancelled. We always return unknown (even if we happen to + * have already received the response. */ + return NM_AUTH_CALL_RESULT_UNKNOWN; + } + + return auth_call->result; +} + +NMAuthSubject * +nm_auth_chain_get_subject(NMAuthChain *self) +{ + g_return_val_if_fail(self, NULL); + + return self->subject; +} + +GDBusMethodInvocation * +nm_auth_chain_get_context(NMAuthChain *self) +{ + g_return_val_if_fail(self, NULL); + + return self->context; +} + +/*****************************************************************************/ + +static void +pk_call_cb(NMAuthManager * auth_manager, + NMAuthManagerCallId *call_id, + gboolean is_authorized, + gboolean is_challenge, + GError * error, + gpointer user_data) +{ + NMAuthChain *self; + AuthCall * call; + + nm_assert(call_id); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + call = user_data; + + _ASSERT_call(call); + nm_assert(call->call_id == call_id); + nm_assert(call->result == NM_AUTH_CALL_RESULT_UNKNOWN); + + self = call->chain; + + nm_assert(!self->is_destroyed); + nm_assert(!self->is_finishing); + + call->call_id = NULL; + + call->result = nm_auth_call_result_eval(is_authorized, is_challenge, error); + + call->chain->num_pending_auth_calls--; + + _ASSERT_call(call); + + if (call->chain->num_pending_auth_calls == 0) { + /* we are on an idle-handler or a clean call-stack (non-reentrant) so it's safe + * to invoke the callback right away. */ + _done_and_destroy(self); + } +} + +/** + * nm_auth_chain_add_call_unsafe: + * @self: the #NMAuthChain + * @permission: the permission string. This string is kept by reference + * and you must make sure that it's lifetime lasts until the NMAuthChain + * gets destroyed. That's why the function is "unsafe". Use + * nm_auth_chain_add_call() instead. + * @allow_interaction: flag + * + * It's "unsafe" because @permission is not copied. It's the callers responsibility + * that the permission string stays valid as long as NMAuthChain. + * + * If you can, use nm_auth_chain_add_call() instead! + * + * If you have a non-static string, you may attach the permission string as + * user-data via nm_auth_chain_set_data(). + */ +void +nm_auth_chain_add_call_unsafe(NMAuthChain *self, const char *permission, gboolean allow_interaction) +{ + AuthCall *call; + + g_return_if_fail(self); + g_return_if_fail(self->subject); + g_return_if_fail(!self->is_finishing); + g_return_if_fail(!self->is_destroyed); + g_return_if_fail(permission && *permission); + nm_assert(NM_IN_SET(nm_auth_subject_get_subject_type(self->subject), + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); + + /* duplicate permissions are not supported, also because nm_auth_chain_get_result() + * can only return one-permission. */ + nm_assert(!_find_auth_call(self, permission)); + + if (!self->is_started) { + self->is_started = TRUE; + nm_assert(!self->cancellable_id); + if (self->cancellable) { + if (g_cancellable_is_cancelled(self->cancellable)) { + /* the operation is already cancelled. Schedule the callback on idle. */ + _cancellable_on_idle(self); + } else { + self->cancellable_id = g_signal_connect(self->cancellable, + "cancelled", + G_CALLBACK(_cancellable_cancelled), + self); + } + } + } + + call = g_slice_new(AuthCall); + *call = (AuthCall){ + .chain = self, + .call_id = NULL, + .result = NM_AUTH_CALL_RESULT_UNKNOWN, + + /* we don't clone the permission string. It's the callers responsibility. */ + .permission = permission, + }; + + /* above we assert that no duplicate permissions are added. Still, track the + * new request to the front of the list so that it would shadow an earlier + * call. */ + c_list_link_front(&self->auth_call_lst_head, &call->auth_call_lst); + + if (self->cancellable_idle_source) { + /* already cancelled. No need to actually start the request. */ + nm_assert(call->result == NM_AUTH_CALL_RESULT_UNKNOWN); + } else { + call->call_id = nm_auth_manager_check_authorization(nm_auth_manager_get(), + self->subject, + permission, + allow_interaction, + pk_call_cb, + call); + + self->num_pending_auth_calls++; + } + + _ASSERT_call(call); + + /* we track auth-calls in a linked list. If we end up requesting too many permissions this + * becomes inefficient. If that ever happens, consider a more efficient data structure for + * a large number of requests. */ + nm_assert(c_list_length(&self->auth_call_lst_head) < 25); + G_STATIC_ASSERT_EXPR(NM_CLIENT_PERMISSION_LAST < 25); +} + +/*****************************************************************************/ + +/* Creates the NMAuthSubject automatically */ +NMAuthChain * +nm_auth_chain_new_context(GDBusMethodInvocation *context, + NMAuthChainResultFunc done_func, + gpointer user_data) +{ + NMAuthSubject *subject; + NMAuthChain * chain; + + g_return_val_if_fail(context, NULL); + nm_assert(done_func); + + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) + return NULL; + + chain = nm_auth_chain_new_subject(subject, context, done_func, user_data); + g_object_unref(subject); + return chain; +} + +NMAuthChain * +nm_auth_chain_new_subject(NMAuthSubject * subject, + GDBusMethodInvocation *context, + NMAuthChainResultFunc done_func, + gpointer user_data) +{ + NMAuthChain *self; + + g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL); + nm_assert(NM_IN_SET(nm_auth_subject_get_subject_type(subject), + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); + nm_assert(done_func); + + self = g_slice_new(NMAuthChain); + *self = (NMAuthChain){ + .done_func = done_func, + .user_data = user_data, + .context = nm_g_object_ref(context), + .subject = g_object_ref(subject), + .parent_lst = C_LIST_INIT(self->parent_lst), + .auth_call_lst_head = C_LIST_INIT(self->auth_call_lst_head), + }; + return self; +} + +/** + * nm_auth_chain_destroy: + * @self: the auth-chain + * + * Destroys the auth-chain. By destroying the auth-chain, you also cancel + * the receipt of the done-callback. IOW, the callback will not be invoked. + * + * The only exception is, you may call nm_auth_chain_destroy() from inside + * the callback. In this case the call has no effect and @self stays alive + * until the callback returns. + * + * Note that you might only destroy an auth-chain exactly once, and never + * after the callback was handled. After the callback returns, the auth chain + * always gets automatically destroyed. So you only need to explicitly destroy + * it, if you want to abort it before the callback complets. + */ +void +nm_auth_chain_destroy(NMAuthChain *self) +{ + g_return_if_fail(self); + g_return_if_fail(!self->is_destroyed); + + self->is_destroyed = TRUE; + + if (self->is_finishing) { + /* we are called from inside the callback. Keep the instance alive for the moment. */ + return; + } + + _auth_chain_destroy(self); +} + +static void +_auth_chain_destroy(NMAuthChain *self) +{ + AuthCall *call; + + c_list_unlink(&self->parent_lst); + + nm_clear_g_object(&self->subject); + nm_clear_g_object(&self->context); + + nm_clear_g_signal_handler(self->cancellable, &self->cancellable_id); + nm_clear_g_source_inst(&self->cancellable_idle_source); + + /* we must first destroy all AuthCall instances before ChainData. The reason is + * that AuthData.permission is not cloned and the lifetime of the string must + * be ensured by the caller. A sensible thing to do for the caller is attach the + * permission string via nm_auth_chain_set_data(). Hence, first free the AuthCall. */ + while ((call = c_list_first_entry(&self->auth_call_lst_head, AuthCall, auth_call_lst))) + auth_call_free(call); + + while (self->data_len > 0) { + ChainData *chain_data = &self->data_arr[--self->data_len]; + + if (chain_data->destroy) + chain_data->destroy(chain_data->data); + } + g_free(self->data_arr); + + nm_g_object_unref(self->cancellable); + + nm_g_slice_free(self); +} + +/****************************************************************************** + * utils + *****************************************************************************/ + +gboolean +nm_auth_is_subject_in_acl(NMConnection *connection, NMAuthSubject *subject, char **out_error_desc) +{ + NMSettingConnection *s_con; + gs_free char * user = NULL; + gulong uid; + + g_return_val_if_fail(connection, FALSE); + g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), FALSE); + nm_assert(NM_IN_SET(nm_auth_subject_get_subject_type(subject), + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); + + if (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL) + return TRUE; + + uid = nm_auth_subject_get_unix_process_uid(subject); + + /* Root gets a free pass */ + if (0 == uid) + return TRUE; + + user = nm_utils_uid_to_name(uid); + if (!user) { + NM_SET_OUT(out_error_desc, + g_strdup_printf("Could not determine username for uid %lu", uid)); + return FALSE; + } + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + /* This can only happen when called from AddAndActivate, so we know + * the user will be authorized when the connection is completed. + */ + return TRUE; + } + + /* Match the username returned by the session check to a user in the ACL */ + if (!nm_setting_connection_permissions_user_allowed(s_con, user)) { + NM_SET_OUT(out_error_desc, + g_strdup_printf("uid %lu has no permission to perform this operation", uid)); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_auth_is_subject_in_acl_set_error(NMConnection * connection, + NMAuthSubject *subject, + GQuark err_domain, + int err_code, + GError ** error) +{ + char *error_desc = NULL; + + nm_assert(!error || !*error); + + if (nm_auth_is_subject_in_acl(connection, subject, error ? &error_desc : NULL)) + return TRUE; + + g_set_error_literal(error, err_domain, err_code, error_desc); + g_free(error_desc); + return FALSE; +} + +gboolean +nm_auth_is_invocation_in_acl_set_error(NMConnection * connection, + GDBusMethodInvocation *invocation, + GQuark err_domain, + int err_code, + NMAuthSubject ** out_subject, + GError ** error) +{ + gs_unref_object NMAuthSubject *subject = NULL; + gboolean success; + + nm_assert(!out_subject || !*out_subject); + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + g_set_error_literal(error, err_domain, err_code, NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + return FALSE; + } + + success = nm_auth_is_subject_in_acl_set_error(connection, subject, err_domain, err_code, error); + + NM_SET_OUT(out_subject, g_steal_pointer(&subject)); + + return success; +} diff --git a/src/core/nm-auth-utils.h b/src/core/nm-auth-utils.h new file mode 100644 index 0000000..999dacf --- /dev/null +++ b/src/core/nm-auth-utils.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_MANAGER_AUTH_H__ +#define __NETWORKMANAGER_MANAGER_AUTH_H__ + +#include "nm-connection.h" + +#include "nm-auth-manager.h" + +/*****************************************************************************/ + +typedef struct _NMAuthChain NMAuthChain; + +typedef void (*NMAuthChainResultFunc)(NMAuthChain * chain, + GDBusMethodInvocation *context, + gpointer user_data); + +NMAuthChain *nm_auth_chain_new_context(GDBusMethodInvocation *context, + NMAuthChainResultFunc done_func, + gpointer user_data); + +NMAuthChain *nm_auth_chain_new_subject(NMAuthSubject * subject, + GDBusMethodInvocation *context, + NMAuthChainResultFunc done_func, + gpointer user_data); + +GCancellable *nm_auth_chain_get_cancellable(NMAuthChain *self); +void nm_auth_chain_set_cancellable(NMAuthChain *self, GCancellable *cancellable); + +gpointer nm_auth_chain_get_data(NMAuthChain *chain, const char *tag); + +gpointer nm_auth_chain_steal_data(NMAuthChain *chain, const char *tag); + +void nm_auth_chain_set_data_unsafe(NMAuthChain * chain, + const char * tag, + gpointer data, + GDestroyNotify data_destroy); + +#define nm_auth_chain_set_data(chain, tag, data, data_destroy) \ + nm_auth_chain_set_data_unsafe((chain), "" tag "", (data), (data_destroy)) + +NMAuthCallResult nm_auth_chain_get_result(NMAuthChain *chain, const char *permission); + +void nm_auth_chain_add_call_unsafe(NMAuthChain *chain, + const char * permission, + gboolean allow_interaction); + +#define nm_auth_chain_add_call(chain, permission, allow_interaction) \ + nm_auth_chain_add_call_unsafe((chain), "" permission "", (allow_interaction)) + +void nm_auth_chain_destroy(NMAuthChain *chain); + +NMAuthSubject *nm_auth_chain_get_subject(NMAuthChain *self); + +GDBusMethodInvocation *nm_auth_chain_get_context(NMAuthChain *self); + +/*****************************************************************************/ + +struct CList; + +static inline NMAuthChain * +nm_auth_chain_parent_lst_entry(struct CList *parent_lst_self) +{ + return (NMAuthChain *) ((void *) parent_lst_self); +} + +static inline struct CList * +nm_auth_chain_parent_lst_list(NMAuthChain *self) +{ + return (struct CList *) ((void *) self); +} + +/*****************************************************************************/ + +/* Caller must free returned error description */ +gboolean +nm_auth_is_subject_in_acl(NMConnection *connection, NMAuthSubject *subject, char **out_error_desc); + +gboolean nm_auth_is_subject_in_acl_set_error(NMConnection * connection, + NMAuthSubject *subject, + GQuark err_domain, + int err_code, + GError ** error); + +gboolean nm_auth_is_invocation_in_acl_set_error(NMConnection * connection, + GDBusMethodInvocation *invocation, + GQuark err_domain, + int err_code, + NMAuthSubject ** out_subject, + GError ** error); + +#endif /* __NETWORKMANAGER_MANAGER_AUTH_H__ */ diff --git a/src/core/nm-checkpoint-manager.c b/src/core/nm-checkpoint-manager.c new file mode 100644 index 0000000..b4c00f9 --- /dev/null +++ b/src/core/nm-checkpoint-manager.c @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-checkpoint-manager.h" + +#include "nm-checkpoint.h" +#include "nm-connection.h" +#include "nm-core-utils.h" +#include "devices/nm-device.h" +#include "nm-manager.h" +#include "nm-utils.h" +#include "c-list/src/c-list.h" + +/*****************************************************************************/ + +struct _NMCheckpointManager { + NMManager * _manager; + GParamSpec *property_spec; + CList checkpoints_lst_head; +}; + +#define GET_MANAGER(self) \ + ({ \ + typeof(self) _self = (self); \ + \ + _nm_unused NMCheckpointManager *_self2 = _self; \ + \ + nm_assert(_self); \ + nm_assert(NM_IS_MANAGER(_self->_manager)); \ + _self->_manager; \ + }) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "checkpoint", __VA_ARGS__) + +/*****************************************************************************/ + +static void +notify_checkpoints(NMCheckpointManager *self) +{ + g_object_notify_by_pspec((GObject *) GET_MANAGER(self), self->property_spec); +} + +static void +destroy_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy) +{ + nm_assert(NM_IS_CHECKPOINT(checkpoint)); + nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(checkpoint))); + nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst)); + + nm_checkpoint_set_timeout_callback(checkpoint, NULL, NULL); + + c_list_unlink(&checkpoint->checkpoints_lst); + + if (log_destroy) + nm_checkpoint_log_destroy(checkpoint); + + notify_checkpoints(self); + + nm_dbus_object_unexport(NM_DBUS_OBJECT(checkpoint)); + g_object_unref(checkpoint); +} + +static GVariant * +rollback_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint) +{ + GVariant * result; + const CList *iter; + + nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst)); + + /* we destroy first all overlapping checkpoints that are younger/newer. */ + for (iter = checkpoint->checkpoints_lst.next; iter != &self->checkpoints_lst_head;) { + NMCheckpoint *cp = c_list_entry(iter, NMCheckpoint, checkpoints_lst); + + iter = iter->next; + if (nm_checkpoint_includes_devices_of(cp, checkpoint)) { + /* the younger checkpoint has overlapping devices and gets obsoleted. + * Destroy it. */ + destroy_checkpoint(self, cp, TRUE); + } + } + + result = nm_checkpoint_rollback(checkpoint); + destroy_checkpoint(self, checkpoint, FALSE); + return result; +} + +static void +rollback_timeout_cb(NMCheckpoint *checkpoint, gpointer user_data) +{ + NMCheckpointManager *self = user_data; + gs_unref_variant GVariant *result = NULL; + + result = rollback_checkpoint(self, checkpoint); +} + +NMCheckpoint * +nm_checkpoint_manager_create(NMCheckpointManager * self, + const char *const * device_paths, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GError ** error) +{ + NMManager * manager; + NMCheckpoint * checkpoint; + gs_unref_ptrarray GPtrArray *devices = NULL; + NMDevice * device; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + manager = GET_MANAGER(self); + + devices = g_ptr_array_new(); + + if (!device_paths || !device_paths[0]) { + const CList *tmp_lst; + + nm_manager_for_each_device (manager, device, tmp_lst) { + /* FIXME: there is no strong reason to skip over unrealized devices. + * Also, NMCheckpoint anticipates to handle them (in parts). */ + if (!nm_device_is_real(device)) + continue; + nm_assert(nm_dbus_object_get_path(NM_DBUS_OBJECT(device))); + g_ptr_array_add(devices, device); + } + } else if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) { + g_set_error_literal( + error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "the DISCONNECT_NEW_DEVICES flag can only be used with an empty device list"); + return NULL; + } else { + for (; *device_paths; device_paths++) { + device = nm_manager_get_device_by_path(manager, *device_paths); + if (!device) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "device %s does not exist", + *device_paths); + return NULL; + } + if (!nm_device_is_real(device)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "device %s is not realized", + *device_paths); + return NULL; + } + g_ptr_array_add(devices, device); + } + } + + if (!devices->len) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "no device available"); + return NULL; + } + + if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL)) + nm_checkpoint_manager_destroy_all(self); + else if (!NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_ALLOW_OVERLAPPING)) { + c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst) { + device = nm_checkpoint_includes_devices(checkpoint, + (NMDevice *const *) devices->pdata, + devices->len); + if (device) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "device '%s' is already included in checkpoint %s", + nm_device_get_iface(device), + nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint))); + return NULL; + } + } + } + + checkpoint = nm_checkpoint_new(manager, devices, rollback_timeout, flags); + + nm_dbus_object_export(NM_DBUS_OBJECT(checkpoint)); + + nm_checkpoint_set_timeout_callback(checkpoint, rollback_timeout_cb, self); + c_list_link_tail(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst); + notify_checkpoints(self); + return checkpoint; +} + +void +nm_checkpoint_manager_destroy_all(NMCheckpointManager *self) +{ + NMCheckpoint *checkpoint; + + g_return_if_fail(self); + + while ((checkpoint = + c_list_first_entry(&self->checkpoints_lst_head, NMCheckpoint, checkpoints_lst))) + destroy_checkpoint(self, checkpoint, TRUE); +} + +gboolean +nm_checkpoint_manager_destroy(NMCheckpointManager *self, const char *path, GError **error) +{ + NMCheckpoint *checkpoint; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(path && path[0] == '/', FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (!nm_dbus_path_not_empty(path)) { + nm_checkpoint_manager_destroy_all(self); + return TRUE; + } + + checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error); + if (!checkpoint) + return FALSE; + + destroy_checkpoint(self, checkpoint, TRUE); + return TRUE; +} + +gboolean +nm_checkpoint_manager_rollback(NMCheckpointManager *self, + const char * path, + GVariant ** results, + GError ** error) +{ + NMCheckpoint *checkpoint; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(path && path[0] == '/', FALSE); + g_return_val_if_fail(results, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error); + if (!checkpoint) + return FALSE; + + *results = rollback_checkpoint(self, checkpoint); + return TRUE; +} + +NMCheckpoint * +nm_checkpoint_manager_lookup_by_path(NMCheckpointManager *self, const char *path, GError **error) +{ + NMCheckpoint *checkpoint; + + g_return_val_if_fail(self, NULL); + + checkpoint = + nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(GET_MANAGER(self))), + path); + if (!checkpoint || !NM_IS_CHECKPOINT(checkpoint)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "checkpoint %s does not exist", + path); + return NULL; + } + + nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst)); + return checkpoint; +} + +const char ** +nm_checkpoint_manager_get_checkpoint_paths(NMCheckpointManager *self, guint *out_length) +{ + NMCheckpoint *checkpoint; + const char ** strv; + guint num, i = 0; + + num = c_list_length(&self->checkpoints_lst_head); + NM_SET_OUT(out_length, num); + if (!num) + return NULL; + + strv = g_new(const char *, num + 1); + c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst) + strv[i++] = nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint)); + nm_assert(i == num); + strv[i] = NULL; + return strv; +} + +gboolean +nm_checkpoint_manager_adjust_rollback_timeout(NMCheckpointManager *self, + const char * path, + guint32 add_timeout, + GError ** error) +{ + NMCheckpoint *checkpoint; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(path && path[0] == '/', FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error); + if (!checkpoint) + return FALSE; + + nm_checkpoint_adjust_rollback_timeout(checkpoint, add_timeout); + return TRUE; +} + +/*****************************************************************************/ + +NMCheckpointManager * +nm_checkpoint_manager_new(NMManager *manager, GParamSpec *spec) +{ + NMCheckpointManager *self; + + g_return_val_if_fail(NM_IS_MANAGER(manager), FALSE); + + self = g_slice_new0(NMCheckpointManager); + + /* the NMCheckpointManager instance is actually owned by NMManager. + * Thus, we cannot take a reference to it, and we also don't bother + * taking a weak-reference. Instead let GET_MANAGER() assert that + * self->_manager is alive -- which we always expect as the lifetime + * of NMManager shall surpass the lifetime of the NMCheckpointManager + * instance. */ + self->_manager = manager; + self->property_spec = spec; + c_list_init(&self->checkpoints_lst_head); + return self; +} + +void +nm_checkpoint_manager_free(NMCheckpointManager *self) +{ + if (!self) + return; + + nm_checkpoint_manager_destroy_all(self); + g_slice_free(NMCheckpointManager, self); +} diff --git a/src/core/nm-checkpoint-manager.h b/src/core/nm-checkpoint-manager.h new file mode 100644 index 0000000..7fe86d6 --- /dev/null +++ b/src/core/nm-checkpoint-manager.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_CHECKPOINT_MANAGER_H__ +#define __NM_CHECKPOINT_MANAGER_H__ + +#include "nm-dbus-interface.h" + +#include "nm-checkpoint.h" + +typedef struct _NMCheckpointManager NMCheckpointManager; + +NMCheckpointManager *nm_checkpoint_manager_new(NMManager *manager, GParamSpec *spec); + +void nm_checkpoint_manager_free(NMCheckpointManager *self); + +NMCheckpoint * +nm_checkpoint_manager_lookup_by_path(NMCheckpointManager *self, const char *path, GError **error); + +NMCheckpoint *nm_checkpoint_manager_create(NMCheckpointManager * self, + const char *const * device_names, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GError ** error); + +void nm_checkpoint_manager_destroy_all(NMCheckpointManager *self); + +gboolean nm_checkpoint_manager_destroy(NMCheckpointManager *self, const char *path, GError **error); +gboolean nm_checkpoint_manager_rollback(NMCheckpointManager *self, + const char * path, + GVariant ** results, + GError ** error); + +gboolean nm_checkpoint_manager_adjust_rollback_timeout(NMCheckpointManager *self, + const char * path, + guint32 add_timeout, + GError ** error); + +const char **nm_checkpoint_manager_get_checkpoint_paths(NMCheckpointManager *self, + guint * out_length); + +#endif /* __NM_CHECKPOINT_MANAGER_H__ */ diff --git a/src/core/nm-checkpoint.c b/src/core/nm-checkpoint.c new file mode 100644 index 0000000..0bee16e --- /dev/null +++ b/src/core/nm-checkpoint.c @@ -0,0 +1,790 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-checkpoint.h" + +#include "nm-active-connection.h" +#include "nm-act-request.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-core-utils.h" +#include "nm-dbus-interface.h" +#include "devices/nm-device.h" +#include "nm-manager.h" +#include "settings/nm-settings.h" +#include "settings/nm-settings-connection.h" +#include "nm-simple-connection.h" +#include "nm-utils.h" + +/*****************************************************************************/ + +typedef struct { + char * original_dev_path; + char * original_dev_name; + NMDeviceType dev_type; + NMDevice * device; + NMConnection * applied_connection; + NMConnection * settings_connection; + guint64 ac_version_id; + NMDeviceState state; + bool is_software : 1; + bool realized : 1; + bool activation_lifetime_bound_to_profile_visibility : 1; + NMUnmanFlagOp unmanaged_explicit; + NMActivationReason activation_reason; + gulong dev_exported_change_id; +} DeviceCheckpoint; + +NM_GOBJECT_PROPERTIES_DEFINE(NMCheckpoint, PROP_DEVICES, PROP_CREATED, PROP_ROLLBACK_TIMEOUT, ); + +struct _NMCheckpointPrivate { + /* properties */ + GHashTable *devices; + GPtrArray * removed_devices; + gint64 created_at_ms; + guint32 rollback_timeout_s; + guint timeout_id; + /* private members */ + NMManager * manager; + NMCheckpointCreateFlags flags; + GHashTable * connection_uuids; + gulong dev_removed_id; + + NMCheckpointTimeoutCallback timeout_cb; + gpointer timeout_data; +}; + +struct _NMCheckpointClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMCheckpoint, nm_checkpoint, NM_TYPE_DBUS_OBJECT) + +#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMCheckpoint, NM_IS_CHECKPOINT) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "checkpoint" +#define _NMLOG_DOMAIN LOGD_CORE + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled(level, _NMLOG_DOMAIN)) { \ + char __prefix[32]; \ + \ + if (self) \ + g_snprintf(__prefix, \ + sizeof(__prefix), \ + "%s[%p]", \ + ""_NMLOG_PREFIX_NAME \ + "", \ + (self)); \ + else \ + g_strlcpy(__prefix, _NMLOG_PREFIX_NAME, sizeof(__prefix)); \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +void +nm_checkpoint_log_destroy(NMCheckpoint *self) +{ + _LOGI("destroy %s", nm_dbus_object_get_path(NM_DBUS_OBJECT(self))); +} + +void +nm_checkpoint_set_timeout_callback(NMCheckpoint * self, + NMCheckpointTimeoutCallback callback, + gpointer user_data) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + + /* in glib world, we would have a GSignal for this. But as there + * is only one subscriber, it's simpler to just set and unset(!) + * the callback this way. */ + priv->timeout_cb = callback; + priv->timeout_data = user_data; +} + +NMDevice * +nm_checkpoint_includes_devices(NMCheckpoint *self, NMDevice *const *devices, guint n_devices) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + guint i; + + for (i = 0; i < n_devices; i++) { + if (g_hash_table_contains(priv->devices, devices[i])) + return devices[i]; + } + return NULL; +} + +NMDevice * +nm_checkpoint_includes_devices_of(NMCheckpoint *self, NMCheckpoint *cp_for_devices) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + NMCheckpointPrivate *priv2 = NM_CHECKPOINT_GET_PRIVATE(cp_for_devices); + GHashTableIter iter; + NMDevice * device; + + g_hash_table_iter_init(&iter, priv2->devices); + while (g_hash_table_iter_next(&iter, (gpointer *) &device, NULL)) { + if (g_hash_table_contains(priv->devices, device)) + return device; + } + return NULL; +} + +static NMSettingsConnection * +find_settings_connection(NMCheckpoint * self, + DeviceCheckpoint *dev_checkpoint, + gboolean * need_update, + gboolean * need_activation) +{ + NMCheckpointPrivate * priv = NM_CHECKPOINT_GET_PRIVATE(self); + NMActiveConnection * active; + NMSettingsConnection *sett_conn; + const char * uuid, *ac_uuid; + const CList * tmp_clist; + + *need_activation = FALSE; + *need_update = FALSE; + + uuid = nm_connection_get_uuid(dev_checkpoint->settings_connection); + sett_conn = nm_settings_get_connection_by_uuid(NM_SETTINGS_GET, uuid); + + if (!sett_conn) + return NULL; + + /* Now check if the connection changed, ... */ + if (!nm_connection_compare(dev_checkpoint->settings_connection, + nm_settings_connection_get_connection(sett_conn), + NM_SETTING_COMPARE_FLAG_EXACT)) { + _LOGT("rollback: settings connection %s changed", uuid); + *need_update = TRUE; + *need_activation = TRUE; + } + + /* ... is active, ... */ + nm_manager_for_each_active_connection (priv->manager, active, tmp_clist) { + ac_uuid = + nm_settings_connection_get_uuid(nm_active_connection_get_settings_connection(active)); + if (nm_streq(uuid, ac_uuid)) { + _LOGT("rollback: connection %s is active", uuid); + break; + } + } + + if (!active) { + _LOGT("rollback: connection %s is not active", uuid); + *need_activation = TRUE; + return sett_conn; + } + + /* ... or if the connection was reactivated/reapplied */ + if (nm_active_connection_version_id_get(active) != dev_checkpoint->ac_version_id) { + _LOGT("rollback: active connection version id of %s changed", uuid); + *need_activation = TRUE; + } + + return sett_conn; +} + +static gboolean +restore_and_activate_connection(NMCheckpoint *self, DeviceCheckpoint *dev_checkpoint) +{ + NMCheckpointPrivate * priv = NM_CHECKPOINT_GET_PRIVATE(self); + NMSettingsConnection *connection; + gs_unref_object NMAuthSubject * subject = NULL; + GError * local_error = NULL; + gboolean need_update, need_activation; + NMSettingsConnectionPersistMode persist_mode; + NMSettingsConnectionIntFlags sett_flags; + NMSettingsConnectionIntFlags sett_mask; + + connection = find_settings_connection(self, dev_checkpoint, &need_update, &need_activation); + + /* FIXME: we need to ensure to re-create/update the profile for the + * same settings plugin. E.g. if it was a keyfile in /run or /etc, + * it must be again. If it was previously handled by a certain settings plugin, + * so it must again. + * + * FIXME: preserve and restore the right settings flags (volatile, nm-generated). */ + sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE; + sett_mask = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE; + + if (connection) { + if (need_update) { + _LOGD("rollback: updating connection %s", nm_settings_connection_get_uuid(connection)); + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP; + nm_settings_connection_update(connection, + dev_checkpoint->settings_connection, + persist_mode, + sett_flags, + sett_mask, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, + "checkpoint-rollback", + NULL); + } + } else { + /* The connection was deleted, recreate it */ + _LOGD("rollback: adding connection %s again", + nm_connection_get_uuid(dev_checkpoint->settings_connection)); + + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + if (!nm_settings_add_connection(NM_SETTINGS_GET, + dev_checkpoint->settings_connection, + persist_mode, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + sett_flags, + &connection, + &local_error)) { + _LOGD("rollback: connection add failure: %s", local_error->message); + g_clear_error(&local_error); + return FALSE; + } + + /* If the device is software, a brand new NMDevice may have been created */ + if (dev_checkpoint->is_software && !dev_checkpoint->device) { + dev_checkpoint->device = nm_manager_get_device(priv->manager, + dev_checkpoint->original_dev_name, + dev_checkpoint->dev_type); + nm_g_object_ref(dev_checkpoint->device); + } + need_activation = TRUE; + } + + if (!dev_checkpoint->device) { + _LOGD("rollback: device cannot be restored"); + return FALSE; + } + + if (need_activation) { + _LOGD("rollback: reactivating connection %s", nm_settings_connection_get_uuid(connection)); + subject = nm_auth_subject_new_internal(); + + /* Disconnect the device if needed. This necessary because now + * the manager prevents the reactivation of the same connection by + * an internal subject. */ + if (nm_device_get_state(dev_checkpoint->device) > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_state(dev_checkpoint->device) < NM_DEVICE_STATE_DEACTIVATING) { + nm_device_state_changed(dev_checkpoint->device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_NEW_ACTIVATION); + } + + if (!nm_manager_activate_connection( + priv->manager, + connection, + dev_checkpoint->applied_connection, + NULL, + dev_checkpoint->device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + dev_checkpoint->activation_reason, + dev_checkpoint->activation_lifetime_bound_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + &local_error)) { + _LOGW("rollback: reactivation of connection %s/%s failed: %s", + nm_settings_connection_get_id(connection), + nm_settings_connection_get_uuid(connection), + local_error->message); + g_clear_error(&local_error); + return FALSE; + } + } + return TRUE; +} + +GVariant * +nm_checkpoint_rollback(NMCheckpoint *self) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + DeviceCheckpoint * dev_checkpoint; + GHashTableIter iter; + NMDevice * device; + GVariantBuilder builder; + uint i; + + _LOGI("rollback of %s", nm_dbus_object_get_path(NM_DBUS_OBJECT(self))); + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{su}")); + + /* Start creating removed devices (if any and if possible) */ + if (priv->removed_devices) { + for (i = 0; i < priv->removed_devices->len; i++) { + guint32 result = NM_ROLLBACK_RESULT_OK; + + dev_checkpoint = priv->removed_devices->pdata[i]; + _LOGD("rollback: restoring removed device %s (state %d, realized %d, explicitly " + "unmanaged %d)", + dev_checkpoint->original_dev_name, + (int) dev_checkpoint->state, + dev_checkpoint->realized, + dev_checkpoint->unmanaged_explicit); + + if (dev_checkpoint->applied_connection) { + if (!restore_and_activate_connection(self, dev_checkpoint)) + result = NM_ROLLBACK_RESULT_ERR_FAILED; + } + g_variant_builder_add(&builder, "{su}", dev_checkpoint->original_dev_path, result); + } + } + + /* Start rolling-back each device */ + g_hash_table_iter_init(&iter, priv->devices); + while (g_hash_table_iter_next(&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) { + guint32 result = NM_ROLLBACK_RESULT_OK; + + _LOGD("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d)", + dev_checkpoint->original_dev_name, + (int) dev_checkpoint->state, + dev_checkpoint->realized, + dev_checkpoint->unmanaged_explicit); + + if (nm_device_is_real(device)) { + if (!dev_checkpoint->realized) { + _LOGD("rollback: device was not realized, unmanage it"); + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_USER_EXPLICIT, + TRUE, + NM_DEVICE_STATE_REASON_NOW_UNMANAGED); + goto next_dev; + } + } else { + if (dev_checkpoint->realized) { + if (dev_checkpoint->is_software) { + /* try to recreate software device */ + _LOGD("rollback: software device not realized, will re-activate"); + goto activate; + } else { + _LOGD("rollback: device is not realized"); + result = NM_ROLLBACK_RESULT_ERR_FAILED; + } + } + goto next_dev; + } + + /* Manage the device again if needed */ + if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT) + && dev_checkpoint->unmanaged_explicit != NM_UNMAN_FLAG_OP_SET_UNMANAGED) { + _LOGD("rollback: restore unmanaged user-explicit"); + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_USER_EXPLICIT, + dev_checkpoint->unmanaged_explicit, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + + if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) { + if (nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED + || dev_checkpoint->unmanaged_explicit == NM_UNMAN_FLAG_OP_SET_UNMANAGED) { + _LOGD("rollback: explicitly unmanage device"); + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_USER_EXPLICIT, + TRUE, + NM_DEVICE_STATE_REASON_NOW_UNMANAGED); + } + goto next_dev; + } + +activate: + if (dev_checkpoint->applied_connection) { + if (!restore_and_activate_connection(self, dev_checkpoint)) { + result = NM_ROLLBACK_RESULT_ERR_FAILED; + goto next_dev; + } + } else { + /* The device was initially disconnected, deactivate any existing connection */ + _LOGD("rollback: disconnecting device"); + + if (nm_device_get_state(device) > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_state(device) < NM_DEVICE_STATE_DEACTIVATING) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + } + +next_dev: + g_variant_builder_add(&builder, "{su}", dev_checkpoint->original_dev_path, result); + } + + if (NM_FLAGS_HAS(priv->flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) { + NMSettingsConnection *con; + gs_free NMSettingsConnection **list = NULL; + + g_return_val_if_fail(priv->connection_uuids, NULL); + list = nm_settings_get_connections_clone( + NM_SETTINGS_GET, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + + for (i = 0; list[i]; i++) { + con = list[i]; + if (!g_hash_table_contains(priv->connection_uuids, + nm_settings_connection_get_uuid(con))) { + _LOGD("rollback: deleting new connection %s", nm_settings_connection_get_uuid(con)); + nm_settings_connection_delete(con, FALSE); + } + } + } + + if (NM_FLAGS_HAS(priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) { + const CList * tmp_lst; + NMDeviceState state; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (g_hash_table_contains(priv->devices, device)) + continue; + state = nm_device_get_state(device); + if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) { + _LOGD("rollback: disconnecting new device %s", nm_device_get_iface(device)); + nm_device_state_changed(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + } + } + + return g_variant_new("(a{su})", &builder); +} + +static void +device_checkpoint_destroy(gpointer data) +{ + DeviceCheckpoint *dev_checkpoint = data; + + nm_clear_g_signal_handler(dev_checkpoint->device, &dev_checkpoint->dev_exported_change_id); + g_clear_object(&dev_checkpoint->applied_connection); + g_clear_object(&dev_checkpoint->settings_connection); + g_clear_object(&dev_checkpoint->device); + g_free(dev_checkpoint->original_dev_path); + g_free(dev_checkpoint->original_dev_name); + + g_slice_free(DeviceCheckpoint, dev_checkpoint); +} + +static void +_move_dev_to_removed_devices(NMDevice *device, NMCheckpoint *checkpoint) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(checkpoint); + DeviceCheckpoint * dev_checkpoint; + + g_return_if_fail(device); + + dev_checkpoint = g_hash_table_lookup(priv->devices, device); + if (!dev_checkpoint) + return; + + g_hash_table_steal(priv->devices, dev_checkpoint->device); + nm_clear_g_signal_handler(dev_checkpoint->device, &dev_checkpoint->dev_exported_change_id); + g_clear_object(&dev_checkpoint->device); + + if (!priv->removed_devices) + priv->removed_devices = + g_ptr_array_new_with_free_func((GDestroyNotify) device_checkpoint_destroy); + g_ptr_array_add(priv->removed_devices, dev_checkpoint); + + _notify(checkpoint, PROP_DEVICES); +} + +static void +_dev_exported_changed(NMDBusObject *obj, NMCheckpoint *checkpoint) +{ + _move_dev_to_removed_devices(NM_DEVICE(obj), checkpoint); +} + +static DeviceCheckpoint * +device_checkpoint_create(NMCheckpoint *checkpoint, NMDevice *device) +{ + DeviceCheckpoint * dev_checkpoint; + NMConnection * applied_connection; + NMSettingsConnection *settings_connection; + const char * path; + NMActRequest * act_request; + + nm_assert(NM_IS_DEVICE(device)); + nm_assert(nm_device_is_real(device)); + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device)); + + dev_checkpoint = g_slice_new0(DeviceCheckpoint); + dev_checkpoint->device = g_object_ref(device); + dev_checkpoint->original_dev_path = g_strdup(path); + dev_checkpoint->original_dev_name = g_strdup(nm_device_get_iface(device)); + dev_checkpoint->dev_type = nm_device_get_device_type(device); + dev_checkpoint->state = nm_device_get_state(device); + dev_checkpoint->is_software = nm_device_is_software(device); + dev_checkpoint->realized = nm_device_is_real(device); + dev_checkpoint->dev_exported_change_id = g_signal_connect(device, + NM_DBUS_OBJECT_EXPORTED_CHANGED, + G_CALLBACK(_dev_exported_changed), + checkpoint); + + if (nm_device_get_unmanaged_mask(device, NM_UNMANAGED_USER_EXPLICIT)) { + dev_checkpoint->unmanaged_explicit = + !!nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT); + } else + dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET; + + act_request = nm_device_get_act_request(device); + if (act_request) { + settings_connection = nm_act_request_get_settings_connection(act_request); + applied_connection = nm_act_request_get_applied_connection(act_request); + + dev_checkpoint->applied_connection = nm_simple_connection_new_clone(applied_connection); + dev_checkpoint->settings_connection = nm_simple_connection_new_clone( + nm_settings_connection_get_connection(settings_connection)); + dev_checkpoint->ac_version_id = + nm_active_connection_version_id_get(NM_ACTIVE_CONNECTION(act_request)); + dev_checkpoint->activation_reason = + nm_active_connection_get_activation_reason(NM_ACTIVE_CONNECTION(act_request)); + dev_checkpoint->activation_lifetime_bound_to_profile_visibility = + NM_FLAGS_HAS(nm_active_connection_get_state_flags(NM_ACTIVE_CONNECTION(act_request)), + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + } + + return dev_checkpoint; +} + +static gboolean +_timeout_cb(gpointer user_data) +{ + NMCheckpoint * self = user_data; + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + + priv->timeout_id = 0; + + if (priv->timeout_cb) + priv->timeout_cb(self, priv->timeout_data); + + /* beware, @self likely got destroyed! */ + return G_SOURCE_REMOVE; +} + +void +nm_checkpoint_adjust_rollback_timeout(NMCheckpoint *self, guint32 add_timeout) +{ + guint32 rollback_timeout_s; + gint64 now_ms, add_timeout_ms, rollback_timeout_ms; + + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + + nm_clear_g_source(&priv->timeout_id); + + if (add_timeout == 0) + rollback_timeout_s = 0; + else { + now_ms = nm_utils_get_monotonic_timestamp_msec(); + add_timeout_ms = ((gint64) add_timeout) * 1000; + rollback_timeout_ms = (now_ms - priv->created_at_ms) + add_timeout_ms; + + /* round to nearest integer second. Since NM_CHECKPOINT_ROLLBACK_TIMEOUT is + * in units seconds, it will be able to exactly express the timeout. */ + rollback_timeout_s = NM_MIN((rollback_timeout_ms + 500) / 1000, (gint64) G_MAXUINT32); + + /* we expect the timeout to be positive, because add_timeout_ms is positive. + * We cannot accept a zero, because it means "infinity". */ + nm_assert(rollback_timeout_s > 0); + + priv->timeout_id = + g_timeout_add(NM_MIN(add_timeout_ms, (gint64) G_MAXUINT32), _timeout_cb, self); + } + + if (rollback_timeout_s != priv->rollback_timeout_s) { + priv->rollback_timeout_s = rollback_timeout_s; + _notify(self, PROP_ROLLBACK_TIMEOUT); + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMCheckpoint * self = NM_CHECKPOINT(object); + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_DEVICES: + nm_dbus_utils_g_value_set_object_path_from_hash(value, priv->devices, FALSE); + break; + case PROP_CREATED: + g_value_set_int64( + value, + nm_utils_monotonic_timestamp_as_boottime(priv->created_at_ms, NM_UTILS_NSEC_PER_MSEC)); + break; + case PROP_ROLLBACK_TIMEOUT: + g_value_set_uint(value, priv->rollback_timeout_s); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_checkpoint_init(NMCheckpoint *self) +{ + NMCheckpointPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_CHECKPOINT, NMCheckpointPrivate); + + self->_priv = priv; + + c_list_init(&self->checkpoints_lst); + + priv->devices = g_hash_table_new_full(nm_direct_hash, NULL, NULL, device_checkpoint_destroy); +} + +static void +_device_removed(NMManager *manager, NMDevice *device, gpointer user_data) +{ + _move_dev_to_removed_devices(device, NM_CHECKPOINT(user_data)); +} + +NMCheckpoint * +nm_checkpoint_new(NMManager * manager, + GPtrArray * devices, + guint32 rollback_timeout_s, + NMCheckpointCreateFlags flags) +{ + NMCheckpoint * self; + NMCheckpointPrivate * priv; + NMSettingsConnection *const *con; + gint64 rollback_timeout_ms; + guint i; + + g_return_val_if_fail(manager, NULL); + g_return_val_if_fail(devices, NULL); + g_return_val_if_fail(devices->len > 0, NULL); + + self = g_object_new(NM_TYPE_CHECKPOINT, NULL); + + priv = NM_CHECKPOINT_GET_PRIVATE(self); + priv->manager = g_object_ref(manager); + priv->rollback_timeout_s = rollback_timeout_s; + priv->created_at_ms = nm_utils_get_monotonic_timestamp_msec(); + priv->flags = flags; + + if (rollback_timeout_s != 0) { + rollback_timeout_ms = ((gint64) rollback_timeout_s) * 1000; + priv->timeout_id = + g_timeout_add(NM_MIN(rollback_timeout_ms, (gint64) G_MAXUINT32), _timeout_cb, self); + } + + if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) { + priv->connection_uuids = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + for (con = nm_settings_get_connections(NM_SETTINGS_GET, NULL); *con; con++) { + g_hash_table_add(priv->connection_uuids, + g_strdup(nm_settings_connection_get_uuid(*con))); + } + } + + for (i = 0; i < devices->len; i++) { + NMDevice *device = devices->pdata[i]; + + /* As long as the check point instance exists, it will keep a reference + * to the device also if the device gets removed (by rmmod or by deleting + * a connection profile for a software device). */ + g_hash_table_insert(priv->devices, device, device_checkpoint_create(self, device)); + } + + priv->dev_removed_id = g_signal_connect(priv->manager, + NM_MANAGER_DEVICE_REMOVED, + G_CALLBACK(_device_removed), + self); + return self; +} + +static void +dispose(GObject *object) +{ + NMCheckpoint * self = NM_CHECKPOINT(object); + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self); + + nm_assert(c_list_is_empty(&self->checkpoints_lst)); + + nm_clear_pointer(&priv->devices, g_hash_table_unref); + nm_clear_pointer(&priv->connection_uuids, g_hash_table_unref); + nm_clear_pointer(&priv->removed_devices, g_ptr_array_unref); + + nm_clear_g_signal_handler(priv->manager, &priv->dev_removed_id); + g_clear_object(&priv->manager); + + nm_clear_g_source(&priv->timeout_id); + + G_OBJECT_CLASS(nm_checkpoint_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_checkpoint = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_CHECKPOINT, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Devices", + "ao", + NM_CHECKPOINT_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Created", "x", NM_CHECKPOINT_CREATED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RollbackTimeout", + "u", + NM_CHECKPOINT_ROLLBACK_TIMEOUT), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_checkpoint_class_init(NMCheckpointClass *checkpoint_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(checkpoint_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(checkpoint_class); + + g_type_class_add_private(object_class, sizeof(NMCheckpointPrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/Checkpoint"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_checkpoint); + + object_class->dispose = dispose; + object_class->get_property = get_property; + + obj_properties[PROP_DEVICES] = g_param_spec_boxed(NM_CHECKPOINT_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CREATED] = g_param_spec_int64(NM_CHECKPOINT_CREATED, + "", + "", + G_MININT64, + G_MAXINT64, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ROLLBACK_TIMEOUT] = + g_param_spec_uint(NM_CHECKPOINT_ROLLBACK_TIMEOUT, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-checkpoint.h b/src/core/nm-checkpoint.h new file mode 100644 index 0000000..28c936f --- /dev/null +++ b/src/core/nm-checkpoint.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_CHECKPOINT_H__ +#define __NETWORKMANAGER_CHECKPOINT_H__ + +#include "nm-dbus-object.h" +#include "nm-dbus-interface.h" + +#define NM_TYPE_CHECKPOINT (nm_checkpoint_get_type()) +#define NM_CHECKPOINT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_CHECKPOINT, NMCheckpoint)) +#define NM_CHECKPOINT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_CHECKPOINT, NMCheckpointClass)) +#define NM_IS_CHECKPOINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_CHECKPOINT)) +#define NM_IS_CHECKPOINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_CHECKPOINT)) +#define NM_CHECKPOINT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_CHECKPOINT, NMCheckpointClass)) + +#define NM_CHECKPOINT_DEVICES "devices" +#define NM_CHECKPOINT_CREATED "created" +#define NM_CHECKPOINT_ROLLBACK_TIMEOUT "rollback-timeout" + +typedef struct _NMCheckpointPrivate NMCheckpointPrivate; + +typedef struct { + NMDBusObject parent; + NMCheckpointPrivate *_priv; + CList checkpoints_lst; +} NMCheckpoint; + +typedef struct _NMCheckpointClass NMCheckpointClass; + +GType nm_checkpoint_get_type(void); + +NMCheckpoint *nm_checkpoint_new(NMManager * manager, + GPtrArray * devices, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags); + +typedef void (*NMCheckpointTimeoutCallback)(NMCheckpoint *self, gpointer user_data); + +void nm_checkpoint_log_destroy(NMCheckpoint *self); + +void nm_checkpoint_set_timeout_callback(NMCheckpoint * self, + NMCheckpointTimeoutCallback callback, + gpointer user_data); + +GVariant *nm_checkpoint_rollback(NMCheckpoint *self); + +void nm_checkpoint_adjust_rollback_timeout(NMCheckpoint *self, guint32 add_timeout); + +NMDevice * +nm_checkpoint_includes_devices(NMCheckpoint *self, NMDevice *const *devices, guint n_devices); +NMDevice *nm_checkpoint_includes_devices_of(NMCheckpoint *self, NMCheckpoint *cp_for_devices); + +#endif /* __NETWORKMANAGER_CHECKPOINT_H__ */ diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c new file mode 100644 index 0000000..b3cd5e9 --- /dev/null +++ b/src/core/nm-config-data.c @@ -0,0 +1,2087 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * Copyright (C) 2013 Thomas Bechtold + */ + +#include "nm-default.h" + +#include "nm-config-data.h" + +#include "nm-config.h" +#include "devices/nm-device.h" +#include "nm-core-internal.h" +#include "nm-keyfile/nm-keyfile-internal.h" +#include "nm-keyfile/nm-keyfile-utils.h" + +/*****************************************************************************/ + +typedef struct { + char * group_name; + gboolean stop_match; + struct { + /* have a separate boolean field @has, because a @spec with + * value %NULL does not necessarily mean, that the property + * "match-device" was unspecified. */ + gboolean has; + GSList * spec; + } match_device; +} MatchSectionInfo; + +struct _NMGlobalDnsDomain { + char * name; + char **servers; + char **options; +}; + +struct _NMGlobalDnsConfig { + char ** searches; + char ** options; + GHashTable * domains; + const char **domain_list; + gboolean internal; +}; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_CONFIG_MAIN_FILE, + PROP_CONFIG_DESCRIPTION, + PROP_KEYFILE_USER, + PROP_KEYFILE_INTERN, + PROP_CONNECTIVITY_ENABLED, + PROP_CONNECTIVITY_URI, + PROP_CONNECTIVITY_INTERVAL, + PROP_CONNECTIVITY_RESPONSE, + PROP_NO_AUTO_DEFAULT, ); + +typedef struct { + char *config_main_file; + char *config_description; + + GKeyFile *keyfile; + GKeyFile *keyfile_user; + GKeyFile *keyfile_intern; + + /* A zero-terminated list of pre-processed information from the + * [connection] sections. This is to speed up lookup. */ + MatchSectionInfo *connection_infos; + + /* A zero-terminated list of pre-processed information from the + * [device] sections. This is to speed up lookup. */ + MatchSectionInfo *device_infos; + + struct { + gboolean enabled; + char * uri; + char * response; + guint interval; + } connectivity; + + int autoconnect_retries_default; + + struct { + /* from /var/lib/NetworkManager/no-auto-default.state */ + char ** arr; + GSList *specs; + + /* from main.no-auto-default setting in NetworkManager.conf. */ + GSList *specs_config; + } no_auto_default; + + GSList *ignore_carrier; + GSList *assume_ipv6ll_only; + + char *dns_mode; + char *rc_manager; + + NMGlobalDnsConfig *global_dns; + + bool systemd_resolved : 1; +} NMConfigDataPrivate; + +struct _NMConfigData { + GObject parent; + NMConfigDataPrivate _priv; +}; + +struct _NMConfigDataClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMConfigData, nm_config_data, G_TYPE_OBJECT) + +#define NM_CONFIG_DATA_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMConfigData, NM_IS_CONFIG_DATA) + +/*****************************************************************************/ + +const char * +nm_config_data_get_config_main_file(const NMConfigData *self) +{ + g_return_val_if_fail(self, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->config_main_file; +} + +const char * +nm_config_data_get_config_description(const NMConfigData *self) +{ + g_return_val_if_fail(self, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->config_description; +} + +gboolean +nm_config_data_has_group(const NMConfigData *self, const char *group) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(group && *group, FALSE); + + return g_key_file_has_group(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, group); +} + +char * +nm_config_data_get_value(const NMConfigData * self, + const char * group, + const char * key, + NMConfigGetValueFlags flags) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), NULL); + g_return_val_if_fail(group && *group, NULL); + g_return_val_if_fail(key && *key, NULL); + + return nm_config_keyfile_get_value(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, + group, + key, + flags); +} + +gboolean +nm_config_data_has_value(const NMConfigData * self, + const char * group, + const char * key, + NMConfigGetValueFlags flags) +{ + gs_free char *value = NULL; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(group && *group, FALSE); + g_return_val_if_fail(key && *key, FALSE); + + value = + nm_config_keyfile_get_value(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, group, key, flags); + return !!value; +} + +int +nm_config_data_get_value_boolean(const NMConfigData *self, + const char * group, + const char * key, + int default_value) +{ + char *str; + int value = default_value; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), default_value); + g_return_val_if_fail(group && *group, default_value); + g_return_val_if_fail(key && *key, default_value); + + /* when parsing the boolean, base it on the raw value from g_key_file_get_value(). */ + str = nm_config_keyfile_get_value(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, + group, + key, + NM_CONFIG_GET_VALUE_RAW); + if (str) { + value = nm_config_parse_boolean(str, default_value); + g_free(str); + } + return value; +} + +gint64 +nm_config_data_get_value_int64(const NMConfigData *self, + const char * group, + const char * key, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + int errsv; + gint64 val; + char * str; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), fallback); + g_return_val_if_fail(group && *group, fallback); + g_return_val_if_fail(key && *key, fallback); + + str = nm_config_keyfile_get_value(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, + group, + key, + NM_CONFIG_GET_VALUE_NONE); + val = _nm_utils_ascii_str_to_int64(str, base, min, max, fallback); + if (str) { + errsv = errno; + g_free(str); + errno = errsv; + } + return val; +} + +char ** +nm_config_data_get_plugins(const NMConfigData *self, gboolean allow_default) +{ + gs_free_error GError * error = NULL; + const NMConfigDataPrivate *priv; + char ** list; + + g_return_val_if_fail(self, NULL); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + list = g_key_file_get_string_list(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + "plugins", + NULL, + &error); + if (nm_keyfile_error_is_not_found(error) && allow_default) { + nm_auto_unref_keyfile GKeyFile *kf = nm_config_create_keyfile(); + + /* let keyfile split the default string according to its own escaping rules. */ + g_key_file_set_value(kf, + NM_CONFIG_KEYFILE_GROUP_MAIN, + "plugins", + NM_CONFIG_DEFAULT_MAIN_PLUGINS); + list = g_key_file_get_string_list(kf, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL); + } + return _nm_utils_strv_cleanup(list, TRUE, TRUE, TRUE); +} + +gboolean +nm_config_data_get_connectivity_enabled(const NMConfigData *self) +{ + g_return_val_if_fail(self, FALSE); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->connectivity.enabled; +} + +const char * +nm_config_data_get_connectivity_uri(const NMConfigData *self) +{ + g_return_val_if_fail(self, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->connectivity.uri; +} + +guint +nm_config_data_get_connectivity_interval(const NMConfigData *self) +{ + g_return_val_if_fail(self, 0); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->connectivity.interval; +} + +const char * +nm_config_data_get_connectivity_response(const NMConfigData *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->connectivity.response; +} + +int +nm_config_data_get_autoconnect_retries_default(const NMConfigData *self) +{ + g_return_val_if_fail(self, FALSE); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->autoconnect_retries_default; +} + +const char *const * +nm_config_data_get_no_auto_default(const NMConfigData *self) +{ + g_return_val_if_fail(self, FALSE); + + return (const char *const *) NM_CONFIG_DATA_GET_PRIVATE(self)->no_auto_default.arr; +} + +gboolean +nm_config_data_get_no_auto_default_for_device(const NMConfigData *self, NMDevice *device) +{ + const NMConfigDataPrivate *priv; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + return nm_device_spec_match_list(device, priv->no_auto_default.specs) + || nm_device_spec_match_list(device, priv->no_auto_default.specs_config); +} + +const char * +nm_config_data_get_dns_mode(const NMConfigData *self) +{ + g_return_val_if_fail(self, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->dns_mode; +} + +const char * +nm_config_data_get_rc_manager(const NMConfigData *self) +{ + g_return_val_if_fail(self, NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->rc_manager; +} + +gboolean +nm_config_data_get_systemd_resolved(const NMConfigData *self) +{ + g_return_val_if_fail(self, FALSE); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->systemd_resolved; +} + +gboolean +nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device) +{ + gs_free char *value = NULL; + gboolean has_match; + int m; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + value = nm_config_data_get_device_config(self, + NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, + device, + &has_match); + if (has_match) + m = nm_config_parse_boolean(value, -1); + else + m = nm_device_spec_match_list_full(device, + NM_CONFIG_DATA_GET_PRIVATE(self)->ignore_carrier, + -1); + + if (NM_IN_SET(m, TRUE, FALSE)) + return m; + + /* if ignore-carrier is not explicitly configed, then it depends on the device (type). */ + return nm_device_ignore_carrier_by_default(device); +} + +gboolean +nm_config_data_get_assume_ipv6ll_only(const NMConfigData *self, NMDevice *device) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + return nm_device_spec_match_list(device, NM_CONFIG_DATA_GET_PRIVATE(self)->assume_ipv6ll_only); +} + +GKeyFile * +nm_config_data_clone_keyfile_intern(const NMConfigData *self) +{ + const NMConfigDataPrivate *priv; + GKeyFile * keyfile; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + keyfile = nm_config_create_keyfile(); + if (priv->keyfile_intern) + _nm_keyfile_copy(keyfile, priv->keyfile_intern); + return keyfile; +} + +GKeyFile * +_nm_config_data_get_keyfile(const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile; +} + +GKeyFile * +_nm_config_data_get_keyfile_intern(const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile_intern; +} + +GKeyFile * +_nm_config_data_get_keyfile_user(const NMConfigData *self) +{ + return NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile_user; +} + +/*****************************************************************************/ + +static NMAuthPolkitMode +nm_auth_polkit_mode_from_string(const char *str) +{ + int as_bool; + + if (!str) + return NM_AUTH_POLKIT_MODE_UNKNOWN; + + if (nm_streq(str, "root-only")) + return NM_AUTH_POLKIT_MODE_ROOT_ONLY; + + as_bool = _nm_utils_ascii_str_to_bool(str, -1); + if (as_bool != -1) { + return as_bool ? NM_AUTH_POLKIT_MODE_USE_POLKIT : NM_AUTH_POLKIT_MODE_ALLOW_ALL; + } + + return NM_AUTH_POLKIT_MODE_UNKNOWN; +} + +static NMAuthPolkitMode +_config_data_get_main_auth_polkit(const NMConfigData *self, gboolean *out_invalid_config) +{ + NMAuthPolkitMode auth_polkit_mode; + const char * str; + + str = nm_config_data_get_value(self, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + auth_polkit_mode = nm_auth_polkit_mode_from_string(str); + if (auth_polkit_mode == NM_AUTH_POLKIT_MODE_UNKNOWN) { + NM_SET_OUT(out_invalid_config, (str != NULL)); + auth_polkit_mode = nm_auth_polkit_mode_from_string(NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT); + if (auth_polkit_mode == NM_AUTH_POLKIT_MODE_UNKNOWN) { + nm_assert_not_reached(); + auth_polkit_mode = NM_AUTH_POLKIT_MODE_ROOT_ONLY; + } + } else + NM_SET_OUT(out_invalid_config, FALSE); + + return auth_polkit_mode; +} + +NMAuthPolkitMode +nm_config_data_get_main_auth_polkit(const NMConfigData *self) +{ + return _config_data_get_main_auth_polkit(self, NULL); +} + +/*****************************************************************************/ + +/** + * nm_config_data_get_groups: + * @self: the #NMConfigData instance + * + * Returns: (transfer full): the list of groups in the configuration. The order + * of the section is undefined, as the configuration gets merged from multiple + * sources. + */ +char ** +nm_config_data_get_groups(const NMConfigData *self) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), NULL); + + return g_key_file_get_groups(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, NULL); +} + +char ** +nm_config_data_get_keys(const NMConfigData *self, const char *group) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), NULL); + g_return_val_if_fail(group && *group, NULL); + + return g_key_file_get_keys(NM_CONFIG_DATA_GET_PRIVATE(self)->keyfile, group, NULL, NULL); +} + +/** + * nm_config_data_is_intern_atomic_group: + * @self: + * @group: name of the group to check. + * + * whether a configuration group @group exists and is entirely overwritten + * by internal configuration, i.e. whether it is an atomic group that is + * overwritten. + * + * It doesn't say, that there actually is a user setting that was overwritten. That + * means there could be no corresponding section defined in user configuration + * that required overwriting. + * + * Returns: %TRUE if @group exists and is an atomic group set via internal configuration. + */ +gboolean +nm_config_data_is_intern_atomic_group(const NMConfigData *self, const char *group) +{ + const NMConfigDataPrivate *priv; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE); + g_return_val_if_fail(group && *group, FALSE); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + if (!priv->keyfile_intern + || !g_key_file_has_key(priv->keyfile_intern, + group, + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + NULL)) + return FALSE; + + /* we have a .was entry for the section. That means that the section would be overwritten + * from user configuration. But it doesn't mean that the merged configuration contains this + * groups, because the internal setting could hide the user section. + * Only return TRUE, if we actually have such a group in the merged configuration.*/ + return g_key_file_has_group(priv->keyfile, group); +} + +/*****************************************************************************/ + +static GKeyFile * +_merge_keyfiles(GKeyFile *keyfile_user, GKeyFile *keyfile_intern) +{ + gs_strfreev char **groups = NULL; + guint g, k; + GKeyFile * keyfile; + gsize ngroups; + + keyfile = nm_config_create_keyfile(); + if (keyfile_user) + _nm_keyfile_copy(keyfile, keyfile_user); + if (!keyfile_intern) + return keyfile; + + groups = g_key_file_get_groups(keyfile_intern, &ngroups); + if (!groups) + return keyfile; + + /* we must reverse the order of the connection settings so that we + * have lowest priority last. */ + _nm_config_sort_groups(groups, ngroups); + for (g = 0; groups[g]; g++) { + const char * group = groups[g]; + gs_strfreev char **keys = NULL; + gboolean is_intern, is_atomic = FALSE; + + keys = g_key_file_get_keys(keyfile_intern, group, NULL, NULL); + if (!keys) + continue; + + is_intern = g_str_has_prefix(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + if (!is_intern + && g_key_file_has_key(keyfile_intern, + group, + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + NULL)) { + /* the entire section is atomically overwritten by @keyfile_intern. */ + g_key_file_remove_group(keyfile, group, NULL); + is_atomic = TRUE; + } + + for (k = 0; keys[k]; k++) { + const char * key = keys[k]; + gs_free char *value = NULL; + + if (is_atomic && strcmp(key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0) + continue; + + if (!is_intern && !is_atomic + && NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char *key_base = &key[NM_STRLEN(NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + + if (!g_key_file_has_key(keyfile_intern, group, key_base, NULL)) + g_key_file_remove_key(keyfile, group, key_base, NULL); + continue; + } + if (!is_intern && !is_atomic + && NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) + continue; + + value = g_key_file_get_value(keyfile_intern, group, key, NULL); + g_key_file_set_value(keyfile, group, key, value); + } + } + return keyfile; +} + +/*****************************************************************************/ + +static int +_nm_config_data_log_sort(const char **pa, const char **pb, gpointer dummy) +{ + gboolean a_is_connection, b_is_connection; + gboolean a_is_device, b_is_device; + gboolean a_is_intern, b_is_intern; + gboolean a_is_main, b_is_main; + const char *a = *pa; + const char *b = *pb; + + /* we sort intern groups to the end. */ + a_is_intern = g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + b_is_intern = g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + + if (a_is_intern && b_is_intern) + return 0; + if (a_is_intern) + return 1; + if (b_is_intern) + return -1; + + /* we sort connection groups before intern groups (to the end). */ + a_is_connection = a && g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + b_is_connection = b && g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + + if (a_is_connection && b_is_connection) { + /* if both are connection groups, we want the explicit [connection] group first. */ + a_is_connection = a[NM_STRLEN(NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)] == '\0'; + b_is_connection = b[NM_STRLEN(NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)] == '\0'; + + if (a_is_connection != b_is_connection) { + if (a_is_connection) + return -1; + return 1; + } + /* the sections are ordered lowest-priority first. Reverse their order. */ + return pa < pb ? 1 : -1; + } + if (a_is_connection && !b_is_connection) + return 1; + if (b_is_connection && !a_is_connection) + return -1; + + /* we sort device groups before connection groups (to the end). */ + a_is_device = a && g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE); + b_is_device = b && g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE); + + if (a_is_device && b_is_device) { + /* if both are device groups, we want the explicit [device] group first. */ + a_is_device = a[NM_STRLEN(NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE)] == '\0'; + b_is_device = b[NM_STRLEN(NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE)] == '\0'; + + if (a_is_device != b_is_device) { + if (a_is_device) + return -1; + return 1; + } + /* the sections are ordered lowest-priority first. Reverse their order. */ + return pa < pb ? 1 : -1; + } + if (a_is_device && !b_is_device) + return 1; + if (b_is_device && !a_is_device) + return -1; + + a_is_main = nm_streq0(a, "main"); + b_is_main = nm_streq0(b, "main"); + if (a_is_main != b_is_main) + return a_is_main ? -1 : 1; + + return g_strcmp0(a, b); +} + +static const struct { + const char *group; + const char *key; + const char *value; +} default_values[] = { + {NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NM_CONFIG_DEFAULT_MAIN_PLUGINS}, + {NM_CONFIG_KEYFILE_GROUP_MAIN, "rc-manager", NM_CONFIG_DEFAULT_MAIN_RC_MANAGER}, + {NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT, + NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT}, + {NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, NM_CONFIG_DEFAULT_MAIN_DHCP}, + {NM_CONFIG_KEYFILE_GROUP_LOGGING, "backend", NM_CONFIG_DEFAULT_LOGGING_BACKEND}, + {NM_CONFIG_KEYFILE_GROUP_LOGGING, "audit", NM_CONFIG_DEFAULT_LOGGING_AUDIT}, +}; + +void +nm_config_data_log(const NMConfigData * self, + const char * prefix, + const char * key_prefix, + const char * no_auto_default_file, + /* FILE* */ gpointer print_stream) +{ + const NMConfigDataPrivate *priv; + gs_strfreev char ** groups = NULL; + gsize ngroups; + guint g, k, i; + FILE * stream = print_stream; + gs_unref_ptrarray GPtrArray *groups_full = NULL; + gboolean print_default = !!stream; + + g_return_if_fail(NM_IS_CONFIG_DATA(self)); + + if (!stream && !nm_logging_enabled(LOGL_DEBUG, LOGD_CORE)) + return; + + if (!prefix) + prefix = ""; + if (!key_prefix) + key_prefix = ""; + +#define _LOG(stream, prefix, ...) \ + G_STMT_START \ + { \ + if (!stream) \ + _nm_log(LOGL_DEBUG, \ + LOGD_CORE, \ + 0, \ + NULL, \ + NULL, \ + "%s"_NM_UTILS_MACRO_FIRST(__VA_ARGS__) "%s", \ + prefix _NM_UTILS_MACRO_REST(__VA_ARGS__), \ + ""); \ + else \ + fprintf(stream, \ + "%s"_NM_UTILS_MACRO_FIRST(__VA_ARGS__) "%s", \ + prefix _NM_UTILS_MACRO_REST(__VA_ARGS__), \ + "\n"); \ + } \ + G_STMT_END + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + groups = g_key_file_get_groups(priv->keyfile, &ngroups); + if (!groups) + ngroups = 0; + + groups_full = g_ptr_array_sized_new(ngroups + 5); + + if (ngroups) { + g_ptr_array_set_size(groups_full, ngroups); + memcpy(groups_full->pdata, groups, sizeof(groups[0]) * ngroups); + g_ptr_array_sort_with_data(groups_full, (GCompareDataFunc) _nm_config_data_log_sort, NULL); + } + + if (print_default) { + for (g = 0; g < G_N_ELEMENTS(default_values); g++) { + const char *group = default_values[g].group; + gssize idx; + + idx = nm_utils_array_find_binary_search((gconstpointer *) groups_full->pdata, + sizeof(char *), + groups_full->len, + &group, + (GCompareDataFunc) _nm_config_data_log_sort, + NULL); + if (idx < 0) + g_ptr_array_insert(groups_full, (~idx), (gpointer) group); + } + } + + if (!stream) + _LOG(stream, prefix, "config-data[%p]: %u groups", self, groups_full->len); + + for (g = 0; g < groups_full->len; g++) { + const char * group = groups_full->pdata[g]; + gs_strfreev char **keys = NULL; + gboolean is_atomic; + + is_atomic = nm_config_data_is_intern_atomic_group(self, group); + + _LOG(stream, prefix, ""); + _LOG(stream, prefix, "[%s]%s", group, is_atomic && !stream ? " # atomic section" : ""); + + /* Print default values as comments */ + if (print_default) { + for (i = 0; i < G_N_ELEMENTS(default_values); i++) { + if (nm_streq(default_values[i].group, group) + && !g_key_file_has_key(priv->keyfile, group, default_values[i].key, NULL)) { + _LOG(stream, + prefix, + "%s# %s=%s", + key_prefix, + default_values[i].key, + default_values[i].value); + } + } + } + + keys = g_key_file_get_keys(priv->keyfile, group, NULL, NULL); + for (k = 0; keys && keys[k]; k++) { + const char * key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value(priv->keyfile, group, key, NULL); + _LOG(stream, prefix, "%s%s=%s", key_prefix, key, value); + } + } + + _LOG(stream, prefix, ""); + _LOG(stream, prefix, "# no-auto-default file \"%s\"", no_auto_default_file); + { + gs_free char *msg = NULL; + + msg = nm_utils_g_slist_strlist_join(priv->no_auto_default.specs, ","); + if (msg) + _LOG(stream, prefix, "# no-auto-default specs \"%s\"", msg); + } + +#undef _LOG +} + +/*****************************************************************************/ + +const char *const * +nm_global_dns_config_get_searches(const NMGlobalDnsConfig *dns_config) +{ + g_return_val_if_fail(dns_config, NULL); + + return (const char *const *) dns_config->searches; +} + +const char *const * +nm_global_dns_config_get_options(const NMGlobalDnsConfig *dns_config) +{ + g_return_val_if_fail(dns_config, NULL); + + return (const char *const *) dns_config->options; +} + +guint +nm_global_dns_config_get_num_domains(const NMGlobalDnsConfig *dns_config) +{ + g_return_val_if_fail(dns_config, 0); + + return dns_config->domains ? g_hash_table_size(dns_config->domains) : 0; +} + +NMGlobalDnsDomain * +nm_global_dns_config_get_domain(const NMGlobalDnsConfig *dns_config, guint i) +{ + NMGlobalDnsDomain *domain; + + g_return_val_if_fail(dns_config, NULL); + g_return_val_if_fail(dns_config->domains, NULL); + g_return_val_if_fail(i < g_hash_table_size(dns_config->domains), NULL); + + nm_assert(NM_PTRARRAY_LEN(dns_config->domain_list) == g_hash_table_size(dns_config->domains)); + + domain = g_hash_table_lookup(dns_config->domains, dns_config->domain_list[i]); + + nm_assert(domain); + return domain; +} + +NMGlobalDnsDomain * +nm_global_dns_config_lookup_domain(const NMGlobalDnsConfig *dns_config, const char *name) +{ + g_return_val_if_fail(dns_config, NULL); + g_return_val_if_fail(name, NULL); + + return dns_config->domains ? g_hash_table_lookup(dns_config->domains, name) : NULL; +} + +const char * +nm_global_dns_domain_get_name(const NMGlobalDnsDomain *domain) +{ + g_return_val_if_fail(domain, NULL); + + return (const char *) domain->name; +} + +const char *const * +nm_global_dns_domain_get_servers(const NMGlobalDnsDomain *domain) +{ + g_return_val_if_fail(domain, NULL); + + return (const char *const *) domain->servers; +} + +const char *const * +nm_global_dns_domain_get_options(const NMGlobalDnsDomain *domain) +{ + g_return_val_if_fail(domain, NULL); + + return (const char *const *) domain->options; +} + +gboolean +nm_global_dns_config_is_internal(const NMGlobalDnsConfig *dns_config) +{ + return dns_config->internal; +} + +gboolean +nm_global_dns_config_is_empty(const NMGlobalDnsConfig *dns_config) +{ + g_return_val_if_fail(dns_config, TRUE); + + return !dns_config->searches && !dns_config->options && !dns_config->domain_list; +} + +void +nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GChecksum *sum) +{ + NMGlobalDnsDomain *domain; + guint i, j; + guint8 v8; + + g_return_if_fail(dns_config); + g_return_if_fail(sum); + + v8 = NM_HASH_COMBINE_BOOLS(guint8, + !dns_config->searches, + !dns_config->options, + !dns_config->domain_list); + g_checksum_update(sum, (guchar *) &v8, 1); + + if (dns_config->searches) { + for (i = 0; dns_config->searches[i]; i++) + g_checksum_update(sum, + (guchar *) dns_config->searches[i], + strlen(dns_config->searches[i]) + 1); + } + if (dns_config->options) { + for (i = 0; dns_config->options[i]; i++) + g_checksum_update(sum, + (guchar *) dns_config->options[i], + strlen(dns_config->options[i]) + 1); + } + + if (dns_config->domain_list) { + for (i = 0; dns_config->domain_list[i]; i++) { + domain = g_hash_table_lookup(dns_config->domains, dns_config->domain_list[i]); + nm_assert(domain); + + v8 = NM_HASH_COMBINE_BOOLS(guint8, !domain->servers, !domain->options); + g_checksum_update(sum, (guchar *) &v8, 1); + + g_checksum_update(sum, (guchar *) domain->name, strlen(domain->name) + 1); + + if (domain->servers) { + for (j = 0; domain->servers[j]; j++) + g_checksum_update(sum, + (guchar *) domain->servers[j], + strlen(domain->servers[j]) + 1); + } + if (domain->options) { + for (j = 0; domain->options[j]; j++) + g_checksum_update(sum, + (guchar *) domain->options[j], + strlen(domain->options[j]) + 1); + } + } + } +} + +static void +global_dns_domain_free(NMGlobalDnsDomain *domain) +{ + if (domain) { + g_free(domain->name); + g_strfreev(domain->servers); + g_strfreev(domain->options); + g_free(domain); + } +} + +void +nm_global_dns_config_free(NMGlobalDnsConfig *dns_config) +{ + if (dns_config) { + g_strfreev(dns_config->searches); + g_strfreev(dns_config->options); + g_free(dns_config->domain_list); + if (dns_config->domains) + g_hash_table_unref(dns_config->domains); + g_free(dns_config); + } +} + +NMGlobalDnsConfig * +nm_config_data_get_global_dns_config(const NMConfigData *self) +{ + g_return_val_if_fail(NM_IS_CONFIG_DATA(self), NULL); + + return NM_CONFIG_DATA_GET_PRIVATE(self)->global_dns; +} + +static void +global_dns_config_seal_domains(NMGlobalDnsConfig *dns_config) +{ + nm_assert(dns_config); + nm_assert(dns_config->domains); + nm_assert(!dns_config->domain_list); + + if (g_hash_table_size(dns_config->domains) == 0) + nm_clear_pointer(&dns_config->domains, g_hash_table_unref); + else + dns_config->domain_list = nm_utils_strdict_get_keys(dns_config->domains, TRUE, NULL); +} + +static NMGlobalDnsConfig * +load_global_dns(GKeyFile *keyfile, gboolean internal) +{ + NMGlobalDnsConfig *dns_config; + char * group, *domain_prefix; + gs_strfreev char **groups = NULL; + int g, i, j, domain_prefix_len; + gboolean default_found = FALSE; + char ** strv; + + group = + internal ? NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS : NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS; + domain_prefix = internal ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN + : NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN; + domain_prefix_len = strlen(domain_prefix); + + if (!nm_config_keyfile_has_global_dns_config(keyfile, internal)) + return NULL; + + dns_config = g_malloc0(sizeof(NMGlobalDnsConfig)); + dns_config->domains = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) global_dns_domain_free); + + strv = g_key_file_get_string_list(keyfile, + group, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES, + NULL, + NULL); + if (strv) { + _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + if (!strv[0]) + g_free(strv); + else + dns_config->searches = strv; + } + + strv = g_key_file_get_string_list(keyfile, + group, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS, + NULL, + NULL); + if (strv) { + _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + for (i = 0, j = 0; strv[i]; i++) { + if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, TRUE, NULL)) + strv[j++] = strv[i]; + else + g_free(strv[i]); + } + if (j == 0) + g_free(strv); + else { + strv[j] = NULL; + dns_config->options = strv; + } + } + + groups = g_key_file_get_groups(keyfile, NULL); + for (g = 0; groups[g]; g++) { + char * name; + char ** servers = NULL, **options = NULL; + NMGlobalDnsDomain *domain; + + if (!g_str_has_prefix(groups[g], domain_prefix) || !groups[g][domain_prefix_len]) + continue; + + strv = g_key_file_get_string_list(keyfile, + groups[g], + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS, + NULL, + NULL); + if (strv) { + _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + for (i = 0, j = 0; strv[i]; i++) { + if (nm_utils_ipaddr_is_valid(AF_INET, strv[i]) + || nm_utils_ipaddr_is_valid(AF_INET6, strv[i])) + strv[j++] = strv[i]; + else + g_free(strv[i]); + } + if (j == 0) + g_free(strv); + else { + strv[j] = NULL; + servers = strv; + } + } + + if (!servers) + continue; + + strv = g_key_file_get_string_list(keyfile, + groups[g], + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS, + NULL, + NULL); + if (strv) { + options = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + if (!options[0]) + nm_clear_g_free(&options); + } + + name = strdup(&groups[g][domain_prefix_len]); + domain = g_malloc0(sizeof(NMGlobalDnsDomain)); + domain->name = name; + domain->servers = servers; + domain->options = options; + + g_hash_table_insert(dns_config->domains, strdup(name), domain); + + if (!strcmp(name, "*")) + default_found = TRUE; + } + + if (!default_found) { + nm_log_dbg(LOGD_CORE, + "%s global DNS configuration is missing default domain, ignore it", + internal ? "internal" : "user"); + nm_global_dns_config_free(dns_config); + return NULL; + } + + dns_config->internal = internal; + global_dns_config_seal_domains(dns_config); + return dns_config; +} + +void +nm_global_dns_config_to_dbus(const NMGlobalDnsConfig *dns_config, GValue *value) +{ + GVariantBuilder conf_builder, domains_builder, domain_builder; + guint i; + + g_variant_builder_init(&conf_builder, G_VARIANT_TYPE("a{sv}")); + if (!dns_config) + goto out; + + if (dns_config->searches) { + g_variant_builder_add(&conf_builder, + "{sv}", + "searches", + g_variant_new_strv((const char *const *) dns_config->searches, -1)); + } + + if (dns_config->options) { + g_variant_builder_add(&conf_builder, + "{sv}", + "options", + g_variant_new_strv((const char *const *) dns_config->options, -1)); + } + + g_variant_builder_init(&domains_builder, G_VARIANT_TYPE("a{sv}")); + if (dns_config->domain_list) { + for (i = 0; dns_config->domain_list[i]; i++) { + NMGlobalDnsDomain *domain; + + domain = g_hash_table_lookup(dns_config->domains, dns_config->domain_list[i]); + + g_variant_builder_init(&domain_builder, G_VARIANT_TYPE("a{sv}")); + + if (domain->servers) { + g_variant_builder_add( + &domain_builder, + "{sv}", + "servers", + g_variant_new_strv((const char *const *) domain->servers, -1)); + } + if (domain->options) { + g_variant_builder_add( + &domain_builder, + "{sv}", + "options", + g_variant_new_strv((const char *const *) domain->options, -1)); + } + + g_variant_builder_add(&domains_builder, + "{sv}", + domain->name, + g_variant_builder_end(&domain_builder)); + } + } + g_variant_builder_add(&conf_builder, + "{sv}", + "domains", + g_variant_builder_end(&domains_builder)); + +out: + g_value_take_variant(value, g_variant_builder_end(&conf_builder)); +} + +static NMGlobalDnsDomain * +global_dns_domain_from_dbus(char *name, GVariant *variant) +{ + NMGlobalDnsDomain *domain; + GVariantIter iter; + char ** strv, *key; + GVariant * val; + int i, j; + + if (!g_variant_is_of_type(variant, G_VARIANT_TYPE("a{sv}"))) + return NULL; + + domain = g_malloc0(sizeof(NMGlobalDnsDomain)); + domain->name = g_strdup(name); + + g_variant_iter_init(&iter, variant); + while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) { + if (!g_strcmp0(key, "servers") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) { + strv = g_variant_dup_strv(val, NULL); + _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + for (i = 0, j = 0; strv && strv[i]; i++) { + if (nm_utils_ipaddr_is_valid(AF_INET, strv[i]) + || nm_utils_ipaddr_is_valid(AF_INET6, strv[i])) + strv[j++] = strv[i]; + else + g_free(strv[i]); + } + if (j == 0) + g_free(strv); + else { + strv[j] = NULL; + g_strfreev(domain->servers); + domain->servers = strv; + } + } else if (!g_strcmp0(key, "options") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) { + strv = g_variant_dup_strv(val, NULL); + g_strfreev(domain->options); + domain->options = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + if (!domain->options[0]) + nm_clear_g_free(&domain->options); + } + + g_variant_unref(val); + } + + /* At least one server is required */ + if (!domain->servers) { + global_dns_domain_free(domain); + return NULL; + } + + return domain; +} + +NMGlobalDnsConfig * +nm_global_dns_config_from_dbus(const GValue *value, GError **error) +{ + NMGlobalDnsConfig *dns_config; + GVariant * variant, *val; + GVariantIter iter; + char ** strv, *key; + int i, j; + + if (!G_VALUE_HOLDS_VARIANT(value)) { + g_set_error(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "invalid value type"); + return NULL; + } + + variant = g_value_get_variant(value); + if (!g_variant_is_of_type(variant, G_VARIANT_TYPE("a{sv}"))) { + g_set_error(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "invalid variant type"); + return NULL; + } + + dns_config = g_malloc0(sizeof(NMGlobalDnsConfig)); + dns_config->domains = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) global_dns_domain_free); + + g_variant_iter_init(&iter, variant); + while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) { + if (!g_strcmp0(key, "searches") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) { + strv = g_variant_dup_strv(val, NULL); + dns_config->searches = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + } else if (!g_strcmp0(key, "options") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) { + strv = g_variant_dup_strv(val, NULL); + _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE); + + for (i = 0, j = 0; strv && strv[i]; i++) { + if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, TRUE, NULL)) + strv[j++] = strv[i]; + else + g_free(strv[i]); + } + if (j == 0) + g_free(strv); + else { + strv[j] = NULL; + dns_config->options = strv; + } + } else if (!g_strcmp0(key, "domains") + && g_variant_is_of_type(val, G_VARIANT_TYPE("a{sv}"))) { + NMGlobalDnsDomain *domain; + GVariantIter domain_iter; + GVariant * v; + char * k; + + g_variant_iter_init(&domain_iter, val); + while (g_variant_iter_next(&domain_iter, "{&sv}", &k, &v)) { + if (k) { + domain = global_dns_domain_from_dbus(k, v); + if (domain) + g_hash_table_insert(dns_config->domains, strdup(k), domain); + } + g_variant_unref(v); + } + } + g_variant_unref(val); + } + + /* An empty value is valid and clears the internal configuration */ + if (!nm_global_dns_config_is_empty(dns_config) + && !nm_global_dns_config_lookup_domain(dns_config, "*")) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Global DNS configuration is missing the default domain"); + nm_global_dns_config_free(dns_config); + return NULL; + } + + global_dns_config_seal_domains(dns_config); + return dns_config; +} + +static gboolean +global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new) +{ + NMGlobalDnsDomain *domain_old, *domain_new; + gpointer key, value_old, value_new; + GHashTableIter iter; + + if (old == new) + return TRUE; + + if (!old || !new) + return FALSE; + + if (!nm_utils_strv_equal(old->options, new->options) + || !nm_utils_strv_equal(old->searches, new->searches)) + return FALSE; + + if ((!old->domains || !new->domains) && old->domains != new->domains) + return FALSE; + + if (g_hash_table_size(old->domains) != g_hash_table_size(new->domains)) + return FALSE; + + g_hash_table_iter_init(&iter, old->domains); + while (g_hash_table_iter_next(&iter, &key, &value_old)) { + value_new = g_hash_table_lookup(new->domains, key); + if (!value_new) + return FALSE; + + domain_old = value_old; + domain_new = value_new; + + if (!nm_utils_strv_equal(domain_old->options, domain_new->options) + || !nm_utils_strv_equal(domain_old->servers, domain_new->servers)) + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static const MatchSectionInfo * +_match_section_infos_lookup(const MatchSectionInfo *match_section_infos, + GKeyFile * keyfile, + const char * property, + NMDevice * device, + const NMPlatformLink * pllink, + const char * match_device_type, + char ** out_value) +{ + const char *match_dhcp_plugin; + + if (!match_section_infos) + return NULL; + + match_dhcp_plugin = nm_dhcp_manager_get_config(nm_dhcp_manager_get()); + + for (; match_section_infos->group_name; match_section_infos++) { + char * value = NULL; + gboolean match; + + /* FIXME: Here we use g_key_file_get_string(). This should be in sync with what keyfile-reader + * does. + * + * Unfortunately that is currently not possible because keyfile-reader does the two steps + * string_to_value(keyfile_to_string(keyfile)) in one. Optimally, keyfile library would + * expose both functions, and we would return here keyfile_to_string(keyfile). + * The caller then could convert the string to the proper value via string_to_value(value). */ + value = g_key_file_get_string(keyfile, match_section_infos->group_name, property, NULL); + if (!value && !match_section_infos->stop_match) + continue; + + if (match_section_infos->match_device.has) { + if (device) + match = nm_device_spec_match_list(device, match_section_infos->match_device.spec); + else if (pllink) + match = nm_match_spec_device_by_pllink(pllink, + match_device_type, + match_dhcp_plugin, + match_section_infos->match_device.spec, + FALSE); + else + match = FALSE; + } else + match = TRUE; + + if (match) { + *out_value = value; + return match_section_infos; + } + g_free(value); + } + return NULL; +} + +char * +nm_config_data_get_device_config(const NMConfigData *self, + const char * property, + NMDevice * device, + gboolean * has_match) +{ + const NMConfigDataPrivate *priv; + const MatchSectionInfo * connection_info; + char * value = NULL; + + NM_SET_OUT(has_match, FALSE); + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(property && *property, NULL); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + connection_info = _match_section_infos_lookup(&priv->device_infos[0], + priv->keyfile, + property, + device, + NULL, + NULL, + &value); + NM_SET_OUT(has_match, !!connection_info); + return value; +} + +char * +nm_config_data_get_device_config_by_pllink(const NMConfigData * self, + const char * property, + const NMPlatformLink *pllink, + const char * match_device_type, + gboolean * has_match) +{ + const NMConfigDataPrivate *priv; + const MatchSectionInfo * connection_info; + char * value = NULL; + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(property && *property, NULL); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + connection_info = _match_section_infos_lookup(&priv->device_infos[0], + priv->keyfile, + property, + NULL, + pllink, + match_device_type, + &value); + NM_SET_OUT(has_match, !!connection_info); + return value; +} + +gboolean +nm_config_data_get_device_config_boolean(const NMConfigData *self, + const char * property, + NMDevice * device, + int val_no_match, + int val_invalid) +{ + gs_free char *value = NULL; + gboolean has_match; + + value = nm_config_data_get_device_config(self, property, device, &has_match); + if (!has_match) + return val_no_match; + return nm_config_parse_boolean(value, val_invalid); +} + +char * +nm_config_data_get_connection_default(const NMConfigData *self, + const char * property, + NMDevice * device) +{ + const NMConfigDataPrivate *priv; + char * value = NULL; + + g_return_val_if_fail(self, NULL); + g_return_val_if_fail(property && *property, NULL); + g_return_val_if_fail(strchr(property, '.'), NULL); + + priv = NM_CONFIG_DATA_GET_PRIVATE(self); + +#if NM_MORE_ASSERTS > 10 + { + const char **ptr; + + for (ptr = __start_connection_defaults; ptr < __stop_connection_defaults; ptr++) { + if (nm_streq(property, *ptr)) + break; + } + + nm_assert(ptr < __stop_connection_defaults); + } +#endif + + _match_section_infos_lookup(&priv->connection_infos[0], + priv->keyfile, + property, + device, + NULL, + NULL, + &value); + return value; +} + +gint64 +nm_config_data_get_connection_default_int64(const NMConfigData *self, + const char * property, + NMDevice * device, + gint64 min, + gint64 max, + gint64 fallback) +{ + gs_free char *value = NULL; + + value = nm_config_data_get_connection_default(self, property, device); + return _nm_utils_ascii_str_to_int64(value, 10, min, max, fallback); +} + +static void +_get_connection_info_init(MatchSectionInfo *connection_info, GKeyFile *keyfile, char *group) +{ + /* pass ownership of @group on... */ + connection_info->group_name = group; + + connection_info->match_device.spec = + nm_config_get_match_spec(keyfile, + group, + NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, + &connection_info->match_device.has); + connection_info->stop_match = + nm_config_keyfile_get_boolean(keyfile, group, NM_CONFIG_KEYFILE_KEY_STOP_MATCH, FALSE); +} + +static void +_match_section_infos_free(MatchSectionInfo *match_section_infos) +{ + guint i; + + if (!match_section_infos) + return; + for (i = 0; match_section_infos[i].group_name; i++) { + g_free(match_section_infos[i].group_name); + g_slist_free_full(match_section_infos[i].match_device.spec, g_free); + } + g_free(match_section_infos); +} + +static MatchSectionInfo * +_match_section_infos_construct(GKeyFile *keyfile, const char *prefix) +{ + char ** groups; + gsize i, j, ngroups; + char * connection_tag = NULL; + MatchSectionInfo *match_section_infos = NULL; + + /* get the list of existing [connection.\+]/[device.\+] sections. + * + * We expect the sections in their right order, with lowest priority + * first. Only exception is the (literal) [connection] section, which + * we will always reorder to the end. */ + groups = g_key_file_get_groups(keyfile, &ngroups); + if (!groups) + return NULL; + + if (ngroups > 0) { + gsize l = strlen(prefix); + + for (i = 0, j = 0; i < ngroups; i++) { + if (g_str_has_prefix(groups[i], prefix)) { + if (groups[i][l] == '\0') + connection_tag = groups[i]; + else + groups[j++] = groups[i]; + } else + g_free(groups[i]); + } + ngroups = j; + } + + if (ngroups == 0 && !connection_tag) { + g_free(groups); + return NULL; + } + + match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (connection_tag ? 1 : 0)); + for (i = 0; i < ngroups; i++) { + /* pass ownership of @group on... */ + _get_connection_info_init(&match_section_infos[i], keyfile, groups[ngroups - i - 1]); + } + if (connection_tag) { + /* pass ownership of @connection_tag on... */ + _get_connection_info_init(&match_section_infos[i], keyfile, connection_tag); + } + g_free(groups); + + return match_section_infos; +} + +/*****************************************************************************/ + +NMConfigChangeFlags +nm_config_data_diff(NMConfigData *old_data, NMConfigData *new_data) +{ + NMConfigChangeFlags changes = NM_CONFIG_CHANGE_NONE; + NMConfigDataPrivate *priv_old, *priv_new; + + g_return_val_if_fail(NM_IS_CONFIG_DATA(old_data), NM_CONFIG_CHANGE_NONE); + g_return_val_if_fail(NM_IS_CONFIG_DATA(new_data), NM_CONFIG_CHANGE_NONE); + + priv_old = NM_CONFIG_DATA_GET_PRIVATE(old_data); + priv_new = NM_CONFIG_DATA_GET_PRIVATE(new_data); + + if (!_nm_keyfile_equals(priv_old->keyfile_user, priv_new->keyfile_user, TRUE)) + changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; + + if (!_nm_keyfile_equals(priv_old->keyfile_intern, priv_new->keyfile_intern, TRUE)) + changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN; + + if (g_strcmp0(nm_config_data_get_config_main_file(old_data), + nm_config_data_get_config_main_file(new_data)) + != 0 + || g_strcmp0(nm_config_data_get_config_description(old_data), + nm_config_data_get_config_description(new_data)) + != 0) + changes |= NM_CONFIG_CHANGE_CONFIG_FILES; + + if (nm_config_data_get_connectivity_enabled(old_data) + != nm_config_data_get_connectivity_enabled(new_data) + || nm_config_data_get_connectivity_interval(old_data) + != nm_config_data_get_connectivity_interval(new_data) + || g_strcmp0(nm_config_data_get_connectivity_uri(old_data), + nm_config_data_get_connectivity_uri(new_data)) + || g_strcmp0(nm_config_data_get_connectivity_response(old_data), + nm_config_data_get_connectivity_response(new_data))) + changes |= NM_CONFIG_CHANGE_CONNECTIVITY; + + if (nm_utils_g_slist_strlist_cmp(priv_old->no_auto_default.specs, + priv_new->no_auto_default.specs) + != 0 + || nm_utils_g_slist_strlist_cmp(priv_old->no_auto_default.specs_config, + priv_new->no_auto_default.specs_config) + != 0) + changes |= NM_CONFIG_CHANGE_NO_AUTO_DEFAULT; + + if (g_strcmp0(nm_config_data_get_dns_mode(old_data), nm_config_data_get_dns_mode(new_data))) + changes |= NM_CONFIG_CHANGE_DNS_MODE; + + if (g_strcmp0(nm_config_data_get_rc_manager(old_data), nm_config_data_get_rc_manager(new_data))) + changes |= NM_CONFIG_CHANGE_RC_MANAGER; + + if (!global_dns_equal(priv_old->global_dns, priv_new->global_dns)) + changes |= NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG; + + nm_assert(!NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSES)); + + return changes; +} + +/*****************************************************************************/ + +void +nm_config_data_get_warnings(const NMConfigData *self, GPtrArray *warnings) +{ + gboolean invalid; + + nm_assert(NM_IS_CONFIG_DATA(self)); + nm_assert(warnings); + + _config_data_get_main_auth_polkit(self, &invalid); + if (invalid) { + g_ptr_array_add( + warnings, + g_strdup_printf( + "invalid setting for %s.%s (should be one of \"true\", \"false\", \"root-only\")", + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT)); + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMConfigData *self = NM_CONFIG_DATA(object); + + switch (prop_id) { + case PROP_CONFIG_MAIN_FILE: + g_value_set_string(value, nm_config_data_get_config_main_file(self)); + break; + case PROP_CONFIG_DESCRIPTION: + g_value_set_string(value, nm_config_data_get_config_description(self)); + break; + case PROP_CONNECTIVITY_ENABLED: + g_value_set_boolean(value, nm_config_data_get_connectivity_enabled(self)); + break; + case PROP_CONNECTIVITY_URI: + g_value_set_string(value, nm_config_data_get_connectivity_uri(self)); + break; + case PROP_CONNECTIVITY_INTERVAL: + g_value_set_uint(value, nm_config_data_get_connectivity_interval(self)); + break; + case PROP_CONNECTIVITY_RESPONSE: + g_value_set_string(value, nm_config_data_get_connectivity_response(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMConfigData * self = NM_CONFIG_DATA(object); + NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_CONFIG_MAIN_FILE: + /* construct-only */ + priv->config_main_file = g_value_dup_string(value); + break; + case PROP_CONFIG_DESCRIPTION: + /* construct-only */ + priv->config_description = g_value_dup_string(value); + break; + case PROP_KEYFILE_USER: + /* construct-only */ + priv->keyfile_user = g_value_dup_boxed(value); + if (priv->keyfile_user && !_nm_keyfile_has_values(priv->keyfile_user)) { + g_key_file_unref(priv->keyfile_user); + priv->keyfile_user = NULL; + } + break; + case PROP_KEYFILE_INTERN: + /* construct-only */ + priv->keyfile_intern = g_value_dup_boxed(value); + if (priv->keyfile_intern && !_nm_keyfile_has_values(priv->keyfile_intern)) { + g_key_file_unref(priv->keyfile_intern); + priv->keyfile_intern = NULL; + } + break; + case PROP_NO_AUTO_DEFAULT: + /* construct-only */ + { + const char *const * value_arr_orig = g_value_get_boxed(value); + gs_free const char **value_arr = NULL; + GSList * specs = NULL; + gsize i, j; + gsize len; + + len = NM_PTRARRAY_LEN(value_arr_orig); + + /* sort entries, remove duplicates and empty words. */ + value_arr = + len == 0 ? NULL : nm_memdup(value_arr_orig, sizeof(const char *) * (len + 1)); + nm_utils_strv_sort(value_arr, len); + _nm_utils_strv_cleanup((char **) value_arr, FALSE, TRUE, TRUE); + + len = NM_PTRARRAY_LEN(value_arr); + j = 0; + for (i = 0; i < len; i++) { + const char *s = value_arr[i]; + gboolean is_mac; + char * spec; + + if (NM_STR_HAS_PREFIX(s, NM_MATCH_SPEC_INTERFACE_NAME_TAG "=")) + is_mac = FALSE; + else if (nm_utils_hwaddr_valid(s, -1)) + is_mac = TRUE; + else { + /* we drop all lines that we don't understand. */ + continue; + } + + value_arr[j++] = s; + + spec = is_mac ? g_strdup_printf(NM_MATCH_SPEC_MAC_TAG "%s", s) : g_strdup(s); + specs = g_slist_prepend(specs, spec); + } + + priv->no_auto_default.arr = nm_utils_strv_dup(value_arr, j, TRUE); + priv->no_auto_default.specs = g_slist_reverse(specs); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_config_data_init(NMConfigData *self) +{} + +static void +constructed(GObject *object) +{ + NMConfigData * self = NM_CONFIG_DATA(object); + NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE(self); + char * str; + + priv->keyfile = _merge_keyfiles(priv->keyfile_user, priv->keyfile_intern); + + priv->connection_infos = + _match_section_infos_construct(priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + priv->device_infos = + _match_section_infos_construct(priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE); + + priv->connectivity.enabled = + nm_config_keyfile_get_boolean(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED, + TRUE); + priv->connectivity.uri = + nm_strstrip(g_key_file_get_string(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI, + NULL)); + priv->connectivity.response = g_key_file_get_string(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE, + NULL); + str = nm_config_keyfile_get_value(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT, + NM_CONFIG_GET_VALUE_NONE); + priv->autoconnect_retries_default = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXINT32, 4); + g_free(str); + + /* On missing config value, fallback to 300. On invalid value, disable connectivity checking by setting + * the interval to zero. */ + str = g_key_file_get_string(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL, + NULL); + priv->connectivity.interval = + _nm_utils_ascii_str_to_int64(str, + 10, + 0, + G_MAXUINT, + NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL); + g_free(str); + + priv->dns_mode = nm_strstrip(g_key_file_get_string(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_DNS, + NULL)); + priv->rc_manager = nm_strstrip(g_key_file_get_string(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER, + NULL)); + priv->systemd_resolved = + nm_config_keyfile_get_boolean(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED, + TRUE); + priv->ignore_carrier = nm_config_get_match_spec(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER, + NULL); + priv->assume_ipv6ll_only = + nm_config_get_match_spec(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY, + NULL); + priv->no_auto_default.specs_config = + nm_config_get_match_spec(priv->keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT, + NULL); + + priv->global_dns = load_global_dns(priv->keyfile_user, FALSE); + if (!priv->global_dns) + priv->global_dns = load_global_dns(priv->keyfile_intern, TRUE); + + G_OBJECT_CLASS(nm_config_data_parent_class)->constructed(object); +} + +NMConfigData * +nm_config_data_new(const char * config_main_file, + const char * config_description, + const char *const *no_auto_default, + GKeyFile * keyfile_user, + GKeyFile * keyfile_intern) +{ + return g_object_new(NM_TYPE_CONFIG_DATA, + NM_CONFIG_DATA_CONFIG_MAIN_FILE, + config_main_file, + NM_CONFIG_DATA_CONFIG_DESCRIPTION, + config_description, + NM_CONFIG_DATA_KEYFILE_USER, + keyfile_user, + NM_CONFIG_DATA_KEYFILE_INTERN, + keyfile_intern, + NM_CONFIG_DATA_NO_AUTO_DEFAULT, + no_auto_default, + NULL); +} + +NMConfigData * +nm_config_data_new_update_keyfile_intern(const NMConfigData *base, GKeyFile *keyfile_intern) +{ + const NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE(base); + + return g_object_new(NM_TYPE_CONFIG_DATA, + NM_CONFIG_DATA_CONFIG_MAIN_FILE, + priv->config_main_file, + NM_CONFIG_DATA_CONFIG_DESCRIPTION, + priv->config_description, + NM_CONFIG_DATA_KEYFILE_USER, + priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */ + NM_CONFIG_DATA_KEYFILE_INTERN, + keyfile_intern, + NM_CONFIG_DATA_NO_AUTO_DEFAULT, + priv->no_auto_default.arr, + NULL); +} + +NMConfigData * +nm_config_data_new_update_no_auto_default(const NMConfigData *base, + const char *const * no_auto_default) +{ + const NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE(base); + + return g_object_new(NM_TYPE_CONFIG_DATA, + NM_CONFIG_DATA_CONFIG_MAIN_FILE, + priv->config_main_file, + NM_CONFIG_DATA_CONFIG_DESCRIPTION, + priv->config_description, + NM_CONFIG_DATA_KEYFILE_USER, + priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */ + NM_CONFIG_DATA_KEYFILE_INTERN, + priv->keyfile_intern, + NM_CONFIG_DATA_NO_AUTO_DEFAULT, + no_auto_default, + NULL); +} + +static void +finalize(GObject *gobject) +{ + NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE(gobject); + + g_free(priv->config_main_file); + g_free(priv->config_description); + + g_free(priv->connectivity.uri); + g_free(priv->connectivity.response); + + g_slist_free_full(priv->no_auto_default.specs, g_free); + g_slist_free_full(priv->no_auto_default.specs_config, g_free); + g_strfreev(priv->no_auto_default.arr); + + g_free(priv->dns_mode); + g_free(priv->rc_manager); + + g_slist_free_full(priv->ignore_carrier, g_free); + g_slist_free_full(priv->assume_ipv6ll_only, g_free); + + nm_global_dns_config_free(priv->global_dns); + + _match_section_infos_free(priv->connection_infos); + _match_section_infos_free(priv->device_infos); + + g_key_file_unref(priv->keyfile); + if (priv->keyfile_user) + g_key_file_unref(priv->keyfile_user); + if (priv->keyfile_intern) + g_key_file_unref(priv->keyfile_intern); + + G_OBJECT_CLASS(nm_config_data_parent_class)->finalize(gobject); +} + +static void +nm_config_data_class_init(NMConfigDataClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(config_class); + + object_class->constructed = constructed; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_CONFIG_MAIN_FILE] = + g_param_spec_string(NM_CONFIG_DATA_CONFIG_MAIN_FILE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONFIG_DESCRIPTION] = + g_param_spec_string(NM_CONFIG_DATA_CONFIG_DESCRIPTION, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_KEYFILE_USER] = + g_param_spec_boxed(NM_CONFIG_DATA_KEYFILE_USER, + "", + "", + G_TYPE_KEY_FILE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_KEYFILE_INTERN] = + g_param_spec_boxed(NM_CONFIG_DATA_KEYFILE_INTERN, + "", + "", + G_TYPE_KEY_FILE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_ENABLED] = + g_param_spec_string(NM_CONFIG_DATA_CONNECTIVITY_ENABLED, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_URI] = + g_param_spec_string(NM_CONFIG_DATA_CONNECTIVITY_URI, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_INTERVAL] = + g_param_spec_uint(NM_CONFIG_DATA_CONNECTIVITY_INTERVAL, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_RESPONSE] = + g_param_spec_string(NM_CONFIG_DATA_CONNECTIVITY_RESPONSE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NO_AUTO_DEFAULT] = + g_param_spec_boxed(NM_CONFIG_DATA_NO_AUTO_DEFAULT, + "", + "", + G_TYPE_STRV, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h new file mode 100644 index 0000000..07cffb4 --- /dev/null +++ b/src/core/nm-config-data.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef NM_CONFIG_DATA_H +#define NM_CONFIG_DATA_H + +/*****************************************************************************/ + +typedef enum { + + /* an invalid mode. */ + NM_AUTH_POLKIT_MODE_UNKNOWN, + + /* don't use PolicyKit, but only allow root user (uid 0). */ + NM_AUTH_POLKIT_MODE_ROOT_ONLY, + + /* don't use PolicyKit, but allow all requests. */ + NM_AUTH_POLKIT_MODE_ALLOW_ALL, + + /* use PolicyKit to authorize requests. Root user (uid 0) always + * gets a free pass, without consulting PolicyKit. If PolicyKit is not + * running, authorization will fail for non root users. */ + NM_AUTH_POLKIT_MODE_USE_POLKIT, + +} NMAuthPolkitMode; + +/*****************************************************************************/ + +#define NM_TYPE_CONFIG_DATA (nm_config_data_get_type()) +#define NM_CONFIG_DATA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_CONFIG_DATA, NMConfigData)) +#define NM_CONFIG_DATA_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_CONFIG_DATA, NMConfigDataClass)) +#define NM_IS_CONFIG_DATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_CONFIG_DATA)) +#define NM_IS_CONFIG_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_CONFIG_DATA)) +#define NM_CONFIG_DATA_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_CONFIG_DATA, NMConfigDataClass)) + +#define NM_CONFIG_DATA_CONFIG_MAIN_FILE "config-main-file" +#define NM_CONFIG_DATA_CONFIG_DESCRIPTION "config-description" +#define NM_CONFIG_DATA_KEYFILE_USER "keyfile-user" +#define NM_CONFIG_DATA_KEYFILE_INTERN "keyfile-intern" +#define NM_CONFIG_DATA_CONNECTIVITY_ENABLED "connectivity-enabled" +#define NM_CONFIG_DATA_CONNECTIVITY_URI "connectivity-uri" +#define NM_CONFIG_DATA_CONNECTIVITY_INTERVAL "connectivity-interval" +#define NM_CONFIG_DATA_CONNECTIVITY_RESPONSE "connectivity-response" +#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, + + /* 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), + + /* 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 + * value to nm_match_spec_split(). */ + NM_CONFIG_GET_VALUE_TYPE_SPEC = NM_CONFIG_GET_VALUE_RAW, +} NMConfigGetValueFlags; + +typedef enum { /*< flags >*/ + 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_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), + + /* 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 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 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 rc-manager changed */ + NM_CONFIG_CHANGE_RC_MANAGER = (1L << 17), + + /* configuration regarding global dns-config changed */ + NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG = (1L << 18), + +} NMConfigChangeFlags; + +typedef struct _NMConfigDataClass NMConfigDataClass; + +typedef struct _NMGlobalDnsConfig NMGlobalDnsConfig; +typedef struct _NMGlobalDnsDomain NMGlobalDnsDomain; + +GType nm_config_data_get_type(void); + +NMConfigData *nm_config_data_new(const char * config_main_file, + const char * config_description, + const char *const *no_auto_default, + GKeyFile * keyfile_user, + GKeyFile * keyfile_intern); +NMConfigData *nm_config_data_new_update_keyfile_intern(const NMConfigData *base, + GKeyFile * keyfile_intern); +NMConfigData *nm_config_data_new_update_no_auto_default(const NMConfigData *base, + const char *const * no_auto_default); + +NMConfigChangeFlags nm_config_data_diff(NMConfigData *old_data, NMConfigData *new_data); + +void nm_config_data_log(const NMConfigData * self, + const char * prefix, + const char * key_prefix, + const char * no_auto_default_file, + /* FILE* */ gpointer print_stream); + +const char *nm_config_data_get_config_main_file(const NMConfigData *config_data); +const char *nm_config_data_get_config_description(const NMConfigData *config_data); + +gboolean nm_config_data_has_group(const NMConfigData *self, const char *group); +gboolean nm_config_data_has_value(const NMConfigData * self, + const char * group, + const char * key, + NMConfigGetValueFlags flags); +char * nm_config_data_get_value(const NMConfigData * config_data, + const char * group, + const char * key, + NMConfigGetValueFlags flags); +int nm_config_data_get_value_boolean(const NMConfigData *self, + const char * group, + const char * key, + int default_value); +gint64 nm_config_data_get_value_int64(const NMConfigData *self, + const char * group, + const char * key, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +char ** nm_config_data_get_plugins(const NMConfigData *config_data, gboolean allow_default); +gboolean nm_config_data_get_connectivity_enabled(const NMConfigData *config_data); +const char *nm_config_data_get_connectivity_uri(const NMConfigData *config_data); +guint nm_config_data_get_connectivity_interval(const NMConfigData *config_data); +const char *nm_config_data_get_connectivity_response(const NMConfigData *config_data); + +int nm_config_data_get_autoconnect_retries_default(const NMConfigData *config_data); + +NMAuthPolkitMode nm_config_data_get_main_auth_polkit(const NMConfigData *config_data); + +const char *const *nm_config_data_get_no_auto_default(const NMConfigData *config_data); +gboolean nm_config_data_get_no_auto_default_for_device(const NMConfigData *self, NMDevice *device); + +const char *nm_config_data_get_dns_mode(const NMConfigData *self); +const char *nm_config_data_get_rc_manager(const NMConfigData *self); +gboolean nm_config_data_get_systemd_resolved(const NMConfigData *self); + +gboolean nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device); +gboolean nm_config_data_get_assume_ipv6ll_only(const NMConfigData *self, NMDevice *device); +int nm_config_data_get_sriov_num_vfs(const NMConfigData *self, NMDevice *device); + +NMGlobalDnsConfig *nm_config_data_get_global_dns_config(const NMConfigData *self); + +extern const char *__start_connection_defaults[]; +extern const char *__stop_connection_defaults[]; + +#define NM_CON_DEFAULT_NOP(name) \ + static const char *NM_UNIQ_T(connection_default, NM_UNIQ) \ + _nm_used _nm_section("connection_defaults") = "" name + +#define NM_CON_DEFAULT(name) \ + ({ \ + static const char *__con_default_prop _nm_used _nm_section("connection_defaults") = \ + "" name; \ + \ + name; \ + }) + +char *nm_config_data_get_connection_default(const NMConfigData *self, + const char * property, + NMDevice * device); + +gint64 nm_config_data_get_connection_default_int64(const NMConfigData *self, + const char * property, + NMDevice * device, + gint64 min, + gint64 max, + gint64 fallback); + +char *nm_config_data_get_device_config(const NMConfigData *self, + const char * property, + NMDevice * device, + gboolean * has_match); + +char *nm_config_data_get_device_config_by_pllink(const NMConfigData * self, + const char * property, + const NMPlatformLink *pllink, + const char * match_device_type, + gboolean * has_match); + +gboolean nm_config_data_get_device_config_boolean(const NMConfigData *self, + const char * property, + NMDevice * device, + int val_no_match, + int val_invalid); + +char ** nm_config_data_get_groups(const NMConfigData *self); +char ** nm_config_data_get_keys(const NMConfigData *self, const char *group); +gboolean nm_config_data_is_intern_atomic_group(const NMConfigData *self, const char *group); + +GKeyFile *nm_config_data_clone_keyfile_intern(const NMConfigData *self); + +const char *const *nm_global_dns_config_get_searches(const NMGlobalDnsConfig *dns_config); +const char *const *nm_global_dns_config_get_options(const NMGlobalDnsConfig *dns_config); +guint nm_global_dns_config_get_num_domains(const NMGlobalDnsConfig *dns_config); +NMGlobalDnsDomain *nm_global_dns_config_get_domain(const NMGlobalDnsConfig *dns_config, guint i); +NMGlobalDnsDomain *nm_global_dns_config_lookup_domain(const NMGlobalDnsConfig *dns_config, + const char * name); +const char * nm_global_dns_domain_get_name(const NMGlobalDnsDomain *domain); +const char *const *nm_global_dns_domain_get_servers(const NMGlobalDnsDomain *domain); +const char *const *nm_global_dns_domain_get_options(const NMGlobalDnsDomain *domain); +gboolean nm_global_dns_config_is_internal(const NMGlobalDnsConfig *dns_config); +gboolean nm_global_dns_config_is_empty(const NMGlobalDnsConfig *dns_config); +void nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GChecksum *sum); +void nm_global_dns_config_free(NMGlobalDnsConfig *dns_config); + +NMGlobalDnsConfig *nm_global_dns_config_from_dbus(const GValue *value, GError **error); +void nm_global_dns_config_to_dbus(const NMGlobalDnsConfig *dns_config, GValue *value); + +void nm_config_data_get_warnings(const NMConfigData *self, GPtrArray *warnings); + +/* private accessors */ +GKeyFile *_nm_config_data_get_keyfile(const NMConfigData *self); +GKeyFile *_nm_config_data_get_keyfile_user(const NMConfigData *self); +GKeyFile *_nm_config_data_get_keyfile_intern(const NMConfigData *self); + +/*****************************************************************************/ + +/* nm-config-data.c requires getting the DHCP manager's configuration. That is a bit + * ugly, and optimally, NMConfig* is independent of NMDhcpManager. Instead of + * including the header, forward declare the two functions that we need. */ +struct _NMDhcpManager; +struct _NMDhcpManager *nm_dhcp_manager_get(void); +const char * nm_dhcp_manager_get_config(struct _NMDhcpManager *self); + +#endif /* NM_CONFIG_DATA_H */ diff --git a/src/core/nm-config.c b/src/core/nm-config.c new file mode 100644 index 0000000..3c0c78e --- /dev/null +++ b/src/core/nm-config.c @@ -0,0 +1,3159 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * Copyright (C) 2013 Thomas Bechtold + */ + +#include "nm-default.h" + +#include "nm-config.h" + +#include + +#include "nm-utils.h" +#include "devices/nm-device.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-keyfile/nm-keyfile-internal.h" +#include "nm-keyfile/nm-keyfile-utils.h" + +#define DEFAULT_CONFIG_MAIN_FILE NMCONFDIR "/NetworkManager.conf" +#define DEFAULT_CONFIG_DIR NMCONFDIR "/conf.d" +#define DEFAULT_CONFIG_MAIN_FILE_OLD NMCONFDIR "/nm-system-settings.conf" +#define DEFAULT_SYSTEM_CONFIG_DIR NMLIBDIR "/conf.d" +#define RUN_CONFIG_DIR NMRUNDIR "/conf.d" +#define DEFAULT_NO_AUTO_DEFAULT_FILE NMSTATEDIR "/no-auto-default.state" +#define DEFAULT_INTERN_CONFIG_FILE NMSTATEDIR "/NetworkManager-intern.conf" +#define DEFAULT_STATE_FILE NMSTATEDIR "/NetworkManager.state" + +/*****************************************************************************/ + +struct NMConfigCmdLineOptions { + char * config_main_file; + char * intern_config_file; + char * config_dir; + char * system_config_dir; + char * state_file; + char * no_auto_default_file; + char * plugins; + NMConfigConfigureAndQuitType configure_and_quit; + + gboolean is_debug; + char * connectivity_uri; + + /* We store interval as signed internally to track whether it's + * set or not via GOptionEntry + */ + int connectivity_interval; + char *connectivity_response; + + /* @first_start is not provided by command line. It is a convenient hack + * to pass in an argument to NMConfig. This makes NMConfigCmdLineOptions a + * misnomer. + * + * It is true, if NM is started the first time -- contrary to a restart + * during the same boot up. That is determined by the content of the + * /run/NetworManager state directory. */ + bool first_start; +}; + +typedef struct { + NMConfigState p; +} State; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_CMD_LINE_OPTIONS, PROP_ATOMIC_SECTION_PREFIXES, ); + +enum { + SIGNAL_CONFIG_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + NMConfigCmdLineOptions cli; + + NMConfigData *config_data; + NMConfigData *config_data_orig; + + char *config_dir; + char *system_config_dir; + char *no_auto_default_file; + char *intern_config_file; + + char *log_level; + char *log_domains; + + NMConfigConfigureAndQuitType configure_and_quit; + + char **atomic_section_prefixes; + + /* The state. This is actually a mutable data member and it makes sense: + * The regular config is immutable (NMConfigData) and can old be swapped + * as a whole (via nm_config_set_values() or during reload). Thus, it can + * be changed, but it is still immutable and is swapped atomically as a + * whole. Also, we emit a config-changed signal on that occasion. + * + * For state, there are no events. You can query it and set it. + * It only gets read *once* at startup, and later is cached and only + * written out to disk. Hence, no need for the immutable dance here + * because the state changes only on explicit actions from the daemon + * itself. */ + State *state; + + /* the hash table of device states. It is only loaded from disk + * once and kept immutable afterwards. + * + * We also read all state file at once. We don't want to support + * that they are changed outside of NM (at least not while NM is running). + * Hence, we read them once, that's it. */ + GHashTable *device_states; + + char **warnings; +} NMConfigPrivate; + +struct _NMConfig { + GObject parent; + NMConfigPrivate _priv; +}; + +struct _NMConfigClass { + GObjectClass parent; +}; + +static void nm_config_initable_iface_init(GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE(NMConfig, + nm_config, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, nm_config_initable_iface_init);) + +#define NM_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMConfig, NM_IS_CONFIG) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "config", __VA_ARGS__) + +/*****************************************************************************/ + +static void +_set_config_data(NMConfig *self, NMConfigData *new_data, NMConfigChangeFlags reload_flags); + +/*****************************************************************************/ + +int +nm_config_parse_boolean(const char *str, int default_value) +{ + return _nm_utils_ascii_str_to_bool(str, default_value); +} + +int +nm_config_keyfile_get_boolean(const GKeyFile *keyfile, + const char * section, + const char * key, + int default_value) +{ + gs_free char *str = NULL; + + g_return_val_if_fail(keyfile != NULL, default_value); + g_return_val_if_fail(section != NULL, default_value); + g_return_val_if_fail(key != NULL, default_value); + + str = g_key_file_get_value((GKeyFile *) keyfile, section, key, NULL); + return nm_config_parse_boolean(str, default_value); +} + +gint64 +nm_config_keyfile_get_int64(const GKeyFile *keyfile, + const char * section, + const char * key, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + gint64 v; + int errsv; + char * str; + + g_return_val_if_fail(keyfile, fallback); + g_return_val_if_fail(section, fallback); + g_return_val_if_fail(key, fallback); + + str = g_key_file_get_value((GKeyFile *) keyfile, section, key, NULL); + v = _nm_utils_ascii_str_to_int64(str, base, min, max, fallback); + if (str) { + errsv = errno; + g_free(str); + errno = errsv; + } + return v; +} + +char * +nm_config_keyfile_get_value(const GKeyFile * keyfile, + const char * section, + const char * key, + NMConfigGetValueFlags flags) +{ + char *value; + + if (NM_FLAGS_HAS(flags, NM_CONFIG_GET_VALUE_RAW)) + value = g_key_file_get_value((GKeyFile *) keyfile, section, key, NULL); + else + value = g_key_file_get_string((GKeyFile *) keyfile, section, key, NULL); + + if (!value) + return NULL; + + if (NM_FLAGS_HAS(flags, NM_CONFIG_GET_VALUE_STRIP)) + g_strstrip(value); + + if (NM_FLAGS_HAS(flags, NM_CONFIG_GET_VALUE_NO_EMPTY) && !*value) { + g_free(value); + return NULL; + } + + return value; +} + +void +nm_config_keyfile_set_string_list(GKeyFile * keyfile, + const char * group, + const char * key, + const char *const *strv, + gssize len) +{ + gsize l; + char *new_value; + + if (len < 0) + len = strv ? g_strv_length((char **) strv) : 0; + + g_key_file_set_string_list(keyfile, group, key, strv, len); + + /* g_key_file_set_string_list() appends a trailing separator to the value. + * We don't like that, get rid of it. */ + + new_value = g_key_file_get_value(keyfile, group, key, NULL); + if (!new_value) + return; + + l = strlen(new_value); + if (l > 0 && new_value[l - 1] == NM_CONFIG_KEYFILE_LIST_SEPARATOR) { + /* Maybe we should check that value doesn't end with "\\,", i.e. + * with an escaped separator. But the way g_key_file_set_string_list() + * is implemented (currently), it always adds a trailing separator. */ + new_value[l - 1] = '\0'; + g_key_file_set_value(keyfile, group, key, new_value); + } + g_free(new_value); +} + +/*****************************************************************************/ + +const char *const * +nm_config_get_warnings(NMConfig *config) +{ + return (const char *const *) NM_CONFIG_GET_PRIVATE(config)->warnings; +} + +void +nm_config_clear_warnings(NMConfig *config) +{ + nm_clear_pointer(&NM_CONFIG_GET_PRIVATE(config)->warnings, g_strfreev); +} + +NMConfigData * +nm_config_get_data(NMConfig *config) +{ + g_return_val_if_fail(config != NULL, NULL); + + return NM_CONFIG_GET_PRIVATE(config)->config_data; +} + +/* The NMConfigData instance is reloadable and will be swapped on reload. + * nm_config_get_data_orig() returns the original configuration, when the NMConfig + * instance was created. */ +NMConfigData * +nm_config_get_data_orig(NMConfig *config) +{ + g_return_val_if_fail(config != NULL, NULL); + + return NM_CONFIG_GET_PRIVATE(config)->config_data_orig; +} + +const char * +nm_config_get_log_level(NMConfig *config) +{ + g_return_val_if_fail(config != NULL, NULL); + + return NM_CONFIG_GET_PRIVATE(config)->log_level; +} + +const char * +nm_config_get_log_domains(NMConfig *config) +{ + g_return_val_if_fail(config != NULL, NULL); + + return NM_CONFIG_GET_PRIVATE(config)->log_domains; +} + +NMConfigConfigureAndQuitType +nm_config_get_configure_and_quit(NMConfig *config) +{ + return NM_CONFIG_GET_PRIVATE(config)->configure_and_quit; +} + +gboolean +nm_config_get_is_debug(NMConfig *config) +{ + return NM_CONFIG_GET_PRIVATE(config)->cli.is_debug; +} + +gboolean +nm_config_get_first_start(NMConfig *config) +{ + return NM_CONFIG_GET_PRIVATE(config)->cli.first_start; +} + +const char * +nm_config_get_no_auto_default_file(NMConfig *config) +{ + return NM_CONFIG_GET_PRIVATE(config)->no_auto_default_file; +} + +/*****************************************************************************/ + +static char ** +no_auto_default_from_file(const char *no_auto_default_file) +{ + gs_free char *data = NULL; + const char ** list = NULL; + gsize i; + + if (no_auto_default_file && g_file_get_contents(no_auto_default_file, &data, NULL, NULL)) + list = nm_utils_strsplit_set(data, "\n"); + + if (list) { + for (i = 0; list[i]; i++) + list[i] = nm_utils_str_utf8safe_unescape_cp(list[i], NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + } + + /* The returned buffer here is not at all compact. That means, it has additional + * memory allocations and is larger than needed. That means, you should not keep + * this result around, only process it further and free it. */ + return (char **) list; +} + +static gboolean +no_auto_default_to_file(const char * no_auto_default_file, + const char *const *no_auto_default, + GError ** error) +{ + nm_auto_free_gstring GString *data = NULL; + gsize i; + + data = g_string_new(""); + for (i = 0; no_auto_default && no_auto_default[i]; i++) { + gs_free char *s_to_free = NULL; + const char * s = no_auto_default[i]; + + s = nm_utils_str_utf8safe_escape(s, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL + | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII, + &s_to_free); + g_string_append(data, s); + g_string_append_c(data, '\n'); + } + return g_file_set_contents(no_auto_default_file, data->str, data->len, error); +} + +gboolean +nm_config_get_no_auto_default_for_device(NMConfig *self, NMDevice *device) +{ + NMConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_CONFIG(self), FALSE); + + priv = NM_CONFIG_GET_PRIVATE(self); + + if (priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) + return TRUE; + + return nm_config_data_get_no_auto_default_for_device(priv->config_data, device); +} + +void +nm_config_set_no_auto_default_for_device(NMConfig *self, NMDevice *device) +{ + NMConfigPrivate * priv; + GError * error = NULL; + NMConfigData * new_data = NULL; + gs_free char * spec_to_free = NULL; + const char * ifname; + const char * hw_address; + const char * spec; + const char *const * no_auto_default_current; + gs_free const char **no_auto_default_new = NULL; + gboolean is_fake; + gsize len; + gssize idx; + + g_return_if_fail(NM_IS_CONFIG(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + + priv = NM_CONFIG_GET_PRIVATE(self); + + hw_address = nm_device_get_permanent_hw_address_full(device, TRUE, &is_fake); + + if (!hw_address) { + /* No MAC address, not even a fake one. We don't do anything for this device. */ + return; + } + + if (is_fake) { + /* A fake MAC address, no point in storing it to the file. + * Also, nm_match_spec_device() would ignore fake MAC addresses. + * + * Instead, try the interface-name... */ + ifname = nm_device_get_ip_iface(device); + if (!nm_utils_ifname_valid_kernel(ifname, NULL)) + return; + + spec_to_free = g_strdup_printf(NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", ifname); + spec = spec_to_free; + } else + spec = hw_address; + + no_auto_default_current = nm_config_data_get_no_auto_default(priv->config_data); + + len = NM_PTRARRAY_LEN(no_auto_default_current); + + idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) no_auto_default_current, + len, + spec, + nm_strcmp_with_data, + NULL, + NULL, + NULL); + if (idx >= 0) { + /* @spec is already blocked. We don't have to update our in-memory representation. + * Maybe we should write to no_auto_default_file anew, but let's save that too. */ + return; + } + + idx = ~idx; + + no_auto_default_new = g_new(const char *, len + 2); + if (idx > 0) + memcpy(no_auto_default_new, no_auto_default_current, sizeof(const char *) * idx); + no_auto_default_new[idx] = spec; + if (idx < len) + memcpy(&no_auto_default_new[idx + 1], + &no_auto_default_current[idx], + sizeof(const char *) * (len - idx)); + no_auto_default_new[len + 1] = NULL; + + if (!no_auto_default_to_file(priv->no_auto_default_file, no_auto_default_new, &error)) { + _LOGW("Could not update no-auto-default.state file: %s", error->message); + g_error_free(error); + } + + new_data = nm_config_data_new_update_no_auto_default(priv->config_data, no_auto_default_new); + + _set_config_data(self, new_data, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT); +} + +/*****************************************************************************/ + +static void +_nm_config_cmd_line_options_clear(NMConfigCmdLineOptions *cli) +{ + nm_clear_g_free(&cli->config_main_file); + nm_clear_g_free(&cli->config_dir); + nm_clear_g_free(&cli->system_config_dir); + nm_clear_g_free(&cli->no_auto_default_file); + nm_clear_g_free(&cli->intern_config_file); + nm_clear_g_free(&cli->state_file); + nm_clear_g_free(&cli->plugins); + cli->configure_and_quit = NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED; + cli->is_debug = FALSE; + nm_clear_g_free(&cli->connectivity_uri); + nm_clear_g_free(&cli->connectivity_response); + cli->connectivity_interval = -1; + cli->first_start = FALSE; +} + +static void +_nm_config_cmd_line_options_copy(const NMConfigCmdLineOptions *cli, NMConfigCmdLineOptions *dst) +{ + g_return_if_fail(cli); + g_return_if_fail(dst); + g_return_if_fail(cli != dst); + + _nm_config_cmd_line_options_clear(dst); + dst->config_dir = g_strdup(cli->config_dir); + dst->system_config_dir = g_strdup(cli->system_config_dir); + dst->config_main_file = g_strdup(cli->config_main_file); + dst->no_auto_default_file = g_strdup(cli->no_auto_default_file); + dst->intern_config_file = g_strdup(cli->intern_config_file); + dst->state_file = g_strdup(cli->state_file); + dst->plugins = g_strdup(cli->plugins); + dst->configure_and_quit = cli->configure_and_quit; + dst->is_debug = cli->is_debug; + dst->connectivity_uri = g_strdup(cli->connectivity_uri); + dst->connectivity_response = g_strdup(cli->connectivity_response); + dst->connectivity_interval = cli->connectivity_interval; + dst->first_start = cli->first_start; +} + +NMConfigCmdLineOptions * +nm_config_cmd_line_options_new(gboolean first_start) +{ + NMConfigCmdLineOptions *cli = g_new0(NMConfigCmdLineOptions, 1); + + _nm_config_cmd_line_options_clear(cli); + + cli->first_start = first_start; + + return cli; +} + +void +nm_config_cmd_line_options_free(NMConfigCmdLineOptions *cli) +{ + g_return_if_fail(cli); + + _nm_config_cmd_line_options_clear(cli); + g_free(cli); +} + +static NMConfigConfigureAndQuitType +string_to_configure_and_quit(const char *value, GError **error) +{ + NMConfigConfigureAndQuitType ret; + + if (value == NULL) + return NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED; + + if (nm_streq(value, "initrd")) + return NM_CONFIG_CONFIGURE_AND_QUIT_INITRD; + + ret = nm_config_parse_boolean(value, NM_CONFIG_CONFIGURE_AND_QUIT_INVALID); + if (ret == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID) + g_set_error(error, 1, 0, N_("'%s' is not valid"), value); + + return ret; +} + +static gboolean +parse_configure_and_quit(const char *option_name, + const char *value, + gpointer user_data, + GError ** error) +{ + NMConfigCmdLineOptions *cli = user_data; + + if (value == NULL) + cli->configure_and_quit = NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED; + else + cli->configure_and_quit = string_to_configure_and_quit(value, error); + + if (cli->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID) { + g_prefix_error(error, N_("Bad '%s' option: "), option_name); + return FALSE; + } + + return TRUE; +} + +void +nm_config_cmd_line_options_add_to_entries(NMConfigCmdLineOptions *cli, GOptionContext *opt_ctx) +{ + GOptionGroup *group; + GOptionEntry config_options[] = { + {"config", + 0, + 0, + G_OPTION_ARG_FILENAME, + &cli->config_main_file, + N_("Config file location"), + DEFAULT_CONFIG_MAIN_FILE}, + {"config-dir", + 0, + 0, + G_OPTION_ARG_FILENAME, + &cli->config_dir, + N_("Config directory location"), + DEFAULT_CONFIG_DIR}, + {"system-config-dir", + 0, + 0, + G_OPTION_ARG_FILENAME, + &cli->system_config_dir, + N_("System config directory location"), + DEFAULT_SYSTEM_CONFIG_DIR}, + {"intern-config", + 0, + 0, + G_OPTION_ARG_FILENAME, + &cli->intern_config_file, + N_("Internal config file location"), + DEFAULT_INTERN_CONFIG_FILE}, + {"state-file", + 0, + 0, + G_OPTION_ARG_FILENAME, + &cli->state_file, + N_("State file location"), + DEFAULT_STATE_FILE}, + {"no-auto-default", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_FILENAME, + &cli->no_auto_default_file, + N_("State file for no-auto-default devices"), + DEFAULT_NO_AUTO_DEFAULT_FILE}, + {"plugins", + 0, + 0, + G_OPTION_ARG_STRING, + &cli->plugins, + N_("List of plugins separated by ','"), + NM_CONFIG_DEFAULT_MAIN_PLUGINS}, + {"configure-and-quit", + 0, + G_OPTION_FLAG_OPTIONAL_ARG, + G_OPTION_ARG_CALLBACK, + parse_configure_and_quit, + N_("Quit after initial configuration"), + NULL}, + {"debug", + 'd', + 0, + G_OPTION_ARG_NONE, + &cli->is_debug, + N_("Don't become a daemon, and log to stderr"), + NULL}, + + /* These three are hidden for now, and should eventually just go away. */ + {"connectivity-uri", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, + &cli->connectivity_uri, + N_("An http(s) address for checking internet connectivity"), + "http://example.com"}, + {"connectivity-interval", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_INT, + &cli->connectivity_interval, + N_("The interval between connectivity checks (in seconds)"), + G_STRINGIFY(NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL)}, + {"connectivity-response", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, + &cli->connectivity_response, + N_("The expected start of the response"), + NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE}, + {0}, + }; + + g_return_if_fail(opt_ctx); + g_return_if_fail(cli); + + group = g_option_group_new("nm", + N_("NetworkManager options"), + N_("Show NetworkManager options"), + cli, + NULL); + + g_option_group_add_entries(group, config_options); + g_option_context_add_group(opt_ctx, group); +} + +/*****************************************************************************/ + +GKeyFile * +nm_config_create_keyfile() +{ + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + g_key_file_set_list_separator(keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR); + return keyfile; +} + +/* this is an external variable, to make loading testable. Other then that, + * no code is supposed to change this. */ +guint _nm_config_match_nm_version = NM_VERSION; +char *_nm_config_match_env = NULL; + +static gboolean +ignore_config_snippet(GKeyFile *keyfile, gboolean is_base_config) +{ + GSList * specs; + gboolean as_bool; + NMMatchSpecMatchType match_type; + + if (is_base_config) + return FALSE; + + if (!g_key_file_has_key(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONFIG, + NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, + NULL)) + return FALSE; + + /* first, let's try to parse the value as plain boolean. If that is possible, we don't treat + * the value as match-spec. */ + as_bool = nm_config_keyfile_get_boolean(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONFIG, + NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, + -1); + if (as_bool != -1) + return !as_bool; + + if (G_UNLIKELY(!_nm_config_match_env)) { + const char *e; + + e = g_getenv("NM_CONFIG_ENABLE_TAG"); + _nm_config_match_env = g_strdup(e ?: ""); + } + + /* second, interpret the value as match-spec. */ + specs = nm_config_get_match_spec(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONFIG, + NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE, + NULL); + match_type = nm_match_spec_config(specs, _nm_config_match_nm_version, _nm_config_match_env); + g_slist_free_full(specs, g_free); + + return match_type != NM_MATCH_SPEC_MATCH; +} + +static int +_sort_groups_cmp(const char **pa, const char **pb, gpointer dummy) +{ + const char *a = *pa; + const char *b = *pb; + gboolean a_is_connection, b_is_connection; + gboolean a_is_device, b_is_device; + + a_is_connection = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + b_is_connection = NM_STR_HAS_PREFIX(b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + + if (a_is_connection != b_is_connection) { + /* one is a [connection*] entry, the other not. We sort [connection*] entries + * after. */ + if (a_is_connection) + return 1; + return -1; + } + if (a_is_connection) { + /* both are [connection.\+] entries. Reverse their order. + * One of the sections might be literally [connection]. That section + * is special and its order will be fixed later. It doesn't actually + * matter here how it compares with [connection.\+] sections. */ + return pa > pb ? -1 : 1; + } + + a_is_device = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE); + b_is_device = NM_STR_HAS_PREFIX(b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE); + + if (a_is_device != b_is_device) { + /* one is a [device*] entry, the other not. We sort [device*] entries + * after. */ + if (a_is_device) + return 1; + return -1; + } + if (a_is_device) { + /* both are [device.\+] entries. Reverse their order. + * One of the sections might be literally [device]. That section + * is special and its order will be fixed later. It doesn't actually + * matter here how it compares with [device.\+] sections. */ + return pa > pb ? -1 : 1; + } + + /* don't reorder the rest. */ + return 0; +} + +void +_nm_config_sort_groups(char **groups, gsize ngroups) +{ + if (ngroups > 1) { + g_qsort_with_data(groups, + ngroups, + sizeof(char *), + (GCompareDataFunc) _sort_groups_cmp, + NULL); + } +} + +static gboolean +_setting_is_device_spec(const char *group, const char *key) +{ +#define _IS(group_v, key_v) (nm_streq(group, "" group_v "") && nm_streq(key, "" key_v "")) + return _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT) + || _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER) + || _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY) + || _IS(NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES) + || (NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) + && nm_streq(key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE)) + || (NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE) + && nm_streq(key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE)); +} + +static gboolean +_setting_is_string_list(const char *group, const char *key) +{ + return _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS) + || _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG) + || _IS(NM_CONFIG_KEYFILE_GROUP_LOGGING, NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS) + || NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST); +#undef _IS +} + +typedef struct { + char * group; + const char *const *keys; + bool is_prefix : 1; + bool is_connection : 1; +} ConfigGroup; + +/* The following comment is used by check-config-options.sh, don't remove it. */ +/* START OPTION LIST */ + +static const ConfigGroup config_groups[] = { + { + .group = NM_CONFIG_KEYFILE_GROUP_MAIN, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT, + NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT, + NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT, + NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG, + NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, + NM_CONFIG_KEYFILE_KEY_MAIN_DNS, + NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE, + NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER, + NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES, + NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT, + NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS, + NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER, + NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, + NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUP_LOGGING, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT, + NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND, + NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS, + NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE, + NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUP_KEYFILE, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME, + NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH, + NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUP_IFUPDOWN, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE, + .is_prefix = TRUE, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, + NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, + NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, + NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, + NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, + NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN, + .is_prefix = TRUE, + .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS, ), + }, + { + .group = NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION, + .is_prefix = TRUE, + .is_connection = TRUE, + .keys = + NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ), + }, + {} /* sentinel */ +}; + +/* The following comment is used by check-config-options.sh, don't remove it. */ +/* END OPTION LIST */ + +static gboolean +check_config_key(const char *group, const char *key) +{ + const ConfigGroup *g; + const char *const *k; + const char ** ptr; + +#if NM_MORE_ASSERTS > 10 + { + static gboolean checked = FALSE; + const char ** ptr1, **ptr2; + + /* check for duplicate elements in the static list */ + + if (!checked) { + for (ptr1 = __start_connection_defaults; ptr1 < __stop_connection_defaults; ptr1++) { + for (ptr2 = ptr1 + 1; ptr2 < __stop_connection_defaults; ptr2++) + nm_assert(!nm_streq(*ptr1, *ptr2)); + } + checked = TRUE; + } + } +#endif + + for (g = config_groups; g->group; g++) { + if ((!g->is_prefix && nm_streq(group, g->group)) + || (g->is_prefix && g_str_has_prefix(group, g->group))) + break; + } + + if (!g->group) + return FALSE; + + for (k = g->keys; *k; k++) { + if (nm_streq(key, *k)) + return TRUE; + } + + if (g->is_connection) { + for (ptr = __start_connection_defaults; ptr < __stop_connection_defaults; ptr++) { + if (nm_streq(key, *ptr)) + return TRUE; + } + return FALSE; + } + + return FALSE; +} + +static gboolean +read_config(GKeyFile * keyfile, + gboolean is_base_config, + const char *dirname, + const char *path, + GPtrArray * warnings, + GError ** error) +{ + nm_auto_unref_keyfile GKeyFile *kf = NULL; + gs_strfreev char ** groups = NULL; + gs_free char * path_free = NULL; + gsize ngroups; + gsize nkeys; + int g; + int k; + + g_return_val_if_fail(keyfile, FALSE); + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (dirname) { + path_free = g_build_filename(dirname, path, NULL); + path = path_free; + } + + if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE) { + g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "file %s not found", path); + return FALSE; + } + + _LOGD("Reading config file '%s'", path); + + kf = nm_config_create_keyfile(); + if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, error)) { + g_prefix_error(error, "%s: ", path); + return FALSE; + } + + if (ignore_config_snippet(kf, is_base_config)) + return TRUE; + + /* the config-group is internal to every configuration snippets. It doesn't make sense + * to merge it into the global configuration, and it doesn't make sense to preserve the + * group beyond this point. */ + g_key_file_remove_group(kf, NM_CONFIG_KEYFILE_GROUP_CONFIG, NULL); + + /* Override the current settings with the new ones */ + groups = g_key_file_get_groups(kf, &ngroups); + if (!groups) + ngroups = 0; + + /* Within one file we reverse the order of the '[connection.\+] sections. + * Here we merge the current file (@kf) into @keyfile. As we merge multiple + * files, earlier sections (with lower priority) will be added first. + * But within one file, we want a top-to-bottom order. This means we + * must reverse the order within each file. + * At the very end, we will revert the order of all sections again and + * get thus the right behavior. This final reversing is done in + * NMConfigData:_get_connection_infos(). */ + _nm_config_sort_groups(groups, ngroups); + + for (g = 0; groups && groups[g]; g++) { + const char * group = groups[g]; + gs_strfreev char **keys = NULL; + + if (NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN)) { + /* internal groups cannot be set by user configuration. */ + continue; + } + keys = g_key_file_get_keys(kf, group, &nkeys, NULL); + if (!keys) + continue; + for (k = 0; keys[k]; k++) { + gs_free char *new_value = NULL; + const char * key; + char last_char; + gsize key_len; + + key = keys[k]; + nm_assert(key && *key); + + if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS) + || NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + /* these keys are protected. We ignore them if the user sets them. */ + continue; + } + + if (nm_streq(key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS)) { + /* the "was" key is protected and it cannot be set by user configuration. */ + continue; + } + + key_len = strlen(key); + last_char = key[key_len - 1]; + if (key_len > 1 && (last_char == '+' || last_char == '-')) { + gs_free char *base_key = g_strndup(key, key_len - 1); + gboolean is_string_list; + gboolean old_val_was_set = FALSE; + + is_string_list = _setting_is_string_list(group, base_key); + + if (is_string_list || _setting_is_device_spec(group, base_key)) { + gs_unref_ptrarray GPtrArray *new = g_ptr_array_new_with_free_func(g_free); + char ** iter_val; + gs_strfreev char **old_val = NULL; + gs_free char ** new_val = NULL; + + if (is_string_list) { + gs_free_error GError *old_error = NULL; + + old_val = + g_key_file_get_string_list(keyfile, group, base_key, NULL, &old_error); + new_val = g_key_file_get_string_list(kf, group, key, NULL, NULL); + if (nm_streq(group, NM_CONFIG_KEYFILE_GROUP_MAIN) + && nm_streq(base_key, "plugins")) { + old_val_was_set = !nm_keyfile_error_is_not_found(old_error); + if (!old_val && !old_val_was_set) { + /* we must fill the unspecified value with the compile-time default. */ + g_key_file_set_value(keyfile, + group, + base_key, + NM_CONFIG_DEFAULT_MAIN_PLUGINS); + old_val = g_key_file_get_string_list(keyfile, + group, + base_key, + NULL, + NULL); + old_val_was_set = TRUE; + } + } + } else { + gs_free char *old_sval = + nm_config_keyfile_get_value(keyfile, + group, + base_key, + NM_CONFIG_GET_VALUE_TYPE_SPEC); + gs_free char *new_sval = + nm_config_keyfile_get_value(kf, + group, + key, + NM_CONFIG_GET_VALUE_TYPE_SPEC); + nm_auto_free_slist GSList *old_specs = nm_match_spec_split(old_sval); + nm_auto_free_slist GSList *new_specs = nm_match_spec_split(new_sval); + + /* the key is a device spec. This is a special kind of string-list, that + * we must split differently. */ + old_val = _nm_utils_slist_to_strv(old_specs, FALSE); + new_val = _nm_utils_slist_to_strv(new_specs, FALSE); + } + + /* merge the string lists, by omitting duplicates. */ + + for (iter_val = old_val; iter_val && *iter_val; iter_val++) { + if (last_char != '-' + || nm_utils_strv_find_first(new_val, -1, *iter_val) < 0) + g_ptr_array_add(new, g_strdup(*iter_val)); + } + for (iter_val = new_val; iter_val && *iter_val; iter_val++) { + /* don't add duplicates. That means an "option=a,b"; "option+=a,c" results in "option=a,b,c" */ + if (last_char == '+' + && nm_utils_strv_find_first(old_val, -1, *iter_val) < 0) + g_ptr_array_add(new, *iter_val); + else + g_free(*iter_val); + } + + if (new->len > 0) { + if (is_string_list) + nm_config_keyfile_set_string_list(keyfile, + group, + base_key, + (const char *const *) new->pdata, + new->len); + else { + nm_auto_free_slist GSList *specs = NULL; + gs_free char * specs_joined = NULL; + + g_ptr_array_add(new, NULL); + specs = _nm_utils_strv_to_slist((char **) new->pdata, FALSE); + + specs_joined = nm_match_spec_join(specs); + + g_key_file_set_value(keyfile, group, base_key, specs_joined); + } + } else { + if (is_string_list && !old_val_was_set) + g_key_file_remove_key(keyfile, group, base_key, NULL); + else + g_key_file_set_value(keyfile, group, base_key, ""); + } + } else { + /* For any other settings we don't support extending the option with +/-. + * Just drop the key. */ + } + continue; + } + + new_value = g_key_file_get_value(kf, group, key, NULL); + g_key_file_set_value(keyfile, group, key, new_value); + + if (!check_config_key(group, key)) { + g_ptr_array_add(warnings, + g_strdup_printf("unknown key '%s' in section [%s] of file '%s'", + key, + group, + path)); + } + } + } + + return TRUE; +} + +static gboolean +read_base_config(GKeyFile * keyfile, + const char *cli_config_main_file, + char ** out_config_main_file, + GPtrArray * warnings, + GError ** error) +{ + GError *my_error = NULL; + + g_return_val_if_fail(keyfile, FALSE); + g_return_val_if_fail(out_config_main_file && !*out_config_main_file, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* Try a user-specified config file first */ + if (cli_config_main_file) { + /* Bad user-specific config file path is a hard error */ + if (read_config(keyfile, TRUE, NULL, cli_config_main_file, warnings, error)) { + *out_config_main_file = g_strdup(cli_config_main_file); + return TRUE; + } else + return FALSE; + } + + /* Even though we prefer NetworkManager.conf, we need to check the + * old nm-system-settings.conf first to preserve compat with older + * setups. In package managed systems dropping a NetworkManager.conf + * onto the system would make NM use it instead of nm-system-settings.conf, + * changing behavior during an upgrade. We don't want that. + */ + + /* Try deprecated nm-system-settings.conf first */ + if (read_config(keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE_OLD, warnings, &my_error)) { + *out_config_main_file = g_strdup(DEFAULT_CONFIG_MAIN_FILE_OLD); + return TRUE; + } + + if (!g_error_matches(my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) { + _LOGW("Old default config file invalid: %s", my_error->message); + } + g_clear_error(&my_error); + + /* Try the standard config file location next */ + if (read_config(keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE, warnings, &my_error)) { + *out_config_main_file = g_strdup(DEFAULT_CONFIG_MAIN_FILE); + return TRUE; + } + + if (!g_error_matches(my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) { + _LOGW("Default config file invalid: %s", my_error->message); + g_propagate_error(error, my_error); + return FALSE; + } + g_clear_error(&my_error); + + /* If for some reason no config file exists, use the default + * config file path. + */ + *out_config_main_file = g_strdup(DEFAULT_CONFIG_MAIN_FILE); + _LOGI("No config file found or given; using %s", DEFAULT_CONFIG_MAIN_FILE); + return TRUE; +} + +static GPtrArray * +_get_config_dir_files(const char *config_dir) +{ + GFile * dir; + GFileEnumerator *direnum; + GFileInfo * info; + GPtrArray * confs; + const char * name; + + g_return_val_if_fail(config_dir, NULL); + + confs = g_ptr_array_new_with_free_func(g_free); + if (!*config_dir) + return confs; + + dir = g_file_new_for_path(config_dir); + direnum = g_file_enumerate_children(dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, NULL); + if (direnum) { + while ((info = g_file_enumerator_next_file(direnum, NULL, NULL))) { + name = g_file_info_get_name(info); + if (NM_STR_HAS_SUFFIX(name, ".conf")) + g_ptr_array_add(confs, g_strdup(name)); + g_object_unref(info); + } + g_object_unref(direnum); + } + g_object_unref(dir); + + g_ptr_array_sort(confs, nm_strcmp_p); + return confs; +} + +static void +_confs_to_description(GString *str, const GPtrArray *confs, const char *name) +{ + guint i; + + if (!confs->len) + return; + + for (i = 0; i < confs->len; i++) { + if (i == 0) + g_string_append_printf(str, " (%s: ", name); + else + g_string_append(str, ", "); + g_string_append(str, confs->pdata[i]); + } + g_string_append(str, ")"); +} + +static GKeyFile * +read_entire_config(const NMConfigCmdLineOptions *cli, + const char * config_dir, + const char * system_config_dir, + char ** out_config_main_file, + char ** out_config_description, + GPtrArray * warnings, + GError ** error) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_ptrarray GPtrArray *system_confs = NULL; + gs_unref_ptrarray GPtrArray *confs = NULL; + gs_unref_ptrarray GPtrArray *run_confs = NULL; + guint i; + gs_free char * o_config_main_file = NULL; + const char * run_config_dir = ""; + + nm_assert(config_dir); + nm_assert(system_config_dir); + nm_assert(!out_config_main_file || !*out_config_main_file); + nm_assert(!out_config_description || !*out_config_description); + nm_assert(!error || !*error); + nm_assert(warnings); + + if (("" RUN_CONFIG_DIR)[0] == '/' && !nm_streq(RUN_CONFIG_DIR, system_config_dir) + && !nm_streq(RUN_CONFIG_DIR, config_dir)) + run_config_dir = RUN_CONFIG_DIR; + + /* create a default configuration file. */ + keyfile = nm_config_create_keyfile(); + + system_confs = _get_config_dir_files(system_config_dir); + confs = _get_config_dir_files(config_dir); + run_confs = _get_config_dir_files(run_config_dir); + + for (i = 0; i < system_confs->len;) { + const char *filename = system_confs->pdata[i]; + + /* if a same named file exists in config_dir or run_config_dir, skip it. */ + if (nm_utils_strv_find_first((char **) confs->pdata, confs->len, filename) >= 0 + || nm_utils_strv_find_first((char **) run_confs->pdata, run_confs->len, filename) + >= 0) { + g_ptr_array_remove_index(system_confs, i); + continue; + } + + if (!read_config(keyfile, FALSE, system_config_dir, filename, warnings, error)) + return NULL; + i++; + } + + for (i = 0; i < run_confs->len;) { + const char *filename = run_confs->pdata[i]; + + /* if a same named file exists in config_dir, skip it. */ + if (nm_utils_strv_find_first((char **) confs->pdata, confs->len, filename) >= 0) { + g_ptr_array_remove_index(run_confs, i); + continue; + } + + if (!read_config(keyfile, FALSE, run_config_dir, filename, warnings, error)) + return NULL; + i++; + } + + /* First read the base config file */ + if (!read_base_config(keyfile, + cli ? cli->config_main_file : NULL, + &o_config_main_file, + warnings, + error)) + return NULL; + + g_assert(o_config_main_file); + + for (i = 0; i < confs->len; i++) { + if (!read_config(keyfile, FALSE, config_dir, confs->pdata[i], warnings, error)) + return NULL; + } + + /* Merge settings from command line. They overwrite everything read from + * config files. */ + + if (cli) { + if (cli->plugins) { + /* plugins is a string list. Set the value directly, so the user has to do proper escaping + * on the command line. */ + g_key_file_set_value(keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", cli->plugins); + } + + switch (cli->configure_and_quit) { + case NM_CONFIG_CONFIGURE_AND_QUIT_INVALID: + g_assert_not_reached(); + break; + case NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED: + /* do nothing */ + break; + case NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED: + g_key_file_set_boolean(keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + "configure-and-quit", + TRUE); + break; + case NM_CONFIG_CONFIGURE_AND_QUIT_INITRD: + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + "configure-and-quit", + "initrd"); + break; + } + + if (cli->connectivity_uri && cli->connectivity_uri[0]) + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + "uri", + cli->connectivity_uri); + if (cli->connectivity_interval >= 0) + g_key_file_set_integer(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + "interval", + cli->connectivity_interval); + if (cli->connectivity_response && cli->connectivity_response[0]) + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + "response", + cli->connectivity_response); + } + + if (out_config_description) { + GString *str; + + str = g_string_new(o_config_main_file); + _confs_to_description(str, system_confs, "lib"); + _confs_to_description(str, run_confs, "run"); + _confs_to_description(str, confs, "etc"); + *out_config_description = g_string_free(str, FALSE); + } + NM_SET_OUT(out_config_main_file, g_steal_pointer(&o_config_main_file)); + + return g_steal_pointer(&keyfile); +} + +static gboolean +_is_atomic_section(const char *const *atomic_section_prefixes, const char *group) +{ + if (atomic_section_prefixes) { + for (; *atomic_section_prefixes; atomic_section_prefixes++) { + if (**atomic_section_prefixes && g_str_has_prefix(group, *atomic_section_prefixes)) + return TRUE; + } + } + return FALSE; +} + +static void +_string_append_val(GString *str, const char *value) +{ + if (!value) + return; + g_string_append_c(str, '+'); + while (TRUE) { + switch (*value) { + case '\0': + return; + case '\\': + case '+': + case '#': + case ':': + g_string_append_c(str, '+'); + /* fall-through */ + default: + g_string_append_c(str, *value); + } + value++; + } +} + +static char * +_keyfile_serialize_section(GKeyFile *keyfile, const char *group) +{ + gs_strfreev char **keys = NULL; + GString * str; + guint k; + + if (keyfile) + keys = g_key_file_get_keys(keyfile, group, NULL, NULL); + if (!keys) + return g_strdup("0#"); + + /* prepend a version. */ + str = g_string_new("1#"); + + for (k = 0; keys[k]; k++) { + const char * key = keys[k]; + gs_free char *value = NULL; + + _string_append_val(str, key); + g_string_append_c(str, ':'); + + value = g_key_file_get_value(keyfile, group, key, NULL); + _string_append_val(str, value); + g_string_append_c(str, '#'); + } + return g_string_free(str, FALSE); +} + +gboolean +nm_config_keyfile_has_global_dns_config(GKeyFile *keyfile, gboolean internal) +{ + gs_strfreev char **groups = NULL; + guint g; + const char * prefix; + + if (!keyfile) + return FALSE; + if (g_key_file_has_group(keyfile, + internal ? NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS + : NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS)) + return TRUE; + + groups = g_key_file_get_groups(keyfile, NULL); + if (!groups) + return FALSE; + + prefix = internal ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN + : NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN; + + for (g = 0; groups[g]; g++) { + if (g_str_has_prefix(groups[g], prefix)) + return TRUE; + } + return FALSE; +} + +/** + * intern_config_read: + * @filename: the filename where to store the internal config + * @keyfile_conf: the merged configuration from user (/etc/NM/NetworkManager.conf). + * @out_needs_rewrite: (allow-none): whether the read keyfile contains inconsistent + * data (compared to @keyfile_conf). If %TRUE, you might want to rewrite + * the file. + * + * Does the opposite of intern_config_write(). It reads the internal configuration. + * Note that the actual format of how the configuration is saved in @filename + * is different then what we return here. NMConfig manages what is written internally + * by having it inside a keyfile_intern. But we don't write that to disk as is. + * Especially, we also store parts of @keyfile_conf as ".was" and on read we compare + * what we have, with what ".was". + * + * Returns: a #GKeyFile instance with the internal configuration. + */ +static GKeyFile * +intern_config_read(const char * filename, + GKeyFile * keyfile_conf, + const char *const *atomic_section_prefixes, + gboolean * out_needs_rewrite) +{ + GKeyFile * keyfile_intern; + GKeyFile * keyfile; + gboolean needs_rewrite = FALSE; + gs_strfreev char **groups = NULL; + guint g, k; + gboolean has_intern = FALSE; + + g_return_val_if_fail(filename, NULL); + + if (!*filename) { + if (out_needs_rewrite) + *out_needs_rewrite = FALSE; + return NULL; + } + + keyfile_intern = nm_config_create_keyfile(); + + keyfile = nm_config_create_keyfile(); + if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, NULL)) { + needs_rewrite = TRUE; + goto out; + } + + groups = g_key_file_get_groups(keyfile, NULL); + for (g = 0; groups && groups[g]; g++) { + gs_strfreev char **keys = NULL; + const char * group = groups[g]; + gboolean is_intern, is_atomic; + + if (nm_streq(group, NM_CONFIG_KEYFILE_GROUP_CONFIG)) + continue; + + keys = g_key_file_get_keys(keyfile, group, NULL, NULL); + if (!keys) + continue; + + is_intern = NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + is_atomic = !is_intern && _is_atomic_section(atomic_section_prefixes, group); + + if (is_atomic) { + gs_free char *conf_section_was = NULL; + gs_free char *conf_section_is = NULL; + + conf_section_is = _keyfile_serialize_section(keyfile_conf, group); + conf_section_was = g_key_file_get_string(keyfile, + group, + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + NULL); + + if (!nm_streq0(conf_section_was, conf_section_is)) { + /* the section no longer matches. Skip it entirely. */ + needs_rewrite = TRUE; + continue; + } + /* we must set the "was" marker in our keyfile, so that we know that the section + * from user config is overwritten. The value doesn't matter, it's just a marker + * that this section is present. */ + g_key_file_set_value(keyfile_intern, + group, + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + ""); + } + + for (k = 0; keys[k]; k++) { + gs_free char *value_set = NULL; + const char * key = keys[k]; + + value_set = g_key_file_get_value(keyfile, group, key, NULL); + + if (is_intern) { + has_intern = TRUE; + g_key_file_set_value(keyfile_intern, group, key, value_set); + } else if (is_atomic) { + if (nm_streq(key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS)) + continue; + g_key_file_set_value(keyfile_intern, group, key, value_set); + } else if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + const char * key_base = &key[NM_STRLEN(NM_CONFIG_KEYFILE_KEYPREFIX_SET)]; + gs_free char *value_was = NULL; + gs_free char *value_conf = NULL; + gs_free char *key_was = + g_strdup_printf(NM_CONFIG_KEYFILE_KEYPREFIX_WAS "%s", key_base); + + if (keyfile_conf) + value_conf = g_key_file_get_value(keyfile_conf, group, key_base, NULL); + value_was = g_key_file_get_value(keyfile, group, key_was, NULL); + + if (!nm_streq0(value_conf, value_was)) { + /* if value_was is no longer the same as @value_conf, it means the user + * changed the configuration since the last write. In this case, we + * drop the value. It also means our file is out-of-date, and we should + * rewrite it. */ + needs_rewrite = TRUE; + continue; + } + has_intern = TRUE; + g_key_file_set_value(keyfile_intern, group, key_base, value_set); + } else if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char * key_base = &key[NM_STRLEN(NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + gs_free char *key_set = + g_strdup_printf(NM_CONFIG_KEYFILE_KEYPREFIX_SET "%s", key_base); + gs_free char *value_was = NULL; + gs_free char *value_conf = NULL; + + if (g_key_file_has_key(keyfile, group, key_set, NULL)) { + /* we have a matching "set" key too. Handle the "was" key there. */ + continue; + } + + if (keyfile_conf) + value_conf = g_key_file_get_value(keyfile_conf, group, key_base, NULL); + value_was = g_key_file_get_value(keyfile, group, key, NULL); + + if (!nm_streq0(value_conf, value_was)) { + /* if value_was is no longer the same as @value_conf, it means the user + * changed the configuration since the last write. In this case, we + * don't overwrite the user-provided value. It also means our file is + * out-of-date, and we should rewrite it. */ + needs_rewrite = TRUE; + continue; + } + has_intern = TRUE; + /* signal the absence of the value. That means, we must propagate the + * "was" key to NMConfigData, so that it knows to hide the corresponding + * user key. */ + g_key_file_set_value(keyfile_intern, group, key, ""); + } else + needs_rewrite = TRUE; + } + } + +out: + /* + * If user configuration specifies global DNS options, the DNS + * options in internal configuration must be deleted. Otherwise, a + * deletion of options from user configuration may cause the + * internal options to appear again. + */ + if (nm_config_keyfile_has_global_dns_config(keyfile_conf, FALSE)) { + if (g_key_file_remove_group(keyfile_intern, + NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + NULL)) + needs_rewrite = TRUE; + for (g = 0; groups && groups[g]; g++) { + if (NM_STR_HAS_PREFIX(groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN) + && groups[g][NM_STRLEN(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)]) { + g_key_file_remove_group(keyfile_intern, groups[g], NULL); + needs_rewrite = TRUE; + } + } + } + + g_key_file_unref(keyfile); + + if (out_needs_rewrite) + *out_needs_rewrite = needs_rewrite; + + _LOGD("intern config file \"%s\"", filename); + + if (!has_intern) { + g_key_file_unref(keyfile_intern); + return NULL; + } + return keyfile_intern; +} + +static int +_intern_config_write_sort_fcn(const char ** a, + const char ** b, + const char *const *atomic_section_prefixes) +{ + const char *g_a = *a; + const char *g_b = *b; + gboolean a_is, b_is; + + a_is = NM_STR_HAS_PREFIX(g_a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + b_is = NM_STR_HAS_PREFIX(g_b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + + if (a_is != b_is) { + if (a_is) + return 1; + return -1; + } + if (!a_is) { + a_is = _is_atomic_section(atomic_section_prefixes, g_a); + b_is = _is_atomic_section(atomic_section_prefixes, g_b); + + if (a_is != b_is) { + if (a_is) + return 1; + return -1; + } + } + return g_strcmp0(g_a, g_b); +} + +static gboolean +intern_config_write(const char * filename, + GKeyFile * keyfile_intern, + GKeyFile * keyfile_conf, + const char *const *atomic_section_prefixes, + GError ** error) +{ + GKeyFile * keyfile; + gs_strfreev char **groups = NULL; + guint g, k; + gboolean success = FALSE; + GError * local = NULL; + + g_return_val_if_fail(filename, FALSE); + + if (!*filename) { + g_set_error(error, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_NOT_FOUND, + "no filename to write (use --intern-config?)"); + return FALSE; + } + + keyfile = nm_config_create_keyfile(); + + if (keyfile_intern) { + groups = g_key_file_get_groups(keyfile_intern, NULL); + if (groups && groups[0]) { + g_qsort_with_data(groups, + g_strv_length(groups), + sizeof(char *), + (GCompareDataFunc) _intern_config_write_sort_fcn, + (gpointer) atomic_section_prefixes); + } + } + for (g = 0; groups && groups[g]; g++) { + gs_strfreev char **keys = NULL; + const char * group = groups[g]; + gboolean is_intern, is_atomic; + + keys = g_key_file_get_keys(keyfile_intern, group, NULL, NULL); + if (!keys) + continue; + + is_intern = NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN); + is_atomic = !is_intern && _is_atomic_section(atomic_section_prefixes, group); + + if (is_atomic) { + if ((!keys[0] + || (!keys[1] && strcmp(keys[0], NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0)) + && !g_key_file_has_group(keyfile_conf, group)) { + /* we are about to save an atomic section. However, we don't have any additional + * keys on our own and there is no user-provided (overlapping) section either. + * We don't have to write an empty section (i.e. skip the useless ".was=0#"). */ + continue; + } else { + gs_free char *conf_section_is = NULL; + + conf_section_is = _keyfile_serialize_section(keyfile_conf, group); + g_key_file_set_string(keyfile, + group, + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + conf_section_is); + g_key_file_set_comment(keyfile, + group, + NULL, + " Overwrites entire section from 'NetworkManager.conf'", + NULL); + } + } + + for (k = 0; keys[k]; k++) { + const char * key = keys[k]; + gs_free char *value_set = NULL; + gs_free char *key_set = NULL; + + if (!is_intern && strcmp(key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0) { + g_warn_if_fail(is_atomic); + continue; + } + + value_set = g_key_file_get_value(keyfile_intern, group, key, NULL); + + if (is_intern || is_atomic) + g_key_file_set_value(keyfile, group, key, value_set); + else { + gs_free char *value_was = NULL; + + if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) { + /* Setting a key with .set prefix has no meaning, as these keys + * are protected. Just set the value you want to set instead. + * Why did this happen?? */ + g_warn_if_reached(); + } else if (NM_STR_HAS_PREFIX_WITH_MORE(key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + const char *key_base = &key[NM_STRLEN(NM_CONFIG_KEYFILE_KEYPREFIX_WAS)]; + + if (NM_STR_HAS_PREFIX_WITH_MORE(key_base, NM_CONFIG_KEYFILE_KEYPREFIX_SET) + || NM_STR_HAS_PREFIX_WITH_MORE(key_base, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) { + g_warn_if_reached(); + continue; + } + + if (g_key_file_has_key(keyfile_intern, group, key_base, NULL)) { + /* There is also a matching key_base entry. Skip processing + * the .was. key ad handle the key_base in the other else branch. */ + continue; + } + + if (keyfile_conf) { + value_was = g_key_file_get_value(keyfile_conf, group, key_base, NULL); + if (value_was) + g_key_file_set_value(keyfile, group, key, value_was); + } + } else { + if (keyfile_conf) { + value_was = g_key_file_get_value(keyfile_conf, group, key, NULL); + if (nm_streq0(value_set, value_was)) { + /* there is no point in storing the identical value as we have via + * user configuration. Skip it. */ + continue; + } + if (value_was) { + gs_free char *key_was = NULL; + + key_was = g_strdup_printf(NM_CONFIG_KEYFILE_KEYPREFIX_WAS "%s", key); + g_key_file_set_value(keyfile, group, key_was, value_was); + } + } + key = key_set = g_strdup_printf(NM_CONFIG_KEYFILE_KEYPREFIX_SET "%s", key); + g_key_file_set_value(keyfile, group, key, value_set); + } + } + } + if (is_intern && g_key_file_has_group(keyfile, group)) { + g_key_file_set_comment(keyfile, + group, + NULL, + " Internal section. Not overwritable via user configuration in " + "'NetworkManager.conf'", + NULL); + } + } + + g_key_file_set_comment( + keyfile, + NULL, + NULL, + " Internal configuration file. This file is written and read\n" + " by NetworkManager and its configuration values are merged\n" + " with the configuration from 'NetworkManager.conf'.\n" + "\n" + " Keys with a \"" NM_CONFIG_KEYFILE_KEYPREFIX_SET "\" prefix specify the value to set.\n" + " A corresponding key with a \"" NM_CONFIG_KEYFILE_KEYPREFIX_WAS + "\" prefix records the value\n" + " of the user configuration at the time of storing the file.\n" + " The value from internal configuration is rejected if the corresponding\n" + " \"" NM_CONFIG_KEYFILE_KEYPREFIX_WAS + "\" key no longer matches the configuration from 'NetworkManager.conf'.\n" + " That means, if you modify a value in 'NetworkManager.conf', the internal\n" + " overwrite no longer matches and is ignored.\n" + "\n" + " Certain sections can only be overwritten whole, not on a per key basis.\n" + " Such sections are marked with a \"" NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS + "\" key that records the user configuration\n" + " at the time of writing.\n" + "\n" + " Internal sections of the form [" NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "*] cannot\n" + " be set by user configuration.\n" + "\n" + " CHANGES TO THIS FILE WILL BE OVERWRITTEN", + NULL); + + success = g_key_file_save_to_file(keyfile, filename, &local); + + _LOGD("write intern config file \"%s\"%s%s", + filename, + success ? "" : ": ", + success ? "" : local->message); + g_key_file_unref(keyfile); + if (!success) + g_propagate_error(error, local); + return success; +} + +/*****************************************************************************/ + +GSList * +nm_config_get_match_spec(const GKeyFile *keyfile, + const char * group, + const char * key, + gboolean * out_has_key) +{ + gs_free char *value = NULL; + + /* nm_match_spec_split() already supports full escaping and is basically + * a modified version of g_key_file_parse_value_as_string(). So we first read + * the raw value (g_key_file_get_value()), and do the parsing ourselves. */ + value = g_key_file_get_value((GKeyFile *) keyfile, group, key, NULL); + if (out_has_key) + *out_has_key = !!value; + return nm_match_spec_split(value); +} + +/*****************************************************************************/ + +gboolean +nm_config_set_global_dns(NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error) +{ + NMConfigPrivate * priv; + GKeyFile * keyfile; + char ** groups; + const NMGlobalDnsConfig *old_global_dns; + guint i; + + g_return_val_if_fail(NM_IS_CONFIG(self), FALSE); + + priv = NM_CONFIG_GET_PRIVATE(self); + g_return_val_if_fail(priv->config_data, FALSE); + + old_global_dns = nm_config_data_get_global_dns_config(priv->config_data); + if (old_global_dns && !nm_global_dns_config_is_internal(old_global_dns)) { + g_set_error_literal(error, + 1, + 0, + "Global DNS configuration already set via configuration file"); + return FALSE; + } + + keyfile = nm_config_data_clone_keyfile_intern(priv->config_data); + + /* Remove existing groups */ + g_key_file_remove_group(keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL); + groups = g_key_file_get_groups(keyfile, NULL); + for (i = 0; groups[i]; i++) { + if (NM_STR_HAS_PREFIX(groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)) + g_key_file_remove_group(keyfile, groups[i], NULL); + } + g_strfreev(groups); + + /* An empty configuration removes everything from internal configuration file */ + if (nm_global_dns_config_is_empty(global_dns)) + goto done; + + /* Set new values */ + nm_config_keyfile_set_string_list(keyfile, + NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES, + nm_global_dns_config_get_searches(global_dns), + -1); + + nm_config_keyfile_set_string_list(keyfile, + NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS, + nm_global_dns_config_get_options(global_dns), + -1); + + for (i = 0; i < nm_global_dns_config_get_num_domains(global_dns); i++) { + NMGlobalDnsDomain *domain = nm_global_dns_config_get_domain(global_dns, i); + gs_free char * group_name = NULL; + + group_name = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN "%s", + nm_global_dns_domain_get_name(domain)); + + nm_config_keyfile_set_string_list(keyfile, + group_name, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS, + nm_global_dns_domain_get_servers(domain), + -1); + nm_config_keyfile_set_string_list(keyfile, + group_name, + NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS, + nm_global_dns_domain_get_options(domain), + -1); + } + +done: + nm_config_set_values(self, keyfile, TRUE, FALSE); + g_key_file_unref(keyfile); + + return TRUE; +} + +/*****************************************************************************/ + +void +nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled) +{ + NMConfigPrivate *priv; + GKeyFile * keyfile; + + g_return_if_fail(NM_IS_CONFIG(self)); + + priv = NM_CONFIG_GET_PRIVATE(self); + g_return_if_fail(priv->config_data); + + keyfile = nm_config_data_clone_keyfile_intern(priv->config_data); + + /* Remove existing groups */ + g_key_file_remove_group(keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, NULL); + + g_key_file_set_value(keyfile, + NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, + "enabled", + enabled ? "true" : "false"); + + nm_config_set_values(self, keyfile, TRUE, FALSE); + g_key_file_unref(keyfile); +} + +/** + * nm_config_set_values: + * @self: the NMConfig instance + * @keyfile_intern_new: (allow-none): the new internal settings to set. + * If %NULL, it is equal to an empty keyfile. + * @allow_write: only if %TRUE, allow writing the changes to file. Otherwise, + * do the changes in-memory only. + * @force_rewrite: if @allow_write is %FALSE, this has no effect. If %FALSE, + * only write the configuration to file, if there are any actual changes. + * If %TRUE, always write the configuration to file, even if tere are seemingly + * no changes. + * + * This is the most flexible function to set values. It all depends on the + * keys and values you set in @keyfile_intern_new. You basically reset all + * internal configuration values to what is in @keyfile_intern_new. + * + * There are 3 types of settings: + * - all groups/sections with a prefix [.intern.*] are taken as is. As these + * groups are separate from user configuration, there is no conflict. You set + * them, that's it. + * - there are atomic sections, i.e. sections whose name start with one of + * NM_CONFIG_ATOMIC_SECTION_PREFIXES. If you put values in these sections, + * it means you completely replace the section from user configuration. + * You can also hide a user provided section by only putting the special + * key NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS into that section. + * - otherwise you can overwrite individual values from user-configuration. + * Just set the value. Keys with a prefix NM_CONFIG_KEYFILE_KEYPREFIX_* + * are protected -- as they are not value user keys. + * You can also hide a certain user setting by putting only a key + * NM_CONFIG_KEYFILE_KEYPREFIX_WAS"keyname" into the keyfile. + */ +void +nm_config_set_values(NMConfig *self, + GKeyFile *keyfile_intern_new, + gboolean allow_write, + gboolean force_rewrite) +{ + NMConfigPrivate * priv; + GKeyFile * keyfile_intern_current; + GKeyFile * keyfile_user; + GKeyFile * keyfile_new; + GError * local = NULL; + NMConfigData * new_data = NULL; + gs_strfreev char **groups = NULL; + int g; + + g_return_if_fail(NM_IS_CONFIG(self)); + + priv = NM_CONFIG_GET_PRIVATE(self); + + keyfile_intern_current = _nm_config_data_get_keyfile_intern(priv->config_data); + + keyfile_new = nm_config_create_keyfile(); + if (keyfile_intern_new) + _nm_keyfile_copy(keyfile_new, keyfile_intern_new); + + /* ensure that every atomic section has a .was entry. */ + groups = g_key_file_get_groups(keyfile_new, NULL); + for (g = 0; groups && groups[g]; g++) { + if (_is_atomic_section((const char *const *) priv->atomic_section_prefixes, groups[g])) + g_key_file_set_value(keyfile_new, + groups[g], + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + ""); + } + + if (!_nm_keyfile_equals(keyfile_intern_current, keyfile_new, TRUE)) + new_data = nm_config_data_new_update_keyfile_intern(priv->config_data, keyfile_new); + + _LOGD("set values(): %s", new_data ? "has changes" : "no changes"); + + if (allow_write && (new_data || force_rewrite)) { + /* We write the internal config file based on the user configuration from + * the last load/reload. That is correct, because the intern properties might + * be in accordance to what NM thinks is currently configured. Even if the files + * on disk changed in the meantime. + * But if they changed, on the next reload with might throw away our just + * written data. That is correct, because from NM's point of view, those + * changes on disk happened in any case *after* now. */ + if (*priv->intern_config_file) { + keyfile_user = _nm_config_data_get_keyfile_user(priv->config_data); + if (!intern_config_write(priv->intern_config_file, + keyfile_new, + keyfile_user, + (const char *const *) priv->atomic_section_prefixes, + &local)) { + _LOGW("error saving internal configuration \"%s\": %s", + priv->intern_config_file, + local->message); + g_clear_error(&local); + } + } else + _LOGD("don't persist internal configuration (no file set, use --intern-config?)"); + } + if (new_data) + _set_config_data(self, new_data, NM_CONFIG_CHANGE_CAUSE_SET_VALUES); + + g_key_file_unref(keyfile_new); +} + +/****************************************************************************** + * State + ******************************************************************************/ + +static const char * +state_get_filename(const NMConfigCmdLineOptions *cli) +{ + /* For an empty filename, we assume the user wants to disable + * state. NMConfig will not try to read it nor write it out. */ + if (!cli->state_file) + return DEFAULT_STATE_FILE; + return cli->state_file[0] ? cli->state_file : NULL; +} + +static State * +state_new(void) +{ + State *state; + + state = g_slice_new0(State); + state->p.net_enabled = TRUE; + state->p.wifi_enabled = TRUE; + state->p.wwan_enabled = TRUE; + + return state; +} + +static void +state_free(State *state) +{ + if (!state) + return; + g_slice_free(State, state); +} + +static State * +state_new_from_file(const char *filename) +{ + GKeyFile * keyfile; + gs_free_error GError *error = NULL; + State * state; + + state = state_new(); + + if (!filename) + return state; + + keyfile = g_key_file_new(); + g_key_file_set_list_separator(keyfile, ','); + if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error)) { + if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + _LOGD("state: missing state file \"%s\": %s", filename, error->message); + else + _LOGW("state: error reading state file \"%s\": %s", filename, error->message); + goto out; + } + + _LOGD("state: successfully read state file \"%s\"", filename); + + state->p.net_enabled = + nm_config_keyfile_get_boolean(keyfile, "main", "NetworkingEnabled", state->p.net_enabled); + state->p.wifi_enabled = + nm_config_keyfile_get_boolean(keyfile, "main", "WirelessEnabled", state->p.wifi_enabled); + state->p.wwan_enabled = + nm_config_keyfile_get_boolean(keyfile, "main", "WWANEnabled", state->p.wwan_enabled); + +out: + g_key_file_unref(keyfile); + return state; +} + +const NMConfigState * +nm_config_state_get(NMConfig *self) +{ + NMConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_CONFIG(self), NULL); + + priv = NM_CONFIG_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->state)) { + /* read the state from file lazy on first access. The reason is that + * we want to log a failure to read the file via nm-logging. + * + * So we cannot read the state during construction of NMConfig, + * because at that time nm-logging is not yet configured. + */ + priv->state = state_new_from_file(state_get_filename(&priv->cli)); + } + + return &priv->state->p; +} + +static void +state_write(NMConfig *self) +{ + NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE(self); + const char * filename; + GString * str; + GError * error = NULL; + + if (priv->configure_and_quit != NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED) + return; + + filename = state_get_filename(&priv->cli); + + if (!filename) { + priv->state->p.dirty = FALSE; + return; + } + + str = g_string_sized_new(256); + + /* Let's construct the keyfile data by hand. */ + + g_string_append(str, "[main]\n"); + g_string_append_printf(str, + "NetworkingEnabled=%s\n", + priv->state->p.net_enabled ? "true" : "false"); + g_string_append_printf(str, + "WirelessEnabled=%s\n", + priv->state->p.wifi_enabled ? "true" : "false"); + g_string_append_printf(str, "WWANEnabled=%s\n", priv->state->p.wwan_enabled ? "true" : "false"); + + if (!g_file_set_contents(filename, str->str, str->len, &error)) { + _LOGD("state: error writing state file \"%s\": %s", filename, error->message); + g_clear_error(&error); + /* we leave the state dirty. That potentially means, that we try to + * write the file over and over again, although it isn't possible. */ + priv->state->p.dirty = TRUE; + } else + priv->state->p.dirty = FALSE; + + _LOGT("state: success writing state file \"%s\"", filename); + + g_string_free(str, TRUE); +} + +void +_nm_config_state_set(NMConfig *self, gboolean allow_persist, gboolean force_persist, ...) +{ + NMConfigPrivate * priv; + va_list ap; + NMConfigRunStatePropertyType property_type; + + g_return_if_fail(NM_IS_CONFIG(self)); + + priv = NM_CONFIG_GET_PRIVATE(self); + + va_start(ap, force_persist); + + /* We expect that the NMConfigRunStatePropertyType is an integer type <= sizeof (int). + * Smaller would be fine, since the variadic arguments get promoted to int. + * Larger would be a problem, also, because we want that "0" is a valid sentinel. */ + G_STATIC_ASSERT_EXPR(sizeof(NMConfigRunStatePropertyType) <= sizeof(int)); + + while ((property_type = va_arg(ap, int)) != NM_CONFIG_STATE_PROPERTY_NONE) { + bool *p_bool, v_bool; + + switch (property_type) { + case NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED: + p_bool = &priv->state->p.net_enabled; + break; + case NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED: + p_bool = &priv->state->p.wifi_enabled; + break; + case NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED: + p_bool = &priv->state->p.wwan_enabled; + break; + default: + va_end(ap); + g_return_if_reached(); + } + + v_bool = va_arg(ap, gboolean); + if (*p_bool == v_bool) + continue; + *p_bool = v_bool; + priv->state->p.dirty = TRUE; + } + + va_end(ap); + + if (allow_persist && (force_persist || priv->state->p.dirty)) + state_write(self); +} + +/*****************************************************************************/ + +#define DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE "device" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED "managed" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE "perm-hw-addr-fake" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID "connection-uuid" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED "nm-owned" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED \ + "route-metric-default-aspired" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE \ + "route-metric-default-effective" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH "root-path" +#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER "next-server" + +static NM_UTILS_LOOKUP_STR_DEFINE( + _device_state_managed_type_to_str, + NMConfigDeviceStateManagedType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED, "unmanaged"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED, "managed"), ); + +static NMConfigDeviceStateData * +_config_device_state_data_new(int ifindex, GKeyFile *kf) +{ + NMConfigDeviceStateData * device_state; + NMConfigDeviceStateManagedType managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN; + gs_free char * connection_uuid = NULL; + gs_free char * perm_hw_addr_fake = NULL; + gsize connection_uuid_len; + gsize perm_hw_addr_fake_len; + NMTernary nm_owned; + char * p; + guint32 route_metric_default_effective; + guint32 route_metric_default_aspired; + + nm_assert(kf); + nm_assert(ifindex > 0); + + switch (nm_config_keyfile_get_boolean(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED, + -1)) { + case TRUE: + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED; + connection_uuid = + nm_config_keyfile_get_value(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + break; + case FALSE: + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED; + break; + case -1: + /* missing property in keyfile. */ + break; + } + + perm_hw_addr_fake = + nm_config_keyfile_get_value(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + if (perm_hw_addr_fake) { + char *normalized; + + normalized = nm_utils_hwaddr_canonical(perm_hw_addr_fake, -1); + g_free(perm_hw_addr_fake); + perm_hw_addr_fake = normalized; + } + + nm_owned = nm_config_keyfile_get_boolean(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED, + NM_TERNARY_DEFAULT); + + /* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias + * for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */ + route_metric_default_effective = nm_config_keyfile_get_int64( + kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE, + 10, + 1, + G_MAXUINT32, + 0); + if (route_metric_default_effective) { + route_metric_default_aspired = nm_config_keyfile_get_int64( + kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE, + 10, + 1, + route_metric_default_effective, + route_metric_default_effective); + } else + route_metric_default_aspired = 0; + + connection_uuid_len = connection_uuid ? strlen(connection_uuid) + 1 : 0; + perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen(perm_hw_addr_fake) + 1 : 0; + + device_state = + g_malloc(sizeof(NMConfigDeviceStateData) + connection_uuid_len + perm_hw_addr_fake_len); + + device_state->ifindex = ifindex; + device_state->managed = managed_type; + device_state->connection_uuid = NULL; + device_state->perm_hw_addr_fake = NULL; + device_state->nm_owned = nm_owned; + device_state->route_metric_default_aspired = route_metric_default_aspired; + device_state->route_metric_default_effective = route_metric_default_effective; + + p = (char *) (&device_state[1]); + if (connection_uuid) { + memcpy(p, connection_uuid, connection_uuid_len); + device_state->connection_uuid = p; + p += connection_uuid_len; + } + if (perm_hw_addr_fake) { + memcpy(p, perm_hw_addr_fake, perm_hw_addr_fake_len); + device_state->perm_hw_addr_fake = p; + p += perm_hw_addr_fake_len; + } + + return device_state; +} + +#define DEVICE_STATE_FILENAME_LEN_MAX 60 + +/** + * nm_config_device_state_load: + * @ifindex: the ifindex for which the state is to load + * + * Returns: (transfer full): a run state object. + * Must be freed with g_free(). + */ +NMConfigDeviceStateData * +nm_config_device_state_load(int ifindex) +{ + NMConfigDeviceStateData *device_state; + char path[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1]; + nm_auto_unref_keyfile GKeyFile *kf = NULL; + const char * nm_owned_str; + + g_return_val_if_fail(ifindex > 0, NULL); + + nm_sprintf_buf(path, "%s/%d", NM_CONFIG_DEVICE_STATE_DIR, ifindex); + + kf = nm_config_create_keyfile(); + if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, NULL)) + return NULL; + + device_state = _config_device_state_data_new(ifindex, kf); + nm_owned_str = device_state->nm_owned == NM_TERNARY_TRUE + ? ", nm-owned=1" + : (device_state->nm_owned == NM_TERNARY_FALSE ? ", nm-owned=0" : ""); + + _LOGT("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, " + "route-metric-default=%" G_GUINT32_FORMAT "-%" G_GUINT32_FORMAT "", + kf ? "read" : "miss", + ifindex, + path, + _device_state_managed_type_to_str(device_state->managed), + NM_PRINT_FMT_QUOTED(device_state->connection_uuid, + ", connection-uuid=", + device_state->connection_uuid, + "", + ""), + NM_PRINT_FMT_QUOTED(device_state->perm_hw_addr_fake, + ", perm-hw-addr-fake=", + device_state->perm_hw_addr_fake, + "", + ""), + nm_owned_str, + device_state->route_metric_default_aspired, + device_state->route_metric_default_effective); + + return device_state; +} + +static int +_device_state_parse_filename(const char *filename) +{ + if (!filename || !filename[0]) + return 0; + if (!NM_STRCHAR_ALL(filename, ch, g_ascii_isdigit(ch))) + return 0; + return _nm_utils_ascii_str_to_int64(filename, 10, 1, G_MAXINT, 0); +} + +GHashTable * +nm_config_device_state_load_all(void) +{ + GHashTable *states; + GDir * dir; + const char *fn; + int ifindex; + + states = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_free); + + dir = g_dir_open(NM_CONFIG_DEVICE_STATE_DIR, 0, NULL); + if (!dir) + return states; + + while ((fn = g_dir_read_name(dir))) { + NMConfigDeviceStateData *state; + + ifindex = _device_state_parse_filename(fn); + if (ifindex <= 0) + continue; + + state = nm_config_device_state_load(ifindex); + if (!state) + continue; + + if (!g_hash_table_insert(states, GINT_TO_POINTER(ifindex), state)) + nm_assert_not_reached(); + } + g_dir_close(dir); + + return states; +} + +gboolean +nm_config_device_state_write(int ifindex, + NMConfigDeviceStateManagedType managed, + const char * perm_hw_addr_fake, + const char * connection_uuid, + NMTernary nm_owned, + guint32 route_metric_default_aspired, + guint32 route_metric_default_effective, + const char * next_server, + const char * root_path) +{ + char path[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1]; + GError *local = NULL; + nm_auto_unref_keyfile GKeyFile *kf = NULL; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(!connection_uuid || *connection_uuid, FALSE); + g_return_val_if_fail(managed == NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED || !connection_uuid, + FALSE); + + nm_assert(!perm_hw_addr_fake || nm_utils_hwaddr_valid(perm_hw_addr_fake, -1)); + + nm_sprintf_buf(path, "%s/%d", NM_CONFIG_DEVICE_STATE_DIR, ifindex); + + kf = nm_config_create_keyfile(); + if (NM_IN_SET(managed, + NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED, + NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED)) { + g_key_file_set_boolean(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED, + managed == NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED); + } + if (perm_hw_addr_fake) { + g_key_file_set_string(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE, + perm_hw_addr_fake); + } + if (connection_uuid) { + g_key_file_set_string(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID, + connection_uuid); + } + if (nm_owned != NM_TERNARY_DEFAULT) { + g_key_file_set_boolean(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED, + nm_owned); + } + + if (route_metric_default_effective != 0) { + g_key_file_set_int64(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE, + route_metric_default_effective); + if (route_metric_default_aspired != route_metric_default_effective) { + g_key_file_set_int64(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED, + route_metric_default_aspired); + } + } + if (next_server) { + g_key_file_set_string(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER, + next_server); + } + if (root_path) { + g_key_file_set_string(kf, + DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE, + DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH, + root_path); + } + + if (!g_key_file_save_to_file(kf, path, &local)) { + _LOGW("device-state: write #%d (%s) failed: %s", ifindex, path, local->message); + g_error_free(local); + return FALSE; + } + _LOGT("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, " + "route-metric-default=%" G_GUINT32_FORMAT "-%" G_GUINT32_FORMAT "%s%s%s%s%s%s", + ifindex, + path, + _device_state_managed_type_to_str(managed), + NM_PRINT_FMT_QUOTED(connection_uuid, ", connection-uuid=", connection_uuid, "", ""), + NM_PRINT_FMT_QUOTED(perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""), + route_metric_default_aspired, + route_metric_default_effective, + NM_PRINT_FMT_QUOTED(next_server, ", next-server=", next_server, "", ""), + NM_PRINT_FMT_QUOTED(root_path, ", root-path=", root_path, "", "")); + return TRUE; +} + +void +nm_config_device_state_prune_stale(GHashTable *preserve_ifindexes, NMPlatform *preserve_in_platform) +{ + GDir * dir; + const char *fn; + char buf[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/") + DEVICE_STATE_FILENAME_LEN_MAX + 1] = + NM_CONFIG_DEVICE_STATE_DIR "/"; + char *buf_p = &buf[NM_STRLEN(NM_CONFIG_DEVICE_STATE_DIR "/")]; + + dir = g_dir_open(NM_CONFIG_DEVICE_STATE_DIR, 0, NULL); + if (!dir) + return; + + while ((fn = g_dir_read_name(dir))) { + int ifindex; + gsize fn_len; + + ifindex = _device_state_parse_filename(fn); + if (ifindex <= 0) + continue; + + if (preserve_ifindexes + && g_hash_table_contains(preserve_ifindexes, GINT_TO_POINTER(ifindex))) + continue; + + if (preserve_in_platform && nm_platform_link_get(preserve_in_platform, ifindex)) + continue; + + fn_len = strlen(fn); + nm_assert(fn_len > 0); + nm_assert(&buf_p[fn_len] < &buf[G_N_ELEMENTS(buf)]); + memcpy(buf_p, fn, fn_len + 1u); + nm_assert(({ + char bb[30]; + + nm_streq0(nm_sprintf_buf(bb, "%d", ifindex), buf_p); + })); + _LOGT("device-state: prune #%d (%s)", ifindex, buf); + (void) unlink(buf); + } + + g_dir_close(dir); +} + +/*****************************************************************************/ + +static GHashTable * +_device_state_get_all(NMConfig *self) +{ + NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->device_states)) + priv->device_states = nm_config_device_state_load_all(); + return priv->device_states; +} + +/** + * nm_config_device_state_get_all: + * @self: the #NMConfig + * + * This function exists to give convenient access to all + * device states. Do not ever try to modify the returned + * hash, it's supposed to be immutable. + * + * Returns: the internal #GHashTable object with all device states. + */ +const GHashTable * +nm_config_device_state_get_all(NMConfig *self) +{ + g_return_val_if_fail(NM_IS_CONFIG(self), NULL); + + return _device_state_get_all(self); +} + +const NMConfigDeviceStateData * +nm_config_device_state_get(NMConfig *self, int ifindex) +{ + g_return_val_if_fail(NM_IS_CONFIG(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + return g_hash_table_lookup(_device_state_get_all(self), GINT_TO_POINTER(ifindex)); +} + +/*****************************************************************************/ + +void +nm_config_reload(NMConfig *self, NMConfigChangeFlags reload_flags, gboolean emit_warnings) +{ + NMConfigPrivate * priv; + GError * error = NULL; + GKeyFile * keyfile, *keyfile_intern; + NMConfigData * new_data = NULL; + char * config_main_file = NULL; + char * config_description = NULL; + gs_strfreev char **no_auto_default = NULL; + gboolean intern_config_needs_rewrite; + gs_unref_ptrarray GPtrArray *warnings = NULL; + guint i; + + g_return_if_fail(NM_IS_CONFIG(self)); + g_return_if_fail(reload_flags && !NM_FLAGS_ANY(reload_flags, ~NM_CONFIG_CHANGE_CAUSES) + && !NM_FLAGS_ANY(reload_flags, + NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT + | NM_CONFIG_CHANGE_CAUSE_SET_VALUES)); + + priv = NM_CONFIG_GET_PRIVATE(self); + + if (!NM_FLAGS_ANY(reload_flags, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_CONF)) { + /* unless SIGHUP is specified, we don't reload the configuration from disc. */ + _set_config_data(self, NULL, reload_flags); + return; + } + + warnings = g_ptr_array_new_with_free_func(g_free); + + /* pass on the original command line options. This means, that + * options specified at command line cannot ever be reloaded from + * file. That seems desirable. + */ + keyfile = read_entire_config(&priv->cli, + priv->config_dir, + priv->system_config_dir, + &config_main_file, + &config_description, + warnings, + &error); + if (!keyfile) { + _LOGE("Failed to reload the configuration: %s", error->message); + g_clear_error(&error); + _set_config_data(self, NULL, reload_flags); + return; + } + + no_auto_default = no_auto_default_from_file(priv->no_auto_default_file); + + keyfile_intern = intern_config_read(priv->intern_config_file, + keyfile, + (const char *const *) priv->atomic_section_prefixes, + &intern_config_needs_rewrite); + if (intern_config_needs_rewrite) { + intern_config_write(priv->intern_config_file, + keyfile_intern, + keyfile, + (const char *const *) priv->atomic_section_prefixes, + NULL); + } + + new_data = nm_config_data_new(config_main_file, + config_description, + (const char *const *) no_auto_default, + keyfile, + keyfile_intern); + + if (emit_warnings) { + nm_config_data_get_warnings(priv->config_data_orig, warnings); + for (i = 0; i < warnings->len; i++) + _LOGW("%s", (const char *) warnings->pdata[i]); + } + + g_free(config_main_file); + g_free(config_description); + g_key_file_unref(keyfile); + if (keyfile_intern) + g_key_file_unref(keyfile_intern); + + _set_config_data(self, new_data, reload_flags); +} + +NM_UTILS_FLAGS2STR_DEFINE(nm_config_change_flags_to_string, + NMConfigChangeFlags, + + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_CONF, "CONF"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_DNS_RC, "DNS_RC"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_DNS_FULL, "DNS_FULL"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_SIGHUP, "SIGHUP"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_SIGUSR1, "SIGUSR1"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_SIGUSR2, "SIGUSR2"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT, + "NO_AUTO_DEFAULT"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CAUSE_SET_VALUES, "SET_VALUES"), + + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CONFIG_FILES, "config-files"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_VALUES, "values"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_VALUES_USER, "values-user"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_VALUES_INTERN, "values-intern"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_CONNECTIVITY, "connectivity"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_NO_AUTO_DEFAULT, "no-auto-default"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_DNS_MODE, "dns-mode"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_RC_MANAGER, "rc-manager"), + NM_UTILS_FLAGS2STR(NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG, + "global-dns-config"), ); + +static void +_set_config_data(NMConfig *self, NMConfigData *new_data, NMConfigChangeFlags reload_flags) +{ + NMConfigPrivate * priv = NM_CONFIG_GET_PRIVATE(self); + NMConfigData * old_data = priv->config_data; + NMConfigChangeFlags changes, changes_diff; + gboolean had_new_data = !!new_data; + + nm_assert(reload_flags); + nm_assert(!NM_FLAGS_ANY(reload_flags, ~NM_CONFIG_CHANGE_CAUSES)); + nm_assert(NM_IN_SET(reload_flags, + NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT, + NM_CONFIG_CHANGE_CAUSE_SET_VALUES) + || !NM_FLAGS_ANY(reload_flags, + NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT + | NM_CONFIG_CHANGE_CAUSE_SET_VALUES)); + + changes = reload_flags; + + if (new_data) { + changes_diff = nm_config_data_diff(old_data, new_data); + if (changes_diff == NM_CONFIG_CHANGE_NONE) + g_clear_object(&new_data); + else + changes |= changes_diff; + } + + if (NM_IN_SET(reload_flags, + NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT, + NM_CONFIG_CHANGE_CAUSE_SET_VALUES, + NM_CONFIG_CHANGE_CAUSE_CONF) + && !new_data) { + /* no relevant changes that should be propagated. Return silently. */ + return; + } + + if (new_data) { + _LOGI("signal: %s (%s)", + nm_config_change_flags_to_string(changes, NULL, 0), + nm_config_data_get_config_description(new_data)); + nm_config_data_log(new_data, "CONFIG: ", " ", priv->no_auto_default_file, NULL); + priv->config_data = new_data; + } else if (had_new_data) + _LOGI("signal: %s (no changes from disk)", + nm_config_change_flags_to_string(changes, NULL, 0)); + else + _LOGI("signal: %s", nm_config_change_flags_to_string(changes, NULL, 0)); + g_signal_emit(self, signals[SIGNAL_CONFIG_CHANGED], 0, new_data ?: old_data, changes, old_data); + if (new_data) + g_object_unref(old_data); +} + +NM_DEFINE_SINGLETON_REGISTER(NMConfig); + +NMConfig * +nm_config_get(void) +{ + g_assert(singleton_instance); + return singleton_instance; +} + +NMConfig * +nm_config_setup(const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error) +{ + g_assert(!singleton_instance); + + singleton_instance = nm_config_new(cli, atomic_section_prefixes, error); + if (singleton_instance) { + nm_singleton_instance_register(); + + /* usually, you would not see this logging line because when creating the + * NMConfig instance, the logging is not yet set up to print debug message. */ + nm_log_dbg(LOGD_CORE, + "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + "NMConfig", + NM_HASH_OBFUSCATE_PTR(singleton_instance)); + } + return singleton_instance; +} + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMConfig * self = NM_CONFIG(object); + NMConfigPrivate * priv = NM_CONFIG_GET_PRIVATE(self); + NMConfigCmdLineOptions *cli; + char ** strv; + + switch (prop_id) { + case PROP_CMD_LINE_OPTIONS: + /* construct-only */ + cli = g_value_get_pointer(value); + if (!cli) + _nm_config_cmd_line_options_clear(&priv->cli); + else + _nm_config_cmd_line_options_copy(cli, &priv->cli); + break; + case PROP_ATOMIC_SECTION_PREFIXES: + /* construct-only */ + strv = g_value_get_boxed(value); + if (strv && !strv[0]) + strv = NULL; + priv->atomic_section_prefixes = g_strdupv(strv); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static gboolean +init_sync(GInitable *initable, GCancellable *cancellable, GError **error) +{ + NMConfig * self = NM_CONFIG(initable); + NMConfigPrivate * priv = NM_CONFIG_GET_PRIVATE(self); + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile_intern = NULL; + gs_free char * config_main_file = NULL; + gs_free char * config_description = NULL; + gs_strfreev char ** no_auto_default = NULL; + gs_unref_ptrarray GPtrArray *warnings = NULL; + gs_free char * configure_and_quit = NULL; + gboolean intern_config_needs_rewrite; + const char * s; + + if (priv->config_dir) { + /* Object is already initialized. */ + if (priv->config_data) + return TRUE; + g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "unspecified error"); + g_return_val_if_reached(FALSE); + } + + s = priv->cli.config_dir ?: "" DEFAULT_CONFIG_DIR; + priv->config_dir = g_strdup(s[0] == '/' ? s : ""); + + s = priv->cli.system_config_dir ?: "" DEFAULT_SYSTEM_CONFIG_DIR; + if (s[0] != '/' || nm_streq(s, priv->config_dir)) + s = ""; + priv->system_config_dir = g_strdup(s); + + if (priv->cli.intern_config_file) + priv->intern_config_file = g_strdup(priv->cli.intern_config_file); + else + priv->intern_config_file = g_strdup(DEFAULT_INTERN_CONFIG_FILE); + + warnings = g_ptr_array_new_with_free_func(g_free); + + keyfile = read_entire_config(&priv->cli, + priv->config_dir, + priv->system_config_dir, + &config_main_file, + &config_description, + warnings, + error); + if (!keyfile) + return FALSE; + + /* Initialize read-only private members */ + + if (priv->cli.no_auto_default_file) + priv->no_auto_default_file = g_strdup(priv->cli.no_auto_default_file); + else + priv->no_auto_default_file = g_strdup(DEFAULT_NO_AUTO_DEFAULT_FILE); + + priv->log_level = nm_strstrip(g_key_file_get_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_LOGGING, + NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL, + NULL)); + priv->log_domains = nm_strstrip(g_key_file_get_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_LOGGING, + NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS, + NULL)); + configure_and_quit = + nm_strstrip(g_key_file_get_string(keyfile, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT, + NULL)); + priv->configure_and_quit = string_to_configure_and_quit(configure_and_quit, error); + if (priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID) + return FALSE; + + no_auto_default = no_auto_default_from_file(priv->no_auto_default_file); + + keyfile_intern = intern_config_read(priv->intern_config_file, + keyfile, + (const char *const *) priv->atomic_section_prefixes, + &intern_config_needs_rewrite); + if (intern_config_needs_rewrite + && priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED) { + intern_config_write(priv->intern_config_file, + keyfile_intern, + keyfile, + (const char *const *) priv->atomic_section_prefixes, + NULL); + } + + priv->config_data_orig = nm_config_data_new(config_main_file, + config_description, + (const char *const *) no_auto_default, + keyfile, + keyfile_intern); + + nm_config_data_get_warnings(priv->config_data_orig, warnings); + + priv->config_data = g_object_ref(priv->config_data_orig); + if (warnings->len > 0) { + g_ptr_array_add(warnings, NULL); + priv->warnings = (char **) g_ptr_array_free(g_steal_pointer(&warnings), FALSE); + } + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_config_init(NMConfig *config) +{} + +NMConfig * +nm_config_new(const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error) +{ + return NM_CONFIG(g_initable_new(NM_TYPE_CONFIG, + NULL, + error, + NM_CONFIG_CMD_LINE_OPTIONS, + cli, + NM_CONFIG_ATOMIC_SECTION_PREFIXES, + atomic_section_prefixes, + NULL)); +} + +static void +finalize(GObject *gobject) +{ + NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE(gobject); + + state_free(priv->state); + + g_free(priv->config_dir); + g_free(priv->system_config_dir); + g_free(priv->no_auto_default_file); + g_free(priv->intern_config_file); + g_free(priv->log_level); + g_free(priv->log_domains); + g_strfreev(priv->atomic_section_prefixes); + g_strfreev(priv->warnings); + + _nm_config_cmd_line_options_clear(&priv->cli); + + g_clear_object(&priv->config_data); + g_clear_object(&priv->config_data_orig); + + G_OBJECT_CLASS(nm_config_parent_class)->finalize(gobject); +} + +static void +nm_config_class_init(NMConfigClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(config_class); + + object_class->finalize = finalize; + object_class->set_property = set_property; + + obj_properties[PROP_CMD_LINE_OPTIONS] = + g_param_spec_pointer(NM_CONFIG_CMD_LINE_OPTIONS, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ATOMIC_SECTION_PREFIXES] = + g_param_spec_boxed(NM_CONFIG_ATOMIC_SECTION_PREFIXES, + "", + "", + G_TYPE_STRV, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[SIGNAL_CONFIG_CHANGED] = + g_signal_new(NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + NM_TYPE_CONFIG_DATA, + /* Use plain guint type for changes argument. This avoids + * glib/ffi bug https://bugzilla.redhat.com/show_bug.cgi?id=1260577 */ + /* NM_TYPE_CONFIG_CHANGE_FLAGS, */ + G_TYPE_UINT, + NM_TYPE_CONFIG_DATA); + + G_STATIC_ASSERT_EXPR(sizeof(guint) == sizeof(NMConfigChangeFlags)); + G_STATIC_ASSERT_EXPR(((gint64)((NMConfigChangeFlags) -1)) > ((gint64) 0)); +} + +static void +nm_config_initable_iface_init(GInitableIface *iface) +{ + iface->init = init_sync; +} diff --git a/src/core/nm-config.h b/src/core/nm-config.h new file mode 100644 index 0000000..6f3f514 --- /dev/null +++ b/src/core/nm-config.h @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * Copyright (C) 2013 Thomas Bechtold + */ + +#ifndef __NETWORKMANAGER_CONFIG_H__ +#define __NETWORKMANAGER_CONFIG_H__ + +#include "nm-config-data.h" + +#define NM_TYPE_CONFIG (nm_config_get_type()) +#define NM_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_CONFIG, NMConfig)) +#define NM_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_CONFIG, NMConfigClass)) +#define NM_IS_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_CONFIG)) +#define NM_IS_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_CONFIG)) +#define NM_CONFIG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_CONFIG, NMConfigClass)) + +/* Properties */ +#define NM_CONFIG_CMD_LINE_OPTIONS "cmd-line-options" +#define NM_CONFIG_ATOMIC_SECTION_PREFIXES "atomic-section-prefixes" + +/* Signals */ +#define NM_CONFIG_SIGNAL_CONFIG_CHANGED "config-changed" + +#define NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL 300 +#define NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE "NetworkManager is online" /* NOT LOCALIZED */ + +#define NM_CONFIG_KEYFILE_LIST_SEPARATOR ',' + +#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN ".intern." +#define NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION "connection" +#define NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE "device" +#define NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN "global-dns-domain-" +#define NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".test-append-stringlist" + +#define NM_CONFIG_KEYFILE_GROUP_MAIN "main" +#define NM_CONFIG_KEYFILE_GROUP_LOGGING "logging" +#define NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY "connectivity" +#define NM_CONFIG_KEYFILE_GROUP_KEYFILE "keyfile" +#define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN "ifupdown" +#define NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS "global-dns" +#define NM_CONFIG_KEYFILE_GROUP_CONFIG ".config" + +#define NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY "assume-ipv6ll-only" +#define NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT "auth-polkit" +#define NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT "autoconnect-retries-default" +#define NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT "configure-and-quit" +#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_HOSTNAME_MODE "hostname-mode" +#define NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER "ignore-carrier" +#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_LOGGING_AUDIT "audit" +#define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend" +#define NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS "domains" +#define NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL "level" + +#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED "enabled" +#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL "interval" +#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE "response" +#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI "uri" + +#define NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH "path" +#define NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES "unmanaged-devices" +#define NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME "hostname" + +#define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED "managed" + +#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES "searches" +#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS "options" + +#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS "servers" +#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS "options" + +#define NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED "managed" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER "ignore-carrier" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS "sriov-num-vfs" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND "wifi.backend" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS "wifi.scan-rand-mac-address" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK \ + "wifi.scan-generate-mac-address-mask" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT "carrier-wait-timeout" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT "wifi.iwd.autoconnect" + +#define NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE "match-device" +#define NM_CONFIG_KEYFILE_KEY_STOP_MATCH "stop-match" + +#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS ".was" /* check-config-options skip */ +#define NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE "enable" /* check-config-options skip */ + +#define NM_CONFIG_KEYFILE_KEYPREFIX_WAS ".was." +#define NM_CONFIG_KEYFILE_KEYPREFIX_SET ".set." + +#define NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS \ + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS +#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN \ + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN + +typedef struct NMConfigCmdLineOptions NMConfigCmdLineOptions; + +typedef enum { + NM_CONFIG_STATE_PROPERTY_NONE, + + /* 1 set-argument: (gboolean enabled) */ + NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, + NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED, + NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED, +} NMConfigRunStatePropertyType; + +typedef enum { + NM_CONFIG_CONFIGURE_AND_QUIT_INVALID = -1, + NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED = FALSE, + NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED = TRUE, + NM_CONFIG_CONFIGURE_AND_QUIT_INITRD, +} NMConfigConfigureAndQuitType; + +typedef struct { + bool net_enabled; + bool wifi_enabled; + bool wwan_enabled; + + /* Whether the runstate is modified and not saved to disk. */ + bool dirty; +} NMConfigState; + +typedef struct _NMConfigClass NMConfigClass; + +GType nm_config_get_type(void); + +NMConfig *nm_config_get(void); + +const char *nm_config_change_flags_to_string(NMConfigChangeFlags flags, char *buf, gsize len); + +NMConfigData *nm_config_get_data(NMConfig *config); +NMConfigData *nm_config_get_data_orig(NMConfig *config); + +#define NM_CONFIG_GET_DATA (nm_config_get_data(nm_config_get())) +#define NM_CONFIG_GET_DATA_ORIG (nm_config_get_data_orig(nm_config_get())) + +const char * nm_config_get_log_level(NMConfig *config); +const char * nm_config_get_log_domains(NMConfig *config); +NMConfigConfigureAndQuitType nm_config_get_configure_and_quit(NMConfig *config); +gboolean nm_config_get_is_debug(NMConfig *config); + +gboolean nm_config_get_first_start(NMConfig *config); + +const char *nm_config_get_no_auto_default_file(NMConfig *config); + +void nm_config_set_values(NMConfig *self, + GKeyFile *keyfile_intern_new, + gboolean allow_write, + gboolean force_rewrite); + +/* for main.c only */ +NMConfigCmdLineOptions *nm_config_cmd_line_options_new(gboolean first_start); +void nm_config_cmd_line_options_free(NMConfigCmdLineOptions *cli); +void nm_config_cmd_line_options_add_to_entries(NMConfigCmdLineOptions *cli, + GOptionContext * opt_ctx); + +gboolean nm_config_get_no_auto_default_for_device(NMConfig *config, NMDevice *device); +void nm_config_set_no_auto_default_for_device(NMConfig *config, NMDevice *device); + +NMConfig * +nm_config_new(const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error); +NMConfig * +nm_config_setup(const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error); +void nm_config_reload(NMConfig *config, NMConfigChangeFlags reload_flags, gboolean emit_warnings); + +const NMConfigState *nm_config_state_get(NMConfig *config); + +void _nm_config_state_set(NMConfig *config, gboolean allow_persist, gboolean force_persist, ...); +#define nm_config_state_set(config, allow_persist, force_persist, ...) \ + _nm_config_state_set(config, allow_persist, force_persist, ##__VA_ARGS__, 0) + +int nm_config_parse_boolean(const char *str, int default_value); + +GKeyFile *nm_config_create_keyfile(void); +int nm_config_keyfile_get_boolean(const GKeyFile *keyfile, + const char * section, + const char * key, + int default_value); +gint64 nm_config_keyfile_get_int64(const GKeyFile *keyfile, + const char * section, + const char * key, + guint base, + gint64 min, + gint64 max, + gint64 fallback); +char * nm_config_keyfile_get_value(const GKeyFile * keyfile, + const char * section, + const char * key, + NMConfigGetValueFlags flags); +void nm_config_keyfile_set_string_list(GKeyFile * keyfile, + const char * group, + const char * key, + const char *const *strv, + gssize len); +gboolean nm_config_keyfile_has_global_dns_config(GKeyFile *keyfile, gboolean internal); + +GSList *nm_config_get_match_spec(const GKeyFile *keyfile, + const char * group, + const char * key, + gboolean * out_has_key); + +void _nm_config_sort_groups(char **groups, gsize ngroups); + +gboolean nm_config_set_global_dns(NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error); + +void nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled); + +/* internal defines ... */ +extern guint _nm_config_match_nm_version; +extern char *_nm_config_match_env; + +/*****************************************************************************/ + +#define NM_CONFIG_DEVICE_STATE_DIR "" NMRUNDIR "/devices" + +#define NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL (nm_streq("" NM_CONFIG_DEFAULT_LOGGING_AUDIT, "true")) + +typedef enum { + NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN = -1, + NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED = 0, + NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED = 1, +} NMConfigDeviceStateManagedType; + +struct _NMConfigDeviceStateData { + int ifindex; + NMConfigDeviceStateManagedType managed; + + /* a value of zero means that no metric is set. */ + guint32 route_metric_default_aspired; + guint32 route_metric_default_effective; + + /* the UUID of the last settings-connection active + * on the device. */ + const char *connection_uuid; + + const char *perm_hw_addr_fake; + + /* whether the device was nm-owned (0/1) or -1 for + * non-software devices. */ + NMTernary nm_owned : 3; +}; + +NMConfigDeviceStateData *nm_config_device_state_load(int ifindex); +GHashTable * nm_config_device_state_load_all(void); +gboolean nm_config_device_state_write(int ifindex, + NMConfigDeviceStateManagedType managed, + const char * perm_hw_addr_fake, + const char * connection_uuid, + NMTernary nm_owned, + guint32 route_metric_default_aspired, + guint32 route_metric_default_effective, + const char * next_server, + const char * root_path); + +void nm_config_device_state_prune_stale(GHashTable *preserve_ifindexes, + NMPlatform *preserve_in_platform); + +const GHashTable * nm_config_device_state_get_all(NMConfig *self); +const NMConfigDeviceStateData *nm_config_device_state_get(NMConfig *self, int ifindex); + +const char *const *nm_config_get_warnings(NMConfig *config); +void nm_config_clear_warnings(NMConfig *config); + +/*****************************************************************************/ + +#endif /* __NETWORKMANAGER_CONFIG_H__ */ diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c new file mode 100644 index 0000000..4e38e77 --- /dev/null +++ b/src/core/nm-connectivity.c @@ -0,0 +1,1229 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Thomas Bechtold + * Copyright (C) 2011 Dan Williams + * Copyright (C) 2016 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-connectivity.h" + +#if WITH_CONCHECK + #include +#endif +#include +#include + +#include "c-list/src/c-list.h" +#include "nm-core-internal.h" +#include "nm-config.h" +#include "NetworkManagerUtils.h" +#include "nm-dbus-manager.h" +#include "dns/nm-dns-manager.h" + +#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n" + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(_state_to_string, + int /*NMConnectivityState*/, + NM_UTILS_LOOKUP_DEFAULT_WARN("???"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_UNKNOWN, "UNKNOWN"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_NONE, "NONE"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_LIMITED, "LIMITED"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_PORTAL, "PORTAL"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_FULL, "FULL"), + + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_ERROR, "ERROR"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_FAKE, "FAKE"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_CANCELLED, "CANCELLED"), + NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_DISPOSING, + "DISPOSING"), ); + +const char * +nm_connectivity_state_to_string(NMConnectivityState state) +{ + return _state_to_string(state); +} + +/*****************************************************************************/ + +typedef struct { + guint ref_count; + char *uri; + char *host; + char *port; + char *response; +} ConConfig; + +struct _NMConnectivityCheckHandle { + CList handles_lst; + NMConnectivity * self; + NMConnectivityCheckCallback callback; + gpointer user_data; + + char *ifspec; + + const char *completed_log_message; + char * completed_log_message_free; + +#if WITH_CONCHECK + struct { + ConConfig *con_config; + + GCancellable * resolve_cancellable; + CURLM * curl_mhandle; + CURL * curl_ehandle; + struct curl_slist *request_headers; + struct curl_slist *hosts; + + gsize response_good_cnt; + + guint curl_timer; + int ch_ifindex; + } concheck; +#endif + + guint64 request_counter; + + int addr_family; + + guint timeout_id; + + NMConnectivityState completed_state; + const char * completed_reason; +}; + +enum { + CONFIG_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + CList handles_lst_head; + CList completed_handles_lst_head; + NMConfig * config; + ConConfig *con_config; + guint interval; + + bool enabled : 1; + bool uri_valid : 1; +} NMConnectivityPrivate; + +struct _NMConnectivity { + GObject parent; + NMConnectivityPrivate _priv; +}; + +struct _NMConnectivityClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMConnectivity, nm_connectivity, G_TYPE_OBJECT) + +#define NM_CONNECTIVITY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMConnectivity, NM_IS_CONNECTIVITY) + +NM_DEFINE_SINGLETON_GETTER(NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECTIVITY); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CONCHECK +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "connectivity", __VA_ARGS__) + +#define _NMLOG2_DOMAIN LOGD_CONCHECK +#define _NMLOG2(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG2_DOMAIN)) { \ + _nm_log(__level, \ + _NMLOG2_DOMAIN, \ + 0, \ + (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \ + NULL, \ + "connectivity: (%s,IPv%c,%" G_GUINT64_FORMAT \ + ") " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (cb_data->ifspec ? &cb_data->ifspec[3] : ""), \ + nm_utils_addr_family_to_char(cb_data->addr_family), \ + cb_data->request_counter _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +#if WITH_CONCHECK +static ConConfig * +_con_config_ref(ConConfig *con_config) +{ + if (con_config) { + nm_assert(con_config->ref_count > 0); + ++con_config->ref_count; + } + return con_config; +} +#endif + +static void +_con_config_unref(ConConfig *con_config) +{ + if (!con_config) + return; + + nm_assert(con_config->ref_count > 0); + + if (--con_config->ref_count != 0) + return; + + g_free(con_config->uri); + g_free(con_config->host); + g_free(con_config->port); + g_free(con_config->response); + g_slice_free(ConConfig, con_config); +} + +#if WITH_CONCHECK +static const char * +_con_config_get_response(const ConConfig *con_config) +{ + return con_config->response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE; +} +#endif + +/*****************************************************************************/ + +static void +cb_data_complete(NMConnectivityCheckHandle *cb_data, + NMConnectivityState state, + const char * log_message) +{ + NMConnectivity *self; + + nm_assert(cb_data); + nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); + nm_assert(cb_data->callback); + nm_assert(state != NM_CONNECTIVITY_UNKNOWN); + nm_assert(log_message); + + self = cb_data->self; + + /* mark the handle as completing. After this point, nm_connectivity_check_cancel() + * is no longer possible. */ + cb_data->self = NULL; + + c_list_unlink_stale(&cb_data->handles_lst); + +#if WITH_CONCHECK + if (cb_data->concheck.curl_ehandle) { + /* Contrary to what cURL manual claim it is *not* safe to remove + * the easy handle "at any moment"; specifically it's not safe to + * remove *any* handle from within a libcurl callback. That is + * why we queue completed handles in this case. + * + * cb_data_complete() is however only called *not* from within a + * libcurl callback. So, this is fine. */ + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL); + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HEADERDATA, NULL); + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_PRIVATE, NULL); + curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HTTPHEADER, NULL); + + curl_multi_remove_handle(cb_data->concheck.curl_mhandle, cb_data->concheck.curl_ehandle); + curl_easy_cleanup(cb_data->concheck.curl_ehandle); + curl_multi_cleanup(cb_data->concheck.curl_mhandle); + + curl_slist_free_all(cb_data->concheck.request_headers); + curl_slist_free_all(cb_data->concheck.hosts); + } + nm_clear_g_source(&cb_data->concheck.curl_timer); + nm_clear_g_cancellable(&cb_data->concheck.resolve_cancellable); +#endif + + nm_clear_g_source(&cb_data->timeout_id); + + _LOG2D("check completed: %s; %s", nm_connectivity_state_to_string(state), log_message); + + cb_data->callback(self, cb_data, state, cb_data->user_data); + + /* Note: self might be a danling pointer at this point. It must not be used + * after this point, and all callers must either take a reference first, or + * not use the self pointer too. */ + +#if WITH_CONCHECK + _con_config_unref(cb_data->concheck.con_config); +#endif + g_free(cb_data->ifspec); + if (cb_data->completed_log_message_free) + g_free(cb_data->completed_log_message_free); + g_slice_free(NMConnectivityCheckHandle, cb_data); +} + +/*****************************************************************************/ + +#if WITH_CONCHECK + +static void +cb_data_queue_completed(NMConnectivityCheckHandle *cb_data, + NMConnectivityState state, + const char * log_message_static, + char * log_message_take /* take */) +{ + nm_assert(cb_data); + nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); + nm_assert(state != NM_CONNECTIVITY_UNKNOWN); + nm_assert(log_message_static || log_message_take); + nm_assert(cb_data->completed_state == NM_CONNECTIVITY_UNKNOWN); + nm_assert(!cb_data->completed_log_message); + nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, + &cb_data->handles_lst)); + + cb_data->completed_state = state; + cb_data->completed_log_message = log_message_static ?: log_message_take; + cb_data->completed_log_message_free = log_message_take; + + c_list_unlink_stale(&cb_data->handles_lst); + c_list_link_tail(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->completed_handles_lst_head, + &cb_data->handles_lst); +} + +static void +_complete_queued(NMConnectivity *self) +{ + NMConnectivity * self_keep_alive = NULL; + NMConnectivityPrivate * priv = NM_CONNECTIVITY_GET_PRIVATE(self); + NMConnectivityCheckHandle *cb_data; + + while ((cb_data = c_list_first_entry(&priv->completed_handles_lst_head, + NMConnectivityCheckHandle, + handles_lst))) { + if (!self_keep_alive) + self_keep_alive = g_object_ref(self); + cb_data_complete(cb_data, cb_data->completed_state, cb_data->completed_log_message); + } + nm_g_object_unref(self_keep_alive); +} + +static gboolean +_con_curl_check_connectivity(CURLM *mhandle, int sockfd, int ev_bitmask) +{ + NMConnectivityCheckHandle *cb_data; + CURLMsg * msg; + int m_left; + long response_code; + CURLMcode ret; + int running_handles; + gboolean success = TRUE; + + ret = curl_multi_socket_action(mhandle, sockfd, ev_bitmask, &running_handles); + if (ret != CURLM_OK) { + _LOGD("connectivity check failed: (%d) %s", ret, curl_multi_strerror(ret)); + success = FALSE; + } + + while ((msg = curl_multi_info_read(mhandle, &m_left))) { + const char *response; + CURLcode eret; + + if (msg->msg != CURLMSG_DONE) + continue; + + /* Here we have completed a session. Check easy session result. */ + eret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **) &cb_data); + if (eret != CURLE_OK) { + _LOGD("curl cannot extract cb_data for easy handle, skipping msg: (%d) %s", + eret, + curl_easy_strerror(eret)); + success = FALSE; + continue; + } + + nm_assert(cb_data); + nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); + + if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { + /* callback was already invoked earlier. Nothing to do. */ + continue; + } + + if (msg->data.result != CURLE_OK) { + cb_data_queue_completed(cb_data, + NM_CONNECTIVITY_LIMITED, + NULL, + g_strdup_printf("check failed: (%d) %s", + msg->data.result, + curl_easy_strerror(msg->data.result))); + continue; + } + + response = _con_config_get_response(cb_data->concheck.con_config); + + if (response[0] == '\0' + && (curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) + == CURLE_OK)) { + if (response_code == 204) { + /* We expected an empty response, and we got a 204 response code (no content). + * We may or may not have received any content (we would ignore it). + * Anyway, the response_code 204 means we are good. */ + cb_data_queue_completed(cb_data, + NM_CONNECTIVITY_FULL, + "no content, as expected", + NULL); + continue; + } + + if (response_code == 200 && cb_data->concheck.response_good_cnt == 0) { + /* we expected no response, and indeed we got an empty reply (with status code 200) */ + cb_data_queue_completed(cb_data, + NM_CONNECTIVITY_FULL, + "empty response, as expected", + NULL); + continue; + } + } + + /* If we get here, it means that easy_write_cb() didn't read enough + * bytes to be able to do a match, or that we were asking for no content + * (204 response code) and we actually got some. Either way, that is + * an indication of a captive portal */ + cb_data_queue_completed(cb_data, NM_CONNECTIVITY_PORTAL, "unexpected short response", NULL); + } + + /* if we return a failure, we don't know what went wrong. It's likely serious, because + * a failure here is not expected. Return FALSE, so that we stop polling the file descriptor. + * Worst case, this leaves the pending connectivity check unhandled, until our regular + * time-out kicks in. */ + return success; +} + +static gboolean +_con_curl_timeout_cb(gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data = user_data; + + _con_curl_check_connectivity(cb_data->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0); + _complete_queued(cb_data->self); + return G_SOURCE_CONTINUE; +} + +static int +multi_timer_cb(CURLM *multi, long timeout_msec, void *userdata) +{ + NMConnectivityCheckHandle *cb_data = userdata; + + nm_clear_g_source(&cb_data->concheck.curl_timer); + if (timeout_msec != -1) + cb_data->concheck.curl_timer = g_timeout_add(timeout_msec, _con_curl_timeout_cb, cb_data); + return 0; +} + +typedef struct { + NMConnectivityCheckHandle *cb_data; + + GSource *source; + + /* this is a very simplistic weak-pointer. If ConCurlSockData gets + * destroyed, it will set *destroy_notify to TRUE. + * + * _con_curl_socketevent_cb() uses this to detect whether it can + * safely access @fdp after _con_curl_check_connectivity(). */ + gboolean *destroy_notify; + +} ConCurlSockData; + +static gboolean +_con_curl_socketevent_cb(int fd, GIOCondition condition, gpointer user_data) +{ + ConCurlSockData * fdp = user_data; + NMConnectivityCheckHandle *cb_data = fdp->cb_data; + int action = 0; + gboolean fdp_destroyed = FALSE; + gboolean success; + + if (condition & G_IO_IN) + action |= CURL_CSELECT_IN; + if (condition & G_IO_OUT) + action |= CURL_CSELECT_OUT; + if (condition & G_IO_ERR) + action |= CURL_CSELECT_ERR; + + nm_assert(!fdp->destroy_notify); + fdp->destroy_notify = &fdp_destroyed; + + success = _con_curl_check_connectivity(cb_data->concheck.curl_mhandle, fd, action); + + if (fdp_destroyed) { + /* hups. fdp got invalidated during _con_curl_check_connectivity(). That's fine, + * just don't touch it. */ + } else { + nm_assert(fdp->destroy_notify == &fdp_destroyed); + fdp->destroy_notify = NULL; + if (!success) + nm_clear_g_source_inst(&fdp->source); + } + + _complete_queued(cb_data->self); + + return G_SOURCE_CONTINUE; +} + +static int +multi_socket_cb(CURL *e_handle, curl_socket_t fd, int what, void *userdata, void *socketp) +{ + NMConnectivityCheckHandle *cb_data = userdata; + ConCurlSockData * fdp = socketp; + + (void) _NM_ENSURE_TYPE(int, fd); + + if (what == CURL_POLL_REMOVE) { + if (fdp) { + if (fdp->destroy_notify) + *fdp->destroy_notify = TRUE; + nm_clear_g_source_inst(&fdp->source); + curl_multi_assign(cb_data->concheck.curl_mhandle, fd, NULL); + g_slice_free(ConCurlSockData, fdp); + } + } else { + GIOCondition condition; + + if (!fdp) { + fdp = g_slice_new(ConCurlSockData); + *fdp = (ConCurlSockData){ + .cb_data = cb_data, + }; + curl_multi_assign(cb_data->concheck.curl_mhandle, fd, fdp); + } else + nm_clear_g_source_inst(&fdp->source); + + if (what == CURL_POLL_IN) + condition = G_IO_IN; + else if (what == CURL_POLL_OUT) + condition = G_IO_OUT; + else if (what == CURL_POLL_INOUT) + condition = G_IO_IN | G_IO_OUT; + else + condition = 0; + + if (condition) { + fdp->source = nm_g_unix_fd_source_new(fd, + condition, + G_PRIORITY_DEFAULT, + _con_curl_socketevent_cb, + fdp, + NULL); + g_source_attach(fdp->source, NULL); + } + } + + return CURLM_OK; +} + +static size_t +easy_header_cb(char *buffer, size_t size, size_t nitems, void *userdata) +{ + NMConnectivityCheckHandle *cb_data = userdata; + size_t len = size * nitems; + + if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { + /* already completed. */ + return 0; + } + + if (len >= sizeof(HEADER_STATUS_ONLINE) - 1 + && !g_ascii_strncasecmp(buffer, HEADER_STATUS_ONLINE, sizeof(HEADER_STATUS_ONLINE) - 1)) { + cb_data_queue_completed(cb_data, NM_CONNECTIVITY_FULL, "status header found", NULL); + return 0; + } + + return len; +} + +static size_t +easy_write_cb(void *buffer, size_t size, size_t nmemb, void *userdata) +{ + NMConnectivityCheckHandle *cb_data = userdata; + size_t len = size * nmemb; + size_t response_len; + size_t check_len; + const char * response; + + if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { + /* already completed. */ + return 0; + } + + if (len == 0) { + /* no data. That can happen, it's fine. */ + return len; + } + + response = _con_config_get_response(cb_data->concheck.con_config); + + if (response[0] == '\0') { + /* no response expected. We are however graceful and accept any + * extra response that we might receive. We determine the empty + * response based on the status code 204. + * + * Continue receiving... */ + cb_data->concheck.response_good_cnt += len; + + if (cb_data->concheck.response_good_cnt > (gsize)(100 * 1024)) { + /* we expect an empty response. We accept either + * 1) status code 204 and any response + * 2) status code 200 and an empty response. + * + * Here, we want to continue receiving data, to see whether we have + * case 1). Arguably, the server shouldn't send us 204 with a non-empty + * response, but we accept that also with a non-empty response, so + * keep receiving. + * + * However, if we get an excessive amount of data, we put a stop on it + * and fail. */ + cb_data_queue_completed(cb_data, + NM_CONNECTIVITY_PORTAL, + "unexpected non-empty response", + NULL); + return 0; + } + + return len; + } + + nm_assert(cb_data->concheck.response_good_cnt < strlen(response)); + + response_len = strlen(response); + + check_len = NM_MIN(len, response_len - cb_data->concheck.response_good_cnt); + + if (strncmp(&response[cb_data->concheck.response_good_cnt], buffer, check_len) != 0) { + cb_data_queue_completed(cb_data, NM_CONNECTIVITY_PORTAL, "unexpected response", NULL); + return 0; + } + + cb_data->concheck.response_good_cnt += len; + + if (cb_data->concheck.response_good_cnt >= response_len) { + /* We already have enough data, and it matched. */ + cb_data_queue_completed(cb_data, NM_CONNECTIVITY_FULL, "expected response", NULL); + return 0; + } + + return len; +} + +static gboolean +_timeout_cb(gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data = user_data; + + nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); + nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, + &cb_data->handles_lst)); + + cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "timeout"); + return G_SOURCE_REMOVE; +} +#endif + +static gboolean +_idle_cb(gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data = user_data; + + nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); + nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, + &cb_data->handles_lst)); + nm_assert(cb_data->completed_reason); + + cb_data->timeout_id = 0; + cb_data_complete(cb_data, cb_data->completed_state, cb_data->completed_reason); + return G_SOURCE_REMOVE; +} + +#if WITH_CONCHECK +static void +do_curl_request(NMConnectivityCheckHandle *cb_data) +{ + CURLM *mhandle; + CURL * ehandle; + long resolve; + + mhandle = curl_multi_init(); + if (!mhandle) { + cb_data_complete(cb_data, NM_CONNECTIVITY_ERROR, "curl error"); + return; + } + + ehandle = curl_easy_init(); + if (!ehandle) { + curl_multi_cleanup(mhandle); + cb_data_complete(cb_data, NM_CONNECTIVITY_ERROR, "curl error"); + return; + } + + cb_data->concheck.curl_mhandle = mhandle; + cb_data->concheck.curl_ehandle = ehandle; + cb_data->concheck.request_headers = curl_slist_append(NULL, "Connection: close"); + cb_data->timeout_id = g_timeout_add_seconds(20, _timeout_cb, cb_data); + + curl_multi_setopt(mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb); + curl_multi_setopt(mhandle, CURLMOPT_SOCKETDATA, cb_data); + curl_multi_setopt(mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb); + curl_multi_setopt(mhandle, CURLMOPT_TIMERDATA, cb_data); + + switch (cb_data->addr_family) { + case AF_INET: + resolve = CURL_IPRESOLVE_V4; + break; + case AF_INET6: + resolve = CURL_IPRESOLVE_V6; + break; + case AF_UNSPEC: + resolve = CURL_IPRESOLVE_WHATEVER; + break; + default: + resolve = CURL_IPRESOLVE_WHATEVER; + g_warn_if_reached(); + } + + curl_easy_setopt(ehandle, CURLOPT_URL, cb_data->concheck.con_config->uri); + curl_easy_setopt(ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb); + curl_easy_setopt(ehandle, CURLOPT_WRITEDATA, cb_data); + curl_easy_setopt(ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb); + curl_easy_setopt(ehandle, CURLOPT_HEADERDATA, cb_data); + curl_easy_setopt(ehandle, CURLOPT_PRIVATE, cb_data); + curl_easy_setopt(ehandle, CURLOPT_HTTPHEADER, cb_data->concheck.request_headers); + curl_easy_setopt(ehandle, CURLOPT_INTERFACE, cb_data->ifspec); + curl_easy_setopt(ehandle, CURLOPT_RESOLVE, cb_data->concheck.hosts); + curl_easy_setopt(ehandle, CURLOPT_IPRESOLVE, resolve); + + curl_multi_add_handle(mhandle, ehandle); +} + +static void +resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data; + gs_unref_variant GVariant *result = NULL; + gs_unref_variant GVariant *addresses = NULL; + gsize no_addresses; + int ifindex; + int addr_family; + gsize len = 0; + gsize i; + gs_free_error GError *error = NULL; + + result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + cb_data = user_data; + + g_clear_object(&cb_data->concheck.resolve_cancellable); + + if (!result) { + /* Never mind. Just let do curl do its own resolving. */ + _LOG2D("can't resolve a name via systemd-resolved: %s", error->message); + do_curl_request(cb_data); + return; + } + + addresses = g_variant_get_child_value(result, 0); + no_addresses = g_variant_n_children(addresses); + + for (i = 0; i < no_addresses; i++) { + gs_unref_variant GVariant *address = NULL; + char str_addr[NM_UTILS_INET_ADDRSTRLEN]; + gs_free char * host_entry = NULL; + const guchar * address_buf; + + g_variant_get_child(addresses, i, "(ii@ay)", &ifindex, &addr_family, &address); + + if (cb_data->addr_family != AF_UNSPEC && cb_data->addr_family != addr_family) + continue; + + address_buf = g_variant_get_fixed_array(address, &len, 1); + if ((addr_family == AF_INET && len != sizeof(struct in_addr)) + || (addr_family == AF_INET6 && len != sizeof(struct in6_addr))) + continue; + + host_entry = g_strdup_printf("%s:%s:%s", + cb_data->concheck.con_config->host, + cb_data->concheck.con_config->port ?: "80", + nm_utils_inet_ntop(addr_family, address_buf, str_addr)); + cb_data->concheck.hosts = curl_slist_append(cb_data->concheck.hosts, host_entry); + _LOG2T("adding '%s' to curl resolve list", host_entry); + } + + do_curl_request(cb_data); +} +#endif + +#define SD_RESOLVED_DNS ((guint64)(1LL << 0)) + +static NMConnectivityState +check_platform_config(NMConnectivity *self, + NMPlatform * platform, + int ifindex, + int addr_family, + const char ** reason) +{ + const NMDedupMultiHeadEntry *addresses; + const NMDedupMultiHeadEntry *routes; + + if (!nm_platform_link_is_connected(platform, ifindex)) { + NM_SET_OUT(reason, "no carrier"); + return NM_CONNECTIVITY_NONE; + } + + addresses = nm_platform_lookup_object(platform, + addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS + : NMP_OBJECT_TYPE_IP6_ADDRESS, + ifindex); + if (!addresses || addresses->len == 0) { + NM_SET_OUT(reason, "no IP address configured"); + return NM_CONNECTIVITY_NONE; + } + + routes = nm_platform_lookup_object(platform, + addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE + : NMP_OBJECT_TYPE_IP6_ROUTE, + ifindex); + if (!routes || routes->len == 0) { + NM_SET_OUT(reason, "no IP route configured"); + return NM_CONNECTIVITY_NONE; + } + + switch (addr_family) { + case AF_INET: + { + const NMPlatformIP4Route *route; + gboolean found_global = FALSE; + NMDedupMultiIter iter; + const NMPObject * plobj; + + /* For IPv4 also require a route with global scope. */ + nmp_cache_iter_for_each (&iter, routes, &plobj) { + route = NMP_OBJECT_CAST_IP4_ROUTE(plobj); + if (nm_platform_route_scope_inv(route->scope_inv) == RT_SCOPE_UNIVERSE) { + found_global = TRUE; + break; + } + } + + if (!found_global) { + NM_SET_OUT(reason, "no global route configured"); + return NM_CONNECTIVITY_LIMITED; + } + break; + } + case AF_INET6: + /* Route scopes aren't meaningful for IPv6 so any route is fine. */ + break; + default: + g_return_val_if_reached(FALSE); + } + + NM_SET_OUT(reason, NULL); + return NM_CONNECTIVITY_UNKNOWN; +} + +NMConnectivityCheckHandle * +nm_connectivity_check_start(NMConnectivity * self, + int addr_family, + NMPlatform * platform, + int ifindex, + const char * iface, + NMConnectivityCheckCallback callback, + gpointer user_data) +{ + NMConnectivityPrivate * priv; + NMConnectivityCheckHandle *cb_data; + static guint64 request_counter = 0; + + g_return_val_if_fail(NM_IS_CONNECTIVITY(self), NULL); + g_return_val_if_fail(callback, NULL); + nm_assert(!platform || NM_IS_PLATFORM(platform)); + + priv = NM_CONNECTIVITY_GET_PRIVATE(self); + + cb_data = g_slice_new0(NMConnectivityCheckHandle); + cb_data->self = self; + cb_data->request_counter = ++request_counter; + c_list_link_tail(&priv->handles_lst_head, &cb_data->handles_lst); + cb_data->callback = callback; + cb_data->user_data = user_data; + cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN; + cb_data->addr_family = addr_family; + if (iface) + cb_data->ifspec = g_strdup_printf("if!%s", iface); + +#if WITH_CONCHECK + + cb_data->concheck.con_config = _con_config_ref(priv->con_config); + + if (iface && ifindex > 0 && priv->enabled && priv->uri_valid) { + gboolean has_systemd_resolved; + NMConnectivityState state; + const char * reason; + + cb_data->concheck.ch_ifindex = ifindex; + + if (platform) { + state = check_platform_config(self, platform, ifindex, addr_family, &reason); + nm_assert((state == NM_CONNECTIVITY_UNKNOWN) == !reason); + if (state != NM_CONNECTIVITY_UNKNOWN) { + _LOG2D("skip connectivity check due to %s", reason); + cb_data->completed_state = state; + cb_data->completed_reason = reason; + cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); + return cb_data; + } + } + + /* note that we pick up support for systemd-resolved right away when we need it. + * We don't need to remember the setting, because we can (cheaply) check anew + * on each request. + * + * Yes, this makes NMConnectivity singleton dependent on NMDnsManager singleton. + * Well, not really: it makes connectivity-check-start dependent on NMDnsManager + * which merely means, not to start a connectivity check, late during shutdown. + * + * NMDnsSystemdResolved tries to D-Bus activate systemd-resolved only once, + * to not spam syslog with failures messages from dbus-daemon. + * Note that unless NMDnsSystemdResolved tried and failed to start systemd-resolved, + * it guesses that systemd-resolved is activatable and returns %TRUE here. That + * means, while NMDnsSystemdResolved would not try to D-Bus activate systemd-resolved + * more than once, NMConnectivity might -- until NMDnsSystemdResolved tried itself + * and noticed that systemd-resolved is not available. + * This is relatively cumbersome to avoid, because we would have to go through + * NMDnsSystemdResolved trying to asynchronously start the service, to ensure there + * is only one attempt to start the service. */ + has_systemd_resolved = nm_dns_manager_has_systemd_resolved(nm_dns_manager_get()); + + if (has_systemd_resolved) { + GDBusConnection *dbus_connection; + + dbus_connection = NM_MAIN_DBUS_CONNECTION_GET; + if (!dbus_connection) { + /* we have no D-Bus connection? That might happen in configure and quit mode. + * + * Anyway, something is very odd, just fail connectivity check. */ + _LOG2D("start fake request (fail due to no D-Bus connection)"); + cb_data->completed_state = NM_CONNECTIVITY_ERROR; + cb_data->completed_reason = "no D-Bus connection"; + cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); + return cb_data; + } + + cb_data->concheck.resolve_cancellable = g_cancellable_new(); + + g_dbus_connection_call(dbus_connection, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname", + g_variant_new("(isit)", + (gint32) cb_data->concheck.ch_ifindex, + cb_data->concheck.con_config->host, + (gint32) cb_data->addr_family, + SD_RESOLVED_DNS), + G_VARIANT_TYPE("(a(iiay)st)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cb_data->concheck.resolve_cancellable, + resolve_cb, + cb_data); + _LOG2D("start request to '%s' (try resolving '%s' using systemd-resolved)", + cb_data->concheck.con_config->uri, + cb_data->concheck.con_config->host); + } else { + _LOG2D("start request to '%s' (systemd-resolved not available)", + cb_data->concheck.con_config->uri); + do_curl_request(cb_data); + } + + return cb_data; + } +#endif + + if (!cb_data->ifspec) { + cb_data->completed_state = NM_CONNECTIVITY_ERROR; + cb_data->completed_reason = "missing interface"; + } else { + cb_data->completed_state = NM_CONNECTIVITY_FAKE; + cb_data->completed_reason = "fake result"; + } + _LOG2D("start fake request (%s)", cb_data->completed_reason); + cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); + + return cb_data; +} + +void +nm_connectivity_check_cancel(NMConnectivityCheckHandle *cb_data) +{ + g_return_if_fail(cb_data); + g_return_if_fail(NM_IS_CONNECTIVITY(cb_data->self)); + + nm_assert( + c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, + &cb_data->handles_lst) + || c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->completed_handles_lst_head, + &cb_data->handles_lst)); + + cb_data_complete(cb_data, NM_CONNECTIVITY_CANCELLED, "cancelled"); +} + +/*****************************************************************************/ + +gboolean +nm_connectivity_check_enabled(NMConnectivity *self) +{ + g_return_val_if_fail(NM_IS_CONNECTIVITY(self), FALSE); + + return NM_CONNECTIVITY_GET_PRIVATE(self)->enabled; +} + +/*****************************************************************************/ + +guint +nm_connectivity_get_interval(NMConnectivity *self) +{ + return nm_connectivity_check_enabled(self) ? NM_CONNECTIVITY_GET_PRIVATE(self)->interval : 0; +} + +static gboolean +host_and_port_from_uri(const char *uri, char **host, char **port) +{ + const char *p = uri; + const char *host_begin = NULL; + size_t host_len = 0; + const char *port_begin = NULL; + size_t port_len = 0; + + /* scheme */ + while (*p != ':' && *p != '/') { + if (!*p++) + return FALSE; + } + + /* :// */ + if (*p++ != ':') + return FALSE; + if (*p++ != '/') + return FALSE; + if (*p++ != '/') + return FALSE; + /* host */ + if (*p == '[') + return FALSE; + host_begin = p; + while (*p && *p != ':' && *p != '/') { + host_len++; + p++; + } + if (host_len == 0) + return FALSE; + *host = g_strndup(host_begin, host_len); + + /* port */ + if (*p++ == ':') { + port_begin = p; + while (*p && *p != '/') { + port_len++; + p++; + } + if (port_len) + *port = g_strndup(port_begin, port_len); + } + + return TRUE; +} + +static void +update_config(NMConnectivity *self, NMConfigData *config_data) +{ + NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE(self); + guint interval; + gboolean enabled; + gboolean changed = FALSE; + const char * cur_uri = priv->con_config ? priv->con_config->uri : NULL; + const char * cur_response = priv->con_config ? priv->con_config->response : NULL; + const char * new_response; + const char * new_uri; + gboolean new_uri_valid = priv->uri_valid; + gboolean new_host_port = FALSE; + gs_free char * new_host = NULL; + gs_free char * new_port = NULL; + + new_uri = nm_config_data_get_connectivity_uri(config_data); + if (!nm_streq0(new_uri, cur_uri)) { + new_uri_valid = (new_uri && *new_uri); + if (new_uri_valid) { + gs_free char *scheme = g_uri_parse_scheme(new_uri); + gboolean is_https = FALSE; + + if (!scheme) { + _LOGE("invalid URI '%s' for connectivity check.", new_uri); + new_uri_valid = FALSE; + } else if (g_ascii_strcasecmp(scheme, "https") == 0) { + _LOGW("use of HTTPS for connectivity checking is not reliable and is discouraged " + "(URI: %s)", + new_uri); + is_https = TRUE; + } else if (g_ascii_strcasecmp(scheme, "http") != 0) { + _LOGE("scheme of '%s' uri doesn't use a scheme that is allowed for connectivity " + "check.", + new_uri); + new_uri_valid = FALSE; + } + if (new_uri_valid) { + new_host_port = TRUE; + if (!host_and_port_from_uri(new_uri, &new_host, &new_port)) { + _LOGE("cannot parse host and port from '%s'", new_uri); + new_uri_valid = FALSE; + } else if (!new_port && is_https) + new_port = g_strdup("443"); + } + } + + if (new_uri_valid || priv->uri_valid != new_uri_valid) + changed = TRUE; + } + + new_response = nm_config_data_get_connectivity_response(config_data); + if (!nm_streq0(new_response, cur_response)) + changed = TRUE; + + if (!priv->con_config || !nm_streq0(new_uri, priv->con_config->uri) + || !nm_streq0(new_response, priv->con_config->response)) { + if (!new_host_port) { + new_host = priv->con_config ? g_strdup(priv->con_config->host) : NULL; + new_port = priv->con_config ? g_strdup(priv->con_config->port) : NULL; + } + _con_config_unref(priv->con_config); + priv->con_config = g_slice_new(ConConfig); + *priv->con_config = (ConConfig){ + .ref_count = 1, + .uri = g_strdup(new_uri), + .response = g_strdup(new_response), + .host = g_steal_pointer(&new_host), + .port = g_steal_pointer(&new_port), + }; + } + priv->uri_valid = new_uri_valid; + + interval = nm_config_data_get_connectivity_interval(config_data); + interval = MIN(interval, (7 * 24 * 3600)); + if (priv->interval != interval) { + priv->interval = interval; + changed = TRUE; + } + + enabled = FALSE; +#if WITH_CONCHECK + if (priv->uri_valid && priv->interval) + enabled = nm_config_data_get_connectivity_enabled(config_data); +#endif + + if (priv->enabled != enabled) { + priv->enabled = enabled; + changed = TRUE; + } + + if (changed) + g_signal_emit(self, signals[CONFIG_CHANGED], 0); +} + +static void +config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMConnectivity * self) +{ + update_config(self, config_data); +} + +/*****************************************************************************/ + +static void +nm_connectivity_init(NMConnectivity *self) +{ + NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE(self); +#if WITH_CONCHECK + CURLcode ret; +#endif + + c_list_init(&priv->handles_lst_head); + c_list_init(&priv->completed_handles_lst_head); + + priv->config = g_object_ref(nm_config_get()); + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed_cb), + self); + +#if WITH_CONCHECK + ret = curl_global_init(CURL_GLOBAL_ALL); + if (ret != CURLE_OK) { + _LOGE("unable to init cURL, connectivity check will not work: (%d) %s", + ret, + curl_easy_strerror(ret)); + } +#endif + + update_config(self, nm_config_get_data(priv->config)); +} + +static void +dispose(GObject *object) +{ + NMConnectivity * self = NM_CONNECTIVITY(object); + NMConnectivityPrivate * priv = NM_CONNECTIVITY_GET_PRIVATE(self); + NMConnectivityCheckHandle *cb_data; + + nm_assert(c_list_is_empty(&priv->completed_handles_lst_head)); + + while ( + (cb_data = + c_list_first_entry(&priv->handles_lst_head, NMConnectivityCheckHandle, handles_lst))) + cb_data_complete(cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down"); + + nm_clear_pointer(&priv->con_config, _con_config_unref); + +#if WITH_CONCHECK + curl_global_cleanup(); +#endif + + if (priv->config) { + g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); + g_clear_object(&priv->config); + } + + G_OBJECT_CLASS(nm_connectivity_parent_class)->dispose(object); +} + +static void +nm_connectivity_class_init(NMConnectivityClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + signals[CONFIG_CHANGED] = g_signal_new(NM_CONNECTIVITY_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + object_class->dispose = dispose; +} diff --git a/src/core/nm-connectivity.h b/src/core/nm-connectivity.h new file mode 100644 index 0000000..c67668c --- /dev/null +++ b/src/core/nm-connectivity.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Thomas Bechtold + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_CONNECTIVITY_H__ +#define __NETWORKMANAGER_CONNECTIVITY_H__ + +#include "nm-dbus-interface.h" + +/*****************************************************************************/ + +static inline int +nm_connectivity_state_cmp(NMConnectivityState a, NMConnectivityState b) +{ + if (a == NM_CONNECTIVITY_PORTAL && b == NM_CONNECTIVITY_LIMITED) + return 1; + if (b == NM_CONNECTIVITY_PORTAL && a == NM_CONNECTIVITY_LIMITED) + return -1; + NM_CMP_DIRECT(a, b); + return 0; +} + +/*****************************************************************************/ + +#define NM_CONNECTIVITY_ERROR ((NMConnectivityState) -1) +#define NM_CONNECTIVITY_FAKE ((NMConnectivityState) -2) +#define NM_CONNECTIVITY_CANCELLED ((NMConnectivityState) -3) +#define NM_CONNECTIVITY_DISPOSING ((NMConnectivityState) -4) + +#define NM_TYPE_CONNECTIVITY (nm_connectivity_get_type()) +#define NM_CONNECTIVITY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_CONNECTIVITY, NMConnectivity)) +#define NM_CONNECTIVITY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_CONNECTIVITY, NMConnectivityClass)) +#define NM_IS_CONNECTIVITY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_CONNECTIVITY)) +#define NM_IS_CONNECTIVITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_CONNECTIVITY)) +#define NM_CONNECTIVITY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_CONNECTIVITY, NMConnectivityClass)) + +#define NM_CONNECTIVITY_CONFIG_CHANGED "config-changed" + +typedef struct _NMConnectivityClass NMConnectivityClass; + +GType nm_connectivity_get_type(void); + +NMConnectivity *nm_connectivity_get(void); + +const char *nm_connectivity_state_to_string(NMConnectivityState state); + +gboolean nm_connectivity_check_enabled(NMConnectivity *self); + +guint nm_connectivity_get_interval(NMConnectivity *self); + +typedef struct _NMConnectivityCheckHandle NMConnectivityCheckHandle; + +typedef void (*NMConnectivityCheckCallback)(NMConnectivity * self, + NMConnectivityCheckHandle *handle, + NMConnectivityState state, + gpointer user_data); + +NMConnectivityCheckHandle *nm_connectivity_check_start(NMConnectivity * self, + int family, + NMPlatform * platform, + int ifindex, + const char * iface, + NMConnectivityCheckCallback callback, + gpointer user_data); + +void nm_connectivity_check_cancel(NMConnectivityCheckHandle *handle); + +#endif /* __NETWORKMANAGER_CONNECTIVITY_H__ */ diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c new file mode 100644 index 0000000..660f6f4 --- /dev/null +++ b/src/core/nm-core-utils.c @@ -0,0 +1,5117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2018 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-core-utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-std-aux/unaligned.h" +#include "nm-glib-aux/nm-random-utils.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-glib-aux/nm-time-utils.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +#ifdef __NM_SD_UTILS_H__ + #error \ + "nm-core-utils.c should stay independent of systemd utils. Are you looking for NetworkMangerUtils.c? " +#endif + +G_STATIC_ASSERT(sizeof(NMUtilsTestFlags) <= sizeof(int)); + +/* we read _nm_utils_testing without memory barrier. This is thread-safe, + * because the static variable is initialized to zero, and only reset + * once to a non-zero value (via g_atomic_int_compare_and_exchange()). + * + * Since there is only one integer that contains the data, there is no + * caching problem reading this (atomic int) variable without + * synchronization/memory-barrier. Contrary to a double-checked locking, + * where one needs a memory barrier to read the variable and ensure + * that also the related data is coherent in cache. Here there is no + * related data. */ +static int _nm_utils_testing = 0; + +gboolean +nm_utils_get_testing_initialized() +{ + NMUtilsTestFlags flags; + + flags = (NMUtilsTestFlags) _nm_utils_testing; + if (flags == NM_UTILS_TEST_NONE) + flags = (NMUtilsTestFlags) g_atomic_int_get(&_nm_utils_testing); + return flags != NM_UTILS_TEST_NONE; +} + +NMUtilsTestFlags +nm_utils_get_testing() +{ + NMUtilsTestFlags flags; + +again: + flags = (NMUtilsTestFlags) _nm_utils_testing; + if (flags != NM_UTILS_TEST_NONE) { + /* Flags already initialized. Return them. */ + return flags & NM_UTILS_TEST_ALL; + } + + /* Accessing nm_utils_get_testing() causes us to set the flags to initialized. + * Detecting running tests also based on g_test_initialized(). */ + flags = _NM_UTILS_TEST_INITIALIZED; + if (g_test_initialized()) + flags |= _NM_UTILS_TEST_GENERAL; + + g_atomic_int_compare_and_exchange(&_nm_utils_testing, 0, (int) flags); + + /* regardless of whether we won the race of initializing _nm_utils_testing, + * go back and read the value again. It must be non-zero by now. */ + goto again; +} + +void +_nm_utils_set_testing(NMUtilsTestFlags flags) +{ + g_assert(!NM_FLAGS_ANY(flags, ~NM_UTILS_TEST_ALL)); + + /* mask out everything except ALL, and always set GENERAL. */ + flags = (flags & NM_UTILS_TEST_ALL) | (_NM_UTILS_TEST_GENERAL | _NM_UTILS_TEST_INITIALIZED); + + if (!g_atomic_int_compare_and_exchange(&_nm_utils_testing, 0, (int) flags)) { + /* We only allow setting _nm_utils_set_testing() once, before fetching the + * value with nm_utils_get_testing(). */ + g_return_if_reached(); + } +} + +/*****************************************************************************/ + +static GSList * _singletons = NULL; +static gboolean _singletons_shutdown = FALSE; + +static void +_nm_singleton_instance_weak_cb(gpointer data, GObject *where_the_object_was) +{ + nm_assert(g_slist_find(_singletons, where_the_object_was)); + + _singletons = g_slist_remove(_singletons, where_the_object_was); +} + +static void __attribute__((destructor)) _nm_singleton_instance_destroy(void) +{ + _singletons_shutdown = TRUE; + + while (_singletons) { + GObject *instance = _singletons->data; + + _singletons = g_slist_delete_link(_singletons, _singletons); + + g_object_weak_unref(instance, _nm_singleton_instance_weak_cb, NULL); + + if (instance->ref_count > 1) { + nm_log_dbg(LOGD_CORE, + "disown %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + G_OBJECT_TYPE_NAME(instance), + NM_HASH_OBFUSCATE_PTR(instance)); + } + + g_object_unref(instance); + } +} + +void +_nm_singleton_instance_register_destruction(GObject *instance) +{ + g_return_if_fail(G_IS_OBJECT(instance)); + + /* Don't allow registration after shutdown. We only destroy the singletons + * once. */ + g_return_if_fail(!_singletons_shutdown); + + g_object_weak_ref(instance, _nm_singleton_instance_weak_cb, NULL); + + _singletons = g_slist_prepend(_singletons, instance); +} + +/*****************************************************************************/ + +static double +_exp10(guint16 ex) +{ + double v; + + if (ex == 0) + return 1.0; + + v = _exp10(ex / 2); + v = v * v; + if (ex % 2) + v *= 10; + return v; +} + +/* + * nm_utils_exp10: + * @ex: the exponent + * + * Returns: 10^ex, or pow(10, ex), or exp10(ex). + */ +double +nm_utils_exp10(gint16 ex) +{ + if (ex >= 0) + return _exp10(ex); + return 1.0 / _exp10(-((gint32) ex)); +} + +/*****************************************************************************/ + +gboolean +nm_ether_addr_is_valid(const NMEtherAddr *addr) +{ + static const guint8 invalid_addr[][ETH_ALEN] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, + {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}, /* prism54 dummy MAC */ + }; + int i; + + if (!addr) + return FALSE; + + /* Check for multicast address */ + if (addr->ether_addr_octet[0] & 0x01u) + return FALSE; + + for (i = 0; i < (int) G_N_ELEMENTS(invalid_addr); i++) { + if (memcmp(addr, invalid_addr[i], ETH_ALEN) == 0) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_ether_addr_is_valid_str(const char *str) +{ + NMEtherAddr addr_bin; + + if (!str) + return FALSE; + + if (!nm_utils_hwaddr_aton(str, &addr_bin, ETH_ALEN)) + return FALSE; + + return nm_ether_addr_is_valid(&addr_bin); +} + +/*****************************************************************************/ + +void +nm_utils_array_remove_at_indexes(GArray *array, const guint *indexes_to_delete, gsize len) +{ + gsize elt_size; + guint index_to_delete; + guint i_src; + guint mm_src, mm_dst, mm_len; + gsize i_itd; + guint res_length; + + g_return_if_fail(array); + if (!len) + return; + g_return_if_fail(indexes_to_delete); + + elt_size = g_array_get_element_size(array); + + i_itd = 0; + index_to_delete = indexes_to_delete[0]; + if (index_to_delete >= array->len) + g_return_if_reached(); + + res_length = array->len - 1; + + mm_dst = index_to_delete; + mm_src = index_to_delete; + mm_len = 0; + + for (i_src = index_to_delete; i_src < array->len; i_src++) { + if (i_src < index_to_delete) + mm_len++; + else { + /* we require indexes_to_delete to contain non-repeated, ascending + * indexes. Otherwise, we would need to presort the indexes. */ + while (TRUE) { + guint dd; + + if (i_itd + 1 >= len) { + index_to_delete = G_MAXUINT; + break; + } + + dd = indexes_to_delete[++i_itd]; + if (dd > index_to_delete) { + if (dd >= array->len) + g_warn_if_reached(); + else { + g_assert(res_length > 0); + res_length--; + } + index_to_delete = dd; + break; + } + g_warn_if_reached(); + } + + if (mm_len) { + memmove(&array->data[mm_dst * elt_size], + &array->data[mm_src * elt_size], + mm_len * elt_size); + mm_dst += mm_len; + mm_src += mm_len + 1; + mm_len = 0; + } else + mm_src++; + } + } + if (mm_len) { + memmove(&array->data[mm_dst * elt_size], + &array->data[mm_src * elt_size], + mm_len * elt_size); + } + g_array_set_size(array, res_length); +} + +static const char * +_trunk_first_line(char *str) +{ + char *s; + + s = strchr(str, '\n'); + if (s) + s[0] = '\0'; + return str; +} + +int +nm_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) +{ + gs_unref_ptrarray GPtrArray *argv = NULL; + int exit_status; + gs_free char * _log_str = NULL; +#define ARGV_TO_STR(argv) \ + (_log_str ? _log_str : (_log_str = g_strjoinv(" ", (char **) argv->pdata))) + GError * local = NULL; + va_list ap; + NMLogLevel llevel = suppress_error_logging ? LOGL_DEBUG : LOGL_ERR; + gs_free char *std_out = NULL, *std_err = NULL; + + g_return_val_if_fail(!error || !*error, -1); + g_return_val_if_fail(arg1, -1); + + /* construct the argument list */ + argv = g_ptr_array_sized_new(4); + g_ptr_array_add(argv, "/sbin/modprobe"); + g_ptr_array_add(argv, "--use-blacklist"); + g_ptr_array_add(argv, (char *) arg1); + + va_start(ap, arg1); + while ((arg1 = va_arg(ap, const char *))) + g_ptr_array_add(argv, (char *) arg1); + va_end(ap); + + g_ptr_array_add(argv, NULL); + + nm_log_dbg(LOGD_CORE, "modprobe: '%s'", ARGV_TO_STR(argv)); + if (!g_spawn_sync(NULL, + (char **) argv->pdata, + NULL, + 0, + NULL, + NULL, + &std_out, + &std_err, + &exit_status, + &local)) { + nm_log(llevel, + LOGD_CORE, + NULL, + NULL, + "modprobe: '%s' failed: %s", + ARGV_TO_STR(argv), + local->message); + g_propagate_error(error, local); + return -1; + } else if (exit_status != 0) { + nm_log(llevel, + LOGD_CORE, + NULL, + NULL, + "modprobe: '%s' exited with error %d%s%s%s%s%s%s", + ARGV_TO_STR(argv), + exit_status, + std_out && *std_out ? " (" : "", + std_out && *std_out ? _trunk_first_line(std_out) : "", + std_out && *std_out ? ")" : "", + std_err && *std_err ? " (" : "", + std_err && *std_err ? _trunk_first_line(std_err) : "", + std_err && *std_err ? ")" : ""); + } + + return exit_status; +} + +/*****************************************************************************/ + +typedef struct { + pid_t pid; + NMLogDomain log_domain; + union { + struct { + gint64 wait_start_us; + guint source_timeout_kill_id; + } async; + struct { + gboolean success; + int child_status; + } sync; + }; + NMUtilsKillChildAsyncCb callback; + void * user_data; + + char log_name[1]; /* variable-length object, must be last element!! */ +} KillChildAsyncData; + +#define LOG_NAME_FMT "kill child process '%s' (%ld)" +#define LOG_NAME_PROCESS_FMT "kill process '%s' (%ld)" +#define LOG_NAME_ARGS log_name, (long) pid + +static KillChildAsyncData * +_kc_async_data_alloc(pid_t pid, + NMLogDomain log_domain, + const char * log_name, + NMUtilsKillChildAsyncCb callback, + void * user_data) +{ + KillChildAsyncData *data; + size_t log_name_len; + + /* append the name at the end of our KillChildAsyncData. */ + log_name_len = strlen(LOG_NAME_FMT) + 20 + strlen(log_name); + data = g_malloc(sizeof(KillChildAsyncData) - 1 + log_name_len); + g_snprintf(data->log_name, log_name_len, LOG_NAME_FMT, LOG_NAME_ARGS); + + data->pid = pid; + data->user_data = user_data; + data->callback = callback; + data->log_domain = log_domain; + + return data; +} + +#define KC_EXIT_TO_STRING_BUF_SIZE 128 +static const char * +_kc_exit_to_string(char *buf, int exit) +#define _kc_exit_to_string(buf, exit) \ + (G_STATIC_ASSERT_EXPR(sizeof(buf) == KC_EXIT_TO_STRING_BUF_SIZE && sizeof((buf)[0]) == 1), \ + _kc_exit_to_string(buf, exit)) +{ + if (WIFEXITED(exit)) + g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "normally with status %d", WEXITSTATUS(exit)); + else if (WIFSIGNALED(exit)) + g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "by signal %d", WTERMSIG(exit)); + else + g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "with unexpected status %d", exit); + return buf; +} + +static const char * +_kc_signal_to_string(int sig) +{ + switch (sig) { + case 0: + return "no signal (0)"; + case SIGKILL: + return "SIGKILL (" G_STRINGIFY(SIGKILL) ")"; + case SIGTERM: + return "SIGTERM (" G_STRINGIFY(SIGTERM) ")"; + default: + return "Unexpected signal"; + } +} + +#define KC_WAITED_TO_STRING 100 +static const char * +_kc_waited_to_string(char *buf, gint64 wait_start_us) +#define _kc_waited_to_string(buf, wait_start_us) \ + (G_STATIC_ASSERT_EXPR(sizeof(buf) == KC_WAITED_TO_STRING && sizeof((buf)[0]) == 1), \ + _kc_waited_to_string(buf, wait_start_us)) +{ + g_snprintf(buf, + KC_WAITED_TO_STRING, + " (%ld usec elapsed)", + (long) (nm_utils_get_monotonic_timestamp_usec() - wait_start_us)); + return buf; +} + +static void +_kc_cb_watch_child(GPid pid, int status, gpointer user_data) +{ + KillChildAsyncData *data = user_data; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE], buf_wait[KC_WAITED_TO_STRING]; + + if (data->async.source_timeout_kill_id) + g_source_remove(data->async.source_timeout_kill_id); + + nm_log_dbg(data->log_domain, + "%s: terminated %s%s", + data->log_name, + _kc_exit_to_string(buf_exit, status), + _kc_waited_to_string(buf_wait, data->async.wait_start_us)); + + if (data->callback) + data->callback(pid, TRUE, status, data->user_data); + + g_free(data); +} + +static gboolean +_kc_cb_timeout_grace_period(void *user_data) +{ + KillChildAsyncData *data = user_data; + int ret, errsv; + + data->async.source_timeout_kill_id = 0; + + if ((ret = kill(data->pid, SIGKILL)) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err(LOGD_CORE | data->log_domain, + "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)", + data->log_name, + ret, + nm_strerror_native(errsv), + errsv); + } + } else { + nm_log_dbg(data->log_domain, + "%s: process not terminated after %ld usec. Sending SIGKILL signal", + data->log_name, + (long) (nm_utils_get_monotonic_timestamp_usec() - data->async.wait_start_us)); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +_kc_invoke_callback_idle(gpointer user_data) +{ + KillChildAsyncData *data = user_data; + + if (data->sync.success) { + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + + nm_log_dbg(data->log_domain, + "%s: invoke callback: terminated %s", + data->log_name, + _kc_exit_to_string(buf_exit, data->sync.child_status)); + } else + nm_log_dbg(data->log_domain, "%s: invoke callback: killing child failed", data->log_name); + + data->callback(data->pid, data->sync.success, data->sync.child_status, data->user_data); + g_free(data); + + return G_SOURCE_REMOVE; +} + +static void +_kc_invoke_callback(pid_t pid, + NMLogDomain log_domain, + const char * log_name, + NMUtilsKillChildAsyncCb callback, + void * user_data, + gboolean success, + int child_status) +{ + KillChildAsyncData *data; + + if (!callback) + return; + + data = _kc_async_data_alloc(pid, log_domain, log_name, callback, user_data); + data->sync.success = success; + data->sync.child_status = child_status; + + g_idle_add(_kc_invoke_callback_idle, data); +} + +/* nm_utils_kill_child_async: + * @pid: the process id of the process to kill + * @sig: signal to send initially. Set to 0 to send not signal. + * @log_domain: the logging domain used for logging (LOGD_NONE to suppress logging) + * @log_name: for logging, the name of the processes to kill + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored. + * @callback: (allow-none): callback after the child terminated. This function will always + * be invoked asynchronously. + * @user_data: passed on to callback + * + * Uses g_child_watch_add(), so note the glib comment: if you obtain pid from g_spawn_async() or + * g_spawn_async_with_pipes() you will need to pass %G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn + * function for the child watching to work. + * Also note, that you must g_source_remove() any other child watchers for @pid because glib + * supports only one watcher per child. + **/ +void +nm_utils_kill_child_async(pid_t pid, + int sig, + NMLogDomain log_domain, + const char * log_name, + guint32 wait_before_kill_msec, + NMUtilsKillChildAsyncCb callback, + void * user_data) +{ + int status = 0, errsv; + pid_t ret; + KillChildAsyncData *data; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + + g_return_if_fail(pid > 0); + g_return_if_fail(log_name != NULL); + + /* let's see if the child already terminated... */ + ret = waitpid(pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, + (long) ret, + _kc_exit_to_string(buf_exit, status)); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, TRUE, status); + return; + } else if (ret != 0) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, + nm_strerror_native(errsv), + errsv); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); + return; + } + } + + /* send the first signal. */ + if (kill(pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": unexpected error sending %s: %s (%d)", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); + return; + } + + /* let's try again with waitpid, probably there was a race... */ + ret = waitpid(pid, &status, 0); + if (ret > 0) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, + (long) ret, + _kc_exit_to_string(buf_exit, status)); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, TRUE, status); + } else { + errsv = errno; + nm_log_err( + LOGD_CORE | log_domain, + LOG_NAME_FMT + ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s", + LOG_NAME_ARGS, + (long) ret, + nm_strerror_native(errsv), + errsv, + _kc_signal_to_string(sig)); + _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); + } + return; + } + + data = _kc_async_data_alloc(pid, log_domain, log_name, callback, user_data); + data->async.wait_start_us = nm_utils_get_monotonic_timestamp_usec(); + + if (sig != SIGKILL && wait_before_kill_msec > 0) { + data->async.source_timeout_kill_id = + g_timeout_add(wait_before_kill_msec, _kc_cb_timeout_grace_period, data); + nm_log_dbg(log_domain, + "%s: wait for process to terminate after sending %s (send SIGKILL in %ld " + "milliseconds)...", + data->log_name, + _kc_signal_to_string(sig), + (long) wait_before_kill_msec); + } else { + data->async.source_timeout_kill_id = 0; + nm_log_dbg(log_domain, + "%s: wait for process to terminate after sending %s...", + data->log_name, + _kc_signal_to_string(sig)); + } + + g_child_watch_add(pid, _kc_cb_watch_child, data); +} + +static gulong +_sleep_duration_convert_ms_to_us(guint32 sleep_duration_msec) +{ + if (sleep_duration_msec > 0) { + guint64 x = (gint64) sleep_duration_msec * (guint64) 1000L; + + return x < G_MAXULONG ? (gulong) x : G_MAXULONG; + } + return G_USEC_PER_SEC / 20; +} + +/* nm_utils_kill_child_sync: + * @pid: process id to kill + * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the + * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. + * @log_domain: log debug information for this domain. Errors and warnings are logged both + * as %LOGD_CORE and @log_domain. + * @log_name: name of the process to kill for logging. + * @child_status: (out) (allow-none): return the exit status of the child, if no error occurred. + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect. + * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. + * Set to zero, to use the default (meaning 20 wakeups per seconds). + * + * Kill a child process synchronously and wait. The function first checks if the child already terminated + * and if it did, return the exit status. Otherwise, send one @sig signal. @sig will always be + * sent unless the child already exited. If the child does not exit within @wait_before_kill_msec milliseconds, + * the function will send %SIGKILL and waits for the child indefinitely. If @wait_before_kill_msec is zero, no + * %SIGKILL signal will be sent. + * + * In case of error, errno is preserved to contain the last reason of failure. + **/ +gboolean +nm_utils_kill_child_sync(pid_t pid, + int sig, + NMLogDomain log_domain, + const char *log_name, + int * child_status, + guint32 wait_before_kill_msec, + guint32 sleep_duration_msec) +{ + int status = 0, errsv = 0; + pid_t ret; + gboolean success = FALSE; + gboolean was_waiting = FALSE, send_kill = FALSE; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + char buf_wait[KC_WAITED_TO_STRING]; + gint64 wait_start_us; + + g_return_val_if_fail(pid > 0, FALSE); + g_return_val_if_fail(log_name != NULL, FALSE); + + /* check if the child process already terminated... */ + ret = waitpid(pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, + (long) ret, + _kc_exit_to_string(buf_exit, status)); + success = TRUE; + goto out; + } else if (ret != 0) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, + nm_strerror_native(errsv), + errsv); + goto out; + } + } + + /* send first signal @sig */ + if (kill(pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": failed to send %s: %s (%d)", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv); + } else { + /* let's try again with waitpid, probably there was a race... */ + ret = waitpid(pid, &status, 0); + if (ret > 0) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, + (long) ret, + _kc_exit_to_string(buf_exit, status)); + success = TRUE; + } else { + errsv = errno; + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid " + "(%s, %d) after sending %s", + LOG_NAME_ARGS, + (long) ret, + nm_strerror_native(errsv), + errsv, + _kc_signal_to_string(sig)); + } + } + goto out; + } + + wait_start_us = nm_utils_get_monotonic_timestamp_usec(); + + /* wait for the process to terminated... */ + if (sig != SIGKILL) { + gint64 wait_until, now; + gulong sleep_time, sleep_duration_usec; + int loop_count = 0; + + sleep_duration_usec = _sleep_duration_convert_ms_to_us(sleep_duration_msec); + wait_until = wait_before_kill_msec <= 0 + ? 0 + : wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); + + while (TRUE) { + ret = waitpid(pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": after sending %s, process %ld exited %s%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + (long) ret, + _kc_exit_to_string(buf_exit, status), + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + success = TRUE; + goto out; + } + if (ret == -1) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv, + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + goto out; + } + } + + if (!wait_until) + break; + + now = nm_utils_get_monotonic_timestamp_usec(); + if (now >= wait_until) + break; + + if (!was_waiting) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": waiting up to %ld milliseconds for process to terminate " + "normally after sending %s...", + LOG_NAME_ARGS, + (long) MAX(wait_before_kill_msec, 0), + _kc_signal_to_string(sig)); + was_waiting = TRUE; + } + + sleep_time = MIN(wait_until - now, sleep_duration_usec); + if (loop_count < 20) { + /* At the beginning we expect the process to die fast. + * Limit the sleep time, the limit doubles with every iteration. */ + sleep_time = MIN(sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); + loop_count++; + } + g_usleep(sleep_time); + } + + /* send SIGKILL, if called with @wait_before_kill_msec > 0 */ + if (wait_until) { + nm_log_dbg(log_domain, LOG_NAME_FMT ": sending SIGKILL...", LOG_NAME_ARGS); + + send_kill = TRUE; + if (kill(pid, SIGKILL) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv); + goto out; + } + } + } + } + + if (!was_waiting) { + nm_log_dbg(log_domain, + LOG_NAME_FMT ": waiting for process to terminate after sending %s%s...", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + send_kill ? " and SIGKILL" : ""); + } + + /* block until the child terminates. */ + while ((ret = waitpid(pid, &status, 0)) <= 0) { + errsv = errno; + + if (errsv != EINTR) { + nm_log_err(LOGD_CORE | log_domain, + LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + send_kill ? " and SIGKILL" : "", + nm_strerror_native(errsv), + errsv, + _kc_waited_to_string(buf_wait, wait_start_us)); + goto out; + } + } + + nm_log_dbg(log_domain, + LOG_NAME_FMT ": after sending %s%s, process %ld exited %s%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + send_kill ? " and SIGKILL" : "", + (long) ret, + _kc_exit_to_string(buf_exit, status), + _kc_waited_to_string(buf_wait, wait_start_us)); + success = TRUE; +out: + if (child_status) + *child_status = success ? status : -1; + errno = success ? 0 : errsv; + return success; +} + +/* nm_utils_kill_process_sync: + * @pid: process id to kill + * @start_time: the start time of the process to kill (as obtained by nm_utils_get_start_time_for_pid()). + * This is an optional argument, to avoid (somewhat) killing the wrong process as @pid + * might get recycled. You can pass 0, to not provide this parameter. + * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the + * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. + * @log_domain: log debug information for this domain. Errors and warnings are logged both + * as %LOGD_CORE and @log_domain. + * @log_name: name of the process to kill for logging. + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has no effect. + * If @max_wait_msec is set but less then @wait_before_kill_msec, the final %SIGKILL will also + * not be send. + * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. + * Set to zero, to use the default (meaning 20 wakeups per seconds). + * @max_wait_msec: if 0, waits indefinitely until the process is gone (or a zombie). Otherwise, this + * is the maximum wait time until returning. If @max_wait_msec is non-zero but smaller then @wait_before_kill_msec, + * we will not send a final %SIGKILL. + * + * Kill a non-child process synchronously and wait. This function will not return before the + * process with PID @pid is gone, the process is a zombie, or @max_wait_msec expires. + **/ +void +nm_utils_kill_process_sync(pid_t pid, + guint64 start_time, + int sig, + NMLogDomain log_domain, + const char *log_name, + guint32 wait_before_kill_msec, + guint32 sleep_duration_msec, + guint32 max_wait_msec) +{ + int errsv; + guint64 start_time0; + gint64 wait_until_sigkill, now, wait_start_us, max_wait_until; + gulong sleep_time, sleep_duration_usec; + int loop_count = 0; + gboolean was_waiting = FALSE; + char buf_wait[KC_WAITED_TO_STRING]; + char p_state; + + g_return_if_fail(pid > 0); + g_return_if_fail(log_name != NULL); + + start_time0 = nm_utils_get_start_time_for_pid(pid, &p_state, NULL); + if (start_time0 == 0) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT ": cannot kill process %ld because it seems already gone", + LOG_NAME_ARGS, + (long int) pid); + return; + } + if (start_time != 0 && start_time != start_time0) { + nm_log_dbg( + log_domain, + LOG_NAME_PROCESS_FMT + ": don't kill process %ld because the start_time is unexpectedly %lu instead of %ld", + LOG_NAME_ARGS, + (long int) pid, + (unsigned long) start_time0, + (unsigned long) start_time); + return; + } + + switch (p_state) { + case 'Z': + case 'x': + case 'X': + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT + ": cannot kill process %ld because it is already a zombie (%c)", + LOG_NAME_ARGS, + (long int) pid, + p_state); + return; + default: + break; + } + + if (kill(pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv == ESRCH) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT ": failed to send %s because process seems gone", + LOG_NAME_ARGS, + _kc_signal_to_string(sig)); + } else { + nm_log_warn(LOGD_CORE | log_domain, + LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv); + } + return; + } + + /* wait for the process to terminate... */ + + wait_start_us = nm_utils_get_monotonic_timestamp_usec(); + + sleep_duration_usec = _sleep_duration_convert_ms_to_us(sleep_duration_msec); + if (sig != SIGKILL && wait_before_kill_msec) + wait_until_sigkill = wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); + else + wait_until_sigkill = 0; + if (max_wait_msec > 0) { + max_wait_until = wait_start_us + (((gint64) max_wait_msec) * 1000L); + if (wait_until_sigkill > 0 && wait_until_sigkill > max_wait_msec) + wait_until_sigkill = 0; + } else + max_wait_until = 0; + + while (TRUE) { + start_time = nm_utils_get_start_time_for_pid(pid, &p_state, NULL); + + if (start_time != start_time0) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT ": process is gone after sending signal %s%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + return; + } + switch (p_state) { + case 'Z': + case 'x': + case 'X': + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT ": process is a zombie (%c) after sending signal %s%s", + LOG_NAME_ARGS, + p_state, + _kc_signal_to_string(sig), + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + return; + default: + break; + } + + if (kill(pid, 0) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv == ESRCH) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT + ": process is gone or a zombie after sending signal %s%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + } else { + nm_log_warn(LOGD_CORE | log_domain, + LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s", + LOG_NAME_ARGS, + (long int) pid, + nm_strerror_native(errsv), + errsv, + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + } + return; + } + + sleep_time = sleep_duration_usec; + now = nm_utils_get_monotonic_timestamp_usec(); + + if (max_wait_until != 0 && now >= max_wait_until) { + if (wait_until_sigkill != 0) { + /* wait_before_kill_msec is not larger then max_wait_until but we did not yet send + * SIGKILL. Although we already reached our timeout, we don't want to skip sending + * the signal. Even if we don't wait for the process to disappear. */ + nm_log_dbg(log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS); + kill(pid, SIGKILL); + } + nm_log_warn(log_domain, + LOG_NAME_PROCESS_FMT + ": timeout %u msec waiting for process to disappear (after sending %s)%s", + LOG_NAME_ARGS, + (unsigned) max_wait_until, + _kc_signal_to_string(sig), + was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); + return; + } + + if (wait_until_sigkill != 0) { + if (now >= wait_until_sigkill) { + /* Still not dead. SIGKILL now... */ + nm_log_dbg(log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS); + if (kill(pid, SIGKILL) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT ": process is gone or a zombie%s", + LOG_NAME_ARGS, + _kc_waited_to_string(buf_wait, wait_start_us)); + } else { + nm_log_warn(LOGD_CORE | log_domain, + LOG_NAME_PROCESS_FMT + ": failed to send SIGKILL (after sending %s), %s (%d)%s", + LOG_NAME_ARGS, + _kc_signal_to_string(sig), + nm_strerror_native(errsv), + errsv, + _kc_waited_to_string(buf_wait, wait_start_us)); + } + return; + } + sig = SIGKILL; + wait_until_sigkill = 0; + loop_count = + 0; /* reset the loop_count. Now we really expect the process to die quickly. */ + } else + sleep_time = MIN(wait_until_sigkill - now, sleep_duration_usec); + } + + if (!was_waiting) { + if (wait_until_sigkill != 0) { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT + ": waiting up to %ld milliseconds for process to disappear before " + "sending KILL signal after sending %s...", + LOG_NAME_ARGS, + (long) wait_before_kill_msec, + _kc_signal_to_string(sig)); + } else if (max_wait_until != 0) { + nm_log_dbg( + log_domain, + LOG_NAME_PROCESS_FMT + ": waiting up to %ld milliseconds for process to disappear after sending %s...", + LOG_NAME_ARGS, + (long) max_wait_msec, + _kc_signal_to_string(sig)); + } else { + nm_log_dbg(log_domain, + LOG_NAME_PROCESS_FMT + ": waiting for process to disappear after sending %s...", + LOG_NAME_ARGS, + _kc_signal_to_string(sig)); + } + was_waiting = TRUE; + } + + if (loop_count < 20) { + /* At the beginning we expect the process to die fast. + * Limit the sleep time, the limit doubles with every iteration. */ + sleep_time = MIN(sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); + loop_count++; + } + g_usleep(sleep_time); + } +} +#undef LOG_NAME_FMT +#undef LOG_NAME_PROCESS_FMT +#undef LOG_NAME_ARGS + +const char *const NM_PATHS_DEFAULT[] = { + PREFIX "/sbin/", + PREFIX "/bin/", + "/usr/local/sbin/", + "/sbin/", + "/usr/sbin/", + "/usr/local/bin/", + "/bin/", + "/usr/bin/", + NULL, +}; + +const char * +nm_utils_find_helper(const char *progname, const char *try_first, GError **error) +{ + return nm_utils_file_search_in_paths(progname, + try_first, + NM_PATHS_DEFAULT, + G_FILE_TEST_IS_EXECUTABLE, + NULL, + NULL, + error); +} + +/*****************************************************************************/ + +/** + * nm_utils_read_link_absolute: + * @link_file: file name of the symbolic link + * @error: error reason in case of failure + * + * Uses to g_file_read_link()/readlink() to read the symlink + * and returns the result as absolute path. + **/ +char * +nm_utils_read_link_absolute(const char *link_file, GError **error) +{ + char *ln, *dirname, *ln_abs; + + ln = g_file_read_link(link_file, error); + if (!ln) + return NULL; + if (g_path_is_absolute(ln)) + return ln; + + dirname = g_path_get_dirname(link_file); + if (!g_path_is_absolute(dirname)) { + gs_free char *current_dir = g_get_current_dir(); + + /* @link_file argument was not an absolute path in the first place. + * That actually may be a bug, because the CWD is not well defined + * in most cases. Anyway, apparently we were able to load the file + * even from a relative path. So, when making the link absolute, we + * also need to prepend the CWD. */ + ln_abs = g_build_filename(current_dir, dirname, ln, NULL); + } else + ln_abs = g_build_filename(dirname, ln, NULL); + g_free(dirname); + g_free(ln); + return ln_abs; +} + +/*****************************************************************************/ + +#define DEVICE_TYPE_TAG "type:" +#define DRIVER_TAG "driver:" +#define DHCP_PLUGIN_TAG "dhcp-plugin:" +#define EXCEPT_TAG "except:" +#define MATCH_TAG_CONFIG_NM_VERSION "nm-version:" +#define MATCH_TAG_CONFIG_NM_VERSION_MIN "nm-version-min:" +#define MATCH_TAG_CONFIG_NM_VERSION_MAX "nm-version-max:" +#define MATCH_TAG_CONFIG_ENV "env:" + +typedef struct { + const char *interface_name; + const char *device_type; + const char *driver; + const char *driver_version; + const char *dhcp_plugin; + struct { + const char *value; + gboolean is_parsed; + guint len; + guint8 bin[NM_UTILS_HWADDR_LEN_MAX]; + } hwaddr; + struct { + const char *value; + gboolean is_parsed; + guint32 a; + guint32 b; + guint32 c; + } s390_subchannels; +} MatchDeviceData; + +static gboolean +match_device_s390_subchannels_parse(const char *s390_subchannels, + guint32 * out_a, + guint32 * out_b, + guint32 * out_c) +{ + char buf[30 + 1]; + const int BUFSIZE = G_N_ELEMENTS(buf) - 1; + guint i = 0; + char * pa = NULL, *pb = NULL, *pc = NULL; + gint64 a, b, c; + + nm_assert(s390_subchannels); + nm_assert(out_a); + nm_assert(out_b); + nm_assert(out_c); + + if (!g_ascii_isxdigit(s390_subchannels[0])) + return FALSE; + + /* Get the first channel */ + for (i = 0; s390_subchannels[i]; i++) { + char ch = s390_subchannels[i]; + + if (!g_ascii_isxdigit(ch) && ch != '.') { + if (ch == ',') { + /* FIXME: currently we consider the first channel and ignore + * everything after the first ',' separator. Maybe we should + * validate all present channels? */ + break; + } + return FALSE; /* Invalid chars */ + } + if (i >= BUFSIZE) + return FALSE; /* Too long to be a subchannel */ + buf[i] = ch; + } + buf[i] = '\0'; + + /* and grab each of its elements, there should be 3 */ + pa = &buf[0]; + pb = strchr(pa, '.'); + if (pb) + pc = strchr(pb + 1, '.'); + if (!pb || !pc) + return FALSE; + *pb++ = '\0'; + *pc++ = '\0'; + + a = _nm_utils_ascii_str_to_int64(pa, 16, 0, G_MAXUINT32, -1); + if (a == -1) + return FALSE; + b = _nm_utils_ascii_str_to_int64(pb, 16, 0, G_MAXUINT32, -1); + if (b == -1) + return FALSE; + c = _nm_utils_ascii_str_to_int64(pc, 16, 0, G_MAXUINT32, -1); + if (c == -1) + return FALSE; + + *out_a = (guint32) a; + *out_b = (guint32) b; + *out_c = (guint32) c; + return TRUE; +} + +static gboolean +match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_data) +{ + guint32 a, b, c; + + if (G_UNLIKELY(!match_data->s390_subchannels.is_parsed)) { + match_data->s390_subchannels.is_parsed = TRUE; + + if (!match_data->s390_subchannels.value + || !match_device_s390_subchannels_parse(match_data->s390_subchannels.value, + &match_data->s390_subchannels.a, + &match_data->s390_subchannels.b, + &match_data->s390_subchannels.c)) { + match_data->s390_subchannels.value = NULL; + return FALSE; + } + } else if (!match_data->s390_subchannels.value) + return FALSE; + + if (!match_device_s390_subchannels_parse(spec_str, &a, &b, &c)) + return FALSE; + return match_data->s390_subchannels.a == a && match_data->s390_subchannels.b == b + && match_data->s390_subchannels.c == c; +} + +static gboolean +match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data) +{ + if (G_UNLIKELY(!match_data->hwaddr.is_parsed)) { + match_data->hwaddr.is_parsed = TRUE; + + if (match_data->hwaddr.value) { + gsize l; + + if (!_nm_utils_hwaddr_aton(match_data->hwaddr.value, + match_data->hwaddr.bin, + sizeof(match_data->hwaddr.bin), + &l)) + g_return_val_if_reached(FALSE); + match_data->hwaddr.len = l; + } else + return FALSE; + } else if (!match_data->hwaddr.len) + return FALSE; + + return nm_utils_hwaddr_matches(spec_str, -1, match_data->hwaddr.bin, match_data->hwaddr.len); +} + +#define _MATCH_CHECK(spec_str, tag) \ + ({ \ + gboolean _has = FALSE; \ + \ + if (!g_ascii_strncasecmp(spec_str, ("" tag ""), NM_STRLEN(tag))) { \ + spec_str += NM_STRLEN(tag); \ + _has = TRUE; \ + } \ + _has; \ + }) + +static NMMatchSpecMatchType +_match_result(gboolean has_except, + gboolean has_not_except, + gboolean has_match, + gboolean has_match_except) +{ + if (has_except && !has_not_except) { + /* a match spec that only consists of a list of except matches is treated specially. */ + nm_assert(!has_match); + if (has_match_except) { + /* one of the "except:" matches matched. The result is an explicit + * negative match. */ + return NM_MATCH_SPEC_NEG_MATCH; + } else { + /* none of the "except:" matches matched. The result is a positive match, + * despite there being no positive match. */ + return NM_MATCH_SPEC_MATCH; + } + } + + if (has_match_except) + return NM_MATCH_SPEC_NEG_MATCH; + if (has_match) + return NM_MATCH_SPEC_MATCH; + return NM_MATCH_SPEC_NO_MATCH; +} + +static const char * +match_except(const char *spec_str, gboolean *out_except) +{ + if (_MATCH_CHECK(spec_str, EXCEPT_TAG)) + *out_except = TRUE; + else + *out_except = FALSE; + return spec_str; +} + +static gboolean +match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *match_data) +{ + if (spec_str[0] == '*' && spec_str[1] == '\0') + return TRUE; + + if (_MATCH_CHECK(spec_str, DEVICE_TYPE_TAG)) { + return match_data->device_type && nm_streq(spec_str, match_data->device_type); + } + + if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_MAC_TAG)) + return match_device_hwaddr_eval(spec_str, match_data); + + if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_INTERFACE_NAME_TAG)) { + gboolean use_pattern = FALSE; + + if (spec_str[0] == '=') + spec_str += 1; + else { + if (spec_str[0] == '~') + spec_str += 1; + use_pattern = TRUE; + } + + if (match_data->interface_name) { + if (nm_streq(spec_str, match_data->interface_name)) + return TRUE; + if (use_pattern && g_pattern_match_simple(spec_str, match_data->interface_name)) + return TRUE; + } + return FALSE; + } + + if (_MATCH_CHECK(spec_str, DRIVER_TAG)) { + const char *t; + + if (!match_data->driver) + return FALSE; + + /* support: + * 1) "${DRIVER}" + * In this case, DRIVER may not contain a '/' character. + * It matches any driver version. + * 2) "${DRIVER}/${DRIVER_VERSION}" + * In this case, DRIVER may contains '/' but DRIVER_VERSION + * may not. A '/' in DRIVER_VERSION may be replaced by '?'. + * + * It follows, that "${DRIVER}/""*" is like 1), but allows + * '/' inside DRIVER. + * + * The fields match to what `nmcli -f GENERAL.DRIVER,GENERAL.DRIVER-VERSION device show` + * gives. However, DRIVER matches literally, while DRIVER_VERSION is a glob + * supporting ? and *. + */ + + t = strrchr(spec_str, '/'); + + if (!t) + return nm_streq(spec_str, match_data->driver); + + return (strncmp(spec_str, match_data->driver, t - spec_str) == 0) + && g_pattern_match_simple(&t[1], match_data->driver_version ?: ""); + } + + if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_S390_SUBCHANNELS_TAG)) + return match_data_s390_subchannels_eval(spec_str, match_data); + + if (_MATCH_CHECK(spec_str, DHCP_PLUGIN_TAG)) + return nm_streq0(spec_str, match_data->dhcp_plugin); + + if (allow_fuzzy) { + if (match_device_hwaddr_eval(spec_str, match_data)) + return TRUE; + if (match_data->interface_name && nm_streq(spec_str, match_data->interface_name)) + return TRUE; + } + + return FALSE; +} + +NMMatchSpecMatchType +nm_match_spec_device(const GSList *specs, + const char * interface_name, + const char * device_type, + const char * driver, + const char * driver_version, + const char * hwaddr, + const char * s390_subchannels, + const char * dhcp_plugin) +{ + const GSList * iter; + gboolean has_match = FALSE; + gboolean has_match_except = FALSE; + gboolean has_except = FALSE; + gboolean has_not_except = FALSE; + const char * spec_str; + MatchDeviceData match_data = { + .interface_name = interface_name, + .device_type = nm_str_not_empty(device_type), + .driver = nm_str_not_empty(driver), + .driver_version = nm_str_not_empty(driver_version), + .dhcp_plugin = nm_str_not_empty(dhcp_plugin), + .hwaddr = + { + .value = hwaddr, + }, + .s390_subchannels = + { + .value = s390_subchannels, + }, + }; + + nm_assert(!hwaddr || nm_utils_hwaddr_valid(hwaddr, -1)); + + if (!specs) + return NM_MATCH_SPEC_NO_MATCH; + + for (iter = specs; iter; iter = iter->next) { + gboolean except; + + spec_str = iter->data; + + if (!spec_str || !*spec_str) + continue; + + spec_str = match_except(spec_str, &except); + + if (except) + has_except = TRUE; + else + has_not_except = TRUE; + + if ((except && has_match_except) || (!except && has_match)) { + /* evaluating the match does not give new information. Skip it. */ + continue; + } + + if (!match_device_eval(spec_str, !except, &match_data)) + continue; + + if (except) + has_match_except = TRUE; + else + has_match = TRUE; + } + + return _match_result(has_except, has_not_except, has_match, has_match_except); +} + +static gboolean +match_config_eval(const char *str, const char *tag, guint cur_nm_version) +{ + gs_free char * s_ver = NULL; + gs_strfreev char **s_ver_tokens = NULL; + int v_maj = -1, v_min = -1, v_mic = -1; + guint c_maj = -1, c_min = -1, c_mic = -1; + guint n_tokens; + + s_ver = g_strdup(str); + g_strstrip(s_ver); + + /* Let's be strict with the accepted format here. No funny stuff!! */ + + if (s_ver[strspn(s_ver, ".0123456789")] != '\0') + return FALSE; + + s_ver_tokens = g_strsplit(s_ver, ".", -1); + n_tokens = g_strv_length(s_ver_tokens); + if (n_tokens == 0 || n_tokens > 3) + return FALSE; + + v_maj = _nm_utils_ascii_str_to_int64(s_ver_tokens[0], 10, 0, 0xFFFF, -1); + if (v_maj < 0) + return FALSE; + if (n_tokens >= 2) { + v_min = _nm_utils_ascii_str_to_int64(s_ver_tokens[1], 10, 0, 0xFF, -1); + if (v_min < 0) + return FALSE; + } + if (n_tokens >= 3) { + v_mic = _nm_utils_ascii_str_to_int64(s_ver_tokens[2], 10, 0, 0xFF, -1); + if (v_mic < 0) + return FALSE; + } + + nm_decode_version(cur_nm_version, &c_maj, &c_min, &c_mic); + +#define CHECK_AND_RETURN_FALSE(cur, val, tag, is_last_digit) \ + G_STMT_START \ + { \ + if (!strcmp(tag, MATCH_TAG_CONFIG_NM_VERSION_MIN)) { \ + if (cur < val) \ + return FALSE; \ + } else if (!strcmp(tag, MATCH_TAG_CONFIG_NM_VERSION_MAX)) { \ + if (cur > val) \ + return FALSE; \ + } else { \ + if (cur != val) \ + return FALSE; \ + } \ + if (!(is_last_digit)) { \ + if (cur != val) \ + return FALSE; \ + } \ + } \ + G_STMT_END + if (v_mic >= 0) + CHECK_AND_RETURN_FALSE(c_mic, v_mic, tag, TRUE); + if (v_min >= 0) + CHECK_AND_RETURN_FALSE(c_min, v_min, tag, v_mic < 0); + CHECK_AND_RETURN_FALSE(c_maj, v_maj, tag, v_min < 0); + return TRUE; +} + +NMMatchSpecMatchType +nm_match_spec_config(const GSList *specs, guint cur_nm_version, const char *env) +{ + const GSList *iter; + gboolean has_match = FALSE; + gboolean has_match_except = FALSE; + gboolean has_except = FALSE; + gboolean has_not_except = FALSE; + + if (!specs) + return NM_MATCH_SPEC_NO_MATCH; + + for (iter = specs; iter; iter = g_slist_next(iter)) { + const char *spec_str = iter->data; + gboolean except; + gboolean v_match; + + if (!spec_str || !*spec_str) + continue; + + spec_str = match_except(spec_str, &except); + + if (except) + has_except = TRUE; + else + has_not_except = TRUE; + + if ((except && has_match_except) || (!except && has_match)) { + /* evaluating the match does not give new information. Skip it. */ + continue; + } + + if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION)) + v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION, cur_nm_version); + else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN)) + v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN, cur_nm_version); + else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX)) + v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX, cur_nm_version); + else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_ENV)) + v_match = env && env[0] && !strcmp(spec_str, env); + else + v_match = FALSE; + + if (!v_match) + continue; + + if (except) + has_match_except = TRUE; + else + has_match = TRUE; + } + + return _match_result(has_except, has_not_except, has_match, has_match_except); +} + +#undef _MATCH_CHECK + +/** + * nm_match_spec_split: + * @value: the string of device specs + * + * Splits the specs from the string and returns them as individual + * entries in a #GSList. + * + * It does not validate any specs, it basically just does a special + * strsplit with ',' or ';' as separators and supporting '\\' as + * escape character. + * + * Leading and trailing spaces of each entry are removed. But the user + * can preserve them by specifying "\\s has 2 leading" or "has 2 trailing \\s". + * + * Specs can have a qualifier like "interface-name:". We still don't strip + * any whitespace after the colon, so "interface-name: X" matches an interface + * named " X". + * + * Returns: (transfer full): the list of device specs. + */ +GSList * +nm_match_spec_split(const char *value) +{ + char * string_value, *p, *q0, *q; + GSList *pieces = NULL; + int trailing_ws; + + if (!value || !*value) + return NULL; + + /* Copied from glibs g_key_file_parse_value_as_string() function + * and adjusted. */ + + string_value = g_new(char, strlen(value) + 1); + + p = (char *) value; + + /* skip over leading whitespace */ + while (g_ascii_isspace(*p)) + p++; + + q0 = q = string_value; + trailing_ws = 0; + while (*p) { + if (*p == '\\') { + p++; + + switch (*p) { + case 's': + *q = ' '; + break; + case 'n': + *q = '\n'; + break; + case 't': + *q = '\t'; + break; + case 'r': + *q = '\r'; + break; + case '\\': + *q = '\\'; + break; + case '\0': + break; + default: + if (NM_IN_SET(*p, ',', ';')) + *q = *p; + else { + *q++ = '\\'; + *q = *p; + } + break; + } + if (*p == '\0') + break; + p++; + trailing_ws = 0; + } else { + *q = *p; + if (*p == '\0') + break; + if (g_ascii_isspace(*p)) { + trailing_ws++; + p++; + } else if (NM_IN_SET(*p, ',', ';')) { + if (q0 < q - trailing_ws) + pieces = g_slist_prepend(pieces, g_strndup(q0, (q - q0) - trailing_ws)); + q0 = q + 1; + p++; + trailing_ws = 0; + while (g_ascii_isspace(*p)) + p++; + } else + p++; + } + q++; + } + + *q = '\0'; + if (q0 < q - trailing_ws) + pieces = g_slist_prepend(pieces, g_strndup(q0, (q - q0) - trailing_ws)); + g_free(string_value); + return g_slist_reverse(pieces); +} + +/** + * nm_match_spec_join: + * @specs: the device specs to join + * + * This is based on g_key_file_parse_string_as_value(), analog to + * nm_match_spec_split() which is based on g_key_file_parse_value_as_string(). + * + * Returns: (transfer full): a joined list of device specs that can be + * split again with nm_match_spec_split(). Note that + * nm_match_spec_split (nm_match_spec_join (specs)) yields the original + * result (which is not true the other way around because there are multiple + * ways to encode the same joined specs string). + */ +char * +nm_match_spec_join(GSList *specs) +{ + const char *p; + GString * str; + + str = g_string_new(""); + + for (; specs; specs = specs->next) { + p = specs->data; + + if (!p || !*p) + continue; + + if (str->len > 0) + g_string_append_c(str, ','); + + /* escape leading whitespace */ + switch (*p) { + case ' ': + g_string_append(str, "\\s"); + p++; + break; + case '\t': + g_string_append(str, "\\t"); + p++; + break; + } + + for (; *p; p++) { + switch (*p) { + case '\n': + g_string_append(str, "\\n"); + break; + case '\r': + g_string_append(str, "\\r"); + break; + case '\\': + g_string_append(str, "\\\\"); + break; + case ',': + g_string_append(str, "\\,"); + break; + case ';': + g_string_append(str, "\\;"); + break; + default: + g_string_append_c(str, *p); + break; + } + } + + /* escape trailing whitespaces */ + switch (str->str[str->len - 1]) { + case ' ': + g_string_overwrite(str, str->len - 1, "\\s"); + break; + case '\t': + g_string_overwrite(str, str->len - 1, "\\t"); + break; + } + } + + return g_string_free(str, FALSE); +} + +static void +_pattern_parse(const char * input, + const char **out_pattern, + gboolean * out_is_inverted, + gboolean * out_is_mandatory) +{ + gboolean is_inverted = FALSE; + gboolean is_mandatory = FALSE; + + if (input[0] == '&') { + input++; + is_mandatory = TRUE; + if (input[0] == '!') { + input++; + is_inverted = TRUE; + } + goto out; + } + + if (input[0] == '|') { + input++; + if (input[0] == '!') { + input++; + is_inverted = TRUE; + } + goto out; + } + + if (input[0] == '!') { + input++; + is_inverted = TRUE; + is_mandatory = TRUE; + goto out; + } + +out: + if (input[0] == '\\') + input++; + + *out_pattern = input; + *out_is_inverted = is_inverted; + *out_is_mandatory = is_mandatory; +} + +gboolean +nm_wildcard_match_check(const char *str, const char *const *patterns, guint num_patterns) +{ + gboolean has_optional = FALSE; + gboolean has_any_optional = FALSE; + guint i; + + for (i = 0; i < num_patterns; i++) { + gboolean is_inverted; + gboolean is_mandatory; + gboolean match; + const char *p; + + _pattern_parse(patterns[i], &p, &is_inverted, &is_mandatory); + + match = (fnmatch(p, str, 0) == 0); + if (is_inverted) + match = !match; + + if (is_mandatory) { + if (!match) + return FALSE; + } else { + has_any_optional = TRUE; + if (match) + has_optional = TRUE; + } + } + + return has_optional || !has_any_optional; +} + +/*****************************************************************************/ + +static gboolean +_kernel_cmdline_match(const char *const *proc_cmdline, const char *pattern) +{ + if (proc_cmdline) { + gboolean has_equal = (!!strchr(pattern, '=')); + gsize pattern_len = strlen(pattern); + + for (; proc_cmdline[0]; proc_cmdline++) { + const char *c = proc_cmdline[0]; + + if (has_equal) { + /* if pattern contains '=' compare full key=value */ + if (nm_streq(c, pattern)) + return TRUE; + continue; + } + + /* otherwise consider pattern as key only */ + if (strncmp(c, pattern, pattern_len) == 0 && NM_IN_SET(c[pattern_len], '\0', '=')) + return TRUE; + } + } + + return FALSE; +} + +gboolean +nm_utils_kernel_cmdline_match_check(const char *const *proc_cmdline, + const char *const *patterns, + guint num_patterns, + GError ** error) +{ + gboolean has_optional = FALSE; + gboolean has_any_optional = FALSE; + guint i; + + for (i = 0; i < num_patterns; i++) { + const char *element = patterns[i]; + gboolean is_inverted = FALSE; + gboolean is_mandatory = FALSE; + gboolean match; + const char *p; + + _pattern_parse(element, &p, &is_inverted, &is_mandatory); + + match = _kernel_cmdline_match(proc_cmdline, p); + if (is_inverted) + match = !match; + + if (is_mandatory) { + if (!match) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not satisfy match.kernel-command-line property %s", + patterns[i]); + return FALSE; + } + } else { + has_any_optional = TRUE; + if (match) + has_optional = TRUE; + } + } + + if (!has_optional && has_any_optional) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not satisfy any match.kernel-command-line property"); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +char * +nm_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id) +{ + guint id_len; + gsize parent_len; + char *ifname; + + g_return_val_if_fail(parent_iface && *parent_iface, NULL); + + if (vlan_id < 10) + id_len = 2; + else if (vlan_id < 100) + id_len = 3; + else if (vlan_id < 1000) + id_len = 4; + else { + g_return_val_if_fail(vlan_id < 4095, NULL); + id_len = 5; + } + + ifname = g_new(char, IFNAMSIZ); + + parent_len = strlen(parent_iface); + parent_len = MIN(parent_len, IFNAMSIZ - 1 - id_len); + memcpy(ifname, parent_iface, parent_len); + g_snprintf(&ifname[parent_len], IFNAMSIZ - parent_len, ".%u", vlan_id); + + return ifname; +} + +/* nm_utils_new_infiniband_name: + * @name: the output-buffer where the value will be written. Must be + * not %NULL and point to a string buffer of at least IFNAMSIZ bytes. + * @parent_name: the parent interface name + * @p_key: the partition key. + * + * Returns: the infiniband name will be written to @name and @name + * is returned. + */ +const char * +nm_utils_new_infiniband_name(char *name, const char *parent_name, int p_key) +{ + g_return_val_if_fail(name, NULL); + g_return_val_if_fail(parent_name && parent_name[0], NULL); + g_return_val_if_fail(strlen(parent_name) < IFNAMSIZ, NULL); + + /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't + * want to assert against that in nm_utils_new_infiniband_name(). So be more + * resilient here, and accept those. */ + g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, NULL); + + /* If parent+suffix is too long, kernel would just truncate + * the name. We do the same. See ipoib_vlan_add(). */ + g_snprintf(name, IFNAMSIZ, "%s.%04x", parent_name, p_key); + return name; +} + +/*****************************************************************************/ + +/** + * nm_utils_cmp_connection_by_autoconnect_priority: + * @a: + * @b: + * + * compare connections @a and @b for their autoconnect property + * (with sorting the connection that has autoconnect enabled before + * the other) + * If they both have autoconnect enabled, sort them depending on their + * autoconnect-priority (with the higher priority first). + * + * If their autoconnect/autoconnect-priority is the same, 0 is returned. + * That is, they compare equal. + * + * Returns: -1, 0, or 1 + */ +int +nm_utils_cmp_connection_by_autoconnect_priority(NMConnection *a, NMConnection *b) +{ + NMSettingConnection *a_s_con; + NMSettingConnection *b_s_con; + int a_ap, b_ap; + gboolean can_autoconnect; + + if (a == b) + return 0; + if (!a) + return 1; + if (!b) + return -1; + + a_s_con = nm_connection_get_setting_connection(a); + b_s_con = nm_connection_get_setting_connection(b); + + if (!a_s_con) + return !b_s_con ? 0 : 1; + if (!b_s_con) + return -1; + + can_autoconnect = !!nm_setting_connection_get_autoconnect(a_s_con); + if (can_autoconnect != (!!nm_setting_connection_get_autoconnect(b_s_con))) + return can_autoconnect ? -1 : 1; + + if (can_autoconnect) { + a_ap = nm_setting_connection_get_autoconnect_priority(a_s_con); + b_ap = nm_setting_connection_get_autoconnect_priority(b_s_con); + if (a_ap != b_ap) + return (a_ap > b_ap) ? -1 : 1; + } + + return 0; +} + +/*****************************************************************************/ + +typedef struct { + const char *name; + NMSetting * setting; + NMSetting * diff_base_setting; + GHashTable *setting_diff; +} LogConnectionSettingData; + +typedef struct { + const char * item_name; + NMSettingDiffResult diff_result; +} LogConnectionSettingItem; + +static int +_log_connection_sort_hashes_fcn(gconstpointer a, gconstpointer b) +{ + const LogConnectionSettingData *v1 = a; + const LogConnectionSettingData *v2 = b; + NMSettingPriority p1, p2; + NMSetting * s1, *s2; + + s1 = v1->setting ?: v1->diff_base_setting; + s2 = v2->setting ?: v2->diff_base_setting; + + g_assert(s1 && s2); + + p1 = _nm_setting_get_setting_priority(s1); + p2 = _nm_setting_get_setting_priority(s2); + + if (p1 != p2) + return p1 > p2 ? 1 : -1; + + return strcmp(v1->name, v2->name); +} + +static GArray * +_log_connection_sort_hashes(NMConnection *connection, + NMConnection *diff_base, + GHashTable * connection_diff) +{ + GHashTableIter iter; + GArray * sorted_hashes; + LogConnectionSettingData setting_data; + + sorted_hashes = g_array_sized_new(TRUE, + FALSE, + sizeof(LogConnectionSettingData), + g_hash_table_size(connection_diff)); + + g_hash_table_iter_init(&iter, connection_diff); + while (g_hash_table_iter_next(&iter, + (gpointer) &setting_data.name, + (gpointer) &setting_data.setting_diff)) { + setting_data.setting = nm_connection_get_setting_by_name(connection, setting_data.name); + setting_data.diff_base_setting = + diff_base ? nm_connection_get_setting_by_name(diff_base, setting_data.name) : NULL; + g_assert(setting_data.setting || setting_data.diff_base_setting); + g_array_append_val(sorted_hashes, setting_data); + } + + g_array_sort(sorted_hashes, _log_connection_sort_hashes_fcn); + return sorted_hashes; +} + +static int +_log_connection_sort_names_fcn(gconstpointer a, gconstpointer b) +{ + const LogConnectionSettingItem *v1 = a; + const LogConnectionSettingItem *v2 = b; + + /* we want to first show the items, that disappeared, then the one that changed and + * then the ones that were added. */ + + if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) + != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_A)) + return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) ? -1 : 1; + if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) + != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_B)) + return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) ? 1 : -1; + return strcmp(v1->item_name, v2->item_name); +} + +static char * +_log_connection_get_property(NMSetting *setting, const char *name) +{ + GValue val = G_VALUE_INIT; + char * s; + + g_return_val_if_fail(setting, NULL); + + if (!NM_IS_SETTING_VPN(setting) && nm_setting_get_secret_flags(setting, name, NULL, NULL)) + return g_strdup("****"); + + if (!_nm_setting_get_property(setting, name, &val)) + return g_strdup(""); + + if (G_VALUE_HOLDS_STRING(&val)) { + const char *val_s; + + val_s = g_value_get_string(&val); + if (!val_s) { + /* for NULL, we want to return the unquoted string "NULL". */ + s = g_strdup("NULL"); + } else { + char *escaped = g_strescape(val_s, "'"); + + s = g_strdup_printf("'%s'", escaped); + g_free(escaped); + } + } else { + s = g_strdup_value_contents(&val); + if (s == NULL) + s = g_strdup("NULL"); + else { + char *escaped = g_strescape(s, "'"); + + g_free(s); + s = escaped; + } + } + g_value_unset(&val); + return s; +} + +static void +_log_connection_sort_names(LogConnectionSettingData *setting_data, GArray *sorted_names) +{ + GHashTableIter iter; + LogConnectionSettingItem item; + gpointer p; + + g_array_set_size(sorted_names, 0); + + g_hash_table_iter_init(&iter, setting_data->setting_diff); + while (g_hash_table_iter_next(&iter, (gpointer) &item.item_name, &p)) { + item.diff_result = GPOINTER_TO_UINT(p); + g_array_append_val(sorted_names, item); + } + + g_array_sort(sorted_names, _log_connection_sort_names_fcn); +} + +void +nm_utils_log_connection_diff(NMConnection *connection, + NMConnection *diff_base, + guint32 level, + guint64 domain, + const char * name, + const char * prefix, + const char * dbus_path) +{ + GHashTable *connection_diff = NULL; + GArray * sorted_hashes; + GArray * sorted_names = NULL; + int i, j; + gboolean connection_diff_are_same; + gboolean print_header = TRUE; + gboolean print_setting_header; + GString * str1; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(!diff_base || (NM_IS_CONNECTION(diff_base) && diff_base != connection)); + + /* For VPN setting types, this is broken, because we cannot (generically) print the content of data/secrets. Bummer... */ + + if (!nm_logging_enabled(level, domain)) + return; + + if (!prefix) + prefix = ""; + if (!name) + name = ""; + + connection_diff_are_same = nm_connection_diff( + connection, + diff_base, + NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, + &connection_diff); + if (connection_diff_are_same) { + const char *t1, *t2; + + t1 = nm_connection_get_connection_type(connection); + if (diff_base) { + t2 = nm_connection_get_connection_type(diff_base); + nm_log(level, + domain, + NULL, + NULL, + "%sconnection '%s' (%p/%s/%s%s%s and %p/%s/%s%s%s): no difference", + prefix, + name, + connection, + G_OBJECT_TYPE_NAME(connection), + NM_PRINT_FMT_QUOTE_STRING(t1), + diff_base, + G_OBJECT_TYPE_NAME(diff_base), + NM_PRINT_FMT_QUOTE_STRING(t2)); + } else { + nm_log(level, + domain, + NULL, + NULL, + "%sconnection '%s' (%p/%s/%s%s%s): no properties set", + prefix, + name, + connection, + G_OBJECT_TYPE_NAME(connection), + NM_PRINT_FMT_QUOTE_STRING(t1)); + } + g_assert(!connection_diff); + return; + } + + /* FIXME: it doesn't nicely show the content of NMSettingVpn, because nm_connection_diff() does not + * expand the hash values. */ + + sorted_hashes = _log_connection_sort_hashes(connection, diff_base, connection_diff); + if (sorted_hashes->len <= 0) + goto out; + + sorted_names = g_array_new(FALSE, FALSE, sizeof(LogConnectionSettingItem)); + str1 = g_string_new(NULL); + + for (i = 0; i < sorted_hashes->len; i++) { + LogConnectionSettingData *setting_data = + &g_array_index(sorted_hashes, LogConnectionSettingData, i); + + _log_connection_sort_names(setting_data, sorted_names); + print_setting_header = TRUE; + for (j = 0; j < sorted_names->len; j++) { + char * str_conn, *str_diff; + LogConnectionSettingItem *item = + &g_array_index(sorted_names, LogConnectionSettingItem, j); + + str_conn = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_A) + ? _log_connection_get_property(setting_data->setting, item->item_name) + : NULL; + str_diff = + (item->diff_result & NM_SETTING_DIFF_RESULT_IN_B) + ? _log_connection_get_property(setting_data->diff_base_setting, item->item_name) + : NULL; + + if (print_header) { + GError * err_verify = NULL; + const char *t1, *t2; + + t1 = nm_connection_get_connection_type(connection); + if (diff_base) { + t2 = nm_connection_get_connection_type(diff_base); + nm_log(level, + domain, + NULL, + NULL, + "%sconnection '%s' (%p/%s/%s%s%s < %p/%s/%s%s%s)%s%s%s:", + prefix, + name, + connection, + G_OBJECT_TYPE_NAME(connection), + NM_PRINT_FMT_QUOTE_STRING(t1), + diff_base, + G_OBJECT_TYPE_NAME(diff_base), + NM_PRINT_FMT_QUOTE_STRING(t2), + NM_PRINT_FMT_QUOTED(dbus_path, " [", dbus_path, "]", "")); + } else { + nm_log(level, + domain, + NULL, + NULL, + "%sconnection '%s' (%p/%s/%s%s%s):%s%s%s", + prefix, + name, + connection, + G_OBJECT_TYPE_NAME(connection), + NM_PRINT_FMT_QUOTE_STRING(t1), + NM_PRINT_FMT_QUOTED(dbus_path, " [", dbus_path, "]", "")); + } + print_header = FALSE; + + if (!nm_connection_verify(connection, &err_verify)) { + nm_log(level, + domain, + NULL, + NULL, + "%sconnection %p does not verify: %s", + prefix, + connection, + err_verify->message); + g_clear_error(&err_verify); + } + } +#define _NM_LOG_ALIGN "-25" + if (print_setting_header) { + if (diff_base) { + if (setting_data->setting && setting_data->diff_base_setting) + g_string_printf(str1, + "%p < %p", + setting_data->setting, + setting_data->diff_base_setting); + else if (setting_data->diff_base_setting) + g_string_printf(str1, "*missing* < %p", setting_data->diff_base_setting); + else + g_string_printf(str1, "%p < *missing*", setting_data->setting); + nm_log(level, + domain, + NULL, + NULL, + "%s%"_NM_LOG_ALIGN + "s [ %s ]", + prefix, + setting_data->name, + str1->str); + } else + nm_log(level, + domain, + NULL, + NULL, + "%s%"_NM_LOG_ALIGN + "s [ %p ]", + prefix, + setting_data->name, + setting_data->setting); + print_setting_header = FALSE; + } + g_string_printf(str1, "%s.%s", setting_data->name, item->item_name); + switch (item->diff_result + & (NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B)) { + case NM_SETTING_DIFF_RESULT_IN_B: + nm_log(level, + domain, + NULL, + NULL, + "%s%"_NM_LOG_ALIGN + "s < %s", + prefix, + str1->str, + str_diff ?: "NULL"); + break; + case NM_SETTING_DIFF_RESULT_IN_A: + nm_log(level, + domain, + NULL, + NULL, + "%s%"_NM_LOG_ALIGN + "s = %s", + prefix, + str1->str, + str_conn ?: "NULL"); + break; + default: + nm_log(level, + domain, + NULL, + NULL, + "%s%"_NM_LOG_ALIGN + "s = %s < %s", + prefix, + str1->str, + str_conn ?: "NULL", + str_diff ?: "NULL"); + break; +#undef _NM_LOG_ALIGN + } + g_free(str_conn); + g_free(str_diff); + } + } + + g_array_free(sorted_names, TRUE); + g_string_free(str1, TRUE); +out: + g_hash_table_destroy(connection_diff); + g_array_free(sorted_hashes, TRUE); +} + +#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/" +#define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/" +G_STATIC_ASSERT(sizeof(IPV4_PROPERTY_DIR) == sizeof(IPV6_PROPERTY_DIR)); +G_STATIC_ASSERT(NM_STRLEN(IPV6_PROPERTY_DIR) + IFNAMSIZ + 60 + == NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE); + +/** + * nm_utils_sysctl_ip_conf_path: + * @addr_family: either AF_INET or AF_INET6. + * @buf: the output buffer where to write the path. It + * must be at least NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE bytes + * long. + * @ifname: an interface name + * @property: a property name + * + * Returns: the path to IPv6 property @property on @ifname. Note that + * this returns the input argument @buf. + */ +const char * +nm_utils_sysctl_ip_conf_path(int addr_family, char *buf, const char *ifname, const char *property) +{ + int len; + + nm_assert(buf); + nm_assert_addr_family(addr_family); + + g_assert(nm_utils_ifname_valid_kernel(ifname, NULL)); + property = NM_ASSERT_VALID_PATH_COMPONENT(property); + + len = g_snprintf(buf, + NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE, + "%s%s/%s", + addr_family == AF_INET6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR, + ifname, + property); + g_assert(len < NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE - 1); + return buf; +} + +gboolean +nm_utils_sysctl_ip_conf_is_path(int addr_family, + const char *path, + const char *ifname, + const char *property) +{ + g_return_val_if_fail(path, FALSE); + NM_ASSERT_VALID_PATH_COMPONENT(property); + g_assert(!ifname || nm_utils_ifname_valid_kernel(ifname, NULL)); + + if (addr_family == AF_INET) { + if (!g_str_has_prefix(path, IPV4_PROPERTY_DIR)) + return FALSE; + path += NM_STRLEN(IPV4_PROPERTY_DIR); + } else if (addr_family == AF_INET6) { + if (!g_str_has_prefix(path, IPV6_PROPERTY_DIR)) + return FALSE; + path += NM_STRLEN(IPV6_PROPERTY_DIR); + } else + g_return_val_if_reached(FALSE); + + if (ifname) { + if (!g_str_has_prefix(path, ifname)) + return FALSE; + path += strlen(ifname); + if (path[0] != '/') + return FALSE; + path++; + } else { + const char *slash; + char buf[IFNAMSIZ]; + gsize l; + + slash = strchr(path, '/'); + if (!slash) + return FALSE; + l = slash - path; + if (l >= IFNAMSIZ) + return FALSE; + memcpy(buf, path, l); + buf[l] = '\0'; + if (!nm_utils_ifname_valid_kernel(buf, NULL)) + return FALSE; + path = slash + 1; + } + + if (!nm_streq(path, property)) + return FALSE; + + return TRUE; +} + +gboolean +nm_utils_is_valid_path_component(const char *name) +{ + const char *n; + + if (name == NULL || name[0] == '\0') + return FALSE; + + if (name[0] == '.') { + if (name[1] == '\0') + return FALSE; + if (name[1] == '.' && name[2] == '\0') + return FALSE; + } + n = name; + do { + if (*n == '/') + return FALSE; + } while (*(++n) != '\0'); + + return TRUE; +} + +const char * +NM_ASSERT_VALID_PATH_COMPONENT(const char *name) +{ + if (G_LIKELY(nm_utils_is_valid_path_component(name))) + return name; + + nm_log_err(LOGD_CORE, + "Failed asserting path component: %s%s%s", + NM_PRINT_FMT_QUOTED(name, "\"", name, "\"", "(null)")); + g_error("FATAL: Failed asserting path component: %s%s%s", + NM_PRINT_FMT_QUOTED(name, "\"", name, "\"", "(null)")); + g_assert_not_reached(); +} + +/*****************************************************************************/ + +typedef struct { + NMUuid bin; + char + _nul_sentinel; /* just for safety, if somebody accidentally uses the binary in a string context. */ + + /* depending on whether the string is packed or not (with/without hyphens), + * it's 32 or 36 characters long (plus the trailing NUL). + * + * The difference is that boot-id is a valid RFC 4211 UUID and represented + * as a 36 ascii string (with hyphens). The machine-id technically is not + * a UUID, but just a 32 byte sequence of hexchars. */ + char str[37]; + bool is_fake; +} UuidData; + +static UuidData * +_uuid_data_init(UuidData *uuid_data, gboolean packed, gboolean is_fake, const NMUuid *uuid) +{ + nm_assert(uuid_data); + nm_assert(uuid); + + uuid_data->bin = *uuid; + uuid_data->_nul_sentinel = '\0'; + uuid_data->is_fake = is_fake; + if (packed) { + G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= (sizeof(*uuid) * 2 + 1)); + nm_utils_bin2hexstr_full(uuid, sizeof(*uuid), '\0', FALSE, uuid_data->str); + } else { + G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= 37); + _nm_utils_uuid_unparse(uuid, uuid_data->str); + } + return uuid_data; +} + +/*****************************************************************************/ + +static const UuidData * +_machine_id_get(gboolean allow_fake) +{ + static const UuidData *volatile p_uuid_data; + const UuidData *d; + +again: + d = g_atomic_pointer_get(&p_uuid_data); + if (G_UNLIKELY(!d)) { + static gsize lock; + static UuidData uuid_data; + gs_free char * content = NULL; + gboolean is_fake = TRUE; + const char * fake_type = NULL; + NMUuid uuid; + + /* Get the machine ID from /etc/machine-id; it's always in /etc no matter + * where our configured SYSCONFDIR is. Alternatively, it might be in + * LOCALSTATEDIR /lib/dbus/machine-id. + */ + if (nm_utils_file_get_contents(-1, + "/etc/machine-id", + 100 * 1024, + 0, + &content, + NULL, + NULL, + NULL) + || nm_utils_file_get_contents(-1, + LOCALSTATEDIR "/lib/dbus/machine-id", + 100 * 1024, + 0, + &content, + NULL, + NULL, + NULL)) { + g_strstrip(content); + if (nm_utils_hexstr2bin_full(content, + FALSE, + FALSE, + FALSE, + NULL, + 16, + (guint8 *) &uuid, + sizeof(uuid), + NULL)) { + if (!nm_utils_uuid_is_null(&uuid)) { + /* an all-zero machine-id is not valid. */ + is_fake = FALSE; + } + } + } + + if (is_fake) { + const guint8 *seed_bin; + const char * hash_seed; + gsize seed_len; + + if (!allow_fake) { + /* we don't allow generating (and memorizing) a fake key. + * Signal that no valid machine-id exists. */ + return NULL; + } + + if (nm_utils_host_id_get(&seed_bin, &seed_len)) { + /* We have no valid machine-id but we have a valid secrey_key. + * Generate a fake machine ID by hashing the secret-key. The secret_key + * is commonly persisted, so it should be stable across reboots (despite + * having a broken system without proper machine-id). + * + * Note that we access the host-id here, which is based on secret_key. + * Also not that the secret_key may be generated based on the machine-id, + * so we have to be careful that they don't depend on each other (and + * no infinite recursion happens. This is done correctly, because the secret-key + * will call _machine_id_get(FALSE), so it won't allow accessing a fake + * machine-id, thus avoiding the problem. */ + fake_type = "secret-key"; + hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6"; + } else { + /* the secret-key is not valid/persistent either. That happens when we fail + * to read/write the secret-key to disk. Fallback to boot-id. The boot-id + * itself may be fake and randomly generated ad-hoc, but that is as best + * as it gets. */ + seed_bin = (const guint8 *) nm_utils_boot_id_bin(); + seed_len = sizeof(NMUuid); + fake_type = "boot-id"; + hash_seed = "7ff0c8f5-5399-4901-ab63-61bf594abe8b"; + } + + /* the fake machine-id is based on secret-key/boot-id, but we hash it + * again, so that they are not literally the same. */ + nm_utils_uuid_generate_from_string_bin(&uuid, + (const char *) seed_bin, + seed_len, + NM_UTILS_UUID_TYPE_VERSION5, + (gpointer) hash_seed); + } + + if (!g_once_init_enter(&lock)) + goto again; + + d = _uuid_data_init(&uuid_data, TRUE, is_fake, &uuid); + g_atomic_pointer_set(&p_uuid_data, d); + g_once_init_leave(&lock, 1); + + if (is_fake) { + nm_log_err(LOGD_CORE, + "/etc/machine-id: no valid machine-id. Use fake one based on %s: %s", + fake_type, + d->str); + } else + nm_log_dbg(LOGD_CORE, "/etc/machine-id: %s", d->str); + } + + return d; +} + +const char * +nm_utils_machine_id_str(void) +{ + return _machine_id_get(TRUE)->str; +} + +const NMUuid * +nm_utils_machine_id_bin(void) +{ + return &_machine_id_get(TRUE)->bin; +} + +gboolean +nm_utils_machine_id_is_fake(void) +{ + return _machine_id_get(TRUE)->is_fake; +} + +/*****************************************************************************/ + +/* prefix for version2 secret key. The secret key is hashed with /etc/machine-id. */ +#define SECRET_KEY_V2_PREFIX "nm-v2:" +#define SECRET_KEY_FILE NMSTATEDIR "/secret_key" + +static gboolean +_host_id_read_timestamp(gboolean use_secret_key_file, + const guint8 *host_id, + gsize host_id_len, + gint64 * out_timestamp_ns) +{ + struct stat st; + gint64 now; + guint64 v; + + if (use_secret_key_file && stat(SECRET_KEY_FILE, &st) == 0) { + /* don't check for overflow or timestamps in the future. We get whatever + * (bogus) date is on the file. */ + *out_timestamp_ns = nm_utils_timespec_to_nsec(&st.st_mtim); + return TRUE; + } + + /* generate a fake timestamp based on the host-id. + * + * This really should never happen under normal circumstances. We already + * are in a code path, where the system has a problem (unable to get good randomness + * and/or can't access the secret_key). In such a scenario, a fake timestamp is the + * least of our problems. + * + * At least, generate something sensible so we don't have to worry about the + * timestamp. It is wrong to worry about using a fake timestamp (which is tied to + * the secret_key) if we are unable to access the secret_key file in the first place. + * + * Pick a random timestamp from the past two years. Yes, this timestamp + * is not stable across restarts, but apparently neither is the host-id + * nor the secret_key itself. */ + +#define EPOCH_TWO_YEARS (G_GINT64_CONSTANT(2 * 365 * 24 * 3600) * NM_UTILS_NSEC_PER_SEC) + + v = nm_hash_siphash42(1156657133u, host_id, host_id_len); + + now = time(NULL); + *out_timestamp_ns = + NM_MAX((gint64) 1, + (now * NM_UTILS_NSEC_PER_SEC) - ((gint64)(v % ((guint64)(EPOCH_TWO_YEARS))))); + return FALSE; +} + +static const guint8 * +_host_id_hash_v2(const guint8 *seed_arr, + gsize seed_len, + guint8 * out_digest /* 32 bytes (NM_UTILS_CHECKSUM_LENGTH_SHA256) */) +{ + nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256); + const UuidData * machine_id_data; + char slen[100]; + + /* + (stat -c '%s' /var/lib/NetworkManager/secret_key; + echo -n ' '; + cat /var/lib/NetworkManager/secret_key; + cat /etc/machine-id | tr -d '\n' | sed -n 's/[a-f0-9-]/\0/pg') | sha256sum + */ + + nm_sprintf_buf(slen, "%" G_GSIZE_FORMAT " ", seed_len); + g_checksum_update(sum, (const guchar *) slen, strlen(slen)); + + g_checksum_update(sum, (const guchar *) seed_arr, seed_len); + + machine_id_data = _machine_id_get(FALSE); + if (machine_id_data && !machine_id_data->is_fake) + g_checksum_update(sum, (const guchar *) machine_id_data->str, strlen(machine_id_data->str)); + + nm_utils_checksum_get_digest_len(sum, out_digest, NM_UTILS_CHECKSUM_LENGTH_SHA256); + return out_digest; +} + +static gboolean +_host_id_read(guint8 **out_host_id, gsize *out_host_id_len) +{ +#define SECRET_KEY_LEN 32u + guint8 sha256_digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; + nm_auto_clear_secret_ptr NMSecretPtr file_content = {0}; + const guint8 * secret_arr; + gsize secret_len; + GError * error = NULL; + gboolean success; + + if (!nm_utils_file_get_contents(-1, + SECRET_KEY_FILE, + 10 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, + &file_content.str, + &file_content.len, + NULL, + &error)) { + if (!nm_utils_error_is_notfound(error)) { + nm_log_warn(LOGD_CORE, + "secret-key: failure reading secret key in \"%s\": %s (generate new key)", + SECRET_KEY_FILE, + error->message); + } + g_clear_error(&error); + } else if (file_content.len >= NM_STRLEN(SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN + && memcmp(file_content.bin, SECRET_KEY_V2_PREFIX, NM_STRLEN(SECRET_KEY_V2_PREFIX)) + == 0) { + /* for this type of secret key, we require a prefix followed at least SECRET_KEY_LEN (32) bytes. We + * (also) do that, because older versions of NetworkManager wrote exactly 32 bytes without + * prefix, so we won't wrongly interpret such legacy keys as v2 (if they accidentally have + * a SECRET_KEY_V2_PREFIX prefix, they'll still have the wrong size). + * + * Note that below we generate the random seed in base64 encoding. But that is only done + * to write an ASCII file. There is no base64 decoding and the ASCII is hashed as-is. + * We would accept any binary data just as well (provided a suitable prefix and at least + * 32 bytes). + * + * Note that when hashing the v2 content, we also hash the prefix. There is no strong reason, + * except that it seems simpler not to distinguish between the v2 prefix and the content. + * It's all just part of the seed. */ + + secret_arr = _host_id_hash_v2(file_content.bin, file_content.len, sha256_digest); + secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256; + success = TRUE; + nm_log_dbg(LOGD_CORE, + "secret-key: v2 secret key loaded from \"%s\" (%zu bytes)", + SECRET_KEY_FILE, + file_content.len); + goto out; + } else if (file_content.len >= 16) { + secret_arr = file_content.bin; + secret_len = file_content.len; + success = TRUE; + nm_log_dbg(LOGD_CORE, + "secret-key: v1 secret key loaded from \"%s\" (%zu bytes)", + SECRET_KEY_FILE, + file_content.len); + goto out; + } else { + /* the secret key is borked. Log a warning, but proceed below to generate + * a new one. */ + nm_log_warn(LOGD_CORE, + "secret-key: too short secret key in \"%s\" (generate new key)", + SECRET_KEY_FILE); + } + + /* generate and persist new key */ + { +#define SECRET_KEY_LEN_BASE64 ((((SECRET_KEY_LEN / 3) + 1) * 4) + 4) + guint8 rnd_buf[SECRET_KEY_LEN]; + guint8 new_content[NM_STRLEN(SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN_BASE64]; + int base64_state = 0; + int base64_save = 0; + gsize len; + + success = nm_utils_random_bytes(rnd_buf, sizeof(rnd_buf)); + + /* Our key is really binary data. But since we anyway generate a random seed + * (with 32 random bytes), don't write it in binary, but instead create + * an pure ASCII (base64) representation. Note that the ASCII will still be taken + * as-is (no base64 decoding is done). The sole purpose is to write a ASCII file + * instead of a binary. The content is gibberish either way. */ + memcpy(new_content, SECRET_KEY_V2_PREFIX, NM_STRLEN(SECRET_KEY_V2_PREFIX)); + len = NM_STRLEN(SECRET_KEY_V2_PREFIX); + len += g_base64_encode_step(rnd_buf, + sizeof(rnd_buf), + FALSE, + (char *) &new_content[len], + &base64_state, + &base64_save); + len += + g_base64_encode_close(FALSE, (char *) &new_content[len], &base64_state, &base64_save); + nm_assert(len <= sizeof(new_content)); + + secret_arr = _host_id_hash_v2(new_content, len, sha256_digest); + secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256; + + if (!success) + nm_log_warn(LOGD_CORE, + "secret-key: failure to generate good random data for secret-key (use " + "non-persistent key)"); + else if (nm_utils_get_testing()) { + /* for test code, we don't write the generated secret-key to disk. */ + } else if (!nm_utils_file_set_contents(SECRET_KEY_FILE, + (const char *) new_content, + len, + 0600, + NULL, + &error)) { + nm_log_warn( + LOGD_CORE, + "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)", + SECRET_KEY_FILE, + error->message); + g_clear_error(&error); + success = FALSE; + } else + nm_log_dbg(LOGD_CORE, + "secret-key: persist new v2 secret key to \"%s\" (%zu bytes)", + SECRET_KEY_FILE, + len); + + nm_explicit_bzero(rnd_buf, sizeof(rnd_buf)); + nm_explicit_bzero(new_content, sizeof(new_content)); + } + +out: + *out_host_id_len = secret_len; + *out_host_id = nm_memdup(secret_arr, secret_len); + return success; +} + +typedef struct { + guint8 *host_id; + gsize host_id_len; + gint64 timestamp_ns; + bool is_good : 1; + bool timestamp_is_good : 1; +} HostIdData; + +static const HostIdData * +_host_id_get(void) +{ + static const HostIdData *volatile host_id_static; + const HostIdData *host_id; + +again: + host_id = g_atomic_pointer_get(&host_id_static); + if (G_UNLIKELY(!host_id)) { + static HostIdData host_id_data; + static gsize init_value = 0; + + if (!g_once_init_enter(&init_value)) + goto again; + + host_id_data.is_good = _host_id_read(&host_id_data.host_id, &host_id_data.host_id_len); + + host_id_data.timestamp_is_good = _host_id_read_timestamp(host_id_data.is_good, + host_id_data.host_id, + host_id_data.host_id_len, + &host_id_data.timestamp_ns); + if (!host_id_data.timestamp_is_good && host_id_data.is_good) + nm_log_warn(LOGD_CORE, "secret-key: failure reading host timestamp (use fake one)"); + + host_id = &host_id_data; + g_atomic_pointer_set(&host_id_static, host_id); + g_once_init_leave(&init_value, 1); + } + + return host_id; +} + +/** + * nm_utils_host_id_get: + * @out_host_id: (out) (transfer none): the binary host key + * @out_host_id_len: the length of the host key. + * + * This returns a per-host key that depends on /var/lib/NetworkManage/secret_key + * and (depending on the version) on /etc/machine-id. If /var/lib/NetworkManage/secret_key + * does not exist, it will be generated and persisted for next boot. + * + * Returns: %TRUE, if the host key is "good". Note that this function + * will always succeed to return a host-key, and that this key + * won't change during the run of the program (no matter what). + * A %FALSE return possibly means, that the secret_key is not persisted + * to disk, and/or that it was generated with bad randomness. + */ +gboolean +nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len) +{ + const HostIdData *host_id; + + host_id = _host_id_get(); + *out_host_id = host_id->host_id; + *out_host_id_len = host_id->host_id_len; + return host_id->is_good; +} + +gint64 +nm_utils_host_id_get_timestamp_ns(void) +{ + return _host_id_get()->timestamp_ns; +} + +/*****************************************************************************/ + +static const UuidData * +_boot_id_get(void) +{ + static const UuidData *volatile p_boot_id; + const UuidData *d; + +again: + d = g_atomic_pointer_get(&p_boot_id); + if (G_UNLIKELY(!d)) { + static gsize lock; + static UuidData boot_id; + gs_free char * contents = NULL; + NMUuid uuid; + gboolean is_fake = FALSE; + + nm_utils_file_get_contents(-1, + "/proc/sys/kernel/random/boot_id", + 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + NULL); + if (!contents || !_nm_utils_uuid_parse(nm_strstrip(contents), &uuid)) { + /* generate a random UUID instead. */ + is_fake = TRUE; + _nm_utils_uuid_generate_random(&uuid); + } + + if (!g_once_init_enter(&lock)) + goto again; + + d = _uuid_data_init(&boot_id, FALSE, is_fake, &uuid); + g_atomic_pointer_set(&p_boot_id, d); + g_once_init_leave(&lock, 1); + } + + return d; +} + +const char * +nm_utils_boot_id_str(void) +{ + return _boot_id_get()->str; +} + +const NMUuid * +nm_utils_boot_id_bin(void) +{ + return &_boot_id_get()->bin; +} + +/*****************************************************************************/ + +const char * +nm_utils_proc_cmdline(void) +{ + static const char *volatile proc_cmdline_cached = NULL; + const char *proc_cmdline; + +again: + proc_cmdline = g_atomic_pointer_get(&proc_cmdline_cached); + if (G_UNLIKELY(!proc_cmdline)) { + gs_free char *str = NULL; + + g_file_get_contents("/proc/cmdline", &str, NULL, NULL); + str = nm_str_realloc(str); + + proc_cmdline = str ?: ""; + if (!g_atomic_pointer_compare_and_exchange(&proc_cmdline_cached, NULL, proc_cmdline)) + goto again; + + g_steal_pointer(&str); + } + + return proc_cmdline; +} + +const char *const * +nm_utils_proc_cmdline_split(void) +{ + static const char *const *volatile proc_cmdline_cached = NULL; + const char *const *proc_cmdline; + +again: + proc_cmdline = g_atomic_pointer_get(&proc_cmdline_cached); + if (G_UNLIKELY(!proc_cmdline)) { + gs_strfreev char **split = NULL; + + split = nm_utils_strsplit_quoted(nm_utils_proc_cmdline()); + if (!g_atomic_pointer_compare_and_exchange(&proc_cmdline_cached, NULL, (gpointer) split)) + goto again; + + proc_cmdline = (const char *const *) g_steal_pointer(&split); + } + + return proc_cmdline; +} + +/*****************************************************************************/ + +/** + * nm_utils_arp_type_detect_from_hwaddrlen: + * @hwaddr_len: the length of the hardware address in bytes. + * + * Detects the arp-type based on the length of the MAC address. + * On success, this returns a (positive) value in uint16_t range, + * like ARPHRD_ETHER or ARPHRD_INFINIBAND. + * + * On failure, returns a negative error code. + * + * Returns: the arp-type or negative value on error. */ +int +nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len) +{ + switch (hwaddr_len) { + case ETH_ALEN: + return ARPHRD_ETHER; + case INFINIBAND_ALEN: + return ARPHRD_INFINIBAND; + default: + /* Note: if you ever support anything but ethernet and infiniband, + * make sure to look at all callers. They assert that it's one of + * these two. */ + return -EINVAL; + } +} + +gboolean +nm_utils_arp_type_validate_hwaddr(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) +{ + if (!hwaddr) + return FALSE; + + if (arp_type == ARPHRD_ETHER) { + G_STATIC_ASSERT(ARPHRD_ETHER >= 0 && ARPHRD_ETHER <= 0xFF); + if (hwaddr_len != ETH_ALEN) + return FALSE; + } else if (arp_type == ARPHRD_INFINIBAND) { + G_STATIC_ASSERT(ARPHRD_INFINIBAND >= 0 && ARPHRD_INFINIBAND <= 0xFF); + if (hwaddr_len != INFINIBAND_ALEN) + return FALSE; + } else + return FALSE; + + nm_assert(arp_type == nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len)); + return TRUE; +} + +gboolean +nm_utils_arp_type_get_hwaddr_relevant_part(int arp_type, const guint8 **hwaddr, gsize *hwaddr_len) +{ + g_return_val_if_fail(hwaddr && hwaddr_len + && nm_utils_arp_type_validate_hwaddr(arp_type, *hwaddr, *hwaddr_len), + FALSE); + + /* for infiniband, we only consider the last 8 bytes. */ + if (arp_type == ARPHRD_INFINIBAND) { + *hwaddr += (INFINIBAND_ALEN - 8); + *hwaddr_len = 8; + } + + return TRUE; +} + +/*****************************************************************************/ + +/* Returns the "u" (universal/local) bit value for a Modified EUI-64 */ +static gboolean +get_gre_eui64_u_bit(guint32 addr) +{ + static const struct { + guint32 mask; + guint32 result; + } items[] = { + {0xff000000}, {0x7f000000}, /* IPv4 loopback */ + {0xf0000000}, {0xe0000000}, /* IPv4 multicast */ + {0xffffff00}, {0xe0000000}, /* IPv4 local multicast */ + {0xffffffff}, {INADDR_BROADCAST}, /* limited broadcast */ + {0xff000000}, {0x00000000}, /* zero net */ + {0xff000000}, {0x0a000000}, /* private 10 (RFC3330) */ + {0xfff00000}, {0xac100000}, /* private 172 */ + {0xffff0000}, {0xc0a80000}, /* private 192 */ + {0xffff0000}, {0xa9fe0000}, /* IPv4 link-local */ + {0xffffff00}, {0xc0586300}, /* anycast 6-to-4 */ + {0xffffff00}, {0xc0000200}, /* test 192 */ + {0xfffe0000}, {0xc6120000}, /* test 198 */ + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(items); i++) { + if ((addr & htonl(items[i].mask)) == htonl(items[i].result)) + return 0x00; /* "local" scope */ + } + return 0x02; /* "universal" scope */ +} + +/** + * nm_utils_get_ipv6_interface_identifier: + * @link_type: the hardware link type + * @hwaddr: the hardware address of the interface + * @hwaddr_len: the length (in bytes) of @hwaddr + * @dev_id: the device identifier, if any + * @out_iid: on success, filled with the interface identifier; on failure + * zeroed out + * + * Constructs an interface identifier in "Modified EUI-64" format which is + * suitable for constructing IPv6 addresses. Note that the identifier is + * not obscured in any way (eg, RFC3041). + * + * Returns: %TRUE if the interface identifier could be constructed, %FALSE if + * if could not be constructed. + */ +gboolean +nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, + const guint8 * hwaddr, + guint hwaddr_len, + guint dev_id, + NMUtilsIPv6IfaceId *out_iid) +{ + guint32 addr; + + g_return_val_if_fail(hwaddr != NULL, FALSE); + g_return_val_if_fail(hwaddr_len > 0, FALSE); + g_return_val_if_fail(out_iid != NULL, FALSE); + + out_iid->id = 0; + + switch (link_type) { + case NM_LINK_TYPE_INFINIBAND: + /* Use the port GUID per http://tools.ietf.org/html/rfc4391#section-8, + * making sure to set the 'u' bit to 1. The GUID is the lower 64 bits + * of the IPoIB interface's hardware address. + */ + g_return_val_if_fail(hwaddr_len == INFINIBAND_ALEN, FALSE); + memcpy(out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8); + out_iid->id_u8[0] |= 0x02; + return TRUE; + case NM_LINK_TYPE_GRE: + /* Hardware address is the network-endian IPv4 address */ + g_return_val_if_fail(hwaddr_len == 4, FALSE); + addr = *(guint32 *) hwaddr; + out_iid->id_u8[0] = get_gre_eui64_u_bit(addr); + out_iid->id_u8[1] = 0x00; + out_iid->id_u8[2] = 0x5E; + out_iid->id_u8[3] = 0xFE; + memcpy(out_iid->id_u8 + 4, &addr, 4); + return TRUE; + case NM_LINK_TYPE_6LOWPAN: + /* The hardware address is already 64-bit. This is the case for + * IEEE 802.15.4 networks. */ + memcpy(out_iid->id_u8, hwaddr, sizeof(out_iid->id_u8)); + return TRUE; + default: + if (hwaddr_len == ETH_ALEN) { + /* Translate 48-bit MAC address to a 64-bit Modified EUI-64. See + * http://tools.ietf.org/html/rfc4291#appendix-A and the Linux + * kernel's net/ipv6/addrconf.c::ipv6_generate_eui64() function. + */ + out_iid->id_u8[0] = hwaddr[0]; + out_iid->id_u8[1] = hwaddr[1]; + out_iid->id_u8[2] = hwaddr[2]; + if (dev_id) { + out_iid->id_u8[3] = (dev_id >> 8) & 0xff; + out_iid->id_u8[4] = dev_id & 0xff; + } else { + out_iid->id_u8[0] ^= 0x02; + out_iid->id_u8[3] = 0xff; + out_iid->id_u8[4] = 0xfe; + } + out_iid->id_u8[5] = hwaddr[3]; + out_iid->id_u8[6] = hwaddr[4]; + out_iid->id_u8[7] = hwaddr[5]; + return TRUE; + } + break; + } + return FALSE; +} + +/*****************************************************************************/ + +/** + * nm_utils_ipv6_addr_set_interface_identifier: + * @addr: output token encoded as %in6_addr + * @iid: %NMUtilsIPv6IfaceId interface identifier + * + * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use + * with Linux platform). This only copies the lower 8 bytes, ignoring + * the /64 network prefix which is expected to be all-zero for a valid + * token. + */ +void +nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, const NMUtilsIPv6IfaceId iid) +{ + memcpy(addr->s6_addr + 8, &iid.id_u8, 8); +} + +/** + * nm_utils_ipv6_interface_identifier_get_from_addr: + * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token + * @addr: token encoded as %in6_addr + * + * Converts the %in6_addr encoded token (as used by Linux platform) to + * the interface identifier. + */ +void +nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId * iid, + const struct in6_addr *addr) +{ + memcpy(iid, addr->s6_addr + 8, 8); +} + +/** + * nm_utils_ipv6_interface_identifier_get_from_token: + * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token + * @token: token encoded as string + * + * Converts the %in6_addr encoded token (as used in ip6 settings) to + * the interface identifier. + * + * Returns: %TRUE if the @token is a valid token, %FALSE otherwise + */ +gboolean +nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, const char *token) +{ + struct in6_addr i6_token; + + g_return_val_if_fail(token, FALSE); + + if (!inet_pton(AF_INET6, token, &i6_token)) + return FALSE; + + if (!_nm_utils_inet6_is_token(&i6_token)) + return FALSE; + + nm_utils_ipv6_interface_identifier_get_from_addr(iid, &i6_token); + return TRUE; +} + +/** + * nm_utils_inet6_interface_identifier_to_token: + * @iid: %NMUtilsIPv6IfaceId interface identifier + * @buf: the destination buffer of at least %NM_UTILS_INET_ADDRSTRLEN + * bytes. + * + * Converts the interface identifier to a string token. + * + * Returns: the input buffer filled with the id as string. + */ +const char * +nm_utils_inet6_interface_identifier_to_token(NMUtilsIPv6IfaceId iid, + char buf[static INET6_ADDRSTRLEN]) +{ + struct in6_addr i6_token = {.s6_addr = { + 0, + }}; + + nm_assert(buf); + nm_utils_ipv6_addr_set_interface_identifier(&i6_token, iid); + return _nm_utils_inet6_ntop(&i6_token, buf); +} + +/*****************************************************************************/ + +char * +nm_utils_stable_id_random(void) +{ + char buf[15]; + + nm_utils_random_bytes(buf, sizeof(buf)); + return g_base64_encode((guchar *) buf, sizeof(buf)); +} + +char * +nm_utils_stable_id_generated_complete(const char *stable_id_generated) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + guint8 buf[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + char * base64; + + /* for NM_UTILS_STABLE_TYPE_GENERATED we generate a possibly long string + * by doing text-substitutions in nm_utils_stable_id_parse(). + * + * Let's shorten the (possibly) long stable_id to something more compact. */ + + g_return_val_if_fail(stable_id_generated, NULL); + + sum = g_checksum_new(G_CHECKSUM_SHA1); + g_checksum_update(sum, (guchar *) stable_id_generated, strlen(stable_id_generated)); + nm_utils_checksum_get_digest(sum, buf); + + /* we don't care to use the sha1 sum in common hex representation. + * Use instead base64, it's 27 chars (stripping the padding) vs. + * 40. */ + + base64 = g_base64_encode((guchar *) buf, sizeof(buf)); + nm_assert(strlen(base64) == 28); + nm_assert(base64[27] == '='); + + base64[27] = '\0'; + return base64; +} + +static void +_stable_id_append(GString *str, const char *substitution) +{ + if (!substitution) + substitution = ""; + g_string_append_printf(str, "=%zu{%s}", strlen(substitution), substitution); +} + +NMUtilsStableType +nm_utils_stable_id_parse(const char *stable_id, + const char *deviceid, + const char *hwaddr, + const char *bootid, + const char *uuid, + char ** out_generated) +{ + gsize i, idx_start; + GString *str = NULL; + + g_return_val_if_fail(out_generated, NM_UTILS_STABLE_TYPE_RANDOM); + + if (!stable_id) { + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_UUID; + } + + /* the stable-id allows for some dynamic by performing text-substitutions + * of ${...} patterns. + * + * At first, it looks a bit like bash parameter substitution. + * In contrast however, the process is unambiguous so that the resulting + * effective id differs if: + * - the original, untranslated stable-id differs + * - or any of the subsitutions differs. + * + * The reason for that is, for example if you specify "${CONNECTION}" in the + * stable-id, then the resulting ID should be always(!) unique for this connection. + * There should be no way another connection could specify any stable-id that results + * in the same addresses to be generated (aside hash collisions). + * + * + * For example: say you have a connection with UUID + * "123e4567-e89b-12d3-a456-426655440000" which happens also to be + * the current boot-id. + * Then: + * (1) connection.stable-id = + * (2) connection.stable-id = "123e4567-e89b-12d3-a456-426655440000" + * (3) connection.stable-id = "${CONNECTION}" + * (3) connection.stable-id = "${BOOT}" + * will all generate different addresses, although in one way or the + * other, they all mangle the uuid "123e4567-e89b-12d3-a456-426655440000". + * + * For example, with stable-id="${FOO}${BAR}" the substitutions + * - FOO="ab", BAR="c" + * - FOO="a", BAR="bc" + * should give a different effective id. + * + * For example, with FOO="x" and BAR="x", the stable-ids + * - "${FOO}${BAR}" + * - "${BAR}${FOO}" + * should give a different effective id. + */ + + idx_start = 0; + for (i = 0; stable_id[i];) { + if (stable_id[i] != '$') { + i++; + continue; + } + +#define CHECK_PREFIX(prefix) \ + ({ \ + gboolean _match = FALSE; \ + \ + if (g_str_has_prefix(&stable_id[i], "" prefix "")) { \ + _match = TRUE; \ + if (!str) \ + str = g_string_sized_new(256); \ + i += NM_STRLEN(prefix); \ + g_string_append_len(str, &(stable_id)[idx_start], i - idx_start); \ + idx_start = i; \ + } \ + _match; \ + }) + if (CHECK_PREFIX("${CONNECTION}")) + _stable_id_append(str, uuid); + else if (CHECK_PREFIX("${BOOT}")) + _stable_id_append(str, bootid); + else if (CHECK_PREFIX("${DEVICE}")) + _stable_id_append(str, deviceid); + else if (CHECK_PREFIX("${MAC}")) + _stable_id_append(str, hwaddr); + else if (g_str_has_prefix(&stable_id[i], "${RANDOM}")) { + /* RANDOM makes not so much sense for cloned-mac-address + * as the result is similar to specifying "cloned-mac-address=random". + * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses + * where this is effectively the only way to generate a different + * (random) host identifier for each connect. + * + * With RANDOM, the user can switch the lifetime of the + * generated cloned-mac-address and IPv6 host identifier + * by toggling only the stable-id property of the connection. + * With RANDOM being the most short-lived, ~non-stable~ variant. + */ + if (str) + g_string_free(str, TRUE); + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_RANDOM; + } else { + /* The text following the '$' is not recognized as valid + * substitution pattern. Treat it verbatim. */ + i++; + + /* Note that using unrecognized substitution patterns might + * yield different results with future versions. Avoid that, + * by not using '$' (except for actual substitutions) or escape + * it as "$$" (which is guaranteed to be treated verbatim + * in future). */ + if (stable_id[i] == '$') + i++; + } + } +#undef CHECK_PREFIX + + if (!str) { + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_STABLE_ID; + } + + if (idx_start < i) + g_string_append_len(str, &stable_id[idx_start], i - idx_start); + *out_generated = g_string_free(str, FALSE); + return NM_UTILS_STABLE_TYPE_GENERATED; +} + +/*****************************************************************************/ + +static gboolean +_is_reserved_ipv6_iid(const guint8 *iid) +{ + /* https://tools.ietf.org/html/rfc5453 */ + /* https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xml */ + + /* 0000:0000:0000:0000 (Subnet-Router Anycast [RFC4291]) */ + if (memcmp(iid, &nm_ip_addr_zero.addr6.s6_addr[8], 8) == 0) + return TRUE; + + /* 0200:5EFF:FE00:0000 - 0200:5EFF:FE00:5212 (Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block [RFC4291]) + * 0200:5EFF:FE00:5213 (Proxy Mobile IPv6 [RFC6543]) + * 0200:5EFF:FE00:5214 - 0200:5EFF:FEFF:FFFF (Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block [RFC4291]) */ + if (memcmp(iid, (const guint8[]){0x02, 0x00, 0x5E, 0xFF, 0xFE}, 5) == 0) + return TRUE; + + /* FDFF:FFFF:FFFF:FF80 - FDFF:FFFF:FFFF:FFFF (Reserved Subnet Anycast Addresses [RFC2526]) */ + if (memcmp(iid, (const guint8[]){0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 7) == 0) { + if (iid[7] & 0x80) + return TRUE; + } + + return FALSE; +} + +static gboolean +_set_stable_privacy(NMUtilsStableType stable_type, + struct in6_addr * addr, + const char * ifname, + const char * network_id, + guint32 dad_counter, + const guint8 * host_id, + gsize host_id_len, + GError ** error) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; + guint32 tmp[2]; + + nm_assert(host_id_len); + nm_assert(network_id); + + sum = g_checksum_new(G_CHECKSUM_SHA256); + + host_id_len = MIN(host_id_len, G_MAXUINT32); + + if (stable_type != NM_UTILS_STABLE_TYPE_UUID) { + guint8 stable_type_uint8; + + nm_assert(stable_type < (NMUtilsStableType) 255); + stable_type_uint8 = (guint8) stable_type; + + /* Preferably, we would always like to include the stable-type, + * but for backward compatibility reasons, we cannot for UUID. + * + * That is no real problem and it is still impossible to + * force a collision here, because of how the remaining + * fields are hashed. That is, as we also hash @host_id_len + * and the terminating '\0' of @network_id, it is unambiguously + * possible to revert the process and deduce the @stable_type. + */ + g_checksum_update(sum, &stable_type_uint8, sizeof(stable_type_uint8)); + } + + g_checksum_update(sum, addr->s6_addr, 8); + g_checksum_update(sum, (const guchar *) ifname, strlen(ifname) + 1); + g_checksum_update(sum, (const guchar *) network_id, strlen(network_id) + 1); + tmp[0] = htonl(dad_counter); + tmp[1] = htonl(host_id_len); + g_checksum_update(sum, (const guchar *) tmp, sizeof(tmp)); + g_checksum_update(sum, (const guchar *) host_id, host_id_len); + nm_utils_checksum_get_digest(sum, digest); + + while (_is_reserved_ipv6_iid(digest)) { + g_checksum_reset(sum); + tmp[0] = htonl(++dad_counter); + g_checksum_update(sum, digest, sizeof(digest)); + g_checksum_update(sum, (const guchar *) &tmp[0], sizeof(tmp[0])); + nm_utils_checksum_get_digest(sum, digest); + } + + memcpy(addr->s6_addr + 8, &digest[0], 8); + return TRUE; +} + +gboolean +nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type, + struct in6_addr * addr, + const char * ifname, + const char * network_id, + guint32 dad_counter, + guint8 * host_id, + gsize host_id_len, + GError ** error) +{ + return _set_stable_privacy(stable_type, + addr, + ifname, + network_id, + dad_counter, + host_id, + host_id_len, + error); +} + +#define RFC7217_IDGEN_RETRIES 3 +/** + * nm_utils_ipv6_addr_set_stable_privacy: + * + * Extend the address prefix with an interface identifier using the + * RFC 7217 Stable Privacy mechanism. + * + * Returns: %TRUE on success, %FALSE if the address could not be generated. + */ +gboolean +nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type, + struct in6_addr * addr, + const char * ifname, + const char * network_id, + guint32 dad_counter, + GError ** error) +{ + const guint8 *host_id; + gsize host_id_len; + + g_return_val_if_fail(network_id, FALSE); + + if (dad_counter >= RFC7217_IDGEN_RETRIES) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Too many DAD collisions"); + return FALSE; + } + + nm_utils_host_id_get(&host_id, &host_id_len); + + return _set_stable_privacy(stable_type, + addr, + ifname, + network_id, + dad_counter, + host_id, + host_id_len, + error); +} + +/*****************************************************************************/ + +static void +_hw_addr_eth_complete(struct ether_addr *addr, + const char * current_mac_address, + const char * generate_mac_address_mask) +{ + struct ether_addr mask; + struct ether_addr oui; + struct ether_addr *ouis; + gsize ouis_len; + guint i; + + /* the second LSB of the first octet means + * "globally unique, OUI enforced, BIA (burned-in-address)" + * vs. "locally-administered". By default, set it to + * generate locally-administered addresses. + * + * Maybe be overwritten by a mask below. */ + addr->ether_addr_octet[0] |= 2; + + if (!generate_mac_address_mask || !*generate_mac_address_mask) + goto out; + if (!_nm_utils_generate_mac_address_mask_parse(generate_mac_address_mask, + &mask, + &ouis, + &ouis_len, + NULL)) + goto out; + + nm_assert((ouis == NULL) ^ (ouis_len != 0)); + if (ouis) { + /* g_random_int() is good enough here. It uses a static GRand instance + * that is seeded from /dev/urandom. */ + oui = ouis[g_random_int() % ouis_len]; + g_free(ouis); + } else { + if (!nm_utils_hwaddr_aton(current_mac_address, &oui, ETH_ALEN)) + goto out; + } + + for (i = 0; i < ETH_ALEN; i++) { + const guint8 a = addr->ether_addr_octet[i]; + const guint8 o = oui.ether_addr_octet[i]; + const guint8 m = mask.ether_addr_octet[i]; + + addr->ether_addr_octet[i] = (a & ~m) | (o & m); + } + +out: + /* The LSB of the first octet must always be cleared, + * it means Unicast vs. Multicast */ + addr->ether_addr_octet[0] &= ~1; +} + +char * +nm_utils_hw_addr_gen_random_eth(const char *current_mac_address, + const char *generate_mac_address_mask) +{ + struct ether_addr bin_addr; + + nm_utils_random_bytes(&bin_addr, ETH_ALEN); + _hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask); + return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN); +} + +static char * +_hw_addr_gen_stable_eth(NMUtilsStableType stable_type, + const char * stable_id, + const guint8 * host_id, + gsize host_id_len, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + guint32 tmp; + guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; + struct ether_addr bin_addr; + guint8 stable_type_uint8; + + nm_assert(stable_id); + nm_assert(host_id); + + sum = g_checksum_new(G_CHECKSUM_SHA256); + + host_id_len = MIN(host_id_len, G_MAXUINT32); + + nm_assert(stable_type < (NMUtilsStableType) 255); + stable_type_uint8 = stable_type; + g_checksum_update(sum, (const guchar *) &stable_type_uint8, sizeof(stable_type_uint8)); + + tmp = htonl((guint32) host_id_len); + g_checksum_update(sum, (const guchar *) &tmp, sizeof(tmp)); + g_checksum_update(sum, (const guchar *) host_id, host_id_len); + g_checksum_update(sum, (const guchar *) (ifname ?: ""), ifname ? (strlen(ifname) + 1) : 1); + g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1); + + nm_utils_checksum_get_digest(sum, digest); + + memcpy(&bin_addr, digest, ETH_ALEN); + _hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask); + return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN); +} + +char * +nm_utils_hw_addr_gen_stable_eth_impl(NMUtilsStableType stable_type, + const char * stable_id, + const guint8 * host_id, + gsize host_id_len, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask) +{ + return _hw_addr_gen_stable_eth(stable_type, + stable_id, + host_id, + host_id_len, + ifname, + current_mac_address, + generate_mac_address_mask); +} + +char * +nm_utils_hw_addr_gen_stable_eth(NMUtilsStableType stable_type, + const char * stable_id, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask) +{ + const guint8 *host_id; + gsize host_id_len; + + g_return_val_if_fail(stable_id, NULL); + + nm_utils_host_id_get(&host_id, &host_id_len); + + return _hw_addr_gen_stable_eth(stable_type, + stable_id, + host_id, + host_id_len, + ifname, + current_mac_address, + generate_mac_address_mask); +} + +/*****************************************************************************/ + +GBytes * +nm_utils_dhcp_client_id_mac(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) +{ + guint8 * client_id_buf; + const guint8 hwaddr_type = arp_type; + + if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) + g_return_val_if_reached(NULL); + + client_id_buf = g_malloc(hwaddr_len + 1); + client_id_buf[0] = hwaddr_type; + memcpy(&client_id_buf[1], hwaddr, hwaddr_len); + return g_bytes_new_take(client_id_buf, hwaddr_len + 1); +} + +#define HASH_KEY \ + ((const guint8[16]){0x80, \ + 0x11, \ + 0x8c, \ + 0xc2, \ + 0xfe, \ + 0x4a, \ + 0x03, \ + 0xee, \ + 0x3e, \ + 0xd6, \ + 0x0c, \ + 0x6f, \ + 0x36, \ + 0x39, \ + 0x14, \ + 0x09}) + +/** + * nm_utils_create_dhcp_iaid: + * @legacy_unstable_byteorder: legacy behavior is to generate a u32 iaid which + * is endianness dependent. This is to preserve backward compatibility. + * For non-legacy behavior, the returned integer is in stable endianness, + * and corresponds to legacy behavior on little endian systems. + * @interface_id: the seed for hashing when generating the ID. Usually, + * this is the interface name. + * @interface_id_len: length of @interface_id + * + * This corresponds to systemd's dhcp_identifier_set_iaid() for generating + * a IAID for the interface. + * + * Returns: the IAID in host byte order. */ +guint32 +nm_utils_create_dhcp_iaid(gboolean legacy_unstable_byteorder, + const guint8 *interface_id, + gsize interface_id_len) +{ + guint64 u64; + guint32 u32; + + u64 = c_siphash_hash(HASH_KEY, interface_id, interface_id_len); + u32 = (u64 & 0xffffffffu) ^ (u64 >> 32); + if (legacy_unstable_byteorder) { + /* legacy systemd code dhcp_identifier_set_iaid() generates the iaid + * dependent on the host endianness. Since this function returns the IAID + * in native-byte order, we need to account for that. + * + * On little endian systems, we want the legacy-behavior is identical to + * the endianness-agnostic behavior. So, we need to swap the bytes on + * big-endian systems. + * + * (https://github.com/systemd/systemd/pull/10614). */ + return htole32(u32); + } else { + /* we return the value as-is, in native byte order. */ + return u32; + } +} + +GBytes * +nm_utils_dhcp_client_id_duid(guint32 iaid, const guint8 *duid, gsize duid_len) +{ + struct _nm_packed { + guint8 type; + guint32 iaid; + guint8 duid[]; + } * client_id; + gsize total_size; + + /* the @duid must include the 16 bit duid-type and the data (of max 128 bytes). */ + g_return_val_if_fail(duid_len > 2 && duid_len < 128 + 2, NULL); + g_return_val_if_fail(duid, NULL); + + total_size = sizeof(*client_id) + duid_len; + + client_id = g_malloc(total_size); + + client_id->type = 255; + unaligned_write_be32(&client_id->iaid, iaid); + memcpy(client_id->duid, duid, duid_len); + return g_bytes_new_take(client_id, total_size); +} + +/** + * nm_utils_dhcp_client_id_systemd_node_specific_full: + * @iaid: the IAID (identity association identifier) in native byte order + * @machine_id: the binary identifier for the machine. It is hashed + * into the DUID. It commonly is /etc/machine-id (parsed in binary as NMUuid). + * @machine_id_len: the length of the @machine_id. + * + * Systemd's sd_dhcp_client generates a default client ID (type 255, node-specific, + * RFC 4361) if no explicit client-id is set. This function duplicates that + * implementation and exposes it as (internal) API. + * + * Returns: a %GBytes of generated client-id. This function cannot fail. + */ +GBytes * +nm_utils_dhcp_client_id_systemd_node_specific_full(guint32 iaid, + const guint8 *machine_id, + gsize machine_id_len) +{ + const guint16 DUID_TYPE_EN = 2; + const guint32 SYSTEMD_PEN = 43793; + struct _nm_packed { + guint8 type; + guint32 iaid; + struct _nm_packed { + guint16 type; + union { + struct _nm_packed { + /* DUID_TYPE_EN */ + guint32 pen; + uint8_t id[8]; + } en; + }; + } duid; + } * client_id; + guint64 u64; + + g_return_val_if_fail(machine_id, NULL); + g_return_val_if_fail(machine_id_len > 0, NULL); + + client_id = g_malloc(sizeof(*client_id)); + + client_id->type = 255; + unaligned_write_be32(&client_id->iaid, iaid); + unaligned_write_be16(&client_id->duid.type, DUID_TYPE_EN); + unaligned_write_be32(&client_id->duid.en.pen, SYSTEMD_PEN); + + u64 = htole64(c_siphash_hash(HASH_KEY, machine_id, machine_id_len)); + memcpy(client_id->duid.en.id, &u64, sizeof(client_id->duid.en.id)); + + G_STATIC_ASSERT_EXPR(sizeof(*client_id) == 19); + return g_bytes_new_take(client_id, 19); +} + +GBytes * +nm_utils_dhcp_client_id_systemd_node_specific(guint32 iaid) +{ + return nm_utils_dhcp_client_id_systemd_node_specific_full( + iaid, + (const guint8 *) nm_utils_machine_id_bin(), + sizeof(NMUuid)); +} + +/*****************************************************************************/ + +GBytes * +nm_utils_generate_duid_llt(int arp_type, const guint8 *hwaddr, gsize hwaddr_len, gint64 time) +{ + guint8 * arr; + const guint16 duid_type = htons(1); + const guint16 hw_type = htons(arp_type); + const guint32 duid_time = htonl(NM_MAX(0, time - NM_UTILS_EPOCH_DATETIME_200001010000)); + + if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) + nm_assert_not_reached(); + + arr = g_new(guint8, (2u + 2u + 4u) + hwaddr_len); + + memcpy(&arr[0], &duid_type, 2); + memcpy(&arr[2], &hw_type, 2); + memcpy(&arr[4], &duid_time, 4); + memcpy(&arr[8], hwaddr, hwaddr_len); + + return g_bytes_new_take(arr, (2u + 2u + 4u) + hwaddr_len); +} + +GBytes * +nm_utils_generate_duid_ll(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) +{ + guint8 * arr; + const guint16 duid_type = htons(3); + const guint16 hw_type = htons(arp_type); + + if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) + nm_assert_not_reached(); + + arr = g_new(guint8, (2u + 2u) + hwaddr_len); + + memcpy(&arr[0], &duid_type, 2); + memcpy(&arr[2], &hw_type, 2); + memcpy(&arr[4], hwaddr, hwaddr_len); + + return g_bytes_new_take(arr, (2u + 2u) + hwaddr_len); +} + +GBytes * +nm_utils_generate_duid_uuid(const NMUuid *uuid) +{ + const guint16 duid_type = htons(4); + guint8 * duid_buffer; + + nm_assert(uuid); + + /* Generate a DHCP Unique Identifier for DHCPv6 using the + * DUID-UUID method (see RFC 6355 section 4). Format is: + * + * u16: type (DUID-UUID = 4) + * u8[16]: UUID bytes + */ + G_STATIC_ASSERT_EXPR(sizeof(duid_type) == 2); + G_STATIC_ASSERT_EXPR(sizeof(*uuid) == 16); + duid_buffer = g_malloc(18); + memcpy(&duid_buffer[0], &duid_type, 2); + memcpy(&duid_buffer[2], uuid, 16); + return g_bytes_new_take(duid_buffer, 18); +} + +GBytes * +nm_utils_generate_duid_from_machine_id(void) +{ + static GBytes *volatile global_duid = NULL; + GBytes *p; + +again: + p = g_atomic_pointer_get(&global_duid); + if (G_UNLIKELY(!p)) { + nm_auto_free_checksum GChecksum *sum = NULL; + const NMUuid * machine_id; + union { + guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256]; + NMUuid uuid; + } digest; + + machine_id = nm_utils_machine_id_bin(); + + /* Hash the machine ID so it's not leaked to the network. + * + * Optimally, we would choose an use case specific seed, but for historic + * reasons we didn't. */ + sum = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(sum, (const guchar *) machine_id, sizeof(*machine_id)); + nm_utils_checksum_get_digest(sum, digest.sha256); + + G_STATIC_ASSERT_EXPR(sizeof(digest.sha256) > sizeof(digest.uuid)); + p = nm_utils_generate_duid_uuid(&digest.uuid); + + if (!g_atomic_pointer_compare_and_exchange(&global_duid, NULL, p)) { + g_bytes_unref(p); + goto again; + } + } + + return g_bytes_ref(p); +} + +/*****************************************************************************/ + +/** + * nm_utils_setpgid: + * @unused: unused + * + * This can be passed as a child setup function to the g_spawn*() family + * of functions, to ensure that the child is in its own process group + * (and thus, in some situations, will not be killed when NetworkManager + * is killed). + */ +void +nm_utils_setpgid(gpointer unused G_GNUC_UNUSED) +{ + pid_t pid; + + pid = getpid(); + setpgid(pid, pid); +} + +/** + * nm_utils_g_value_set_strv: + * @value: a #GValue, initialized to store a #G_TYPE_STRV + * @strings: a #GPtrArray of strings. %NULL values are not + * allowed. + * + * Converts @strings to a #GStrv and stores it in @value. + */ +void +nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings) +{ + char **strv; + guint i; + + strv = g_new(char *, strings->len + 1); + for (i = 0; i < strings->len; i++) { + nm_assert(strings->pdata[i]); + strv[i] = g_strdup(strings->pdata[i]); + } + strv[i] = NULL; + + g_value_take_boxed(value, strv); +} + +/*****************************************************************************/ + +/** + * Takes a pair @timestamp and @duration, and returns the remaining duration based + * on the new timestamp @now. + */ +guint32 +nm_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now) +{ + gint64 t; + + nm_assert(now >= 0); + + if (duration == NM_PLATFORM_LIFETIME_PERMANENT) + return NM_PLATFORM_LIFETIME_PERMANENT; + + if (timestamp == 0) { + /* if the @timestamp is zero, assume it was just left unset and that the relative + * @duration starts counting from @now. This is convenient to construct an address + * and print it in nm_platform_ip4_address_to_string(). + * + * In general it does not make sense to set the @duration without anchoring at + * @timestamp because you don't know the absolute expiration time when looking + * at the address at a later moment. */ + timestamp = now; + } + + /* For timestamp > now, just accept it and calculate the expected(?) result. */ + t = (gint64) timestamp + (gint64) duration - (gint64) now; + + if (t <= 0) + return 0; + if (t >= NM_PLATFORM_LIFETIME_PERMANENT) + return NM_PLATFORM_LIFETIME_PERMANENT - 1; + return t; +} + +guint32 +nm_utils_lifetime_get(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + gint32 now, + guint32 *out_preferred) +{ + guint32 t_lifetime, t_preferred; + + nm_assert(now >= 0); + + if (timestamp == 0 && lifetime == 0) { + /* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy + * creation of such addresses (without requiring to set the lifetime fields to + * NM_PLATFORM_LIFETIME_PERMANENT). The real lifetime==0 addresses (E.g. DHCP6 telling us + * to drop an address will have timestamp set. + */ + NM_SET_OUT(out_preferred, NM_PLATFORM_LIFETIME_PERMANENT); + g_return_val_if_fail(preferred == 0, NM_PLATFORM_LIFETIME_PERMANENT); + return NM_PLATFORM_LIFETIME_PERMANENT; + } + + if (now <= 0) + now = nm_utils_get_monotonic_timestamp_sec(); + + t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now); + if (!t_lifetime) { + NM_SET_OUT(out_preferred, 0); + return 0; + } + + t_preferred = nm_utils_lifetime_rebase_relative_time_on_now(timestamp, preferred, now); + + NM_SET_OUT(out_preferred, MIN(t_preferred, t_lifetime)); + + /* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now() + * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always + * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew. + */ + g_return_val_if_fail(timestamp != 0 + || (lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && preferred == NM_PLATFORM_LIFETIME_PERMANENT), + t_lifetime); + g_return_val_if_fail(t_preferred <= t_lifetime, t_lifetime); + + return t_lifetime; +} + +const char * +nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size) +{ + const char *msg; + + nm_utils_to_string_buffer_init(&dest, &size); + + if (status == 0) + msg = "Success"; + else if (status == 1) + msg = "Configuration problem"; + else if (status == 2) + msg = "Network access problem (address in use, permissions)"; + else if (status == 3) + msg = "Filesystem problem (missing file/directory, permissions)"; + else if (status == 4) + msg = "Memory allocation failure"; + else if (status == 5) + msg = "Other problem"; + else if (status >= 11) { + g_snprintf(dest, size, "Lease script failed with error %d", status - 10); + return dest; + } else + msg = "Unknown problem"; + + g_snprintf(dest, size, "%s (%d)", msg, status); + return dest; +} + +/** + * nm_utils_get_reverse_dns_domains_ip_4: + * @addr: IP address in network order + * @plen: prefix length + * @domains: array for results + * + * Creates reverse DNS domains for the given address and prefix length, and + * append them to @domains. + */ +void +nm_utils_get_reverse_dns_domains_ip_4(guint32 addr, guint8 plen, GPtrArray *domains) +{ + guint32 ip, ip2, mask; + guchar *p; + guint octets; + guint i; + gsize len0, len; + char * str, *s; + + g_return_if_fail(domains); + g_return_if_fail(plen <= 32); + + if (!plen) + return; + + octets = (plen - 1) / 8 + 1; + ip = ntohl(addr); + mask = 0xFFFFFFFF << (32 - plen); + ip &= mask; + ip2 = ip; + + len0 = NM_STRLEN("in-addr.arpa") + (4 * octets) + 1; + while ((ip2 & mask) == ip) { + addr = htonl(ip2); + p = (guchar *) &addr; + + len = len0; + str = s = g_malloc(len); + for (i = octets; i > 0; i--) + nm_utils_strbuf_append(&s, &len, "%u.", p[i - 1] & 0xff); + nm_utils_strbuf_append_str(&s, &len, "in-addr.arpa"); + + g_ptr_array_add(domains, str); + + ip2 += 1 << ((32 - plen) & ~7); + } +} + +/** + * nm_utils_get_reverse_dns_domains_ip_6: + * @addr: IPv6 address + * @plen: prefix length + * @domains: array for results + * + * Creates reverse DNS domains for the given address and prefix length, and + * append them to @domains. + */ +void +nm_utils_get_reverse_dns_domains_ip_6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains) +{ + struct in6_addr addr; + guint nibbles, bits, entries; + int i, j; + gsize len0, len; + char * str, *s; + + g_return_if_fail(domains); + g_return_if_fail(plen <= 128); + + if (!plen) + return; + + memcpy(&addr, ip, sizeof(struct in6_addr)); + nm_utils_ip6_address_clear_host_address(&addr, NULL, plen); + + /* Number of nibbles to include in domains */ + nibbles = (plen - 1) / 4 + 1; + /* Prefix length in nibble */ + bits = plen - ((plen - 1) / 4 * 4); + /* Number of domains */ + entries = 1 << (4 - bits); + + len0 = NM_STRLEN("ip6.arpa") + (2 * nibbles) + 1; + +#define N_SHIFT(x) ((x) % 2 ? 0 : 4) + + for (i = 0; i < entries; i++) { + len = len0; + str = s = g_malloc(len); + + for (j = nibbles - 1; j >= 0; j--) + nm_utils_strbuf_append(&s, &len, "%x.", (addr.s6_addr[j / 2] >> N_SHIFT(j)) & 0xf); + nm_utils_strbuf_append_str(&s, &len, "ip6.arpa"); + + g_ptr_array_add(domains, str); + + addr.s6_addr[(nibbles - 1) / 2] += 1 << N_SHIFT(nibbles - 1); + } + +#undef N_SHIFT +} + +struct plugin_info { + char * path; + struct stat st; +}; + +static int +read_device_factory_paths_sort_fcn(gconstpointer a, gconstpointer b) +{ + const struct plugin_info *da = a; + const struct plugin_info *db = b; + time_t ta, tb; + + ta = MAX(da->st.st_mtime, da->st.st_ctime); + tb = MAX(db->st.st_mtime, db->st.st_ctime); + + if (ta < tb) + return 1; + if (ta > tb) + return -1; + return 0; +} + +gboolean +nm_utils_validate_plugin(const char *path, struct stat *st, GError **error) +{ + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(st, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (!S_ISREG(st->st_mode)) { + g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "not a regular file"); + return FALSE; + } + + if (st->st_uid != 0) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "file has invalid owner (should be root)"); + return FALSE; + } + + if (st->st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "file has invalid permissions"); + return FALSE; + } + + return TRUE; +} + +char ** +nm_utils_read_plugin_paths(const char *dirname, const char *prefix) +{ + GDir * dir; + GError * error = NULL; + const char *item; + GArray * paths; + char ** result; + guint i; + + g_return_val_if_fail(dirname, NULL); + g_return_val_if_fail(prefix, NULL); + + dir = g_dir_open(dirname, 0, &error); + if (!dir) { + nm_log_warn(LOGD_CORE, + "device plugin: failed to open directory %s: %s", + dirname, + error->message); + g_clear_error(&error); + return NULL; + } + + paths = g_array_new(FALSE, FALSE, sizeof(struct plugin_info)); + + while ((item = g_dir_read_name(dir))) { + int errsv; + struct plugin_info data; + + if (!g_str_has_prefix(item, prefix)) + continue; + if (!g_str_has_suffix(item, ".so")) + continue; + + data.path = g_build_filename(dirname, item, NULL); + + if (stat(data.path, &data.st) != 0) { + errsv = errno; + nm_log_warn(LOGD_CORE, + "plugin: skip invalid file %s (error during stat: %s)", + data.path, + nm_strerror_native(errsv)); + goto skip; + } + + if (!nm_utils_validate_plugin(data.path, &data.st, &error)) { + nm_log_warn(LOGD_CORE, "plugin: skip invalid file %s: %s", data.path, error->message); + g_clear_error(&error); + goto skip; + } + + g_array_append_val(paths, data); + continue; +skip: + g_free(data.path); + } + g_dir_close(dir); + + /* sort filenames by modification time. */ + g_array_sort(paths, read_device_factory_paths_sort_fcn); + + result = g_new(char *, paths->len + 1); + for (i = 0; i < paths->len; i++) + result[i] = g_array_index(paths, struct plugin_info, i).path; + result[i] = NULL; + + g_array_free(paths, TRUE); + return result; +} + +char * +nm_utils_format_con_diff_for_audit(GHashTable *diff) +{ + GHashTable * setting_diff; + char * setting_name, *prop_name; + GHashTableIter iter, iter2; + GString * str; + + str = g_string_sized_new(32); + g_hash_table_iter_init(&iter, diff); + + while (g_hash_table_iter_next(&iter, (gpointer *) &setting_name, (gpointer *) &setting_diff)) { + if (!setting_diff) + continue; + + g_hash_table_iter_init(&iter2, setting_diff); + + while (g_hash_table_iter_next(&iter2, (gpointer *) &prop_name, NULL)) + g_string_append_printf(str, "%s.%s,", setting_name, prop_name); + } + + if (str->len) + str->str[str->len - 1] = '\0'; + + return g_string_free(str, FALSE); +} + +const char * +nm_utils_parse_dns_domain(const char *domain, gboolean *is_routing) +{ + g_return_val_if_fail(domain, NULL); + g_return_val_if_fail(domain[0], NULL); + + if (domain[0] == '~') { + domain++; + NM_SET_OUT(is_routing, TRUE); + } else + NM_SET_OUT(is_routing, FALSE); + + return domain; +} + +/*****************************************************************************/ + +static guint32 +get_max_rate_ht_20(int mcs) +{ + switch (mcs) { + case 0: + return 6500000; + case 1: + case 8: + return 13000000; + case 2: + case 16: + return 19500000; + case 3: + case 9: + case 24: + return 26000000; + case 4: + case 10: + case 17: + return 39000000; + case 5: + case 11: + case 25: + return 52000000; + case 6: + case 18: + return 58500000; + case 7: + return 65000000; + case 12: + case 19: + case 26: + return 78000000; + case 13: + case 27: + return 104000000; + case 14: + case 20: + return 117000000; + case 15: + return 130000000; + case 21: + case 28: + return 156000000; + case 22: + return 175500000; + case 23: + return 195000000; + case 29: + return 208000000; + case 30: + return 234000000; + case 31: + return 260000000; + } + return 0; +} + +static guint32 +get_max_rate_ht_40(int mcs) +{ + switch (mcs) { + case 0: + return 13500000; + case 1: + case 8: + return 27000000; + case 2: + return 40500000; + case 3: + case 9: + case 24: + return 54000000; + case 4: + case 10: + case 17: + return 81000000; + case 5: + case 11: + case 25: + return 108000000; + case 6: + case 18: + return 121500000; + case 7: + return 135000000; + case 12: + case 19: + case 26: + return 162000000; + case 13: + case 27: + return 216000000; + case 14: + case 20: + return 243000000; + case 15: + return 270000000; + case 16: + return 40500000; + case 21: + case 28: + return 324000000; + case 22: + return 364500000; + case 23: + return 405000000; + case 29: + return 432000000; + case 30: + return 486000000; + case 31: + return 540000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_80_ss1(int mcs) +{ + switch (mcs) { + case 0: + return 29300000; + case 1: + return 58500000; + case 2: + return 87800000; + case 3: + return 117000000; + case 4: + return 175500000; + case 5: + return 234000000; + case 6: + return 263300000; + case 7: + return 292500000; + case 8: + return 351000000; + case 9: + return 390000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_80_ss2(int mcs) +{ + switch (mcs) { + case 0: + return 58500000; + case 1: + return 117000000; + case 2: + return 175500000; + case 3: + return 234000000; + case 4: + return 351000000; + case 5: + return 468000000; + case 6: + return 526500000; + case 7: + return 585000000; + case 8: + return 702000000; + case 9: + return 780000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_80_ss3(int mcs) +{ + switch (mcs) { + case 0: + return 87800000; + case 1: + return 175500000; + case 2: + return 263300000; + case 3: + return 351000000; + case 4: + return 526500000; + case 5: + return 702000000; + case 6: + return 0; + case 7: + return 877500000; + case 8: + return 105300000; + case 9: + return 117000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_160_ss1(int mcs) +{ + switch (mcs) { + case 0: + return 58500000; + case 1: + return 117000000; + case 2: + return 175500000; + case 3: + return 234000000; + case 4: + return 351000000; + case 5: + return 468000000; + case 6: + return 526500000; + case 7: + return 585000000; + case 8: + return 702000000; + case 9: + return 780000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_160_ss2(int mcs) +{ + switch (mcs) { + case 0: + return 117000000; + case 1: + return 234000000; + case 2: + return 351000000; + case 3: + return 468000000; + case 4: + return 702000000; + case 5: + return 936000000; + case 6: + return 1053000000; + case 7: + return 1170000000; + case 8: + return 1404000000; + case 9: + return 1560000000; + } + return 0; +} + +static guint32 +get_max_rate_vht_160_ss3(int mcs) +{ + switch (mcs) { + case 0: + return 175500000; + case 1: + return 351000000; + case 2: + return 526500000; + case 3: + return 702000000; + case 4: + return 1053000000; + case 5: + return 1404000000; + case 6: + return 1579500000; + case 7: + return 1755000000; + case 8: + return 2106000000; + case 9: + return 0; + } + return 0; +} + +static gboolean +get_max_rate_ht(const guint8 *bytes, guint len, guint32 *out_maxrate) +{ + guint32 i; + guint8 ht_cap_info; + const guint8 *supported_mcs_set; + guint32 rate; + + /* http://standards.ieee.org/getieee802/download/802.11-2012.pdf + * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/ + */ + + if (len != 26) + return FALSE; + + ht_cap_info = bytes[0]; + supported_mcs_set = &bytes[3]; + *out_maxrate = 0; + + /* Find the maximum supported mcs rate */ + for (i = 0; i <= 76; i++) { + const unsigned mcs_octet = i / 8; + const unsigned MCS_RATE_BIT = 1 << i % 8; + + if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) { + /* Check for 40Mhz wide channel support */ + if (ht_cap_info & (1 << 1)) + rate = get_max_rate_ht_40(i); + else + rate = get_max_rate_ht_20(i); + + if (rate > *out_maxrate) + *out_maxrate = rate; + } + } + + return TRUE; +} + +static gboolean +get_max_rate_vht(const guint8 *bytes, guint len, guint32 *out_maxrate) +{ + guint32 mcs, m; + guint8 vht_cap, tx_map; + + /* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html + * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */ + + if (len != 12) + return FALSE; + + vht_cap = bytes[0]; + tx_map = bytes[8]; + + /* Check for mcs rates 8 and 9 support */ + if (tx_map & 0x2a) + mcs = 9; + else if (tx_map & 0x15) + mcs = 8; + else + mcs = 7; + + /* Check for 160Mhz wide channel support and + * spatial stream support */ + if (vht_cap & (1 << 2)) { + if (tx_map & 0x30) + m = get_max_rate_vht_160_ss3(mcs); + else if (tx_map & 0x0C) + m = get_max_rate_vht_160_ss2(mcs); + else + m = get_max_rate_vht_160_ss1(mcs); + } else { + if (tx_map & 0x30) + m = get_max_rate_vht_80_ss3(mcs); + else if (tx_map & 0x0C) + m = get_max_rate_vht_80_ss2(mcs); + else + m = get_max_rate_vht_80_ss1(mcs); + } + + *out_maxrate = m; + return TRUE; +} + +/* Management Frame Information Element IDs, ieee80211_eid */ +#define WLAN_EID_HT_CAPABILITY 45 +#define WLAN_EID_VHT_CAPABILITY 191 +#define WLAN_EID_VENDOR_SPECIFIC 221 + +void +nm_wifi_utils_parse_ies(const guint8 *bytes, + gsize len, + guint32 * out_max_rate, + gboolean * out_metered, + gboolean * out_owe_transition_mode) +{ + guint8 id, elem_len; + guint32 m; + + NM_SET_OUT(out_max_rate, 0); + NM_SET_OUT(out_metered, FALSE); + NM_SET_OUT(out_owe_transition_mode, FALSE); + + while (len) { + if (len < 2) + break; + + id = *bytes++; + elem_len = *bytes++; + len -= 2; + + if (elem_len > len) + break; + + switch (id) { + case WLAN_EID_HT_CAPABILITY: + if (out_max_rate) { + if (get_max_rate_ht(bytes, elem_len, &m)) + *out_max_rate = NM_MAX(*out_max_rate, m); + } + break; + case WLAN_EID_VHT_CAPABILITY: + if (out_max_rate) { + if (get_max_rate_vht(bytes, elem_len, &m)) + *out_max_rate = NM_MAX(*out_max_rate, m); + } + break; + case WLAN_EID_VENDOR_SPECIFIC: + if (len == 8 && bytes[0] == 0x00 /* OUI: Microsoft */ + && bytes[1] == 0x50 && bytes[2] == 0xf2 + && bytes[3] == 0x11) /* OUI type: Network cost */ + NM_SET_OUT(out_metered, (bytes[7] > 1)); /* Cost level > 1 */ + if (elem_len >= 10 && bytes[0] == 0x50 /* OUI: WiFi Alliance */ + && bytes[1] == 0x6f && bytes[2] == 0x9a + && bytes[3] == 0x1c) /* OUI type: OWE Transition Mode */ + NM_SET_OUT(out_owe_transition_mode, TRUE); + break; + } + + len -= elem_len; + bytes += elem_len; + } +} + +/*****************************************************************************/ + +guint8 +nm_wifi_utils_level_to_quality(int val) +{ + if (val < 0) { + /* Assume dBm already; rough conversion: best = -40, worst = -100 */ + val = abs(CLAMP(val, -100, -40) + 40); /* normalize to 0 */ + val = 100 - (int) ((100.0 * (double) val) / 60.0); + } else if (val > 110 && val < 256) { + /* assume old-style WEXT 8-bit unsigned signal level */ + val -= 256; /* subtract 256 to convert to dBm */ + val = abs(CLAMP(val, -100, -40) + 40); /* normalize to 0 */ + val = 100 - (int) ((100.0 * (double) val) / 60.0); + } else { + /* Assume signal is a "quality" percentage */ + } + + return CLAMP(val, 0, 100); +} + +/*****************************************************************************/ + +NM_UTILS_ENUM2STR_DEFINE(nm_icmpv6_router_pref_to_string, + NMIcmpv6RouterPref, + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_LOW, "low"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_MEDIUM, "medium"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_HIGH, "high"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"), ); + +NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string, + NMActivationType, + NM_UTILS_LOOKUP_DEFAULT_WARN("(unknown)"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_MANAGED, "managed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"), + NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_EXTERNAL, "external"), ); diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h new file mode 100644 index 0000000..a3f4787 --- /dev/null +++ b/src/core/nm-core-utils.h @@ -0,0 +1,597 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2016 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#ifndef __NM_CORE_UTILS_H__ +#define __NM_CORE_UTILS_H__ + +#include +#include + +#include "nm-connection.h" + +#include "nm-glib-aux/nm-time-utils.h" + +/*****************************************************************************/ + +#define NM_PLATFORM_LIFETIME_PERMANENT G_MAXUINT32 + +#define NM_DEFINE_SINGLETON_INSTANCE(TYPE) static TYPE *singleton_instance + +#define NM_DEFINE_SINGLETON_REGISTER(TYPE) \ + NM_DEFINE_SINGLETON_INSTANCE(TYPE); \ + static void _singleton_instance_weak_ref_cb(gpointer data, GObject *where_the_object_was) \ + { \ + nm_log_dbg(LOGD_CORE, \ + "disposing %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", \ + G_STRINGIFY(TYPE), \ + NM_HASH_OBFUSCATE_PTR(singleton_instance)); \ + singleton_instance = NULL; \ + } \ + static inline void nm_singleton_instance_register(void) \ + { \ + g_object_weak_ref(G_OBJECT(singleton_instance), _singleton_instance_weak_ref_cb, NULL); \ + _nm_singleton_instance_register_destruction(G_OBJECT(singleton_instance)); \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +void _nm_singleton_instance_register_destruction(GObject *instance); + +/* By default, the getter will assert that the singleton will be created only once. You can + * change this by redefining NM_DEFINE_SINGLETON_ALLOW_MULTIPLE. */ +#ifndef NM_DEFINE_SINGLETON_ALLOW_MULTIPLE + #define NM_DEFINE_SINGLETON_ALLOW_MULTIPLE FALSE +#endif + +#define NM_DEFINE_SINGLETON_GETTER(TYPE, GETTER, GTYPE, ...) \ + NM_DEFINE_SINGLETON_INSTANCE(TYPE); \ + NM_DEFINE_SINGLETON_REGISTER(TYPE); \ + static char _already_created_##GETTER = FALSE; \ + TYPE * GETTER(void) \ + { \ + if (G_UNLIKELY(!singleton_instance)) { \ + g_assert(!(_already_created_##GETTER) || (NM_DEFINE_SINGLETON_ALLOW_MULTIPLE)); \ + (_already_created_##GETTER) = TRUE; \ + singleton_instance = (g_object_new(GTYPE, ##__VA_ARGS__, NULL)); \ + g_assert(singleton_instance); \ + nm_singleton_instance_register(); \ + nm_log_dbg(LOGD_CORE, \ + "create %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", \ + G_STRINGIFY(TYPE), \ + NM_HASH_OBFUSCATE_PTR(singleton_instance)); \ + } \ + return singleton_instance; \ + } \ + _nm_unused static void _nmtst_##GETTER##_reset(TYPE *instance) \ + { \ + /* usually, the singleton can only be created once (and further instantiations + * are guarded by an assert). For testing, we need to reset the singleton to + * allow multiple instantiations. */ \ + g_assert(G_IS_OBJECT(instance)); \ + g_assert(instance == singleton_instance); \ + g_assert(_already_created_##GETTER); \ + g_object_unref(instance); \ + \ + /* require that the last unref also destroyed the singleton. If this fails, + * somebody still keeps a reference. Fix your test! */ \ + g_assert(!singleton_instance); \ + _already_created_##GETTER = FALSE; \ + } + +/* attach @instance to the data or @owner. @owner owns a reference + * to @instance thus the lifetime of @instance is at least as long + * as that of @owner. Use this when @owner depends on @instance. */ +#define NM_UTILS_KEEP_ALIVE(owner, instance, unique_token) \ + G_STMT_START \ + { \ + g_object_set_data_full(G_OBJECT(owner), \ + ".nm-utils-keep-alive-" unique_token "", \ + g_object_ref(instance), \ + g_object_unref); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +gboolean nm_ether_addr_is_valid(const NMEtherAddr *addr); +gboolean nm_ether_addr_is_valid_str(const char *str); + +gconstpointer +nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen); +in_addr_t nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen); +const struct in6_addr *nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, + const struct in6_addr *src, + guint8 plen); + +static inline int +nm_utils_ip4_address_same_prefix_cmp(in_addr_t addr_a, in_addr_t addr_b, guint8 plen) +{ + NM_CMP_DIRECT(htonl(nm_utils_ip4_address_clear_host_address(addr_a, plen)), + htonl(nm_utils_ip4_address_clear_host_address(addr_b, plen))); + return 0; +} + +int nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen); + +static inline int +nm_utils_ip_address_same_prefix_cmp(int addr_family, + gconstpointer addr_a, + gconstpointer addr_b, + guint8 plen) +{ + nm_assert_addr_family(addr_family); + + NM_CMP_SELF(addr_a, addr_b); + + if (NM_IS_IPv4(addr_family)) { + return nm_utils_ip4_address_same_prefix_cmp(*((const in_addr_t *) addr_a), + *((const in_addr_t *) addr_b), + plen); + } + + return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen); +} + +static inline gboolean +nm_utils_ip4_address_same_prefix(in_addr_t addr_a, in_addr_t addr_b, guint8 plen) +{ + return nm_utils_ip4_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline gboolean +nm_utils_ip6_address_same_prefix(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen) +{ + return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline gboolean +nm_utils_ip_address_same_prefix(int addr_family, + gconstpointer addr_a, + gconstpointer addr_b, + guint8 plen) +{ + return nm_utils_ip_address_same_prefix_cmp(addr_family, addr_a, addr_b, plen) == 0; +} + +#define NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_utils_ip4_address_same_prefix_cmp((a), (b), (plen))) + +#define NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_utils_ip6_address_same_prefix_cmp((a), (b), (plen))) + +static inline void +nm_hash_update_in6addr(NMHashState *h, const struct in6_addr *addr) +{ + nm_assert(addr); + + nm_hash_update(h, addr, sizeof(*addr)); +} + +static inline void +nm_hash_update_in6addr_prefix(NMHashState *h, const struct in6_addr *addr, guint8 plen) +{ + struct in6_addr a; + + nm_assert(addr); + + nm_utils_ip6_address_clear_host_address(&a, addr, plen); + /* we don't hash plen itself. The caller may want to do that.*/ + nm_hash_update_in6addr(h, &a); +} + +double nm_utils_exp10(gint16 e); + +/** + * nm_utils_ip6_route_metric_normalize: + * @metric: the route metric + * + * For IPv6 route, when adding a route via netlink, kernel treats the value 0 as IP6_RT_PRIO_USER (1024). + * So, user space cannot add routes with such a metric, and 0 gets "normalized" + * to NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6. + * + * Note that kernel itself can add IPv6 routes with metric zero. Also, you can delete + * them, but mostly because with `ip -6 route delete ... metric 0` the 0 acts as a wildcard + * and kills the first matching route. + * + * Returns: @metric, if @metric is not zero, otherwise 1024. + */ +static inline guint32 +nm_utils_ip6_route_metric_normalize(guint32 metric) +{ + return metric ?: 1024 /*NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6*/; +} + +static inline guint32 +nm_utils_ip_route_metric_normalize(int addr_family, guint32 metric) +{ + return NM_IS_IPv4(addr_family) ? metric : nm_utils_ip6_route_metric_normalize(metric); +} + +static inline guint32 +nm_utils_ip_route_metric_penalize(guint32 metric, guint32 penalty) +{ + if (metric < G_MAXUINT32 - penalty) + return metric + penalty; + return G_MAXUINT32; +} + +int nm_utils_modprobe(GError **error, gboolean suppress_error_loggin, const char *arg1, ...) + G_GNUC_NULL_TERMINATED; + +void nm_utils_kill_process_sync(pid_t pid, + guint64 start_time, + int sig, + guint64 log_domain, + const char *log_name, + guint32 wait_before_kill_msec, + guint32 sleep_duration_msec, + guint32 max_wait_msec); + +typedef void (*NMUtilsKillChildAsyncCb)(pid_t pid, + gboolean success, + int child_status, + void * user_data); +void nm_utils_kill_child_async(pid_t pid, + int sig, + guint64 log_domain, + const char * log_name, + guint32 wait_before_kill_msec, + NMUtilsKillChildAsyncCb callback, + void * user_data); +gboolean nm_utils_kill_child_sync(pid_t pid, + int sig, + guint64 log_domain, + const char *log_name, + int * child_status, + guint32 wait_before_kill_msec, + guint32 sleep_duration_msec); + +const char *nm_utils_find_helper(const char *progname, const char *try_first, GError **error); + +char *nm_utils_read_link_absolute(const char *link_file, GError **error); + +#define NM_MATCH_SPEC_MAC_TAG "mac:" +#define NM_MATCH_SPEC_S390_SUBCHANNELS_TAG "s390-subchannels:" +#define NM_MATCH_SPEC_INTERFACE_NAME_TAG "interface-name:" + +typedef enum { + NM_MATCH_SPEC_NO_MATCH = 0, + NM_MATCH_SPEC_MATCH = 1, + NM_MATCH_SPEC_NEG_MATCH = 2, +} NMMatchSpecMatchType; + +NMMatchSpecMatchType nm_match_spec_device(const GSList *specs, + const char * interface_name, + const char * device_type, + const char * driver, + const char * driver_version, + const char * hwaddr, + const char * s390_subchannels, + const char * dhcp_plugin); +NMMatchSpecMatchType nm_match_spec_config(const GSList *specs, guint nm_version, const char *env); +GSList * nm_match_spec_split(const char *value); +char * nm_match_spec_join(GSList *specs); + +gboolean nm_wildcard_match_check(const char *str, const char *const *patterns, guint num_patterns); + +gboolean nm_utils_kernel_cmdline_match_check(const char *const *proc_cmdline, + const char *const *patterns, + guint num_patterns, + GError ** error); + +/*****************************************************************************/ + +gboolean nm_utils_connection_has_default_route(NMConnection *connection, + int addr_family, + gboolean * out_is_never_default); + +char * nm_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id); +const char *nm_utils_new_infiniband_name(char *name, const char *parent_name, int p_key); + +int nm_utils_cmp_connection_by_autoconnect_priority(NMConnection *a, NMConnection *b); + +void nm_utils_log_connection_diff(NMConnection *connection, + NMConnection *diff_base, + guint32 level, + guint64 domain, + const char * name, + const char * prefix, + const char * dbus_path); + +gboolean nm_utils_is_valid_path_component(const char *name); +const char *NM_ASSERT_VALID_PATH_COMPONENT(const char *name); + +#define NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE 100 + +const char * +nm_utils_sysctl_ip_conf_path(int addr_family, char *buf, const char *ifname, const char *property); + +gboolean nm_utils_sysctl_ip_conf_is_path(int addr_family, + const char *path, + const char *ifname, + const char *property); + +gboolean nm_utils_is_specific_hostname(const char *name); + +struct _NMUuid; + +const char * nm_utils_machine_id_str(void); +const struct _NMUuid *nm_utils_machine_id_bin(void); +gboolean nm_utils_machine_id_is_fake(void); + +const char * nm_utils_boot_id_str(void); +const struct _NMUuid *nm_utils_boot_id_bin(void); +const char * nm_utils_proc_cmdline(void); +const char *const * nm_utils_proc_cmdline_split(void); + +gboolean nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len); +gint64 nm_utils_host_id_get_timestamp_ns(void); + +/*****************************************************************************/ + +int nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len); + +gboolean nm_utils_arp_type_validate_hwaddr(int arp_type, const guint8 *hwaddr, gsize hwaddr_len); + +gboolean +nm_utils_arp_type_get_hwaddr_relevant_part(int arp_type, const guint8 **hwaddr, gsize *hwaddr_len); + +/*****************************************************************************/ + +/* IPv6 Interface Identifier helpers */ + +/** + * NMUtilsIPv6IfaceId: + * @id: convenience member for validity checking; never use directly + * @id_u8: the 64-bit Interface Identifier + * + * Holds a 64-bit IPv6 Interface Identifier. The IID is a sequence of bytes + * and should not normally be treated as a %guint64, but this is done for + * convenience of validity checking and initialization. + */ +struct _NMUtilsIPv6IfaceId { + union { + guint64 id; + guint8 id_u8[8]; + }; +}; + +#define NM_UTILS_IPV6_IFACE_ID_INIT \ + { \ + { \ + .id = 0 \ + } \ + } + +void nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr * addr, + const NMUtilsIPv6IfaceId iid); + +void nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId * iid, + const struct in6_addr *addr); + +gboolean nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, + const char * token); + +const char *nm_utils_inet6_interface_identifier_to_token(NMUtilsIPv6IfaceId iid, + char buf[static INET6_ADDRSTRLEN]); + +gboolean nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, + const guint8 * hwaddr, + guint len, + guint dev_id, + NMUtilsIPv6IfaceId *out_iid); + +typedef enum { + /* The stable type. Note that this value is encoded in the + * generated addresses, thus the numbers MUST not change. + * + * Also note, if we ever allocate ID 255, we must take care + * that nm_utils_ipv6_addr_set_stable_privacy() extends the + * uint8 encoding of this value. */ + NM_UTILS_STABLE_TYPE_UUID = 0, + NM_UTILS_STABLE_TYPE_STABLE_ID = 1, + NM_UTILS_STABLE_TYPE_GENERATED = 2, + NM_UTILS_STABLE_TYPE_RANDOM = 3, +} NMUtilsStableType; + +NMUtilsStableType nm_utils_stable_id_parse(const char *stable_id, + const char *deviceid, + const char *hwaddr, + const char *bootid, + const char *uuid, + char ** out_generated); + +char *nm_utils_stable_id_random(void); +char *nm_utils_stable_id_generated_complete(const char *msg); + +gboolean nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type, + struct in6_addr * addr, + const char * ifname, + const char * network_id, + guint32 dad_counter, + guint8 * host_id, + gsize host_id_len, + GError ** error); + +gboolean nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType id_type, + struct in6_addr * addr, + const char * ifname, + const char * network_id, + guint32 dad_counter, + GError ** error); + +char *nm_utils_hw_addr_gen_random_eth(const char *current_mac_address, + const char *generate_mac_address_mask); +char *nm_utils_hw_addr_gen_stable_eth_impl(NMUtilsStableType stable_type, + const char * stable_id, + const guint8 * host_id, + gsize host_id_len, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask); +char *nm_utils_hw_addr_gen_stable_eth(NMUtilsStableType stable_type, + const char * stable_id, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask); + +/*****************************************************************************/ + +GBytes *nm_utils_dhcp_client_id_mac(int arp_type, const guint8 *hwaddr, gsize hwaddr_len); + +guint32 nm_utils_create_dhcp_iaid(gboolean legacy_unstable_byteorder, + const guint8 *interface_id, + gsize interface_id_len); + +GBytes *nm_utils_dhcp_client_id_duid(guint32 iaid, const guint8 *duid, gsize duid_len); + +GBytes *nm_utils_dhcp_client_id_systemd_node_specific_full(guint32 iaid, + const guint8 *machine_id, + gsize machine_id_len); + +GBytes *nm_utils_dhcp_client_id_systemd_node_specific(guint32 iaid); + +/*****************************************************************************/ + +/* RFC 3315 defines the epoch for the DUID-LLT time field on Jan 1st 2000. */ +#define NM_UTILS_EPOCH_DATETIME_200001010000 946684800 + +struct _NMUuid; + +GBytes * +nm_utils_generate_duid_llt(int arp_type, const guint8 *hwaddr, gsize hwaddr_len, gint64 time); + +GBytes *nm_utils_generate_duid_ll(int arp_type, const guint8 *hwaddr, gsize hwaddr_len); + +GBytes *nm_utils_generate_duid_uuid(const struct _NMUuid *uuid); + +GBytes *nm_utils_generate_duid_from_machine_id(void); + +/*****************************************************************************/ + +void nm_utils_array_remove_at_indexes(GArray *array, const guint *indexes_to_delete, gsize len); + +void nm_utils_setpgid(gpointer unused); + +typedef enum { + NM_UTILS_TEST_NONE = 0, + + /* Internal flag, marking that either nm_utils_get_testing() or _nm_utils_set_testing() was called. */ + _NM_UTILS_TEST_INITIALIZED = (1LL << 0), + + /* Indicate that test mode is enabled in general. Explicitly calling _nm_utils_set_testing() will always set this flag. */ + _NM_UTILS_TEST_GENERAL = (1LL << 1), + + /* Don't check the owner of keyfiles during testing. */ + NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK = (1LL << 2), + + _NM_UTILS_TEST_LAST, + NM_UTILS_TEST_ALL = (((_NM_UTILS_TEST_LAST - 1) << 1) - 1) & ~(_NM_UTILS_TEST_INITIALIZED), +} NMUtilsTestFlags; + +gboolean nm_utils_get_testing_initialized(void); +NMUtilsTestFlags nm_utils_get_testing(void); +void _nm_utils_set_testing(NMUtilsTestFlags flags); + +void nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings); + +guint32 +nm_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now); + +guint32 nm_utils_lifetime_get(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + gint32 now, + guint32 *out_preferred); + +/*****************************************************************************/ + +#define NM_IPV4LL_NETWORK ((in_addr_t)(htonl(0xA9FE0000lu))) +#define NM_IPV4LL_NETMASK ((in_addr_t)(htonl(0xFFFF0000lu))) + +static inline gboolean +nm_utils_ip4_address_is_link_local(in_addr_t addr) +{ + return (addr & NM_IPV4LL_NETMASK) == NM_IPV4LL_NETWORK; +} + +static inline gboolean +nm_utils_ip4_address_is_zeronet(in_addr_t network) +{ + /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ + return (network & htonl(0xFF000000u)) == htonl(0x00000000u); +} + +/*****************************************************************************/ + +const char *nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size); + +void nm_utils_get_reverse_dns_domains_ip_4(guint32 ip, guint8 plen, GPtrArray *domains); +void +nm_utils_get_reverse_dns_domains_ip_6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains); + +static inline void +nm_utils_get_reverse_dns_domains_ip(int addr_family, + gconstpointer addr, + guint8 plen, + GPtrArray * domains) +{ + if (NM_IS_IPv4(addr_family)) + nm_utils_get_reverse_dns_domains_ip_4(*((const in_addr_t *) addr), plen, domains); + else + nm_utils_get_reverse_dns_domains_ip_6(addr, plen, domains); +} + +struct stat; + +gboolean nm_utils_validate_plugin(const char *path, struct stat *stat, GError **error); +char ** nm_utils_read_plugin_paths(const char *dirname, const char *prefix); +char * nm_utils_format_con_diff_for_audit(GHashTable *diff); + +/*****************************************************************************/ + +/* this enum is compatible with ICMPV6_ROUTER_PREF_* (from , + * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference" + * from . */ +typedef enum _nm_packed { + NM_ICMPV6_ROUTER_PREF_MEDIUM = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */ + NM_ICMPV6_ROUTER_PREF_LOW = 0x3, /* ICMPV6_ROUTER_PREF_LOW */ + NM_ICMPV6_ROUTER_PREF_HIGH = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */ + NM_ICMPV6_ROUTER_PREF_INVALID = 0x2, /* ICMPV6_ROUTER_PREF_INVALID */ +} NMIcmpv6RouterPref; + +const char *nm_icmpv6_router_pref_to_string(NMIcmpv6RouterPref pref, char *buf, gsize len); + +/*****************************************************************************/ + +const char *nm_activation_type_to_string(NMActivationType activation_type); + +/*****************************************************************************/ + +const char *nm_utils_parse_dns_domain(const char *domain, gboolean *is_routing); + +/*****************************************************************************/ + +void nm_wifi_utils_parse_ies(const guint8 *bytes, + gsize len, + guint32 * out_max_rate, + gboolean * out_metered, + gboolean * out_owe_transition_mode); + +guint8 nm_wifi_utils_level_to_quality(int val); + +/*****************************************************************************/ + +#define NM_VPN_ROUTE_METRIC_DEFAULT 50 + +#define NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED "Unable to authenticate the request" +#define NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN "Unable to determine UID of the request" +#define NM_UTILS_ERROR_MSG_INSUFF_PRIV "Insufficient privileges" + +#endif /* __NM_CORE_UTILS_H__ */ diff --git a/src/core/nm-dbus-manager.c b/src/core/nm-dbus-manager.c new file mode 100644 index 0000000..e05ae39 --- /dev/null +++ b/src/core/nm-dbus-manager.c @@ -0,0 +1,1720 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-manager.h" + +#include +#include +#include + +#include "c-list/src/c-list.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-dbus-interface.h" +#include "nm-core-internal.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-dbus-object.h" +#include "NetworkManagerUtils.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" + +/* The base path for our GDBusObjectManagerServers. They do not contain + * "NetworkManager" because GDBusObjectManagerServer requires that all + * exported objects be *below* the base path, and eg the Manager object + * is the base path already. + */ +#define OBJECT_MANAGER_SERVER_BASE_PATH "/org/freedesktop" + +/*****************************************************************************/ + +typedef struct { + CList caller_info_lst; + gulong uid; + gulong pid; + gint64 uid_checked_at; + gint64 pid_checked_at; + bool uid_valid : 1; + bool pid_valid : 1; + char sender[0]; +} CallerInfo; + +typedef struct { + GVariant *value; +} PropertyCacheData; + +typedef struct { + CList registration_lst; + NMDBusObject * obj; + NMDBusObjectClass *klass; + guint info_idx; + guint registration_id; + PropertyCacheData property_cache[]; +} RegistrationData; + +/* we require that @path is the first member of NMDBusManagerData + * because _objects_by_path_hash() requires that. */ +G_STATIC_ASSERT(G_STRUCT_OFFSET(struct _NMDBusObjectInternal, path) == 0); + +enum { + PRIVATE_CONNECTION_NEW, + PRIVATE_CONNECTION_DISCONNECTED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +typedef struct { + GHashTable *objects_by_path; + CList objects_lst_head; + + CList private_servers_lst_head; + + NMDBusManagerSetPropertyHandler set_property_handler; + gpointer set_property_handler_data; + + GDBusConnection *main_dbus_connection; + + CList caller_info_lst_head; + + guint objmgr_registration_id; + bool started : 1; + bool shutting_down : 1; +} NMDBusManagerPrivate; + +struct _NMDBusManager { + GObject parent; + NMDBusManagerPrivate _priv; +}; + +struct _NMDBusManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMDBusManager, nm_dbus_manager, G_TYPE_OBJECT) + +#define NM_DBUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDBusManager, NM_IS_DBUS_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__) + +NM_DEFINE_SINGLETON_GETTER(NMDBusManager, nm_dbus_manager_get, NM_TYPE_DBUS_MANAGER); + +/*****************************************************************************/ + +static const GDBusInterfaceInfo interface_info_objmgr; +static const GDBusSignalInfo signal_info_objmgr_interfaces_added; +static const GDBusSignalInfo signal_info_objmgr_interfaces_removed; +static GVariantBuilder *_obj_collect_properties_all(NMDBusObject *obj, GVariantBuilder *builder); + +/*****************************************************************************/ + +static guint +_objects_by_path_hash(gconstpointer user_data) +{ + const char *const *p_data = user_data; + + nm_assert(p_data); + nm_assert(*p_data); + nm_assert((*p_data)[0] == '/'); + + return nm_hash_str(*p_data); +} + +static gboolean +_objects_by_path_equal(gconstpointer user_data_a, gconstpointer user_data_b) +{ + const char *const *p_data_a = user_data_a; + const char *const *p_data_b = user_data_b; + + nm_assert(p_data_a); + nm_assert(*p_data_a); + nm_assert((*p_data_a)[0] == '/'); + nm_assert(p_data_b); + nm_assert(*p_data_b); + nm_assert((*p_data_b)[0] == '/'); + + return nm_streq(*p_data_a, *p_data_b); +} + +/*****************************************************************************/ + +typedef struct { + CList private_servers_lst; + + const char * tag; + GQuark detail; + char * address; + GDBusServer *server; + + /* With peer bus connections, we'll get a new connection for each + * client. For each connection we create an ObjectManager for + * that connection to handle exporting our objects. + * + * Note that even for connections that don't export any objects + * we'll still create GDBusObjectManager since that's where we store + * the pointer to the GDBusConnection. + */ + CList object_mgr_lst_head; + + NMDBusManager *manager; +} PrivateServer; + +typedef struct { + CList object_mgr_lst; + GDBusObjectManagerServer *manager; + char * fake_sender; +} ObjectMgrData; + +typedef struct { + GDBusConnection *connection; + PrivateServer * server; + gboolean remote_peer_vanished; +} CloseConnectionInfo; + +/*****************************************************************************/ + +static void +_object_mgr_data_free(ObjectMgrData *obj_mgr_data) +{ + GDBusConnection *connection; + + c_list_unlink_stale(&obj_mgr_data->object_mgr_lst); + + connection = g_dbus_object_manager_server_get_connection(obj_mgr_data->manager); + if (!g_dbus_connection_is_closed(connection)) + g_dbus_connection_close(connection, NULL, NULL, NULL); + g_dbus_object_manager_server_set_connection(obj_mgr_data->manager, NULL); + g_object_unref(obj_mgr_data->manager); + g_object_unref(connection); + + g_free(obj_mgr_data->fake_sender); + + g_slice_free(ObjectMgrData, obj_mgr_data); +} + +/*****************************************************************************/ + +static gboolean +close_connection_in_idle(gpointer user_data) +{ + CloseConnectionInfo *info = user_data; + PrivateServer * server = info->server; + ObjectMgrData * obj_mgr_data, *obj_mgr_data_safe; + + /* Emit this for the manager */ + g_signal_emit(server->manager, + signals[PRIVATE_CONNECTION_DISCONNECTED], + server->detail, + info->connection); + + /* FIXME: there's a bug (754730) in GLib for which the connection + * is marked as closed when the remote peer vanishes but its + * resources are not cleaned up. Work around it by explicitly + * closing the connection in that case. */ + if (info->remote_peer_vanished) + g_dbus_connection_close(info->connection, NULL, NULL, NULL); + + c_list_for_each_entry_safe (obj_mgr_data, + obj_mgr_data_safe, + &server->object_mgr_lst_head, + object_mgr_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = g_dbus_object_manager_server_get_connection(obj_mgr_data->manager); + if (connection == info->connection) { + _object_mgr_data_free(obj_mgr_data); + break; + } + } + + g_object_unref(server->manager); + g_slice_free(CloseConnectionInfo, info); + + return G_SOURCE_REMOVE; +} + +static void +private_server_closed_connection(GDBusConnection *conn, + gboolean remote_peer_vanished, + GError * error, + gpointer user_data) +{ + PrivateServer * s = user_data; + CloseConnectionInfo *info; + + /* Clean up after the connection */ + _LOGD("(%s) closed connection " NM_HASH_OBFUSCATE_PTR_FMT " on private socket", + s->tag, + NM_HASH_OBFUSCATE_PTR(conn)); + + info = g_slice_new0(CloseConnectionInfo); + info->connection = conn; + info->server = s; + info->remote_peer_vanished = remote_peer_vanished; + + g_object_ref(s->manager); + + /* Delay the close of connection to ensure that D-Bus signals + * are handled */ + g_idle_add(close_connection_in_idle, info); +} + +static gboolean +private_server_new_connection(GDBusServer *server, GDBusConnection *conn, gpointer user_data) +{ + PrivateServer * s = user_data; + ObjectMgrData * obj_mgr_data; + static guint32 counter = 0; + GDBusObjectManagerServer *manager; + char * sender; + + g_signal_connect(conn, "closed", G_CALLBACK(private_server_closed_connection), s); + + /* Fake a sender since private connections don't have one */ + sender = g_strdup_printf("x:y:%d", counter++); + + manager = g_dbus_object_manager_server_new(OBJECT_MANAGER_SERVER_BASE_PATH); + g_dbus_object_manager_server_set_connection(manager, conn); + + obj_mgr_data = g_slice_new(ObjectMgrData); + obj_mgr_data->manager = manager; + obj_mgr_data->fake_sender = sender; + c_list_link_tail(&s->object_mgr_lst_head, &obj_mgr_data->object_mgr_lst); + + _LOGD("(%s) accepted connection " NM_HASH_OBFUSCATE_PTR_FMT " on private socket", + s->tag, + NM_HASH_OBFUSCATE_PTR(conn)); + + /* Emit this for the manager. + * + * It is essential to do this from the "new-connection" signal handler, as + * at that point no messages from the connection are yet processed + * (which avoids races with registering objects). */ + g_signal_emit(s->manager, signals[PRIVATE_CONNECTION_NEW], s->detail, conn, manager); + return TRUE; +} + +static gboolean +private_server_authorize(GDBusAuthObserver *observer, + GIOStream * stream, + GCredentials * credentials, + gpointer user_data) +{ + return g_credentials_get_unix_user(credentials, NULL) == 0; +} + +static gboolean +private_server_allow_mechanism(GDBusAuthObserver *observer, + const char * mechanism, + gpointer user_data) +{ + return NM_IN_STRSET(mechanism, "EXTERNAL"); +} + +static void +private_server_free(gpointer ptr) +{ + PrivateServer *s = ptr; + ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe; + + c_list_unlink_stale(&s->private_servers_lst); + + unlink(s->address); + g_free(s->address); + + c_list_for_each_entry_safe (obj_mgr_data, + obj_mgr_data_safe, + &s->object_mgr_lst_head, + object_mgr_lst) + _object_mgr_data_free(obj_mgr_data); + + g_dbus_server_stop(s->server); + + g_signal_handlers_disconnect_by_func(s->server, G_CALLBACK(private_server_new_connection), s); + + g_object_unref(s->server); + + g_slice_free(PrivateServer, s); +} + +void +nm_dbus_manager_private_server_register(NMDBusManager *self, const char *path, const char *tag) +{ + NMDBusManagerPrivate *priv; + PrivateServer * s; + gs_unref_object GDBusAuthObserver *auth_observer = NULL; + GDBusServer * server; + GError * error = NULL; + gs_free char * address = NULL; + gs_free char * guid = NULL; + + g_return_if_fail(NM_IS_DBUS_MANAGER(self)); + g_return_if_fail(path); + g_return_if_fail(tag); + + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + /* Only one instance per tag; but don't warn */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + if (nm_streq0(tag, s->tag)) + return; + } + + unlink(path); + address = g_strdup_printf("unix:path=%s", path); + + _LOGD("(%s) creating private socket %s", tag, address); + + guid = g_dbus_generate_guid(); + auth_observer = g_dbus_auth_observer_new(); + g_signal_connect(auth_observer, + "authorize-authenticated-peer", + G_CALLBACK(private_server_authorize), + NULL); + g_signal_connect(auth_observer, + "allow-mechanism", + G_CALLBACK(private_server_allow_mechanism), + NULL); + server = g_dbus_server_new_sync(address, + G_DBUS_SERVER_FLAGS_NONE, + guid, + auth_observer, + NULL, + &error); + + if (!server) { + _LOGW("(%s) failed to set up private socket %s: %s", tag, address, error->message); + g_error_free(error); + return; + } + + s = g_slice_new0(PrivateServer); + s->address = g_steal_pointer(&address); + s->server = server; + g_signal_connect(server, "new-connection", G_CALLBACK(private_server_new_connection), s); + + c_list_init(&s->object_mgr_lst_head); + + s->manager = self; + s->detail = g_quark_from_string(tag); + s->tag = g_quark_to_string(s->detail); + + c_list_link_tail(&priv->private_servers_lst_head, &s->private_servers_lst); + + g_dbus_server_start(server); +} + +static const char * +private_server_get_connection_owner(PrivateServer *s, GDBusConnection *connection) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert(s); + nm_assert(G_IS_DBUS_CONNECTION(connection)); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + gs_unref_object GDBusConnection *c = NULL; + + c = g_dbus_object_manager_server_get_connection(obj_mgr_data->manager); + if (c == connection) + return obj_mgr_data->fake_sender; + } + return NULL; +} + +static GDBusConnection * +private_server_get_connection_by_owner(PrivateServer *s, const char *owner) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert(s); + nm_assert(owner); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + if (nm_streq(owner, obj_mgr_data->fake_sender)) + return g_dbus_object_manager_server_get_connection(obj_mgr_data->manager); + } + return NULL; +} + +/*****************************************************************************/ + +static void +_caller_info_free(CallerInfo *caller_info) +{ + c_list_unlink_stale(&caller_info->caller_info_lst); + g_free(caller_info); +} + +static gboolean +_bus_get_unix_pid(NMDBusManager *self, const char *sender, gulong *out_pid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + guint32 unix_pid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + if (!priv->main_dbus_connection) + return FALSE; + + ret = g_dbus_connection_call_sync(priv->main_dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID", + g_variant_new("(s)", sender), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + 2000, + NULL, + NULL); + if (!ret) + return FALSE; + + g_variant_get(ret, "(u)", &unix_pid); + + *out_pid = (gulong) unix_pid; + return TRUE; +} + +static gboolean +_bus_get_unix_user(NMDBusManager *self, const char *sender, gulong *out_user) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + guint32 unix_uid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + if (!priv->main_dbus_connection) + return FALSE; + + ret = g_dbus_connection_call_sync(priv->main_dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixUser", + g_variant_new("(s)", sender), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + 2000, + NULL, + NULL); + if (!ret) + return FALSE; + + g_variant_get(ret, "(u)", &unix_uid); + + *out_user = (gulong) unix_uid; + return TRUE; +} + +static const CallerInfo * +_get_caller_info_ensure(NMDBusManager *self, + const char * sender, + gboolean ensure_uid, + gboolean ensure_pid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + CallerInfo * caller_info; + CallerInfo * ci; + gint64 now_ns; + gsize num; + +#define CALLER_INFO_MAX_AGE (NM_UTILS_NSEC_PER_SEC * 1) + + /* Linear search the cache for the sender. + * + * The number of cached caller-infos is limited. Hence, it's O(1) and + * the list is reasonably short. + * Also, the entire caching assumes that we repeatedly ask for the + * same sender. That means, we expect to find the right caller info + * at the front of the list. */ + num = 1; + caller_info = NULL; + c_list_for_each_entry (ci, &priv->caller_info_lst_head, caller_info_lst) { + if (nm_streq(sender, ci->sender)) { + caller_info = ci; + break; + } + num++; + } + + if (caller_info) + nm_c_list_move_front(&priv->caller_info_lst_head, &caller_info->caller_info_lst); + else { + gsize l = strlen(sender) + 1; + + caller_info = g_malloc(sizeof(CallerInfo) + l); + *caller_info = (CallerInfo){ + .uid_checked_at = -CALLER_INFO_MAX_AGE, + .pid_checked_at = -CALLER_INFO_MAX_AGE, + }; + memcpy(caller_info->sender, sender, l); + c_list_link_front(&priv->caller_info_lst_head, &caller_info->caller_info_lst); + + /* only cache the last few entries. */ + while (TRUE) { + nm_assert(num > 0 && num == c_list_length(&priv->caller_info_lst_head)); + if (num-- <= 5) + break; + _caller_info_free( + c_list_last_entry(&priv->caller_info_lst_head, CallerInfo, caller_info_lst)); + } + } + + now_ns = nm_utils_get_monotonic_timestamp_nsec(); + + if (ensure_uid && (now_ns - caller_info->uid_checked_at) > CALLER_INFO_MAX_AGE) { + caller_info->uid_checked_at = now_ns; + if (!(caller_info->uid_valid = _bus_get_unix_user(self, sender, &caller_info->uid))) + caller_info->uid = G_MAXULONG; + } + + if (ensure_pid && (now_ns - caller_info->pid_checked_at) > CALLER_INFO_MAX_AGE) { + caller_info->pid_checked_at = now_ns; + if (!(caller_info->pid_valid = _bus_get_unix_pid(self, sender, &caller_info->pid))) + caller_info->pid = G_MAXULONG; + } + + return caller_info; +} + +static gboolean +_get_caller_info(NMDBusManager * self, + GDBusMethodInvocation *context, + GDBusConnection * connection, + GDBusMessage * message, + const char ** out_sender, + gulong * out_uid, + gulong * out_pid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + const CallerInfo * caller_info; + const char * sender; + + if (context) { + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + connection = g_dbus_method_invocation_get_connection(context); + + /* only bus connections will have a sender */ + sender = g_dbus_method_invocation_get_sender(context); + } else { + nm_assert(G_IS_DBUS_MESSAGE(message)); + sender = g_dbus_message_get_sender(message); + } + nm_assert(G_IS_DBUS_CONNECTION(connection)); + + if (!sender) { + PrivateServer *s; + + /* Might be a private connection, for which we fake a sender */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + sender = private_server_get_connection_owner(s, connection); + if (sender) { + NM_SET_OUT(out_uid, 0); + NM_SET_OUT(out_sender, sender); + if (out_pid) { + GCredentials *creds; + + creds = g_dbus_connection_get_peer_credentials(connection); + if (creds) { + pid_t pid; + + pid = g_credentials_get_unix_pid(creds, NULL); + if (pid == -1) + *out_pid = G_MAXULONG; + else + *out_pid = pid; + } else + *out_pid = G_MAXULONG; + } + return TRUE; + } + } + NM_SET_OUT(out_sender, NULL); + NM_SET_OUT(out_uid, G_MAXULONG); + NM_SET_OUT(out_pid, G_MAXULONG); + return FALSE; + } + + caller_info = _get_caller_info_ensure(self, sender, !!out_uid, !!out_pid); + + NM_SET_OUT(out_sender, caller_info->sender); + NM_SET_OUT(out_uid, caller_info->uid); + NM_SET_OUT(out_pid, caller_info->pid); + + if (out_uid && !caller_info->uid_valid) + return FALSE; + if (out_pid && !caller_info->pid_valid) + return FALSE; + return TRUE; +} + +gboolean +nm_dbus_manager_get_caller_info(NMDBusManager * self, + GDBusMethodInvocation *context, + const char ** out_sender, + gulong * out_uid, + gulong * out_pid) +{ + return _get_caller_info(self, context, NULL, NULL, out_sender, out_uid, out_pid); +} + +gboolean +nm_dbus_manager_get_caller_info_from_message(NMDBusManager * self, + GDBusConnection *connection, + GDBusMessage * message, + const char ** out_sender, + gulong * out_uid, + gulong * out_pid) +{ + return _get_caller_info(self, NULL, connection, message, out_sender, out_uid, out_pid); +} + +/** + * nm_dbus_manager_ensure_uid: + * + * @self: bus manager instance + * @context: D-Bus method invocation + * @uid: a user-id + * @error_domain: error domain to return on failure + * @error_code: error code to return on failure + * + * Retrieves the uid of the D-Bus method caller and + * checks that it matches @uid, unless @uid is G_MAXULONG. + * In case of failure the function returns FALSE and finishes + * handling the D-Bus method with an error. + * + * Returns: %TRUE if the check succeeded, %FALSE otherwise + */ +gboolean +nm_dbus_manager_ensure_uid(NMDBusManager * self, + GDBusMethodInvocation *context, + gulong uid, + GQuark error_domain, + int error_code) +{ + gulong caller_uid; + GError *error = NULL; + + g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); + g_return_val_if_fail(G_IS_DBUS_METHOD_INVOCATION(context), FALSE); + + if (!nm_dbus_manager_get_caller_info(self, context, NULL, &caller_uid, NULL)) { + error = g_error_new_literal(error_domain, error_code, "Unable to determine request UID."); + g_dbus_method_invocation_take_error(context, error); + return FALSE; + } + + if (uid != G_MAXULONG && caller_uid != uid) { + error = g_error_new_literal(error_domain, error_code, "Permission denied"); + g_dbus_method_invocation_take_error(context, error); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_dbus_manager_get_unix_user(NMDBusManager *self, const char *sender, gulong *out_uid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + const CallerInfo * caller_info; + PrivateServer * s; + + g_return_val_if_fail(sender != NULL, FALSE); + g_return_val_if_fail(out_uid != NULL, FALSE); + + /* Check if it's a private connection sender, which we fake */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = private_server_get_connection_by_owner(s, sender); + if (connection) { + *out_uid = 0; + return TRUE; + } + } + + /* Otherwise, a bus connection */ + caller_info = _get_caller_info_ensure(self, sender, TRUE, FALSE); + *out_uid = caller_info->uid; + if (!caller_info->uid_valid) { + _LOGW("failed to get unix user for dbus sender '%s'", sender); + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended * +_reg_data_get_interface_info(RegistrationData *reg_data) +{ + nm_assert(reg_data); + + return reg_data->klass->interface_infos[reg_data->info_idx]; +} + +/*****************************************************************************/ + +static void +dbus_vtable_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMDBusManager * self; + NMDBusManagerPrivate * priv; + RegistrationData * reg_data = user_data; + NMDBusObject * obj = reg_data->obj; + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + const NMDBusMethodInfoExtended * method_info = NULL; + gboolean on_same_interface; + + on_same_interface = nm_streq(interface_info->parent.name, interface_name); + + /* handle property setter first... */ + if (!on_same_interface && nm_streq(interface_name, DBUS_INTERFACE_PROPERTIES) + && nm_streq(method_name, "Set")) { + const NMDBusPropertyInfoExtended *property_info = NULL; + const char * property_interface; + const char * property_name; + gs_unref_variant GVariant *value = NULL; + + self = nm_dbus_object_get_manager(obj); + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + g_variant_get(parameters, "(&s&sv)", &property_interface, &property_name, &value); + + nm_assert(nm_streq(property_interface, interface_info->parent.name)); + + property_info = + (const NMDBusPropertyInfoExtended *) nm_dbus_utils_interface_info_lookup_property( + &interface_info->parent, + property_name, + NULL); + if (!property_info + || !NM_FLAGS_HAS(property_info->parent.flags, G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) + g_return_if_reached(); + + if (!priv->set_property_handler) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_AUTH_FAILED, + "Cannot authenticate setting property %s", + property_name); + return; + } + + priv->set_property_handler(obj, + interface_info, + property_info, + connection, + sender, + invocation, + value, + priv->set_property_handler_data); + return; + } + + if (on_same_interface) { + method_info = (const NMDBusMethodInfoExtended *) nm_dbus_utils_interface_info_lookup_method( + &interface_info->parent, + method_name); + } + if (!method_info) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); + return; + } + + self = nm_dbus_object_get_manager(obj); + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + if (priv->shutting_down && !method_info->allow_during_shutdown) { + g_dbus_method_invocation_return_error_literal(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "NetworkManager is exiting"); + return; + } + + method_info->handle(reg_data->obj, + interface_info, + method_info, + connection, + sender, + invocation, + parameters); +} + +static GVariant * +_obj_get_property(RegistrationData *reg_data, guint property_idx, gboolean refetch) +{ + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + const NMDBusPropertyInfoExtended * property_info; + GVariant * value; + + property_info = + (const NMDBusPropertyInfoExtended *) (interface_info->parent.properties[property_idx]); + + if (refetch) + nm_clear_g_variant(®_data->property_cache[property_idx].value); + else { + value = reg_data->property_cache[property_idx].value; + if (value) + goto out; + } + + value = nm_dbus_utils_get_property(G_OBJECT(reg_data->obj), + property_info->parent.signature, + property_info->property_name); + reg_data->property_cache[property_idx].value = value; +out: + return g_variant_ref(value); +} + +static GVariant * +dbus_vtable_get_property(GDBusConnection *connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * property_name, + GError ** error, + gpointer user_data) +{ + RegistrationData * reg_data = user_data; + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + guint property_idx; + + if (!nm_dbus_utils_interface_info_lookup_property(&interface_info->parent, + property_name, + &property_idx)) + g_return_val_if_reached(NULL); + + return _obj_get_property(reg_data, property_idx, FALSE); +} + +static const GDBusInterfaceVTable dbus_vtable = { + .method_call = dbus_vtable_method_call, + .get_property = dbus_vtable_get_property, + + /* set_property is handled via method_call as well. We need to authenticate + * which requires an asynchronous handler. */ + .set_property = NULL, +}; + +static void +_obj_register(NMDBusManager *self, NMDBusObject *obj) +{ + NMDBusManagerPrivate * priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + guint i, k; + guint n_klasses; + GType gtype; + NMDBusObjectClass * klasses[10]; + const NMDBusInterfaceInfoExtended *const *prev_interface_infos = NULL; + GVariantBuilder builder; + + nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); + nm_assert(priv->main_dbus_connection); + nm_assert(priv->objmgr_registration_id != 0); + nm_assert(priv->started); + + n_klasses = 0; + gtype = G_OBJECT_TYPE(obj); + while (gtype != NM_TYPE_DBUS_OBJECT) { + nm_assert(n_klasses < G_N_ELEMENTS(klasses)); + klasses[n_klasses++] = g_type_class_ref(gtype); + gtype = g_type_parent(gtype); + } + + for (k = n_klasses; k > 0;) { + NMDBusObjectClass *klass = NM_DBUS_OBJECT_CLASS(klasses[--k]); + + if (!klass->interface_infos) + continue; + + if (prev_interface_infos == klass->interface_infos) { + /* derived classes inherrit the interface-infos from the parent class. + * For convenience, we allow the subclass to leave interface-infos untouched, + * but it means we must ignore the parent's interface, because we already + * handled it. + * + * Note that the loop goes from the parent classes to child classes */ + continue; + } + prev_interface_infos = klass->interface_infos; + + for (i = 0; klass->interface_infos[i]; i++) { + const NMDBusInterfaceInfoExtended *interface_info = klass->interface_infos[i]; + RegistrationData * reg_data; + gs_free_error GError *error = NULL; + guint registration_id; + guint prop_len = NM_PTRARRAY_LEN(interface_info->parent.properties); + + reg_data = g_malloc0(sizeof(RegistrationData) + (sizeof(PropertyCacheData) * prop_len)); + + registration_id = g_dbus_connection_register_object( + priv->main_dbus_connection, + obj->internal.path, + NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info->parent), + &dbus_vtable, + reg_data, + NULL, + &error); + if (!registration_id) { + _LOGE("failure to register object %s: %s", obj->internal.path, error->message); + g_free(reg_data); + continue; + } + + reg_data->obj = obj; + reg_data->klass = g_type_class_ref(G_TYPE_FROM_CLASS(klass)); + reg_data->info_idx = i; + reg_data->registration_id = registration_id; + c_list_link_tail(&obj->internal.registration_lst_head, ®_data->registration_lst); + } + } + + for (k = 0; k < n_klasses; k++) + g_type_class_unref(klasses[k]); + + nm_assert(!c_list_is_empty(&obj->internal.registration_lst_head)); + + /* Currently, the interfaces of an object do not changed and strictly depend on the object glib type. + * We don't need more flexibility, and it simplifies the code. Hence, now emit interface-added + * signal for the new object. + * + * Warning: note that if @obj's notify signal is currently blocked via g_object_freeze_notify(), + * we might emit properties with an inconsistent (internal) state. There is no easy solution, + * because we have to emit the signal now, and we don't know what the correct desired state + * of the properties is. + * Another problem is, upon unfreezing the signals, we immediately send PropertiesChanged + * notifications out. Which is a bit odd, as we just export the object. + * + * In general, it's ok to export an object with frozen signals. But you better make sure + * that all properties are in a self-consistent state when exporting the object. */ + g_dbus_connection_emit_signal(priv->main_dbus_connection, + NULL, + OBJECT_MANAGER_SERVER_BASE_PATH, + interface_info_objmgr.name, + signal_info_objmgr_interfaces_added.name, + g_variant_new("(oa{sa{sv}})", + obj->internal.path, + _obj_collect_properties_all(obj, &builder)), + NULL); +} + +static void +_obj_unregister(NMDBusManager *self, NMDBusObject *obj) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + RegistrationData * reg_data; + GVariantBuilder builder; + + nm_assert(NM_IS_DBUS_OBJECT(obj)); + nm_assert(priv->main_dbus_connection); + nm_assert(priv->objmgr_registration_id != 0); + nm_assert(priv->started); + nm_assert(!c_list_is_empty(&obj->internal.registration_lst_head)); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); + + while ((reg_data = c_list_last_entry(&obj->internal.registration_lst_head, + RegistrationData, + registration_lst))) { + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + guint i; + + g_variant_builder_add(&builder, "s", interface_info->parent.name); + c_list_unlink_stale(®_data->registration_lst); + if (!g_dbus_connection_unregister_object(priv->main_dbus_connection, + reg_data->registration_id)) + nm_assert_not_reached(); + + if (interface_info->parent.properties) { + for (i = 0; interface_info->parent.properties[i]; i++) + nm_clear_g_variant(®_data->property_cache[i].value); + } + + g_type_class_unref(reg_data->klass); + g_free(reg_data); + } + + g_dbus_connection_emit_signal(priv->main_dbus_connection, + NULL, + OBJECT_MANAGER_SERVER_BASE_PATH, + interface_info_objmgr.name, + signal_info_objmgr_interfaces_removed.name, + g_variant_new("(oas)", obj->internal.path, &builder), + NULL); +} + +gpointer +nm_dbus_manager_lookup_object(NMDBusManager *self, const char *path) +{ + NMDBusManagerPrivate *priv; + gpointer ptr; + NMDBusObject * obj; + + g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), NULL); + g_return_val_if_fail(path, NULL); + + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + ptr = g_hash_table_lookup(priv->objects_by_path, &path); + if (!ptr) + return NULL; + + obj = (NMDBusObject *) (((char *) ptr) - G_STRUCT_OFFSET(NMDBusObject, internal)); + nm_assert(NM_IS_DBUS_OBJECT(obj)); + return obj; +} + +void +_nm_dbus_manager_obj_export(NMDBusObject *obj) +{ + NMDBusManager * self; + NMDBusManagerPrivate *priv; + + g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); + g_return_if_fail(obj->internal.path); + g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); + g_return_if_fail(c_list_is_empty(&obj->internal.objects_lst)); + nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + if (!g_hash_table_add(priv->objects_by_path, &obj->internal)) + nm_assert_not_reached(); + c_list_link_tail(&priv->objects_lst_head, &obj->internal.objects_lst); + + if (priv->started) + _obj_register(self, obj); +} + +void +_nm_dbus_manager_obj_unexport(NMDBusObject *obj) +{ + NMDBusManager * self; + NMDBusManagerPrivate *priv; + + g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); + g_return_if_fail(obj->internal.path); + g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); + g_return_if_fail(!c_list_is_empty(&obj->internal.objects_lst)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + nm_assert(&obj->internal == g_hash_table_lookup(priv->objects_by_path, &obj->internal)); + nm_assert(c_list_contains(&priv->objects_lst_head, &obj->internal.objects_lst)); + + if (priv->started) + _obj_unregister(self, obj); + else + nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); + + if (!g_hash_table_remove(priv->objects_by_path, &obj->internal)) + nm_assert_not_reached(); + c_list_unlink(&obj->internal.objects_lst); +} + +void +_nm_dbus_manager_obj_notify(NMDBusObject *obj, guint n_pspecs, const GParamSpec *const *pspecs) +{ + NMDBusManager * self; + NMDBusManagerPrivate *priv; + RegistrationData * reg_data; + guint i, p; + gboolean any_legacy_signals = FALSE; + gboolean any_legacy_properties = FALSE; + GVariantBuilder legacy_builder; + GVariant * device_statistics_args = NULL; + + nm_assert(NM_IS_DBUS_OBJECT(obj)); + nm_assert(obj->internal.path); + nm_assert(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); + nm_assert(!c_list_is_empty(&obj->internal.objects_lst)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + nm_assert(!priv->started || priv->objmgr_registration_id != 0); + nm_assert(priv->objmgr_registration_id == 0 || priv->main_dbus_connection); + nm_assert(c_list_is_empty(&obj->internal.registration_lst_head) != priv->started); + + if (G_UNLIKELY(!priv->started)) + return; + + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + if (_reg_data_get_interface_info(reg_data)->legacy_property_changed) { + any_legacy_signals = TRUE; + break; + } + } + + /* do a naive search for the matching NMDBusPropertyInfoExtended infos. Since the number of + * (interfaces x properties) is static and possibly small, this naive search is effectively + * O(1). We might wanna introduce some index to lookup the properties in question faster. + * + * The nice part of this implementation is however, that the order in which properties + * are added to the GVariant is strictly defined to be the order in which the D-Bus property-info + * is declared. Getting a defined ordering with some smart lookup would be hard. */ + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + gboolean has_properties = FALSE; + GVariantBuilder builder; + GVariantBuilder invalidated_builder; + GVariant * args; + + if (!interface_info->parent.properties) + continue; + + for (i = 0; interface_info->parent.properties[i]; i++) { + const NMDBusPropertyInfoExtended *property_info = + (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; + + for (p = 0; p < n_pspecs; p++) { + const GParamSpec *pspec = pspecs[p]; + gs_unref_variant GVariant *value = NULL; + + if (!nm_streq(property_info->property_name, pspec->name)) + continue; + + value = _obj_get_property(reg_data, i, TRUE); + + if (property_info->include_in_legacy_property_changed && any_legacy_signals) { + /* also track the value in the legacy_builder to emit legacy signals below. */ + if (!any_legacy_properties) { + any_legacy_properties = TRUE; + g_variant_builder_init(&legacy_builder, G_VARIANT_TYPE("a{sv}")); + } + g_variant_builder_add(&legacy_builder, + "{sv}", + property_info->parent.name, + value); + } + + if (!has_properties) { + has_properties = TRUE; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + } + g_variant_builder_add(&builder, "{sv}", property_info->parent.name, value); + } + } + + if (!has_properties) + continue; + + args = g_variant_builder_end(&builder); + + if (G_UNLIKELY(interface_info == &nm_interface_info_device_statistics)) { + /* we treat the Device.Statistics signal special, because we need to + * emit a signal also for it (below). */ + nm_assert(!device_statistics_args); + device_statistics_args = g_variant_ref_sink(args); + } + + g_variant_builder_init(&invalidated_builder, G_VARIANT_TYPE("as")); + g_dbus_connection_emit_signal( + priv->main_dbus_connection, + NULL, + obj->internal.path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(s@a{sv}as)", interface_info->parent.name, args, &invalidated_builder), + NULL); + } + + if (G_UNLIKELY(device_statistics_args)) { + /* this is a special interface: it has a legacy PropertiesChanged signal, + * however, contrary to other interfaces with ~regular~ legacy signals, + * we only notify about properties that actually belong to this interface. */ + g_dbus_connection_emit_signal(priv->main_dbus_connection, + NULL, + obj->internal.path, + nm_interface_info_device_statistics.parent.name, + "PropertiesChanged", + g_variant_new("(@a{sv})", device_statistics_args), + NULL); + g_variant_unref(device_statistics_args); + } + + if (any_legacy_properties) { + gs_unref_variant GVariant *args = NULL; + + /* The legacy PropertyChanged signal on the NetworkManager D-Bus interface is + * deprecated for the standard signal on org.freedesktop.DBus.Properties. However, + * for backward compatibility, we still need to emit it. + * + * Due to a bug in dbus-glib in NetworkManager <= 1.0, the signal would + * not only notify about properties that were actually on the corresponding + * D-Bus interface. Instead, it would notify about all relevant properties + * on all interfaces that had such a signal. + * + * For example, "HwAddress" gets emitted both on "fdo.NM.Device.Ethernet" + * and "fdo.NM.Device.Veth" for veth interfaces, although only the former + * actually has such a property. + * Also note that "fdo.NM.Device" interface has no legacy signal. All notifications + * about its properties are instead emitted on the interfaces of the subtypes. + * + * See bgo#770629 and commit bef26a2e69f51259095fa080221db73de09fd38d. + */ + args = g_variant_ref_sink(g_variant_new("(a{sv})", &legacy_builder)); + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + const NMDBusInterfaceInfoExtended *interface_info = + _reg_data_get_interface_info(reg_data); + + if (interface_info->legacy_property_changed) { + g_dbus_connection_emit_signal(priv->main_dbus_connection, + NULL, + obj->internal.path, + interface_info->parent.name, + "PropertiesChanged", + args, + NULL); + } + } + } +} + +void +_nm_dbus_manager_obj_emit_signal(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + GVariant * args) +{ + NMDBusManager * self; + NMDBusManagerPrivate *priv; + + g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); + g_return_if_fail(obj->internal.path); + g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); + g_return_if_fail(!c_list_is_empty(&obj->internal.objects_lst)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + if (!priv->started) { + nm_g_variant_unref_floating(args); + return; + } + + g_dbus_connection_emit_signal(priv->main_dbus_connection, + NULL, + obj->internal.path, + interface_info->parent.name, + signal_info->name, + args, + NULL); +} + +/*****************************************************************************/ + +static GVariantBuilder * +_obj_collect_properties_per_interface(NMDBusObject * obj, + RegistrationData *reg_data, + GVariantBuilder * builder) +{ + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); + guint i; + + g_variant_builder_init(builder, G_VARIANT_TYPE("a{sv}")); + if (interface_info->parent.properties) { + for (i = 0; interface_info->parent.properties[i]; i++) { + const NMDBusPropertyInfoExtended *property_info = + (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; + gs_unref_variant GVariant *variant = NULL; + + variant = _obj_get_property(reg_data, i, FALSE); + g_variant_builder_add(builder, "{sv}", property_info->parent.name, variant); + } + } + return builder; +} + +static GVariantBuilder * +_obj_collect_properties_all(NMDBusObject *obj, GVariantBuilder *builder) +{ + RegistrationData *reg_data; + + g_variant_builder_init(builder, G_VARIANT_TYPE("a{sa{sv}}")); + + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + GVariantBuilder properties_builder; + + g_variant_builder_add( + builder, + "{sa{sv}}", + _reg_data_get_interface_info(reg_data)->parent.name, + _obj_collect_properties_per_interface(obj, reg_data, &properties_builder)); + } + + return builder; +} + +static void +dbus_vtable_objmgr_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMDBusManager * self = user_data; + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + GVariantBuilder array_builder; + NMDBusObject * obj; + + nm_assert(nm_streq0(object_path, OBJECT_MANAGER_SERVER_BASE_PATH)); + + if (!nm_streq(method_name, "GetManagedObjects") + || !nm_streq(interface_name, interface_info_objmgr.name)) { + g_dbus_method_invocation_return_error( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s - only GetManagedObjects() is supported", + method_name); + return; + } + + g_variant_builder_init(&array_builder, G_VARIANT_TYPE("a{oa{sa{sv}}}")); + c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) { + GVariantBuilder interfaces_builder; + + /* note that we are called on an idle handler. Hence, all properties are + * supposed to be in a consistent state. That is true, if you always + * g_object_thaw_notify() before returning to the mainloop. Keeping + * signals frozen between while returning from the current call stack + * is anyway a very fragile thing, easy to get wrong. Don't do that. */ + g_variant_builder_add(&array_builder, + "{oa{sa{sv}}}", + obj->internal.path, + _obj_collect_properties_all(obj, &interfaces_builder)); + } + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(a{oa{sa{sv}}})", &array_builder)); +} + +static const GDBusInterfaceVTable dbus_vtable_objmgr = {.method_call = + dbus_vtable_objmgr_method_call}; + +static const GDBusSignalInfo signal_info_objmgr_interfaces_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "InterfacesAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("object_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("interfaces_and_properties", "a{sa{sv}}"), ), ); + +static const GDBusSignalInfo signal_info_objmgr_interfaces_removed = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "InterfacesRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("object_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("interfaces", "as"), ), ); + +static const GDBusInterfaceInfo interface_info_objmgr = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + DBUS_INTERFACE_OBJECT_MANAGER, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO( + "GetManagedObjects", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("object_paths_interfaces_and_properties", + "a{oa{sa{sv}}}"), ), ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_objmgr_interfaces_added, + &signal_info_objmgr_interfaces_removed, ), ); + +/*****************************************************************************/ + +GDBusConnection * +nm_dbus_manager_get_dbus_connection(NMDBusManager *self) +{ + g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), NULL); + + return NM_DBUS_MANAGER_GET_PRIVATE(self)->main_dbus_connection; +} + +void +nm_dbus_manager_start(NMDBusManager * self, + NMDBusManagerSetPropertyHandler set_property_handler, + gpointer set_property_handler_data) +{ + NMDBusManagerPrivate *priv; + NMDBusObject * obj; + + g_return_if_fail(NM_IS_DBUS_MANAGER(self)); + + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + nm_assert(!priv->started); + + if (priv->objmgr_registration_id == 0) { + /* Do nothing. We're presumably in the configure-and-quit mode. */ + return; + } + + priv->set_property_handler = set_property_handler; + priv->set_property_handler_data = set_property_handler_data; + priv->started = TRUE; + + c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) + _obj_register(self, obj); +} + +gboolean +nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name) +{ + NMDBusManagerPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + guint32 result; + guint registration_id; + + g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); + + priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + /* Create the D-Bus connection and registering the name synchronously. + * That is necessary because we need to exit right away if we can't + * acquire the name despite connecting to the bus successfully. + * It means that something is gravely broken -- such as another NetworkManager + * instance running. */ + priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!priv->main_dbus_connection) { + _LOGE("cannot connect to D-Bus: %s", error->message); + return FALSE; + } + + g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE); + + if (!request_name) { + _LOGD("D-Bus connection created"); + return TRUE; + } + + registration_id = g_dbus_connection_register_object( + priv->main_dbus_connection, + OBJECT_MANAGER_SERVER_BASE_PATH, + NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr), + &dbus_vtable_objmgr, + self, + NULL, + &error); + if (!registration_id) { + _LOGE("failure to register object manager: %s", error->message); + return FALSE; + } + + ret = g_dbus_connection_call_sync( + priv->main_dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName", + g_variant_new("(su)", NM_DBUS_SERVICE, DBUS_NAME_FLAG_DO_NOT_QUEUE), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (!ret) { + _LOGE("fatal failure to acquire D-Bus service \"%s" + ": %s", + NM_DBUS_SERVICE, + error->message); + g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); + return FALSE; + } + + g_variant_get(ret, "(u)", &result); + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOGE("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken", + NM_DBUS_SERVICE, + (guint) result); + g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); + return FALSE; + } + + priv->objmgr_registration_id = registration_id; + + _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE); + + return TRUE; +} + +void +nm_dbus_manager_stop(NMDBusManager *self) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + priv->shutting_down = TRUE; + + /* during shutdown we also clear the set-property-handler. It's no longer + * possible to set a property, because doing so would require authorization, + * which is async, which is just complicated to get right. No more property + * setting from now on. */ + priv->set_property_handler = NULL; + priv->set_property_handler_data = NULL; +} + +gboolean +nm_dbus_manager_is_stopping(NMDBusManager *self) +{ + return NM_DBUS_MANAGER_GET_PRIVATE(self)->shutting_down; +} + +/*****************************************************************************/ + +static void +nm_dbus_manager_init(NMDBusManager *self) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + + c_list_init(&priv->private_servers_lst_head); + c_list_init(&priv->objects_lst_head); + + priv->objects_by_path = + g_hash_table_new((GHashFunc) _objects_by_path_hash, (GEqualFunc) _objects_by_path_equal); + + c_list_init(&priv->caller_info_lst_head); +} + +static void +dispose(GObject *object) +{ + NMDBusManager * self = NM_DBUS_MANAGER(object); + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); + PrivateServer * s, *s_safe; + CallerInfo * caller_info; + + /* All exported NMDBusObject instances keep the manager alive, so we don't + * expect any remaining objects. */ + nm_assert(!priv->objects_by_path || g_hash_table_size(priv->objects_by_path) == 0); + nm_assert(c_list_is_empty(&priv->objects_lst_head)); + + nm_clear_pointer(&priv->objects_by_path, g_hash_table_destroy); + + c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst) + private_server_free(s); + + if (priv->objmgr_registration_id) { + g_dbus_connection_unregister_object(priv->main_dbus_connection, + nm_steal_int(&priv->objmgr_registration_id)); + } + + g_clear_object(&priv->main_dbus_connection); + + G_OBJECT_CLASS(nm_dbus_manager_parent_class)->dispose(object); + + while ((caller_info = + c_list_first_entry(&priv->caller_info_lst_head, CallerInfo, caller_info_lst))) + _caller_info_free(caller_info); +} + +static void +nm_dbus_manager_class_init(NMDBusManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + + signals[PRIVATE_CONNECTION_NEW] = g_signal_new(NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_DBUS_CONNECTION, + G_TYPE_DBUS_OBJECT_MANAGER_SERVER); + + signals[PRIVATE_CONNECTION_DISCONNECTED] = + g_signal_new(NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + +static NMAuthSubject * +_new_unix_process(GDBusMethodInvocation *context, + GDBusConnection * connection, + GDBusMessage * message) +{ + NMAuthSubject *self; + const char * dbus_sender = NULL; + gulong uid = 0; + gulong pid = 0; + gboolean success; + + g_return_val_if_fail(context || (connection && message), NULL); + + if (context) { + success = nm_dbus_manager_get_caller_info(nm_dbus_manager_get(), + context, + &dbus_sender, + &uid, + &pid); + } else { + nm_assert(message); + success = nm_dbus_manager_get_caller_info_from_message(nm_dbus_manager_get(), + connection, + message, + &dbus_sender, + &uid, + &pid); + } + + if (!success) + return NULL; + + g_return_val_if_fail(dbus_sender && *dbus_sender, NULL); + /* polkit glib library stores uid and pid as int. There might be some + * pitfalls if the id ever happens to be larger then that. Just assert against + * it here. */ + g_return_val_if_fail(uid <= MIN(G_MAXINT, G_MAXINT32), NULL); + g_return_val_if_fail(pid > 0 && pid <= MIN(G_MAXINT, G_MAXINT32), NULL); + + self = nm_auth_subject_new_unix_process(dbus_sender, pid, uid); + + if (nm_auth_subject_get_subject_type(self) != NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { + /* this most likely happened because the process is gone (start_time==0). + * Either that is not assert-worthy, or constructed() already asserted. + * Just return NULL. */ + g_clear_object(&self); + } + return self; +} + +NMAuthSubject * +nm_dbus_manager_new_auth_subject_from_context(GDBusMethodInvocation *context) +{ + return _new_unix_process(context, NULL, NULL); +} + +NMAuthSubject * +nm_dbus_manager_new_auth_subject_from_message(GDBusConnection *connection, GDBusMessage *message) +{ + return _new_unix_process(NULL, connection, message); +} diff --git a/src/core/nm-dbus-manager.h b/src/core/nm-dbus-manager.h new file mode 100644 index 0000000..06876af --- /dev/null +++ b/src/core/nm-dbus-manager.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2008 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_DBUS_MANAGER_H__ +#define __NM_DBUS_MANAGER_H__ + +#include "nm-dbus-utils.h" + +#define NM_TYPE_DBUS_MANAGER (nm_dbus_manager_get_type()) +#define NM_DBUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_DBUS_MANAGER, NMDBusManager)) +#define NM_DBUS_MANAGER_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DBUS_MANAGER, NMDBusManagerClass)) +#define NM_IS_DBUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_DBUS_MANAGER)) +#define NM_IS_DBUS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), NM_TYPE_DBUS_MANAGER)) +#define NM_DBUS_MANAGER_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_DBUS_MANAGER, NMDBusManagerClass)) + +#define NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "private-connection-new" +#define NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "private-connection-disconnected" + +typedef struct _NMDBusManagerClass NMDBusManagerClass; + +GType nm_dbus_manager_get_type(void); + +NMDBusManager *nm_dbus_manager_get(void); + +typedef void (*NMDBusManagerSetPropertyHandler)(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusPropertyInfoExtended * property_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * value, + gpointer user_data); + +gboolean nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name); + +GDBusConnection *nm_dbus_manager_get_dbus_connection(NMDBusManager *self); + +#define NM_MAIN_DBUS_CONNECTION_GET (nm_dbus_manager_get_dbus_connection(nm_dbus_manager_get())) + +void nm_dbus_manager_start(NMDBusManager * self, + NMDBusManagerSetPropertyHandler set_property_handler, + gpointer set_property_handler_data); + +void nm_dbus_manager_stop(NMDBusManager *self); + +gboolean nm_dbus_manager_is_stopping(NMDBusManager *self); + +gpointer nm_dbus_manager_lookup_object(NMDBusManager *self, const char *path); + +void _nm_dbus_manager_obj_export(NMDBusObject *obj); +void _nm_dbus_manager_obj_unexport(NMDBusObject *obj); +void +_nm_dbus_manager_obj_notify(NMDBusObject *obj, guint n_pspecs, const GParamSpec *const *pspecs); +void _nm_dbus_manager_obj_emit_signal(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + GVariant * args); + +gboolean nm_dbus_manager_get_caller_info(NMDBusManager * self, + GDBusMethodInvocation *context, + const char ** out_sender, + gulong * out_uid, + gulong * out_pid); + +gboolean nm_dbus_manager_ensure_uid(NMDBusManager * self, + GDBusMethodInvocation *context, + gulong uid, + GQuark error_domain, + int error_code); + +gboolean nm_dbus_manager_get_unix_user(NMDBusManager *self, const char *sender, gulong *out_uid); + +gboolean nm_dbus_manager_get_caller_info_from_message(NMDBusManager * self, + GDBusConnection *connection, + GDBusMessage * message, + const char ** out_sender, + gulong * out_uid, + gulong * out_pid); + +void +nm_dbus_manager_private_server_register(NMDBusManager *self, const char *path, const char *tag); + +NMAuthSubject *nm_dbus_manager_new_auth_subject_from_context(GDBusMethodInvocation *context); + +NMAuthSubject *nm_dbus_manager_new_auth_subject_from_message(GDBusConnection *connection, + GDBusMessage * message); + +#endif /* __NM_DBUS_MANAGER_H__ */ diff --git a/src/core/nm-dbus-object.c b/src/core/nm-dbus-object.c new file mode 100644 index 0000000..4f6d2af --- /dev/null +++ b/src/core/nm-dbus-object.c @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-object.h" + +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +enum { + EXPORTED_CHANGED, + + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_ABSTRACT_TYPE(NMDBusObject, nm_dbus_object, G_TYPE_OBJECT); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) \ + __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG_DOMAIN, "dbus-object", __VA_ARGS__) + +#define _NMLOG2_DOMAIN LOGD_DBUS_PROPS +#define _NMLOG2(level, ...) \ + __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG2_DOMAIN, "properties-changed", __VA_ARGS__) + +/*****************************************************************************/ + +static void +_emit_exported_changed(NMDBusObject *self) +{ + g_signal_emit(self, signals[EXPORTED_CHANGED], 0); +} + +static char * +_create_export_path(NMDBusObjectClass *klass) +{ + nm_assert(NM_IS_DBUS_OBJECT_CLASS(klass)); + nm_assert(klass->export_path.path); + +#if NM_MORE_ASSERTS + { + const char *p; + + p = strchr(klass->export_path.path, '%'); + if (klass->export_path.int_counter) { + nm_assert(p); + nm_assert(p[1] == 'l'); + nm_assert(p[2] == 'l'); + nm_assert(p[3] == 'u'); + nm_assert(p[4] == '\0'); + } else + nm_assert(!p); + } +#endif + + if (klass->export_path.int_counter) { + NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") + return g_strdup_printf(klass->export_path.path, ++(*klass->export_path.int_counter)); + NM_PRAGMA_WARNING_REENABLE + } + return g_strdup(klass->export_path.path); +} + +/** + * nm_dbus_object_export: + * @self: an #NMDBusObject + * + * Exports @self on all active and future D-Bus connections. + * + * The path to export @self on is taken from its #NMObjectClass's %export_path + * member. If the %export_path contains "%u", then it will be replaced with a + * monotonically increasing integer ID (with each distinct %export_path having + * its own counter). Otherwise, %export_path will be used literally (implying + * that @self must be a singleton). + * + * Returns: the path @self was exported under + */ +const char * +nm_dbus_object_export(gpointer /* (NMDBusObject *) */ self) +{ + NMDBusObject * self1 = self; + static guint64 id_counter = 0; + + g_return_val_if_fail(NM_IS_DBUS_OBJECT(self1), NULL); + + g_return_val_if_fail(!self1->internal.path, self1->internal.path); + + nm_assert(!self1->internal.is_unexporting); + + self1->internal.path = _create_export_path(NM_DBUS_OBJECT_GET_CLASS(self1)); + + self1->internal.export_version_id = ++id_counter; + + _LOGT("export: \"%s\"", self1->internal.path); + + _nm_dbus_manager_obj_export(self1); + + _emit_exported_changed(self1); + return self1->internal.path; +} + +/** + * nm_dbus_object_unexport: + * @self: an #NMDBusObject + * + * Unexports @self on all active D-Bus connections (and prevents it from being + * auto-exported on future connections). + */ +void +nm_dbus_object_unexport(gpointer /* (NMDBusObject *) */ self) +{ + NMDBusObject *self1 = self; + + g_return_if_fail(NM_IS_DBUS_OBJECT(self1)); + + g_return_if_fail(self1->internal.path); + + _LOGT("unexport: \"%s\"", self1->internal.path); + + /* note that we emit the signal *before* actually unexporting the object. + * The reason is, that listeners want to use this signal to know that + * the object goes away, and clear their D-Bus path to this object. + * + * But this must happen before we actually unregister the object, so + * that we first emit a D-Bus signal that other objects no longer + * reference this object, before finally unregistering the object itself. + * + * The inconvenient part is, that at this point nm_dbus_object_get_path() + * still returns the path. So, the callee needs to handle that. Possibly + * by using "nm_dbus_object_get_path_still_exported()". */ + self1->internal.is_unexporting = TRUE; + + _emit_exported_changed(self1); + + _nm_dbus_manager_obj_unexport(self1); + + nm_clear_g_free(&self1->internal.path); + self1->internal.export_version_id = 0; + + self1->internal.is_unexporting = FALSE; +} + +static gboolean +_unexport_on_idle_cb(gpointer user_data) +{ + gs_unref_object NMDBusObject *self = user_data; + + nm_dbus_object_unexport(self); + return G_SOURCE_REMOVE; +} + +void +nm_dbus_object_unexport_on_idle(gpointer /* (NMDBusObject *) */ self_take) +{ + NMDBusObject *self = g_steal_pointer(&self_take); + + if (!self) + return; + + g_return_if_fail(NM_IS_DBUS_OBJECT(self)); + + g_return_if_fail(self->internal.path); + + /* There is no mechanism to cancel or abort the unexport. It will always + * gonna happen. + * + * However, we register it to block shutdown, so that we ensure that it will happen. */ + + nm_shutdown_wait_obj_register_object(self, "unexport-dbus-obj-on-idle"); + + /* pass on ownership. */ + g_idle_add(_unexport_on_idle_cb, g_steal_pointer(&self)); +} + +/*****************************************************************************/ + +void +_nm_dbus_object_clear_and_unexport(NMDBusObject **location) +{ + NMDBusObject *self; + + g_return_if_fail(location); + if (!*location) + return; + + self = g_steal_pointer(location); + + g_return_if_fail(NM_IS_DBUS_OBJECT(self)); + + if (self->internal.path) + nm_dbus_object_unexport(self); + + g_object_unref(self); +} + +/*****************************************************************************/ + +void +nm_dbus_object_emit_signal_variant(NMDBusObject * self, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + GVariant * args) +{ + if (!self->internal.path) { + nm_g_variant_unref_floating(args); + return; + } + _nm_dbus_manager_obj_emit_signal(self, interface_info, signal_info, args); +} + +void +nm_dbus_object_emit_signal(NMDBusObject * self, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + const char * format, + ...) +{ + va_list ap; + + nm_assert(NM_IS_DBUS_OBJECT(self)); + nm_assert(format); + + if (!self->internal.path) + return; + + va_start(ap, format); + _nm_dbus_manager_obj_emit_signal(self, + interface_info, + signal_info, + g_variant_new_va(format, NULL, &ap)); + va_end(ap); +} + +/*****************************************************************************/ + +static void +dispatch_properties_changed(GObject *object, guint n_pspecs, GParamSpec **pspecs) +{ + NMDBusObject *self = NM_DBUS_OBJECT(object); + + if (self->internal.path) + _nm_dbus_manager_obj_notify(self, n_pspecs, (const GParamSpec *const *) pspecs); + + G_OBJECT_CLASS(nm_dbus_object_parent_class) + ->dispatch_properties_changed(object, n_pspecs, pspecs); +} + +/*****************************************************************************/ + +static void +nm_dbus_object_init(NMDBusObject *self) +{ + c_list_init(&self->internal.objects_lst); + c_list_init(&self->internal.registration_lst_head); + self->internal.bus_manager = nm_g_object_ref(nm_dbus_manager_get()); +} + +static void +constructed(GObject *object) +{ + NMDBusObjectClass *klass; + + G_OBJECT_CLASS(nm_dbus_object_parent_class)->constructed(object); + + klass = NM_DBUS_OBJECT_GET_CLASS(object); + + if (klass->export_on_construction) + nm_dbus_object_export((NMDBusObject *) object); + + /* NMDBusObject types should be very careful when overwriting notify(). + * It is possible to do, but this is a reminder that it's probably not + * a good idea. + * + * It's not a good idea, because NMDBusObject uses dispatch_properties_changed() + * to emit signals about a bunch of property changes. So, we want to make + * use of g_object_freeze_notify() / g_object_thaw_notify() to combine multiple + * property changes in one signal on D-Bus. Note that notify() is not invoked + * while the signal is frozen, that means, whatever you do inside notify() + * will not make it into the same batch of PropertiesChanged signal. That is + * confusing, and probably not what you want. + * + * Simple solution: don't overwrite notify(). */ + nm_assert(!G_OBJECT_CLASS(klass)->notify); +} + +static void +dispose(GObject *object) +{ + NMDBusObject *self = NM_DBUS_OBJECT(object); + + /* Objects should have already been unexported by their owner, unless + * we are quitting, where many objects stick around until exit. + */ + if (self->internal.path) { + if (!nm_dbus_manager_is_stopping(nm_dbus_object_get_manager(self))) + g_warn_if_reached(); + nm_dbus_object_unexport(self); + } + + G_OBJECT_CLASS(nm_dbus_object_parent_class)->dispose(object); + + g_clear_object(&self->internal.bus_manager); +} + +static void +nm_dbus_object_class_init(NMDBusObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->dispatch_properties_changed = dispatch_properties_changed; + + signals[EXPORTED_CHANGED] = g_signal_new(NM_DBUS_OBJECT_EXPORTED_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/nm-dbus-object.h b/src/core/nm-dbus-object.h new file mode 100644 index 0000000..d94bd5d --- /dev/null +++ b/src/core/nm-dbus-object.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_DBUS_OBJECT_H__ +#define __NM_DBUS_OBJECT_H__ + +/*****************************************************************************/ + +#include "c-list/src/c-list.h" +#include "nm-dbus-utils.h" + +/*****************************************************************************/ + +typedef struct { + const char *path; + + /* if path is of type NM_DBUS_EXPORT_PATH_NUMBERED(), we need a + * per-class counter when generating a new numbered path. + * + * Each NMDBusObjectClass instance has a shallow clone of the NMDBusObjectClass parent + * instance in every derived type. Hence we cannot embed the counter there directly, + * because it must be shared, e.g. between NMDeviceBond and NMDeviceEthernet. + * Make int_counter a pointer to the actual counter that is used by ever sibling + * class. */ + long long unsigned *int_counter; +} NMDBusExportPath; + +#define NM_DBUS_EXPORT_PATH_STATIC(basepath) \ + ({ \ + ((NMDBusExportPath){ \ + .path = "" basepath "", \ + }); \ + }) + +#define NM_DBUS_EXPORT_PATH_NUMBERED(basepath) \ + ({ \ + static long long unsigned _int_counter = 0; \ + ((NMDBusExportPath){ \ + .path = "" basepath "/%llu", \ + .int_counter = &_int_counter, \ + }); \ + }) + +/*****************************************************************************/ + +/* "org.freedesktop.NetworkManager.Device.Statistics" is a special interface, + * because although it has a legacy PropertiesChanged signal, it only notifies + * about properties that actually exist on that interface. That is, because it + * was added with 1.4.0 release, and thus didn't have the broken behavior like + * other legacy interfaces. Those notify about *all* properties, even if they + * are not part of that D-Bus interface. See also "include_in_legacy_property_changed" + * and "legacy_property_changed". */ +extern const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics; + +/*****************************************************************************/ + +#define NM_TYPE_DBUS_OBJECT (nm_dbus_object_get_type()) +#define NM_DBUS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DBUS_OBJECT, NMDBusObject)) +#define NM_DBUS_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DBUS_OBJECT, NMDBusObjectClass)) +#define NM_IS_DBUS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DBUS_OBJECT)) +#define NM_IS_DBUS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DBUS_OBJECT)) +#define NM_DBUS_OBJECT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DBUS_OBJECT, NMDBusObjectClass)) + +#define NM_DBUS_OBJECT_EXPORTED_CHANGED "exported-changed" + +/* NMDBusObject and NMDBusManager cooperate strongly. Hence, there is an + * internal data structure attached to the NMDBusObject accessible to both of them. */ +struct _NMDBusObjectInternal { + char * path; + NMDBusManager *bus_manager; + CList objects_lst; + CList registration_lst_head; + + /* we perform asynchronous operation on exported objects. For example, we receive + * a Set property call, and asynchronously validate the operation. We must make + * sure that when the authentication is complete, that we are still looking at + * the same (exported) object. In the meantime, the object could have been + * unexported, or even re-exported afterwards. If that happens, we want + * to fail the request. For that, we keep track of a version id. */ + guint64 export_version_id; + bool is_unexporting : 1; +}; + +struct _NMDBusObject { + GObject parent; + struct _NMDBusObjectInternal internal; +}; + +#define NM_DEFINE_DBUS_INTERFACE_INFO(...) \ + ((NMDBusInterfaceInfo *) (&((const NMDBusInterfaceInfo){__VA_ARGS__}))) + +typedef struct { + GObjectClass parent; + + NMDBusExportPath export_path; + + const NMDBusInterfaceInfoExtended *const *interface_infos; + + bool export_on_construction; +} NMDBusObjectClass; + +GType nm_dbus_object_get_type(void); + +static inline NMDBusManager * +nm_dbus_object_get_manager(NMDBusObject *obj) +{ + nm_assert(NM_IS_DBUS_OBJECT(obj)); + + return obj->internal.bus_manager; +} + +static inline guint64 +nm_dbus_object_get_export_version_id(NMDBusObject *obj) +{ + nm_assert(NM_IS_DBUS_OBJECT(obj)); + + return obj->internal.export_version_id; +} + +/** + * nm_dbus_object_get_path: + * @self: an #NMDBusObject + * + * Gets @self's D-Bus path. + * + * Returns: @self's D-Bus path, or %NULL if @self is not exported. + */ +static inline const char * +nm_dbus_object_get_path(NMDBusObject *self) +{ + g_return_val_if_fail(NM_IS_DBUS_OBJECT(self), NULL); + + return self->internal.path; +} + +/** + * nm_dbus_object_is_exported: + * @self: an #NMDBusObject + * + * Checks if @self is exported + * + * Returns: %TRUE if @self is exported + */ +static inline gboolean +nm_dbus_object_is_exported(NMDBusObject *self) +{ + return !!nm_dbus_object_get_path(self); +} + +static inline const char * +nm_dbus_object_get_path_still_exported(NMDBusObject *self) +{ + g_return_val_if_fail(NM_IS_DBUS_OBJECT(self), NULL); + + /* like nm_dbus_object_get_path(), however, while unexporting + * (exported-changed signal), returns %NULL instead of the path. */ + return self->internal.is_unexporting ? NULL : self->internal.path; +} + +const char *nm_dbus_object_export(gpointer /* (NMDBusObject *) */ self); +void nm_dbus_object_unexport(gpointer /* (NMDBusObject *) */ self); + +void nm_dbus_object_unexport_on_idle(gpointer /* (NMDBusObject *) */ self_take); + +void _nm_dbus_object_clear_and_unexport(NMDBusObject **location); +#define nm_dbus_object_clear_and_unexport(location) \ + _nm_dbus_object_clear_and_unexport((NMDBusObject **) (location)) + +void nm_dbus_object_emit_signal_variant(NMDBusObject * self, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + GVariant * args); + +void nm_dbus_object_emit_signal(NMDBusObject * self, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + const char * format, + ...); + +#endif /* __NM_DBUS_OBJECT_H__ */ diff --git a/src/core/nm-dbus-utils.c b/src/core/nm-dbus-utils.c new file mode 100644 index 0000000..40e10e2 --- /dev/null +++ b/src/core/nm-dbus-utils.c @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-utils.h" + +#include "nm-dbus-object.h" + +/*****************************************************************************/ + +const GDBusSignalInfo nm_signal_info_property_changed_legacy = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "PropertiesChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("properties", "a{sv}"), ), ); + +GDBusPropertyInfo * +nm_dbus_utils_interface_info_lookup_property(const GDBusInterfaceInfo *interface_info, + const char * property_name, + guint * property_idx) +{ + guint i; + + nm_assert(interface_info); + nm_assert(property_name); + + /* there is also g_dbus_interface_info_lookup_property(), however that makes use + * of a global cache. */ + if (interface_info->properties) { + for (i = 0; interface_info->properties[i]; i++) { + GDBusPropertyInfo *info = interface_info->properties[i]; + + if (nm_streq(info->name, property_name)) { + NM_SET_OUT(property_idx, i); + return info; + } + } + } + + return NULL; +} + +GDBusMethodInfo * +nm_dbus_utils_interface_info_lookup_method(const GDBusInterfaceInfo *interface_info, + const char * method_name) +{ + guint i; + + nm_assert(interface_info); + nm_assert(method_name); + + /* there is also g_dbus_interface_info_lookup_property(), however that makes use + * of a global cache. */ + if (interface_info->methods) { + for (i = 0; interface_info->methods[i]; i++) { + GDBusMethodInfo *info = interface_info->methods[i]; + + if (nm_streq(info->name, method_name)) + return info; + } + } + + return NULL; +} + +GVariant * +nm_dbus_utils_get_property(GObject *obj, const char *signature, const char *property_name) +{ + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + GParamSpec * pspec; + GVariant * variant; + + nm_assert(G_IS_OBJECT(obj)); + nm_assert(g_variant_type_string_is_valid(signature)); + nm_assert(property_name && property_name[0]); + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), property_name); + + nm_assert(pspec); + + nm_assert(pspec->value_type != G_TYPE_VARIANT + || nm_streq((char *) (((GParamSpecVariant *) pspec)->type), signature)); + + g_value_init(&value, pspec->value_type); + + g_object_get_property(obj, property_name, &value); + + variant = g_dbus_gvalue_to_gvariant(&value, G_VARIANT_TYPE(signature)); + + nm_assert(!variant || g_variant_is_of_type(variant, G_VARIANT_TYPE(signature))); + + /* returns never-floating variant */ + nm_assert(!variant || !g_variant_is_floating(variant)); + + return variant; +} + +/*****************************************************************************/ + +void +nm_dbus_utils_g_value_set_object_path(GValue *value, gpointer object) +{ + const char *path; + + g_return_if_fail(!object || NM_IS_DBUS_OBJECT(object)); + + if (object && (path = nm_dbus_object_get_path(object))) + g_value_set_string(value, path); + else + g_value_set_string(value, NULL); +} + +void +nm_dbus_utils_g_value_set_object_path_still_exported(GValue *value, gpointer object) +{ + const char *path; + + g_return_if_fail(!object || NM_IS_DBUS_OBJECT(object)); + + if (object && (path = nm_dbus_object_get_path_still_exported(object))) + g_value_set_string(value, path); + else + g_value_set_string(value, "/"); +} + +void +nm_dbus_utils_g_value_set_object_path_from_hash( + GValue * value, + GHashTable *hash /* has keys of NMDBusObject type. */, + gboolean expect_all_exported) +{ + NMDBusObject * obj; + char ** strv; + guint i, n; + GHashTableIter iter; + + nm_assert(value); + nm_assert(hash); + + n = g_hash_table_size(hash); + strv = g_new(char *, n + 1); + i = 0; + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &obj, NULL)) { + const char *path; + + path = nm_dbus_object_get_path_still_exported(obj); + if (!path) { + nm_assert(!expect_all_exported); + continue; + } + strv[i++] = g_strdup(path); + } + nm_assert(i <= n); + strv[i] = NULL; + + /* sort the names, to give a well-defined, stable order. */ + nm_utils_strv_sort(strv, i); + + g_value_take_boxed(value, strv); +} + +const char ** +nm_dbus_utils_get_paths_for_clist(const CList *lst_head, + gssize lst_len, + guint member_offset, + gboolean expect_all_exported) +{ + const CList *iter; + const char **strv; + const char * path; + gsize i, n; + + nm_assert(lst_head); + + if (lst_len < 0) + n = c_list_length(lst_head); + else { + n = lst_len; + nm_assert(n == c_list_length(lst_head)); + } + + i = 0; + strv = g_new(const char *, n + 1); + c_list_for_each (iter, lst_head) { + NMDBusObject *obj = (NMDBusObject *) (((const char *) iter) - member_offset); + + path = nm_dbus_object_get_path(obj); + if (!path) { + nm_assert(expect_all_exported); + continue; + } + + nm_assert(i < n); + strv[i++] = path; + } + nm_assert(i <= n); + strv[i] = NULL; + + return strv; +} + +/*****************************************************************************/ + +void +nm_dbus_track_obj_path_init(NMDBusTrackObjPath *track, GObject *target, const GParamSpec *pspec) +{ + nm_assert(track); + nm_assert(G_IS_OBJECT(target)); + nm_assert(G_IS_PARAM_SPEC(pspec)); + + track->_obj = NULL; + track->_notify_target = target; + track->_notify_pspec = pspec; + track->_notify_signal_id = 0; + track->_visible = FALSE; +} + +void +nm_dbus_track_obj_path_deinit(NMDBusTrackObjPath *track) +{ + /* we allow deinit() to be called multiple times (e.g. from + * dispose(), which must be re-entrant). */ + nm_assert(track); + nm_assert(!track->_notify_target || G_IS_OBJECT(track->_notify_target)); + + nm_clear_g_signal_handler(track->obj, &track->_notify_signal_id); + track->_notify_target = NULL; + track->_notify_pspec = NULL; + track->_visible = FALSE; + nm_clear_g_object(&track->_obj); +} + +void +nm_dbus_track_obj_path_notify(const NMDBusTrackObjPath *track) +{ + nm_assert(track); + nm_assert(G_IS_OBJECT(track->_notify_target)); + nm_assert(G_IS_PARAM_SPEC(track->_notify_pspec)); + + g_object_notify_by_pspec(track->_notify_target, (GParamSpec *) track->_notify_pspec); +} + +const char * +nm_dbus_track_obj_path_get(const NMDBusTrackObjPath *track) +{ + nm_assert(track); + nm_assert(G_IS_OBJECT(track->_notify_target)); + + return track->obj && track->visible ? nm_dbus_object_get_path_still_exported(track->obj) : NULL; +} + +static void +_track_obj_exported_changed(NMDBusObject *obj, NMDBusTrackObjPath *track) +{ + nm_dbus_track_obj_path_notify(track); +} + +void +nm_dbus_track_obj_path_set(NMDBusTrackObjPath *track, gpointer obj, gboolean visible) +{ + gs_unref_object NMDBusObject *old_obj = NULL; + const char * old_path; + + nm_assert(track); + nm_assert(G_IS_OBJECT(track->_notify_target)); + + g_return_if_fail(!obj || NM_IS_DBUS_OBJECT(obj)); + + if (track->obj == obj && track->visible == !!visible) + return; + + old_path = nm_dbus_track_obj_path_get(track); + + track->_visible = visible; + + if (track->obj != obj) { + nm_clear_g_signal_handler(track->obj, &track->_notify_signal_id); + + old_obj = track->obj; + track->_obj = nm_g_object_ref(obj); + + if (obj) { + track->_notify_signal_id = g_signal_connect(obj, + NM_DBUS_OBJECT_EXPORTED_CHANGED, + G_CALLBACK(_track_obj_exported_changed), + track); + } + } + + if (!nm_streq0(old_path, nm_dbus_track_obj_path_get(track))) + nm_dbus_track_obj_path_notify(track); +} diff --git a/src/core/nm-dbus-utils.h b/src/core/nm-dbus-utils.h new file mode 100644 index 0000000..501e199 --- /dev/null +++ b/src/core/nm-dbus-utils.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_DBUS_UTILS_H__ +#define __NM_DBUS_UTILS_H__ + +/*****************************************************************************/ + +struct _NMDBusInterfaceInfoExtended; +struct _NMDBusMethodInfoExtended; + +struct _NMDBusPropertyInfoExtendedBase { + GDBusPropertyInfo _parent; + const char * property_name; + + /* Whether the properties needs to be notified on the legacy + * PropertyChanged signal. This is only to preserve API, new + * properties should not use this. */ + bool include_in_legacy_property_changed; +}; + +struct _NMDBusPropertyInfoExtendedReadWritable { + struct _NMDBusPropertyInfoExtendedBase _base; + + /* this is the polkit permission type for authenticating setting + * the property. */ + const char *permission; + + /* this is the audit operation type for writing the property. */ + const char *audit_op; +}; + +typedef struct { + union { + GDBusPropertyInfo _parent; + struct _NMDBusPropertyInfoExtendedBase _base; + struct _NMDBusPropertyInfoExtendedReadWritable writable; + + /* duplicate the base structure in the union, so that the common fields + * are accessible directly in the parent struct. */ + struct { + GDBusPropertyInfo parent; + const char * property_name; + + /* Whether the properties needs to be notified on the legacy + * PropertyChanged signal. This is only to preserve API, new + * properties should not use this. */ + bool include_in_legacy_property_changed; + }; + }; +} NMDBusPropertyInfoExtended; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMDBusPropertyInfoExtended, property_name) + == G_STRUCT_OFFSET(struct _NMDBusPropertyInfoExtendedBase, property_name)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMDBusPropertyInfoExtended, include_in_legacy_property_changed) + == G_STRUCT_OFFSET(struct _NMDBusPropertyInfoExtendedBase, + include_in_legacy_property_changed)); + +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL(m_name, \ + m_signature, \ + m_property_name, \ + m_include_in_legacy_property_changed) \ + ((GDBusPropertyInfo *) &((const struct _NMDBusPropertyInfoExtendedBase){ \ + ._parent = \ + { \ + .ref_count = -1, \ + .name = m_name, \ + .signature = m_signature, \ + .flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, \ + }, \ + .property_name = m_property_name, \ + .include_in_legacy_property_changed = m_include_in_legacy_property_changed, \ + })) + +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(m_name, m_signature, m_property_name) \ + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL(m_name, m_signature, m_property_name, FALSE) + +/* define a legacy property. Do not use for new code. */ +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(m_name, m_signature, m_property_name) \ + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL(m_name, m_signature, m_property_name, TRUE) + +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL( \ + m_name, \ + m_signature, \ + m_property_name, \ + m_permission, \ + m_audit_op, \ + m_include_in_legacy_property_changed) \ + ((GDBusPropertyInfo *) &((const struct _NMDBusPropertyInfoExtendedReadWritable){ \ + ._base = \ + { \ + ._parent = \ + { \ + .ref_count = -1, \ + .name = m_name, \ + .signature = m_signature, \ + .flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE \ + | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, \ + }, \ + .property_name = m_property_name, \ + .include_in_legacy_property_changed = m_include_in_legacy_property_changed, \ + }, \ + .permission = m_permission, \ + .audit_op = m_audit_op, \ + })) + +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE(m_name, \ + m_signature, \ + m_property_name, \ + m_permission, \ + m_audit_op) \ + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL(m_name, \ + m_signature, \ + m_property_name, \ + m_permission, \ + m_audit_op, \ + FALSE) + +/* define a legacy property. Do not use for new code. */ +#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L(m_name, \ + m_signature, \ + m_property_name, \ + m_permission, \ + m_audit_op) \ + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL(m_name, \ + m_signature, \ + m_property_name, \ + m_permission, \ + m_audit_op, \ + TRUE) + +typedef struct _NMDBusMethodInfoExtended { + GDBusMethodInfo parent; + void (*handle)(NMDBusObject * obj, + const struct _NMDBusInterfaceInfoExtended *interface_info, + const struct _NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters); + bool allow_during_shutdown; +} NMDBusMethodInfoExtended; + +#define NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(parent_, ...) \ + ((GDBusMethodInfo *) (&((const NMDBusMethodInfoExtended){.parent = parent_, __VA_ARGS__}))) + +typedef struct _NMDBusInterfaceInfoExtended { + GDBusInterfaceInfo parent; + + /* Whether the interface has a legacy property changed signal (@nm_signal_info_property_changed_legacy). + * New interfaces should not use this. */ + bool legacy_property_changed : 1; +} NMDBusInterfaceInfoExtended; + +extern const GDBusSignalInfo nm_signal_info_property_changed_legacy; + +#define NM_DBUS_INTERFACE_INFOS(...) \ + ({ \ + static const NMDBusInterfaceInfoExtended *const _interface_infos[] = { \ + __VA_ARGS__, \ + NULL, \ + }; \ + _interface_infos; \ + }); + +/*****************************************************************************/ + +GDBusPropertyInfo * +nm_dbus_utils_interface_info_lookup_property(const GDBusInterfaceInfo *interface_info, + const char * property_name, + guint * property_idx); + +GDBusMethodInfo * +nm_dbus_utils_interface_info_lookup_method(const GDBusInterfaceInfo *interface_info, + const char * method_name); + +GVariant * +nm_dbus_utils_get_property(GObject *obj, const char *signature, const char *property_name); + +/*****************************************************************************/ + +struct CList; + +const char **nm_dbus_utils_get_paths_for_clist(const struct CList *lst_head, + gssize lst_len, + guint member_offset, + gboolean expect_all_exported); + +void nm_dbus_utils_g_value_set_object_path(GValue *value, gpointer object); + +void nm_dbus_utils_g_value_set_object_path_still_exported(GValue *value, gpointer object); + +void nm_dbus_utils_g_value_set_object_path_from_hash(GValue * value, + GHashTable *hash, + gboolean expect_all_exported); + +/*****************************************************************************/ + +typedef struct { + union { + gpointer const obj; + gpointer _obj; + }; + GObject * _notify_target; + const GParamSpec *_notify_pspec; + gulong _notify_signal_id; + union { + const bool visible; + bool _visible; + }; +} NMDBusTrackObjPath; + +void +nm_dbus_track_obj_path_init(NMDBusTrackObjPath *track, GObject *target, const GParamSpec *pspec); + +void nm_dbus_track_obj_path_deinit(NMDBusTrackObjPath *track); + +void nm_dbus_track_obj_path_notify(const NMDBusTrackObjPath *track); + +const char *nm_dbus_track_obj_path_get(const NMDBusTrackObjPath *track); + +void nm_dbus_track_obj_path_set(NMDBusTrackObjPath *track, gpointer obj, gboolean visible); + +#endif /* __NM_DBUS_UTILS_H__ */ diff --git a/src/core/nm-dcb.c b/src/core/nm-dcb.c new file mode 100644 index 0000000..df34543 --- /dev/null +++ b/src/core/nm-dcb.c @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-dcb.h" +#include "platform/nm-platform.h" +#include "NetworkManagerUtils.h" + +static const char *helper_names[] = {"dcbtool", "fcoeadm"}; + +gboolean +do_helper(const char *iface, + guint which, + DcbFunc run_func, + gpointer user_data, + GError ** error, + const char *fmt, + ...) +{ + gs_free const char **split = NULL; + gs_free char * cmdline = NULL; + gs_free const char **argv = NULL; + gsize i; + gsize u; + va_list args; + + g_return_val_if_fail(fmt != NULL, FALSE); + + va_start(args, fmt); + cmdline = g_strdup_vprintf(fmt, args); + va_end(args); + + split = nm_utils_strsplit_set_with_empty(cmdline, " "); + if (!split) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "failure parsing %s command line", + helper_names[which]); + return FALSE; + } + + /* Allocate space for path, custom arg, interface name, arguments, and NULL */ + i = 0; + argv = g_new(const char *, NM_PTRARRAY_LEN(split) + 4); + argv[i++] = NULL; /* Placeholder for dcbtool path */ + if (which == DCBTOOL) { + argv[i++] = "sc"; + argv[i++] = (char *) iface; + } + for (u = 0; split[u]; u++) + argv[i++] = split[u]; + argv[i++] = NULL; + + if (!run_func((char **) argv, which, user_data, error)) { + g_assert(!error || *error); + return FALSE; + } + + return TRUE; +} + +gboolean +_dcb_enable(const char *iface, + gboolean enable, + DcbFunc run_func, + gpointer user_data, + GError ** error) +{ + if (enable) + return do_helper(iface, DCBTOOL, run_func, user_data, error, "dcb on"); + else + return do_helper(iface, DCBTOOL, run_func, user_data, error, "dcb off"); +} + +#define SET_FLAGS(f, tag) \ + G_STMT_START \ + { \ + if (!do_helper(iface, \ + DCBTOOL, \ + run_func, \ + user_data, \ + error, \ + tag " e:%c a:%c w:%c", \ + f & NM_SETTING_DCB_FLAG_ENABLE ? '1' : '0', \ + f & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0', \ + f & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0')) \ + return FALSE; \ + } \ + G_STMT_END + +#define SET_APP(f, s, tag) \ + G_STMT_START \ + { \ + int prio = nm_setting_dcb_get_app_##tag##_priority(s); \ + \ + SET_FLAGS(f, "app:" #tag); \ + if ((f & NM_SETTING_DCB_FLAG_ENABLE) && (prio >= 0)) { \ + if (!do_helper(iface, \ + DCBTOOL, \ + run_func, \ + user_data, \ + error, \ + "app:" #tag " appcfg:%02x", \ + (1 << prio))) \ + return FALSE; \ + } \ + } \ + G_STMT_END + +gboolean +_dcb_setup(const char * iface, + NMSettingDcb *s_dcb, + DcbFunc run_func, + gpointer user_data, + GError ** error) +{ + NMSettingDcbFlags flags; + guint i; + + g_assert(s_dcb); + + /* FCoE */ + flags = nm_setting_dcb_get_app_fcoe_flags(s_dcb); + SET_APP(flags, s_dcb, fcoe); + + /* iSCSI */ + flags = nm_setting_dcb_get_app_iscsi_flags(s_dcb); + SET_APP(flags, s_dcb, iscsi); + + /* FIP */ + flags = nm_setting_dcb_get_app_fip_flags(s_dcb); + SET_APP(flags, s_dcb, fip); + + /* Priority Flow Control */ + flags = nm_setting_dcb_get_priority_flow_control_flags(s_dcb); + SET_FLAGS(flags, "pfc"); + if (flags & NM_SETTING_DCB_FLAG_ENABLE) { + char buf[10]; + + for (i = 0; i < 8; i++) + buf[i] = nm_setting_dcb_get_priority_flow_control(s_dcb, i) ? '1' : '0'; + buf[i] = 0; + if (!do_helper(iface, DCBTOOL, run_func, user_data, error, "pfc pfcup:%s", buf)) + return FALSE; + } + + /* Priority Groups */ + flags = nm_setting_dcb_get_priority_group_flags(s_dcb); + if (flags & NM_SETTING_DCB_FLAG_ENABLE) { + GString *s; + gboolean success; + guint id; + + s = g_string_sized_new(150); + + g_string_append_printf(s, + "pg e:1 a:%c w:%c", + flags & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0', + flags & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0'); + + /* Priority Groups */ + g_string_append(s, " pgid:"); + for (i = 0; i < 8; i++) { + id = nm_setting_dcb_get_priority_group_id(s_dcb, i); + g_assert(id < 8 || id == 15); + g_string_append_c(s, (id < 8) ? ('0' + id) : 'f'); + } + + /* Priority Group Bandwidth */ + g_string_append_printf(s, + " pgpct:%u,%u,%u,%u,%u,%u,%u,%u", + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 0), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 1), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 2), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 3), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 4), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 5), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 6), + nm_setting_dcb_get_priority_group_bandwidth(s_dcb, 7)); + + /* Priority Bandwidth */ + g_string_append_printf(s, + " uppct:%u,%u,%u,%u,%u,%u,%u,%u", + nm_setting_dcb_get_priority_bandwidth(s_dcb, 0), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 1), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 2), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 3), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 4), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 5), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 6), + nm_setting_dcb_get_priority_bandwidth(s_dcb, 7)); + + /* Strict Bandwidth */ + g_string_append(s, " strict:"); + for (i = 0; i < 8; i++) + g_string_append_c(s, + nm_setting_dcb_get_priority_strict_bandwidth(s_dcb, i) ? '1' : '0'); + + /* Priority Traffic Class */ + g_string_append(s, " up2tc:"); + for (i = 0; i < 8; i++) { + id = nm_setting_dcb_get_priority_traffic_class(s_dcb, i); + g_assert(id < 8); + g_string_append_c(s, '0' + id); + } + + success = do_helper(iface, DCBTOOL, run_func, user_data, error, "%s", s->str); + g_string_free(s, TRUE); + if (!success) + return FALSE; + } else { + /* Ignore disable failure since lldpad <= 0.9.46 does not support disabling + * priority groups without specifying an entire PG config. + */ + (void) do_helper(iface, DCBTOOL, run_func, user_data, error, "pg e:0"); + } + + return TRUE; +} + +gboolean +_dcb_cleanup(const char *iface, DcbFunc run_func, gpointer user_data, GError **error) +{ + const char *cmds[] = + {"app:fcoe e:0", "app:iscsi e:0", "app:fip e:0", "pfc e:0", "pg e:0", NULL}; + const char **iter = cmds; + gboolean success = TRUE; + + /* Turn everything off and return first error we get (if any) */ + while (iter && *iter) { + if (!do_helper(iface, DCBTOOL, run_func, user_data, success ? error : NULL, "%s", *iter)) + success = FALSE; + iter++; + } + + if (!_dcb_enable(iface, FALSE, run_func, user_data, success ? error : NULL)) + success = FALSE; + + return success; +} + +gboolean +_fcoe_setup(const char * iface, + NMSettingDcb *s_dcb, + DcbFunc run_func, + gpointer user_data, + GError ** error) +{ + NMSettingDcbFlags flags; + + g_assert(s_dcb); + + flags = nm_setting_dcb_get_app_fcoe_flags(s_dcb); + if (flags & NM_SETTING_DCB_FLAG_ENABLE) { + const char *mode = nm_setting_dcb_get_app_fcoe_mode(s_dcb); + + if (!do_helper(NULL, FCOEADM, run_func, user_data, error, "-m %s -c %s", mode, iface)) + return FALSE; + } else { + if (!do_helper(NULL, FCOEADM, run_func, user_data, error, "-d %s", iface)) + return FALSE; + } + + return TRUE; +} + +gboolean +_fcoe_cleanup(const char *iface, DcbFunc run_func, gpointer user_data, GError **error) +{ + return do_helper(NULL, FCOEADM, run_func, user_data, error, "-d %s", iface); +} + +static gboolean +run_helper(char **argv, guint which, gpointer user_data, GError **error) +{ + const char *helper_path; + int exit_status = 0; + gboolean success; + char * errmsg = NULL, *outmsg = NULL; + char * cmdline; + + helper_path = nm_utils_find_helper((which == DCBTOOL) ? "dcbtool" : "fcoeadm", NULL, error); + if (!helper_path) + return FALSE; + + argv[0] = (char *) helper_path; + cmdline = g_strjoinv(" ", argv); + nm_log_dbg(LOGD_DCB, "%s", cmdline); + + success = g_spawn_sync("/", + argv, + NULL, + 0 /*G_SPAWN_DEFAULT*/, + NULL, + NULL, + &outmsg, + &errmsg, + &exit_status, + error); + /* Log any stderr output */ + if (success && WIFEXITED(exit_status) && WEXITSTATUS(exit_status) && (errmsg || outmsg)) { + gboolean ignore_error = FALSE; + + /* Ignore fcoeadm "success" errors like when FCoE is already set up */ + if (errmsg && strstr(errmsg, "Connection already created")) + ignore_error = TRUE; + + if (ignore_error == FALSE) { + nm_log_warn(LOGD_DCB, + "'%s' failed: '%s'", + cmdline, + (errmsg && strlen(errmsg)) ? errmsg : outmsg); + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Failed to run '%s'", + cmdline); + success = FALSE; + } + } + + g_free(outmsg); + g_free(errmsg); + g_free(cmdline); + return success; +} + +gboolean +nm_dcb_enable(const char *iface, gboolean enable, GError **error) +{ + return _dcb_enable(iface, enable, run_helper, GUINT_TO_POINTER(DCBTOOL), error); +} + +gboolean +nm_dcb_setup(const char *iface, NMSettingDcb *s_dcb, GError **error) +{ + gboolean success; + + success = _dcb_setup(iface, s_dcb, run_helper, GUINT_TO_POINTER(DCBTOOL), error); + if (success) + success = _fcoe_setup(iface, s_dcb, run_helper, GUINT_TO_POINTER(FCOEADM), error); + + return success; +} + +static void +carrier_wait(const char *iface, guint secs, gboolean up) +{ + int ifindex, count = secs * 10; + + g_return_if_fail(iface != NULL); + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, iface); + if (ifindex > 0) { + /* To work around driver quirks and lldpad handling of carrier status, + * we must wait a short period of time to see if the carrier goes + * down, and then wait for the carrier to come back up again. Otherwise + * subsequent lldpad calls may fail with "Device not found, link down + * or DCB not enabled" errors. + */ + nm_log_dbg(LOGD_DCB, "(%s): cleanup waiting for carrier %s", iface, up ? "up" : "down"); + g_usleep(G_USEC_PER_SEC / 4); + while (nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex) != up && count-- > 0) { + g_usleep(G_USEC_PER_SEC / 10); + nm_platform_link_refresh(NM_PLATFORM_GET, ifindex); + } + } +} + +gboolean +nm_dcb_cleanup(const char *iface, GError **error) +{ + /* Ignore FCoE cleanup errors */ + _fcoe_cleanup(iface, run_helper, GUINT_TO_POINTER(FCOEADM), NULL); + + /* Must pause a bit to wait for carrier-up since disabling FCoE may + * cause the device to take the link down, making lldpad return errors. + */ + carrier_wait(iface, 2, FALSE); + carrier_wait(iface, 4, TRUE); + + return _dcb_cleanup(iface, run_helper, GUINT_TO_POINTER(DCBTOOL), error); +} diff --git a/src/core/nm-dcb.h b/src/core/nm-dcb.h new file mode 100644 index 0000000..7cb5e7e --- /dev/null +++ b/src/core/nm-dcb.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DCB_H__ +#define __NETWORKMANAGER_DCB_H__ + +#include "nm-setting-dcb.h" + +gboolean nm_dcb_enable(const char *iface, gboolean enable, GError **error); +gboolean nm_dcb_setup(const char *iface, NMSettingDcb *s_dcb, GError **error); +gboolean nm_dcb_cleanup(const char *iface, GError **error); + +/* For testcases only! */ +typedef gboolean (*DcbFunc)(char **argv, guint which, gpointer user_data, GError **error); + +#define DCBTOOL 0 +#define FCOEADM 1 + +gboolean do_helper(const char *iface, + guint which, + DcbFunc run_func, + gpointer user_data, + GError ** error, + const char *fmt, + ...) G_GNUC_PRINTF(6, 7); + +gboolean _dcb_enable(const char *iface, + gboolean enable, + DcbFunc run_func, + gpointer user_data, + GError ** error); + +gboolean _dcb_setup(const char * iface, + NMSettingDcb *s_dcb, + DcbFunc run_func, + gpointer user_data, + GError ** error); + +gboolean _dcb_cleanup(const char *iface, DcbFunc run_func, gpointer user_data, GError **error); + +gboolean _fcoe_setup(const char * iface, + NMSettingDcb *s_dcb, + DcbFunc run_func, + gpointer user_data, + GError ** error); + +gboolean _fcoe_cleanup(const char *iface, DcbFunc run_func, gpointer user_data, GError **error); + +#endif /* __NETWORKMANAGER_DCB_H__ */ diff --git a/src/core/nm-dhcp-config.c b/src/core/nm-dhcp-config.c new file mode 100644 index 0000000..390a73f --- /dev/null +++ b/src/core/nm-dhcp-config.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-config.h" + +#include "nm-dbus-interface.h" +#include "nm-utils.h" +#include "nm-dbus-object.h" +#include "nm-core-utils.h" + +/*****************************************************************************/ + +#define NM_TYPE_DHCP4_CONFIG (nm_dhcp4_config_get_type()) +#define NM_DHCP4_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4Config)) +#define NM_DHCP4_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass)) +#define NM_IS_DHCP4_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP4_CONFIG)) +#define NM_IS_DHCP4_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP4_CONFIG)) +#define NM_DHCP4_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass)) + +typedef struct _NMDhcp4Config NMDhcp4Config; +typedef struct _NMDhcp4ConfigClass NMDhcp4ConfigClass; + +static GType nm_dhcp4_config_get_type(void); + +#define NM_TYPE_DHCP6_CONFIG (nm_dhcp6_config_get_type()) +#define NM_DHCP6_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6Config)) +#define NM_DHCP6_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass)) +#define NM_IS_DHCP6_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP6_CONFIG)) +#define NM_IS_DHCP6_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP6_CONFIG)) +#define NM_DHCP6_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass)) + +typedef struct _NMDhcp6Config NMDhcp6Config; +typedef struct _NMDhcp6ConfigClass NMDhcp6ConfigClass; + +static GType nm_dhcp6_config_get_type(void); + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDhcpConfig, PROP_OPTIONS, ); + +typedef struct { + GVariant *options; +} NMDhcpConfigPrivate; + +struct _NMDhcpConfig { + NMDBusObject parent; + NMDhcpConfigPrivate _priv; +}; + +struct _NMDhcpConfigClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_ABSTRACT_TYPE(NMDhcpConfig, nm_dhcp_config, NM_TYPE_DBUS_OBJECT) + +#define NM_DHCP_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpConfig, NM_IS_DHCP_CONFIG) + +/*****************************************************************************/ + +void +nm_dhcp_config_set_options(NMDhcpConfig *self, GHashTable *options) +{ + NMDhcpConfigPrivate *priv; + + g_return_if_fail(NM_IS_DHCP_CONFIG(self)); + g_return_if_fail(options); + + priv = NM_DHCP_CONFIG_GET_PRIVATE(self); + + nm_g_variant_unref(priv->options); + priv->options = g_variant_ref_sink(nm_utils_strdict_to_variant_asv(options)); + _notify(self, PROP_OPTIONS); +} + +const char * +nm_dhcp_config_get_option(NMDhcpConfig *self, const char *key) +{ + NMDhcpConfigPrivate *priv; + const char * value; + + g_return_val_if_fail(NM_IS_DHCP_CONFIG(self), NULL); + g_return_val_if_fail(key, NULL); + + priv = NM_DHCP_CONFIG_GET_PRIVATE(self); + + if (priv->options && g_variant_lookup(priv->options, key, "&s", &value)) + return value; + else + return NULL; +} + +GVariant * +nm_dhcp_config_get_options(NMDhcpConfig *self) +{ + g_return_val_if_fail(NM_IS_DHCP_CONFIG(self), NULL); + + return NM_DHCP_CONFIG_GET_PRIVATE(self)->options; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDhcpConfigPrivate *priv = NM_DHCP_CONFIG_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_OPTIONS: + g_value_set_variant(value, + priv->options ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_dhcp_config_init(NMDhcpConfig *self) +{} + +NMDhcpConfig * +nm_dhcp_config_new(int addr_family) +{ + nm_assert_addr_family(addr_family); + + return g_object_new(addr_family != AF_INET ? NM_TYPE_DHCP6_CONFIG : NM_TYPE_DHCP4_CONFIG, NULL); +} + +static void +finalize(GObject *object) +{ + NMDhcpConfigPrivate *priv = NM_DHCP_CONFIG_GET_PRIVATE(object); + + nm_g_variant_unref(priv->options); + + G_OBJECT_CLASS(nm_dhcp_config_parent_class)->finalize(object); +} + +static void +nm_dhcp_config_class_init(NMDhcpConfigClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(config_class); + + object_class->get_property = get_property; + object_class->finalize = finalize; + + obj_properties[PROP_OPTIONS] = g_param_spec_variant(NM_DHCP_CONFIG_OPTIONS, + "", + "", + G_VARIANT_TYPE("a{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +/*****************************************************************************/ + +struct _NMDhcp4Config { + NMDhcpConfig parent; +}; + +struct _NMDhcp4ConfigClass { + NMDhcpConfigClass parent; +}; + +G_DEFINE_TYPE(NMDhcp4Config, nm_dhcp4_config, NM_TYPE_DHCP_CONFIG) + +static void +nm_dhcp4_config_init(NMDhcp4Config *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_dhcp4_config = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DHCP4_CONFIG, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Options", + "a{sv}", + NM_DHCP_CONFIG_OPTIONS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_dhcp4_config_class_init(NMDhcp4ConfigClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/DHCP4Config"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_dhcp4_config); + dbus_object_class->export_on_construction = TRUE; +} + +/*****************************************************************************/ + +struct _NMDhcp6Config { + NMDhcpConfig parent; +}; + +struct _NMDhcp6ConfigClass { + NMDhcpConfigClass parent; +}; + +G_DEFINE_TYPE(NMDhcp6Config, nm_dhcp6_config, NM_TYPE_DHCP_CONFIG) + +static void +nm_dhcp6_config_init(NMDhcp6Config *self) +{} + +static const NMDBusInterfaceInfoExtended interface_info_dhcp6_config = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DHCP6_CONFIG, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Options", + "a{sv}", + NM_DHCP_CONFIG_OPTIONS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_dhcp6_config_class_init(NMDhcp6ConfigClass *klass) +{ + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/DHCP6Config"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_dhcp6_config); + dbus_object_class->export_on_construction = TRUE; +} diff --git a/src/core/nm-dhcp-config.h b/src/core/nm-dhcp-config.h new file mode 100644 index 0000000..9e68f2f --- /dev/null +++ b/src/core/nm-dhcp-config.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef __NM_DHCP_CONFIG_H__ +#define __NM_DHCP_CONFIG_H__ + +/*****************************************************************************/ + +#define NM_TYPE_DHCP_CONFIG (nm_dhcp_config_get_type()) +#define NM_DHCP_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_CONFIG, NMDhcpConfig)) +#define NM_DHCP_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_CONFIG, NMDhcpConfigClass)) +#define NM_IS_DHCP_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_CONFIG)) +#define NM_IS_DHCP_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_CONFIG)) +#define NM_DHCP_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_CONFIG, NMDhcpConfigClass)) + +#define NM_DHCP_CONFIG_OPTIONS "options" + +typedef struct _NMDhcpConfigClass NMDhcpConfigClass; + +GType nm_dhcp_config_get_type(void); + +NMDhcpConfig *nm_dhcp_config_new(int addr_family); + +int nm_dhcp_config_get_addr_family(NMDhcpConfig *self); + +void nm_dhcp_config_set_options(NMDhcpConfig *self, GHashTable *options); + +const char *nm_dhcp_config_get_option(NMDhcpConfig *self, const char *option); + +GVariant *nm_dhcp_config_get_options(NMDhcpConfig *self); + +#endif /* __NM_DHCP_CONFIG_H__ */ diff --git a/src/core/nm-dispatcher.c b/src/core/nm-dispatcher.c new file mode 100644 index 0000000..a848d41 --- /dev/null +++ b/src/core/nm-dispatcher.c @@ -0,0 +1,940 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2018 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-dispatcher.h" + +#include "nm-libnm-core-aux/nm-dispatcher-api.h" +#include "NetworkManagerUtils.h" +#include "nm-utils.h" +#include "nm-connectivity.h" +#include "nm-act-request.h" +#include "devices/nm-device.h" +#include "nm-dhcp-config.h" +#include "nm-proxy-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-manager.h" +#include "settings/nm-settings-connection.h" +#include "platform/nm-platform.h" +#include "nm-core-internal.h" + +#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */ + +#define _NMLOG_DOMAIN LOGD_DISPATCH +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "dispatcher", __VA_ARGS__) + +#define _NMLOG2_DOMAIN LOGD_DISPATCH +#define _NMLOG2(level, request_id, log_ifname, log_con_uuid, ...) \ + nm_log((level), \ + _NMLOG2_DOMAIN, \ + (log_ifname), \ + (log_con_uuid), \ + "dispatcher: (%u) " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (request_id) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +#define _NMLOG3_DOMAIN LOGD_DISPATCH +#define _NMLOG3(level, call_id, ...) \ + G_STMT_START \ + { \ + const NMDispatcherCallId *const _call_id = (call_id); \ + \ + _NMLOG2(level, \ + _call_id->request_id, \ + _call_id->log_ifname, \ + _call_id->log_con_uuid, \ + __VA_ARGS__); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +struct NMDispatcherCallId { + NMDispatcherFunc callback; + gpointer user_data; + const char * log_ifname; + const char * log_con_uuid; + NMDispatcherAction action; + guint idle_id; + guint32 request_id; + char extra_strings[]; +}; + +/*****************************************************************************/ + +/* FIXME(shutdown): on shutdown, we should not run dispatcher scripts synchronously. + * Instead, we should of course still run them asynchronously. + * + * Also, we should wait for all pending requests to complete before exiting the main-loop + * (with a watchdog). If we hit a timeout, we log a warning and quit (but leave the scripts + * running). + * + * Finally, cleanup the global structures. */ +static struct { + GDBusConnection *dbus_connection; + GHashTable * requests; + guint request_id_counter; +} gl; + +/*****************************************************************************/ + +static NMDispatcherCallId * +dispatcher_call_id_new(guint32 request_id, + NMDispatcherAction action, + NMDispatcherFunc callback, + gpointer user_data, + const char * log_ifname, + const char * log_con_uuid) +{ + NMDispatcherCallId *call_id; + gsize l_log_ifname; + gsize l_log_con_uuid; + char * extra_strings; + + l_log_ifname = log_ifname ? (strlen(log_ifname) + 1) : 0u; + l_log_con_uuid = log_con_uuid ? (strlen(log_con_uuid) + 1) : 0u; + + call_id = g_malloc(sizeof(NMDispatcherCallId) + l_log_ifname + l_log_con_uuid); + + call_id->action = action; + call_id->request_id = request_id; + call_id->callback = callback; + call_id->user_data = user_data; + call_id->idle_id = 0; + + extra_strings = &call_id->extra_strings[0]; + + if (log_ifname) { + call_id->log_ifname = extra_strings; + memcpy(extra_strings, log_ifname, l_log_ifname); + extra_strings += l_log_ifname; + } else + call_id->log_ifname = NULL; + + if (log_con_uuid) { + call_id->log_con_uuid = extra_strings; + memcpy(extra_strings, log_con_uuid, l_log_con_uuid); + } else + call_id->log_con_uuid = NULL; + + return call_id; +} + +static void +dispatcher_call_id_free(NMDispatcherCallId *call_id) +{ + nm_clear_g_source(&call_id->idle_id); + g_free(call_id); +} + +/*****************************************************************************/ + +static void +_init_dispatcher(void) +{ + if (G_UNLIKELY(gl.requests == NULL)) { + gl.requests = g_hash_table_new(nm_direct_hash, NULL); + gl.dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!gl.dbus_connection) + _LOGD("No D-Bus connection to talk with NetworkManager-dispatcher service"); + } +} + +/*****************************************************************************/ + +static void +dump_proxy_to_props(NMProxyConfig *proxy, GVariantBuilder *builder) +{ + const char *pac_url = NULL, *pac_script = NULL; + + if (nm_proxy_config_get_method(proxy) == NM_PROXY_CONFIG_METHOD_NONE) + return; + + pac_url = nm_proxy_config_get_pac_url(proxy); + if (pac_url) { + g_variant_builder_add(builder, "{sv}", "pac-url", g_variant_new_string(pac_url)); + } + + pac_script = nm_proxy_config_get_pac_script(proxy); + if (pac_script) { + g_variant_builder_add(builder, "{sv}", "pac-script", g_variant_new_string(pac_script)); + } +} + +static void +dump_ip_to_props(NMIPConfig *ip, GVariantBuilder *builder) +{ + const int addr_family = nm_ip_config_get_addr_family(ip); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPObject *obj; + GVariantBuilder int_builder; + NMDedupMultiIter ipconf_iter; + GVariant * var1; + GVariant * var2; + guint n; + guint i; + const NMPObject *default_route; + + if (IS_IPv4) + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau")); + else + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuay)")); + default_route = nm_ip_config_best_default_route_get(ip); + if (IS_IPv4) + nm_ip_config_iter_ip4_address_init(&ipconf_iter, NM_IP4_CONFIG(ip)); + else + nm_ip_config_iter_ip6_address_init(&ipconf_iter, NM_IP6_CONFIG(ip)); + while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter, + &obj, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))) { + const NMPlatformIPXAddress *addr = NMP_OBJECT_CAST_IPX_ADDRESS(obj); + + if (IS_IPv4) { + guint32 array[3]; + in_addr_t gw; + + gw = 0u; + if (default_route) { + gw = NMP_OBJECT_CAST_IP4_ROUTE(default_route)->gateway; + default_route = NULL; + } + array[0] = addr->a4.address; + array[1] = addr->a4.plen; + array[2] = gw; + g_variant_builder_add( + &int_builder, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 3, sizeof(guint32))); + } else { + const struct in6_addr *gw = &in6addr_any; + + if (default_route) { + gw = &NMP_OBJECT_CAST_IP6_ROUTE(default_route)->gateway; + default_route = NULL; + } + var1 = nm_g_variant_new_ay_in6addr(&addr->a6.address); + var2 = nm_g_variant_new_ay_in6addr(gw); + g_variant_builder_add(&int_builder, "(@ayu@ay)", var1, addr->a6.plen, var2); + } + } + g_variant_builder_add(builder, "{sv}", "addresses", g_variant_builder_end(&int_builder)); + + if (IS_IPv4) + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au")); + else + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aay")); + n = nm_ip_config_get_num_nameservers(ip); + for (i = 0; i < n; i++) { + if (IS_IPv4) { + g_variant_builder_add(&int_builder, + "u", + nm_ip4_config_get_nameserver(NM_IP4_CONFIG(ip), i)); + } else { + var1 = nm_g_variant_new_ay_in6addr(nm_ip6_config_get_nameserver(NM_IP6_CONFIG(ip), i)); + g_variant_builder_add(&int_builder, "@ay", var1); + } + } + g_variant_builder_add(builder, "{sv}", "nameservers", g_variant_builder_end(&int_builder)); + + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("as")); + n = nm_ip_config_get_num_domains(ip); + for (i = 0; i < n; i++) + g_variant_builder_add(&int_builder, "s", nm_ip_config_get_domain(ip, i)); + g_variant_builder_add(builder, "{sv}", "domains", g_variant_builder_end(&int_builder)); + + if (IS_IPv4) { + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au")); + n = nm_ip4_config_get_num_wins(NM_IP4_CONFIG(ip)); + for (i = 0; i < n; i++) + g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_wins(NM_IP4_CONFIG(ip), i)); + g_variant_builder_add(builder, "{sv}", "wins-servers", g_variant_builder_end(&int_builder)); + } + + if (IS_IPv4) + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau")); + else + g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuayu)")); + if (IS_IPv4) + nm_ip_config_iter_ip4_route_init(&ipconf_iter, NM_IP4_CONFIG(ip)); + else + nm_ip_config_iter_ip6_route_init(&ipconf_iter, NM_IP6_CONFIG(ip)); + while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter, + &obj, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))) { + const NMPlatformIPXRoute *route = NMP_OBJECT_CAST_IPX_ROUTE(obj); + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) + continue; + + if (IS_IPv4) { + guint32 array[4]; + + array[0] = route->r4.network; + array[1] = route->r4.plen; + array[2] = route->r4.gateway; + array[3] = route->r4.metric; + g_variant_builder_add( + &int_builder, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 4, sizeof(guint32))); + } else { + var1 = nm_g_variant_new_ay_in6addr(&route->r6.network); + var2 = nm_g_variant_new_ay_in6addr(&route->r6.gateway); + g_variant_builder_add(&int_builder, + "(@ayu@ayu)", + var1, + route->r6.plen, + var2, + route->r6.metric); + } + } + g_variant_builder_add(builder, "{sv}", "routes", g_variant_builder_end(&int_builder)); +} + +static void +fill_device_props(NMDevice * device, + GVariantBuilder *dev_builder, + GVariantBuilder *proxy_builder, + GVariantBuilder *ip4_builder, + GVariantBuilder *ip6_builder, + GVariant ** dhcp4_props, + GVariant ** dhcp6_props) +{ + NMProxyConfig *proxy_config; + NMIP4Config * ip4_config; + NMIP6Config * ip6_config; + NMDhcpConfig * dhcp_config; + + /* If the action is for a VPN, send the VPN's IP interface instead of the device's */ + g_variant_builder_add(dev_builder, + "{sv}", + NMD_DEVICE_PROPS_IP_INTERFACE, + g_variant_new_string(nm_device_get_ip_iface(device))); + g_variant_builder_add(dev_builder, + "{sv}", + NMD_DEVICE_PROPS_INTERFACE, + g_variant_new_string(nm_device_get_iface(device))); + g_variant_builder_add(dev_builder, + "{sv}", + NMD_DEVICE_PROPS_TYPE, + g_variant_new_uint32(nm_device_get_device_type(device))); + g_variant_builder_add(dev_builder, + "{sv}", + NMD_DEVICE_PROPS_STATE, + g_variant_new_uint32(nm_device_get_state(device))); + if (nm_dbus_object_is_exported(NM_DBUS_OBJECT(device))) { + g_variant_builder_add( + dev_builder, + "{sv}", + NMD_DEVICE_PROPS_PATH, + g_variant_new_object_path(nm_dbus_object_get_path(NM_DBUS_OBJECT(device)))); + } + + proxy_config = nm_device_get_proxy_config(device); + if (proxy_config) + dump_proxy_to_props(proxy_config, proxy_builder); + + ip4_config = nm_device_get_ip4_config(device); + if (ip4_config) + dump_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder); + + ip6_config = nm_device_get_ip6_config(device); + if (ip6_config) + dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder); + + dhcp_config = nm_device_get_dhcp_config(device, AF_INET); + if (dhcp_config) + *dhcp4_props = nm_g_variant_ref(nm_dhcp_config_get_options(dhcp_config)); + + dhcp_config = nm_device_get_dhcp_config(device, AF_INET6); + if (dhcp_config) + *dhcp6_props = nm_g_variant_ref(nm_dhcp_config_get_options(dhcp_config)); +} + +static void +fill_vpn_props(NMProxyConfig * proxy_config, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config, + GVariantBuilder *proxy_builder, + GVariantBuilder *ip4_builder, + GVariantBuilder *ip6_builder) +{ + if (proxy_config) + dump_proxy_to_props(proxy_config, proxy_builder); + if (ip4_config) + dump_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder); + if (ip6_config) + dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder); +} + +static const char * +dispatch_result_to_string(DispatchResult result) +{ + switch (result) { + case DISPATCH_RESULT_UNKNOWN: + return "unknown"; + case DISPATCH_RESULT_SUCCESS: + return "success"; + case DISPATCH_RESULT_EXEC_FAILED: + return "exec failed"; + case DISPATCH_RESULT_FAILED: + return "failed"; + case DISPATCH_RESULT_TIMEOUT: + return "timed out"; + } + g_assert_not_reached(); +} + +static void +dispatcher_results_process(guint32 request_id, + const char *log_ifname, + const char *log_con_uuid, + GVariant * v_results) +{ + nm_auto_free_variant_iter GVariantIter *results = NULL; + const char * script, *err; + guint32 result; + + g_variant_get(v_results, "(a(sus))", &results); + + if (g_variant_iter_n_children(results) == 0) { + _LOG2D(request_id, log_ifname, log_con_uuid, "succeeded but no scripts invoked"); + return; + } + + while (g_variant_iter_next(results, "(&su&s)", &script, &result, &err)) { + if (result == DISPATCH_RESULT_SUCCESS) { + _LOG2D(request_id, log_ifname, log_con_uuid, "%s succeeded", script); + } else { + _LOG2W(request_id, + log_ifname, + log_con_uuid, + "%s failed (%s): %s", + script, + dispatch_result_to_string(result), + err); + } + } +} + +static void +dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + NMDispatcherCallId * call_id = user_data; + + nm_assert((gpointer) source == gl.dbus_connection); + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (!ret) { + if (_nm_dbus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed")) { + g_dbus_error_strip_remote_error(error); + _LOG3W(call_id, "failed to call dispatcher scripts: %s", error->message); + } else { + _LOG3D(call_id, "failed to call dispatcher scripts: %s", error->message); + } + } else { + dispatcher_results_process(call_id->request_id, + call_id->log_ifname, + call_id->log_con_uuid, + ret); + } + + g_hash_table_remove(gl.requests, call_id); + + if (call_id->callback) + call_id->callback(call_id, call_id->user_data); + + dispatcher_call_id_free(call_id); +} + +static const char *action_table[] = {[NM_DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME, + [NM_DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP, + [NM_DISPATCHER_ACTION_UP] = NMD_ACTION_UP, + [NM_DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN, + [NM_DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN, + [NM_DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP, + [NM_DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP, + [NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN, + [NM_DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN, + [NM_DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE, + [NM_DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE, + [NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = + NMD_ACTION_CONNECTIVITY_CHANGE}; + +static const char * +action_to_string(NMDispatcherAction action) +{ + if (G_UNLIKELY((gsize) action >= G_N_ELEMENTS(action_table))) + g_return_val_if_reached(NULL); + return action_table[(gsize) action]; +} + +static gboolean +_dispatcher_call(NMDispatcherAction action, + gboolean blocking, + NMDevice * device, + NMSettingsConnection *settings_connection, + NMConnection * applied_connection, + gboolean activation_type_external, + NMConnectivityState connectivity_state, + const char * vpn_iface, + NMProxyConfig * vpn_proxy_config, + NMIP4Config * vpn_ip4_config, + NMIP6Config * vpn_ip6_config, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId ** out_call_id) +{ + GVariant * connection_dict; + GVariantBuilder connection_props; + GVariantBuilder device_props; + GVariantBuilder device_proxy_props; + GVariantBuilder device_ip4_props; + GVariantBuilder device_ip6_props; + gs_unref_variant GVariant *parameters_floating = NULL; + gs_unref_variant GVariant *device_dhcp4_props = NULL; + gs_unref_variant GVariant *device_dhcp6_props = NULL; + GVariantBuilder vpn_proxy_props; + GVariantBuilder vpn_ip4_props; + GVariantBuilder vpn_ip6_props; + NMDispatcherCallId * call_id; + guint request_id; + const char * connectivity_state_string = "UNKNOWN"; + const char * log_ifname; + const char * log_con_uuid; + + g_return_val_if_fail(!blocking || (!callback && !user_data), FALSE); + + NM_SET_OUT(out_call_id, NULL); + + _init_dispatcher(); + + if (!gl.dbus_connection) + return FALSE; + + log_ifname = device ? nm_device_get_iface(device) : NULL; + log_con_uuid = + settings_connection ? nm_settings_connection_get_uuid(settings_connection) : NULL; + + request_id = ++gl.request_id_counter; + if (G_UNLIKELY(!request_id)) + request_id = ++gl.request_id_counter; + + /* All actions except 'hostname' and 'connectivity-change' require a device */ + if (action == NM_DISPATCHER_ACTION_HOSTNAME + || action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) { + _LOG2D(request_id, + log_ifname, + log_con_uuid, + "dispatching action '%s'%s", + action_to_string(action), + blocking ? " (blocking)" : (callback ? " (with callback)" : "")); + } else { + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + _LOG2D(request_id, + log_ifname, + log_con_uuid, + "(%s) dispatching action '%s'%s", + vpn_iface ?: nm_device_get_iface(device), + action_to_string(action), + blocking ? " (blocking)" : (callback ? " (with callback)" : "")); + } + + if (applied_connection) + connection_dict = + nm_connection_to_dbus(applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS); + else + connection_dict = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); + + g_variant_builder_init(&connection_props, G_VARIANT_TYPE_VARDICT); + if (settings_connection) { + const char *connection_path; + const char *filename; + + connection_path = nm_dbus_object_get_path(NM_DBUS_OBJECT(settings_connection)); + if (connection_path) { + g_variant_builder_add(&connection_props, + "{sv}", + NMD_CONNECTION_PROPS_PATH, + g_variant_new_object_path(connection_path)); + } + filename = nm_settings_connection_get_filename(settings_connection); + if (filename) { + g_variant_builder_add(&connection_props, + "{sv}", + NMD_CONNECTION_PROPS_FILENAME, + g_variant_new_string(filename)); + } + if (activation_type_external) { + g_variant_builder_add(&connection_props, + "{sv}", + NMD_CONNECTION_PROPS_EXTERNAL, + g_variant_new_boolean(TRUE)); + } + } + + g_variant_builder_init(&device_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&device_proxy_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&device_ip4_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&device_ip6_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&vpn_proxy_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&vpn_ip4_props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&vpn_ip6_props, G_VARIANT_TYPE_VARDICT); + + /* hostname and connectivity-change actions don't send device data */ + if (action != NM_DISPATCHER_ACTION_HOSTNAME + && action != NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) { + fill_device_props(device, + &device_props, + &device_proxy_props, + &device_ip4_props, + &device_ip6_props, + &device_dhcp4_props, + &device_dhcp6_props); + if (vpn_ip4_config || vpn_ip6_config) { + fill_vpn_props(vpn_proxy_config, + vpn_ip4_config, + vpn_ip6_config, + &vpn_proxy_props, + &vpn_ip4_props, + &vpn_ip6_props); + } + } + + connectivity_state_string = nm_connectivity_state_to_string(connectivity_state); + + parameters_floating = + g_variant_new("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)", + action_to_string(action), + connection_dict, + &connection_props, + &device_props, + &device_proxy_props, + &device_ip4_props, + &device_ip6_props, + device_dhcp4_props ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), + device_dhcp6_props ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), + connectivity_state_string, + vpn_iface ?: "", + &vpn_proxy_props, + &vpn_ip4_props, + &vpn_ip6_props, + nm_logging_enabled(LOGL_DEBUG, LOGD_DISPATCH)); + + /* Send the action to the dispatcher */ + if (blocking) { + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + + ret = g_dbus_connection_call_sync(gl.dbus_connection, + NM_DISPATCHER_DBUS_SERVICE, + NM_DISPATCHER_DBUS_PATH, + NM_DISPATCHER_DBUS_INTERFACE, + "Action", + g_steal_pointer(¶meters_floating), + G_VARIANT_TYPE("(a(sus))"), + G_DBUS_CALL_FLAGS_NONE, + CALL_TIMEOUT, + NULL, + &error); + if (!ret) { + g_dbus_error_strip_remote_error(error); + _LOG2W(request_id, log_ifname, log_con_uuid, "failed: %s", error->message); + return FALSE; + } + dispatcher_results_process(request_id, log_ifname, log_con_uuid, ret); + return TRUE; + } + + call_id = + dispatcher_call_id_new(request_id, action, callback, user_data, log_ifname, log_con_uuid); + + g_dbus_connection_call(gl.dbus_connection, + NM_DISPATCHER_DBUS_SERVICE, + NM_DISPATCHER_DBUS_PATH, + NM_DISPATCHER_DBUS_INTERFACE, + "Action", + g_steal_pointer(¶meters_floating), + G_VARIANT_TYPE("(a(sus))"), + G_DBUS_CALL_FLAGS_NONE, + CALL_TIMEOUT, + NULL, + dispatcher_done_cb, + call_id); + g_hash_table_add(gl.requests, call_id); + NM_SET_OUT(out_call_id, call_id); + return TRUE; +} + +/** + * nm_dispatcher_call_hostname: + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the dispatcher action asynchronously. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_hostname(NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id) +{ + return _dispatcher_call(NM_DISPATCHER_ACTION_HOSTNAME, + FALSE, + NULL, + NULL, + NULL, + FALSE, + NM_CONNECTIVITY_UNKNOWN, + NULL, + NULL, + NULL, + NULL, + callback, + user_data, + out_call_id); +} + +/** + * nm_dispatcher_call_device: + * @action: the %NMDispatcherAction + * @device: the #NMDevice the action applies to + * @act_request: the #NMActRequest for the action. If %NULL, use the + * current request of the device. + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the device dispatcher action asynchronously. To ignore + * the result, pass %NULL to @callback. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_device(NMDispatcherAction action, + NMDevice * device, + NMActRequest * act_request, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id) +{ + nm_assert(NM_IS_DEVICE(device)); + if (!act_request) { + act_request = nm_device_get_act_request(device); + if (!act_request) + return FALSE; + } + nm_assert(NM_IN_SET(nm_active_connection_get_device(NM_ACTIVE_CONNECTION(act_request)), + NULL, + device)); + return _dispatcher_call( + action, + FALSE, + device, + nm_act_request_get_settings_connection(act_request), + nm_act_request_get_applied_connection(act_request), + nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(act_request)) + == NM_ACTIVATION_TYPE_EXTERNAL, + NM_CONNECTIVITY_UNKNOWN, + NULL, + NULL, + NULL, + NULL, + callback, + user_data, + out_call_id); +} + +/** + * nm_dispatcher_call_device_sync(): + * @action: the %NMDispatcherAction + * @device: the #NMDevice the action applies to + * @act_request: the #NMActRequest for the action. If %NULL, use the + * current request of the device. + * + * This method always invokes the dispatcher action synchronously and it may + * take a long time to return. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_device_sync(NMDispatcherAction action, + NMDevice * device, + NMActRequest * act_request) +{ + nm_assert(NM_IS_DEVICE(device)); + if (!act_request) { + act_request = nm_device_get_act_request(device); + if (!act_request) + return FALSE; + } + nm_assert(NM_IN_SET(nm_active_connection_get_device(NM_ACTIVE_CONNECTION(act_request)), + NULL, + device)); + return _dispatcher_call( + action, + TRUE, + device, + nm_act_request_get_settings_connection(act_request), + nm_act_request_get_applied_connection(act_request), + nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(act_request)) + == NM_ACTIVATION_TYPE_EXTERNAL, + NM_CONNECTIVITY_UNKNOWN, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); +} + +/** + * nm_dispatcher_call_vpn(): + * @action: the %NMDispatcherAction + * @settings_connection: the #NMSettingsConnection the action applies to + * @applied_connection: the currently applied connection + * @parent_device: the parent #NMDevice of the VPN connection + * @vpn_iface: the IP interface of the VPN tunnel, if any + * @vpn_proxy_config: the #NMProxyConfig of the VPN connection + * @vpn_ip4_config: the #NMIP4Config of the VPN connection + * @vpn_ip6_config: the #NMIP6Config of the VPN connection + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the dispatcher action asynchronously. To ignore + * the result, pass %NULL to @callback. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_vpn(NMDispatcherAction action, + NMSettingsConnection *settings_connection, + NMConnection * applied_connection, + NMDevice * parent_device, + const char * vpn_iface, + NMProxyConfig * vpn_proxy_config, + NMIP4Config * vpn_ip4_config, + NMIP6Config * vpn_ip6_config, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId ** out_call_id) +{ + return _dispatcher_call(action, + FALSE, + parent_device, + settings_connection, + applied_connection, + FALSE, + NM_CONNECTIVITY_UNKNOWN, + vpn_iface, + vpn_proxy_config, + vpn_ip4_config, + vpn_ip6_config, + callback, + user_data, + out_call_id); +} + +/** + * nm_dispatcher_call_vpn_sync(): + * @action: the %NMDispatcherAction + * @settings_connection: the #NMSettingsConnection the action applies to + * @applied_connection: the currently applied connection + * @parent_device: the parent #NMDevice of the VPN connection + * @vpn_iface: the IP interface of the VPN tunnel, if any + * @vpn_proxy_config: the #NMProxyConfig of the VPN connection + * @vpn_ip4_config: the #NMIP4Config of the VPN connection + * @vpn_ip6_config: the #NMIP6Config of the VPN connection + * + * This method always invokes the dispatcher action synchronously and it may + * take a long time to return. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_vpn_sync(NMDispatcherAction action, + NMSettingsConnection *settings_connection, + NMConnection * applied_connection, + NMDevice * parent_device, + const char * vpn_iface, + NMProxyConfig * vpn_proxy_config, + NMIP4Config * vpn_ip4_config, + NMIP6Config * vpn_ip6_config) +{ + return _dispatcher_call(action, + TRUE, + parent_device, + settings_connection, + applied_connection, + FALSE, + NM_CONNECTIVITY_UNKNOWN, + vpn_iface, + vpn_proxy_config, + vpn_ip4_config, + vpn_ip6_config, + NULL, + NULL, + NULL); +} + +/** + * nm_dispatcher_call_connectivity(): + * @connectivity_state: the #NMConnectivityState value + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method does not block the caller. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_connectivity(NMConnectivityState connectivity_state, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id) +{ + return _dispatcher_call(NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, + FALSE, + NULL, + NULL, + NULL, + FALSE, + connectivity_state, + NULL, + NULL, + NULL, + NULL, + callback, + user_data, + out_call_id); +} + +void +nm_dispatcher_call_cancel(NMDispatcherCallId *call_id) +{ + if (!call_id || g_hash_table_lookup(gl.requests, call_id) != call_id || !call_id->callback) + g_return_if_reached(); + + /* Canceling just means the callback doesn't get called, so set the + * DispatcherInfo's callback to NULL. + */ + _LOG3D(call_id, "cancelling dispatcher callback action"); + call_id->callback = NULL; +} diff --git a/src/core/nm-dispatcher.h b/src/core/nm-dispatcher.h new file mode 100644 index 0000000..d588de4 --- /dev/null +++ b/src/core/nm-dispatcher.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2012 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#ifndef __NM_DISPATCHER_H__ +#define __NM_DISPATCHER_H__ + +#include "nm-connection.h" + +typedef enum { + NM_DISPATCHER_ACTION_HOSTNAME, + NM_DISPATCHER_ACTION_PRE_UP, + NM_DISPATCHER_ACTION_UP, + NM_DISPATCHER_ACTION_PRE_DOWN, + NM_DISPATCHER_ACTION_DOWN, + NM_DISPATCHER_ACTION_VPN_PRE_UP, + NM_DISPATCHER_ACTION_VPN_UP, + NM_DISPATCHER_ACTION_VPN_PRE_DOWN, + NM_DISPATCHER_ACTION_VPN_DOWN, + NM_DISPATCHER_ACTION_DHCP4_CHANGE, + NM_DISPATCHER_ACTION_DHCP6_CHANGE, + NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE +} NMDispatcherAction; + +typedef struct NMDispatcherCallId NMDispatcherCallId; + +typedef void (*NMDispatcherFunc)(NMDispatcherCallId *call_id, gpointer user_data); + +gboolean nm_dispatcher_call_hostname(NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id); + +gboolean nm_dispatcher_call_device(NMDispatcherAction action, + NMDevice * device, + NMActRequest * act_request, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id); + +gboolean nm_dispatcher_call_device_sync(NMDispatcherAction action, + NMDevice * device, + NMActRequest * act_request); + +gboolean nm_dispatcher_call_vpn(NMDispatcherAction action, + NMSettingsConnection *settings_connection, + NMConnection * applied_connection, + NMDevice * parent_device, + const char * vpn_iface, + NMProxyConfig * vpn_proxy_config, + NMIP4Config * vpn_ip4_config, + NMIP6Config * vpn_ip6_config, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId ** out_call_id); + +gboolean nm_dispatcher_call_vpn_sync(NMDispatcherAction action, + NMSettingsConnection *settings_connection, + NMConnection * applied_connection, + NMDevice * parent_device, + const char * vpn_iface, + NMProxyConfig * vpn_proxy_config, + NMIP4Config * vpn_ip4_config, + NMIP6Config * vpn_ip6_config); + +gboolean nm_dispatcher_call_connectivity(NMConnectivityState state, + NMDispatcherFunc callback, + gpointer user_data, + NMDispatcherCallId **out_call_id); + +void nm_dispatcher_call_cancel(NMDispatcherCallId *call_id); + +#endif /* __NM_DISPATCHER_H__ */ diff --git a/src/core/nm-firewall-manager.c b/src/core/nm-firewall-manager.c new file mode 100644 index 0000000..ff0c1a0 --- /dev/null +++ b/src/core/nm-firewall-manager.c @@ -0,0 +1,623 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-firewall-manager.h" + +#include "nm-glib-aux/nm-dbus-aux.h" +#include "c-list/src/c-list.h" + +#include "NetworkManagerUtils.h" +#include "nm-dbus-manager.h" + +#define FIREWALL_DBUS_SERVICE "org.fedoraproject.FirewallD1" +#define FIREWALL_DBUS_PATH "/org/fedoraproject/FirewallD1" +#define FIREWALL_DBUS_INTERFACE_ZONE "org.fedoraproject.FirewallD1.zone" + +/*****************************************************************************/ + +enum { STATE_CHANGED, LAST_SIGNAL }; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GDBusConnection *dbus_connection; + + GCancellable *get_name_owner_cancellable; + + CList pending_calls; + + guint name_owner_changed_id; + + bool dbus_inited : 1; + bool running : 1; +} NMFirewallManagerPrivate; + +struct _NMFirewallManager { + GObject parent; + NMFirewallManagerPrivate _priv; +}; + +struct _NMFirewallManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMFirewallManager, nm_firewall_manager, G_TYPE_OBJECT) + +#define NM_FIREWALL_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMFirewallManager, NM_IS_FIREWALL_MANAGER) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMFirewallManager, nm_firewall_manager_get, NM_TYPE_FIREWALL_MANAGER); + +/*****************************************************************************/ + +typedef enum { + OPS_TYPE_ADD = 1, + OPS_TYPE_CHANGE, + OPS_TYPE_REMOVE, +} OpsType; + +struct _NMFirewallManagerCallId { + CList lst; + + NMFirewallManager *self; + + char *iface; + + NMFirewallManagerAddRemoveCallback callback; + gpointer user_data; + + union { + struct { + GCancellable *cancellable; + GVariant * arg; + } dbus; + struct { + guint id; + } idle; + }; + + OpsType ops_type; + + bool is_idle : 1; +}; + +/*****************************************************************************/ + +static const char * +_ops_type_to_string(OpsType ops_type) +{ + switch (ops_type) { + case OPS_TYPE_ADD: + return "add"; + case OPS_TYPE_REMOVE: + return "remove"; + case OPS_TYPE_CHANGE: + return "change"; + } + nm_assert_not_reached(); + return NULL; +} + +#define _NMLOG_DOMAIN LOGD_FIREWALL +#define _NMLOG_PREFIX_NAME "firewall" +#define _NMLOG(level, call_id, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + NMFirewallManagerCallId *_call_id = (call_id); \ + char _prefix_name[30]; \ + char _prefix_info[100]; \ + \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: %s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (self) != singleton_instance ? ({ \ + g_snprintf(_prefix_name, \ + sizeof(_prefix_name), \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]", \ + ""_NMLOG_PREFIX_NAME, \ + NM_HASH_OBFUSCATE_PTR(self)); \ + _prefix_name; \ + }) \ + : _NMLOG_PREFIX_NAME, \ + _call_id ? ({ \ + g_snprintf(_prefix_info, \ + sizeof(_prefix_info), \ + "[" NM_HASH_OBFUSCATE_PTR_FMT ",%s%s:%s%s%s]: ", \ + NM_HASH_OBFUSCATE_PTR(_call_id), \ + _ops_type_to_string(_call_id->ops_type), \ + _call_id->is_idle ? "*" : "", \ + NM_PRINT_FMT_QUOTE_STRING(_call_id->iface)); \ + _prefix_info; \ + }) \ + : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static gboolean +_get_running(NMFirewallManagerPrivate *priv) +{ + /* when starting, we need to asynchronously check whether there is + * a name owner. During that time we optimistically assume that the + * service is indeed running. That is the time when we queue the + * requests, and they will be started once the get-name-owner call + * returns. */ + return priv->running || (priv->dbus_connection && !priv->dbus_inited); +} + +gboolean +nm_firewall_manager_get_running(NMFirewallManager *self) +{ + g_return_val_if_fail(NM_IS_FIREWALL_MANAGER(self), FALSE); + + return _get_running(NM_FIREWALL_MANAGER_GET_PRIVATE(self)); +} + +/*****************************************************************************/ + +static NMFirewallManagerCallId * +_cb_info_create(NMFirewallManager * self, + OpsType ops_type, + const char * iface, + const char * zone, + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data) +{ + NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + NMFirewallManagerCallId * call_id; + + call_id = g_slice_new0(NMFirewallManagerCallId); + + call_id->self = g_object_ref(self); + call_id->ops_type = ops_type; + call_id->iface = g_strdup(iface); + call_id->callback = callback; + call_id->user_data = user_data; + + if (_get_running(priv)) { + call_id->is_idle = FALSE; + call_id->dbus.arg = g_variant_new("(ss)", zone ?: "", iface); + } else + call_id->is_idle = TRUE; + + c_list_link_tail(&priv->pending_calls, &call_id->lst); + + return call_id; +} + +static void +_cb_info_complete(NMFirewallManagerCallId *call_id, GError *error) +{ + c_list_unlink(&call_id->lst); + + if (call_id->callback) + call_id->callback(call_id->self, call_id, error, call_id->user_data); + + if (call_id->is_idle) + nm_clear_g_source(&call_id->idle.id); + else { + nm_g_variant_unref(call_id->dbus.arg); + nm_clear_g_cancellable(&call_id->dbus.cancellable); + } + g_free(call_id->iface); + g_object_unref(call_id->self); + nm_g_slice_free(call_id); +} + +static gboolean +_handle_idle_cb(gpointer user_data) +{ + NMFirewallManager * self; + NMFirewallManagerCallId *call_id = user_data; + + nm_assert(call_id); + nm_assert(NM_IS_FIREWALL_MANAGER(call_id->self)); + nm_assert(call_id->is_idle); + nm_assert(c_list_contains(&NM_FIREWALL_MANAGER_GET_PRIVATE(call_id->self)->pending_calls, + &call_id->lst)); + + self = call_id->self; + + _LOGD(call_id, "complete: fake success"); + + call_id->idle.id = 0; + + _cb_info_complete(call_id, NULL); + return G_SOURCE_REMOVE; +} + +static gboolean +_handle_idle_start(NMFirewallManager *self, NMFirewallManagerCallId *call_id) +{ + if (!call_id->callback) { + /* if the user did not provide a callback and firewalld is not running, + * there is no point in scheduling an idle-request to fake success. Just + * return right away. */ + _LOGD(call_id, "complete: drop request simulating success"); + _cb_info_complete(call_id, NULL); + return FALSE; + } + call_id->idle.id = g_idle_add(_handle_idle_cb, call_id); + return TRUE; +} + +static void +_handle_dbus_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMFirewallManager * self; + NMFirewallManagerCallId *call_id; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!ret && nm_utils_error_is_cancelled(error)) + return; + + call_id = user_data; + + nm_assert(call_id); + nm_assert(NM_IS_FIREWALL_MANAGER(call_id->self)); + nm_assert(!call_id->is_idle); + nm_assert(c_list_contains(&NM_FIREWALL_MANAGER_GET_PRIVATE(call_id->self)->pending_calls, + &call_id->lst)); + + self = call_id->self; + + if (error) { + const char *non_error = NULL; + + g_dbus_error_strip_remote_error(error); + + switch (call_id->ops_type) { + case OPS_TYPE_ADD: + case OPS_TYPE_CHANGE: + non_error = "ZONE_ALREADY_SET"; + break; + case OPS_TYPE_REMOVE: + non_error = "UNKNOWN_INTERFACE"; + break; + } + if (error->message && non_error && g_str_has_prefix(error->message, non_error) + && NM_IN_SET(error->message[strlen(non_error)], '\0', ':')) { + _LOGD(call_id, "complete: request failed with a non-error (%s)", error->message); + + /* The operation failed with an error reason that we don't want + * to propagate. Instead, signal success. */ + g_clear_error(&error); + } else + _LOGW(call_id, "complete: request failed (%s)", error->message); + } else + _LOGD(call_id, "complete: success"); + + g_clear_object(&call_id->dbus.cancellable); + + _cb_info_complete(call_id, error); +} + +static void +_handle_dbus_start(NMFirewallManager *self, NMFirewallManagerCallId *call_id) +{ + NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + const char * dbus_method = NULL; + GVariant * arg; + + nm_assert(call_id); + nm_assert(priv->running); + nm_assert(!call_id->is_idle); + nm_assert(c_list_contains(&priv->pending_calls, &call_id->lst)); + + switch (call_id->ops_type) { + case OPS_TYPE_ADD: + dbus_method = "addInterface"; + break; + case OPS_TYPE_CHANGE: + dbus_method = "changeZone"; + break; + case OPS_TYPE_REMOVE: + dbus_method = "removeInterface"; + break; + } + nm_assert(dbus_method); + + arg = g_steal_pointer(&call_id->dbus.arg); + + nm_assert(arg && g_variant_is_floating(arg)); + + nm_assert(!call_id->dbus.cancellable); + + call_id->dbus.cancellable = g_cancellable_new(); + + g_dbus_connection_call(priv->dbus_connection, + FIREWALL_DBUS_SERVICE, + FIREWALL_DBUS_PATH, + FIREWALL_DBUS_INTERFACE_ZONE, + dbus_method, + arg, + NULL, + G_DBUS_CALL_FLAGS_NONE, + 10000, + call_id->dbus.cancellable, + _handle_dbus_cb, + call_id); +} + +static NMFirewallManagerCallId * +_start_request(NMFirewallManager * self, + OpsType ops_type, + const char * iface, + const char * zone, + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data) +{ + NMFirewallManagerPrivate *priv; + NMFirewallManagerCallId * call_id; + + g_return_val_if_fail(NM_IS_FIREWALL_MANAGER(self), NULL); + g_return_val_if_fail(iface && *iface, NULL); + + priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + + call_id = _cb_info_create(self, ops_type, iface, zone, callback, user_data); + + _LOGD(call_id, + "firewall zone %s %s:%s%s%s%s", + _ops_type_to_string(call_id->ops_type), + iface, + NM_PRINT_FMT_QUOTED(zone, "\"", zone, "\"", "default"), + call_id->is_idle ? " (not running, simulate success)" + : (!priv->running ? " (waiting to initialize)" : "")); + + if (!call_id->is_idle) { + if (priv->running) + _handle_dbus_start(self, call_id); + if (!call_id->callback) { + /* if the user did not provide a callback, the call_id is useless. + * Especially, the user cannot use the call-id to cancel the request, + * because he cannot know whether the request is still pending. + * + * Hence, returning %NULL doesn't mean that the request could not be started + * (this function never fails and always starts a request). */ + return NULL; + } + } else { + if (!_handle_idle_start(self, call_id)) { + /* if the user did not provide a callback and firewalld is not running, + * there is no point in scheduling an idle-request to fake success. Just + * return right away. */ + return NULL; + } + } + + return call_id; +} + +NMFirewallManagerCallId * +nm_firewall_manager_add_or_change_zone(NMFirewallManager *self, + const char * iface, + const char * zone, + gboolean add, /* TRUE == add, FALSE == change */ + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data) +{ + return _start_request(self, + add ? OPS_TYPE_ADD : OPS_TYPE_CHANGE, + iface, + zone, + callback, + user_data); +} + +NMFirewallManagerCallId * +nm_firewall_manager_remove_from_zone(NMFirewallManager * self, + const char * iface, + const char * zone, + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data) +{ + return _start_request(self, OPS_TYPE_REMOVE, iface, zone, callback, user_data); +} + +void +nm_firewall_manager_cancel_call(NMFirewallManagerCallId *call_id) +{ + NMFirewallManager * self; + NMFirewallManagerPrivate *priv; + gs_free_error GError *error = NULL; + + g_return_if_fail(call_id); + g_return_if_fail(NM_IS_FIREWALL_MANAGER(call_id->self)); + g_return_if_fail(!c_list_is_empty(&call_id->lst)); + + self = call_id->self; + priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + + nm_assert(c_list_contains(&priv->pending_calls, &call_id->lst)); + + nm_utils_error_set_cancelled(&error, FALSE, "NMFirewallManager"); + + _LOGD(call_id, "complete: cancel (%s)", error->message); + + _cb_info_complete(call_id, error); +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMFirewallManager *self, const char *owner) +{ + _nm_unused gs_unref_object NMFirewallManager *self_keep_alive = g_object_ref(self); + NMFirewallManagerPrivate * priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + gboolean was_running; + gboolean now_running; + gboolean just_initied; + + owner = nm_str_not_empty(owner); + + if (!owner) + _LOGT(NULL, "D-Bus name for firewalld has no owner (firewall stopped)"); + else + _LOGT(NULL, "D-Bus name for firewalld has owner %s (firewall started)", owner); + + was_running = _get_running(priv); + just_initied = !priv->dbus_inited; + + priv->dbus_inited = TRUE; + priv->running = !!owner; + + now_running = _get_running(priv); + + if (just_initied) { + NMFirewallManagerCallId *call_id_safe; + NMFirewallManagerCallId *call_id; + + /* We kick of the requests that we have pending. Note that this is + * entirely asynchronous and also we don't invoke any callbacks for + * the user. + * Even _handle_idle_start() just schedules an idle handler. That is, + * because we don't want to callback to the user before emitting the + * DISCONNECTED signal below. Also, emitting callbacks means the user + * can call back to modify the list of pending-calls and we'd have + * to handle reentrancy. */ + c_list_for_each_entry_safe (call_id, call_id_safe, &priv->pending_calls, lst) { + nm_assert(!call_id->is_idle); + nm_assert(call_id->dbus.arg); + + if (priv->running) { + _LOGD(call_id, "initalizing: make D-Bus call"); + _handle_dbus_start(self, call_id); + } else { + /* we don't want to invoke callbacks to the user right away. That is because + * the user might schedule/cancel more calls, which messes up the order. + * + * Instead, convert the pending calls to idle requests... */ + nm_clear_pointer(&call_id->dbus.arg, g_variant_unref); + call_id->is_idle = TRUE; + _LOGD(call_id, "initializing: fake success on idle"); + _handle_idle_start(self, call_id); + } + } + } + + if (was_running != now_running) + g_signal_emit(self, signals[STATE_CHANGED], 0, FALSE); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMFirewallManager *self = user_data; + const char * new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(self, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + NMFirewallManager * self; + NMFirewallManagerPrivate *priv; + + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->get_name_owner_cancellable); + + name_owner_changed(self, name_owner); +} + +/*****************************************************************************/ + +static void +nm_firewall_manager_init(NMFirewallManager *self) +{ + NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + + c_list_init(&priv->pending_calls); + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!priv->dbus_connection) { + _LOGD(NULL, "no D-Bus connection"); + return; + } + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + FIREWALL_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + + priv->get_name_owner_cancellable = g_cancellable_new(); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + FIREWALL_DBUS_SERVICE, + -1, + priv->get_name_owner_cancellable, + get_name_owner_cb, + self); +} + +static void +dispose(GObject *object) +{ + NMFirewallManager * self = NM_FIREWALL_MANAGER(object); + NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self); + + /* as every pending operation takes a reference to the manager, + * we don't expect pending operations at this point. */ + nm_assert(c_list_is_empty(&priv->pending_calls)); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_cancellable(&priv->get_name_owner_cancellable); + + G_OBJECT_CLASS(nm_firewall_manager_parent_class)->dispose(object); + + g_clear_object(&priv->dbus_connection); +} + +static void +nm_firewall_manager_class_init(NMFirewallManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + + signals[STATE_CHANGED] = g_signal_new(NM_FIREWALL_MANAGER_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN /* initialized_now */); +} diff --git a/src/core/nm-firewall-manager.h b/src/core/nm-firewall-manager.h new file mode 100644 index 0000000..1ce802b --- /dev/null +++ b/src/core/nm-firewall-manager.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_FIREWALL_MANAGER_H__ +#define __NETWORKMANAGER_FIREWALL_MANAGER_H__ + +#define NM_TYPE_FIREWALL_MANAGER (nm_firewall_manager_get_type()) +#define NM_FIREWALL_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_FIREWALL_MANAGER, NMFirewallManager)) +#define NM_FIREWALL_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_FIREWALL_MANAGER, NMFirewallManagerClass)) +#define NM_IS_FIREWALL_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_FIREWALL_MANAGER)) +#define NM_IS_FIREWALL_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_FIREWALL_MANAGER)) +#define NM_FIREWALL_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_FIREWALL_MANAGER, NMFirewallManagerClass)) + +#define NM_FIREWALL_MANAGER_STATE_CHANGED "state-changed" + +typedef struct _NMFirewallManagerCallId NMFirewallManagerCallId; + +typedef struct _NMFirewallManager NMFirewallManager; +typedef struct _NMFirewallManagerClass NMFirewallManagerClass; + +GType nm_firewall_manager_get_type(void); + +NMFirewallManager *nm_firewall_manager_get(void); + +gboolean nm_firewall_manager_get_running(NMFirewallManager *self); + +typedef void (*NMFirewallManagerAddRemoveCallback)(NMFirewallManager * self, + NMFirewallManagerCallId *call_id, + GError * error, + gpointer user_data); + +NMFirewallManagerCallId * +nm_firewall_manager_add_or_change_zone(NMFirewallManager * mgr, + const char * iface, + const char * zone, + gboolean add, + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data); +NMFirewallManagerCallId * +nm_firewall_manager_remove_from_zone(NMFirewallManager * mgr, + const char * iface, + const char * zone, + NMFirewallManagerAddRemoveCallback callback, + gpointer user_data); + +void nm_firewall_manager_cancel_call(NMFirewallManagerCallId *call_id); + +#endif /* __NETWORKMANAGER_FIREWALL_MANAGER_H__ */ diff --git a/src/core/nm-hostname-manager.c b/src/core/nm-hostname-manager.c new file mode 100644 index 0000000..ca23b97 --- /dev/null +++ b/src/core/nm-hostname-manager.c @@ -0,0 +1,649 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-hostname-manager.h" + +#include + +#if HAVE_SELINUX + #include + #include +#endif + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-dbus-interface.h" +#include "nm-connection.h" +#include "nm-utils.h" +#include "nm-core-internal.h" + +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +#define HOSTNAMED_SERVICE_NAME "org.freedesktop.hostname1" +#define HOSTNAMED_SERVICE_PATH "/org/freedesktop/hostname1" +#define HOSTNAMED_SERVICE_INTERFACE "org.freedesktop.hostname1" + +#define HOSTNAME_FILE_DEFAULT "/etc/hostname" +#define HOSTNAME_FILE_UCASE_HOSTNAME "/etc/HOSTNAME" +#define HOSTNAME_FILE_GENTOO "/etc/conf.d/hostname" + +#define CONF_DHCP SYSCONFDIR "/sysconfig/network/dhcp" + +#if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) \ + + defined(HOSTNAME_PERSIST_GENTOO)) \ + > 1 + #error "Can only define one of HOSTNAME_PERSIST_*" +#endif + +#if defined(HOSTNAME_PERSIST_SUSE) + #define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME +#elif defined(HOSTNAME_PERSIST_SLACKWARE) + #define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME +#elif defined(HOSTNAME_PERSIST_GENTOO) + #define HOSTNAME_FILE HOSTNAME_FILE_GENTOO +#else + #define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT +#endif + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMHostnameManager, PROP_HOSTNAME, ); + +typedef struct { + char * current_hostname; + GFileMonitor *monitor; + GFileMonitor *dhcp_monitor; + gulong monitor_id; + gulong dhcp_monitor_id; + GDBusProxy * hostnamed_proxy; +} NMHostnameManagerPrivate; + +struct _NMHostnameManager { + GObject parent; + NMHostnameManagerPrivate _priv; +}; + +struct _NMHostnameManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMHostnameManager, nm_hostname_manager, G_TYPE_OBJECT); + +#define NM_HOSTNAME_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMHostnameManager, NM_IS_HOSTNAME_MANAGER) + +NM_DEFINE_SINGLETON_GETTER(NMHostnameManager, nm_hostname_manager_get, NM_TYPE_HOSTNAME_MANAGER); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "hostname", __VA_ARGS__) + +/*****************************************************************************/ + +#if defined(HOSTNAME_PERSIST_GENTOO) +static char * +read_hostname_gentoo(const char *path) +{ + gs_free char * contents = NULL; + gs_strfreev char **all_lines = NULL; + const char * tmp; + guint i; + + if (!g_file_get_contents(path, &contents, NULL, NULL)) + return NULL; + + all_lines = g_strsplit(contents, "\n", 0); + for (i = 0; all_lines[i]; i++) { + g_strstrip(all_lines[i]); + if (all_lines[i][0] == '#' || all_lines[i][0] == '\0') + continue; + if (g_str_has_prefix(all_lines[i], "hostname=")) { + tmp = &all_lines[i][NM_STRLEN("hostname=")]; + return g_shell_unquote(tmp, NULL); + } + } + return NULL; +} +#endif + +#if defined(HOSTNAME_PERSIST_SLACKWARE) +static char * +read_hostname_slackware(const char *path) +{ + gs_free char * contents = NULL; + gs_strfreev char **all_lines = NULL; + guint i = 0; + + if (!g_file_get_contents(path, &contents, NULL, NULL)) + return NULL; + + all_lines = g_strsplit(contents, "\n", 0); + for (i = 0; all_lines[i]; i++) { + g_strstrip(all_lines[i]); + if (all_lines[i][0] == '#' || all_lines[i][0] == '\0') + continue; + return g_shell_unquote(&all_lines[i][0], NULL); + } + return NULL; +} +#endif + +#if defined(HOSTNAME_PERSIST_SUSE) +static gboolean +hostname_is_dynamic(void) +{ + GIOChannel *channel; + char * str = NULL; + gboolean dynamic = FALSE; + + channel = g_io_channel_new_file(CONF_DHCP, "r", NULL); + if (!channel) + return dynamic; + + while (g_io_channel_read_line(channel, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) { + if (str) { + g_strstrip(str); + if (g_str_has_prefix(str, "DHCLIENT_SET_HOSTNAME=")) + dynamic = strcmp(&str[NM_STRLEN("DHCLIENT_SET_HOSTNAME=")], "\"yes\"") == 0; + g_free(str); + } + } + + g_io_channel_shutdown(channel, FALSE, NULL); + g_io_channel_unref(channel); + + return dynamic; +} +#endif + +/* Returns an allocated string which the caller owns and must eventually free */ +char * +nm_hostname_manager_read_hostname(NMHostnameManager *self) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + char * hostname = NULL; + + if (priv->hostnamed_proxy) { + hostname = g_strdup(priv->current_hostname); + goto out; + } + +#if defined(HOSTNAME_PERSIST_SUSE) + if (priv->dhcp_monitor_id && hostname_is_dynamic()) + return NULL; +#endif + +#if defined(HOSTNAME_PERSIST_GENTOO) + hostname = read_hostname_gentoo(HOSTNAME_FILE); +#elif defined(HOSTNAME_PERSIST_SLACKWARE) + hostname = read_hostname_slackware(HOSTNAME_FILE); +#else + if (g_file_get_contents(HOSTNAME_FILE, &hostname, NULL, NULL)) + g_strchomp(hostname); +#endif + +out: + if (hostname && !hostname[0]) { + g_free(hostname); + return NULL; + } + + return hostname; +} + +/*****************************************************************************/ + +const char * +nm_hostname_manager_get_hostname(NMHostnameManager *self) +{ + g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), NULL); + return NM_HOSTNAME_MANAGER_GET_PRIVATE(self)->current_hostname; +} + +static void +_set_hostname_take(NMHostnameManager *self, char *hostname) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + _LOGI("hostname changed from %s%s%s to %s%s%s", + NM_PRINT_FMT_QUOTED(priv->current_hostname, "\"", priv->current_hostname, "\"", "(none)"), + NM_PRINT_FMT_QUOTED(hostname, "\"", hostname, "\"", "(none)")); + + g_free(priv->current_hostname); + priv->current_hostname = hostname; + _notify(self, PROP_HOSTNAME); +} + +static void +_set_hostname(NMHostnameManager *self, const char *hostname) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + hostname = nm_str_not_empty(hostname); + if (!nm_streq0(hostname, priv->current_hostname)) + _set_hostname_take(self, g_strdup(hostname)); +} + +static void +_set_hostname_read(NMHostnameManager *self) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + char * hostname; + + if (priv->hostnamed_proxy) { + /* read-hostname returns the current hostname with hostnamed. */ + return; + } + + hostname = nm_hostname_manager_read_hostname(self); + + if (nm_streq0(hostname, priv->current_hostname)) { + g_free(hostname); + return; + } + + _set_hostname_take(self, hostname); +} + +/*****************************************************************************/ + +typedef struct { + char * hostname; + NMHostnameManagerSetHostnameCb cb; + gpointer user_data; +} SetHostnameInfo; + +static void +set_transient_hostname_done(GObject *object, GAsyncResult *res, gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY(object); + gs_free SetHostnameInfo *info = user_data; + gs_unref_variant GVariant *result = NULL; + gs_free_error GError *error = NULL; + + result = g_dbus_proxy_call_finish(proxy, res, &error); + + if (error) { + _LOGW("couldn't set the system hostname to '%s' using hostnamed: %s", + info->hostname, + error->message); + } + + info->cb(info->hostname, !error, info->user_data); + g_free(info->hostname); +} + +void +nm_hostname_manager_set_transient_hostname(NMHostnameManager * self, + const char * hostname, + NMHostnameManagerSetHostnameCb cb, + gpointer user_data) +{ + NMHostnameManagerPrivate *priv; + SetHostnameInfo * info; + + g_return_if_fail(NM_IS_HOSTNAME_MANAGER(self)); + + priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + if (!priv->hostnamed_proxy) { + cb(hostname, FALSE, user_data); + return; + } + + info = g_new0(SetHostnameInfo, 1); + info->hostname = g_strdup(hostname); + info->cb = cb; + info->user_data = user_data; + + g_dbus_proxy_call(priv->hostnamed_proxy, + "SetHostname", + g_variant_new("(sb)", hostname, FALSE), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + set_transient_hostname_done, + info); +} + +gboolean +nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostname) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + GVariant * v_hostname; + + if (!priv->hostnamed_proxy) + return FALSE; + + v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "Hostname"); + if (!v_hostname) { + _LOGT("transient hostname retrieval failed"); + return FALSE; + } + + *hostname = g_variant_dup_string(v_hostname, NULL); + g_variant_unref(v_hostname); + + return TRUE; +} + +gboolean +nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname) +{ + NMHostnameManagerPrivate *priv; + char * hostname_eol; + gboolean ret; + gs_free_error GError *error = NULL; + const char * file = HOSTNAME_FILE; + gs_free char * link_path = NULL; + gs_unref_variant GVariant *var = NULL; + struct stat file_stat; +#if HAVE_SELINUX + gboolean fcon_was_set = FALSE; + char * fcon_prev = NULL; +#endif + + g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), FALSE); + + priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + if (priv->hostnamed_proxy) { + var = g_dbus_proxy_call_sync(priv->hostnamed_proxy, + "SetStaticHostname", + g_variant_new("(sb)", hostname, FALSE), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) + _LOGW("could not set hostname: %s", error->message); + + return !error; + } + + /* If the hostname file is a symbolic link, follow it to find where the + * real file is located, otherwise g_file_set_contents will attempt to + * replace the link with a plain file. + */ + if (lstat(file, &file_stat) == 0 && S_ISLNK(file_stat.st_mode) + && (link_path = nm_utils_read_link_absolute(file, NULL))) + file = link_path; + +#if defined(HOSTNAME_PERSIST_GENTOO) + hostname_eol = g_strdup_printf("#Generated by NetworkManager\n" + "hostname=\"%s\"\n", + hostname); +#else + hostname_eol = g_strdup_printf("%s\n", hostname); +#endif + +#if HAVE_SELINUX + /* Get default context for hostname file and set it for fscreate */ + { + struct selabel_handle *handle; + + handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (handle) { + mode_t st_mode = 0; + char * fcon = NULL; + + if (stat(file, &file_stat) == 0) + st_mode = file_stat.st_mode; + + if ((selabel_lookup(handle, &fcon, file, st_mode) == 0) + && (getfscreatecon(&fcon_prev) == 0)) { + setfscreatecon(fcon); + fcon_was_set = TRUE; + } + + selabel_close(handle); + freecon(fcon); + } + } +#endif + + ret = g_file_set_contents(file, hostname_eol, -1, &error); + +#if HAVE_SELINUX + /* Restore previous context and cleanup */ + if (fcon_was_set) + setfscreatecon(fcon_prev); + if (fcon_prev) + freecon(fcon_prev); +#endif + + g_free(hostname_eol); + + if (!ret) { + _LOGW("could not save hostname to %s: %s", file, error->message); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_hostname_manager_validate_hostname(const char *hostname) +{ + const char *p; + gboolean dot = TRUE; + + if (!hostname || !hostname[0]) + return FALSE; + + for (p = hostname; *p; p++) { + if (*p == '.') { + if (dot) + return FALSE; + dot = TRUE; + } else { + if (!g_ascii_isalnum(*p) && (*p != '-') && (*p != '_')) + return FALSE; + dot = FALSE; + } + } + + if (dot) + return FALSE; + + return (p - hostname <= HOST_NAME_MAX); +} + +static void +hostname_file_changed_cb(GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + _set_hostname_read(user_data); +} + +/*****************************************************************************/ + +static void +hostnamed_properties_changed(GDBusProxy *proxy, + GVariant * changed_properties, + char ** invalidated_properties, + gpointer user_data) +{ + NMHostnameManager * self = user_data; + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + GVariant * v_hostname; + + v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "StaticHostname"); + if (v_hostname) { + _set_hostname(self, g_variant_get_string(v_hostname, NULL)); + g_variant_unref(v_hostname); + } +} + +static void +setup_hostname_file_monitors(NMHostnameManager *self) +{ + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + GFileMonitor * monitor; + const char * path = HOSTNAME_FILE; + char * link_path = NULL; + struct stat file_stat; + GFile * file; + + /* resolve the path to the hostname file if it is a symbolic link */ + if (lstat(path, &file_stat) == 0 && S_ISLNK(file_stat.st_mode) + && (link_path = nm_utils_read_link_absolute(path, NULL))) { + path = link_path; + if (lstat(link_path, &file_stat) == 0 && S_ISLNK(file_stat.st_mode)) { + _LOGW("only one level of symbolic link indirection is allowed when " + "monitoring " HOSTNAME_FILE); + } + } + + /* monitor changes to hostname file */ + file = g_file_new_for_path(path); + monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + g_free(link_path); + if (monitor) { + priv->monitor_id = + g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self); + priv->monitor = monitor; + } + +#if defined(HOSTNAME_PERSIST_SUSE) + /* monitor changes to dhcp file to know whether the hostname is valid */ + file = g_file_new_for_path(CONF_DHCP); + monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + if (monitor) { + priv->dhcp_monitor_id = + g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self); + priv->dhcp_monitor = monitor; + } +#endif + + _set_hostname_read(self); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMHostnameManager *self = NM_HOSTNAME_MANAGER(object); + + switch (prop_id) { + case PROP_HOSTNAME: + g_value_set_string(value, nm_hostname_manager_get_hostname(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_hostname_manager_init(NMHostnameManager *self) +{} + +static void +constructed(GObject *object) +{ + NMHostnameManager * self = NM_HOSTNAME_MANAGER(object); + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + GDBusProxy * proxy; + GVariant * variant; + gs_free_error GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + 0, + NULL, + HOSTNAMED_SERVICE_NAME, + HOSTNAMED_SERVICE_PATH, + HOSTNAMED_SERVICE_INTERFACE, + NULL, + &error); + if (proxy) { + variant = g_dbus_proxy_get_cached_property(proxy, "StaticHostname"); + if (variant) { + _LOGI("hostname: using hostnamed"); + priv->hostnamed_proxy = proxy; + g_signal_connect(proxy, + "g-properties-changed", + G_CALLBACK(hostnamed_properties_changed), + self); + hostnamed_properties_changed(proxy, NULL, NULL, self); + g_variant_unref(variant); + } else { + _LOGI("hostname: couldn't get property from hostnamed"); + g_object_unref(proxy); + } + } else { + _LOGI("hostname: hostnamed not used as proxy creation failed with: %s", error->message); + g_clear_error(&error); + } + + if (!priv->hostnamed_proxy) + setup_hostname_file_monitors(self); + + G_OBJECT_CLASS(nm_hostname_manager_parent_class)->constructed(object); +} + +static void +dispose(GObject *object) +{ + NMHostnameManager * self = NM_HOSTNAME_MANAGER(object); + NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self); + + if (priv->hostnamed_proxy) { + g_signal_handlers_disconnect_by_func(priv->hostnamed_proxy, + G_CALLBACK(hostnamed_properties_changed), + self); + g_clear_object(&priv->hostnamed_proxy); + } + + if (priv->monitor) { + if (priv->monitor_id) + g_signal_handler_disconnect(priv->monitor, priv->monitor_id); + + g_file_monitor_cancel(priv->monitor); + g_clear_object(&priv->monitor); + } + + if (priv->dhcp_monitor) { + if (priv->dhcp_monitor_id) + g_signal_handler_disconnect(priv->dhcp_monitor, priv->dhcp_monitor_id); + + g_file_monitor_cancel(priv->dhcp_monitor); + g_clear_object(&priv->dhcp_monitor); + } + + nm_clear_g_free(&priv->current_hostname); + + G_OBJECT_CLASS(nm_hostname_manager_parent_class)->dispose(object); +} + +static void +nm_hostname_manager_class_init(NMHostnameManagerClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->dispose = dispose; + + obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_HOSTNAME_MANAGER_HOSTNAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-hostname-manager.h b/src/core/nm-hostname-manager.h new file mode 100644 index 0000000..0fa1377 --- /dev/null +++ b/src/core/nm-hostname-manager.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Søren Sandmann + * Dan Williams + * Tambet Ingo + * Copyright (C) 2007 - 2011, 2017 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#ifndef __NM_HOSTNAME_MANAGER_H__ +#define __NM_HOSTNAME_MANAGER_H__ + +#define NM_TYPE_HOSTNAME_MANAGER (nm_hostname_manager_get_type()) +#define NM_HOSTNAME_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_HOSTNAME_MANAGER, NMHostnameManager)) +#define NM_HOSTNAME_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_HOSTNAME_MANAGER, NMHostnameManagerClass)) +#define NM_IS_HOSTNAME_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_HOSTNAME_MANAGER)) +#define NM_IS_HOSTNAME_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_HOSTNAME_MANAGER)) +#define NM_HOSTNAME_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_HOSTNAME_MANAGER, NMHostnameManagerClass)) + +#define NM_HOSTNAME_MANAGER_HOSTNAME "hostname" + +typedef struct _NMHostnameManager NMHostnameManager; +typedef struct _NMHostnameManagerClass NMHostnameManagerClass; + +typedef void (*NMHostnameManagerSetHostnameCb)(const char *name, + gboolean result, + gpointer user_data); + +GType nm_hostname_manager_get_type(void); + +NMHostnameManager *nm_hostname_manager_get(void); + +const char *nm_hostname_manager_get_hostname(NMHostnameManager *self); + +char *nm_hostname_manager_read_hostname(NMHostnameManager *self); + +gboolean nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname); + +void nm_hostname_manager_set_transient_hostname(NMHostnameManager * self, + const char * hostname, + NMHostnameManagerSetHostnameCb cb, + gpointer user_data); + +gboolean nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostname); + +gboolean nm_hostname_manager_validate_hostname(const char *hostname); + +#endif /* __NM_HOSTNAME_MANAGER_H__ */ diff --git a/src/core/nm-iface-helper.c b/src/core/nm-iface-helper.c new file mode 100644 index 0000000..42483cf --- /dev/null +++ b/src/core/nm-iface-helper.c @@ -0,0 +1,858 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-c-list.h" + +#include "main-utils.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-linux-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "dhcp/nm-dhcp-manager.h" +#include "ndisc/nm-ndisc.h" +#include "ndisc/nm-lndp-ndisc.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-setting-ip6-config.h" +#include "systemd/nm-sd.h" + +#if !defined(NM_DIST_VERSION) + #define NM_DIST_VERSION VERSION +#endif + +#define NMIH_PID_FILE_FMT NMRUNDIR "/nm-iface-helper-%d.pid" + +/*****************************************************************************/ + +static struct { + GMainLoop *main_loop; + int ifindex; + gboolean is_vrf_device; + + guint dad_failed_id; + CList dad_failed_lst_head; +} gl /*obal*/ = { + .ifindex = -1, + .is_vrf_device = FALSE, +}; + +static struct { + gboolean slaac; + gboolean show_version; + gboolean become_daemon; + gboolean debug; + gboolean g_fatal_warnings; + gboolean slaac_required; + gboolean dhcp4_required; + int tempaddr; + char * ifname; + char * uuid; + char * stable_id; + char * dhcp4_address; + char * dhcp4_clientid; + char * dhcp4_hostname; + char * dhcp4_fqdn; + char * mud_url; + char * iid_str; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + char * logging_backend; + char * opt_log_level; + char * opt_log_domains; + guint32 priority_v4; + guint32 priority_v6; +} global_opt = { + .tempaddr = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + .priority_v4 = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, + .priority_v6 = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, +}; + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "nm-iface-helper" +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + global_opt.ifname, \ + NULL, \ + "iface-helper: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static void +dhcp4_state_changed(NMDhcpClient *client, + NMDhcpState state, + NMIP4Config * ip4_config, + GHashTable * options, + gpointer user_data) +{ + static NMIP4Config *last_config = NULL; + NMIP4Config * existing; + gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL; + gs_free_error GError *error = NULL; + + g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config)); + + _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state); + + switch (state) { + case NM_DHCP_STATE_BOUND: + case NM_DHCP_STATE_EXTENDED: + g_assert(ip4_config); + g_assert(nm_ip4_config_get_ifindex(ip4_config) == gl.ifindex); + + existing = nm_ip4_config_capture(nm_platform_get_multi_idx(NM_PLATFORM_GET), + NM_PLATFORM_GET, + gl.ifindex); + if (last_config) + nm_ip4_config_subtract(existing, last_config, 0); + + nm_ip4_config_merge(existing, ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0); + nm_ip4_config_add_dependent_routes(existing, + RT_TABLE_MAIN, + global_opt.priority_v4, + gl.is_vrf_device, + &ip4_dev_route_blacklist); + if (!nm_ip4_config_commit(existing, NM_PLATFORM_GET, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN)) + _LOGW(LOGD_DHCP4, "failed to apply DHCPv4 config"); + + if (!last_config && !nm_dhcp_client_accept(client, &error)) + _LOGW(LOGD_DHCP4, "failed to accept lease: %s", error->message); + + nm_platform_ip4_dev_route_blacklist_set(NM_PLATFORM_GET, + gl.ifindex, + ip4_dev_route_blacklist); + + if (last_config) + g_object_unref(last_config); + last_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_dhcp_client_get_ifindex(client)); + nm_ip4_config_replace(last_config, ip4_config, NULL); + break; + case NM_DHCP_STATE_TIMEOUT: + case NM_DHCP_STATE_DONE: + case NM_DHCP_STATE_FAIL: + if (global_opt.dhcp4_required) { + _LOGW(LOGD_DHCP4, "DHCPv4 timed out or failed, quitting..."); + g_main_loop_quit(gl.main_loop); + } else + _LOGW(LOGD_DHCP4, "DHCPv4 timed out or failed"); + break; + default: + break; + } +} + +static void +ndisc_config_changed(NMNDisc * ndisc, + const NMNDiscData *rdata, + guint changed_int, + gpointer user_data) +{ + NMNDiscConfigMap changed = changed_int; + static NMIP6Config *ndisc_config = NULL; + NMIP6Config * existing; + + existing = nm_ip6_config_capture(nm_platform_get_multi_idx(NM_PLATFORM_GET), + NM_PLATFORM_GET, + gl.ifindex, + global_opt.tempaddr); + if (ndisc_config) + nm_ip6_config_subtract(existing, ndisc_config, 0); + else { + ndisc_config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), gl.ifindex); + } + + if (changed & NM_NDISC_CONFIG_ADDRESSES) { + guint8 plen; + guint32 ifa_flags; + + /* Check, whether kernel is recent enough to help user space handling RA. + * If it's not supported, we have no ipv6-privacy and must add autoconf + * addresses as /128. The reason for the /128 is to prevent the kernel + * from adding a prefix route for this address. */ + ifa_flags = 0; + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) { + ifa_flags |= IFA_F_NOPREFIXROUTE; + if (NM_IN_SET(global_opt.tempaddr, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)) + ifa_flags |= IFA_F_MANAGETEMPADDR; + plen = 64; + } else + plen = 128; + + nm_ip6_config_reset_addresses_ndisc(ndisc_config, + rdata->addresses, + rdata->addresses_n, + plen, + ifa_flags); + } + + if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) { + nm_ip6_config_reset_routes_ndisc( + ndisc_config, + rdata->gateways, + rdata->gateways_n, + rdata->routes, + rdata->routes_n, + RT_TABLE_MAIN, + global_opt.priority_v6, + nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)); + } + + if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) { + /* Unsupported until systemd DHCPv6 is ready */ + } + + if (changed & NM_NDISC_CONFIG_HOP_LIMIT) + nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NM_PLATFORM_GET, + global_opt.ifname, + rdata->hop_limit); + + if (changed & NM_NDISC_CONFIG_REACHABLE_TIME) { + nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NM_PLATFORM_GET, + global_opt.ifname, + rdata->reachable_time_ms); + } + + if (changed & NM_NDISC_CONFIG_RETRANS_TIMER) { + nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NM_PLATFORM_GET, + global_opt.ifname, + rdata->retrans_timer_ms); + } + + if (changed & NM_NDISC_CONFIG_MTU) { + nm_platform_sysctl_ip_conf_set_int64(NM_PLATFORM_GET, + AF_INET6, + global_opt.ifname, + "mtu", + rdata->mtu); + } + + nm_ip6_config_merge(existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT, 0); + nm_ip6_config_add_dependent_routes(existing, + RT_TABLE_MAIN, + global_opt.priority_v6, + gl.is_vrf_device); + if (!nm_ip6_config_commit(existing, NM_PLATFORM_GET, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, NULL)) + _LOGW(LOGD_IP6, "failed to apply IPv6 config"); +} + +static void +ndisc_ra_timeout(NMNDisc *ndisc, gpointer user_data) +{ + if (global_opt.slaac_required) { + _LOGW(LOGD_IP6, "IPv6 timed out or failed, quitting..."); + g_main_loop_quit(gl.main_loop); + } else + _LOGW(LOGD_IP6, "IPv6 timed out or failed"); +} + +static gboolean +quit_handler(gpointer user_data) +{ + g_main_loop_quit(gl.main_loop); + return G_SOURCE_REMOVE; +} + +static void +setup_signals(void) +{ + signal(SIGPIPE, SIG_IGN); + g_unix_signal_add(SIGINT, quit_handler, NULL); + g_unix_signal_add(SIGTERM, quit_handler, NULL); +} + +static gboolean +do_early_setup(int *argc, char **argv[]) +{ + gint64 priority64_v4 = -1; + gint64 priority64_v6 = -1; + GOptionEntry options[] = { + /* Interface/IP config */ + {"ifname", + 'i', + 0, + G_OPTION_ARG_STRING, + &global_opt.ifname, + N_("The interface to manage"), + "eth0"}, + {"uuid", + 'u', + 0, + G_OPTION_ARG_STRING, + &global_opt.uuid, + N_("Connection UUID"), + "661e8cd0-b618-46b8-9dc9-31a52baaa16b"}, + {"stable-id", + '\0', + 0, + G_OPTION_ARG_STRING, + &global_opt.stable_id, + N_("Connection Token for Stable IDs"), + "eth"}, + {"slaac", + 's', + 0, + G_OPTION_ARG_NONE, + &global_opt.slaac, + N_("Whether to manage IPv6 SLAAC"), + NULL}, + {"slaac-required", + '6', + 0, + G_OPTION_ARG_NONE, + &global_opt.slaac_required, + N_("Whether SLAAC must be successful"), + NULL}, + {"slaac-tempaddr", + 't', + 0, + G_OPTION_ARG_INT, + &global_opt.tempaddr, + N_("Use an IPv6 temporary privacy address"), + NULL}, + {"dhcp4", + 'd', + 0, + G_OPTION_ARG_STRING, + &global_opt.dhcp4_address, + N_("Current DHCPv4 address"), + NULL}, + {"dhcp4-required", + '4', + 0, + G_OPTION_ARG_NONE, + &global_opt.dhcp4_required, + N_("Whether DHCPv4 must be successful"), + NULL}, + {"dhcp4-clientid", + 'c', + 0, + G_OPTION_ARG_STRING, + &global_opt.dhcp4_clientid, + N_("Hex-encoded DHCPv4 client ID"), + NULL}, + {"dhcp4-hostname", + 'h', + 0, + G_OPTION_ARG_STRING, + &global_opt.dhcp4_hostname, + N_("Hostname to send to DHCP server"), + N_("barbar")}, + {"dhcp4-fqdn", + 'F', + 0, + G_OPTION_ARG_STRING, + &global_opt.dhcp4_fqdn, + N_("FQDN to send to DHCP server"), + N_("host.domain.org")}, + {"priority4", + '\0', + 0, + G_OPTION_ARG_INT64, + &priority64_v4, + N_("Route priority for IPv4"), + N_("0")}, + {"priority6", + '\0', + 0, + G_OPTION_ARG_INT64, + &priority64_v6, + N_("Route priority for IPv6"), + N_("1024")}, + {"iid", + 'e', + 0, + G_OPTION_ARG_STRING, + &global_opt.iid_str, + N_("Hex-encoded Interface Identifier"), + ""}, + {"addr-gen-mode", + 'e', + 0, + G_OPTION_ARG_INT, + &global_opt.addr_gen_mode, + N_("IPv6 SLAAC address generation mode"), + "eui64"}, + {"logging-backend", + '\0', + 0, + G_OPTION_ARG_STRING, + &global_opt.logging_backend, + N_("The logging backend configuration value. See logging.backend in NetworkManager.conf"), + NULL}, + + /* Logging/debugging */ + {"version", + 'V', + 0, + G_OPTION_ARG_NONE, + &global_opt.show_version, + N_("Print NetworkManager version and exit"), + NULL}, + {"no-daemon", + 'n', + G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, + &global_opt.become_daemon, + N_("Don't become a daemon"), + NULL}, + {"debug", + 'b', + 0, + G_OPTION_ARG_NONE, + &global_opt.debug, + N_("Don't become a daemon, and log to stderr"), + NULL}, + {"log-level", + 0, + 0, + G_OPTION_ARG_STRING, + &global_opt.opt_log_level, + N_("Log level: one of [%s]"), + "INFO"}, + {"log-domains", + 0, + 0, + G_OPTION_ARG_STRING, + &global_opt.opt_log_domains, + N_("Log domains separated by ',': any combination of [%s]"), + "PLATFORM,RFKILL,WIFI"}, + {"g-fatal-warnings", + 0, + 0, + G_OPTION_ARG_NONE, + &global_opt.g_fatal_warnings, + N_("Make all warnings fatal"), + NULL}, + {NULL}}; + + if (!nm_main_utils_early_setup("nm-iface-helper", + argc, + argv, + options, + NULL, + NULL, + _("nm-iface-helper is a small, standalone process that manages " + "a single network interface."))) + return FALSE; + + if (priority64_v4 >= 0 && priority64_v4 <= G_MAXUINT32) + global_opt.priority_v4 = (guint32) priority64_v4; + if (priority64_v6 >= 0 && priority64_v6 <= G_MAXUINT32) + global_opt.priority_v6 = (guint32) priority64_v6; + return TRUE; +} + +typedef struct { + NMPlatform *platform; + NMNDisc * ndisc; +} DadFailedHandleData; + +static gboolean +dad_failed_handle_idle(gpointer user_data) +{ + DadFailedHandleData *data = user_data; + NMCListElem * elem; + + while ((elem = c_list_first_entry(&gl.dad_failed_lst_head, NMCListElem, lst))) { + nm_auto_nmpobj const NMPObject *obj = elem->data; + + nm_c_list_elem_free(elem); + + if (nm_ndisc_dad_addr_is_fail_candidate(data->platform, obj)) { + nm_ndisc_dad_failed(data->ndisc, &NMP_OBJECT_CAST_IP6_ADDRESS(obj)->address, TRUE); + } + } + + gl.dad_failed_id = 0; + return G_SOURCE_REMOVE; +} + +static void +ip6_address_changed(NMPlatform * platform, + int obj_type_i, + int iface, + const NMPlatformIP6Address *addr, + int change_type_i, + NMNDisc * ndisc) +{ + const NMPlatformSignalChangeType change_type = change_type_i; + DadFailedHandleData * data; + + if (!nm_ndisc_dad_addr_is_fail_candidate_event(change_type, addr)) + return; + + c_list_link_tail( + &gl.dad_failed_lst_head, + &nm_c_list_elem_new_stale((gpointer) nmp_object_ref(NMP_OBJECT_UP_CAST(addr)))->lst); + if (gl.dad_failed_id) + return; + + data = g_slice_new(DadFailedHandleData); + data->platform = platform; + data->ndisc = ndisc; + gl.dad_failed_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + dad_failed_handle_idle, + data, + nm_g_slice_free_fcn(DadFailedHandleData)); +} + +int +main(int argc, char *argv[]) +{ + char * bad_domains = NULL; + gs_free_error GError *error = NULL; + gboolean wrote_pidfile = FALSE; + gs_free char * pidfile = NULL; + gs_unref_object NMDhcpClient *dhcp4_client = NULL; + gs_unref_object NMNDisc *ndisc = NULL; + gs_unref_bytes GBytes *hwaddr = NULL; + gs_unref_bytes GBytes *bcast_hwaddr = NULL; + gs_unref_bytes GBytes *client_id = NULL; + gs_free NMUtilsIPv6IfaceId *iid = NULL; + const NMPlatformLink * pllink; + guint sd_id; + int errsv; + + c_list_init(&gl.dad_failed_lst_head); + + setpgid(getpid(), getpid()); + + if (!do_early_setup(&argc, &argv)) + return 1; + + nm_logging_init_pre("nm-iface-helper", + g_strdup_printf("%s[%ld] (%s): ", + _NMLOG_PREFIX_NAME, + (long) getpid(), + global_opt.ifname ?: "???")); + + if (global_opt.g_fatal_warnings) { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal(G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal(fatal_mask); + } + + if (global_opt.show_version) { + fprintf(stdout, NM_DIST_VERSION "\n"); + return 0; + } + + nm_main_utils_ensure_root(); + + if (!global_opt.ifname || !global_opt.uuid) { + fprintf(stderr, _("An interface name and UUID are required\n")); + return 1; + } + + gl.ifindex = nmp_utils_if_nametoindex(global_opt.ifname); + if (gl.ifindex <= 0) { + errsv = errno; + fprintf(stderr, + _("Failed to find interface index for %s (%s)\n"), + global_opt.ifname, + nm_strerror_native(errsv)); + return 1; + } + pidfile = g_strdup_printf(NMIH_PID_FILE_FMT, gl.ifindex); + nm_main_utils_ensure_not_running_pidfile(pidfile); + + nm_main_utils_ensure_rundir(); + + if (!nm_logging_setup(global_opt.opt_log_level, + global_opt.opt_log_domains, + &bad_domains, + &error)) { + fprintf(stderr, + _("%s. Please use --help to see a list of valid options.\n"), + error->message); + return 1; + } else if (bad_domains) { + fprintf(stderr, + _("Ignoring unrecognized log domain(s) '%s' passed on command line.\n"), + bad_domains); + nm_clear_g_free(&bad_domains); + } + + if (global_opt.become_daemon && !global_opt.debug) { + if (daemon(0, 0) < 0) { + errsv = errno; + fprintf(stderr, + _("Could not daemonize: %s [error %u]\n"), + nm_strerror_native(errsv), + errsv); + return 1; + } + if (nm_main_utils_write_pidfile(pidfile)) + wrote_pidfile = TRUE; + } + + /* Set up unix signal handling - before creating threads, but after daemonizing! */ + gl.main_loop = g_main_loop_new(NULL, FALSE); + setup_signals(); + + nm_logging_init(global_opt.logging_backend, global_opt.debug); + + _LOGI(LOGD_CORE, "nm-iface-helper (version " NM_DIST_VERSION ") is starting..."); + + /* Set up platform interaction layer */ + nm_linux_platform_setup(); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, gl.ifindex); + if (pllink) { + hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address); + bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast); + + if (pllink->master > 0) { + gl.is_vrf_device = + nm_platform_link_get_type(NM_PLATFORM_GET, pllink->master) == NM_LINK_TYPE_VRF; + } + } + + if (global_opt.iid_str) { + GBytes *bytes; + gsize ignored = 0; + + bytes = nm_utils_hexstr2bin(global_opt.iid_str); + if (!bytes || g_bytes_get_size(bytes) != sizeof(*iid)) { + fprintf(stderr, _("(%s): Invalid IID %s\n"), global_opt.ifname, global_opt.iid_str); + return 1; + } + iid = g_bytes_unref_to_data(bytes, &ignored); + } + + if (global_opt.dhcp4_clientid) { + /* this string is just a plain hex-string. Unlike ipv4.dhcp-client-id, which + * is parsed via nm_dhcp_utils_client_id_string_to_bytes(). */ + client_id = nm_utils_hexstr2bin(global_opt.dhcp4_clientid); + if (!client_id || g_bytes_get_size(client_id) < 2) { + fprintf(stderr, + _("(%s): Invalid DHCP client-id %s\n"), + global_opt.ifname, + global_opt.dhcp4_clientid); + return 1; + } + } + + if (global_opt.dhcp4_address) { + nm_platform_sysctl_ip_conf_set(NM_PLATFORM_GET, + AF_INET, + global_opt.ifname, + "promote_secondaries", + "1"); + + dhcp4_client = nm_dhcp_manager_start_ip4(nm_dhcp_manager_get(), + nm_platform_get_multi_idx(NM_PLATFORM_GET), + global_opt.ifname, + gl.ifindex, + hwaddr, + bcast_hwaddr, + global_opt.uuid, + RT_TABLE_MAIN, + global_opt.priority_v4, + !!global_opt.dhcp4_hostname, + global_opt.dhcp4_hostname, + global_opt.dhcp4_fqdn, + NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4, + global_opt.mud_url, + client_id, + NM_DHCP_TIMEOUT_DEFAULT, + NULL, + global_opt.dhcp4_address, + NULL, + NULL, + &error); + if (!dhcp4_client) + g_error("failure to start DHCP: %s", error->message); + + g_signal_connect(dhcp4_client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK(dhcp4_state_changed), + NULL); + } + + if (global_opt.slaac) { + NMUtilsStableType stable_type = NM_UTILS_STABLE_TYPE_UUID; + const char * stable_id = global_opt.uuid; + int router_solicitation_interval; + int router_solicitations; + guint32 default_ra_timeout; + int max_addresses; + + nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, gl.ifindex, TRUE); + + if (global_opt.stable_id + && (global_opt.stable_id[0] >= '0' && global_opt.stable_id[0] <= '9') + && global_opt.stable_id[1] == ' ') { + /* strict parsing of --stable-id, which is the numeric stable-type + * and the ID, joined with one space. For now, only support stable-types + * from 0 to 9. */ + stable_type = (global_opt.stable_id[0] - '0'); + stable_id = &global_opt.stable_id[2]; + } + + nm_lndp_ndisc_get_sysctl(NM_PLATFORM_GET, + global_opt.ifname, + &max_addresses, + &router_solicitations, + &router_solicitation_interval, + &default_ra_timeout); + + ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET, + gl.ifindex, + global_opt.ifname, + stable_type, + stable_id, + global_opt.addr_gen_mode, + NM_NDISC_NODE_TYPE_HOST, + max_addresses, + router_solicitations, + router_solicitation_interval, + default_ra_timeout, + NULL); + g_assert(ndisc); + + if (iid) + nm_ndisc_set_iid(ndisc, *iid); + + nm_platform_sysctl_ip_conf_set(NM_PLATFORM_GET, + AF_INET6, + global_opt.ifname, + "accept_ra", + "0"); + + g_signal_connect(NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK(ip6_address_changed), + ndisc); + g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(ndisc_config_changed), NULL); + g_signal_connect(ndisc, NM_NDISC_RA_TIMEOUT_SIGNAL, G_CALLBACK(ndisc_ra_timeout), NULL); + nm_ndisc_start(ndisc); + } + + sd_id = nm_sd_event_attach_default(); + + g_main_loop_run(gl.main_loop); + + nm_clear_g_source(&gl.dad_failed_id); + nm_c_list_elem_free_all(&gl.dad_failed_lst_head, (GDestroyNotify) nmp_object_unref); + + if (pidfile && wrote_pidfile) + unlink(pidfile); + + _LOGI(LOGD_CORE, "exiting"); + + nm_clear_g_source(&sd_id); + nm_clear_pointer(&gl.main_loop, g_main_loop_unref); + return 0; +} + +/*****************************************************************************/ + +const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = { + /* For nm-iface-helper there is no option to choose a DHCP plugin. + * It just uses the "internal" one. */ + &_nm_dhcp_client_factory_internal, +}; + +/*****************************************************************************/ +/* Stub functions */ + +#include "nm-config.h" +#include "devices/nm-device.h" +#include "nm-active-connection.h" +#include "nm-dbus-manager.h" + +void +nm_main_config_reload(int signal) +{ + _LOGI(LOGD_CORE, "reloading configuration not supported"); +} + +NMConfig * +nm_config_get(void) +{ + return GUINT_TO_POINTER(1); +} + +NMConfigData * +nm_config_get_data_orig(NMConfig *config) +{ + return GUINT_TO_POINTER(1); +} + +char * +nm_config_data_get_value(const NMConfigData * config_data, + const char * group, + const char * key, + NMConfigGetValueFlags flags) +{ + return NULL; +} + +NMConfigConfigureAndQuitType +nm_config_get_configure_and_quit(NMConfig *config) +{ + return NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED; +} + +NMDBusManager * +nm_dbus_manager_get(void) +{ + return NULL; +} + +gboolean +nm_dbus_manager_is_stopping(NMDBusManager *self) +{ + return FALSE; +} + +void +_nm_dbus_manager_obj_export(NMDBusObject *obj) +{} + +void +_nm_dbus_manager_obj_unexport(NMDBusObject *obj) +{} + +void +_nm_dbus_manager_obj_notify(NMDBusObject *obj, guint n_pspecs, const GParamSpec *const *pspecs) +{} + +void +_nm_dbus_manager_obj_emit_signal(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo * signal_info, + GVariant * args) +{} + +GType +nm_device_get_type(void) +{ + g_return_val_if_reached(0); +} + +GType +nm_active_connection_get_type(void) +{ + g_return_val_if_reached(0); +} diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c new file mode 100644 index 0000000..040483e --- /dev/null +++ b/src/core/nm-ip-config.c @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-ip-config.h" + +#include "nm-l3cfg.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMIPConfig, PROP_L3CFG, PROP_IS_VPN, ); + +typedef struct _NMIPConfigPrivate { + NML3Cfg *l3cfg; + bool is_vpn : 1; +} NMIPConfigPrivate; + +G_DEFINE_ABSTRACT_TYPE(NMIPConfig, nm_ip_config, NM_TYPE_DBUS_OBJECT) + +#define NM_IP_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMIPConfig, NM_IS_IP_CONFIG) + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMIPConfig * self = NM_IP_CONFIG(object); + NMIPConfigPrivate *priv = NM_IP_CONFIG_GET_PRIVATE(self); + + (void) priv; + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMIPConfig * self = NM_IP_CONFIG(object); + NMIPConfigPrivate *priv = NM_IP_CONFIG_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_L3CFG: + /* construct-only */ + priv->l3cfg = nm_g_object_ref(g_value_get_pointer(value)); + nm_assert(!priv->l3cfg || NM_IS_L3CFG(priv->l3cfg)); + break; + case PROP_IS_VPN: + /* construct-only */ + priv->is_vpn = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_ip_config_init(NMIPConfig *self) +{ + NMIPConfigPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_IP_CONFIG, NMIPConfigPrivate); + + self->_priv = priv; +} + +NMIPConfig * +nm_ip_config_new(int addr_family, NML3Cfg *l3cfg, gboolean is_vpn) +{ + nm_assert_addr_family(addr_family); + nm_assert(NM_L3CFG(l3cfg)); + + return g_object_new(NM_IS_IPv4(addr_family) ? nm_ip4_config_get_type() + : nm_ip6_config_get_type(), + NM_IP_CONFIG_L3CFG, + l3cfg, + NM_IP_CONFIG_IS_VPN, + is_vpn, + NULL); +} + +static void +finalize(GObject *object) +{ + NMIPConfig * self = NM_IP_CONFIG(object); + NMIPConfigPrivate *priv = NM_IP_CONFIG_GET_PRIVATE(self); + + nm_g_object_unref(priv->l3cfg); + + G_OBJECT_CLASS(nm_ip_config_parent_class)->finalize(object); +} + +static void +nm_ip_config_class_init(NMIPConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMIPConfigPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + obj_properties[PROP_L3CFG] = + g_param_spec_pointer(NM_IP_CONFIG_L3CFG, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IS_VPN] = + g_param_spec_boolean(NM_IP_CONFIG_IS_VPN, + "", + "", + FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-ip-config.h b/src/core/nm-ip-config.h new file mode 100644 index 0000000..8e1a593 --- /dev/null +++ b/src/core/nm-ip-config.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2013 Red Hat, Inc. + */ + +#ifndef __NM_IP_CONFIG_H__ +#define __NM_IP_CONFIG_H__ + +#include "nm-dbus-object.h" + +/*****************************************************************************/ + +#define NM_TYPE_IP_CONFIG (nm_ip_config_get_type()) +#define NM_IP_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP_CONFIG, NMIPConfig)) +#define NM_IP_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_IP_CONFIG, NMIPConfigClass)) +#define NM_IS_IP_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_IP_CONFIG)) +#define NM_IS_IP_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_IP_CONFIG)) +#define NM_IP_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IP_CONFIG, NMIPConfigClass)) + +#define NM_IP_CONFIG_L3CFG "l3cfg" +#define NM_IP_CONFIG_IS_VPN "is-vpn" + +struct _NMIPConfigPrivate; + +struct _NMIPConfig { + NMDBusObject parent; + struct _NMIPConfigPrivate *_priv; +}; + +typedef struct { + NMDBusObjectClass parent; + gboolean is_ipv4; + int addr_family; +} NMIPConfigClass; + +GType nm_ip_config_get_type(void); +GType nm_ip4_config_get_type(void); +GType nm_ip6_config_get_type(void); + +NMIPConfig *nm_ip_config_new(int addr_family, NML3Cfg *l3cfg, gboolean is_vpn); + +#endif /* __NM_IP_CONFIG_H__ */ diff --git a/src/core/nm-ip4-config.c b/src/core/nm-ip4-config.c new file mode 100644 index 0000000..8014549 --- /dev/null +++ b/src/core/nm-ip4-config.c @@ -0,0 +1,3320 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-ip4-config.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" + +#include "nm-utils.h" +#include "platform/nmp-object.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-dbus-object.h" + +/*****************************************************************************/ + +/* internal guint32 are assigned to gobject properties of type uint. Ensure, that uint is large enough */ +G_STATIC_ASSERT(sizeof(uint) >= sizeof(guint32)); +G_STATIC_ASSERT(G_MAXUINT >= 0xFFFFFFFF); + +/*****************************************************************************/ + +static gboolean +_route_valid(const NMPlatformIP4Route *r) +{ + return r && r->plen <= 32 + && r->network == nm_utils_ip4_address_clear_host_address(r->network, r->plen); +} + +/*****************************************************************************/ + +static void +_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + nmp_object_id_hash_update((NMPObject *) obj, h); +} + +static gboolean +_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + return nmp_object_id_equal((NMPObject *) obj_a, (NMPObject *) obj_b); +} + +void +nm_ip_config_dedup_multi_idx_type_init(NMIPConfigDedupMultiIdxType *idx_type, + NMPObjectType obj_type) +{ + static const NMDedupMultiIdxTypeClass idx_type_class = { + .idx_obj_id_hash_update = _idx_obj_id_hash_update, + .idx_obj_id_equal = _idx_obj_id_equal, + }; + + nm_dedup_multi_idx_type_init((NMDedupMultiIdxType *) idx_type, &idx_type_class); + idx_type->obj_type = obj_type; +} + +/*****************************************************************************/ + +gboolean +_nm_ip_config_add_obj(NMDedupMultiIndex * multi_idx, + NMIPConfigDedupMultiIdxType *idx_type, + int ifindex, + const NMPObject * obj_new, + const NMPlatformObject * pl_new, + gboolean merge, + gboolean append_force, + const NMPObject ** out_obj_old /* returns a reference! */, + const NMPObject ** out_obj_new /* does not return a reference */) +{ + NMPObject obj_new_stackinit; + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new; + + nm_assert(multi_idx); + nm_assert(idx_type); + nm_assert(NM_IN_SET(idx_type->obj_type, + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(ifindex > 0); + + /* we go through extra lengths to accept a full obj_new object. That one, + * can be reused by increasing the ref-count. */ + if (!obj_new) { + nm_assert(pl_new); + obj_new = nmp_object_stackinit(&obj_new_stackinit, idx_type->obj_type, pl_new); + NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(&obj_new_stackinit)->ifindex = ifindex; + } else { + nm_assert(!pl_new); + nm_assert(NMP_OBJECT_GET_TYPE(obj_new) == idx_type->obj_type); + if (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_new)->ifindex != ifindex) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(&obj_new_stackinit)->ifindex = ifindex; + } + } + nm_assert(NMP_OBJECT_GET_TYPE(obj_new) == idx_type->obj_type); + nm_assert(nmp_object_is_alive(obj_new)); + + entry_old = nm_dedup_multi_index_lookup_obj(multi_idx, &idx_type->parent, obj_new); + + if (entry_old) { + gboolean modified = FALSE; + const NMPObject *obj_old = entry_old->obj; + + if (nmp_object_equal(obj_new, obj_old)) { + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto append_force_and_out; + } + + /* if @merge, we merge the new object with the existing one. + * Otherwise, we replace it entirely. */ + if (merge) { + switch (idx_type->obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + /* for addresses that we read from the kernel, we keep the timestamps as defined + * by the previous source (item_old). The reason is, that the other source configured the lifetimes + * with "what should be" and the kernel values are "what turned out after configuring it". + * + * For other sources, the longer lifetime wins. */ + if ((obj_new->ip_address.addr_source == NM_IP_CONFIG_SOURCE_KERNEL + && obj_old->ip_address.addr_source != NM_IP_CONFIG_SOURCE_KERNEL) + || nm_platform_ip_address_cmp_expiry(NMP_OBJECT_CAST_IP_ADDRESS(obj_old), + NMP_OBJECT_CAST_IP_ADDRESS(obj_new)) + > 0) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_address.timestamp = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->timestamp; + obj_new_stackinit.ip_address.lifetime = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->lifetime; + obj_new_stackinit.ip_address.preferred = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->preferred; + modified = TRUE; + } + + /* keep the maximum addr_source. */ + if (obj_new->ip_address.addr_source < obj_old->ip_address.addr_source) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source; + modified = TRUE; + } + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + /* keep the maximum rt_source. */ + if (obj_new->ip_route.rt_source < obj_old->ip_route.rt_source) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source; + modified = TRUE; + } + break; + default: + nm_assert_not_reached(); + break; + } + + if (modified && nmp_object_equal(obj_new, obj_old)) { + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto append_force_and_out; + } + } + } + + if (!nm_dedup_multi_index_add_full(multi_idx, + &idx_type->parent, + obj_new, + append_force ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE + : NM_DEDUP_MULTI_IDX_MODE_APPEND, + NULL, + entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, + NULL, + &entry_new, + out_obj_old)) { + nm_assert_not_reached(); + NM_SET_OUT(out_obj_new, NULL); + return FALSE; + } + + NM_SET_OUT(out_obj_new, entry_new->obj); + return TRUE; + +append_force_and_out: + NM_SET_OUT(out_obj_old, nmp_object_ref(entry_old->obj)); + NM_SET_OUT(out_obj_new, entry_old->obj); + if (append_force) { + if (nm_dedup_multi_entry_reorder(entry_old, NULL, TRUE)) + return TRUE; + } + return FALSE; +} + +/** + * _nm_ip_config_lookup_ip_route: + * @multi_idx: + * @idx_type: + * @needle: + * @cmp_type: after lookup, filter the result by comparing with @cmp_type. Only + * return the result, if it compares equal to @needle according to this @cmp_type. + * Note that the index uses %NM_PLATFORM_IP_ROUTE_CMP_TYPE_DST type, so passing + * that compare-type means not to filter any further. + * + * Returns: the found entry or %NULL. + */ +const NMDedupMultiEntry * +_nm_ip_config_lookup_ip_route(const NMDedupMultiIndex * multi_idx, + const NMIPConfigDedupMultiIdxType *idx_type, + const NMPObject * needle, + NMPlatformIPRouteCmpType cmp_type) +{ + const NMDedupMultiEntry *entry; + + nm_assert(multi_idx); + nm_assert(idx_type); + nm_assert(NM_IN_SET(idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(NMP_OBJECT_GET_TYPE(needle) == idx_type->obj_type); + + entry = nm_dedup_multi_index_lookup_obj(multi_idx, &idx_type->parent, needle); + if (!entry) + return NULL; + + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { + nm_assert((NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP4_ROUTE + && nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP4_ROUTE(needle), + cmp_type) + == 0) + || (NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP6_ROUTE + && nm_platform_ip6_route_cmp(NMP_OBJECT_CAST_IP6_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP6_ROUTE(needle), + cmp_type) + == 0)); + } else { + if (NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP4_ROUTE) { + if (nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP4_ROUTE(needle), + cmp_type) + != 0) + return NULL; + } else { + if (nm_platform_ip6_route_cmp(NMP_OBJECT_CAST_IP6_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP6_ROUTE(needle), + cmp_type) + != 0) + return NULL; + } + } + return entry; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMIP4Config, + PROP_MULTI_IDX, + PROP_IFINDEX, + PROP_ADDRESS_DATA, + PROP_ADDRESSES, + PROP_ROUTE_DATA, + PROP_ROUTES, + PROP_GATEWAY, + PROP_NAMESERVER_DATA, + PROP_NAMESERVERS, + PROP_DOMAINS, + PROP_SEARCHES, + PROP_DNS_OPTIONS, + PROP_WINS_SERVER_DATA, + PROP_WINS_SERVERS, + PROP_DNS_PRIORITY, ); + +typedef struct { + bool metered : 1; + bool never_default : 1; + guint32 mtu; + int ifindex; + NMIPConfigSource mtu_source; + int dns_priority; + NMSettingConnectionMdns mdns; + NMSettingConnectionLlmnr llmnr; + GArray * nameservers; + GPtrArray * domains; + GPtrArray * searches; + GPtrArray * dns_options; + GArray * nis; + char * nis_domain; + GArray * wins; + GVariant * address_data_variant; + GVariant * addresses_variant; + GVariant * route_data_variant; + GVariant * routes_variant; + NMDedupMultiIndex * multi_idx; + const NMPObject * best_default_route; + union { + NMIPConfigDedupMultiIdxType idx_ip4_addresses_; + NMDedupMultiIdxType idx_ip4_addresses; + }; + union { + NMIPConfigDedupMultiIdxType idx_ip4_routes_; + NMDedupMultiIdxType idx_ip4_routes; + }; + NMIPConfigFlags config_flags; +} NMIP4ConfigPrivate; + +struct _NMIP4Config { + NMIPConfig parent; + NMIP4ConfigPrivate _priv; +}; + +struct _NMIP4ConfigClass { + NMIPConfigClass parent; +}; + +G_DEFINE_TYPE(NMIP4Config, nm_ip4_config, NM_TYPE_IP_CONFIG) + +#define NM_IP4_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP4Config, NM_IS_IP4_CONFIG) + +/*****************************************************************************/ + +static void +_add_address(NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new); +static void _add_route(NMIP4Config * self, + const NMPObject *obj_new, + const NMPlatformIP4Route *new, + const NMPObject **out_obj_new); +static const NMDedupMultiEntry * +_lookup_route(const NMIP4Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type); + +/*****************************************************************************/ + +int +nm_ip4_config_get_ifindex(const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE(self)->ifindex; +} + +NMDedupMultiIndex * +nm_ip4_config_get_multi_idx(const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE(self)->multi_idx; +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_ip4_config_lookup_addresses(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip4_addresses, NULL); +} + +void +nm_ip_config_iter_ip4_address_init(NMDedupMultiIter *ipconf_iter, const NMIP4Config *self) +{ + nm_assert(NM_IS_IP4_CONFIG(self)); + + nm_dedup_multi_iter_init(ipconf_iter, nm_ip4_config_lookup_addresses(self)); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_ip4_config_lookup_routes(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip4_routes, NULL); +} + +void +nm_ip_config_iter_ip4_route_init(NMDedupMultiIter *ipconf_iter, const NMIP4Config *self) +{ + nm_assert(NM_IS_IP4_CONFIG(self)); + + nm_dedup_multi_iter_init(ipconf_iter, nm_ip4_config_lookup_routes(self)); +} + +/*****************************************************************************/ + +const NMPObject * +_nm_ip_config_best_default_route_find_better(const NMPObject *obj_cur, const NMPObject *obj_cmp) +{ + nm_assert(!obj_cur + || NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_cur), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(!obj_cmp + || (!obj_cur + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_cmp), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)) + || NMP_OBJECT_GET_TYPE(obj_cur) == NMP_OBJECT_GET_TYPE(obj_cmp)); + nm_assert(!obj_cur || nmp_object_ip_route_is_best_defaut_route(obj_cur)); + + /* assumes that @obj_cur is already the best default route (or NULL). It checks whether + * @obj_cmp is also a default route and returns the best of both. */ + if (obj_cmp && nmp_object_ip_route_is_best_defaut_route(obj_cmp)) { + guint32 metric_cur, metric_cmp; + + if (!obj_cur) + return obj_cmp; + + metric_cur = NMP_OBJECT_CAST_IP_ROUTE(obj_cur)->metric; + metric_cmp = NMP_OBJECT_CAST_IP_ROUTE(obj_cmp)->metric; + + if (metric_cmp < metric_cur) + return obj_cmp; + + if (metric_cmp == metric_cur) { + int c; + + /* Routes have the same metric. We still want to deterministically + * prefer one or the other. It's important to consistently choose one + * or the other, so that the order doesn't matter how routes are added + * (and merged). */ + c = nmp_object_cmp(obj_cur, obj_cmp); + if (c != 0) + return c < 0 ? obj_cur : obj_cmp; + + /* as last resort, compare pointers. */ + if (obj_cmp < obj_cur) + return obj_cmp; + } + } + return obj_cur; +} + +gboolean +_nm_ip_config_best_default_route_merge(const NMPObject **best_default_route, + const NMPObject * new_candidate) +{ + new_candidate = + _nm_ip_config_best_default_route_find_better(*best_default_route, new_candidate); + return nmp_object_ref_set(best_default_route, new_candidate); +} + +const NMPObject * +nm_ip4_config_best_default_route_get(const NMIP4Config *self) +{ + g_return_val_if_fail(NM_IS_IP4_CONFIG(self), NULL); + + return NM_IP4_CONFIG_GET_PRIVATE(self)->best_default_route; +} + +const NMPObject * +_nm_ip4_config_best_default_route_find(const NMIP4Config *self) +{ + NMDedupMultiIter ipconf_iter; + const NMPObject *new_best_default_route = NULL; + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, NULL) { + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, + ipconf_iter.current->obj); + } + return new_best_default_route; +} + +in_addr_t +nmtst_ip4_config_get_gateway(NMIP4Config *config) +{ + const NMPObject *rt; + + g_assert(NM_IS_IP4_CONFIG(config)); + + rt = nm_ip4_config_best_default_route_get(config); + if (!rt) + return 0; + return NMP_OBJECT_CAST_IP4_ROUTE(rt)->gateway; +} + +/*****************************************************************************/ + +static void +_notify_addresses(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + nm_clear_g_variant(&priv->address_data_variant); + nm_clear_g_variant(&priv->addresses_variant); + nm_gobject_notify_together(self, PROP_ADDRESS_DATA, PROP_ADDRESSES); +} + +static void +_notify_routes(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + nm_assert(priv->best_default_route == _nm_ip4_config_best_default_route_find(self)); + nm_clear_g_variant(&priv->route_data_variant); + nm_clear_g_variant(&priv->routes_variant); + nm_gobject_notify_together(self, PROP_ROUTE_DATA, PROP_ROUTES); +} + +/*****************************************************************************/ + +static int +sort_captured_addresses(const CList *lst_a, const CList *lst_b, gconstpointer user_data) +{ + const NMPlatformIP4Address *addr_a = + NMP_OBJECT_CAST_IP4_ADDRESS(c_list_entry(lst_a, NMDedupMultiEntry, lst_entries)->obj); + const NMPlatformIP4Address *addr_b = + NMP_OBJECT_CAST_IP4_ADDRESS(c_list_entry(lst_b, NMDedupMultiEntry, lst_entries)->obj); + + nm_assert(addr_a); + nm_assert(addr_b); + + /* Primary addresses first */ + return NM_FLAGS_HAS(addr_a->n_ifa_flags, IFA_F_SECONDARY) + - NM_FLAGS_HAS(addr_b->n_ifa_flags, IFA_F_SECONDARY); +} + +NMIP4Config * +nm_ip4_config_clone(const NMIP4Config *self) +{ + NMIP4Config *copy; + + copy = nm_ip4_config_new(nm_ip4_config_get_multi_idx(self), -1); + nm_ip4_config_replace(copy, self, NULL); + + return copy; +} + +NMIP4Config * +nm_ip4_config_capture(NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex) +{ + NMIP4Config * self; + NMIP4ConfigPrivate * priv; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * plobj = NULL; + + nm_assert(ifindex > 0); + + /* Slaves have no IP configuration */ + if (nm_platform_link_get_master(platform, ifindex) > 0) + return NULL; + + self = nm_ip4_config_new(multi_idx, ifindex); + priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex); + if (head_entry) { + nmp_cache_iter_for_each (&iter, head_entry, &plobj) { + if (!_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip4_addresses_, + ifindex, + plobj, + NULL, + FALSE, + TRUE, + NULL, + NULL)) + nm_assert_not_reached(); + } + head_entry = nm_ip4_config_lookup_addresses(self); + nm_assert(head_entry); + nm_dedup_multi_head_entry_sort(head_entry, sort_captured_addresses, NULL); + _notify_addresses(self); + } + + head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex); + + /* Extract gateway from default route */ + nmp_cache_iter_for_each (&iter, head_entry, &plobj) + _add_route(self, plobj, NULL, NULL); + + return self; +} + +void +nm_ip4_config_update_routes_metric(NMIP4Config *self, gint64 metric) +{ + gs_free NMPlatformIP4Route *routes = NULL; + gboolean need_update = FALSE; + const NMPlatformIP4Route * r; + NMDedupMultiIter iter; + guint num = 0, i = 0; + + nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) { + if (r->metric != metric) + need_update = TRUE; + num++; + } + if (!need_update) + return; + + routes = g_new(NMPlatformIP4Route, num); + nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) { + routes[i] = *r; + routes[i].metric = metric; + i++; + } + + g_object_freeze_notify(G_OBJECT(self)); + nm_ip4_config_reset_routes(self); + for (i = 0; i < num; i++) + nm_ip4_config_add_route(self, &routes[i], NULL); + g_object_thaw_notify(G_OBJECT(self)); +} + +void +nm_ip4_config_add_dependent_routes(NMIP4Config *self, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf, + GPtrArray ** out_ip4_dev_route_blacklist) +{ + GPtrArray * ip4_dev_route_blacklist = NULL; + const NMPlatformIP4Address *my_addr; + const NMPlatformIP4Route * my_route; + int ifindex; + NMDedupMultiIter iter; + + g_return_if_fail(NM_IS_IP4_CONFIG(self)); + + ifindex = nm_ip4_config_get_ifindex(self); + g_return_if_fail(ifindex > 0); + + /* For IPv6 slaac, we explicitly add the device-routes (onlink) to NMIP6Config. + * As we don't do that for IPv4 (and manual IPv6 addresses), add them explicitly. */ + + nm_ip_config_iter_ip4_address_for_each (&iter, self, &my_addr) { + nm_auto_nmpobj NMPObject *r = NULL; + NMPlatformIP4Route * route; + in_addr_t network; + + if (my_addr->plen == 0) + continue; + + nm_assert(my_addr->plen <= 32); + + /* The destination network depends on the peer-address. */ + network = nm_utils_ip4_address_clear_host_address(my_addr->peer_address, my_addr->plen); + + if (my_addr->external) + continue; + + /* Pre-generate local route added by kernel */ + r = nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP4_ROUTE(r); + route->ifindex = ifindex; + route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL; + route->network = my_addr->address; + route->plen = 32; + route->pref_src = my_addr->address; + route->type_coerced = nm_platform_route_type_coerce(RTN_LOCAL); + route->scope_inv = nm_platform_route_scope_inv(RT_SCOPE_HOST); + route->table_coerced = + nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL); + _add_route(self, r, NULL, NULL); + nm_clear_pointer(&r, nmp_object_unref); + + if (nm_utils_ip4_address_is_zeronet(network)) { + /* Kernel doesn't add device-routes for destinations that + * start with 0.x.y.z. Skip them. */ + continue; + } + + if (my_addr->plen == 32 && my_addr->address == my_addr->peer_address) { + /* Kernel doesn't add device-routes for /32 addresses unless + * they have a peer. */ + continue; + } + + r = nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP4_ROUTE(r); + + route->ifindex = ifindex; + route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL; + route->network = network; + route->plen = my_addr->plen; + route->pref_src = my_addr->address; + route->table_coerced = nm_platform_route_table_coerce(route_table); + route->metric = route_metric; + route->scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK); + + nm_platform_ip_route_normalize(AF_INET, (NMPlatformIPRoute *) route); + + if (_lookup_route(self, r, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) { + /* we already track this route. Don't add it again. */ + } else + _add_route(self, r, NULL, NULL); + + if (out_ip4_dev_route_blacklist + && (route_table != RT_TABLE_MAIN + || route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE)) { + nm_auto_nmpobj NMPObject *r_dev = NULL; + + r_dev = nmp_object_clone(r, FALSE); + route = NMP_OBJECT_CAST_IP4_ROUTE(r_dev); + route->table_coerced = nm_platform_route_table_coerce(RT_TABLE_MAIN); + route->metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE; + + nm_platform_ip_route_normalize(AF_INET, (NMPlatformIPRoute *) route); + + if (_lookup_route(self, r_dev, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) { + /* we track such a route explicitly. Don't blacklist it. */ + } else { + if (!ip4_dev_route_blacklist) + ip4_dev_route_blacklist = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(ip4_dev_route_blacklist, g_steal_pointer(&r_dev)); + } + } + } + +again: + nm_ip_config_iter_ip4_route_for_each (&iter, self, &my_route) { + NMPlatformIP4Route rt; + + if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route) || my_route->gateway == 0 + || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rt_source) + || nm_ip4_config_get_direct_route_for_host( + self, + my_route->gateway, + nm_platform_route_table_uncoerce(my_route->table_coerced, TRUE))) + continue; + + rt = *my_route; + rt.network = my_route->gateway; + rt.plen = 32; + rt.gateway = 0; + _add_route(self, NULL, &rt, NULL); + /* adding the route might have invalidated the iteration. Start again. */ + goto again; + } + + NM_SET_OUT(out_ip4_dev_route_blacklist, ip4_dev_route_blacklist); +} + +gboolean +nm_ip4_config_commit(const NMIP4Config * self, + NMPlatform * platform, + NMIPRouteTableSyncMode route_table_sync) +{ + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + int ifindex; + gboolean success = TRUE; + + g_return_val_if_fail(NM_IS_IP4_CONFIG(self), FALSE); + + ifindex = nm_ip4_config_get_ifindex(self); + g_return_val_if_fail(ifindex > 0, FALSE); + + addresses = + nm_dedup_multi_objs_to_ptr_array_head(nm_ip4_config_lookup_addresses(self), NULL, NULL); + + routes = nm_dedup_multi_objs_to_ptr_array_head(nm_ip4_config_lookup_routes(self), NULL, NULL); + + routes_prune = + nm_platform_ip_route_get_prune_list(platform, AF_INET, ifindex, route_table_sync); + + nm_platform_ip4_address_sync(platform, ifindex, addresses); + + if (!nm_platform_ip_route_sync(platform, AF_INET, ifindex, routes, routes_prune, NULL)) + success = FALSE; + + return success; +} + +void +nm_ip4_config_merge_setting(NMIP4Config * self, + NMSettingIPConfig * setting, + NMSettingConnectionMdns mdns, + NMSettingConnectionLlmnr llmnr, + guint32 route_table, + guint32 route_metric) +{ + guint naddresses, nroutes, nnameservers, nsearches; + int i, priority; + const char *gateway_str; + guint32 gateway_bin; + + if (!setting) + return; + + g_return_if_fail(NM_IS_SETTING_IP4_CONFIG(setting)); + + g_object_freeze_notify(G_OBJECT(self)); + + naddresses = nm_setting_ip_config_get_num_addresses(setting); + nroutes = nm_setting_ip_config_get_num_routes(setting); + nnameservers = nm_setting_ip_config_get_num_dns(setting); + nsearches = nm_setting_ip_config_get_num_dns_searches(setting); + + /* Gateway */ + if (!nm_setting_ip_config_get_never_default(setting) + && (gateway_str = nm_setting_ip_config_get_gateway(setting)) + && inet_pton(AF_INET, gateway_str, &gateway_bin) == 1 && gateway_bin) { + const NMPlatformIP4Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .gateway = gateway_bin, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + + _add_route(self, NULL, &r, NULL); + } + + /* Addresses */ + for (i = 0; i < naddresses; i++) { + NMIPAddress * s_addr = nm_setting_ip_config_get_address(setting, i); + GVariant * label; + NMPlatformIP4Address address; + + memset(&address, 0, sizeof(address)); + nm_ip_address_get_address_binary(s_addr, &address.address); + address.peer_address = address.address; + address.plen = nm_ip_address_get_prefix(s_addr); + nm_assert(address.plen <= 32); + address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + address.preferred = NM_PLATFORM_LIFETIME_PERMANENT; + address.addr_source = NM_IP_CONFIG_SOURCE_USER; + + label = nm_ip_address_get_attribute(s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (label) + g_strlcpy(address.label, g_variant_get_string(label, NULL), sizeof(address.label)); + + _add_address(self, NULL, &address); + } + + /* Routes */ + for (i = 0; i < nroutes; i++) { + NMIPRoute * s_route = nm_setting_ip_config_get_route(setting, i); + NMPlatformIP4Route route; + gint64 m; + + if (nm_ip_route_get_family(s_route) != AF_INET) { + nm_assert_not_reached(); + continue; + } + + memset(&route, 0, sizeof(route)); + nm_ip_route_get_dest_binary(s_route, &route.network); + + route.plen = nm_ip_route_get_prefix(s_route); + nm_assert(route.plen <= 32); + + nm_ip_route_get_next_hop_binary(s_route, &route.gateway); + m = nm_ip_route_get_metric(s_route); + if (m < 0) + route.metric = route_metric; + else + route.metric = m; + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + + route.network = nm_utils_ip4_address_clear_host_address(route.network, route.plen); + + nm_utils_ip_route_attribute_to_platform(AF_INET, + s_route, + NM_PLATFORM_IP_ROUTE_CAST(&route), + route_table); + _add_route(self, NULL, &route, NULL); + } + + /* DNS */ + if (nm_setting_ip_config_get_ignore_auto_dns(setting)) { + nm_ip4_config_reset_nameservers(self); + nm_ip4_config_reset_domains(self); + nm_ip4_config_reset_searches(self); + } + for (i = 0; i < nnameservers; i++) { + guint32 ip; + + if (inet_pton(AF_INET, nm_setting_ip_config_get_dns(setting, i), &ip) == 1) + nm_ip4_config_add_nameserver(self, ip); + } + for (i = 0; i < nsearches; i++) + nm_ip4_config_add_search(self, nm_setting_ip_config_get_dns_search(setting, i)); + + i = 0; + while ((i = nm_setting_ip_config_next_valid_dns_option(setting, i)) >= 0) { + nm_ip4_config_add_dns_option(self, nm_setting_ip_config_get_dns_option(setting, i)); + i++; + } + + priority = nm_setting_ip_config_get_dns_priority(setting); + if (priority) + nm_ip4_config_set_dns_priority(self, priority); + + nm_ip4_config_mdns_set(self, mdns); + nm_ip4_config_llmnr_set(self, llmnr); + + nm_ip4_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting)); + + g_object_thaw_notify(G_OBJECT(self)); +} + +NMSetting * +nm_ip4_config_create_setting(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate * priv; + NMSettingIPConfig * s_ip4; + guint nnameservers, nsearches, noptions; + const char * method = NULL; + int i; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *address; + const NMPlatformIP4Route * route; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + + if (!self) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + return NM_SETTING(s_ip4); + } + + priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + nnameservers = nm_ip4_config_get_num_nameservers(self); + nsearches = nm_ip4_config_get_num_searches(self); + noptions = nm_ip4_config_get_num_dns_options(self); + + /* Addresses */ + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) { + NMIPAddress *s_addr; + + /* Detect dynamic address */ + if (address->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) { + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + continue; + } + + /* Static address found. */ + if (!method) + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + + s_addr = nm_ip_address_new_binary(AF_INET, &address->address, address->plen, NULL); + if (*address->label) + nm_ip_address_set_attribute(s_addr, + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string(address->label)); + + nm_setting_ip_config_add_address(s_ip4, s_addr); + nm_ip_address_unref(s_addr); + } + + /* Gateway */ + if (priv->best_default_route && nm_setting_ip_config_get_num_addresses(s_ip4) > 0) { + g_object_set( + s_ip4, + NM_SETTING_IP_CONFIG_GATEWAY, + _nm_utils_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(priv->best_default_route)->gateway, + sbuf), + NULL); + } + + /* Use 'disabled' if the method wasn't previously set */ + if (!method) + method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, method, NULL); + + /* Routes */ + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) { + NMIPRoute *s_route; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) + continue; + + /* Ignore routes provided by external sources */ + if (route->rt_source + != nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER)) + continue; + + s_route = nm_ip_route_new_binary(AF_INET, + &route->network, + route->plen, + &route->gateway, + route->metric, + NULL); + nm_setting_ip_config_add_route(s_ip4, s_route); + nm_ip_route_unref(s_route); + } + + /* DNS */ + for (i = 0; i < nnameservers; i++) { + guint32 nameserver = nm_ip4_config_get_nameserver(self, i); + + nm_setting_ip_config_add_dns(s_ip4, _nm_utils_inet4_ntop(nameserver, sbuf)); + } + for (i = 0; i < nsearches; i++) { + const char *search = nm_ip4_config_get_search(self, i); + + nm_setting_ip_config_add_dns_search(s_ip4, search); + } + + for (i = 0; i < noptions; i++) { + const char *option = nm_ip4_config_get_dns_option(self, i); + + nm_setting_ip_config_add_dns_option(s_ip4, option); + } + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_DNS_PRIORITY, + nm_ip4_config_get_dns_priority(self), + NULL); + + return NM_SETTING(s_ip4); +} + +/*****************************************************************************/ + +void +nm_ip4_config_merge(NMIP4Config * dst, + const NMIP4Config * src, + NMIPConfigMergeFlags merge_flags, + guint32 default_route_metric_penalty) +{ + NMIP4ConfigPrivate * dst_priv; + const NMIP4ConfigPrivate * src_priv; + guint32 i; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *address = NULL; + + g_return_if_fail(src != NULL); + g_return_if_fail(dst != NULL); + + dst_priv = NM_IP4_CONFIG_GET_PRIVATE(dst); + src_priv = NM_IP4_CONFIG_GET_PRIVATE(src); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &address) { + if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_EXTERNAL) && !address->external) { + NMPlatformIP4Address a; + + a = *address; + a.external = TRUE; + _add_address(dst, NULL, &a); + } else + _add_address(dst, NMP_OBJECT_UP_CAST(address), NULL); + } + + /* nameservers */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_nameservers(src); i++) + nm_ip4_config_add_nameserver(dst, nm_ip4_config_get_nameserver(src, i)); + } + + /* routes */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) { + const NMPlatformIP4Route *r_src; + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r_src) { + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) { + if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS(src_priv->config_flags, + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) + continue; + if (default_route_metric_penalty) { + NMPlatformIP4Route r = *r_src; + + r.metric = + nm_utils_ip_route_metric_penalize(r.metric, default_route_metric_penalty); + _add_route(dst, NULL, &r, NULL); + continue; + } + } + _add_route(dst, ipconf_iter.current->obj, NULL, NULL); + } + } + + /* domains */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_domains(src); i++) + nm_ip4_config_add_domain(dst, nm_ip4_config_get_domain(src, i)); + } + + /* dns searches */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_searches(src); i++) + nm_ip4_config_add_search(dst, nm_ip4_config_get_search(src, i)); + } + + /* dns options */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_dns_options(src); i++) + nm_ip4_config_add_dns_option(dst, nm_ip4_config_get_dns_option(src, i)); + } + + /* MTU */ + if (src_priv->mtu_source > dst_priv->mtu_source + || (src_priv->mtu_source == dst_priv->mtu_source + && ((!dst_priv->mtu && src_priv->mtu) + || (dst_priv->mtu && src_priv->mtu < dst_priv->mtu)))) + nm_ip4_config_set_mtu(dst, src_priv->mtu, src_priv->mtu_source); + + /* NIS */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_nis_servers(src); i++) + nm_ip4_config_add_nis_server(dst, nm_ip4_config_get_nis_server(src, i)); + + if (nm_ip4_config_get_nis_domain(src)) + nm_ip4_config_set_nis_domain(dst, nm_ip4_config_get_nis_domain(src)); + } + + /* WINS */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip4_config_get_num_wins(src); i++) + nm_ip4_config_add_wins(dst, nm_ip4_config_get_wins(src, i)); + } + + /* metered flag */ + nm_ip4_config_set_metered(dst, + nm_ip4_config_get_metered(dst) || nm_ip4_config_get_metered(src)); + + /* never default */ + nm_ip4_config_set_never_default(dst, + nm_ip4_config_get_never_default(dst) + || nm_ip4_config_get_never_default(src)); + + /* DNS priority */ + if (nm_ip4_config_get_dns_priority(src)) + nm_ip4_config_set_dns_priority(dst, nm_ip4_config_get_dns_priority(src)); + + /* mdns */ + nm_ip4_config_mdns_set(dst, NM_MAX(nm_ip4_config_mdns_get(src), nm_ip4_config_mdns_get(dst))); + /* LLMNR */ + nm_ip4_config_llmnr_set(dst, + NM_MAX(nm_ip4_config_llmnr_get(src), nm_ip4_config_llmnr_get(dst))); + + g_object_thaw_notify(G_OBJECT(dst)); +} + +/*****************************************************************************/ + +static int +_nameservers_get_index(const NMIP4Config *self, guint32 ns) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->nameservers->len; i++) { + guint32 n = g_array_index(priv->nameservers, guint32, i); + + if (ns == n) + return (int) i; + } + return -1; +} + +static int +_domains_get_index(const NMIP4Config *self, const char *domain) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->domains->len; i++) { + const char *d = g_ptr_array_index(priv->domains, i); + + if (g_strcmp0(domain, d) == 0) + return (int) i; + } + return -1; +} + +static int +_searches_get_index(const NMIP4Config *self, const char *search) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->searches->len; i++) { + const char *s = g_ptr_array_index(priv->searches, i); + + if (g_strcmp0(search, s) == 0) + return (int) i; + } + return -1; +} + +static int +_dns_options_get_index(const NMIP4Config *self, const char *option) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->dns_options->len; i++) { + const char *s = g_ptr_array_index(priv->dns_options, i); + + if (g_strcmp0(option, s) == 0) + return (int) i; + } + return -1; +} + +static int +_nis_servers_get_index(const NMIP4Config *self, guint32 nis_server) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->nis->len; i++) { + guint32 n = g_array_index(priv->nis, guint32, i); + + if (n == nis_server) + return (int) i; + } + return -1; +} + +static int +_wins_get_index(const NMIP4Config *self, guint32 wins_server) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->wins->len; i++) { + guint32 n = g_array_index(priv->wins, guint32, i); + + if (n == wins_server) + return (int) i; + } + return -1; +} + +/*****************************************************************************/ + +/** + * nm_ip4_config_subtract: + * @dst: config from which to remove everything in @src + * @src: config to remove from @dst + * @default_route_metric_penalty: pretend that on source we applied + * a route penalty on the default-route. It means, for default routes + * we don't remove routes that match exactly, but those with a lower + * metric (with the penalty removed). + * + * Removes everything in @src from @dst. + */ +void +nm_ip4_config_subtract(NMIP4Config * dst, + const NMIP4Config *src, + guint32 default_route_metric_penalty) +{ + NMIP4ConfigPrivate * dst_priv; + guint i; + int idx; + const NMPlatformIP4Address *a; + const NMPlatformIP4Route * r; + NMDedupMultiIter ipconf_iter; + gboolean changed; + gboolean changed_default_route; + + g_return_if_fail(src != NULL); + g_return_if_fail(dst != NULL); + + dst_priv = NM_IP4_CONFIG_GET_PRIVATE(dst); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + changed = FALSE; + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &a) { + if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, + &dst_priv->idx_ip4_addresses, + NMP_OBJECT_UP_CAST(a), + NULL)) + changed = TRUE; + } + if (changed) + _notify_addresses(dst); + + /* nameservers */ + for (i = 0; i < nm_ip4_config_get_num_nameservers(src); i++) { + idx = _nameservers_get_index(dst, nm_ip4_config_get_nameserver(src, i)); + if (idx >= 0) + nm_ip4_config_del_nameserver(dst, idx); + } + + /* routes */ + changed = FALSE; + changed_default_route = FALSE; + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r) { + const NMPObject * o_src = NMP_OBJECT_UP_CAST(r); + NMPObject o_lookup_copy; + const NMPObject * o_lookup; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { + NMPlatformIP4Route *rr; + + /* the default route was penalized when merging it to the combined ip-config. + * When subtracting the routes, we must re-do that process when comparing + * the routes. */ + o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_src); + rr = NMP_OBJECT_CAST_IP4_ROUTE(&o_lookup_copy); + rr->metric = + nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); + } else + o_lookup = o_src; + + if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, + &dst_priv->idx_ip4_routes, + o_lookup, + (gconstpointer *) &obj_old)) { + if (dst_priv->best_default_route == obj_old) { + nm_clear_nmp_object(&dst_priv->best_default_route); + changed_default_route = TRUE; + } + changed = TRUE; + } + } + if (changed_default_route) { + nmp_object_ref_set(&dst_priv->best_default_route, + _nm_ip4_config_best_default_route_find(dst)); + _notify(dst, PROP_GATEWAY); + } + if (changed) + _notify_routes(dst); + + /* domains */ + for (i = 0; i < nm_ip4_config_get_num_domains(src); i++) { + idx = _domains_get_index(dst, nm_ip4_config_get_domain(src, i)); + if (idx >= 0) + nm_ip4_config_del_domain(dst, idx); + } + + /* dns searches */ + for (i = 0; i < nm_ip4_config_get_num_searches(src); i++) { + idx = _searches_get_index(dst, nm_ip4_config_get_search(src, i)); + if (idx >= 0) + nm_ip4_config_del_search(dst, idx); + } + + /* dns options */ + for (i = 0; i < nm_ip4_config_get_num_dns_options(src); i++) { + idx = _dns_options_get_index(dst, nm_ip4_config_get_dns_option(src, i)); + if (idx >= 0) + nm_ip4_config_del_dns_option(dst, idx); + } + + /* MTU */ + if (nm_ip4_config_get_mtu(src) == nm_ip4_config_get_mtu(dst) + && nm_ip4_config_get_mtu_source(src) == nm_ip4_config_get_mtu_source(dst)) + nm_ip4_config_set_mtu(dst, 0, NM_IP_CONFIG_SOURCE_UNKNOWN); + + /* NIS */ + for (i = 0; i < nm_ip4_config_get_num_nis_servers(src); i++) { + idx = _nis_servers_get_index(dst, nm_ip4_config_get_nis_server(src, i)); + if (idx >= 0) + nm_ip4_config_del_nis_server(dst, idx); + } + + if (g_strcmp0(nm_ip4_config_get_nis_domain(src), nm_ip4_config_get_nis_domain(dst)) == 0) + nm_ip4_config_set_nis_domain(dst, NULL); + + /* WINS */ + for (i = 0; i < nm_ip4_config_get_num_wins(src); i++) { + idx = _wins_get_index(dst, nm_ip4_config_get_wins(src, i)); + if (idx >= 0) + nm_ip4_config_del_wins(dst, idx); + } + + /* DNS priority */ + if (nm_ip4_config_get_dns_priority(src) == nm_ip4_config_get_dns_priority(dst)) + nm_ip4_config_set_dns_priority(dst, 0); + + /* mdns */ + if (nm_ip4_config_mdns_get(src) == nm_ip4_config_mdns_get(dst)) + nm_ip4_config_mdns_set(dst, NM_SETTING_CONNECTION_MDNS_DEFAULT); + + /* LLMNR */ + if (nm_ip4_config_llmnr_get(src) == nm_ip4_config_llmnr_get(dst)) + nm_ip4_config_llmnr_set(dst, NM_SETTING_CONNECTION_LLMNR_DEFAULT); + + g_object_thaw_notify(G_OBJECT(dst)); +} + +static gboolean +_nm_ip4_config_intersect_helper(NMIP4Config * dst, + const NMIP4Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty, + gboolean update_dst) +{ + NMIP4ConfigPrivate * dst_priv; + const NMIP4ConfigPrivate * src_priv; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *a; + const NMPlatformIP4Route * r; + const NMPObject * new_best_default_route; + gboolean changed, result = FALSE; + + g_return_val_if_fail(src, FALSE); + g_return_val_if_fail(dst, FALSE); + + dst_priv = NM_IP4_CONFIG_GET_PRIVATE(dst); + src_priv = NM_IP4_CONFIG_GET_PRIVATE(src); + + if (update_dst) + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + if (intersect_addresses) { + changed = FALSE; + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) { + if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, + &src_priv->idx_ip4_addresses, + NMP_OBJECT_UP_CAST(a))) + continue; + + if (!update_dst) + return TRUE; + + if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) + nm_assert_not_reached(); + changed = TRUE; + } + if (changed) { + _notify_addresses(dst); + result = TRUE; + } + } + + /* ignore nameservers */ + + /* routes */ + if (!intersect_routes) + goto skip_routes; + + changed = FALSE; + new_best_default_route = NULL; + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, dst, &r) { + const NMPObject *o_dst = NMP_OBJECT_UP_CAST(r); + const NMPObject *o_lookup; + NMPObject o_lookup_copy; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { + NMPlatformIP4Route *rr; + + /* the default route was penalized when merging it to the combined ip-config. + * When intersecting the routes, we must re-do that process when comparing + * the routes. */ + o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_dst); + rr = NMP_OBJECT_CAST_IP4_ROUTE(&o_lookup_copy); + rr->metric = + nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); + } else + o_lookup = o_dst; + + if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, + &src_priv->idx_ip4_routes, + o_lookup)) { + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, o_dst); + continue; + } + + if (!update_dst) + return TRUE; + + if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) + nm_assert_not_reached(); + changed = TRUE; + } + if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) { + nm_assert(changed); + _notify(dst, PROP_GATEWAY); + } + + if (changed) { + _notify_routes(dst); + result = TRUE; + } + +skip_routes: + /* ignore domains */ + /* ignore dns searches */ + /* ignore dns options */ + /* ignore NIS */ + /* ignore WINS */ + /* ignore mdns */ + /* ignore LLMNR */ + + if (update_dst) + g_object_thaw_notify(G_OBJECT(dst)); + return result; +} + +/** + * nm_ip4_config_intersect: + * @dst: a configuration to be updated + * @src: another configuration + * @intersect_addresses: whether addresses should be intersected + * @intersect_routes: whether routes should be intersected + * @default_route_metric_penalty: the default route metric penalty + * + * Computes the intersection between @src and @dst and updates @dst in place + * with the result. + */ +void +nm_ip4_config_intersect(NMIP4Config * dst, + const NMIP4Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + _nm_ip4_config_intersect_helper(dst, + src, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + TRUE); +} + +/** + * nm_ip4_config_intersect_alloc: + * @a: a configuration + * @b: another configuration + * @intersect_addresses: whether addresses should be intersected + * @intersect_routes: whether routes should be intersected + * @default_route_metric_penalty: the default route metric penalty + * + * Computes the intersection between @a and @b and returns the result in a newly + * allocated configuration. As a special case, if @a and @b are identical (with + * respect to the only properties considered - addresses and routes) the + * functions returns NULL so that one of existing configuration can be reused + * without allocation. + * + * Returns: the intersection between @a and @b, or %NULL if the result is equal + * to @a and @b. + */ +NMIP4Config * +nm_ip4_config_intersect_alloc(const NMIP4Config *a, + const NMIP4Config *b, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + NMIP4Config *a_copy; + + if (_nm_ip4_config_intersect_helper((NMIP4Config *) a, + b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + FALSE)) { + a_copy = nm_ip4_config_clone(a); + _nm_ip4_config_intersect_helper(a_copy, + b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + TRUE); + return a_copy; + } else + return NULL; +} + +/** + * nm_ip4_config_replace: + * @dst: config to replace with @src content + * @src: source config to copy + * @relevant_changes: return whether there are changes to the + * destination object that are relevant. This is equal to + * nm_ip4_config_equal() showing any difference. + * + * Replaces everything in @dst with @src so that the two configurations + * contain the same content -- with the exception of the dbus path. + * + * Returns: whether the @dst instance changed in any way (including minor changes, + * that are not signaled by the output parameter @relevant_changes). + */ +gboolean +nm_ip4_config_replace(NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes) +{ +#if NM_MORE_ASSERTS + gboolean config_equal; +#endif + gboolean has_minor_changes = FALSE, has_relevant_changes = FALSE, are_equal; + guint i, num; + NMIP4ConfigPrivate * dst_priv; + const NMIP4ConfigPrivate * src_priv; + NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst; + const NMDedupMultiHeadEntry *head_entry_src; + const NMPObject * new_best_default_route; + + g_return_val_if_fail(src != NULL, FALSE); + g_return_val_if_fail(dst != NULL, FALSE); + g_return_val_if_fail(src != dst, FALSE); + +#if NM_MORE_ASSERTS + config_equal = nm_ip4_config_equal(dst, src); +#endif + + dst_priv = NM_IP4_CONFIG_GET_PRIVATE(dst); + src_priv = NM_IP4_CONFIG_GET_PRIVATE(src); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* ifindex */ + if (src_priv->ifindex != dst_priv->ifindex) { + dst_priv->ifindex = src_priv->ifindex; + has_minor_changes = TRUE; + } + + /* addresses */ + head_entry_src = nm_ip4_config_lookup_addresses(src); + nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); + nm_ip_config_iter_ip4_address_init(&ipconf_iter_dst, dst); + are_equal = TRUE; + while (TRUE) { + gboolean has; + const NMPlatformIP4Address *r_src = NULL; + const NMPlatformIP4Address *r_dst = NULL; + + has = nm_platform_dedup_multi_iter_next_ip4_address(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip4_address(&ipconf_iter_dst, &r_dst)) { + are_equal = FALSE; + has_relevant_changes = TRUE; + break; + } + if (!has) + break; + + if (nm_platform_ip4_address_cmp(r_src, r_dst) != 0) { + are_equal = FALSE; + if (r_src->address != r_dst->address || r_src->plen != r_dst->plen + || r_src->peer_address != r_dst->peer_address) { + has_relevant_changes = TRUE; + break; + } + } + } + if (!are_equal) { + has_minor_changes = TRUE; + nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip4_addresses); + nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { + _nm_ip_config_add_obj(dst_priv->multi_idx, + &dst_priv->idx_ip4_addresses_, + dst_priv->ifindex, + ipconf_iter_src.current->obj, + NULL, + FALSE, + TRUE, + NULL, + NULL); + } + nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, + &dst_priv->idx_ip4_addresses, + FALSE); + _notify_addresses(dst); + } + + /* routes */ + head_entry_src = nm_ip4_config_lookup_routes(src); + nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); + nm_ip_config_iter_ip4_route_init(&ipconf_iter_dst, dst); + are_equal = TRUE; + while (TRUE) { + gboolean has; + const NMPlatformIP4Route *r_src = NULL; + const NMPlatformIP4Route *r_dst = NULL; + + has = nm_platform_dedup_multi_iter_next_ip4_route(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip4_route(&ipconf_iter_dst, &r_dst)) { + are_equal = FALSE; + has_relevant_changes = TRUE; + break; + } + if (!has) + break; + + if (nm_platform_ip4_route_cmp_full(r_src, r_dst) != 0) { + are_equal = FALSE; + if (r_src->plen != r_dst->plen + || !nm_utils_ip4_address_same_prefix(r_src->network, r_dst->network, r_src->plen) + || r_src->gateway != r_dst->gateway || r_src->metric != r_dst->metric) { + has_relevant_changes = TRUE; + break; + } + } + } + if (!are_equal) { + has_minor_changes = TRUE; + new_best_default_route = NULL; + nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip4_routes); + nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { + const NMPObject *o = ipconf_iter_src.current->obj; + const NMPObject *obj_new; + + _nm_ip_config_add_obj(dst_priv->multi_idx, + &dst_priv->idx_ip4_routes_, + dst_priv->ifindex, + o, + NULL, + FALSE, + TRUE, + NULL, + &obj_new); + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); + } + nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, + &dst_priv->idx_ip4_routes, + FALSE); + if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) + _notify(dst, PROP_GATEWAY); + _notify_routes(dst); + } + + /* nameservers */ + num = nm_ip4_config_get_num_nameservers(src); + are_equal = num == nm_ip4_config_get_num_nameservers(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (nm_ip4_config_get_nameserver(src, i) != nm_ip4_config_get_nameserver(dst, i)) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_nameservers(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_nameserver(dst, nm_ip4_config_get_nameserver(src, i)); + has_relevant_changes = TRUE; + } + + /* domains */ + num = nm_ip4_config_get_num_domains(src); + are_equal = num == nm_ip4_config_get_num_domains(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip4_config_get_domain(src, i), nm_ip4_config_get_domain(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_domains(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_domain(dst, nm_ip4_config_get_domain(src, i)); + has_relevant_changes = TRUE; + } + + /* dns searches */ + num = nm_ip4_config_get_num_searches(src); + are_equal = num == nm_ip4_config_get_num_searches(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip4_config_get_search(src, i), nm_ip4_config_get_search(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_searches(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_search(dst, nm_ip4_config_get_search(src, i)); + has_relevant_changes = TRUE; + } + + /* dns options */ + num = nm_ip4_config_get_num_dns_options(src); + are_equal = num == nm_ip4_config_get_num_dns_options(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip4_config_get_dns_option(src, i), + nm_ip4_config_get_dns_option(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_dns_options(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_dns_option(dst, nm_ip4_config_get_dns_option(src, i)); + has_relevant_changes = TRUE; + } + + if (src_priv->mdns != dst_priv->mdns) { + dst_priv->mdns = src_priv->mdns; + has_relevant_changes = TRUE; + } + + if (src_priv->llmnr != dst_priv->llmnr) { + dst_priv->llmnr = src_priv->llmnr; + has_relevant_changes = TRUE; + } + + /* DNS priority */ + if (src_priv->dns_priority != dst_priv->dns_priority) { + nm_ip4_config_set_dns_priority(dst, src_priv->dns_priority); + has_minor_changes = TRUE; + } + + /* nis */ + num = nm_ip4_config_get_num_nis_servers(src); + are_equal = num == nm_ip4_config_get_num_nis_servers(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (nm_ip4_config_get_nis_server(src, i) != nm_ip4_config_get_nis_server(dst, i)) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_nis_servers(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_nis_server(dst, nm_ip4_config_get_nis_server(src, i)); + has_relevant_changes = TRUE; + } + + /* nis_domain */ + if (g_strcmp0(src_priv->nis_domain, dst_priv->nis_domain)) { + nm_ip4_config_set_nis_domain(dst, src_priv->nis_domain); + has_relevant_changes = TRUE; + } + + /* wins */ + num = nm_ip4_config_get_num_wins(src); + are_equal = num == nm_ip4_config_get_num_wins(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (nm_ip4_config_get_wins(src, i) != nm_ip4_config_get_wins(dst, i)) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip4_config_reset_wins(dst); + for (i = 0; i < num; i++) + nm_ip4_config_add_wins(dst, nm_ip4_config_get_wins(src, i)); + has_relevant_changes = TRUE; + } + + /* mtu */ + if (src_priv->mtu != dst_priv->mtu || src_priv->mtu_source != dst_priv->mtu_source) { + nm_ip4_config_set_mtu(dst, src_priv->mtu, src_priv->mtu_source); + has_minor_changes = TRUE; + } + + /* metered */ + if (src_priv->metered != dst_priv->metered) { + dst_priv->metered = src_priv->metered; + has_minor_changes = TRUE; + } + + /* never default */ + if (src_priv->never_default != dst_priv->never_default) { + dst_priv->never_default = src_priv->never_default; + has_minor_changes = TRUE; + } + +#if NM_MORE_ASSERTS + /* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes + * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */ + nm_assert(config_equal == !has_relevant_changes); +#endif + + g_object_thaw_notify(G_OBJECT(dst)); + + if (relevant_changes) + *relevant_changes = has_relevant_changes; + + return has_relevant_changes || has_minor_changes; +} + +void +nm_ip_config_dump(const NMIPConfig *self, const char *detail, NMLogLevel level, NMLogDomain domain) +{ + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *addr4; + const NMPlatformIP6Address *addr6; + const NMPlatformIP4Route * route4; + const NMPlatformIP6Route * route6; + const NMIP4Config * ip4; + const NMIP6Config * ip6; + int addr_family = AF_UNSPEC; + char addr_family_char = '?'; + const char * path; + gconstpointer ptr; + guint i; + + if (self) { + addr_family = nm_ip_config_get_addr_family(self); + addr_family_char = nm_utils_addr_family_to_char(addr_family); + } + + nm_log(level, domain, NULL, NULL, "---- NMIP%cConfig %p (%s)", addr_family_char, self, detail); + + if (!self) + return; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(self)); + if (path) + nm_log(level, domain, NULL, NULL, " path : %s", path); + + if (addr_family == AF_INET) { + ip4 = NM_IP4_CONFIG(self); + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr4) { + nm_log(level, + domain, + NULL, + NULL, + " address : %s", + nm_platform_ip4_address_to_string(addr4, NULL, 0)); + } + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route4) { + nm_log(level, + domain, + NULL, + NULL, + " route : %s", + nm_platform_ip4_route_to_string(route4, NULL, 0)); + } + } else { + ip6 = NM_IP6_CONFIG(self); + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr6) { + nm_log(level, + domain, + NULL, + NULL, + " address : %s", + nm_platform_ip6_address_to_string(addr6, NULL, 0)); + } + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route6) { + nm_log(level, + domain, + NULL, + NULL, + " route : %s", + nm_platform_ip6_route_to_string(route6, NULL, 0)); + } + } + + for (i = 0; i < nm_ip_config_get_num_nameservers(self); i++) { + char buf[NM_UTILS_INET_ADDRSTRLEN]; + + ptr = nm_ip_config_get_nameserver(self, i); + nm_log(level, + domain, + NULL, + NULL, + " dns : %s", + nm_utils_inet_ntop(addr_family, ptr, buf)); + } + + for (i = 0; i < nm_ip_config_get_num_domains(self); i++) + nm_log(level, domain, NULL, NULL, " domain : %s", nm_ip_config_get_domain(self, i)); + + for (i = 0; i < nm_ip_config_get_num_searches(self); i++) + nm_log(level, domain, NULL, NULL, " search : %s", nm_ip_config_get_search(self, i)); + + for (i = 0; i < nm_ip_config_get_num_dns_options(self); i++) + nm_log(level, domain, NULL, NULL, "dns-option: %s", nm_ip_config_get_dns_option(self, i)); + + nm_log(level, domain, NULL, NULL, " dns-prio : %d", nm_ip_config_get_dns_priority(self)); + + if (addr_family == AF_INET) { + ip4 = NM_IP4_CONFIG(self); + nm_log(level, + domain, + NULL, + NULL, + " mtu : %" G_GUINT32_FORMAT " (source: %d)", + nm_ip4_config_get_mtu(ip4), + (int) nm_ip4_config_get_mtu_source(ip4)); + nm_log(level, domain, NULL, NULL, " metered : %d", (int) nm_ip4_config_get_metered(ip4)); + } +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_addresses(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip4_addresses) > 0) + _notify_addresses(self); +} + +static void +_add_address(NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip4_addresses_, + priv->ifindex, + obj_new, + (const NMPlatformObject *) new, + TRUE, + FALSE, + NULL, + NULL)) + _notify_addresses(self); +} + +/** + * nm_ip4_config_add_address: + * @self: the #NMIP4Config + * @new: the new address to add to @self + * + * Adds the new address to @self. If an address with the same basic properties + * (address, prefix) already exists in @self, it is overwritten with the + * lifetime and preferred of @new. The source is also overwritten by the source + * from @new if that source is higher priority. + */ +void +nm_ip4_config_add_address(NMIP4Config *self, const NMPlatformIP4Address *new) +{ + g_return_if_fail(self); + g_return_if_fail(new); + g_return_if_fail(new->plen <= 32); + g_return_if_fail(NM_IP4_CONFIG_GET_PRIVATE(self)->ifindex > 0); + + _add_address(self, NULL, new); +} + +void +_nmtst_ip4_config_del_address(NMIP4Config *self, guint i) +{ + const NMPlatformIP4Address *a; + + a = _nmtst_ip4_config_get_address(self, i); + if (!nm_ip4_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(a))) + g_assert_not_reached(); +} + +guint +nm_ip4_config_get_num_addresses(const NMIP4Config *self) +{ + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_ip4_config_lookup_addresses(self); + return head_entry ? head_entry->len : 0; +} + +const NMPlatformIP4Address * +nm_ip4_config_get_first_address(const NMIP4Config *self) +{ + NMDedupMultiIter iter; + const NMPlatformIP4Address *a = NULL; + + nm_ip_config_iter_ip4_address_for_each (&iter, self, &a) + return a; + return NULL; +} + +const NMPlatformIP4Address * +_nmtst_ip4_config_get_address(const NMIP4Config *self, guint i) +{ + NMDedupMultiIter iter = {}; + const NMPlatformIP4Address *a = NULL; + guint j; + + j = 0; + nm_ip_config_iter_ip4_address_for_each (&iter, self, &a) { + if (i == j) + return a; + j++; + } + g_return_val_if_reached(NULL); +} + +gboolean +nm_ip4_config_address_exists(const NMIP4Config *self, const NMPlatformIP4Address *needle) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + NMPObject obj_stack; + + nmp_object_stackinit_id_ip4_address(&obj_stack, + priv->ifindex, + needle->address, + needle->plen, + needle->peer_address); + return !!nm_dedup_multi_index_lookup_obj(priv->multi_idx, &priv->idx_ip4_addresses, &obj_stack); +} + +/*****************************************************************************/ + +static const NMDedupMultiEntry * +_lookup_route(const NMIP4Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type) +{ + const NMIP4ConfigPrivate *priv; + + nm_assert(NM_IS_IP4_CONFIG(self)); + nm_assert(NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP4_ROUTE); + + priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return _nm_ip_config_lookup_ip_route(priv->multi_idx, &priv->idx_ip4_routes_, needle, cmp_type); +} + +void +nm_ip4_config_reset_routes(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip4_routes) > 0) { + if (nm_clear_nmp_object(&priv->best_default_route)) + _notify(self, PROP_GATEWAY); + _notify_routes(self); + } +} + +static void +_add_route(NMIP4Config * self, + const NMPObject *obj_new, + const NMPlatformIP4Route *new, + const NMPObject **out_obj_new) +{ + NMIP4ConfigPrivate * priv = NM_IP4_CONFIG_GET_PRIVATE(self); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + const NMPObject * obj_new_2; + + nm_assert((!new) != (!obj_new)); + nm_assert(!new || _route_valid(new)); + nm_assert(!obj_new || _route_valid(NMP_OBJECT_CAST_IP4_ROUTE(obj_new))); + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip4_routes_, + priv->ifindex, + obj_new, + (const NMPlatformObject *) new, + TRUE, + FALSE, + &obj_old, + &obj_new_2)) { + gboolean changed_default_route = FALSE; + + if (priv->best_default_route == obj_old && obj_old != obj_new_2) { + changed_default_route = TRUE; + nm_clear_nmp_object(&priv->best_default_route); + } + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); + if (_nm_ip_config_best_default_route_merge(&priv->best_default_route, obj_new_2)) + changed_default_route = TRUE; + + if (changed_default_route) + _notify(self, PROP_GATEWAY); + _notify_routes(self); + } else + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); +} + +/** + * nm_ip4_config_add_route: + * @self: the #NMIP4Config + * @new: the new route to add to @self + * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed + * by caller. + * + * Adds the new route to @self. If a route with the same basic properties + * (network, prefix) already exists in @self, it is overwritten including the + * gateway and metric of @new. The source is also overwritten by the source + * from @new if that source is higher priority. + */ +void +nm_ip4_config_add_route(NMIP4Config *self, + const NMPlatformIP4Route *new, + const NMPObject **out_obj_new) +{ + g_return_if_fail(self); + g_return_if_fail(new); + g_return_if_fail(new->plen <= 32); + g_return_if_fail(NM_IP4_CONFIG_GET_PRIVATE(self)->ifindex > 0); + + _add_route(self, NULL, new, out_obj_new); +} + +void +_nmtst_ip4_config_del_route(NMIP4Config *self, guint i) +{ + const NMPlatformIP4Route *r; + + r = _nmtst_ip4_config_get_route(self, i); + if (!nm_ip4_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(r))) + g_assert_not_reached(); +} + +guint +nm_ip4_config_get_num_routes(const NMIP4Config *self) +{ + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_ip4_config_lookup_routes(self); + nm_assert(!head_entry || head_entry->len == c_list_length(&head_entry->lst_entries_head)); + return head_entry ? head_entry->len : 0; +} + +const NMPlatformIP4Route * +_nmtst_ip4_config_get_route(const NMIP4Config *self, guint i) +{ + NMDedupMultiIter iter; + const NMPlatformIP4Route *r = NULL; + guint j; + + j = 0; + nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) { + if (i == j) + return r; + j++; + } + g_return_val_if_reached(NULL); +} + +const NMPlatformIP4Route * +nm_ip4_config_get_direct_route_for_host(const NMIP4Config *self, + in_addr_t host, + guint32 route_table) +{ + const NMPlatformIP4Route *best_route = NULL; + const NMPlatformIP4Route *item; + NMDedupMultiIter ipconf_iter; + + g_return_val_if_fail(host, NULL); + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &item) { + if (item->gateway != 0) + continue; + + if (best_route && best_route->plen > item->plen) + continue; + + if (nm_platform_route_table_uncoerce(item->table_coerced, TRUE) != route_table) + continue; + + if (nm_utils_ip4_address_clear_host_address(host, item->plen) + != nm_utils_ip4_address_clear_host_address(item->network, item->plen)) + continue; + + if (best_route && best_route->metric <= item->metric) + continue; + + best_route = item; + } + return best_route; +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_nameservers(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priv->nameservers->len != 0) { + g_array_set_size(priv->nameservers, 0); + nm_gobject_notify_together(self, PROP_NAMESERVER_DATA, PROP_NAMESERVERS); + } +} + +void +nm_ip4_config_add_nameserver(NMIP4Config *self, guint32 new) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + int i; + + g_return_if_fail(new != 0); + + for (i = 0; i < priv->nameservers->len; i++) + if (new == g_array_index(priv->nameservers, guint32, i)) + return; + + g_array_append_val(priv->nameservers, new); + nm_gobject_notify_together(self, PROP_NAMESERVER_DATA, PROP_NAMESERVERS); +} + +void +nm_ip4_config_del_nameserver(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->nameservers->len); + + g_array_remove_index(priv->nameservers, i); + nm_gobject_notify_together(self, PROP_NAMESERVER_DATA, PROP_NAMESERVERS); +} + +guint +nm_ip4_config_get_num_nameservers(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->nameservers->len; +} + +guint32 +nm_ip4_config_get_nameserver(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_array_index(priv->nameservers, guint32, i); +} + +const in_addr_t * +_nm_ip4_config_get_nameserver(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return &g_array_index(priv->nameservers, guint32, i); +} + +/*****************************************************************************/ + +gboolean +_nm_ip_config_check_and_add_domain(GPtrArray *array, const char *domain) +{ + char * copy = NULL; + size_t len; + + g_return_val_if_fail(domain, FALSE); + g_return_val_if_fail(domain[0] != '\0', FALSE); + + if (domain[0] == '.' || strstr(domain, "..")) + return FALSE; + + len = strlen(domain); + if (domain[len - 1] == '.') + domain = copy = g_strndup(domain, len - 1); + + if (nm_utils_strv_find_first((char **) array->pdata, array->len, domain) >= 0) { + g_free(copy); + return FALSE; + } + + g_ptr_array_add(array, copy ?: g_strdup(domain)); + return TRUE; +} + +void +nm_ip4_config_reset_domains(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priv->domains->len != 0) { + g_ptr_array_set_size(priv->domains, 0); + _notify(self, PROP_DOMAINS); + } +} + +void +nm_ip4_config_add_domain(NMIP4Config *self, const char *domain) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_check_and_add_domain(priv->domains, domain)) + _notify(self, PROP_DOMAINS); +} + +void +nm_ip4_config_del_domain(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->domains->len); + + g_ptr_array_remove_index(priv->domains, i); + _notify(self, PROP_DOMAINS); +} + +guint +nm_ip4_config_get_num_domains(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->domains->len; +} + +const char * +nm_ip4_config_get_domain(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->domains, i); +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_searches(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priv->searches->len != 0) { + g_ptr_array_set_size(priv->searches, 0); + _notify(self, PROP_SEARCHES); + } +} + +void +nm_ip4_config_add_search(NMIP4Config *self, const char *search) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_check_and_add_domain(priv->searches, search)) + _notify(self, PROP_SEARCHES); +} + +void +nm_ip4_config_del_search(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->searches->len); + + g_ptr_array_remove_index(priv->searches, i); + _notify(self, PROP_SEARCHES); +} + +guint +nm_ip4_config_get_num_searches(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->searches->len; +} + +const char * +nm_ip4_config_get_search(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->searches, i); +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_dns_options(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priv->dns_options->len != 0) { + g_ptr_array_set_size(priv->dns_options, 0); + _notify(self, PROP_DNS_OPTIONS); + } +} + +void +nm_ip4_config_add_dns_option(NMIP4Config *self, const char *new) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + int i; + + g_return_if_fail(new != NULL); + g_return_if_fail(new[0] != '\0'); + + for (i = 0; i < priv->dns_options->len; i++) + if (!g_strcmp0(g_ptr_array_index(priv->dns_options, i), new)) + return; + + g_ptr_array_add(priv->dns_options, g_strdup(new)); + _notify(self, PROP_DNS_OPTIONS); +} + +void +nm_ip4_config_del_dns_option(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->dns_options->len); + + g_ptr_array_remove_index(priv->dns_options, i); + _notify(self, PROP_DNS_OPTIONS); +} + +guint +nm_ip4_config_get_num_dns_options(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->dns_options->len; +} + +const char * +nm_ip4_config_get_dns_option(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->dns_options, i); +} + +/*****************************************************************************/ + +NMSettingConnectionMdns +nm_ip4_config_mdns_get(const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE(self)->mdns; +} + +void +nm_ip4_config_mdns_set(NMIP4Config *self, NMSettingConnectionMdns mdns) +{ + NM_IP4_CONFIG_GET_PRIVATE(self)->mdns = mdns; +} + +NMSettingConnectionLlmnr +nm_ip4_config_llmnr_get(const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE(self)->llmnr; +} + +void +nm_ip4_config_llmnr_set(NMIP4Config *self, NMSettingConnectionLlmnr llmnr) +{ + NM_IP4_CONFIG_GET_PRIVATE(self)->llmnr = llmnr; +} + +/*****************************************************************************/ + +NMIPConfigFlags +nm_ip4_config_get_config_flags(const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE(self)->config_flags; +} + +void +nm_ip4_config_set_config_flags(NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (mask == 0) { + /* for convenience, accept 0 mask to set any flags. */ + mask = flags; + } + + nm_assert(!NM_FLAGS_ANY(flags, ~mask)); + priv->config_flags = (flags & mask) | (priv->config_flags & ~mask); +} + +/*****************************************************************************/ + +void +nm_ip4_config_set_dns_priority(NMIP4Config *self, int priority) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priority != priv->dns_priority) { + priv->dns_priority = priority; + _notify(self, PROP_DNS_PRIORITY); + } +} + +int +nm_ip4_config_get_dns_priority(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->dns_priority; +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_nis_servers(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_array_set_size(priv->nis, 0); +} + +void +nm_ip4_config_add_nis_server(NMIP4Config *self, guint32 nis) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + int i; + + for (i = 0; i < priv->nis->len; i++) + if (nis == g_array_index(priv->nis, guint32, i)) + return; + + g_array_append_val(priv->nis, nis); +} + +void +nm_ip4_config_del_nis_server(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->nis->len); + + g_array_remove_index(priv->nis, i); +} + +guint +nm_ip4_config_get_num_nis_servers(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->nis->len; +} + +guint32 +nm_ip4_config_get_nis_server(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_array_index(priv->nis, guint32, i); +} + +void +nm_ip4_config_set_nis_domain(NMIP4Config *self, const char *domain) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_free(priv->nis_domain); + priv->nis_domain = g_strdup(domain); +} + +const char * +nm_ip4_config_get_nis_domain(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->nis_domain; +} + +/*****************************************************************************/ + +void +nm_ip4_config_reset_wins(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (priv->wins->len != 0) { + g_array_set_size(priv->wins, 0); + nm_gobject_notify_together(self, PROP_WINS_SERVER_DATA, PROP_WINS_SERVERS); + } +} + +void +nm_ip4_config_add_wins(NMIP4Config *self, guint32 wins) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + int i; + + g_return_if_fail(wins != 0); + + for (i = 0; i < priv->wins->len; i++) + if (wins == g_array_index(priv->wins, guint32, i)) + return; + + g_array_append_val(priv->wins, wins); + nm_gobject_notify_together(self, PROP_WINS_SERVER_DATA, PROP_WINS_SERVERS); +} + +void +nm_ip4_config_del_wins(NMIP4Config *self, guint i) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->wins->len); + + g_array_remove_index(priv->wins, i); + nm_gobject_notify_together(self, PROP_WINS_SERVER_DATA, PROP_WINS_SERVERS); +} + +guint +nm_ip4_config_get_num_wins(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->wins->len; +} + +guint32 +nm_ip4_config_get_wins(const NMIP4Config *self, guint i) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return g_array_index(priv->wins, guint32, i); +} + +/*****************************************************************************/ + +void +nm_ip4_config_set_mtu(NMIP4Config *self, guint32 mtu, NMIPConfigSource source) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + if (!mtu) + source = NM_IP_CONFIG_SOURCE_UNKNOWN; + + priv->mtu = mtu; + priv->mtu_source = source; +} + +guint32 +nm_ip4_config_get_mtu(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->mtu; +} + +NMIPConfigSource +nm_ip4_config_get_mtu_source(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->mtu_source; +} + +/*****************************************************************************/ + +void +nm_ip4_config_set_metered(NMIP4Config *self, gboolean metered) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + priv->metered = metered; +} + +gboolean +nm_ip4_config_get_metered(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->metered; +} + +/*****************************************************************************/ + +void +nm_ip4_config_set_never_default(NMIP4Config *self, gboolean never_default) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + priv->never_default = never_default; +} + +gboolean +nm_ip4_config_get_never_default(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->never_default; +} + +/*****************************************************************************/ + +const NMPObject * +nm_ip4_config_nmpobj_lookup(const NMIP4Config *self, const NMPObject *needle) +{ + const NMIP4ConfigPrivate * priv; + const NMDedupMultiIdxType *idx_type; + + g_return_val_if_fail(NM_IS_IP4_CONFIG(self), NULL); + + priv = NM_IP4_CONFIG_GET_PRIVATE(self); + switch (NMP_OBJECT_GET_TYPE(needle)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + idx_type = &priv->idx_ip4_addresses; + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + idx_type = &priv->idx_ip4_routes; + break; + default: + g_return_val_if_reached(NULL); + } + + return nm_dedup_multi_entry_get_obj( + nm_dedup_multi_index_lookup_obj(priv->multi_idx, idx_type, needle)); +} + +gboolean +nm_ip4_config_nmpobj_remove(NMIP4Config *self, const NMPObject *needle) +{ + NMIP4ConfigPrivate * priv; + NMDedupMultiIdxType *idx_type; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + guint n; + + g_return_val_if_fail(NM_IS_IP4_CONFIG(self), FALSE); + + priv = NM_IP4_CONFIG_GET_PRIVATE(self); + switch (NMP_OBJECT_GET_TYPE(needle)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + idx_type = &priv->idx_ip4_addresses; + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + idx_type = &priv->idx_ip4_routes; + break; + default: + g_return_val_if_reached(FALSE); + } + + n = nm_dedup_multi_index_remove_obj(priv->multi_idx, + idx_type, + needle, + (gconstpointer *) &obj_old); + if (n != 1) { + nm_assert(n == 0); + return FALSE; + } + + nm_assert(NMP_OBJECT_GET_TYPE(obj_old) == NMP_OBJECT_GET_TYPE(needle)); + + switch (NMP_OBJECT_GET_TYPE(obj_old)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + _notify_addresses(self); + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + if (priv->best_default_route == obj_old) { + if (nmp_object_ref_set(&priv->best_default_route, + _nm_ip4_config_best_default_route_find(self))) + _notify(self, PROP_GATEWAY); + } + _notify_routes(self); + break; + default: + nm_assert_not_reached(); + } + return TRUE; +} + +/*****************************************************************************/ + +static void +hash_u32(GChecksum *sum, guint32 n) +{ + g_checksum_update(sum, (const guint8 *) &n, sizeof(n)); +} + +void +nm_ip4_config_hash(const NMIP4Config *self, GChecksum *sum, gboolean dns_only) +{ + guint i; + const char * s; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Address *address; + const NMPlatformIP4Route * route; + int val; + + g_return_if_fail(self); + g_return_if_fail(sum); + + if (!dns_only) { + nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) { + hash_u32(sum, address->address); + hash_u32(sum, address->plen); + hash_u32(sum, address->peer_address & _nm_utils_ip4_prefix_to_netmask(address->plen)); + } + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) { + hash_u32(sum, route->network); + hash_u32(sum, route->plen); + hash_u32(sum, route->gateway); + hash_u32(sum, route->metric); + } + + for (i = 0; i < nm_ip4_config_get_num_nis_servers(self); i++) + hash_u32(sum, nm_ip4_config_get_nis_server(self, i)); + + s = nm_ip4_config_get_nis_domain(self); + if (s) + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + for (i = 0; i < nm_ip4_config_get_num_nameservers(self); i++) + hash_u32(sum, nm_ip4_config_get_nameserver(self, i)); + + for (i = 0; i < nm_ip4_config_get_num_wins(self); i++) + hash_u32(sum, nm_ip4_config_get_wins(self, i)); + + for (i = 0; i < nm_ip4_config_get_num_domains(self); i++) { + s = nm_ip4_config_get_domain(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + for (i = 0; i < nm_ip4_config_get_num_searches(self); i++) { + s = nm_ip4_config_get_search(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + for (i = 0; i < nm_ip4_config_get_num_dns_options(self); i++) { + s = nm_ip4_config_get_dns_option(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + val = nm_ip4_config_mdns_get(self); + if (val != NM_SETTING_CONNECTION_MDNS_DEFAULT) + g_checksum_update(sum, (const guint8 *) &val, sizeof(val)); + + val = nm_ip4_config_llmnr_get(self); + if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT) + g_checksum_update(sum, (const guint8 *) &val, sizeof(val)); + + /* FIXME(ip-config-checksum): the DNS priority should be considered relevant + * and added into the checksum as well, but this can't be done right now + * because in the DNS manager we rely on the fact that an empty + * configuration (i.e. just created) has a zero checksum. This is needed to + * avoid rewriting resolv.conf when there is no change. + * + * The DNS priority initial value depends on the connection type (VPN or + * not), so it's a bit difficult to add it to checksum maintaining the + * assumption of checksum(empty)=0 + */ +} + +/** + * nm_ip4_config_equal: + * @a: first config to compare + * @b: second config to compare + * + * Compares two #NMIP4Configs for basic equality. This means that all + * attributes must exist in the same order in both configs (addresses, routes, + * domains, DNS servers, etc) but some attributes (address lifetimes, and address + * and route sources) are ignored. + * + * Returns: %TRUE if the configurations are basically equal to each other, + * %FALSE if not + */ +gboolean +nm_ip4_config_equal(const NMIP4Config *a, const NMIP4Config *b) +{ + nm_auto_free_checksum GChecksum *a_checksum = g_checksum_new(G_CHECKSUM_SHA1); + nm_auto_free_checksum GChecksum *b_checksum = g_checksum_new(G_CHECKSUM_SHA1); + guint8 a_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + guint8 b_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + + if (a) + nm_ip4_config_hash(a, a_checksum, FALSE); + if (b) + nm_ip4_config_hash(b, b_checksum, FALSE); + + nm_utils_checksum_get_digest(a_checksum, a_data); + nm_utils_checksum_get_digest(b_checksum, b_data); + return !memcmp(a_data, b_data, sizeof(a_data)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMIP4Config * self = NM_IP4_CONFIG(object); + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + GVariantBuilder builder_data; + guint i; + + switch (prop_id) { + case PROP_IFINDEX: + g_value_set_int(value, priv->ifindex); + break; + case PROP_ADDRESS_DATA: + case PROP_ADDRESSES: + nm_assert(!!priv->address_data_variant == !!priv->addresses_variant); + + if (!priv->address_data_variant) { + nm_utils_ip_addresses_to_dbus(AF_INET, + nm_ip4_config_lookup_addresses(self), + priv->best_default_route, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + &priv->address_data_variant, + &priv->addresses_variant); + g_variant_ref_sink(priv->address_data_variant); + g_variant_ref_sink(priv->addresses_variant); + } + + g_value_set_variant(value, + prop_id == PROP_ADDRESS_DATA ? priv->address_data_variant + : priv->addresses_variant); + break; + case PROP_ROUTE_DATA: + case PROP_ROUTES: + nm_assert(!!priv->route_data_variant == !!priv->routes_variant); + + if (!priv->route_data_variant) { + nm_utils_ip_routes_to_dbus(AF_INET, + nm_ip4_config_lookup_routes(self), + &priv->route_data_variant, + &priv->routes_variant); + g_variant_ref_sink(priv->route_data_variant); + g_variant_ref_sink(priv->routes_variant); + } + + g_value_set_variant(value, + prop_id == PROP_ROUTE_DATA ? priv->route_data_variant + : priv->routes_variant); + break; + case PROP_GATEWAY: + if (priv->best_default_route) { + g_value_take_string(value, + nm_utils_inet4_ntop_dup( + NMP_OBJECT_CAST_IP4_ROUTE(priv->best_default_route)->gateway)); + } else + g_value_set_string(value, NULL); + break; + case PROP_NAMESERVER_DATA: + g_variant_builder_init(&builder_data, G_VARIANT_TYPE("aa{sv}")); + + for (i = 0; i < priv->nameservers->len; i++) { + GVariantBuilder nested_builder; + + _nm_utils_inet4_ntop(g_array_index(priv->nameservers, in_addr_t, i), addr_str); + + g_variant_builder_init(&nested_builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&nested_builder, + "{sv}", + "address", + g_variant_new_string(addr_str)); + g_variant_builder_add(&builder_data, "a{sv}", &nested_builder); + } + + g_value_take_variant(value, g_variant_builder_end(&builder_data)); + break; + case PROP_NAMESERVERS: + g_value_take_variant(value, + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + priv->nameservers->data, + priv->nameservers->len, + sizeof(guint32))); + break; + case PROP_DOMAINS: + nm_utils_g_value_set_strv(value, priv->domains); + break; + case PROP_SEARCHES: + nm_utils_g_value_set_strv(value, priv->searches); + break; + case PROP_DNS_OPTIONS: + nm_utils_g_value_set_strv(value, priv->dns_options); + break; + case PROP_DNS_PRIORITY: + g_value_set_int(value, priv->dns_priority); + break; + case PROP_WINS_SERVER_DATA: + g_variant_builder_init(&builder_data, G_VARIANT_TYPE("as")); + for (i = 0; i < priv->wins->len; i++) { + g_variant_builder_add( + &builder_data, + "s", + _nm_utils_inet4_ntop(g_array_index(priv->wins, in_addr_t, i), addr_str)); + } + g_value_take_variant(value, g_variant_builder_end(&builder_data)); + break; + case PROP_WINS_SERVERS: + g_value_take_variant(value, + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + priv->wins->data, + priv->wins->len, + sizeof(guint32))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMIP4Config * self = NM_IP4_CONFIG(object); + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MULTI_IDX: + /* construct-only */ + priv->multi_idx = g_value_get_pointer(value); + if (!priv->multi_idx) + g_return_if_reached(); + nm_dedup_multi_index_ref(priv->multi_idx); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_ip4_config_init(NMIP4Config *self) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_addresses, + NMP_OBJECT_TYPE_IP4_ADDRESS); + nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes, + NMP_OBJECT_TYPE_IP4_ROUTE); + + priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; + priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT; + priv->nameservers = g_array_new(FALSE, FALSE, sizeof(guint32)); + priv->domains = g_ptr_array_new_with_free_func(g_free); + priv->searches = g_ptr_array_new_with_free_func(g_free); + priv->dns_options = g_ptr_array_new_with_free_func(g_free); + priv->nis = g_array_new(FALSE, TRUE, sizeof(guint32)); + priv->wins = g_array_new(FALSE, TRUE, sizeof(guint32)); +} + +NMIP4Config * +nm_ip4_config_new(NMDedupMultiIndex *multi_idx, int ifindex) +{ + g_return_val_if_fail(ifindex >= -1, NULL); + return g_object_new(NM_TYPE_IP4_CONFIG, + NM_IP4_CONFIG_MULTI_IDX, + multi_idx, + NM_IP4_CONFIG_IFINDEX, + ifindex, + NULL); +} + +static void +finalize(GObject *object) +{ + NMIP4Config * self = NM_IP4_CONFIG(object); + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + nm_clear_nmp_object(&priv->best_default_route); + + nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip4_addresses); + nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip4_routes); + + nm_clear_g_variant(&priv->address_data_variant); + nm_clear_g_variant(&priv->addresses_variant); + nm_clear_g_variant(&priv->route_data_variant); + nm_clear_g_variant(&priv->routes_variant); + + g_array_unref(priv->nameservers); + g_ptr_array_unref(priv->domains); + g_ptr_array_unref(priv->searches); + g_ptr_array_unref(priv->dns_options); + g_array_unref(priv->nis); + g_free(priv->nis_domain); + g_array_unref(priv->wins); + + G_OBJECT_CLASS(nm_ip4_config_parent_class)->finalize(object); + + nm_dedup_multi_index_unref(priv->multi_idx); +} + +static const NMDBusInterfaceInfoExtended interface_info_ip4_config = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_IP4_CONFIG, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Addresses", + "aau", + NM_IP4_CONFIG_ADDRESSES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AddressData", + "aa{sv}", + NM_IP4_CONFIG_ADDRESS_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Gateway", "s", NM_IP4_CONFIG_GATEWAY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Routes", "aau", NM_IP4_CONFIG_ROUTES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RouteData", + "aa{sv}", + NM_IP4_CONFIG_ROUTE_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NameserverData", + "aa{sv}", + NM_IP4_CONFIG_NAMESERVER_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Nameservers", + "au", + NM_IP4_CONFIG_NAMESERVERS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Domains", + "as", + NM_IP4_CONFIG_DOMAINS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Searches", + "as", + NM_IP4_CONFIG_SEARCHES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsOptions", + "as", + NM_IP4_CONFIG_DNS_OPTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsPriority", + "i", + NM_IP4_CONFIG_DNS_PRIORITY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("WinsServerData", + "as", + NM_IP4_CONFIG_WINS_SERVER_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WinsServers", + "au", + NM_IP4_CONFIG_WINS_SERVERS), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_ip4_config_class_init(NMIP4ConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMIPConfigClass * ip_config_class = NM_IP_CONFIG_CLASS(klass); + + ip_config_class->is_ipv4 = TRUE; + ip_config_class->addr_family = AF_INET; + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/IP4Config"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_ip4_config); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + obj_properties[PROP_MULTI_IDX] = + g_param_spec_pointer(NM_IP4_CONFIG_MULTI_IDX, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_IP4_CONFIG_IFINDEX, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ADDRESS_DATA] = + g_param_spec_variant(NM_IP4_CONFIG_ADDRESS_DATA, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ADDRESSES] = + g_param_spec_variant(NM_IP4_CONFIG_ADDRESSES, + "", + "", + G_VARIANT_TYPE("aau"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTE_DATA] = + g_param_spec_variant(NM_IP4_CONFIG_ROUTE_DATA, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTES] = g_param_spec_variant(NM_IP4_CONFIG_ROUTES, + "", + "", + G_VARIANT_TYPE("aau"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_GATEWAY] = g_param_spec_string(NM_IP4_CONFIG_GATEWAY, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NAMESERVER_DATA] = + g_param_spec_variant(NM_IP4_CONFIG_NAMESERVER_DATA, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NAMESERVERS] = + g_param_spec_variant(NM_IP4_CONFIG_NAMESERVERS, + "", + "", + G_VARIANT_TYPE("au"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DOMAINS] = g_param_spec_boxed(NM_IP4_CONFIG_DOMAINS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SEARCHES] = g_param_spec_boxed(NM_IP4_CONFIG_SEARCHES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DNS_OPTIONS] = + g_param_spec_boxed(NM_IP4_CONFIG_DNS_OPTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DNS_PRIORITY] = g_param_spec_int(NM_IP4_CONFIG_DNS_PRIORITY, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_WINS_SERVER_DATA] = + g_param_spec_variant(NM_IP4_CONFIG_WINS_SERVER_DATA, + "", + "", + G_VARIANT_TYPE("as"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_WINS_SERVERS] = + g_param_spec_variant(NM_IP4_CONFIG_WINS_SERVERS, + "", + "", + G_VARIANT_TYPE("au"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-ip4-config.h b/src/core/nm-ip4-config.h new file mode 100644 index 0000000..a795e15 --- /dev/null +++ b/src/core/nm-ip4-config.h @@ -0,0 +1,667 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_IP4_CONFIG_H__ +#define __NETWORKMANAGER_IP4_CONFIG_H__ + +#include "nm-setting-connection.h" + +#include "nm-setting-ip4-config.h" + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "platform/nmp-object.h" +#include "nm-ip-config.h" + +/*****************************************************************************/ + +typedef enum _NMIPConfigFlags { + NM_IP_CONFIG_FLAG_NONE = 0, + + /* if set, then the merge flag NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES gets + * ignored during merge. */ + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES = (1ull << 0), +} NMIPConfigFlags; + +typedef struct { + NMDedupMultiIdxType parent; + NMPObjectType obj_type; +} NMIPConfigDedupMultiIdxType; + +void nm_ip_config_dedup_multi_idx_type_init(NMIPConfigDedupMultiIdxType *idx_type, + NMPObjectType obj_type); + +/*****************************************************************************/ + +void nm_ip_config_iter_ip4_address_init(NMDedupMultiIter *iter, const NMIP4Config *self); +void nm_ip_config_iter_ip4_route_init(NMDedupMultiIter *iter, const NMIP4Config *self); + +#define nm_ip_config_iter_ip4_address_for_each(iter, self, address) \ + for (nm_ip_config_iter_ip4_address_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip4_address((iter), (address));) + +#define nm_ip_config_iter_ip4_route_for_each(iter, self, route) \ + for (nm_ip_config_iter_ip4_route_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip4_route((iter), (route));) + +/*****************************************************************************/ + +const NMPObject *_nm_ip_config_best_default_route_find_better(const NMPObject *obj_cur, + const NMPObject *obj_cmp); +gboolean _nm_ip_config_best_default_route_merge(const NMPObject **best_default_route, + const NMPObject * new_candidate); + +/*****************************************************************************/ + +gboolean _nm_ip_config_add_obj(NMDedupMultiIndex * multi_idx, + NMIPConfigDedupMultiIdxType *idx_type, + int ifindex, + const NMPObject * obj_new, + const NMPlatformObject * pl_new, + gboolean merge, + gboolean append_force, + const NMPObject ** out_obj_old, + const NMPObject ** out_obj_new); + +const NMDedupMultiEntry *_nm_ip_config_lookup_ip_route(const NMDedupMultiIndex * multi_idx, + const NMIPConfigDedupMultiIdxType *idx_type, + const NMPObject * needle, + NMPlatformIPRouteCmpType cmp_type); + +/*****************************************************************************/ + +#define NM_TYPE_IP4_CONFIG (nm_ip4_config_get_type()) +#define NM_IP4_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP4_CONFIG, NMIP4Config)) +#define NM_IP4_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_IP4_CONFIG, NMIP4ConfigClass)) +#define NM_IS_IP4_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_IP4_CONFIG)) +#define NM_IS_IP4_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_IP4_CONFIG)) +#define NM_IP4_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IP4_CONFIG, NMIP4ConfigClass)) + +typedef struct _NMIP4ConfigClass NMIP4ConfigClass; + +/* internal */ +#define NM_IP4_CONFIG_MULTI_IDX "multi-idx" +#define NM_IP4_CONFIG_IFINDEX "ifindex" + +/* public*/ +#define NM_IP4_CONFIG_ADDRESS_DATA "address-data" +#define NM_IP4_CONFIG_ROUTE_DATA "route-data" +#define NM_IP4_CONFIG_GATEWAY "gateway" +#define NM_IP4_CONFIG_NAMESERVER_DATA "nameserver-data" +#define NM_IP4_CONFIG_DOMAINS "domains" +#define NM_IP4_CONFIG_SEARCHES "searches" +#define NM_IP4_CONFIG_DNS_OPTIONS "dns-options" +#define NM_IP4_CONFIG_DNS_PRIORITY "dns-priority" +#define NM_IP4_CONFIG_WINS_SERVER_DATA "wins-server-data" + +/* deprecated */ +#define NM_IP4_CONFIG_ADDRESSES "addresses" +#define NM_IP4_CONFIG_ROUTES "routes" +#define NM_IP4_CONFIG_NAMESERVERS "nameservers" +#define NM_IP4_CONFIG_WINS_SERVERS "wins-servers" + +GType nm_ip4_config_get_type(void); + +NMIP4Config *nm_ip4_config_new(NMDedupMultiIndex *multi_idx, int ifindex); + +NMIP4Config *nm_ip4_config_clone(const NMIP4Config *self); +int nm_ip4_config_get_ifindex(const NMIP4Config *self); + +NMDedupMultiIndex *nm_ip4_config_get_multi_idx(const NMIP4Config *self); + +NMIP4Config *nm_ip4_config_capture(NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex); + +void nm_ip4_config_add_dependent_routes(NMIP4Config *self, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf, + GPtrArray ** out_ip4_dev_route_blacklist); + +gboolean nm_ip4_config_commit(const NMIP4Config * self, + NMPlatform * platform, + NMIPRouteTableSyncMode route_table_sync); + +void nm_ip4_config_merge_setting(NMIP4Config * self, + NMSettingIPConfig * setting, + NMSettingConnectionMdns mdns, + NMSettingConnectionLlmnr llmnr, + guint32 route_table, + guint32 route_metric); +NMSetting *nm_ip4_config_create_setting(const NMIP4Config *self); + +void nm_ip4_config_merge(NMIP4Config * dst, + const NMIP4Config * src, + NMIPConfigMergeFlags merge_flags, + guint32 default_route_metric_penalty); +void nm_ip4_config_subtract(NMIP4Config * dst, + const NMIP4Config *src, + guint32 default_route_metric_penalty); +void nm_ip4_config_intersect(NMIP4Config * dst, + const NMIP4Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty); +NMIP4Config *nm_ip4_config_intersect_alloc(const NMIP4Config *a, + const NMIP4Config *b, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty); +gboolean +nm_ip4_config_replace(NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes); + +const NMPObject *nm_ip4_config_best_default_route_get(const NMIP4Config *self); +const NMPObject *_nm_ip4_config_best_default_route_find(const NMIP4Config *self); + +in_addr_t nmtst_ip4_config_get_gateway(NMIP4Config *config); + +NMSettingConnectionMdns nm_ip4_config_mdns_get(const NMIP4Config *self); +void nm_ip4_config_mdns_set(NMIP4Config *self, NMSettingConnectionMdns mdns); +NMSettingConnectionLlmnr nm_ip4_config_llmnr_get(const NMIP4Config *self); +void nm_ip4_config_llmnr_set(NMIP4Config *self, NMSettingConnectionLlmnr llmnr); + +void nm_ip4_config_set_config_flags(NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask); +NMIPConfigFlags nm_ip4_config_get_config_flags(const NMIP4Config *self); + +const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses(const NMIP4Config *self); +void nm_ip4_config_reset_addresses(NMIP4Config *self); +void nm_ip4_config_add_address(NMIP4Config *self, const NMPlatformIP4Address *address); +void _nmtst_ip4_config_del_address(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_addresses(const NMIP4Config *self); +const NMPlatformIP4Address *nm_ip4_config_get_first_address(const NMIP4Config *self); +const NMPlatformIP4Address *_nmtst_ip4_config_get_address(const NMIP4Config *self, guint i); +gboolean nm_ip4_config_address_exists(const NMIP4Config *self, const NMPlatformIP4Address *address); + +const NMDedupMultiHeadEntry *nm_ip4_config_lookup_routes(const NMIP4Config *self); +void nm_ip4_config_reset_routes(NMIP4Config *self); +void nm_ip4_config_add_route(NMIP4Config * self, + const NMPlatformIP4Route *route, + const NMPObject ** out_obj_new); +void _nmtst_ip4_config_del_route(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_routes(const NMIP4Config *self); +const NMPlatformIP4Route * _nmtst_ip4_config_get_route(const NMIP4Config *self, guint i); + +const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host(const NMIP4Config *self, + in_addr_t host, + guint32 route_table); +void nm_ip4_config_update_routes_metric(NMIP4Config *self, gint64 metric); + +void nm_ip4_config_reset_nameservers(NMIP4Config *self); +void nm_ip4_config_add_nameserver(NMIP4Config *self, guint32 nameserver); + +static inline void +_nm_ip4_config_add_nameserver(NMIP4Config *self, const guint32 *nameserver) +{ + nm_ip4_config_add_nameserver(self, *nameserver); +} + +void nm_ip4_config_del_nameserver(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_nameservers(const NMIP4Config *self); +guint32 nm_ip4_config_get_nameserver(const NMIP4Config *self, guint i); +const in_addr_t *_nm_ip4_config_get_nameserver(const NMIP4Config *self, guint i); + +void nm_ip4_config_reset_domains(NMIP4Config *self); +void nm_ip4_config_add_domain(NMIP4Config *self, const char *domain); +void nm_ip4_config_del_domain(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_domains(const NMIP4Config *self); +const char *nm_ip4_config_get_domain(const NMIP4Config *self, guint i); + +void nm_ip4_config_reset_searches(NMIP4Config *self); +void nm_ip4_config_add_search(NMIP4Config *self, const char *search); +void nm_ip4_config_del_search(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_searches(const NMIP4Config *self); +const char *nm_ip4_config_get_search(const NMIP4Config *self, guint i); + +void nm_ip4_config_reset_dns_options(NMIP4Config *self); +void nm_ip4_config_add_dns_option(NMIP4Config *self, const char *option); +void nm_ip4_config_del_dns_option(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_dns_options(const NMIP4Config *self); +const char *nm_ip4_config_get_dns_option(const NMIP4Config *self, guint i); + +void nm_ip4_config_set_dns_priority(NMIP4Config *self, int priority); +int nm_ip4_config_get_dns_priority(const NMIP4Config *self); + +void nm_ip4_config_reset_nis_servers(NMIP4Config *self); +void nm_ip4_config_add_nis_server(NMIP4Config *self, guint32 nis); +void nm_ip4_config_del_nis_server(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_nis_servers(const NMIP4Config *self); +guint32 nm_ip4_config_get_nis_server(const NMIP4Config *self, guint i); +void nm_ip4_config_set_nis_domain(NMIP4Config *self, const char *domain); +const char *nm_ip4_config_get_nis_domain(const NMIP4Config *self); + +void nm_ip4_config_reset_wins(NMIP4Config *self); +void nm_ip4_config_add_wins(NMIP4Config *self, guint32 wins); +void nm_ip4_config_del_wins(NMIP4Config *self, guint i); +guint nm_ip4_config_get_num_wins(const NMIP4Config *self); +guint32 nm_ip4_config_get_wins(const NMIP4Config *self, guint i); + +void nm_ip4_config_set_mtu(NMIP4Config *self, guint32 mtu, NMIPConfigSource source); +guint32 nm_ip4_config_get_mtu(const NMIP4Config *self); +NMIPConfigSource nm_ip4_config_get_mtu_source(const NMIP4Config *self); + +void nm_ip4_config_set_metered(NMIP4Config *self, gboolean metered); +gboolean nm_ip4_config_get_metered(const NMIP4Config *self); + +void nm_ip4_config_set_never_default(NMIP4Config *self, gboolean never_default); +gboolean nm_ip4_config_get_never_default(const NMIP4Config *self); + +const NMPObject *nm_ip4_config_nmpobj_lookup(const NMIP4Config *self, const NMPObject *needle); +gboolean nm_ip4_config_nmpobj_remove(NMIP4Config *self, const NMPObject *needle); + +void nm_ip4_config_hash(const NMIP4Config *self, GChecksum *sum, gboolean dns_only); +gboolean nm_ip4_config_equal(const NMIP4Config *a, const NMIP4Config *b); + +gboolean _nm_ip_config_check_and_add_domain(GPtrArray *array, const char *domain); + +void +nm_ip_config_dump(const NMIPConfig *self, const char *detail, NMLogLevel level, NMLogDomain domain); + +/*****************************************************************************/ + +#include "nm-ip6-config.h" + +static inline gboolean +NM_IS_IP_CONFIG_ADDR_FAMILY(gconstpointer config, int addr_family) +{ + if (addr_family == AF_UNSPEC) + return NM_IS_IP4_CONFIG(config) || NM_IS_IP6_CONFIG(config); + if (addr_family == AF_INET) + return NM_IS_IP4_CONFIG(config); + if (addr_family == AF_INET6) + return NM_IS_IP6_CONFIG(config); + g_return_val_if_reached(FALSE); +} + +#if _NM_CC_SUPPORT_GENERIC + /* _NM_IS_IP_CONFIG() is a bit unusual. If _Generic() is supported, + * it checks whether @config is either NM_IS_IP4_CONFIG() or NM_IS_IP6_CONFIG(), + * depending on the pointer type of @config. + * + * For example, with _Generic() support, the following assertions would fail: + * NMIP6Config *ptr = (NMIP6Config *) nm_ip4_config_new(...); + * g_assert (_NM_IS_IP_CONFIG (ptr, ptr)); + * but the following would pass: + * NMIP4Config *ptr = nm_ip4_config_new(...); + * g_assert (_NM_IS_IP_CONFIG (ptr, ptr)); + */ + #define _NM_IS_IP_CONFIG(typeexpr, config) \ + ({ \ + const void *const _config = (config); \ + _Generic ((typeexpr), \ + const void *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + const void * : (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + void *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + void * : (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + const NMIPConfig *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + const NMIPConfig * : (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + NMIPConfig *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + NMIPConfig * : (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \ + const NMIP4Config *const: (NM_IS_IP4_CONFIG (_config)), \ + const NMIP4Config * : (NM_IS_IP4_CONFIG (_config)), \ + NMIP4Config *const: (NM_IS_IP4_CONFIG (_config)), \ + NMIP4Config * : (NM_IS_IP4_CONFIG (_config)), \ + const NMIP6Config *const: (NM_IS_IP6_CONFIG (_config)), \ + const NMIP6Config * : (NM_IS_IP6_CONFIG (_config)), \ + NMIP6Config *const: (NM_IS_IP6_CONFIG (_config)), \ + NMIP6Config * : (NM_IS_IP6_CONFIG (_config))); \ + }) +#else + #define _NM_IS_IP_CONFIG(typeexpr, config) NM_IS_IP_CONFIG(config) +#endif + +#define NM_IP_CONFIG_CAST(config) \ + ({ \ + const void *const _configx = (config); \ + \ + nm_assert(!_configx || _NM_IS_IP_CONFIG((config), _configx)); \ + NM_CONSTCAST_FULL(NMIPConfig, (config), _configx, NMIP4Config, NMIP6Config); \ + }) + +static inline gboolean +nm_ip_config_is_ipv4(const NMIPConfig *config) +{ + if (NM_IP_CONFIG_GET_CLASS(config)->is_ipv4) { + nm_assert(NM_IS_IP4_CONFIG(config)); + return TRUE; + } + nm_assert(NM_IS_IP6_CONFIG(config)); + return FALSE; +} + +static inline int +nm_ip_config_get_addr_family(const NMIPConfig *config) +{ + return nm_ip_config_is_ipv4(config) ? AF_INET : AF_INET6; +} + +#define _NM_IP_CONFIG_DISPATCH(config, v4_func, v6_func, ...) \ + G_STMT_START \ + { \ + gconstpointer _config = (config); \ + \ + if (nm_ip_config_is_ipv4(_config)) { \ + return v4_func((NMIP4Config *) _config, ##__VA_ARGS__); \ + } else { \ + return v6_func((NMIP6Config *) _config, ##__VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _NM_IP_CONFIG_DISPATCH_VOID(config, v4_func, v6_func, ...) \ + G_STMT_START \ + { \ + gconstpointer _config = (config); \ + \ + if (nm_ip_config_is_ipv4(_config)) { \ + v4_func((NMIP4Config *) _config, ##__VA_ARGS__); \ + } else { \ + v6_func((NMIP6Config *) _config, ##__VA_ARGS__); \ + } \ + } \ + G_STMT_END + +static inline int +nm_ip_config_get_ifindex(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_ifindex, nm_ip6_config_get_ifindex); +} + +static inline void +nm_ip_config_hash(const NMIPConfig *self, GChecksum *sum, gboolean dns_only) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_hash, nm_ip6_config_hash, sum, dns_only); +} + +static inline gconstpointer +nm_ip_config_get_first_address(NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_first_address, nm_ip6_config_get_first_address); +} + +static inline void +nm_ip_config_iter_ip_address_init(NMDedupMultiIter *iter, const NMIPConfig *self) +{ + if (nm_ip_config_is_ipv4(self)) + nm_ip_config_iter_ip4_address_init(iter, (const NMIP4Config *) self); + else + nm_ip_config_iter_ip6_address_init(iter, (const NMIP6Config *) self); +} + +#define nm_ip_config_iter_ip_address_for_each(iter, self, address) \ + for (nm_ip_config_iter_ip_address_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip_address((iter), (address));) + +static inline void +nm_ip_config_iter_ip_route_init(NMDedupMultiIter *iter, const NMIPConfig *self) +{ + if (nm_ip_config_is_ipv4(self)) + nm_ip_config_iter_ip4_route_init(iter, (const NMIP4Config *) self); + else + nm_ip_config_iter_ip6_route_init(iter, (const NMIP6Config *) self); +} + +#define nm_ip_config_iter_ip_route_for_each(iter, self, route) \ + for (nm_ip_config_iter_ip_route_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip_route((iter), (route));) + +static inline void +nm_ip_config_add_address(NMIPConfig *self, const NMPlatformIPAddress *address) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_add_address, + nm_ip6_config_add_address, + (gconstpointer) address); +} + +static inline void +nm_ip_config_reset_addresses(NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_reset_addresses, nm_ip6_config_reset_addresses); +} + +static inline void +nm_ip_config_add_route(NMIPConfig *self, + const NMPlatformIPRoute *new, + const NMPObject **out_obj_new) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_add_route, + nm_ip6_config_add_route, + (gpointer) new, + out_obj_new); +} + +static inline void +nm_ip_config_reset_routes(NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_reset_routes, nm_ip6_config_reset_routes); +} + +static inline int +nm_ip_config_get_dns_priority(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_dns_priority, nm_ip6_config_get_dns_priority); +} + +static inline void +nm_ip_config_set_dns_priority(NMIPConfig *self, int priority) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_set_dns_priority, + nm_ip6_config_set_dns_priority, + priority); +} + +static inline void +nm_ip_config_add_nameserver(NMIPConfig *self, const NMIPAddr *ns) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + _nm_ip4_config_add_nameserver, + nm_ip6_config_add_nameserver, + (gconstpointer) ns); +} + +static inline void +nm_ip_config_reset_nameservers(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_reset_nameservers, + nm_ip6_config_reset_nameservers); +} + +static inline guint +nm_ip_config_get_num_nameservers(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, + nm_ip4_config_get_num_nameservers, + nm_ip6_config_get_num_nameservers); +} + +static inline gconstpointer +nm_ip_config_get_nameserver(const NMIPConfig *self, guint i) +{ + _NM_IP_CONFIG_DISPATCH(self, _nm_ip4_config_get_nameserver, nm_ip6_config_get_nameserver, i); +} + +static inline guint +nm_ip_config_get_num_domains(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_num_domains, nm_ip6_config_get_num_domains); +} + +static inline const char * +nm_ip_config_get_domain(const NMIPConfig *self, guint i) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_domain, nm_ip6_config_get_domain, i); +} + +static inline void +nm_ip_config_reset_searches(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_reset_searches, nm_ip6_config_reset_searches); +} + +static inline void +nm_ip_config_add_search(const NMIPConfig *self, const char *new) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_add_search, nm_ip6_config_add_search, new); +} + +static inline guint +nm_ip_config_get_num_searches(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_num_searches, nm_ip6_config_get_num_searches); +} + +static inline const char * +nm_ip_config_get_search(const NMIPConfig *self, guint i) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_search, nm_ip6_config_get_search, i); +} + +static inline guint +nm_ip_config_get_num_dns_options(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, + nm_ip4_config_get_num_dns_options, + nm_ip6_config_get_num_dns_options); +} + +static inline const char * +nm_ip_config_get_dns_option(const NMIPConfig *self, guint i) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_dns_option, nm_ip6_config_get_dns_option, i); +} + +static inline const NMPObject * +nm_ip_config_best_default_route_get(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, + nm_ip4_config_best_default_route_get, + nm_ip6_config_best_default_route_get); +} + +static inline NMIPConfigFlags +nm_ip_config_get_config_flags(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_config_flags, nm_ip6_config_get_config_flags); +} + +static inline void +nm_ip_config_set_config_flags(NMIPConfig *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_set_config_flags, + nm_ip6_config_set_config_flags, + flags, + mask); +} + +static inline gboolean +nm_ip_config_get_never_default(const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_never_default, nm_ip6_config_get_never_default); +} + +static inline void +nm_ip_config_set_never_default(NMIPConfig *self, gboolean never_default) +{ + _NM_IP_CONFIG_DISPATCH_VOID(self, + nm_ip4_config_set_never_default, + nm_ip6_config_set_never_default, + never_default); +} + +#define _NM_IP_CONFIG_DISPATCH_SET_OP(_return, dst, src, v4_func, v6_func, ...) \ + G_STMT_START \ + { \ + gpointer _dst = (dst); \ + gconstpointer _src = (src); \ + \ + if (nm_ip_config_is_ipv4(_dst)) { \ + _return v4_func((NMIP4Config *) _dst, (const NMIP4Config *) _src, ##__VA_ARGS__); \ + } else { \ + _return v6_func((NMIP6Config *) _dst, (const NMIP6Config *) _src, ##__VA_ARGS__); \ + } \ + } \ + G_STMT_END + +static inline void +nm_ip_config_intersect(NMIPConfig * dst, + const NMIPConfig *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + _NM_IP_CONFIG_DISPATCH_SET_OP(, + dst, + src, + nm_ip4_config_intersect, + nm_ip6_config_intersect, + intersect_addresses, + intersect_routes, + default_route_metric_penalty); +} + +static inline void +nm_ip_config_subtract(NMIPConfig *dst, const NMIPConfig *src, guint32 default_route_metric_penalty) +{ + _NM_IP_CONFIG_DISPATCH_SET_OP(, + dst, + src, + nm_ip4_config_subtract, + nm_ip6_config_subtract, + default_route_metric_penalty); +} + +static inline void +nm_ip_config_merge(NMIPConfig * dst, + const NMIPConfig * src, + NMIPConfigMergeFlags merge_flags, + guint32 default_route_metric_penalty) +{ + _NM_IP_CONFIG_DISPATCH_SET_OP(, + dst, + src, + nm_ip4_config_merge, + nm_ip6_config_merge, + merge_flags, + default_route_metric_penalty); +} + +static inline gboolean +nm_ip_config_replace(NMIPConfig *dst, const NMIPConfig *src, gboolean *relevant_changes) +{ + _NM_IP_CONFIG_DISPATCH_SET_OP( + return, dst, src, nm_ip4_config_replace, nm_ip6_config_replace, relevant_changes); +} + +static inline NMIPConfig * +nm_ip_config_intersect_alloc(const NMIPConfig *a, + const NMIPConfig *b, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + if (nm_ip_config_is_ipv4(a)) { + nm_assert(NM_IS_IP4_CONFIG(a)); + nm_assert(NM_IS_IP4_CONFIG(b)); + return (NMIPConfig *) nm_ip4_config_intersect_alloc((const NMIP4Config *) a, + (const NMIP4Config *) b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty); + } else { + nm_assert(NM_IS_IP6_CONFIG(a)); + nm_assert(NM_IS_IP6_CONFIG(b)); + return (NMIPConfig *) nm_ip6_config_intersect_alloc((const NMIP6Config *) a, + (const NMIP6Config *) b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty); + } +} + +#endif /* __NETWORKMANAGER_IP4_CONFIG_H__ */ diff --git a/src/core/nm-ip6-config.c b/src/core/nm-ip6-config.c new file mode 100644 index 0000000..d48c6e3 --- /dev/null +++ b/src/core/nm-ip6-config.c @@ -0,0 +1,2750 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-ip6-config.h" + +#include +#include +#include +#include + +#include "nm-glib-aux/nm-dedup-multi.h" + +#include "nm-utils.h" +#include "platform/nmp-object.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" +#include "nm-ip4-config.h" +#include "ndisc/nm-ndisc.h" +#include "nm-dbus-object.h" + +/*****************************************************************************/ + +static gboolean +_route_valid(const NMPlatformIP6Route *r) +{ + struct in6_addr n; + + return r && r->plen <= 128 + && (memcmp(&r->network, + nm_utils_ip6_address_clear_host_address(&n, &r->network, r->plen), + sizeof(n)) + == 0); +} + +/*****************************************************************************/ + +typedef struct { + int ifindex; + int dns_priority; + NMSettingIP6ConfigPrivacy privacy; + GArray * nameservers; + GPtrArray * domains; + GPtrArray * searches; + GPtrArray * dns_options; + GVariant * address_data_variant; + GVariant * addresses_variant; + GVariant * route_data_variant; + GVariant * routes_variant; + NMDedupMultiIndex * multi_idx; + const NMPObject * best_default_route; + union { + NMIPConfigDedupMultiIdxType idx_ip6_addresses_; + NMDedupMultiIdxType idx_ip6_addresses; + }; + union { + NMIPConfigDedupMultiIdxType idx_ip6_routes_; + NMDedupMultiIdxType idx_ip6_routes; + }; + NMIPConfigFlags config_flags; + bool never_default : 1; +} NMIP6ConfigPrivate; + +struct _NMIP6Config { + NMIPConfig parent; + NMIP6ConfigPrivate _priv; +}; + +struct _NMIP6ConfigClass { + NMIPConfigClass parent; +}; + +G_DEFINE_TYPE(NMIP6Config, nm_ip6_config, NM_TYPE_IP_CONFIG) + +#define NM_IP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP6Config, NM_IS_IP6_CONFIG) + +NM_GOBJECT_PROPERTIES_DEFINE(NMIP6Config, + PROP_MULTI_IDX, + PROP_IFINDEX, + PROP_ADDRESS_DATA, + PROP_ADDRESSES, + PROP_ROUTE_DATA, + PROP_ROUTES, + PROP_GATEWAY, + PROP_NAMESERVERS, + PROP_DOMAINS, + PROP_SEARCHES, + PROP_DNS_OPTIONS, + PROP_DNS_PRIORITY, ); + +/*****************************************************************************/ + +static void +_add_address(NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Address *new); +static void _add_route(NMIP6Config * self, + const NMPObject *obj_new, + const NMPlatformIP6Route *new, + const NMPObject **out_obj_new); +static const NMDedupMultiEntry * +_lookup_route(const NMIP6Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type); + +/*****************************************************************************/ + +int +nm_ip6_config_get_ifindex(const NMIP6Config *self) +{ + return NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex; +} + +NMDedupMultiIndex * +nm_ip6_config_get_multi_idx(const NMIP6Config *self) +{ + return NM_IP6_CONFIG_GET_PRIVATE(self)->multi_idx; +} + +void +nm_ip6_config_set_privacy(NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + priv->privacy = privacy; +} + +/*****************************************************************************/ + +void +nm_ip6_config_set_never_default(NMIP6Config *self, gboolean never_default) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + priv->never_default = never_default; +} + +gboolean +nm_ip6_config_get_never_default(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->never_default; +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_ip6_config_lookup_addresses(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip6_addresses, NULL); +} + +void +nm_ip_config_iter_ip6_address_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self) +{ + nm_assert(NM_IS_IP6_CONFIG(self)); + + nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_addresses(self)); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_ip6_config_lookup_routes(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip6_routes, NULL); +} + +void +nm_ip_config_iter_ip6_route_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self) +{ + nm_assert(NM_IS_IP6_CONFIG(self)); + + nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_routes(self)); +} + +/*****************************************************************************/ + +const NMPObject * +nm_ip6_config_best_default_route_get(const NMIP6Config *self) +{ + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); + + return NM_IP6_CONFIG_GET_PRIVATE(self)->best_default_route; +} + +const NMPObject * +_nm_ip6_config_best_default_route_find(const NMIP6Config *self) +{ + NMDedupMultiIter ipconf_iter; + const NMPObject *new_best_default_route = NULL; + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, NULL) { + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, + ipconf_iter.current->obj); + } + return new_best_default_route; +} + +/*****************************************************************************/ + +static void +_notify_addresses(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + nm_clear_g_variant(&priv->address_data_variant); + nm_clear_g_variant(&priv->addresses_variant); + nm_gobject_notify_together(self, PROP_ADDRESS_DATA, PROP_ADDRESSES); +} + +static void +_notify_routes(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + nm_assert(priv->best_default_route == _nm_ip6_config_best_default_route_find(self)); + nm_clear_g_variant(&priv->route_data_variant); + nm_clear_g_variant(&priv->routes_variant); + nm_gobject_notify_together(self, PROP_ROUTE_DATA, PROP_ROUTES); +} + +/*****************************************************************************/ + +static int +sort_captured_addresses(const CList *lst_a, const CList *lst_b, gconstpointer user_data) +{ + return nm_platform_ip6_address_pretty_sort_cmp( + NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_a, NMDedupMultiEntry, lst_entries)->obj), + NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_b, NMDedupMultiEntry, lst_entries)->obj), + (((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT(user_data)) + == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); +} + +gboolean +_nmtst_ip6_config_addresses_sort(NMIP6Config *self) +{ + NMIP6ConfigPrivate * priv; + const NMDedupMultiHeadEntry *head_entry; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); + + head_entry = nm_ip6_config_lookup_addresses(self); + if (head_entry && head_entry->len > 1) { + gboolean changed; + gs_free gconstpointer *addresses_old = NULL; + guint naddr, j; + NMDedupMultiIter iter; + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + addresses_old = nm_dedup_multi_objs_to_array_head(head_entry, NULL, NULL, &naddr); + nm_assert(addresses_old); + nm_assert(naddr > 0 && naddr == head_entry->len); + + nm_dedup_multi_head_entry_sort(head_entry, + sort_captured_addresses, + GINT_TO_POINTER(priv->privacy)); + + changed = FALSE; + j = 0; + nm_dedup_multi_iter_for_each (&iter, head_entry) { + nm_assert(j < naddr); + if (iter.current->obj != addresses_old[j++]) + changed = TRUE; + } + nm_assert(j == naddr); + + if (changed) { + _notify_addresses(self); + return TRUE; + } + } + return FALSE; +} + +NMIP6Config * +nm_ip6_config_clone(const NMIP6Config *self) +{ + NMIP6Config *copy; + + copy = nm_ip6_config_new(nm_ip6_config_get_multi_idx(self), -1); + nm_ip6_config_replace(copy, self, NULL); + + return copy; +} + +NMIP6Config * +nm_ip6_config_capture(NMDedupMultiIndex * multi_idx, + NMPlatform * platform, + int ifindex, + NMSettingIP6ConfigPrivacy use_temporary) +{ + NMIP6Config * self; + NMIP6ConfigPrivate * priv; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * plobj = NULL; + + nm_assert(ifindex > 0); + + /* Slaves have no IP configuration */ + if (nm_platform_link_get_master(platform, ifindex) > 0) + return NULL; + + self = nm_ip6_config_new(multi_idx, ifindex); + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); + if (head_entry) { + nmp_cache_iter_for_each (&iter, head_entry, &plobj) { + if (!_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_addresses_, + ifindex, + plobj, + NULL, + FALSE, + TRUE, + NULL, + NULL)) + nm_assert_not_reached(); + } + head_entry = nm_ip6_config_lookup_addresses(self); + nm_assert(head_entry); + nm_dedup_multi_head_entry_sort(head_entry, + sort_captured_addresses, + GINT_TO_POINTER(use_temporary)); + _notify_addresses(self); + } + + head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex); + + nmp_cache_iter_for_each (&iter, head_entry, &plobj) + _add_route(self, plobj, NULL, NULL); + + return self; +} + +void +nm_ip6_config_update_routes_metric(NMIP6Config *self, gint64 metric) +{ + gs_free NMPlatformIP6Route *routes = NULL; + gboolean need_update = FALSE; + const NMPlatformIP6Route * r; + NMDedupMultiIter iter; + guint num = 0, i = 0; + + nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { + if (r->metric != metric) + need_update = TRUE; + num++; + } + if (!need_update) + return; + + routes = g_new(NMPlatformIP6Route, num); + nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { + routes[i] = *r; + routes[i].metric = metric; + i++; + } + + g_object_freeze_notify(G_OBJECT(self)); + nm_ip6_config_reset_routes(self); + for (i = 0; i < num; i++) + nm_ip6_config_add_route(self, &routes[i], NULL); + g_object_thaw_notify(G_OBJECT(self)); +} + +void +nm_ip6_config_add_dependent_routes(NMIP6Config *self, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf) +{ + const NMPlatformIP6Address *my_addr; + const NMPlatformIP6Route * my_route; + int ifindex; + NMDedupMultiIter iter; + + g_return_if_fail(NM_IS_IP6_CONFIG(self)); + + ifindex = nm_ip6_config_get_ifindex(self); + g_return_if_fail(ifindex > 0); + + /* For IPv6 addresses received via SLAAC/autoconf, we explicitly add the + * device-routes (onlink) to NMIP6Config. + * + * For manually added IPv6 routes, add the device routes explicitly. */ + + /* Pre-generate multicast route */ + { + nm_auto_nmpobj NMPObject *r = NULL; + NMPlatformIP6Route * route; + + r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP6_ROUTE(r); + route->ifindex = ifindex; + route->network.s6_addr[0] = 0xffu; + route->plen = 8; + route->table_coerced = nm_platform_route_table_coerce(RT_TABLE_LOCAL); + route->type_coerced = nm_platform_route_type_coerce(RTN_UNICAST); + route->metric = 256; + + _add_route(self, r, NULL, NULL); + } + + nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) { + NMPlatformIP6Route *route; + gboolean has_peer; + int routes_n, routes_i; + + if (my_addr->external) + continue; + + { + nm_auto_nmpobj NMPObject *r = NULL; + + /* Pre-generate local route added by kernel */ + r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP6_ROUTE(r); + route->ifindex = ifindex; + route->network = my_addr->address; + route->plen = 128; + route->type_coerced = nm_platform_route_type_coerce(RTN_LOCAL); + route->metric = 0; + route->table_coerced = + nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL); + _add_route(self, r, NULL, NULL); + } + + if (NM_FLAGS_HAS(my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE)) + continue; + if (my_addr->plen == 0) + continue; + + has_peer = !IN6_IS_ADDR_UNSPECIFIED(&my_addr->peer_address); + + /* If we have an IPv6 peer, we add two /128 routes + * (unless, both addresses are identical). */ + routes_n = + (has_peer && !IN6_ARE_ADDR_EQUAL(&my_addr->address, &my_addr->peer_address)) ? 2 : 1; + + for (routes_i = 0; routes_i < routes_n; routes_i++) { + nm_auto_nmpobj NMPObject *r = NULL; + + r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP6_ROUTE(r); + + route->ifindex = ifindex; + route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL; + route->table_coerced = nm_platform_route_table_coerce(route_table); + route->metric = route_metric; + + if (has_peer) { + if (routes_i == 0) + route->network = my_addr->address; + else + route->network = my_addr->peer_address; + route->plen = 128; + } else { + nm_utils_ip6_address_clear_host_address(&route->network, + &my_addr->address, + my_addr->plen); + route->plen = my_addr->plen; + } + + nm_platform_ip_route_normalize(AF_INET6, (NMPlatformIPRoute *) route); + + if (_lookup_route(self, r, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) { + /* we already track this route. Don't add it again. */ + } else + _add_route(self, r, NULL, NULL); + } + } + +again: + nm_ip_config_iter_ip6_route_for_each (&iter, self, &my_route) { + NMPlatformIP6Route rt; + + if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route) + || IN6_IS_ADDR_UNSPECIFIED(&my_route->gateway) + || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rt_source) + || nm_ip6_config_get_direct_route_for_host( + self, + &my_route->gateway, + nm_platform_route_table_uncoerce(my_route->table_coerced, TRUE))) + continue; + + rt = *my_route; + rt.network = my_route->gateway; + rt.plen = 128; + rt.gateway = in6addr_any; + _add_route(self, NULL, &rt, NULL); + /* adding the route might have invalidated the iteration. Start again. */ + goto again; + } +} + +gboolean +nm_ip6_config_commit(const NMIP6Config * self, + NMPlatform * platform, + NMIPRouteTableSyncMode route_table_sync, + GPtrArray ** out_temporary_not_available) +{ + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + int ifindex; + gboolean success = TRUE; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); + + ifindex = nm_ip6_config_get_ifindex(self); + g_return_val_if_fail(ifindex > 0, FALSE); + + addresses = + nm_dedup_multi_objs_to_ptr_array_head(nm_ip6_config_lookup_addresses(self), NULL, NULL); + + routes = nm_dedup_multi_objs_to_ptr_array_head(nm_ip6_config_lookup_routes(self), NULL, NULL); + + routes_prune = + nm_platform_ip_route_get_prune_list(platform, AF_INET6, ifindex, route_table_sync); + + nm_platform_ip6_address_sync(platform, ifindex, addresses, FALSE); + + if (!nm_platform_ip_route_sync(platform, + AF_INET6, + ifindex, + routes, + routes_prune, + out_temporary_not_available)) + success = FALSE; + + return success; +} + +void +nm_ip6_config_merge_setting(NMIP6Config * self, + NMSettingIPConfig *setting, + guint32 route_table, + guint32 route_metric) +{ + guint naddresses, nroutes, nnameservers, nsearches; + const char * gateway_str; + struct in6_addr gateway_bin; + int i, priority; + + if (!setting) + return; + + g_return_if_fail(NM_IS_SETTING_IP6_CONFIG(setting)); + + naddresses = nm_setting_ip_config_get_num_addresses(setting); + nroutes = nm_setting_ip_config_get_num_routes(setting); + nnameservers = nm_setting_ip_config_get_num_dns(setting); + nsearches = nm_setting_ip_config_get_num_dns_searches(setting); + + g_object_freeze_notify(G_OBJECT(self)); + + /* Gateway */ + if (!nm_setting_ip_config_get_never_default(setting) + && (gateway_str = nm_setting_ip_config_get_gateway(setting)) + && inet_pton(AF_INET6, gateway_str, &gateway_bin) == 1 + && !IN6_IS_ADDR_UNSPECIFIED(&gateway_bin)) { + const NMPlatformIP6Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .gateway = gateway_bin, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + + _add_route(self, NULL, &r, NULL); + } + + /* Addresses */ + for (i = 0; i < naddresses; i++) { + NMIPAddress * s_addr = nm_setting_ip_config_get_address(setting, i); + NMPlatformIP6Address address; + + memset(&address, 0, sizeof(address)); + nm_ip_address_get_address_binary(s_addr, &address.address); + address.plen = nm_ip_address_get_prefix(s_addr); + nm_assert(address.plen <= 128); + address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + address.preferred = NM_PLATFORM_LIFETIME_PERMANENT; + address.addr_source = NM_IP_CONFIG_SOURCE_USER; + + _add_address(self, NULL, &address); + } + + /* Routes */ + for (i = 0; i < nroutes; i++) { + NMIPRoute * s_route = nm_setting_ip_config_get_route(setting, i); + NMPlatformIP6Route route; + gint64 m; + + if (nm_ip_route_get_family(s_route) != AF_INET6) { + nm_assert_not_reached(); + continue; + } + + memset(&route, 0, sizeof(route)); + nm_ip_route_get_dest_binary(s_route, &route.network); + + route.plen = nm_ip_route_get_prefix(s_route); + nm_assert(route.plen <= 128); + + nm_ip_route_get_next_hop_binary(s_route, &route.gateway); + m = nm_ip_route_get_metric(s_route); + if (m < 0) + route.metric = route_metric; + else + route.metric = nm_utils_ip6_route_metric_normalize(m); + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + + nm_utils_ip6_address_clear_host_address(&route.network, &route.network, route.plen); + + nm_utils_ip_route_attribute_to_platform(AF_INET6, + s_route, + NM_PLATFORM_IP_ROUTE_CAST(&route), + route_table); + _add_route(self, NULL, &route, NULL); + } + + /* DNS */ + if (nm_setting_ip_config_get_ignore_auto_dns(setting)) { + nm_ip6_config_reset_nameservers(self); + nm_ip6_config_reset_domains(self); + nm_ip6_config_reset_searches(self); + } + for (i = 0; i < nnameservers; i++) { + struct in6_addr ip; + + if (inet_pton(AF_INET6, nm_setting_ip_config_get_dns(setting, i), &ip) == 1) + nm_ip6_config_add_nameserver(self, &ip); + } + for (i = 0; i < nsearches; i++) + nm_ip6_config_add_search(self, nm_setting_ip_config_get_dns_search(setting, i)); + + i = 0; + while ((i = nm_setting_ip_config_next_valid_dns_option(setting, i)) >= 0) { + nm_ip6_config_add_dns_option(self, nm_setting_ip_config_get_dns_option(setting, i)); + i++; + } + + priority = nm_setting_ip_config_get_dns_priority(setting); + if (priority) + nm_ip6_config_set_dns_priority(self, priority); + + nm_ip6_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting)); + + g_object_thaw_notify(G_OBJECT(self)); +} + +NMSetting * +nm_ip6_config_create_setting(const NMIP6Config *self, gboolean maybe_ipv6_disabled) +{ + const NMIP6ConfigPrivate * priv; + NMSettingIPConfig * s_ip6; + guint nnameservers, nsearches, noptions; + const char * method = NULL; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + int i; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *address; + const NMPlatformIP6Route * route; + + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + + if (!self) { + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + return NM_SETTING(s_ip6); + } + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + nnameservers = nm_ip6_config_get_num_nameservers(self); + nsearches = nm_ip6_config_get_num_searches(self); + noptions = nm_ip6_config_get_num_dns_options(self); + + /* Addresses */ + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) { + NMIPAddress *s_addr; + + /* Ignore link-local address. */ + if (IN6_IS_ADDR_LINKLOCAL(&address->address)) { + if (!method) + method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; + continue; + } + + /* Detect dynamic address */ + if (address->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) { + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + continue; + } + + /* Static address found. */ + if (!method || strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) + method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + + s_addr = nm_ip_address_new_binary(AF_INET6, &address->address, address->plen, NULL); + nm_setting_ip_config_add_address(s_ip6, s_addr); + nm_ip_address_unref(s_addr); + } + + /* Gateway */ + if (priv->best_default_route && nm_setting_ip_config_get_num_addresses(s_ip6) > 0) { + g_object_set( + s_ip6, + NM_SETTING_IP_CONFIG_GATEWAY, + _nm_utils_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(priv->best_default_route)->gateway, + sbuf), + NULL); + } + + /* Use 'ignore' if the method wasn't previously set */ + if (!method) { + method = maybe_ipv6_disabled ? NM_SETTING_IP6_CONFIG_METHOD_DISABLED + : NM_SETTING_IP6_CONFIG_METHOD_IGNORE; + } + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, method, NULL); + + /* Routes */ + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) { + NMIPRoute *s_route; + + /* Ignore link-local route. */ + if (IN6_IS_ADDR_LINKLOCAL(&route->network)) + continue; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) + continue; + + /* Ignore routes provided by external sources */ + if (route->rt_source + != nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER)) + continue; + + s_route = nm_ip_route_new_binary(AF_INET6, + &route->network, + route->plen, + &route->gateway, + route->metric, + NULL); + nm_setting_ip_config_add_route(s_ip6, s_route); + nm_ip_route_unref(s_route); + } + + /* DNS */ + for (i = 0; i < nnameservers; i++) { + const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(self, i); + + nm_setting_ip_config_add_dns(s_ip6, _nm_utils_inet6_ntop(nameserver, sbuf)); + } + for (i = 0; i < nsearches; i++) { + const char *search = nm_ip6_config_get_search(self, i); + + nm_setting_ip_config_add_dns_search(s_ip6, search); + } + for (i = 0; i < noptions; i++) { + const char *option = nm_ip6_config_get_dns_option(self, i); + + nm_setting_ip_config_add_dns_option(s_ip6, option); + } + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_DNS_PRIORITY, + nm_ip6_config_get_dns_priority(self), + NULL); + + return NM_SETTING(s_ip6); +} + +/*****************************************************************************/ + +void +nm_ip6_config_merge(NMIP6Config * dst, + const NMIP6Config * src, + NMIPConfigMergeFlags merge_flags, + guint32 default_route_metric_penalty) +{ + guint32 i; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *address = NULL; + const NMIP6ConfigPrivate * src_priv; + + g_return_if_fail(src != NULL); + g_return_if_fail(dst != NULL); + + src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &address) { + if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_EXTERNAL) && !address->external) { + NMPlatformIP6Address a; + + a = *address; + a.external = TRUE; + _add_address(dst, NULL, &a); + } else + _add_address(dst, NMP_OBJECT_UP_CAST(address), NULL); + } + + /* nameservers */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip6_config_get_num_nameservers(src); i++) + nm_ip6_config_add_nameserver(dst, nm_ip6_config_get_nameserver(src, i)); + } + + /* routes */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) { + const NMPlatformIP6Route *r_src; + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r_src) { + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) { + if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS(src_priv->config_flags, + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) + continue; + if (default_route_metric_penalty) { + NMPlatformIP6Route r = *r_src; + + r.metric = + nm_utils_ip_route_metric_penalize(r.metric, default_route_metric_penalty); + _add_route(dst, NULL, &r, NULL); + continue; + } + } + _add_route(dst, ipconf_iter.current->obj, NULL, NULL); + } + } + + /* domains */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip6_config_get_num_domains(src); i++) + nm_ip6_config_add_domain(dst, nm_ip6_config_get_domain(src, i)); + } + + /* dns searches */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip6_config_get_num_searches(src); i++) + nm_ip6_config_add_search(dst, nm_ip6_config_get_search(src, i)); + } + + /* dns options */ + if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { + for (i = 0; i < nm_ip6_config_get_num_dns_options(src); i++) + nm_ip6_config_add_dns_option(dst, nm_ip6_config_get_dns_option(src, i)); + } + + /* DNS priority */ + if (nm_ip6_config_get_dns_priority(src)) + nm_ip6_config_set_dns_priority(dst, nm_ip6_config_get_dns_priority(src)); + + g_object_thaw_notify(G_OBJECT(dst)); +} + +/*****************************************************************************/ + +static int +_nameservers_get_index(const NMIP6Config *self, const struct in6_addr *ns) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->nameservers->len; i++) { + const struct in6_addr *n = &g_array_index(priv->nameservers, struct in6_addr, i); + + if (IN6_ARE_ADDR_EQUAL(ns, n)) + return (int) i; + } + return -1; +} + +static int +_domains_get_index(const NMIP6Config *self, const char *domain) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->domains->len; i++) { + const char *d = g_ptr_array_index(priv->domains, i); + + if (g_strcmp0(domain, d) == 0) + return (int) i; + } + return -1; +} + +static int +_searches_get_index(const NMIP6Config *self, const char *search) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->searches->len; i++) { + const char *s = g_ptr_array_index(priv->searches, i); + + if (g_strcmp0(search, s) == 0) + return (int) i; + } + return -1; +} + +static int +_dns_options_get_index(const NMIP6Config *self, const char *option) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + guint i; + + for (i = 0; i < priv->dns_options->len; i++) { + const char *s = g_ptr_array_index(priv->dns_options, i); + + if (g_strcmp0(option, s) == 0) + return (int) i; + } + return -1; +} + +/*****************************************************************************/ + +/** + * nm_ip6_config_subtract: + * @dst: config from which to remove everything in @src + * @src: config to remove from @dst + * @default_route_metric_penalty: pretend that on source we applied + * a route penalty on the default-route. It means, for default routes + * we don't remove routes that match exactly, but those with a lower + * metric (with the penalty removed). +* + * Removes everything in @src from @dst. + */ +void +nm_ip6_config_subtract(NMIP6Config * dst, + const NMIP6Config *src, + guint32 default_route_metric_penalty) +{ + NMIP6ConfigPrivate * dst_priv; + guint i; + int idx; + const NMPlatformIP6Address *a; + const NMPlatformIP6Route * r; + NMDedupMultiIter ipconf_iter; + gboolean changed; + gboolean changed_default_route; + + g_return_if_fail(src != NULL); + g_return_if_fail(dst != NULL); + + dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + changed = FALSE; + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &a) { + if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, + &dst_priv->idx_ip6_addresses, + NMP_OBJECT_UP_CAST(a), + NULL)) + changed = TRUE; + } + if (changed) + _notify_addresses(dst); + + /* nameservers */ + for (i = 0; i < nm_ip6_config_get_num_nameservers(src); i++) { + idx = _nameservers_get_index(dst, nm_ip6_config_get_nameserver(src, i)); + if (idx >= 0) + nm_ip6_config_del_nameserver(dst, idx); + } + + /* routes */ + changed = FALSE; + changed_default_route = FALSE; + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r) { + const NMPObject * o_src = NMP_OBJECT_UP_CAST(r); + NMPObject o_lookup_copy; + const NMPObject * o_lookup; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { + NMPlatformIP6Route *rr; + + /* the default route was penalized when merging it to the combined ip-config. + * When subtracting the routes, we must re-do that process when comparing + * the routes. */ + o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_src); + rr = NMP_OBJECT_CAST_IP6_ROUTE(&o_lookup_copy); + rr->metric = + nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); + } else + o_lookup = o_src; + + if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, + &dst_priv->idx_ip6_routes, + o_lookup, + (gconstpointer *) &obj_old)) { + if (dst_priv->best_default_route == obj_old) { + nm_clear_nmp_object(&dst_priv->best_default_route); + changed_default_route = TRUE; + } + changed = TRUE; + } + } + if (changed_default_route) { + nmp_object_ref_set(&dst_priv->best_default_route, + _nm_ip6_config_best_default_route_find(dst)); + _notify(dst, PROP_GATEWAY); + } + if (changed) + _notify_routes(dst); + + /* domains */ + for (i = 0; i < nm_ip6_config_get_num_domains(src); i++) { + idx = _domains_get_index(dst, nm_ip6_config_get_domain(src, i)); + if (idx >= 0) + nm_ip6_config_del_domain(dst, idx); + } + + /* dns searches */ + for (i = 0; i < nm_ip6_config_get_num_searches(src); i++) { + idx = _searches_get_index(dst, nm_ip6_config_get_search(src, i)); + if (idx >= 0) + nm_ip6_config_del_search(dst, idx); + } + + /* dns options */ + for (i = 0; i < nm_ip6_config_get_num_dns_options(src); i++) { + idx = _dns_options_get_index(dst, nm_ip6_config_get_dns_option(src, i)); + if (idx >= 0) + nm_ip6_config_del_dns_option(dst, idx); + } + + /* DNS priority */ + if (nm_ip6_config_get_dns_priority(src) == nm_ip6_config_get_dns_priority(dst)) + nm_ip6_config_set_dns_priority(dst, 0); + + g_object_thaw_notify(G_OBJECT(dst)); +} + +static gboolean +_nm_ip6_config_intersect_helper(NMIP6Config * dst, + const NMIP6Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty, + gboolean update_dst) +{ + NMIP6ConfigPrivate * dst_priv; + const NMIP6ConfigPrivate * src_priv; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *a; + const NMPlatformIP6Route * r; + gboolean changed, result = FALSE; + const NMPObject * new_best_default_route; + + g_return_val_if_fail(src, FALSE); + g_return_val_if_fail(dst, FALSE); + + dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); + src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); + + if (update_dst) + g_object_freeze_notify(G_OBJECT(dst)); + + /* addresses */ + if (intersect_addresses) { + changed = FALSE; + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) { + if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, + &src_priv->idx_ip6_addresses, + NMP_OBJECT_UP_CAST(a))) + continue; + + if (!update_dst) + return TRUE; + + if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) + nm_assert_not_reached(); + changed = TRUE; + } + if (changed) { + _notify_addresses(dst); + result = TRUE; + } + } + + /* ignore nameservers */ + + /* routes */ + if (!intersect_routes) + goto skip_routes; + + changed = FALSE; + new_best_default_route = NULL; + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, dst, &r) { + const NMPObject *o_dst = NMP_OBJECT_UP_CAST(r); + const NMPObject *o_lookup; + NMPObject o_lookup_copy; + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { + NMPlatformIP6Route *rr; + + /* the default route was penalized when merging it to the combined ip-config. + * When intersecting the routes, we must re-do that process when comparing + * the routes. */ + o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_dst); + rr = NMP_OBJECT_CAST_IP6_ROUTE(&o_lookup_copy); + rr->metric = + nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); + } else + o_lookup = o_dst; + + if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, + &src_priv->idx_ip6_routes, + o_lookup)) { + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, o_dst); + continue; + } + + if (!update_dst) + return TRUE; + + if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) + nm_assert_not_reached(); + changed = TRUE; + } + if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) { + nm_assert(changed); + _notify(dst, PROP_GATEWAY); + } + if (changed) { + _notify_routes(dst); + result = TRUE; + } + +skip_routes: + /* ignore domains */ + /* ignore dns searches */ + /* ignore dns options */ + + if (update_dst) + g_object_thaw_notify(G_OBJECT(dst)); + + return result; +} + +/** + * nm_ip6_config_intersect: + * @dst: a configuration to be updated + * @src: another configuration + * @intersect_addresses: whether addresses should be intersected + * @intersect_routes: whether routes should be intersected + * @default_route_metric_penalty: the default route metric penalty + * + * Computes the intersection between @src and @dst and updates @dst in place + * with the result. + */ +void +nm_ip6_config_intersect(NMIP6Config * dst, + const NMIP6Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + _nm_ip6_config_intersect_helper(dst, + src, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + TRUE); +} + +/** + * nm_ip6_config_intersect_alloc: + * @a: a configuration + * @b: another configuration + * @intersect_addresses: whether addresses should be intersected + * @intersect_routes: whether routes should be intersected + * @default_route_metric_penalty: the default route metric penalty + * + * Computes the intersection between @a and @b and returns the result in a newly + * allocated configuration. As a special case, if @a and @b are identical (with + * respect to the only properties considered - addresses and routes) the + * functions returns NULL so that one of existing configuration can be reused + * without allocation. + * + * Returns: the intersection between @a and @b, or %NULL if the result is equal + * to @a and @b. + */ +NMIP6Config * +nm_ip6_config_intersect_alloc(const NMIP6Config *a, + const NMIP6Config *b, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty) +{ + NMIP6Config *a_copy; + + if (_nm_ip6_config_intersect_helper((NMIP6Config *) a, + b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + FALSE)) { + a_copy = nm_ip6_config_clone(a); + _nm_ip6_config_intersect_helper(a_copy, + b, + intersect_addresses, + intersect_routes, + default_route_metric_penalty, + TRUE); + return a_copy; + } else + return NULL; +} + +/** + * nm_ip6_config_replace: + * @dst: config which will be replaced with everything in @src + * @src: config to copy over to @dst + * @relevant_changes: return whether there are changes to the + * destination object that are relevant. This is equal to + * nm_ip6_config_equal() showing any difference. + * + * Replaces everything in @dst with @src so that the two configurations + * contain the same content -- with the exception of the dbus path. + * + * Returns: whether the @dst instance changed in any way (including minor changes, + * that are not signaled by the output parameter @relevant_changes). + */ +gboolean +nm_ip6_config_replace(NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes) +{ +#if NM_MORE_ASSERTS + gboolean config_equal; +#endif + gboolean has_minor_changes = FALSE, has_relevant_changes = FALSE, are_equal; + guint i, num; + NMIP6ConfigPrivate * dst_priv; + const NMIP6ConfigPrivate * src_priv; + NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst; + const NMDedupMultiHeadEntry *head_entry_src; + const NMPObject * new_best_default_route; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(src), FALSE); + g_return_val_if_fail(NM_IS_IP6_CONFIG(dst), FALSE); + g_return_val_if_fail(src != dst, FALSE); + +#if NM_MORE_ASSERTS + config_equal = nm_ip6_config_equal(dst, src); +#endif + + dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); + src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); + + g_return_val_if_fail(src_priv->ifindex > 0, FALSE); + + g_object_freeze_notify(G_OBJECT(dst)); + + /* ifindex */ + if (src_priv->ifindex != dst_priv->ifindex) { + dst_priv->ifindex = src_priv->ifindex; + has_minor_changes = TRUE; + } + + /* addresses */ + head_entry_src = nm_ip6_config_lookup_addresses(src); + nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); + nm_ip_config_iter_ip6_address_init(&ipconf_iter_dst, dst); + are_equal = TRUE; + while (TRUE) { + gboolean has; + const NMPlatformIP6Address *r_src = NULL; + const NMPlatformIP6Address *r_dst = NULL; + + has = nm_platform_dedup_multi_iter_next_ip6_address(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip6_address(&ipconf_iter_dst, &r_dst)) { + are_equal = FALSE; + has_relevant_changes = TRUE; + break; + } + if (!has) + break; + + if (nm_platform_ip6_address_cmp(r_src, r_dst) != 0) { + are_equal = FALSE; + if (!IN6_ARE_ADDR_EQUAL(&r_src->address, &r_dst->address) || r_src->plen != r_dst->plen + || !IN6_ARE_ADDR_EQUAL(nm_platform_ip6_address_get_peer(r_src), + nm_platform_ip6_address_get_peer(r_dst))) { + has_relevant_changes = TRUE; + break; + } + } + } + if (!are_equal) { + has_minor_changes = TRUE; + nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_addresses); + nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { + _nm_ip_config_add_obj(dst_priv->multi_idx, + &dst_priv->idx_ip6_addresses_, + dst_priv->ifindex, + ipconf_iter_src.current->obj, + NULL, + FALSE, + TRUE, + NULL, + NULL); + } + nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, + &dst_priv->idx_ip6_addresses, + FALSE); + _notify_addresses(dst); + } + + /* routes */ + head_entry_src = nm_ip6_config_lookup_routes(src); + nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); + nm_ip_config_iter_ip6_route_init(&ipconf_iter_dst, dst); + are_equal = TRUE; + while (TRUE) { + gboolean has; + const NMPlatformIP6Route *r_src = NULL; + const NMPlatformIP6Route *r_dst = NULL; + + has = nm_platform_dedup_multi_iter_next_ip6_route(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip6_route(&ipconf_iter_dst, &r_dst)) { + are_equal = FALSE; + has_relevant_changes = TRUE; + break; + } + if (!has) + break; + + if (nm_platform_ip6_route_cmp_full(r_src, r_dst) != 0) { + are_equal = FALSE; + if (r_src->plen != r_dst->plen + || !nm_utils_ip6_address_same_prefix(&r_src->network, &r_dst->network, r_src->plen) + || r_src->metric != r_dst->metric + || !IN6_ARE_ADDR_EQUAL(&r_src->gateway, &r_dst->gateway)) { + has_relevant_changes = TRUE; + break; + } + } + } + if (!are_equal) { + has_minor_changes = TRUE; + new_best_default_route = NULL; + nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_routes); + nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { + const NMPObject *o = ipconf_iter_src.current->obj; + const NMPObject *obj_new; + + _nm_ip_config_add_obj(dst_priv->multi_idx, + &dst_priv->idx_ip6_routes_, + dst_priv->ifindex, + o, + NULL, + FALSE, + TRUE, + NULL, + &obj_new); + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); + } + nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, + &dst_priv->idx_ip6_routes, + FALSE); + if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) + _notify(dst, PROP_GATEWAY); + _notify_routes(dst); + } + + /* nameservers */ + num = nm_ip6_config_get_num_nameservers(src); + are_equal = num == nm_ip6_config_get_num_nameservers(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (!IN6_ARE_ADDR_EQUAL(nm_ip6_config_get_nameserver(src, i), + nm_ip6_config_get_nameserver(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip6_config_reset_nameservers(dst); + for (i = 0; i < num; i++) + nm_ip6_config_add_nameserver(dst, nm_ip6_config_get_nameserver(src, i)); + has_relevant_changes = TRUE; + } + + /* domains */ + num = nm_ip6_config_get_num_domains(src); + are_equal = num == nm_ip6_config_get_num_domains(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip6_config_get_domain(src, i), nm_ip6_config_get_domain(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip6_config_reset_domains(dst); + for (i = 0; i < num; i++) + nm_ip6_config_add_domain(dst, nm_ip6_config_get_domain(src, i)); + has_relevant_changes = TRUE; + } + + /* dns searches */ + num = nm_ip6_config_get_num_searches(src); + are_equal = num == nm_ip6_config_get_num_searches(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip6_config_get_search(src, i), nm_ip6_config_get_search(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip6_config_reset_searches(dst); + for (i = 0; i < num; i++) + nm_ip6_config_add_search(dst, nm_ip6_config_get_search(src, i)); + has_relevant_changes = TRUE; + } + + /* dns options */ + num = nm_ip6_config_get_num_dns_options(src); + are_equal = num == nm_ip6_config_get_num_dns_options(dst); + if (are_equal) { + for (i = 0; i < num; i++) { + if (g_strcmp0(nm_ip6_config_get_dns_option(src, i), + nm_ip6_config_get_dns_option(dst, i))) { + are_equal = FALSE; + break; + } + } + } + if (!are_equal) { + nm_ip6_config_reset_dns_options(dst); + for (i = 0; i < num; i++) + nm_ip6_config_add_dns_option(dst, nm_ip6_config_get_dns_option(src, i)); + has_relevant_changes = TRUE; + } + + /* DNS priority */ + if (src_priv->dns_priority != dst_priv->dns_priority) { + nm_ip6_config_set_dns_priority(dst, src_priv->dns_priority); + has_minor_changes = TRUE; + } + + if (src_priv->privacy != dst_priv->privacy) { + nm_ip6_config_set_privacy(dst, src_priv->privacy); + has_minor_changes = TRUE; + } + +#if NM_MORE_ASSERTS + /* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes + * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */ + nm_assert(config_equal == !has_relevant_changes); +#endif + + g_object_thaw_notify(G_OBJECT(dst)); + + if (relevant_changes) + *relevant_changes = has_relevant_changes; + + return has_relevant_changes || has_minor_changes; +} + +/*****************************************************************************/ + +void +nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self, + const NMNDiscAddress *addresses, + guint addresses_n, + guint8 plen, + guint32 ifa_flags) +{ + NMIP6ConfigPrivate *priv; + guint i; + gboolean changed = FALSE; + gint32 base_time_sec; + + g_return_if_fail(NM_IS_IP6_CONFIG(self)); + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(priv->ifindex > 0); + + /* the base-timestamp doesn't matter it's only an anchor for the + * expiry. However, try to re-use the same base-time for a while + * by rounding it to 10000 seconds. + * + * That is because we deduplicate and NMPlatformIP6Address instances + * so using the same timestamps is preferable. */ + base_time_sec = nm_utils_get_monotonic_timestamp_sec(); + base_time_sec = (base_time_sec / 10000) * 10000; + base_time_sec = NM_MAX(1, base_time_sec); + + nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_addresses); + + for (i = 0; i < addresses_n; i++) { + const NMNDiscAddress *ndisc_addr = &addresses[i]; + NMPObject obj; + NMPlatformIP6Address *a; + + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); + a = NMP_OBJECT_CAST_IP6_ADDRESS(&obj); + a->ifindex = priv->ifindex; + a->address = ndisc_addr->address; + a->plen = plen; + a->timestamp = base_time_sec, + a->lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000, + ndisc_addr->expiry_msec, + TRUE), + a->preferred = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000, + ndisc_addr->expiry_preferred_msec, + TRUE), + a->addr_source = NM_IP_CONFIG_SOURCE_NDISC; + a->n_ifa_flags = ifa_flags; + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_addresses_, + priv->ifindex, + &obj, + NULL, + FALSE, + TRUE, + NULL, + NULL)) + changed = TRUE; + } + + if (nm_dedup_multi_index_dirty_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses, FALSE) > 0) + changed = TRUE; + + if (changed) + _notify_addresses(self); +} + +void +nm_ip6_config_reset_addresses(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses) > 0) + _notify_addresses(self); +} + +static void +_add_address(NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Address *new) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_addresses_, + priv->ifindex, + obj_new, + (const NMPlatformObject *) new, + TRUE, + FALSE, + NULL, + NULL)) + _notify_addresses(self); +} + +/** + * nm_ip6_config_add_address: + * @self: the #NMIP6Config + * @new: the new address to add to @self + * + * Adds the new address to @self. If an address with the same basic properties + * (address, prefix) already exists in @self, it is overwritten with the + * lifetime and preferred of @new. The source is also overwritten by the source + * from @new if that source is higher priority. + */ +void +nm_ip6_config_add_address(NMIP6Config *self, const NMPlatformIP6Address *new) +{ + g_return_if_fail(self); + g_return_if_fail(new); + g_return_if_fail(new->plen <= 128); + g_return_if_fail(NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex > 0); + + _add_address(self, NULL, new); +} + +void +_nmtst_ip6_config_del_address(NMIP6Config *self, guint i) +{ + const NMPlatformIP6Address *a; + + a = _nmtst_ip6_config_get_address(self, i); + if (!nm_ip6_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(a))) + g_assert_not_reached(); +} + +guint +nm_ip6_config_get_num_addresses(const NMIP6Config *self) +{ + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_ip6_config_lookup_addresses(self); + return head_entry ? head_entry->len : 0; +} + +const NMPlatformIP6Address * +nm_ip6_config_get_first_address(const NMIP6Config *self) +{ + NMDedupMultiIter iter; + const NMPlatformIP6Address *a = NULL; + + nm_ip_config_iter_ip6_address_for_each (&iter, self, &a) + return a; + return NULL; +} + +const NMPlatformIP6Address * +_nmtst_ip6_config_get_address(const NMIP6Config *self, guint i) +{ + NMDedupMultiIter iter; + const NMPlatformIP6Address *a = NULL; + guint j; + + j = 0; + nm_ip_config_iter_ip6_address_for_each (&iter, self, &a) { + if (i == j) + return a; + j++; + } + g_return_val_if_reached(NULL); +} + +const NMPlatformIP6Address * +nm_ip6_config_lookup_address(const NMIP6Config *self, const struct in6_addr *addr) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + NMPObject obj_stack; + const NMDedupMultiEntry * entry; + + nmp_object_stackinit_id_ip6_address(&obj_stack, priv->ifindex, addr); + entry = nm_dedup_multi_index_lookup_obj(priv->multi_idx, &priv->idx_ip6_addresses, &obj_stack); + return entry ? NMP_OBJECT_CAST_IP6_ADDRESS(entry->obj) : NULL; +} + +const NMPlatformIP6Address * +nm_ip6_config_find_first_address(const NMIP6Config *self, NMPlatformMatchFlags match_flag) +{ + const NMPlatformIP6Address *addr; + NMDedupMultiIter iter; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); + + nm_assert(!NM_FLAGS_ANY( + match_flag, + ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))); + + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY)); + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)); + + nm_ip_config_iter_ip6_address_for_each (&iter, self, &addr) { + if (nm_platform_ip6_address_match(addr, match_flag)) + return addr; + } + return NULL; +} + +/** + * nm_ip6_config_has_dad_pending_addresses + * @self: configuration containing the addresses to check + * @candidates: configuration with the list of addresses we are + * interested in + * + * Check whether there are addresses with DAD pending in @self, that + * are also contained in @candidates. + * + * Returns: %TRUE if at least one matching address was found, %FALSE + * otherwise + */ +gboolean +nm_ip6_config_has_any_dad_pending(const NMIP6Config *self, const NMIP6Config *candidates) +{ + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *addr, *addr_c; + + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &addr) { + if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED) + && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC)) { + addr_c = nm_ip6_config_lookup_address(candidates, &addr->address); + if (addr_c) { + if (addr->plen == addr_c->plen) + return TRUE; + } + } + } + + return FALSE; +} + +/*****************************************************************************/ + +static const NMDedupMultiEntry * +_lookup_route(const NMIP6Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type) +{ + const NMIP6ConfigPrivate *priv; + + nm_assert(NM_IS_IP6_CONFIG(self)); + nm_assert(NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP6_ROUTE); + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return _nm_ip_config_lookup_ip_route(priv->multi_idx, &priv->idx_ip6_routes_, needle, cmp_type); +} + +void +nm_ip6_config_reset_routes_ndisc(NMIP6Config * self, + const NMNDiscGateway *gateways, + guint gateways_n, + const NMNDiscRoute * routes, + guint routes_n, + guint32 route_table, + guint32 route_metric, + gboolean kernel_support_rta_pref) +{ + NMIP6ConfigPrivate *priv; + guint i; + gboolean changed = FALSE; + const NMPObject * new_best_default_route; + + g_return_if_fail(NM_IS_IP6_CONFIG(self)); + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(priv->ifindex > 0); + + nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_routes); + + new_best_default_route = NULL; + for (i = 0; i < routes_n; i++) { + const NMNDiscRoute *ndisc_route = &routes[i]; + NMPObject obj; + const NMPObject * obj_new; + NMPlatformIP6Route *r; + + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + r = NMP_OBJECT_CAST_IP6_ROUTE(&obj); + r->ifindex = priv->ifindex; + r->network = ndisc_route->network; + r->plen = ndisc_route->plen; + r->gateway = ndisc_route->gateway; + r->rt_source = NM_IP_CONFIG_SOURCE_NDISC; + r->table_coerced = nm_platform_route_table_coerce(route_table); + r->metric = route_metric; + r->rt_pref = ndisc_route->preference; + nm_assert((NMIcmpv6RouterPref) r->rt_pref == ndisc_route->preference); + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_routes_, + priv->ifindex, + &obj, + NULL, + FALSE, + TRUE, + NULL, + &obj_new)) + changed = TRUE; + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); + } + + if (gateways_n) { + const NMPObject * obj_new; + NMPlatformIP6Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_NDISC, + .ifindex = priv->ifindex, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + const NMIcmpv6RouterPref first_pref = gateways[0].preference; + + for (i = 0; i < gateways_n; i++) { + r.gateway = gateways[i].address; + r.rt_pref = gateways[i].preference; + nm_assert((NMIcmpv6RouterPref) r.rt_pref == gateways[i].preference); + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_routes_, + priv->ifindex, + NULL, + (const NMPlatformObject *) &r, + FALSE, + TRUE, + NULL, + &obj_new)) + changed = TRUE; + new_best_default_route = + _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); + + if (first_pref != gateways[i].preference && !kernel_support_rta_pref) { + /* We are unable to configure a router preference. Hence, we skip all gateways + * with a different preference from the first gateway. Note, that the gateways + * are sorted in order of highest to lowest preference. */ + break; + } + } + } + + if (nm_dedup_multi_index_dirty_remove_idx(priv->multi_idx, &priv->idx_ip6_routes, FALSE) > 0) + changed = TRUE; + + if (nmp_object_ref_set(&priv->best_default_route, new_best_default_route)) { + changed = TRUE; + _notify(self, PROP_GATEWAY); + } + + if (changed) + _notify_routes(self); +} + +void +nm_ip6_config_reset_routes(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_routes) > 0) { + if (nm_clear_nmp_object(&priv->best_default_route)) + _notify(self, PROP_GATEWAY); + _notify_routes(self); + } +} + +static void +_add_route(NMIP6Config * self, + const NMPObject *obj_new, + const NMPlatformIP6Route *new, + const NMPObject **out_obj_new) +{ + NMIP6ConfigPrivate * priv = NM_IP6_CONFIG_GET_PRIVATE(self); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + const NMPObject * obj_new_2; + + nm_assert((!new) != (!obj_new)); + nm_assert(!new || _route_valid(new)); + nm_assert(!obj_new || _route_valid(NMP_OBJECT_CAST_IP6_ROUTE(obj_new))); + + if (_nm_ip_config_add_obj(priv->multi_idx, + &priv->idx_ip6_routes_, + priv->ifindex, + obj_new, + (const NMPlatformObject *) new, + TRUE, + FALSE, + &obj_old, + &obj_new_2)) { + gboolean changed_default_route = FALSE; + + if (priv->best_default_route == obj_old && obj_old != obj_new_2) { + changed_default_route = TRUE; + nm_clear_nmp_object(&priv->best_default_route); + } + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); + if (_nm_ip_config_best_default_route_merge(&priv->best_default_route, obj_new_2)) + changed_default_route = TRUE; + + if (changed_default_route) + _notify(self, PROP_GATEWAY); + _notify_routes(self); + } else + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); +} + +/** + * nm_ip6_config_add_route: + * @self: the #NMIP6Config + * @new: the new route to add to @self + * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed + * by caller. + * + * Adds the new route to @self. If a route with the same basic properties + * (network, prefix) already exists in @self, it is overwritten including the + * gateway and metric of @new. The source is also overwritten by the source + * from @new if that source is higher priority. + */ +void +nm_ip6_config_add_route(NMIP6Config *self, + const NMPlatformIP6Route *new, + const NMPObject **out_obj_new) +{ + g_return_if_fail(self); + g_return_if_fail(new); + g_return_if_fail(new->plen <= 128); + g_return_if_fail(NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex > 0); + + _add_route(self, NULL, new, out_obj_new); +} + +void +_nmtst_ip6_config_del_route(NMIP6Config *self, guint i) +{ + const NMPlatformIP6Route *r; + + r = _nmtst_ip6_config_get_route(self, i); + if (!nm_ip6_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(r))) + g_assert_not_reached(); +} + +guint +nm_ip6_config_get_num_routes(const NMIP6Config *self) +{ + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_ip6_config_lookup_routes(self); + nm_assert(!head_entry || head_entry->len == c_list_length(&head_entry->lst_entries_head)); + return head_entry ? head_entry->len : 0; +} + +const NMPlatformIP6Route * +_nmtst_ip6_config_get_route(const NMIP6Config *self, guint i) +{ + NMDedupMultiIter iter; + const NMPlatformIP6Route *r = NULL; + guint j; + + j = 0; + nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { + if (i == j) + return r; + j++; + } + g_return_val_if_reached(NULL); +} + +const NMPlatformIP6Route * +nm_ip6_config_get_direct_route_for_host(const NMIP6Config * self, + const struct in6_addr *host, + guint32 route_table) +{ + const NMPlatformIP6Route *best_route = NULL; + const NMPlatformIP6Route *item; + NMDedupMultiIter ipconf_iter; + + g_return_val_if_fail(host && !IN6_IS_ADDR_UNSPECIFIED(host), NULL); + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &item) { + if (!IN6_IS_ADDR_UNSPECIFIED(&item->gateway)) + continue; + + if (best_route && best_route->plen > item->plen) + continue; + + if (nm_platform_route_table_uncoerce(item->table_coerced, TRUE) != route_table) + continue; + + if (!nm_utils_ip6_address_same_prefix(host, &item->network, item->plen)) + continue; + + if (best_route && best_route->metric <= item->metric) + continue; + + best_route = item; + } + return best_route; +} + +const NMPlatformIP6Address * +nm_ip6_config_get_subnet_for_host(const NMIP6Config *self, const struct in6_addr *host) +{ + NMDedupMultiIter iter; + const NMPlatformIP6Address *item; + const NMPlatformIP6Address *subnet = NULL; + struct in6_addr subnet2, host2; + + g_return_val_if_fail(host && !IN6_IS_ADDR_UNSPECIFIED(host), NULL); + + nm_ip_config_iter_ip6_address_for_each (&iter, self, &item) { + if (subnet && subnet->plen >= item->plen) + continue; + + nm_utils_ip6_address_clear_host_address(&host2, host, item->plen); + nm_utils_ip6_address_clear_host_address(&subnet2, &item->address, item->plen); + + if (IN6_ARE_ADDR_EQUAL(&subnet2, &host2)) + subnet = item; + } + + return subnet; +} + +/*****************************************************************************/ + +void +nm_ip6_config_reset_nameservers(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (priv->nameservers->len != 0) { + g_array_set_size(priv->nameservers, 0); + _notify(self, PROP_NAMESERVERS); + } +} + +void +nm_ip6_config_add_nameserver(NMIP6Config *self, const struct in6_addr *new) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + int i; + + g_return_if_fail(new != NULL); + + for (i = 0; i < priv->nameservers->len; i++) + if (IN6_ARE_ADDR_EQUAL(new, &g_array_index(priv->nameservers, struct in6_addr, i))) + return; + + g_array_append_val(priv->nameservers, *new); + _notify(self, PROP_NAMESERVERS); +} + +void +nm_ip6_config_del_nameserver(NMIP6Config *self, guint i) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->nameservers->len); + + g_array_remove_index(priv->nameservers, i); + _notify(self, PROP_NAMESERVERS); +} + +guint +nm_ip6_config_get_num_nameservers(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->nameservers->len; +} + +const struct in6_addr * +nm_ip6_config_get_nameserver(const NMIP6Config *self, guint i) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return &g_array_index(priv->nameservers, struct in6_addr, i); +} + +/*****************************************************************************/ + +void +nm_ip6_config_reset_domains(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (priv->domains->len != 0) { + g_ptr_array_set_size(priv->domains, 0); + _notify(self, PROP_DOMAINS); + } +} + +void +nm_ip6_config_add_domain(NMIP6Config *self, const char *domain) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_check_and_add_domain(priv->domains, domain)) + _notify(self, PROP_DOMAINS); +} + +void +nm_ip6_config_del_domain(NMIP6Config *self, guint i) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->domains->len); + + g_ptr_array_remove_index(priv->domains, i); + _notify(self, PROP_DOMAINS); +} + +guint +nm_ip6_config_get_num_domains(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->domains->len; +} + +const char * +nm_ip6_config_get_domain(const NMIP6Config *self, guint i) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->domains, i); +} + +/*****************************************************************************/ + +void +nm_ip6_config_reset_searches(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (priv->searches->len != 0) { + g_ptr_array_set_size(priv->searches, 0); + _notify(self, PROP_SEARCHES); + } +} + +void +nm_ip6_config_add_search(NMIP6Config *self, const char *search) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (_nm_ip_config_check_and_add_domain(priv->searches, search)) + _notify(self, PROP_SEARCHES); +} + +void +nm_ip6_config_del_search(NMIP6Config *self, guint i) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->searches->len); + + g_ptr_array_remove_index(priv->searches, i); + _notify(self, PROP_SEARCHES); +} + +guint +nm_ip6_config_get_num_searches(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->searches->len; +} + +const char * +nm_ip6_config_get_search(const NMIP6Config *self, guint i) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->searches, i); +} + +/*****************************************************************************/ + +void +nm_ip6_config_reset_dns_options(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (priv->dns_options->len != 0) { + g_ptr_array_set_size(priv->dns_options, 0); + _notify(self, PROP_DNS_OPTIONS); + } +} + +void +nm_ip6_config_add_dns_option(NMIP6Config *self, const char *new) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + int i; + + g_return_if_fail(new != NULL); + g_return_if_fail(new[0] != '\0'); + + for (i = 0; i < priv->dns_options->len; i++) + if (!g_strcmp0(g_ptr_array_index(priv->dns_options, i), new)) + return; + + g_ptr_array_add(priv->dns_options, g_strdup(new)); + _notify(self, PROP_DNS_OPTIONS); +} + +void +nm_ip6_config_del_dns_option(NMIP6Config *self, guint i) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + g_return_if_fail(i < priv->dns_options->len); + + g_ptr_array_remove_index(priv->dns_options, i); + _notify(self, PROP_DNS_OPTIONS); +} + +guint +nm_ip6_config_get_num_dns_options(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->dns_options->len; +} + +const char * +nm_ip6_config_get_dns_option(const NMIP6Config *self, guint i) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return g_ptr_array_index(priv->dns_options, i); +} + +/*****************************************************************************/ + +NMIPConfigFlags +nm_ip6_config_get_config_flags(const NMIP6Config *self) +{ + return NM_IP6_CONFIG_GET_PRIVATE(self)->config_flags; +} + +void +nm_ip6_config_set_config_flags(NMIP6Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (mask == 0) { + /* for convenience, accept 0 mask to set any flags. */ + mask = flags; + } + + nm_assert(!NM_FLAGS_ANY(flags, ~mask)); + priv->config_flags = (flags & mask) | (priv->config_flags & ~mask); +} + +/*****************************************************************************/ + +void +nm_ip6_config_set_dns_priority(NMIP6Config *self, int priority) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + if (priority != priv->dns_priority) { + priv->dns_priority = priority; + _notify(self, PROP_DNS_PRIORITY); + } +} + +int +nm_ip6_config_get_dns_priority(const NMIP6Config *self) +{ + const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + return priv->dns_priority; +} + +/*****************************************************************************/ + +const NMPObject * +nm_ip6_config_nmpobj_lookup(const NMIP6Config *self, const NMPObject *needle) +{ + const NMIP6ConfigPrivate * priv; + const NMDedupMultiIdxType *idx_type; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + switch (NMP_OBJECT_GET_TYPE(needle)) { + case NMP_OBJECT_TYPE_IP6_ADDRESS: + idx_type = &priv->idx_ip6_addresses; + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + idx_type = &priv->idx_ip6_routes; + break; + default: + g_return_val_if_reached(NULL); + } + + return nm_dedup_multi_entry_get_obj( + nm_dedup_multi_index_lookup_obj(priv->multi_idx, idx_type, needle)); +} + +gboolean +nm_ip6_config_nmpobj_remove(NMIP6Config *self, const NMPObject *needle) +{ + NMIP6ConfigPrivate * priv; + NMDedupMultiIdxType *idx_type; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + guint n; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); + + priv = NM_IP6_CONFIG_GET_PRIVATE(self); + switch (NMP_OBJECT_GET_TYPE(needle)) { + case NMP_OBJECT_TYPE_IP6_ADDRESS: + idx_type = &priv->idx_ip6_addresses; + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + idx_type = &priv->idx_ip6_routes; + break; + default: + g_return_val_if_reached(FALSE); + } + + n = nm_dedup_multi_index_remove_obj(priv->multi_idx, + idx_type, + needle, + (gconstpointer *) &obj_old); + if (n != 1) { + nm_assert(n == 0); + return FALSE; + } + + nm_assert(NMP_OBJECT_GET_TYPE(obj_old) == NMP_OBJECT_GET_TYPE(needle)); + + switch (NMP_OBJECT_GET_TYPE(obj_old)) { + case NMP_OBJECT_TYPE_IP6_ADDRESS: + _notify_addresses(self); + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + if (priv->best_default_route == obj_old) { + if (nmp_object_ref_set(&priv->best_default_route, + _nm_ip6_config_best_default_route_find(self))) + _notify(self, PROP_GATEWAY); + } + _notify_routes(self); + break; + default: + nm_assert_not_reached(); + } + return TRUE; +} + +/*****************************************************************************/ + +static void +hash_u32(GChecksum *sum, guint32 n) +{ + g_checksum_update(sum, (const guint8 *) &n, sizeof(n)); +} + +static void +hash_in6addr(GChecksum *sum, const struct in6_addr *a) +{ + if (a) + g_checksum_update(sum, (const guint8 *) a, sizeof(*a)); + else + g_checksum_update(sum, (const guint8 *) &in6addr_any, sizeof(in6addr_any)); +} + +void +nm_ip6_config_hash(const NMIP6Config *self, GChecksum *sum, gboolean dns_only) +{ + guint32 i; + const char * s; + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *address; + const NMPlatformIP6Route * route; + + g_return_if_fail(self); + g_return_if_fail(sum); + + if (dns_only == FALSE) { + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) { + hash_in6addr(sum, &address->address); + hash_u32(sum, address->plen); + } + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) { + hash_in6addr(sum, &route->network); + hash_u32(sum, route->plen); + hash_in6addr(sum, &route->gateway); + hash_u32(sum, route->metric); + } + } + + for (i = 0; i < nm_ip6_config_get_num_nameservers(self); i++) + hash_in6addr(sum, nm_ip6_config_get_nameserver(self, i)); + + for (i = 0; i < nm_ip6_config_get_num_domains(self); i++) { + s = nm_ip6_config_get_domain(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + for (i = 0; i < nm_ip6_config_get_num_searches(self); i++) { + s = nm_ip6_config_get_search(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } + + for (i = 0; i < nm_ip6_config_get_num_dns_options(self); i++) { + s = nm_ip6_config_get_dns_option(self, i); + g_checksum_update(sum, (const guint8 *) s, strlen(s)); + } +} + +/** + * nm_ip6_config_equal: + * @a: first config to compare + * @b: second config to compare + * + * Compares two #NMIP6Configs for basic equality. This means that all + * attributes must exist in the same order in both configs (addresses, routes, + * domains, DNS servers, etc) but some attributes (address lifetimes, and address + * and route sources) are ignored. + * + * Returns: %TRUE if the configurations are basically equal to each other, + * %FALSE if not + */ +gboolean +nm_ip6_config_equal(const NMIP6Config *a, const NMIP6Config *b) +{ + nm_auto_free_checksum GChecksum *a_checksum = g_checksum_new(G_CHECKSUM_SHA1); + nm_auto_free_checksum GChecksum *b_checksum = g_checksum_new(G_CHECKSUM_SHA1); + guint8 a_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + guint8 b_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; + + if (a) + nm_ip6_config_hash(a, a_checksum, FALSE); + if (b) + nm_ip6_config_hash(b, b_checksum, FALSE); + + nm_utils_checksum_get_digest(a_checksum, a_data); + nm_utils_checksum_get_digest(b_checksum, b_data); + return !memcmp(a_data, b_data, sizeof(a_data)); +} + +/*****************************************************************************/ + +static void +nameservers_to_gvalue(GArray *array, GValue *value) +{ + GVariantBuilder builder; + guint i = 0; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aay")); + + while (array && (i < array->len)) { + struct in6_addr *addr; + + addr = &g_array_index(array, struct in6_addr, i++); + g_variant_builder_add(&builder, + "@ay", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, addr, 16, 1)); + } + + g_value_take_variant(value, g_variant_builder_end(&builder)); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMIP6Config * self = NM_IP6_CONFIG(object); + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_IFINDEX: + g_value_set_int(value, priv->ifindex); + break; + case PROP_ADDRESS_DATA: + case PROP_ADDRESSES: + nm_assert(!!priv->address_data_variant == !!priv->addresses_variant); + + if (!priv->address_data_variant) { + nm_utils_ip_addresses_to_dbus(AF_INET6, + nm_ip6_config_lookup_addresses(self), + priv->best_default_route, + priv->privacy, + &priv->address_data_variant, + &priv->addresses_variant); + g_variant_ref_sink(priv->address_data_variant); + g_variant_ref_sink(priv->addresses_variant); + } + g_value_set_variant(value, + prop_id == PROP_ADDRESS_DATA ? priv->address_data_variant + : priv->addresses_variant); + break; + + case PROP_ROUTE_DATA: + case PROP_ROUTES: + nm_assert(!!priv->route_data_variant == !!priv->routes_variant); + + if (!priv->route_data_variant) { + nm_utils_ip_routes_to_dbus(AF_INET6, + nm_ip6_config_lookup_routes(self), + &priv->route_data_variant, + &priv->routes_variant); + g_variant_ref_sink(priv->route_data_variant); + g_variant_ref_sink(priv->routes_variant); + } + + g_value_set_variant(value, + prop_id == PROP_ROUTE_DATA ? priv->route_data_variant + : priv->routes_variant); + break; + case PROP_GATEWAY: + if (priv->best_default_route) { + g_value_take_string(value, + nm_utils_inet6_ntop_dup( + &NMP_OBJECT_CAST_IP6_ROUTE(priv->best_default_route)->gateway)); + } else + g_value_set_string(value, NULL); + break; + case PROP_NAMESERVERS: + nameservers_to_gvalue(priv->nameservers, value); + break; + case PROP_DOMAINS: + nm_utils_g_value_set_strv(value, priv->domains); + break; + case PROP_SEARCHES: + nm_utils_g_value_set_strv(value, priv->searches); + break; + case PROP_DNS_OPTIONS: + nm_utils_g_value_set_strv(value, priv->dns_options); + break; + case PROP_DNS_PRIORITY: + g_value_set_int(value, priv->dns_priority); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMIP6Config * self = NM_IP6_CONFIG(object); + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MULTI_IDX: + /* construct-only */ + priv->multi_idx = g_value_get_pointer(value); + if (!priv->multi_idx) + g_return_if_reached(); + nm_dedup_multi_index_ref(priv->multi_idx); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_ip6_config_init(NMIP6Config *self) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_addresses, + NMP_OBJECT_TYPE_IP6_ADDRESS); + nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_routes, + NMP_OBJECT_TYPE_IP6_ROUTE); + + priv->nameservers = g_array_new(FALSE, TRUE, sizeof(struct in6_addr)); + priv->domains = g_ptr_array_new_with_free_func(g_free); + priv->searches = g_ptr_array_new_with_free_func(g_free); + priv->dns_options = g_ptr_array_new_with_free_func(g_free); +} + +NMIP6Config * +nm_ip6_config_new(NMDedupMultiIndex *multi_idx, int ifindex) +{ + g_return_val_if_fail(ifindex >= -1, NULL); + return g_object_new(NM_TYPE_IP6_CONFIG, + NM_IP6_CONFIG_MULTI_IDX, + multi_idx, + NM_IP6_CONFIG_IFINDEX, + ifindex, + NULL); +} + +NMIP6Config * +nm_ip6_config_new_cloned(const NMIP6Config *src) +{ + NMIP6Config *new; + + g_return_val_if_fail(NM_IS_IP6_CONFIG(src), NULL); + + new = nm_ip6_config_new(nm_ip6_config_get_multi_idx(src), nm_ip6_config_get_ifindex(src)); + nm_ip6_config_replace(new, src, NULL); + return new; +} + +static void +finalize(GObject *object) +{ + NMIP6Config * self = NM_IP6_CONFIG(object); + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); + + nm_clear_nmp_object(&priv->best_default_route); + + nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses); + nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_routes); + + nm_clear_g_variant(&priv->address_data_variant); + nm_clear_g_variant(&priv->addresses_variant); + nm_clear_g_variant(&priv->route_data_variant); + nm_clear_g_variant(&priv->routes_variant); + + g_array_unref(priv->nameservers); + g_ptr_array_unref(priv->domains); + g_ptr_array_unref(priv->searches); + g_ptr_array_unref(priv->dns_options); + + G_OBJECT_CLASS(nm_ip6_config_parent_class)->finalize(object); + + nm_dedup_multi_index_unref(priv->multi_idx); +} + +static const NMDBusInterfaceInfoExtended interface_info_ip6_config = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_IP6_CONFIG, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Addresses", + "a(ayuay)", + NM_IP6_CONFIG_ADDRESSES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AddressData", + "aa{sv}", + NM_IP6_CONFIG_ADDRESS_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Gateway", "s", NM_IP6_CONFIG_GATEWAY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Routes", + "a(ayuayu)", + NM_IP6_CONFIG_ROUTES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RouteData", + "aa{sv}", + NM_IP6_CONFIG_ROUTE_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Nameservers", + "aay", + NM_IP6_CONFIG_NAMESERVERS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Domains", + "as", + NM_IP6_CONFIG_DOMAINS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Searches", + "as", + NM_IP6_CONFIG_SEARCHES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsOptions", + "as", + NM_IP6_CONFIG_DNS_OPTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsPriority", + "i", + NM_IP6_CONFIG_DNS_PRIORITY), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_ip6_config_class_init(NMIP6ConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMIPConfigClass * ip_config_class = NM_IP_CONFIG_CLASS(klass); + + ip_config_class->is_ipv4 = FALSE; + ip_config_class->addr_family = AF_INET6; + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/IP6Config"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_ip6_config); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + obj_properties[PROP_MULTI_IDX] = + g_param_spec_pointer(NM_IP6_CONFIG_MULTI_IDX, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_IP6_CONFIG_IFINDEX, + "", + "", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ADDRESS_DATA] = + g_param_spec_variant(NM_IP6_CONFIG_ADDRESS_DATA, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ADDRESSES] = + g_param_spec_variant(NM_IP6_CONFIG_ADDRESSES, + "", + "", + G_VARIANT_TYPE("a(ayuay)"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTE_DATA] = + g_param_spec_variant(NM_IP6_CONFIG_ROUTE_DATA, + "", + "", + G_VARIANT_TYPE("aa{sv}"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ROUTES] = g_param_spec_variant(NM_IP6_CONFIG_ROUTES, + "", + "", + G_VARIANT_TYPE("a(ayuayu)"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_GATEWAY] = g_param_spec_string(NM_IP6_CONFIG_GATEWAY, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_NAMESERVERS] = + g_param_spec_variant(NM_IP6_CONFIG_NAMESERVERS, + "", + "", + G_VARIANT_TYPE("aay"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DOMAINS] = g_param_spec_boxed(NM_IP6_CONFIG_DOMAINS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SEARCHES] = g_param_spec_boxed(NM_IP6_CONFIG_SEARCHES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DNS_OPTIONS] = + g_param_spec_boxed(NM_IP6_CONFIG_DNS_OPTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DNS_PRIORITY] = g_param_spec_int(NM_IP6_CONFIG_DNS_PRIORITY, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-ip6-config.h b/src/core/nm-ip6-config.h new file mode 100644 index 0000000..398186d --- /dev/null +++ b/src/core/nm-ip6-config.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_IP6_CONFIG_H__ +#define __NETWORKMANAGER_IP6_CONFIG_H__ + +#include + +#include "nm-setting-ip6-config.h" + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "platform/nmp-object.h" +#include "nm-ip-config.h" + +/*****************************************************************************/ + +void nm_ip_config_iter_ip6_address_init(NMDedupMultiIter *iter, const NMIP6Config *self); +void nm_ip_config_iter_ip6_route_init(NMDedupMultiIter *iter, const NMIP6Config *self); + +#define nm_ip_config_iter_ip6_address_for_each(iter, self, address) \ + for (nm_ip_config_iter_ip6_address_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip6_address((iter), (address));) + +#define nm_ip_config_iter_ip6_route_for_each(iter, self, route) \ + for (nm_ip_config_iter_ip6_route_init((iter), (self)); \ + nm_platform_dedup_multi_iter_next_ip6_route((iter), (route));) + +/*****************************************************************************/ + +#define NM_TYPE_IP6_CONFIG (nm_ip6_config_get_type()) +#define NM_IP6_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP6_CONFIG, NMIP6Config)) +#define NM_IP6_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_IP6_CONFIG, NMIP6ConfigClass)) +#define NM_IS_IP6_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_IP6_CONFIG)) +#define NM_IS_IP6_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_IP6_CONFIG)) +#define NM_IP6_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IP6_CONFIG, NMIP6ConfigClass)) + +typedef struct _NMIP6ConfigClass NMIP6ConfigClass; + +/* internal */ +#define NM_IP6_CONFIG_MULTI_IDX "multi-idx" +#define NM_IP6_CONFIG_IFINDEX "ifindex" + +/* public */ +#define NM_IP6_CONFIG_ADDRESS_DATA "address-data" +#define NM_IP6_CONFIG_ROUTE_DATA "route-data" +#define NM_IP6_CONFIG_GATEWAY "gateway" +#define NM_IP6_CONFIG_NAMESERVERS "nameservers" +#define NM_IP6_CONFIG_DOMAINS "domains" +#define NM_IP6_CONFIG_SEARCHES "searches" +#define NM_IP6_CONFIG_DNS_OPTIONS "dns-options" +#define NM_IP6_CONFIG_DNS_PRIORITY "dns-priority" + +/* deprecated */ +#define NM_IP6_CONFIG_ADDRESSES "addresses" +#define NM_IP6_CONFIG_ROUTES "routes" + +GType nm_ip6_config_get_type(void); + +NMIP6Config *nm_ip6_config_new(struct _NMDedupMultiIndex *multi_idx, int ifindex); +NMIP6Config *nm_ip6_config_new_cloned(const NMIP6Config *src); + +NMIP6Config *nm_ip6_config_clone(const NMIP6Config *self); +int nm_ip6_config_get_ifindex(const NMIP6Config *self); + +struct _NMDedupMultiIndex *nm_ip6_config_get_multi_idx(const NMIP6Config *self); + +NMIP6Config *nm_ip6_config_capture(struct _NMDedupMultiIndex *multi_idx, + NMPlatform * platform, + int ifindex, + NMSettingIP6ConfigPrivacy use_temporary); + +void nm_ip6_config_add_dependent_routes(NMIP6Config *self, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf); + +gboolean nm_ip6_config_commit(const NMIP6Config * self, + NMPlatform * platform, + NMIPRouteTableSyncMode route_table_sync, + GPtrArray ** out_temporary_not_available); +void nm_ip6_config_merge_setting(NMIP6Config * self, + NMSettingIPConfig *setting, + guint32 route_table, + guint32 route_metric); +NMSetting *nm_ip6_config_create_setting(const NMIP6Config *self, gboolean maybe_ipv6_disabled); + +void nm_ip6_config_merge(NMIP6Config * dst, + const NMIP6Config * src, + NMIPConfigMergeFlags merge_flags, + guint32 default_route_metric_penalty); +void nm_ip6_config_subtract(NMIP6Config * dst, + const NMIP6Config *src, + guint32 default_route_metric_penalty); +void nm_ip6_config_intersect(NMIP6Config * dst, + const NMIP6Config *src, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty); +NMIP6Config *nm_ip6_config_intersect_alloc(const NMIP6Config *a, + const NMIP6Config *b, + gboolean intersect_addresses, + gboolean intersect_routes, + guint32 default_route_metric_penalty); +gboolean +nm_ip6_config_replace(NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes); + +const NMPObject *nm_ip6_config_best_default_route_get(const NMIP6Config *self); +const NMPObject *_nm_ip6_config_best_default_route_find(const NMIP6Config *self); + +enum _NMIPConfigFlags; + +void nm_ip6_config_set_config_flags(NMIP6Config * self, + enum _NMIPConfigFlags flags, + enum _NMIPConfigFlags mask); +enum _NMIPConfigFlags nm_ip6_config_get_config_flags(const NMIP6Config *self); + +const NMDedupMultiHeadEntry *nm_ip6_config_lookup_addresses(const NMIP6Config *self); +void nm_ip6_config_reset_addresses(NMIP6Config *self); +void nm_ip6_config_add_address(NMIP6Config *self, const NMPlatformIP6Address *address); +void _nmtst_ip6_config_del_address(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_addresses(const NMIP6Config *self); +const NMPlatformIP6Address *nm_ip6_config_get_first_address(const NMIP6Config *self); +const NMPlatformIP6Address *_nmtst_ip6_config_get_address(const NMIP6Config *self, guint i); +const NMPlatformIP6Address *nm_ip6_config_find_first_address(const NMIP6Config * self, + NMPlatformMatchFlags match_flag); +gboolean nm_ip6_config_address_exists(const NMIP6Config *self, const NMPlatformIP6Address *address); +const NMPlatformIP6Address *nm_ip6_config_lookup_address(const NMIP6Config * self, + const struct in6_addr *addr); +gboolean _nmtst_ip6_config_addresses_sort(NMIP6Config *self); +gboolean nm_ip6_config_has_any_dad_pending(const NMIP6Config *self, const NMIP6Config *candidates); + +const NMDedupMultiHeadEntry *nm_ip6_config_lookup_routes(const NMIP6Config *self); +void nm_ip6_config_reset_routes(NMIP6Config *self); +void nm_ip6_config_add_route(NMIP6Config * self, + const NMPlatformIP6Route *route, + const NMPObject ** out_obj_new); +void _nmtst_ip6_config_del_route(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_routes(const NMIP6Config *self); +const NMPlatformIP6Route * _nmtst_ip6_config_get_route(const NMIP6Config *self, guint i); + +const NMPlatformIP6Route * nm_ip6_config_get_direct_route_for_host(const NMIP6Config * self, + const struct in6_addr *host, + guint32 route_table); +const NMPlatformIP6Address *nm_ip6_config_get_subnet_for_host(const NMIP6Config * self, + const struct in6_addr *host); + +void nm_ip6_config_reset_nameservers(NMIP6Config *self); +void nm_ip6_config_add_nameserver(NMIP6Config *self, const struct in6_addr *nameserver); +void nm_ip6_config_del_nameserver(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_nameservers(const NMIP6Config *self); +const struct in6_addr *nm_ip6_config_get_nameserver(const NMIP6Config *self, guint i); + +void nm_ip6_config_reset_domains(NMIP6Config *self); +void nm_ip6_config_add_domain(NMIP6Config *self, const char *domain); +void nm_ip6_config_del_domain(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_domains(const NMIP6Config *self); +const char *nm_ip6_config_get_domain(const NMIP6Config *self, guint i); + +void nm_ip6_config_reset_searches(NMIP6Config *self); +void nm_ip6_config_add_search(NMIP6Config *self, const char *search); +void nm_ip6_config_del_search(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_searches(const NMIP6Config *self); +const char *nm_ip6_config_get_search(const NMIP6Config *self, guint i); + +void nm_ip6_config_reset_dns_options(NMIP6Config *self); +void nm_ip6_config_add_dns_option(NMIP6Config *self, const char *option); +void nm_ip6_config_del_dns_option(NMIP6Config *self, guint i); +guint nm_ip6_config_get_num_dns_options(const NMIP6Config *self); +const char *nm_ip6_config_get_dns_option(const NMIP6Config *self, guint i); + +void nm_ip6_config_set_dns_priority(NMIP6Config *self, int priority); +int nm_ip6_config_get_dns_priority(const NMIP6Config *self); + +void nm_ip6_config_set_never_default(NMIP6Config *self, gboolean never_default); +gboolean nm_ip6_config_get_never_default(const NMIP6Config *self); + +const NMPObject *nm_ip6_config_nmpobj_lookup(const NMIP6Config *self, const NMPObject *needle); +gboolean nm_ip6_config_nmpobj_remove(NMIP6Config *self, const NMPObject *needle); + +void nm_ip6_config_hash(const NMIP6Config *self, GChecksum *sum, gboolean dns_only); +gboolean nm_ip6_config_equal(const NMIP6Config *a, const NMIP6Config *b); + +void nm_ip6_config_set_privacy(NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy); + +struct _NMNDiscAddress; +void nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self, + const struct _NMNDiscAddress *addresses, + guint addresses_n, + guint8 plen, + guint32 ifa_flags); +struct _NMNDiscRoute; +struct _NMNDiscGateway; +void nm_ip6_config_reset_routes_ndisc(NMIP6Config * self, + const struct _NMNDiscGateway *gateways, + guint gateways_n, + const struct _NMNDiscRoute * routes, + guint routes_n, + guint32 route_table, + guint32 route_metric, + gboolean kernel_support_rta_pref); + +void nm_ip6_config_update_routes_metric(NMIP6Config *self, gint64 metric); + +#endif /* __NETWORKMANAGER_IP6_CONFIG_H__ */ diff --git a/src/core/nm-keep-alive.c b/src/core/nm-keep-alive.c new file mode 100644 index 0000000..d0cce40 --- /dev/null +++ b/src/core/nm-keep-alive.c @@ -0,0 +1,501 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keep-alive.h" + +#include "settings/nm-settings-connection.h" +#include "nm-glib-aux/nm-dbus-aux.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMKeepAlive, PROP_ALIVE, ); + +typedef struct { + GObject *owner; + + NMSettingsConnection *connection; + GDBusConnection * dbus_connection; + char * dbus_client; + + GCancellable *dbus_client_confirm_cancellable; + guint subscription_id; + + bool armed : 1; + bool disarmed : 1; + + bool alive : 1; + bool dbus_client_confirmed : 1; + bool dbus_client_watching : 1; + bool connection_was_visible : 1; +} NMKeepAlivePrivate; + +struct _NMKeepAlive { + GObject parent; + NMKeepAlivePrivate _priv; +}; + +struct _NMKeepAliveClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMKeepAlive, nm_keep_alive, G_TYPE_OBJECT) + +#define NM_KEEP_ALIVE_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMKeepAlive, NM_IS_KEEP_ALIVE) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "keep-alive", __VA_ARGS__) + +/*****************************************************************************/ + +static gboolean _is_alive_dbus_client(NMKeepAlive *self); +static void cleanup_dbus_watch(NMKeepAlive *self); + +/*****************************************************************************/ + +static gboolean +_is_alive(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + nm_assert(!priv->disarmed); + + if (!priv->armed) { + /* before arming, the instance is always alive. */ + return TRUE; + } + + if (priv->dbus_client_watching) { + if (_is_alive_dbus_client(self)) { + /* no matter what, the keep-alive is alive, because there is a D-Bus client + * still around keeping it alive. */ + return TRUE; + } + /* the D-Bus client is gone. The only other binding (below) for the connection's + * visibility cannot keep the instance alive. + * + * As such, a D-Bus client watch is authoritative and overrules other conditions (that + * we have so far). */ + return FALSE; + } + + if (priv->connection && priv->connection_was_visible + && !NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { + /* note that we only declare the keep-alive as dead due to invisible + * connection, if + * (1) we monitor a connection, obviously + * (2) the connection was visible earlier and is no longer. It was + * was invisible all the time, it does not suffice. + */ + return FALSE; + } + + /* by default, the instance is alive. */ + return TRUE; +} + +static void +_notify_alive(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (priv->disarmed) { + /* once disarmed, the alive state is frozen. */ + return; + } + + if (priv->alive == _is_alive(self)) + return; + priv->alive = !priv->alive; + _LOGD("instance is now %s", priv->alive ? "alive" : "dead"); + _notify(self, PROP_ALIVE); +} + +gboolean +nm_keep_alive_is_alive(NMKeepAlive *self) +{ + return NM_KEEP_ALIVE_GET_PRIVATE(self)->alive; +} + +/*****************************************************************************/ + +static void +connection_flags_changed(NMSettingsConnection *connection, NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (!priv->connection_was_visible + && NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { + /* the profile was never visible but now it becomes visible. + * Remember that. + * + * Before this happens (that is, if the device was invisible all along), + * the keep alive instance is considered alive (w.r.t. watching the connection). + * + * The reason is to allow a user to manually activate an invisible profile and keep + * it alive. At least, as long until the user logs out the first time (which is the + * first time, the profiles changes from visible to invisible). + * + * Yes, that is odd. How to improve? */ + priv->connection_was_visible = TRUE; + } + _notify_alive(self); +} + +static void +_set_settings_connection_watch_visible(NMKeepAlive * self, + NMSettingsConnection *connection, + gboolean emit_signal) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + gs_unref_object NMSettingsConnection *old_connection = NULL; + + if (priv->connection == connection) + return; + + if (priv->connection) { + g_signal_handlers_disconnect_by_func(priv->connection, + G_CALLBACK(connection_flags_changed), + self); + old_connection = g_steal_pointer(&priv->connection); + } + + if (connection && !priv->disarmed) { + priv->connection = g_object_ref(connection); + priv->connection_was_visible = + NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE); + g_signal_connect(priv->connection, + NM_SETTINGS_CONNECTION_FLAGS_CHANGED, + G_CALLBACK(connection_flags_changed), + self); + } + + if (emit_signal) + _notify_alive(self); +} + +void +nm_keep_alive_set_settings_connection_watch_visible(NMKeepAlive * self, + NMSettingsConnection *connection) +{ + _set_settings_connection_watch_visible(self, connection, TRUE); +} + +/*****************************************************************************/ + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + NMKeepAlive * self; + NMKeepAlivePrivate *priv; + + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (name_owner && nm_streq(name_owner, priv->dbus_client)) { + /* all good, the name is confirmed. */ + return; + } + + _LOGD("DBus client for keep alive is not on the bus"); + cleanup_dbus_watch(self); + _notify_alive(self); +} + +static gboolean +_is_alive_dbus_client(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (!priv->dbus_client) + return FALSE; + + if (!priv->dbus_client_confirmed) { + /* it's unconfirmed that the D-Bus client is really alive. + * It looks like it is, but as we are claiming that to be + * the case, issue an async GetNameOwner call to make sure. */ + priv->dbus_client_confirmed = TRUE; + priv->dbus_client_confirm_cancellable = g_cancellable_new(); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + priv->dbus_client, + -1, + priv->dbus_client_confirm_cancellable, + get_name_owner_cb, + self); + } + return TRUE; +} + +static void +cleanup_dbus_watch(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (!priv->dbus_client) + return; + + _LOGD("Cleanup DBus client watch"); + + nm_clear_g_cancellable(&priv->dbus_client_confirm_cancellable); + nm_clear_g_free(&priv->dbus_client); + if (priv->dbus_connection) { + g_dbus_connection_signal_unsubscribe(priv->dbus_connection, + nm_steal_int(&priv->subscription_id)); + g_clear_object(&priv->dbus_connection); + } +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMKeepAlive *self = NM_KEEP_ALIVE(user_data); + const char * old_owner; + const char * new_owner; + + g_variant_get(parameters, "(&s&s&s)", NULL, &old_owner, &new_owner); + + if (!nm_streq0(new_owner, "")) + return; + + _LOGD("DBus client for keep alive disappeared from bus"); + cleanup_dbus_watch(self); + _notify_alive(self); +} + +void +nm_keep_alive_set_dbus_client_watch(NMKeepAlive * self, + GDBusConnection *connection, + const char * client_address) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (priv->disarmed) + return; + + cleanup_dbus_watch(self); + + if (client_address) { + _LOGD("Registering dbus client watch for keep alive"); + + priv->dbus_client = g_strdup(client_address); + priv->dbus_client_watching = TRUE; + priv->dbus_client_confirmed = FALSE; + priv->dbus_connection = g_object_ref(connection); + priv->subscription_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + priv->dbus_client, + name_owner_changed_cb, + self, + NULL); + } else + priv->dbus_client_watching = FALSE; + + _notify_alive(self); +} + +/*****************************************************************************/ + +/** + * nm_keep_alive_arm: + * @self: the #NMKeepAlive + * + * A #NMKeepAlive instance is unarmed by default. That means, it's + * alive and stays alive until being armed. Arming means, that the conditions + * start to be actively evaluated, that the alive state may change, and + * that property changed signals are emitted. + * + * The opposite is nm_keep_alive_disarm() which freezes the alive state + * for good. Once disarmed, the instance cannot be armed again. Arming an + * instance multiple times has no effect. Arming an already disarmed instance + * also has no effect. */ +void +nm_keep_alive_arm(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + if (!priv->armed) { + priv->armed = TRUE; + _notify_alive(self); + } +} + +/** + * nm_keep_alive_disarm: + * @self: the #NMKeepAlive instance + * + * Once the instance is disarmed, it will not change its alive state + * anymore and will not emit anymore property changed signals about + * alive state changed. + * + * As such, it will also free internal resources (since they no longer + * affect the externally visible state). + * + * Once disarmed, the instance is frozen and cannot change anymore. + */ +void +nm_keep_alive_disarm(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + priv->disarmed = TRUE; + + /* release internal data. */ + _set_settings_connection_watch_visible(self, NULL, FALSE); + cleanup_dbus_watch(self); +} + +/** + * nm_keep_alive_destroy: + * @self: (allow-none): the #NMKeepAlive instance to destroy. + * + * This does 3 things in one: + * + * - set owner to %NULL + * - disarm the instance. + * - unref @self. + */ +void +nm_keep_alive_destroy(NMKeepAlive *self) +{ + if (!self) + return; + _nm_keep_alive_set_owner(self, NULL); + nm_keep_alive_disarm(self); + g_object_unref(self); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMKeepAlive *self = NM_KEEP_ALIVE(object); + + switch (prop_id) { + case PROP_ALIVE: + g_value_set_boolean(value, nm_keep_alive_is_alive(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +/** + * nm_keep_alive_get_owner: + * @self: the #NMKeepAlive + * + * Returns: the owner instance associated with this @self. This commonly + * is set to be the target instance, which @self guards for being alive. + * Returns a gpointer, but of course it's some GObject instance. */ +gpointer /* GObject * */ +nm_keep_alive_get_owner(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + nm_assert(!priv->owner || G_IS_OBJECT(priv->owner)); + + return priv->owner; +} + +/** + * _nm_keep_alive_set_owner: + * @self: the #NMKeepAlive + * @owner: the owner to set or unset. + * + * Sets or unsets the owner instance. Think of the owner the target + * instance that is guarded by @self. It's the responsibility of the + * owner to set and properly unset this pointer. As the owner also + * controls the lifetime of the NMKeepAlive instance. + * + * This API is not to be called by everybody, but only the owner of + * @self. + */ +void +_nm_keep_alive_set_owner(NMKeepAlive *self, GObject *owner) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + nm_assert(!owner || G_IS_OBJECT(owner)); + + /* it's bad style to reset the owner object. You are supposed to + * set it once, and clear it once. That's it. */ + nm_assert(!owner || !priv->owner); + + /* optimally, we would take a reference to @owner. But the + * owner already owns a reference to the keep-alive, so we cannot + * just own a reference back. + * + * We could register a weak-pointer here. But instead, declare that + * owner is required to set itself as owner when creating the + * keep-alive instance, and unset itself when it lets go of the + * keep-alive instance (at latest, when the owner itself gets destroyed). + */ + priv->owner = owner; +} + +/*****************************************************************************/ + +static void +nm_keep_alive_init(NMKeepAlive *self) +{ + NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); + + priv->alive = TRUE; + + nm_assert(priv->alive == _is_alive(self)); +} + +NMKeepAlive * +nm_keep_alive_new(void) +{ + return g_object_new(NM_TYPE_KEEP_ALIVE, NULL); +} + +static void +dispose(GObject *object) +{ + NMKeepAlive *self = NM_KEEP_ALIVE(object); + + nm_assert(!NM_KEEP_ALIVE_GET_PRIVATE(self)->owner); + + /* disarm also happens to free all resources. */ + nm_keep_alive_disarm(self); +} + +static void +nm_keep_alive_class_init(NMKeepAliveClass *keep_alive_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(keep_alive_class); + + object_class->get_property = get_property; + object_class->dispose = dispose; + + obj_properties[PROP_ALIVE] = g_param_spec_string(NM_KEEP_ALIVE_ALIVE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-keep-alive.h b/src/core/nm-keep-alive.h new file mode 100644 index 0000000..2c2b4fa --- /dev/null +++ b/src/core/nm-keep-alive.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_KEEP_ALIVE_H__ +#define __NETWORKMANAGER_KEEP_ALIVE_H__ + +#define NM_TYPE_KEEP_ALIVE (nm_keep_alive_get_type()) +#define NM_KEEP_ALIVE(o) (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_KEEP_ALIVE, NMKeepAlive)) +#define NM_KEEP_ALIVE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_KEEP_ALIVE, NMKeepAliveClass)) +#define NM_KEEP_ALIVE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_KEEP_ALIVE, NMKeepAliveClass)) +#define NM_IS_KEEP_ALIVE(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_KEEP_ALIVE)) +#define NM_IS_KEEP_ALIVE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), NM_TYPE_KEEP_ALIVE)) + +#define NM_KEEP_ALIVE_ALIVE "alive" + +typedef struct _NMKeepAliveClass NMKeepAliveClass; + +GType nm_keep_alive_get_type(void) G_GNUC_CONST; + +NMKeepAlive *nm_keep_alive_new(void); + +gboolean nm_keep_alive_is_alive(NMKeepAlive *self); + +void nm_keep_alive_arm(NMKeepAlive *self); +void nm_keep_alive_disarm(NMKeepAlive *self); + +void nm_keep_alive_destroy(NMKeepAlive *self); + +void nm_keep_alive_set_settings_connection_watch_visible(NMKeepAlive * self, + NMSettingsConnection *connection); + +void nm_keep_alive_set_dbus_client_watch(NMKeepAlive * self, + GDBusConnection *connection, + const char * client_address); + +gpointer /* GObject * */ nm_keep_alive_get_owner(NMKeepAlive *self); + +/* _nm_keep_alive_set_owner() is reserved for the owner to set/unset itself. */ +void _nm_keep_alive_set_owner(NMKeepAlive *self, GObject *owner); + +#endif /* __NETWORKMANAGER_KEEP_ALIVE_H__ */ diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c new file mode 100644 index 0000000..0f689d4 --- /dev/null +++ b/src/core/nm-l3-config-data.c @@ -0,0 +1,2712 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include "nm-l3-config-data.h" + +#include +#include +#include + +#include "nm-glib-aux/nm-enum-utils.h" +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "platform/nmp-object.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +typedef struct { + NMDedupMultiIdxType parent; + NMPObjectType obj_type; +} DedupMultiIdxType; + +struct _NML3ConfigData { + NMDedupMultiIndex *multi_idx; + + union { + struct { + DedupMultiIdxType idx_addresses_6; + DedupMultiIdxType idx_addresses_4; + }; + DedupMultiIdxType idx_addresses_x[2]; + }; + + union { + struct { + DedupMultiIdxType idx_routes_6; + DedupMultiIdxType idx_routes_4; + }; + DedupMultiIdxType idx_routes_x[2]; + }; + + union { + struct { + const NMPObject *best_default_route_6; + const NMPObject *best_default_route_4; + }; + const NMPObject *best_default_route_x[2]; + }; + + GArray *wins; + GArray *nis_servers; + + char *nis_domain; + + union { + struct { + NMDhcpLease *dhcp_lease_6; + NMDhcpLease *dhcp_lease_4; + }; + NMDhcpLease *dhcp_lease_x[2]; + }; + + union { + struct { + GArray *nameservers_6; + GArray *nameservers_4; + }; + GArray *nameservers_x[2]; + }; + + union { + struct { + GPtrArray *domains_6; + GPtrArray *domains_4; + }; + GPtrArray *domains_x[2]; + }; + + union { + struct { + GPtrArray *searches_6; + GPtrArray *searches_4; + }; + GPtrArray *searches_x[2]; + }; + + union { + struct { + GPtrArray *dns_options_6; + GPtrArray *dns_options_4; + }; + GPtrArray *dns_options_x[2]; + }; + + int ifindex; + + int ref_count; + + union { + struct { + int dns_priority_6; + int dns_priority_4; + }; + int dns_priority_x[2]; + }; + + union { + struct { + NMIPRouteTableSyncMode route_table_sync_6; + NMIPRouteTableSyncMode route_table_sync_4; + }; + NMIPRouteTableSyncMode route_table_sync_x[2]; + }; + + NMSettingConnectionMdns mdns; + NMSettingConnectionLlmnr llmnr; + + NML3ConfigDatFlags flags; + + NMIPConfigSource source; + + int ndisc_hop_limit_val; + + guint32 mtu; + guint32 ip6_mtu; + guint32 ndisc_reachable_time_msec_val; + guint32 ndisc_retrans_timer_msec_val; + + NMTernary metered : 3; + + NMSettingIP6ConfigPrivacy ip6_privacy : 4; + + bool is_sealed : 1; + + bool has_routes_with_type_local_4_set : 1; + bool has_routes_with_type_local_6_set : 1; + bool has_routes_with_type_local_4_val : 1; + bool has_routes_with_type_local_6_val : 1; + + bool ndisc_hop_limit_set : 1; + bool ndisc_reachable_time_msec_set : 1; + bool ndisc_retrans_timer_msec_set : 1; +}; + +/*****************************************************************************/ + +static GArray * +_garray_inaddr_ensure(GArray **p_arr, int addr_family) +{ + nm_assert(p_arr); + nm_assert_addr_family(addr_family); + + if (G_UNLIKELY(!*p_arr)) { + *p_arr = g_array_new(FALSE, FALSE, nm_utils_addr_family_to_size(addr_family)); + } + return *p_arr; +} + +static GArray * +_garray_inaddr_clone(const GArray *src, int addr_family) +{ + const gsize elt_size = nm_utils_addr_family_to_size(addr_family); + GArray * dst; + + nm_assert_addr_family(addr_family); + + if (!src || src->len == 0) + return NULL; + + dst = g_array_sized_new(FALSE, FALSE, elt_size, src->len); + g_array_set_size(dst, src->len); + memcpy(dst->data, src->data, src->len * elt_size); + return dst; +} + +static void +_garray_inaddr_merge(GArray **p_dst, const GArray *src, int addr_family) +{ + guint dst_initial_len; + const char *p_dst_arr; + const char *p_src; + gsize elt_size; + guint i; + guint j; + + if (nm_g_array_len(src) == 0) + return; + + if (!*p_dst) { + *p_dst = _garray_inaddr_clone(src, addr_family); + return; + } + + elt_size = nm_utils_addr_family_to_size(addr_family); + + dst_initial_len = (*p_dst)->len; + p_dst_arr = (*p_dst)->data; + p_src = src->data; + + for (i = 0; i < src->len; i++, p_src += elt_size) { + for (j = 0; j < dst_initial_len; j++) { + if (memcmp(&p_dst_arr[j * elt_size], p_src, elt_size) == 0) + goto next; + } + g_array_append_vals(*p_dst, p_src, 1); + p_dst_arr = (*p_dst)->data; +next:; + } +} + +static gssize +_garray_inaddr_find(GArray * arr, + int addr_family, + gconstpointer needle, + /* (const NMIPAddr **) */ gpointer out_addr) +{ + guint i; + + nm_assert_addr_family(addr_family); + nm_assert(needle); + + if (arr) { + const gsize elt_size = nm_utils_addr_family_to_size(addr_family); + const char *p; + + p = arr->data; + for (i = 0; i < arr->len; i++, p += elt_size) { + if (memcmp(p, needle, elt_size) == 0) { + NM_SET_OUT((gconstpointer *) out_addr, p); + return i; + } + } + } + NM_SET_OUT((gconstpointer *) out_addr, NULL); + return -1; +} + +static gconstpointer +_garray_inaddr_get(GArray *arr, guint *out_len) +{ + nm_assert(out_len); + + if (!arr) { + *out_len = 0; + return NULL; + } + *out_len = arr->len; + return arr->data; +} + +static gconstpointer +_garray_inaddr_at(GArray *arr, guint idx, gboolean IS_IPv4) +{ + nm_assert(arr); + nm_assert(idx < arr->len); + + if (IS_IPv4) + return &g_array_index(arr, in_addr_t, idx); + return &g_array_index(arr, struct in6_addr, idx); +} + +static gboolean +_garray_inaddr_add(GArray **p_arr, int addr_family, gconstpointer addr) +{ + nm_assert(p_arr); + nm_assert_addr_family(addr_family); + nm_assert(addr); + + if (!*p_arr) + _garray_inaddr_ensure(p_arr, addr_family); + else { + if (_garray_inaddr_find(*p_arr, addr_family, addr, NULL) >= 0) + return FALSE; + } + + g_array_append_vals(*p_arr, addr, 1); + return TRUE; +} + +static int +_garray_inaddr_cmp(const GArray *a, const GArray *b, int addr_family) +{ + guint l; + + l = nm_g_array_len(a); + NM_CMP_DIRECT(l, nm_g_array_len(b)); + + if (l > 0) + NM_CMP_DIRECT_MEMCMP(a->data, b->data, l * nm_utils_addr_family_to_size(addr_family)); + + return 0; +} + +static void +_strv_ptrarray_merge(GPtrArray **p_dst, const GPtrArray *src) +{ + guint dst_initial_len; + guint i; + + if (nm_g_ptr_array_len(src) == 0) + return; + + if (!*p_dst) { + /* we trust src to contain unique strings. Just clone it. */ + *p_dst = nm_strv_ptrarray_clone(src, TRUE); + return; + } + + nm_strv_ptrarray_ensure(p_dst); + + dst_initial_len = (*p_dst)->len; + + for (i = 0; i < src->len; i++) { + const char *s = src->pdata[i]; + + if (dst_initial_len > 0 + && nm_utils_strv_find_first((char **) ((*p_dst)->pdata), dst_initial_len, s) >= 0) + continue; + + g_ptr_array_add(*p_dst, g_strdup(s)); + } +} + +/*****************************************************************************/ + +void +nm_l3_config_data_log(const NML3ConfigData *self, + const char * title, + const char * prefix, + NMLogLevel log_level, + NMLogDomain log_domain) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + int IS_IPv4; + guint i; + + if (!nm_logging_enabled(log_level, log_domain)) + return; + +#define _L(...) \ + _nm_log(log_level, \ + log_domain, \ + 0, \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)) + + if (!prefix) + prefix = ""; + + if (!self) { + _L("l3cd %s%s%s(NULL)", NM_PRINT_FMT_QUOTED(title, "\"", title, "\" ", "")); + return; + } + + nm_assert(!NM_FLAGS_ANY(self->flags, + ~(NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES + | NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_4 + | NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_6))); + + _L("l3cd %s%s%s(" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d%s%s%s%s)", + NM_PRINT_FMT_QUOTED(title, "\"", title, "\" ", ""), + NM_HASH_OBFUSCATE_PTR(self), + self->ifindex, + NM_PRINT_FMT_QUOTED2(self->source != NM_IP_CONFIG_SOURCE_UNKNOWN, + ", source=", + nmp_utils_ip_config_source_to_string(self->source, sbuf, sizeof(sbuf)), + ""), + NM_FLAGS_HAS(self->flags, NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES) + ? ", merge-no-default-routes" + : "", + !self->is_sealed ? ", not-sealed" : ""); + + if (self->mtu != 0 || self->ip6_mtu != 0) { + _L("mtu: %u, ip6-mtu: %u", self->mtu, self->ip6_mtu); + } + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + NMDedupMultiIter iter; + const NMPObject *obj; + + i = 0; + nm_l3_config_data_iter_obj_for_each (&iter, + self, + &obj, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { + _L("address%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + i++; + } + + if (!IS_IPv4) { + if (self->ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) { + gs_free char *s = NULL; + + _L("ip6-privacy: %s", + (s = _nm_utils_enum_to_str_full(nm_setting_ip6_config_privacy_get_type(), + self->ip6_privacy, + " ", + NULL))); + } + } + + i = 0; + nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + _L("route%c[%u]: %s%s", + nm_utils_addr_family_to_char(addr_family), + i, + self->best_default_route_x[IS_IPv4] == obj ? "[DEFAULT] " : "", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + i++; + } + + if (self->route_table_sync_x[IS_IPv4] != NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) { + _L("route-table-sync-mode%c: %d", + nm_utils_addr_family_to_char(addr_family), + (int) self->route_table_sync_x[IS_IPv4]); + } + + if (!IS_IPv4) { + if (self->ndisc_hop_limit_set || self->ndisc_reachable_time_msec_set + || self->ndisc_retrans_timer_msec_set) { + gsize l = sizeof(sbuf); + char * p = sbuf; + const char *s_prefix = "ndisc: "; + + if (self->ndisc_hop_limit_set) { + nm_utils_strbuf_append(&p, + &l, + "%shop-limit=%d", + s_prefix, + self->ndisc_hop_limit_val); + s_prefix = ", "; + } + if (self->ndisc_reachable_time_msec_set) { + nm_utils_strbuf_append(&p, + &l, + "%sreachable-time-msec=%u", + s_prefix, + self->ndisc_reachable_time_msec_val); + s_prefix = ", "; + } + if (self->ndisc_retrans_timer_msec_set) { + nm_utils_strbuf_append(&p, + &l, + "%sretrans-timer-msec=%u", + s_prefix, + self->ndisc_retrans_timer_msec_val); + s_prefix = ", "; + } + _L("%s", sbuf); + } + } + + if (NM_FLAGS_ANY(self->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4))) { + _L("dns-priority%c: %d", + nm_utils_addr_family_to_char(addr_family), + self->dns_priority_x[IS_IPv4]); + } + + for (i = 0; i < nm_g_array_len(self->nameservers_x[IS_IPv4]); i++) { + _L("nameserver%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + nm_utils_inet_ntop(addr_family, + _garray_inaddr_at(self->nameservers_x[IS_IPv4], IS_IPv4, i), + sbuf_addr)); + } + + for (i = 0; i < nm_g_ptr_array_len(self->domains_x[IS_IPv4]); i++) { + _L("domain%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + (const char *) self->domains_x[IS_IPv4]->pdata[i]); + } + + for (i = 0; i < nm_g_ptr_array_len(self->searches_x[IS_IPv4]); i++) { + _L("search%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + (const char *) self->searches_x[IS_IPv4]->pdata[i]); + } + + for (i = 0; i < nm_g_ptr_array_len(self->dns_options_x[IS_IPv4]); i++) { + _L("dns_option%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + (const char *) self->dns_options_x[IS_IPv4]->pdata[i]); + } + + if (IS_IPv4) { + for (i = 0; i < nm_g_array_len(self->wins); i++) { + _L("wins[%u]: %s", + i, + _nm_utils_inet4_ntop(g_array_index(self->wins, in_addr_t, i), sbuf_addr)); + } + for (i = 0; i < nm_g_array_len(self->nis_servers); i++) { + _L("nis-server[%u]: %s", + i, + _nm_utils_inet4_ntop(g_array_index(self->nis_servers, in_addr_t, i), sbuf_addr)); + } + if (self->nis_domain) + _L("nis-domain: %s", self->nis_domain); + } + + if (self->dhcp_lease_x[IS_IPv4]) { + gs_free NMUtilsNamedValue *options_free = NULL; + NMUtilsNamedValue options_buffer[30]; + NMUtilsNamedValue * options; + guint options_len; + + options = nm_utils_named_values_from_strdict( + nm_dhcp_lease_get_options(self->dhcp_lease_x[IS_IPv4]), + &options_len, + options_buffer, + &options_free); + if (options_len == 0) { + _L("dhcp-lease%c (%u options)", + nm_utils_addr_family_to_char(addr_family), + options_len); + } + for (i = 0; i < options_len; i++) { + _L("dhcp-lease%c[%u]: \"%s\" => \"%s\"", + nm_utils_addr_family_to_char(addr_family), + i, + options[i].name, + options[i].value_str); + } + } + } + + if (self->mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) { + gs_free char *s = NULL; + + _L("mdns: %s", + (s = _nm_utils_enum_to_str_full(nm_setting_connection_mdns_get_type(), + self->mdns, + " ", + NULL))); + } + + if (self->llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) { + gs_free char *s = NULL; + + _L("llmnr: %s", + (s = _nm_utils_enum_to_str_full(nm_setting_connection_llmnr_get_type(), + self->llmnr, + " ", + NULL))); + } + + if (self->metered != NM_TERNARY_DEFAULT) { + _L("metered: %s", self->metered ? "yes" : "no"); + } + +#undef _L +} + +/*****************************************************************************/ + +static gboolean +_route_valid_4(const NMPlatformIP4Route *r) +{ + return r && r->plen <= 32 + && r->network == nm_utils_ip4_address_clear_host_address(r->network, r->plen); +} + +static gboolean +_route_valid_6(const NMPlatformIP6Route *r) +{ + struct in6_addr n; + + return r && r->plen <= 128 + && (memcmp(&r->network, + nm_utils_ip6_address_clear_host_address(&n, &r->network, r->plen), + sizeof(n)) + == 0); +} + +static gboolean +_route_valid(int addr_family, gconstpointer r) +{ + return NM_IS_IPv4(addr_family) ? _route_valid_4(r) : _route_valid_6(r); +} + +static gboolean +_NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self, gboolean allow_sealed) +{ + nm_assert(!self || (self->ifindex > 0 && self->multi_idx && self->ref_count > 0)); + return self && self->ref_count > 0 && (allow_sealed || !self->is_sealed); +} + +static void +_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + nmp_object_id_hash_update((NMPObject *) obj, h); +} + +static gboolean +_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + return nmp_object_id_equal((NMPObject *) obj_a, (NMPObject *) obj_b); +} + +static void +_idx_type_init(DedupMultiIdxType *idx_type, NMPObjectType obj_type) +{ + static const NMDedupMultiIdxTypeClass idx_type_class = { + .idx_obj_id_hash_update = _idx_obj_id_hash_update, + .idx_obj_id_equal = _idx_obj_id_equal, + }; + + nm_dedup_multi_idx_type_init(&idx_type->parent, &idx_type_class); + idx_type->obj_type = obj_type; +} + +NML3ConfigData * +nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex) +{ + NML3ConfigData *self; + + nm_assert(multi_idx); + nm_assert(ifindex > 0); + + self = g_slice_new(NML3ConfigData); + *self = (NML3ConfigData){ + .ref_count = 1, + .ifindex = ifindex, + .multi_idx = nm_dedup_multi_index_ref(multi_idx), + .mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT, + .llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT, + .flags = NM_L3_CONFIG_DAT_FLAGS_NONE, + .metered = NM_TERNARY_DEFAULT, + .route_table_sync_4 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, + .route_table_sync_6 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, + .source = NM_IP_CONFIG_SOURCE_UNKNOWN, + .ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + .ndisc_hop_limit_set = FALSE, + .ndisc_reachable_time_msec_set = FALSE, + .ndisc_retrans_timer_msec_set = FALSE, + }; + + _idx_type_init(&self->idx_addresses_4, NMP_OBJECT_TYPE_IP4_ADDRESS); + _idx_type_init(&self->idx_addresses_6, NMP_OBJECT_TYPE_IP6_ADDRESS); + _idx_type_init(&self->idx_routes_4, NMP_OBJECT_TYPE_IP4_ROUTE); + _idx_type_init(&self->idx_routes_6, NMP_OBJECT_TYPE_IP6_ROUTE); + + return self; +} + +const NML3ConfigData * +nm_l3_config_data_ref(const NML3ConfigData *self) +{ + if (self) { + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + ((NML3ConfigData *) self)->ref_count++; + } + return self; +} + +const NML3ConfigData * +nm_l3_config_data_ref_and_seal(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + ((NML3ConfigData *) self)->is_sealed = TRUE; + ((NML3ConfigData *) self)->ref_count++; + return self; +} + +const NML3ConfigData * +nm_l3_config_data_seal(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + ((NML3ConfigData *) self)->is_sealed = TRUE; + return self; +} + +gboolean +nm_l3_config_data_is_sealed(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + return self->is_sealed; +} + +void +nm_l3_config_data_unref(const NML3ConfigData *self) +{ + NML3ConfigData *mutable; + + if (!self) + return; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + /* NML3ConfigData aims to be an immutable, ref-counted type. The mode of operation + * is to create/initialize the instance once, then seal it and pass around the reference. + * + * That means, also ref/unref operate on const pointers (otherwise, you'd have to cast all + * the time). Hence, we cast away the constness during ref/unref/seal operations. */ + + mutable = (NML3ConfigData *) self; + + if (--mutable->ref_count > 0) + return; + + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_addresses_4.parent); + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_addresses_6.parent); + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_routes_4.parent); + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_routes_6.parent); + + nmp_object_unref(mutable->best_default_route_4); + nmp_object_unref(mutable->best_default_route_6); + + nm_clear_pointer(&mutable->wins, g_array_unref); + nm_clear_pointer(&mutable->nis_servers, g_array_unref); + + nm_clear_pointer(&mutable->dhcp_lease_4, nm_dhcp_lease_unref); + nm_clear_pointer(&mutable->dhcp_lease_6, nm_dhcp_lease_unref); + + nm_clear_pointer(&mutable->nameservers_4, g_array_unref); + nm_clear_pointer(&mutable->nameservers_6, g_array_unref); + + nm_clear_pointer(&mutable->domains_4, g_ptr_array_unref); + nm_clear_pointer(&mutable->domains_6, g_ptr_array_unref); + + nm_clear_pointer(&mutable->searches_4, g_ptr_array_unref); + nm_clear_pointer(&mutable->searches_6, g_ptr_array_unref); + + nm_clear_pointer(&mutable->dns_options_4, g_ptr_array_unref); + nm_clear_pointer(&mutable->dns_options_6, g_ptr_array_unref); + + nm_dedup_multi_index_unref(mutable->multi_idx); + + g_free(mutable->nis_domain); + + nm_g_slice_free(mutable); +} + +/*****************************************************************************/ + +static const NMDedupMultiEntry * +_lookup_route(const NMDedupMultiIndex *multi_idx, + const DedupMultiIdxType *idx_type, + const NMPObject * needle) +{ + const NMDedupMultiEntry *entry; + + nm_assert(multi_idx); + nm_assert(idx_type); + nm_assert(NM_IN_SET(idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(NMP_OBJECT_GET_TYPE(needle) == idx_type->obj_type); + + entry = nm_dedup_multi_index_lookup_obj(multi_idx, &idx_type->parent, needle); + nm_assert(!entry + || (NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP4_ROUTE + && nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP4_ROUTE(needle), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) + == 0) + || (NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP6_ROUTE + && nm_platform_ip6_route_cmp(NMP_OBJECT_CAST_IP6_ROUTE(entry->obj), + NMP_OBJECT_CAST_IP6_ROUTE(needle), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) + == 0)); + + return entry; +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_route_obj(const NML3ConfigData *self, const NMPObject *needle) +{ + gboolean IS_IPv4; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(needle), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + + IS_IPv4 = NM_IS_IPv4(NMP_OBJECT_GET_ADDR_FAMILY(needle)); + return _lookup_route(self->multi_idx, &self->idx_routes_x[IS_IPv4], needle); +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_route(const NML3ConfigData * self, + int addr_family, + const NMPlatformIPRoute *needle) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + NMPObject obj_stack; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + nm_assert(needle); + + return _lookup_route( + self->multi_idx, + &self->idx_routes_x[IS_IPv4], + nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4), needle)); +} + +const NMDedupMultiIdxType * +nm_l3_config_data_lookup_index(const NML3ConfigData *self, NMPObjectType obj_type) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + return &self->idx_addresses_4.parent; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return &self->idx_addresses_6.parent; + case NMP_OBJECT_TYPE_IP4_ROUTE: + return &self->idx_routes_4.parent; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return &self->idx_routes_6.parent; + default: + return nm_assert_unreachable_val(NULL); + } +} + +const NMDedupMultiHeadEntry * +nm_l3_config_data_lookup_objs(const NML3ConfigData *self, NMPObjectType obj_type) +{ + if (!self) + return NULL; + return nm_dedup_multi_index_lookup_head(self->multi_idx, + nm_l3_config_data_lookup_index(self, obj_type), + NULL); +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_obj(const NML3ConfigData *self, const NMPObject *obj) +{ + const NMDedupMultiIdxType *idx; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + idx = nm_l3_config_data_lookup_index(self, NMP_OBJECT_GET_TYPE(obj)); + + return nm_dedup_multi_index_lookup_obj(self->multi_idx, idx, obj); +} + +const NMPlatformIP6Address * +nm_l3_config_data_lookup_address_6(const NML3ConfigData *self, const struct in6_addr *addr) +{ + const NMDedupMultiEntry *head; + NMPObject obj_stack; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + /* this works only, because the primary key for a Ipv6 address is the + * ifindex and the "struct in6_addr". */ + nmp_object_stackinit_id_ip6_address(&obj_stack, self->ifindex, addr); + + head = nm_l3_config_data_lookup_obj(self, &obj_stack); + if (!head) + return NULL; + + return NMP_OBJECT_CAST_IP6_ADDRESS(head->obj); +} + +const NMPObject * +nmtst_l3_config_data_get_obj_at(const NML3ConfigData *self, NMPObjectType obj_type, guint i) +{ + NMDedupMultiIter iter; + guint j; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + j = 0; + nm_dedup_multi_iter_init(&iter, nm_l3_config_data_lookup_objs(self, obj_type)); + while (nm_dedup_multi_iter_next(&iter)) { + nm_assert(iter.current); + nm_assert(NMP_OBJECT_GET_TYPE(iter.current->obj) == obj_type); + if (i == j) + return iter.current->obj; + j++; + } + + g_return_val_if_reached(NULL); +} + +/*****************************************************************************/ + +gboolean +nm_l3_config_data_has_routes_with_type_local(const NML3ConfigData *self, int addr_family) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + NML3ConfigData * self_mutable; + NMDedupMultiIter iter; + const NMPObject *obj; + gboolean val; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + + if (IS_IPv4) { + if (G_LIKELY(self->has_routes_with_type_local_4_set)) + return self->has_routes_with_type_local_4_val; + } else { + if (G_LIKELY(self->has_routes_with_type_local_6_set)) + return self->has_routes_with_type_local_6_val; + } + + val = FALSE; + nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + if (NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced + == nm_platform_route_type_coerce(RTN_LOCAL)) { + val = TRUE; + break; + } + } + + /* the value gets accumulated and cached. Doing that is also permissible to a + * const/sealed instance. Hence, we cast the const-ness away. */ + self_mutable = (NML3ConfigData *) self; + if (IS_IPv4) { + self_mutable->has_routes_with_type_local_4_set = TRUE; + self_mutable->has_routes_with_type_local_4_val = val; + } else { + self_mutable->has_routes_with_type_local_6_set = TRUE; + self_mutable->has_routes_with_type_local_6_val = val; + } + + return val; +} + +/*****************************************************************************/ + +NMDedupMultiIndex * +nm_l3_config_data_get_multi_idx(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->multi_idx; +} + +int +nm_l3_config_data_get_ifindex(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->ifindex; +} + +/*****************************************************************************/ + +NML3ConfigDatFlags +nm_l3_config_data_get_flags(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->flags; +} + +void +nm_l3_config_data_set_flags_full(NML3ConfigData * self, + NML3ConfigDatFlags flags, + NML3ConfigDatFlags mask) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert(!NM_FLAGS_ANY(flags, ~mask)); + nm_assert(!NM_FLAGS_ANY(mask, ~NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)); + + self->flags = (self->flags & ~mask) | (flags & mask); +} + +/*****************************************************************************/ + +const NMPObject * +nm_l3_config_data_get_first_obj(const NML3ConfigData *self, + NMPObjectType obj_type, + gboolean (*predicate)(const NMPObject *obj)) +{ + NMDedupMultiIter iter; + const NMPObject *obj; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, obj_type) { + if (!predicate || predicate(obj)) + return obj; + } + return NULL; +} + +/*****************************************************************************/ + +static gboolean +_l3_config_data_add_obj(NMDedupMultiIndex * multi_idx, + DedupMultiIdxType * idx_type, + int ifindex, + const NMPObject * obj_new, + const NMPlatformObject *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject ** out_obj_old /* returns a reference! */, + const NMPObject ** out_obj_new /* does not return a reference */) +{ + NMPObject obj_new_stackinit; + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new; + + nm_assert(nm_utils_is_power_of_two_or_zero( + add_flags & (NM_L3_CONFIG_ADD_FLAGS_MERGE | NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE))); + nm_assert(multi_idx); + nm_assert(idx_type); + nm_assert(ifindex > 0); + nm_assert(NM_IN_SET(idx_type->obj_type, + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP6_ROUTE)); + /* we go through extra lengths to accept a full obj_new object. That one, + * can be reused by increasing the ref-count. */ + if (!obj_new) { + nm_assert(pl_new); + obj_new = nmp_object_stackinit(&obj_new_stackinit, idx_type->obj_type, pl_new); + NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(&obj_new_stackinit)->ifindex = ifindex; + } else { + nm_assert(!pl_new); + nm_assert(NMP_OBJECT_GET_TYPE(obj_new) == idx_type->obj_type); + if (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_new)->ifindex != ifindex) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(&obj_new_stackinit)->ifindex = ifindex; + } + } + nm_assert(NMP_OBJECT_GET_TYPE(obj_new) == idx_type->obj_type); + nm_assert(nmp_object_is_alive(obj_new)); + + entry_old = nm_dedup_multi_index_lookup_obj(multi_idx, &idx_type->parent, obj_new); + + if (entry_old) { + gboolean modified = FALSE; + const NMPObject *obj_old = entry_old->obj; + + if (NM_FLAGS_HAS(add_flags, NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE)) { + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto append_force_and_out; + } + + if (nmp_object_equal(obj_new, obj_old)) { + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto append_force_and_out; + } + + if (NM_FLAGS_HAS(add_flags, NM_L3_CONFIG_ADD_FLAGS_MERGE)) { + switch (idx_type->obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + /* for addresses that we read from the kernel, we keep the timestamps as defined + * by the previous source (item_old). The reason is, that the other source configured the lifetimes + * with "what should be" and the kernel values are "what turned out after configuring it". + * + * For other sources, the longer lifetime wins. */ + if ((obj_new->ip_address.addr_source == NM_IP_CONFIG_SOURCE_KERNEL + && obj_old->ip_address.addr_source != NM_IP_CONFIG_SOURCE_KERNEL) + || nm_platform_ip_address_cmp_expiry(NMP_OBJECT_CAST_IP_ADDRESS(obj_old), + NMP_OBJECT_CAST_IP_ADDRESS(obj_new)) + > 0) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_address.timestamp = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->timestamp; + obj_new_stackinit.ip_address.lifetime = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->lifetime; + obj_new_stackinit.ip_address.preferred = + NMP_OBJECT_CAST_IP_ADDRESS(obj_old)->preferred; + modified = TRUE; + } + + /* keep the maximum addr_source. */ + if (obj_new->ip_address.addr_source < obj_old->ip_address.addr_source) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source; + modified = TRUE; + } + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + /* keep the maximum rt_source. */ + if (obj_new->ip_route.rt_source < obj_old->ip_route.rt_source) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source; + modified = TRUE; + } + break; + default: + nm_assert_not_reached(); + break; + } + + if (modified && nmp_object_equal(obj_new, obj_old)) { + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto append_force_and_out; + } + } + } + + if (!nm_dedup_multi_index_add_full(multi_idx, + &idx_type->parent, + obj_new, + NM_FLAGS_HAS(add_flags, NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE) + ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE + : NM_DEDUP_MULTI_IDX_MODE_APPEND, + NULL, + entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, + NULL, + &entry_new, + out_obj_old)) { + nm_assert_not_reached(); + NM_SET_OUT(out_obj_new, NULL); + return FALSE; + } + + NM_SET_OUT(out_obj_new, entry_new->obj); + return TRUE; + +append_force_and_out: + NM_SET_OUT(out_obj_old, nmp_object_ref(entry_old->obj)); + NM_SET_OUT(out_obj_new, entry_old->obj); + if (NM_FLAGS_HAS(add_flags, NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE)) { + if (nm_dedup_multi_entry_reorder(entry_old, NULL, TRUE)) + return TRUE; + } + return FALSE; +} + +static const NMPObject * +_l3_config_best_default_route_find_better(const NMPObject *obj_cur, const NMPObject *obj_cmp) +{ + nm_assert(!obj_cur + || NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_cur), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(!obj_cmp + || (!obj_cur + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_cmp), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)) + || NMP_OBJECT_GET_TYPE(obj_cur) == NMP_OBJECT_GET_TYPE(obj_cmp)); + nm_assert(!obj_cur || nmp_object_ip_route_is_best_defaut_route(obj_cur)); + + /* assumes that @obj_cur is already the best default route (or NULL). It checks whether + * @obj_cmp is also a default route and returns the best of both. */ + if (obj_cmp && nmp_object_ip_route_is_best_defaut_route(obj_cmp)) { + guint32 metric_cur, metric_cmp; + + if (!obj_cur) + return obj_cmp; + + if (obj_cur == obj_cmp) + return obj_cmp; + + metric_cur = NMP_OBJECT_CAST_IP_ROUTE(obj_cur)->metric; + metric_cmp = NMP_OBJECT_CAST_IP_ROUTE(obj_cmp)->metric; + + if (metric_cmp < metric_cur) + return obj_cmp; + + if (metric_cmp == metric_cur) { + int c; + + /* Routes have the same metric. We still want to deterministically + * prefer one or the other. It's important to consistently choose one + * or the other, so that the order doesn't matter how routes are added + * (and merged). */ + c = nmp_object_cmp(obj_cur, obj_cmp); + if (c != 0) + return c < 0 ? obj_cur : obj_cmp; + + /* as last resort, compare pointers. */ + if (((uintptr_t)((void *) (obj_cmp))) < ((uintptr_t)((void *) (obj_cur)))) + return obj_cmp; + } + } + return obj_cur; +} + +gboolean +nm_l3_config_data_add_address_full(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPAddress *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject ** out_obj_new) +{ + const NMPObject *new; + gboolean changed; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert((!pl_new) != (!obj_new)); + nm_assert(!obj_new || NMP_OBJECT_GET_ADDR_FAMILY(obj_new) == addr_family); + + changed = _l3_config_data_add_obj(self->multi_idx, + &self->idx_addresses_x[NM_IS_IPv4(addr_family)], + self->ifindex, + obj_new, + (const NMPlatformObject *) pl_new, + add_flags, + NULL, + &new); + NM_SET_OUT(out_obj_new, nmp_object_ref(new)); + return changed; +} + +static gboolean +_l3_config_best_default_route_merge(const NMPObject **best_default_route, + const NMPObject * new_candidate) +{ + new_candidate = _l3_config_best_default_route_find_better(*best_default_route, new_candidate); + return nmp_object_ref_set(best_default_route, new_candidate); +} + +gboolean +nm_l3_config_data_add_route_full(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPRoute *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject ** out_obj_new, + gboolean * out_changed_best_default_route) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + const NMPObject * obj_new_2; + gboolean changed = FALSE; + gboolean changed_best_default_route = FALSE; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert((!pl_new) != (!obj_new)); + nm_assert(!pl_new || _route_valid(addr_family, pl_new)); + nm_assert(!obj_new + || (NMP_OBJECT_GET_ADDR_FAMILY(obj_new) == addr_family + && _route_valid(addr_family, NMP_OBJECT_CAST_IP_ROUTE(obj_new)))); + + if (IS_IPv4) + self->has_routes_with_type_local_4_set = FALSE; + else + self->has_routes_with_type_local_6_set = FALSE; + if (_l3_config_data_add_obj(self->multi_idx, + &self->idx_routes_x[NM_IS_IPv4(addr_family)], + self->ifindex, + obj_new, + (const NMPlatformObject *) pl_new, + add_flags, + &obj_old, + &obj_new_2)) { + if (self->best_default_route_x[IS_IPv4] == obj_old && obj_old != obj_new_2) { + changed_best_default_route = TRUE; + nm_clear_nmp_object(&self->best_default_route_x[IS_IPv4]); + } + + if (_l3_config_best_default_route_merge(&self->best_default_route_x[IS_IPv4], obj_new_2)) + changed_best_default_route = TRUE; + + changed = TRUE; + } + + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); + NM_SET_OUT(out_changed_best_default_route, changed_best_default_route); + return changed; +} + +const NMPObject * +nm_l3_config_data_get_best_default_route(const NML3ConfigData *self, int addr_family) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + + return self->best_default_route_x[NM_IS_IPv4(addr_family)]; +} + +/*****************************************************************************/ + +static gboolean +_check_and_add_domain(GPtrArray **p_arr, const char *domain) +{ + gs_free char *copy = NULL; + gsize len; + + nm_assert(p_arr); + g_return_val_if_fail(domain, FALSE); + + if (domain[0] == '\0') + return FALSE; + + if (domain[0] == '.' || strstr(domain, "..")) + return FALSE; + + len = strlen(domain); + if (domain[len - 1] == '.') { + copy = g_strndup(domain, len - 1); + domain = copy; + } + + if (nm_strv_ptrarray_contains(*p_arr, domain)) + return FALSE; + + nm_strv_ptrarray_add_string_take(nm_strv_ptrarray_ensure(p_arr), + g_steal_pointer(©) ?: g_strdup(domain)); + return TRUE; +} + +gconstpointer +nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, guint *out_len) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + nm_assert(out_len); + + return _garray_inaddr_get(self->nameservers_x[NM_IS_IPv4(addr_family)], out_len); +} + +gboolean +nm_l3_config_data_add_nameserver(NML3ConfigData * self, + int addr_family, + gconstpointer /* (const NMIPAddr *) */ nameserver) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert(nameserver); + + return _garray_inaddr_add(&self->nameservers_x[NM_IS_IPv4(addr_family)], + addr_family, + nameserver); +} + +gboolean +nm_l3_config_data_clear_nameservers(NML3ConfigData *self, int addr_family) +{ + gs_unref_array GArray *old = NULL; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + old = g_steal_pointer(&self->nameservers_x[NM_IS_IPv4(addr_family)]); + return (nm_g_array_len(old) > 0); +} + +const in_addr_t * +nm_l3_config_data_get_wins(const NML3ConfigData *self, guint *out_len) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return _garray_inaddr_get(self->wins, out_len); +} + +gboolean +nm_l3_config_data_add_wins(NML3ConfigData *self, in_addr_t wins) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + return _garray_inaddr_add(&self->wins, AF_INET, &wins); +} + +gboolean +nm_l3_config_data_add_nis_server(NML3ConfigData *self, in_addr_t nis_server) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + return _garray_inaddr_add(&self->nis_servers, AF_INET, &nis_server); +} + +gboolean +nm_l3_config_data_set_nis_domain(NML3ConfigData *self, const char *nis_domain) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + return nm_utils_strdup_reset(&self->nis_domain, nis_domain); +} + +const char *const * +nm_l3_config_data_get_domains(const NML3ConfigData *self, int addr_family, guint *out_len) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert(out_len); + + return nm_strv_ptrarray_get_unsafe(self->domains_x[NM_IS_IPv4(addr_family)], out_len); +} + +gboolean +nm_l3_config_data_add_domain(NML3ConfigData *self, int addr_family, const char *domain) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + return _check_and_add_domain(&self->domains_x[NM_IS_IPv4(addr_family)], domain); +} + +const char *const * +nm_l3_config_data_get_searches(const NML3ConfigData *self, int addr_family, guint *out_len) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + nm_assert(out_len); + + return nm_strv_ptrarray_get_unsafe(self->searches_x[NM_IS_IPv4(addr_family)], out_len); +} + +gboolean +nm_l3_config_data_add_search(NML3ConfigData *self, int addr_family, const char *search) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + return _check_and_add_domain(&self->searches_x[NM_IS_IPv4(addr_family)], search); +} + +gboolean +nm_l3_config_data_clear_searches(NML3ConfigData *self, int addr_family) +{ + gs_unref_ptrarray GPtrArray *old = NULL; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + old = g_steal_pointer(&self->searches_x[NM_IS_IPv4(addr_family)]); + return (nm_g_ptr_array_len(old) > 0); +} + +gboolean +nm_l3_config_data_add_dns_option(NML3ConfigData *self, int addr_family, const char *dns_option) +{ + GPtrArray **p_arr; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + g_return_val_if_fail(dns_option, FALSE); + + if (dns_option[0] == '\0') + return FALSE; + + p_arr = &self->dns_options_x[NM_IS_IPv4(addr_family)]; + + if (nm_strv_ptrarray_contains(*p_arr, dns_option)) + return FALSE; + + nm_strv_ptrarray_add_string_dup(nm_strv_ptrarray_ensure(p_arr), dns_option); + return TRUE; +} + +gboolean +nm_l3_config_data_set_dns_priority(NML3ConfigData *self, int addr_family, int dns_priority) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + const NML3ConfigDatFlags has_dns_priority_flag = + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4); + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + if (self->dns_priority_x[IS_IPv4] == dns_priority + && NM_FLAGS_ANY(self->flags, has_dns_priority_flag)) + return FALSE; + + self->flags |= has_dns_priority_flag; + self->dns_priority_x[IS_IPv4] = dns_priority; + return TRUE; +} + +gboolean +nm_l3_config_data_set_mdns(NML3ConfigData *self, NMSettingConnectionMdns mdns) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->mdns == mdns) + return FALSE; + + self->mdns = mdns; + return TRUE; +} + +gboolean +nm_l3_config_data_set_llmnr(NML3ConfigData *self, NMSettingConnectionLlmnr llmnr) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->llmnr == llmnr) + return FALSE; + + self->llmnr = llmnr; + return TRUE; +} + +NMIPRouteTableSyncMode +nm_l3_config_data_get_route_table_sync(const NML3ConfigData *self, int addr_family) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + + return self->route_table_sync_x[NM_IS_IPv4(addr_family)]; +} + +gboolean +nm_l3_config_data_set_route_table_sync(NML3ConfigData * self, + int addr_family, + NMIPRouteTableSyncMode route_table_sync) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + if (self->route_table_sync_x[IS_IPv4] == route_table_sync) + return FALSE; + + self->route_table_sync_x[IS_IPv4] = route_table_sync; + return TRUE; +} + +NMTernary +nm_l3_config_data_get_metered(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->metered; +} + +gboolean +nm_l3_config_data_set_metered(NML3ConfigData *self, NMTernary metered) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert(NM_IN_SET(metered, NM_TERNARY_DEFAULT, NM_TERNARY_FALSE, NM_TERNARY_TRUE)); + + if (self->metered == metered) + return FALSE; + + self->metered = metered; + return TRUE; +} + +guint32 +nm_l3_config_data_get_mtu(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->mtu; +} + +gboolean +nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->mtu == mtu) + return FALSE; + + self->mtu = mtu; + return TRUE; +} + +guint32 +nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->ip6_mtu; +} + +gboolean +nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->ip6_mtu == ip6_mtu) + return FALSE; + + self->ip6_mtu = ip6_mtu; + return TRUE; +} + +gboolean +nm_l3_config_data_set_source(NML3ConfigData *self, NMIPConfigSource source) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->source == source) + return FALSE; + + self->source = source; + return TRUE; +} + +NMSettingIP6ConfigPrivacy +nm_l3_config_data_get_ip6_privacy(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + return self->ip6_privacy; +} + +gboolean +nm_l3_config_data_set_ip6_privacy(NML3ConfigData *self, NMSettingIP6ConfigPrivacy ip6_privacy) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert(NM_IN_SET(ip6_privacy, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); + + if (self->ip6_privacy == ip6_privacy) + return FALSE; + self->ip6_privacy = ip6_privacy; + nm_assert(self->ip6_privacy == ip6_privacy); + return TRUE; +} + +gboolean +nm_l3_config_data_get_ndisc_hop_limit(const NML3ConfigData *self, int *out_val) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (!self->ndisc_hop_limit_set) { + NM_SET_OUT(out_val, 0); + return FALSE; + } + NM_SET_OUT(out_val, self->ndisc_hop_limit_val); + return TRUE; +} + +gboolean +nm_l3_config_data_set_ndisc_hop_limit(NML3ConfigData *self, int val) +{ + if (self->ndisc_hop_limit_set && self->ndisc_hop_limit_val == val) + return FALSE; + self->ndisc_hop_limit_set = TRUE; + self->ndisc_hop_limit_val = val; + return TRUE; +} + +gboolean +nm_l3_config_data_get_ndisc_reachable_time_msec(const NML3ConfigData *self, guint32 *out_val) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (!self->ndisc_reachable_time_msec_set) { + NM_SET_OUT(out_val, 0); + return FALSE; + } + NM_SET_OUT(out_val, self->ndisc_reachable_time_msec_val); + return TRUE; +} + +gboolean +nm_l3_config_data_set_ndisc_reachable_time_msec(NML3ConfigData *self, guint32 val) +{ + if (self->ndisc_reachable_time_msec_set && self->ndisc_reachable_time_msec_val == val) + return FALSE; + self->ndisc_reachable_time_msec_set = TRUE; + self->ndisc_reachable_time_msec_val = val; + return TRUE; +} + +gboolean +nm_l3_config_data_get_ndisc_retrans_timer_msec(const NML3ConfigData *self, guint32 *out_val) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (!self->ndisc_retrans_timer_msec_set) { + NM_SET_OUT(out_val, 0); + return FALSE; + } + NM_SET_OUT(out_val, self->ndisc_retrans_timer_msec_val); + return TRUE; +} + +gboolean +nm_l3_config_data_set_ndisc_retrans_timer_msec(NML3ConfigData *self, guint32 val) +{ + if (self->ndisc_retrans_timer_msec_set && self->ndisc_retrans_timer_msec_val == val) + return FALSE; + self->ndisc_retrans_timer_msec_set = TRUE; + self->ndisc_retrans_timer_msec_val = val; + return TRUE; +} + +/*****************************************************************************/ + +NMDhcpLease * +nm_l3_config_data_get_dhcp_lease(const NML3ConfigData *self, int addr_family) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->dhcp_lease_x[NM_IS_IPv4(addr_family)]; +} + +gboolean +nm_l3_config_data_set_dhcp_lease(NML3ConfigData *self, int addr_family, NMDhcpLease *lease) +{ + nm_auto_unref_dhcplease NMDhcpLease *lease_old = NULL; + NMDhcpLease ** p_lease; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + p_lease = &self->dhcp_lease_x[NM_IS_IPv4(addr_family)]; + + if (*p_lease == lease) + return FALSE; + + if (lease) + nm_dhcp_lease_ref(lease); + lease_old = *p_lease; + *p_lease = lease; + return TRUE; +} + +gboolean +nm_l3_config_data_set_dhcp_lease_from_options(NML3ConfigData *self, + int addr_family, + GHashTable * options_take) +{ + nm_auto_unref_dhcplease NMDhcpLease *lease = NULL; + nm_auto_unref_dhcplease NMDhcpLease *lease_old = NULL; + NMDhcpLease ** p_lease; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (options_take) + lease = nm_dhcp_lease_new_from_options(g_steal_pointer(&options_take)); + + p_lease = &self->dhcp_lease_x[NM_IS_IPv4(addr_family)]; + + if (*p_lease == lease) + return FALSE; + + lease_old = *p_lease; + *p_lease = g_steal_pointer(&lease); + return TRUE; +} + +/*****************************************************************************/ + +static int +_dedup_multi_index_cmp(const NML3ConfigData *a, const NML3ConfigData *b, NMPObjectType obj_type) +{ + const NMDedupMultiHeadEntry *h_a = nm_l3_config_data_lookup_objs(a, obj_type); + const NMDedupMultiHeadEntry *h_b = nm_l3_config_data_lookup_objs(b, obj_type); + NMDedupMultiIter iter_a; + NMDedupMultiIter iter_b; + + NM_CMP_SELF(h_a, h_b); + NM_CMP_DIRECT(h_a->len, h_b->len); + + nm_assert(h_a->len > 0); + + nm_dedup_multi_iter_init(&iter_a, h_a); + nm_dedup_multi_iter_init(&iter_b, h_b); + + while (TRUE) { + const NMPObject *obj_a; + const NMPObject *obj_b; + gboolean have_a; + gboolean have_b; + + have_a = nm_platform_dedup_multi_iter_next_obj(&iter_a, &obj_a, obj_type); + if (!have_a) { + nm_assert(!nm_platform_dedup_multi_iter_next_obj(&iter_b, &obj_b, obj_type)); + return 0; + } + + have_b = nm_platform_dedup_multi_iter_next_obj(&iter_b, &obj_b, obj_type); + nm_assert(have_b); + + NM_CMP_RETURN(nmp_object_cmp(obj_a, obj_b)); + } +} + +int +nm_l3_config_data_cmp(const NML3ConfigData *a, const NML3ConfigData *b) +{ + int IS_IPv4; + + NM_CMP_SELF(a, b); + + NM_CMP_DIRECT(a->ifindex, b->ifindex); + + NM_CMP_DIRECT(a->flags, b->flags); + + NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ADDRESS)); + NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ADDRESS)); + NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ROUTE)); + NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ROUTE)); + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + + NM_CMP_RETURN( + nmp_object_cmp(a->best_default_route_x[IS_IPv4], b->best_default_route_x[IS_IPv4])); + + NM_CMP_RETURN( + _garray_inaddr_cmp(a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4], addr_family)); + + NM_CMP_RETURN(nm_utils_hashtable_cmp(nm_dhcp_lease_get_options(a->dhcp_lease_x[IS_IPv4]), + nm_dhcp_lease_get_options(b->dhcp_lease_x[IS_IPv4]), + TRUE, + nm_strcmp_with_data, + nm_strcmp_with_data, + NULL)); + + NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->domains_x[IS_IPv4], b->domains_x[IS_IPv4])); + NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->searches_x[IS_IPv4], b->searches_x[IS_IPv4])); + NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->dns_options_x[IS_IPv4], b->dns_options_x[IS_IPv4])); + + if (NM_FLAGS_ANY(a->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4))) + NM_CMP_DIRECT(a->dns_priority_x[IS_IPv4], b->dns_priority_x[IS_IPv4]); + + NM_CMP_DIRECT(a->route_table_sync_x[IS_IPv4], b->route_table_sync_x[IS_IPv4]); + } + + NM_CMP_RETURN(_garray_inaddr_cmp(a->wins, b->wins, AF_INET)); + NM_CMP_RETURN(_garray_inaddr_cmp(a->nis_servers, b->nis_servers, AF_INET)); + NM_CMP_FIELD_STR0(a, b, nis_domain); + NM_CMP_DIRECT(a->mdns, b->mdns); + NM_CMP_DIRECT(a->llmnr, b->llmnr); + NM_CMP_DIRECT(a->mtu, b->mtu); + NM_CMP_DIRECT(a->ip6_mtu, b->ip6_mtu); + NM_CMP_DIRECT_UNSAFE(a->metered, b->metered); + NM_CMP_DIRECT_UNSAFE(a->ip6_privacy, b->ip6_privacy); + + NM_CMP_DIRECT_UNSAFE(a->ndisc_hop_limit_set, b->ndisc_hop_limit_set); + if (a->ndisc_hop_limit_set) + NM_CMP_DIRECT(a->ndisc_hop_limit_val, b->ndisc_hop_limit_val); + + NM_CMP_DIRECT_UNSAFE(a->ndisc_reachable_time_msec_set, b->ndisc_reachable_time_msec_set); + if (a->ndisc_reachable_time_msec_set) + NM_CMP_DIRECT(a->ndisc_reachable_time_msec_val, b->ndisc_reachable_time_msec_val); + + NM_CMP_DIRECT_UNSAFE(a->ndisc_retrans_timer_msec_set, b->ndisc_retrans_timer_msec_set); + if (a->ndisc_retrans_timer_msec_set) + NM_CMP_DIRECT(a->ndisc_retrans_timer_msec_val, b->ndisc_retrans_timer_msec_val); + + NM_CMP_FIELD(a, b, source); + + /* these fields are not considered by cmp(): + * + * - multi_idx + * - ref_count + * - is_sealed + */ + + return 0; +} + +/*****************************************************************************/ + +static const NMPObject * +_data_get_direct_route_for_host(const NML3ConfigData *self, + int addr_family, + gconstpointer host, + guint32 route_table) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPObject * best_route_obj = NULL; + const NMPlatformIPXRoute *best_route = NULL; + const NMPObject * item_obj; + NMDedupMultiIter ipconf_iter; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + nm_assert_addr_family(addr_family); + nm_assert(host); + + if (nm_ip_addr_is_null(addr_family, host)) + return NULL; + + nm_l3_config_data_iter_obj_for_each (&ipconf_iter, + self, + &item_obj, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + const NMPlatformIPXRoute *item = NMP_OBJECT_CAST_IPX_ROUTE(item_obj); + + if (nm_ip_addr_is_null(addr_family, + nm_platform_ip_route_get_gateway(addr_family, &item->rx))) + continue; + + if (best_route && best_route->rx.plen > item->rx.plen) + continue; + + if (nm_platform_route_table_uncoerce(item->rx.table_coerced, TRUE) != route_table) + continue; + + if (!nm_utils_ip_address_same_prefix(addr_family, + host, + item->rx.network_ptr, + item->rx.plen)) + continue; + + if (best_route && best_route->rx.metric <= item->rx.metric) + continue; + + best_route_obj = item_obj; + best_route = item; + } + return best_route_obj; +} + +/*****************************************************************************/ + +/* Kernel likes to add device routes for all addresses. Normally, we want to suppress that + * with IFA_F_NOPREFIXROUTE. But we also want to support kernels that don't support that + * flag. So, we collect here all those routes that kernel might add but we don't want. + * If the route shows up within a certain timeout of us configuring the address, we assume + * that it was (undesirably) added by kernel and we remove it. + * + * The most common reason is that for each IPv4 address we want to add a corresponding device + * route with the right ipv4.route-metric. The route that kernel adds has metric 0, so it is + * undesired. + * + * FIXME(l3cfg): implement handling blacklisted routes. + * + * For IPv6, IFA_F_NOPREFIXROUTE is supported for a longer time and we don't do such a hack. + */ +GPtrArray * +nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolean is_vrf) +{ + gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL; + const NMPObject * my_addr_obj; + NMDedupMultiIter iter; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + /* For IPv6 slaac, we explicitly add the device-routes (onlink). + * As we don't do that for IPv4 and manual IPv6 addresses. Add them here + * as dependent routes. */ + + nm_l3_config_data_iter_obj_for_each (&iter, self, &my_addr_obj, NMP_OBJECT_TYPE_IP4_ADDRESS) { + const NMPlatformIP4Address *const my_addr = NMP_OBJECT_CAST_IP4_ADDRESS(my_addr_obj); + in_addr_t network_4; + NMPlatformIPXRoute rx; + + if (my_addr->external) + continue; + + nm_assert(my_addr->plen <= 32); + if (my_addr->plen == 0) + continue; + + network_4 = nm_utils_ip4_address_clear_host_address(my_addr->peer_address, my_addr->plen); + + if (nm_utils_ip4_address_is_zeronet(network_4)) { + /* Kernel doesn't add device-routes for destinations that + * start with 0.x.y.z. Skip them. */ + continue; + } + + if (my_addr->plen == 32 && my_addr->address == my_addr->peer_address) { + /* Kernel doesn't add device-routes for /32 addresses unless + * they have a peer. */ + continue; + } + + rx.r4 = (NMPlatformIP4Route){ + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = network_4, + .plen = my_addr->plen, + .pref_src = my_addr->address, + .table_coerced = nm_platform_route_table_coerce(RT_TABLE_MAIN), + .metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE, + .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK), + }; + nm_platform_ip_route_normalize(AF_INET, &rx.rx); + + if (nm_l3_config_data_lookup_route(self, AF_INET, &rx.rx)) { + /* we track such a route explicitly. Don't blacklist it. */ + continue; + } + + if (!ip4_dev_route_blacklist) + ip4_dev_route_blacklist = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(ip4_dev_route_blacklist, nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, &rx)); + } + + return g_steal_pointer(&ip4_dev_route_blacklist); +} + +/*****************************************************************************/ + +void +nm_l3_config_data_add_dependent_routes(NML3ConfigData *self, + int addr_family, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_ptrarray GPtrArray *extra_onlink_routes = NULL; + const NMPObject * my_addr_obj; + const NMPObject * my_route_obj; + NMPlatformIPXRoute rx; + NMDedupMultiIter iter; + in_addr_t network_4 = 0; + guint i; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + /* For IPv6 slaac, we explicitly add the device-routes (onlink). + * As we don't do that for IPv4 and manual IPv6 addresses. Add them here + * as dependent routes. */ + + if (!IS_IPv4) { + /* Pre-generate multicast route */ + rx.r6 = (NMPlatformIP6Route){ + .ifindex = self->ifindex, + .network.s6_addr[0] = 0xffu, + .plen = 8, + .table_coerced = nm_platform_route_table_coerce(RT_TABLE_LOCAL), + .type_coerced = nm_platform_route_type_coerce(RTN_UNICAST), + .metric = 256, + }; + nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx); + } + + nm_l3_config_data_iter_obj_for_each (&iter, + self, + &my_addr_obj, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { + const NMPlatformIPXAddress *const my_addr = NMP_OBJECT_CAST_IPX_ADDRESS(my_addr_obj); + + if (my_addr->ax.external) + continue; + + if (IS_IPv4) { + nm_assert(my_addr->a4.plen <= 32); + if (my_addr->a4.plen == 0) + continue; + } + + if (IS_IPv4) { + rx.r4 = (NMPlatformIP4Route){ + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = my_addr->a4.address, + .plen = 32, + .pref_src = my_addr->a4.address, + .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL), + .scope_inv = nm_platform_route_scope_inv(RT_SCOPE_HOST), + .table_coerced = + nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL), + }; + } else { + rx.r6 = (NMPlatformIP6Route){ + .ifindex = self->ifindex, + .network = my_addr->a6.address, + .plen = 128, + .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL), + .metric = 0, + .table_coerced = + nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL), + }; + } + nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx); + + if (my_addr->ax.plen == 0) + continue; + + if (IS_IPv4) { + network_4 = + nm_utils_ip4_address_clear_host_address(my_addr->a4.peer_address, my_addr->a4.plen); + + if (nm_utils_ip4_address_is_zeronet(network_4)) { + /* Kernel doesn't add device-routes for destinations that + * start with 0.x.y.z. Skip them. */ + continue; + } + + if (my_addr->a4.plen == 32 && my_addr->a4.address == my_addr->a4.peer_address) { + /* Kernel doesn't add device-routes for /32 addresses unless + * they have a peer. */ + continue; + } + } else { + if (NM_FLAGS_HAS(my_addr->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE)) + continue; + } + + if (IS_IPv4) { + rx.r4 = (NMPlatformIP4Route){ + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = network_4, + .plen = my_addr->a4.plen, + .pref_src = my_addr->a4.address, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK), + }; + nm_platform_ip_route_normalize(addr_family, &rx.rx); + nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx); + } else { + const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED(&my_addr->a6.peer_address); + int routes_i; + + /* If we have an IPv6 peer, we add two /128 routes + * (unless, both addresses are identical). */ + for (routes_i = 0; routes_i < 2; routes_i++) { + struct in6_addr a6_stack; + const struct in6_addr *a6; + guint8 plen; + + if (routes_i == 1 && has_peer + && IN6_ARE_ADDR_EQUAL(&my_addr->a6.address, &my_addr->a6.peer_address)) + break; + + if (has_peer) { + if (routes_i == 0) + a6 = &my_addr->a6.address; + else + a6 = &my_addr->a6.peer_address; + plen = 128; + } else { + a6 = nm_utils_ip6_address_clear_host_address(&a6_stack, + &my_addr->a6.address, + my_addr->a6.plen); + plen = my_addr->a6.plen; + } + + rx.r6 = (NMPlatformIP6Route){ + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .network = *a6, + .plen = plen, + }; + nm_platform_ip_route_normalize(addr_family, &rx.rx); + nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx); + } + } + } + + nm_l3_config_data_iter_obj_for_each (&iter, + self, + &my_route_obj, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + const NMPlatformIPXRoute *my_route = NMP_OBJECT_CAST_IPX_ROUTE(my_route_obj); + NMPObject * new_route; + NMPlatformIPXRoute * new_r; + const NMIPAddr * p_gateway; + + if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route) + || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rx.rt_source)) + continue; + + p_gateway = nm_platform_ip_route_get_gateway(addr_family, &my_route->rx); + + if (nm_ip_addr_is_null(addr_family, p_gateway)) + continue; + + if (_data_get_direct_route_for_host( + self, + addr_family, + p_gateway, + nm_platform_route_table_uncoerce(my_route->rx.table_coerced, TRUE))) + continue; + + new_route = nmp_object_clone(my_route_obj, FALSE); + new_r = NMP_OBJECT_CAST_IPX_ROUTE(new_route); + if (IS_IPv4) { + new_r->r4.network = my_route->r4.gateway; + new_r->r4.plen = 32; + new_r->r4.gateway = 0; + } else { + new_r->r6.network = my_route->r6.gateway; + new_r->r6.plen = 128; + new_r->r6.gateway = in6addr_any; + } + + /* we cannot add the route right away, because that invalidates the iteration. */ + if (!extra_onlink_routes) + extra_onlink_routes = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + g_ptr_array_add(extra_onlink_routes, new_route); + } + if (extra_onlink_routes) { + for (i = 0; i < extra_onlink_routes->len; i++) { + nm_l3_config_data_add_route_full(self, + addr_family, + extra_onlink_routes->pdata[i], + NULL, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); + } + } +} + +/*****************************************************************************/ + +static void +_init_from_connection_ip(NML3ConfigData *self, + int addr_family, + NMConnection * connection, + guint32 route_table, + guint32 route_metric) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + NMSettingIPConfig *s_ip; + guint naddresses; + guint nroutes; + guint nnameservers; + guint nsearches; + const char * gateway_str; + NMIPAddr gateway_bin; + guint i; + int idx; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + + if (!connection) + return; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (!s_ip) + return; + + if (!nm_setting_ip_config_get_never_default(s_ip) + && (gateway_str = nm_setting_ip_config_get_gateway(s_ip)) + && inet_pton(addr_family, gateway_str, &gateway_bin) == 1 + && !nm_ip_addr_is_null(addr_family, &gateway_bin)) { + NMPlatformIPXRoute r; + + if (IS_IPv4) { + r.r4 = (NMPlatformIP4Route){ + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .gateway = gateway_bin.addr4, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + } else { + r.r6 = (NMPlatformIP6Route){ + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .gateway = gateway_bin.addr6, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + }; + } + + nm_l3_config_data_add_route(self, addr_family, NULL, &r.rx); + } + + naddresses = nm_setting_ip_config_get_num_addresses(s_ip); + for (i = 0; i < naddresses; i++) { + NMIPAddress * s_addr = nm_setting_ip_config_get_address(s_ip, i); + NMPlatformIPXAddress a; + NMIPAddr addr_bin; + GVariant * label; + + nm_assert(nm_ip_address_get_family(s_addr) == addr_family); + + nm_ip_address_get_address_binary(s_addr, &addr_bin); + + if (IS_IPv4) { + a.a4 = (NMPlatformIP4Address){ + .address = addr_bin.addr4, + .peer_address = addr_bin.addr4, + .plen = nm_ip_address_get_prefix(s_addr), + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .addr_source = NM_IP_CONFIG_SOURCE_USER, + }; + label = nm_ip_address_get_attribute(s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (label) + g_strlcpy(a.a4.label, g_variant_get_string(label, NULL), sizeof(a.a4.label)); + + nm_assert(a.a4.plen <= 32); + } else { + a.a6 = (NMPlatformIP6Address){ + .address = addr_bin.addr6, + .plen = nm_ip_address_get_prefix(s_addr), + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .addr_source = NM_IP_CONFIG_SOURCE_USER, + }; + + nm_assert(a.a6.plen <= 128); + } + + nm_l3_config_data_add_address(self, addr_family, NULL, &a.ax); + } + + nroutes = nm_setting_ip_config_get_num_routes(s_ip); + for (i = 0; i < nroutes; i++) { + NMIPRoute * s_route = nm_setting_ip_config_get_route(s_ip, i); + NMPlatformIPXRoute r; + NMIPAddr network_bin; + NMIPAddr next_hop_bin; + gint64 metric64; + guint32 metric; + guint plen; + + nm_assert(nm_ip_route_get_family(s_route) == addr_family); + + nm_ip_route_get_dest_binary(s_route, &network_bin); + nm_ip_route_get_next_hop_binary(s_route, &next_hop_bin); + + metric64 = nm_ip_route_get_metric(s_route); + if (metric64 < 0) + metric = route_metric; + else + metric = metric64; + metric = nm_utils_ip_route_metric_normalize(addr_family, metric); + + plen = nm_ip_route_get_prefix(s_route); + + nm_utils_ipx_address_clear_host_address(addr_family, &network_bin, &network_bin, plen); + + if (IS_IPv4) { + r.r4 = (NMPlatformIP4Route){ + .network = network_bin.addr4, + .plen = nm_ip_route_get_prefix(s_route), + .gateway = next_hop_bin.addr4, + .metric = metric, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + }; + nm_assert(r.r4.plen <= 32); + } else { + r.r6 = (NMPlatformIP6Route){ + .network = network_bin.addr6, + .plen = nm_ip_route_get_prefix(s_route), + .gateway = next_hop_bin.addr6, + .metric = metric, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + }; + nm_assert(r.r6.plen <= 128); + } + + nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, route_table); + + nm_l3_config_data_add_route(self, addr_family, NULL, &r.rx); + } + + nnameservers = nm_setting_ip_config_get_num_dns(s_ip); + for (i = 0; i < nnameservers; i++) { + const char *s; + NMIPAddr ip; + + s = nm_setting_ip_config_get_dns(s_ip, i); + if (!nm_utils_parse_inaddr_bin(addr_family, s, NULL, &ip)) + continue; + nm_l3_config_data_add_nameserver(self, addr_family, &ip); + } + + nsearches = nm_setting_ip_config_get_num_dns_searches(s_ip); + for (i = 0; i < nsearches; i++) { + nm_l3_config_data_add_search(self, + addr_family, + nm_setting_ip_config_get_dns_search(s_ip, i)); + } + + idx = 0; + while ((idx = nm_setting_ip_config_next_valid_dns_option(s_ip, i)) >= 0) { + nm_l3_config_data_add_dns_option(self, + addr_family, + nm_setting_ip_config_get_dns_option(s_ip, i)); + idx++; + } + + nm_l3_config_data_set_dns_priority(self, + addr_family, + nm_setting_ip_config_get_dns_priority(s_ip)); +} + +NML3ConfigData * +nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx, + int ifindex, + NMConnection * connection, + guint32 route_table_4, + guint32 route_table_6, + guint32 route_metric_4, + guint32 route_metric_6) +{ + NML3ConfigData *self; + + self = nm_l3_config_data_new(multi_idx, ifindex); + + _init_from_connection_ip(self, AF_INET, connection, route_table_4, route_metric_4); + _init_from_connection_ip(self, AF_INET6, connection, route_table_6, route_metric_6); + return self; +} + +static int +sort_captured_addresses_4(const CList *lst_a, const CList *lst_b, gconstpointer user_data) +{ + const NMPlatformIP4Address *addr_a = + NMP_OBJECT_CAST_IP4_ADDRESS(c_list_entry(lst_a, NMDedupMultiEntry, lst_entries)->obj); + const NMPlatformIP4Address *addr_b = + NMP_OBJECT_CAST_IP4_ADDRESS(c_list_entry(lst_b, NMDedupMultiEntry, lst_entries)->obj); + + nm_assert(addr_a); + nm_assert(addr_b); + + /* Primary addresses first */ + return NM_FLAGS_HAS(addr_a->n_ifa_flags, IFA_F_SECONDARY) + - NM_FLAGS_HAS(addr_b->n_ifa_flags, IFA_F_SECONDARY); +} + +static int +sort_captured_addresses_6(const CList *lst_a, const CList *lst_b, gconstpointer user_data) +{ + NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941 = GPOINTER_TO_INT(user_data); + const NMPlatformIP6Address *addr_a = + NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_a, NMDedupMultiEntry, lst_entries)->obj); + const NMPlatformIP6Address *addr_b = + NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_b, NMDedupMultiEntry, lst_entries)->obj); + + return nm_platform_ip6_address_pretty_sort_cmp( + addr_a, + addr_b, + ipv6_privacy_rfc4941 == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR); +} + +static void +_init_from_platform(NML3ConfigData * self, + int addr_family, + NMPlatform * platform, + NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941) +{ + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDedupMultiHeadEntry *head_entry; + const NMPObject * plobj = NULL; + NMDedupMultiIter iter; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + + head_entry = nm_platform_lookup_object(platform, + IS_IPv4 ? NMP_OBJECT_TYPE_IP4_ADDRESS + : NMP_OBJECT_TYPE_IP6_ADDRESS, + self->ifindex); + if (head_entry) { + if (IS_IPv4) + self->has_routes_with_type_local_4_set = FALSE; + else + self->has_routes_with_type_local_6_set = FALSE; + nmp_cache_iter_for_each (&iter, head_entry, &plobj) { + if (!_l3_config_data_add_obj(self->multi_idx, + &self->idx_addresses_x[IS_IPv4], + self->ifindex, + plobj, + NULL, + NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE, + NULL, + NULL)) + nm_assert_not_reached(); + } + head_entry = nm_l3_config_data_lookup_addresses(self, addr_family); + nm_assert(head_entry); + nm_dedup_multi_head_entry_sort(head_entry, + IS_IPv4 ? sort_captured_addresses_4 + : sort_captured_addresses_6, + GINT_TO_POINTER(ipv6_privacy_rfc4941)); + } + + head_entry = + nm_platform_lookup_object(platform, + IS_IPv4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, + self->ifindex); + nmp_cache_iter_for_each (&iter, head_entry, &plobj) + nm_l3_config_data_add_route(self, addr_family, plobj, NULL); +} + +NML3ConfigData * +nm_l3_config_data_new_from_platform(NMDedupMultiIndex * multi_idx, + int ifindex, + NMPlatform * platform, + NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941) +{ + NML3ConfigData *self; + + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(ifindex > 0); + + /* Slaves have no IP configuration */ + if (nm_platform_link_get_master(platform, ifindex) > 0) + return NULL; + + self = nm_l3_config_data_new(multi_idx, ifindex); + + _init_from_platform(self, AF_INET, platform, ipv6_privacy_rfc4941); + _init_from_platform(self, AF_INET6, platform, ipv6_privacy_rfc4941); + + return self; +} + +/*****************************************************************************/ + +void +nm_l3_config_data_merge(NML3ConfigData * self, + const NML3ConfigData *src, + NML3ConfigMergeFlags merge_flags, + const guint32 * default_route_table_x /* length 2, for IS_IPv4 */, + const guint32 * default_route_metric_x /* length 2, for IS_IPv4 */, + const guint32 * default_route_penalty_x /* length 2, for IS_IPv4 */, + NML3ConfigMergeHookAddObj hook_add_addr, + gpointer hook_user_data) +{ + static const guint32 x_default_route_table_x[2] = {RT_TABLE_MAIN, RT_TABLE_MAIN}; + static const guint32 x_default_route_metric_x[2] = {NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4}; + static const guint32 x_default_route_penalty_x[2] = {0, 0}; + NMDedupMultiIter iter; + const NMPObject * obj; + int IS_IPv4; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert(_NM_IS_L3_CONFIG_DATA(src, TRUE)); + + if (!default_route_table_x) + default_route_table_x = x_default_route_table_x; + if (!default_route_metric_x) + default_route_metric_x = x_default_route_metric_x; + if (!default_route_penalty_x) + default_route_penalty_x = x_default_route_penalty_x; + + nm_assert(default_route_table_x[0] != 0); + nm_assert(default_route_table_x[1] != 0); + nm_assert(default_route_metric_x[0] != 0); /* IPv6 route metric cannot be zero. */ + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + const NML3ConfigDatFlags has_dns_priority_flag = + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4); + + nm_l3_config_data_iter_obj_for_each (&iter, + src, + &obj, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { + NMPlatformIPXAddress addr_stack; + const NMPlatformIPAddress *addr = NULL; + NMTernary ip4acd_not_ready = NM_TERNARY_DEFAULT; + + if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data)) + continue; + + if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT + && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) { + addr_stack.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); + addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready); + addr = &addr_stack.ax; + } else + nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT); + + nm_l3_config_data_add_address_full(self, + addr_family, + addr ? NULL : obj, + addr, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL); + } + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) { + nm_l3_config_data_iter_obj_for_each (&iter, + src, + &obj, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj); + NMPlatformIPXRoute r; + +#define _ensure_r() \ + G_STMT_START \ + { \ + if (r_src != &r.rx) { \ + r_src = &r.rx; \ + if (IS_IPv4) \ + r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE(obj); \ + else \ + r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE(obj); \ + r.rx.ifindex = self->ifindex; \ + } \ + } \ + G_STMT_END + + if (r_src->table_any) { + _ensure_r(); + r.rx.table_any = FALSE; + r.rx.table_coerced = default_route_table_x[IS_IPv4]; + } + + if (r_src->metric_any) { + _ensure_r(); + r.rx.metric_any = FALSE; + r.rx.metric = nm_add_clamped_u32(r.rx.metric, default_route_metric_x[IS_IPv4]); + } + + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) { + if (NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS(src->flags, + NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) + continue; + if (default_route_penalty_x && default_route_penalty_x[IS_IPv4] > 0) { + _ensure_r(); + r.rx.metric = + nm_utils_ip_route_metric_penalize(r.rx.metric, + default_route_penalty_x[IS_IPv4]); + } + } + + nm_l3_config_data_add_route_full(self, + addr_family, + r_src == &r.rx ? NULL : obj, + r_src == &r.rx ? r_src : NULL, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); + } + +#undef _ensure_r + } + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _garray_inaddr_merge(&self->nameservers_x[IS_IPv4], + src->nameservers_x[IS_IPv4], + addr_family); + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge(&self->domains_x[IS_IPv4], src->domains_x[IS_IPv4]); + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge(&self->searches_x[IS_IPv4], src->searches_x[IS_IPv4]); + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge(&self->dns_options_x[IS_IPv4], src->dns_options_x[IS_IPv4]); + + if (!NM_FLAGS_ANY(self->flags, has_dns_priority_flag) + && NM_FLAGS_ANY(src->flags, has_dns_priority_flag)) { + self->dns_priority_x[IS_IPv4] = src->dns_priority_x[IS_IPv4]; + self->flags |= has_dns_priority_flag; + } + + if (self->route_table_sync_x[IS_IPv4] == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) + self->route_table_sync_x[IS_IPv4] = src->route_table_sync_x[IS_IPv4]; + } + + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) { + _garray_inaddr_merge(&self->wins, src->wins, AF_INET); + _garray_inaddr_merge(&self->nis_servers, src->nis_servers, AF_INET); + + if (!self->nis_domain && src->nis_domain) + self->nis_domain = g_strdup(src->nis_domain); + } + + if (self->mdns == NM_SETTING_CONNECTION_MDNS_DEFAULT) + self->mdns = src->mdns; + + if (self->llmnr == NM_SETTING_CONNECTION_LLMNR_DEFAULT) + self->llmnr = src->llmnr; + + self->metered = NM_MAX((NMTernary) self->metered, (NMTernary) src->metered); + + if (self->ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) + self->ip6_privacy = src->ip6_privacy; + + if (!self->ndisc_hop_limit_set && src->ndisc_hop_limit_set) { + self->ndisc_hop_limit_set = TRUE; + self->ndisc_hop_limit_val = src->ndisc_hop_limit_val; + } + + if (!self->ndisc_reachable_time_msec_set && src->ndisc_reachable_time_msec_set) { + self->ndisc_reachable_time_msec_set = TRUE; + self->ndisc_reachable_time_msec_val = src->ndisc_reachable_time_msec_val; + } + + if (!self->ndisc_retrans_timer_msec_set && src->ndisc_retrans_timer_msec_set) { + self->ndisc_retrans_timer_msec_set = TRUE; + self->ndisc_retrans_timer_msec_val = src->ndisc_retrans_timer_msec_val; + } + + if (self->mtu == 0u) + self->mtu = src->mtu; + + if (self->ip6_mtu == 0u) + self->ip6_mtu = src->ip6_mtu; + + /* self->source does not get merged. */ + /* self->dhcp_lease_x does not get merged. */ +} + +NML3ConfigData * +nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex) +{ + NML3ConfigData *self; + + nm_assert(_NM_IS_L3_CONFIG_DATA(src, TRUE)); + + /* pass 0, to use the original ifindex. You can also use this function to + * copy the configuration for a different ifindex. */ + nm_assert(ifindex >= 0); + if (ifindex <= 0) + ifindex = src->ifindex; + + self = nm_l3_config_data_new(src->multi_idx, ifindex); + nm_l3_config_data_merge(self, src, NM_L3_CONFIG_MERGE_FLAGS_NONE, NULL, NULL, NULL, NULL, NULL); + return self; +} diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h new file mode 100644 index 0000000..b3d9cd3 --- /dev/null +++ b/src/core/nm-l3-config-data.h @@ -0,0 +1,507 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_L3_CONFIG_DATA_H__ +#define __NM_L3_CONFIG_DATA_H__ + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip6-config.h" +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" + +typedef enum { + NM_L3_CONFIG_DAT_FLAGS_NONE = 0, + + /* if set, then the merge flag NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES gets + * ignored during merge. */ + NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES = (1ull << 0), + + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_4 = (1ull << 1), + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_6 = (1ull << 2), +#define NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(is_ipv4) \ + ((is_ipv4) ? NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_4 \ + : NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_6) + +} NML3ConfigDatFlags; + +typedef enum { + NM_L3_CONFIG_ADD_FLAGS_NONE = 0, + + /* If the object does not yet exist, it will be added. If it already exists, + * by default the object will be replaced. With this flag, the new object will + * be merged with the existing one. */ + NM_L3_CONFIG_ADD_FLAGS_MERGE = (1ull << 0), + + /* If the object does not yet exist, it will be added. If it already exists, + * by default the object will be replaced. With this flag, the add will have + * no effect and the existing object will be kept. */ + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE = (1ull << 1), + + /* A new object gets appended by default. If the object already exists, + * by default it will not be moved. With APPEND-FORCE, we will always move + * an existing object to the end of the list. */ + NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE = (1ull << 2), +} NML3ConfigAddFlags; + +/** + * NML3ConfigMergeFlags: + * @NM_L3_CONFIG_MERGE_FLAGS_NONE: no flags set + * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set, + * the the NML3ConfigData doesn't get merged and it's information won't be + * synced. The only purpose is to run ACD on its IPv4 addresses, but + * regardless whether ACD succeeds/fails, the IP addresses won't be configured. + * The point is to run ACD first (without configuring it), and only + * commit the settings if requested. That can either happen by + * nm_l3cfg_add_config() the same NML3Cfg again (with a different + * tag), or by calling nm_l3cfg_add_config() again with this flag + * cleared (and the same tag). + * @NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES: don't merge routes + * @NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES: don't merge default routes. + * Note that if the respective NML3ConfigData has NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES + * set, this flag gets ignored during merge. + * @NM_L3_CONFIG_MERGE_FLAGS_NO_DNS: don't merge DNS information + */ +typedef enum _nm_packed { + NM_L3_CONFIG_MERGE_FLAGS_NONE = 0, + NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD = (1LL << 0), + NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 1), + NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 2), + NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 3), +} NML3ConfigMergeFlags; + +/*****************************************************************************/ + +static inline gboolean NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self); + +NML3ConfigData * nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex); +const NML3ConfigData *nm_l3_config_data_ref(const NML3ConfigData *self); +const NML3ConfigData *nm_l3_config_data_ref_and_seal(const NML3ConfigData *self); +const NML3ConfigData *nm_l3_config_data_seal(const NML3ConfigData *self); +void nm_l3_config_data_unref(const NML3ConfigData *self); + +#define nm_clear_l3cd(ptr) nm_clear_pointer((ptr), nm_l3_config_data_unref) + +NM_AUTO_DEFINE_FCN0(const NML3ConfigData *, _nm_auto_unref_l3cd, nm_l3_config_data_unref); +#define nm_auto_unref_l3cd nm_auto(_nm_auto_unref_l3cd) + +NM_AUTO_DEFINE_FCN0(NML3ConfigData *, _nm_auto_unref_l3cd_init, nm_l3_config_data_unref); +#define nm_auto_unref_l3cd_init nm_auto(_nm_auto_unref_l3cd_init) + +static inline gboolean +nm_l3_config_data_reset(const NML3ConfigData **dst, const NML3ConfigData *src) +{ + nm_auto_unref_l3cd const NML3ConfigData *old = NULL; + + nm_assert(dst); + nm_assert(!*dst || NM_IS_L3_CONFIG_DATA(*dst)); + nm_assert(!src || NM_IS_L3_CONFIG_DATA(src)); + + if (*dst == src) + return FALSE; + old = *dst; + *dst = src ? nm_l3_config_data_ref_and_seal(src) : NULL; + return TRUE; +} + +static inline gboolean +nm_l3_config_data_reset_take(const NML3ConfigData **dst, const NML3ConfigData *src) +{ + nm_auto_unref_l3cd const NML3ConfigData *old = NULL; + + nm_assert(dst); + nm_assert(!*dst || NM_IS_L3_CONFIG_DATA(*dst)); + nm_assert(!src || NM_IS_L3_CONFIG_DATA(src)); + + if (*dst == src) { + if (src) + nm_l3_config_data_unref(src); + return FALSE; + } + old = *dst; + *dst = src ? nm_l3_config_data_seal(src) : NULL; + return TRUE; +} + +gboolean nm_l3_config_data_is_sealed(const NML3ConfigData *self); + +NML3ConfigData *nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex); + +NML3ConfigData *nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx, + int ifindex, + NMConnection * connection, + guint32 route_table_4, + guint32 route_table_6, + guint32 route_metric_4, + guint32 route_metric_6); + +NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex * multi_idx, + int ifindex, + NMPlatform * platform, + NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941); + +typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd, + const NMPObject * obj, + NMTernary * out_ip4acd_not_ready, + gpointer user_data); + +void nm_l3_config_data_merge(NML3ConfigData * self, + const NML3ConfigData *src, + NML3ConfigMergeFlags merge_flags, + const guint32 *default_route_table_x /* length 2, for IS_IPv4 */, + const guint32 *default_route_metric_x /* length 2, for IS_IPv4 */, + const guint32 *default_route_penalty_x /* length 2, for IS_IPv4 */, + NML3ConfigMergeHookAddObj hook_add_addr, + gpointer hook_user_data); + +GPtrArray *nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, + gboolean is_vrf); + +void nm_l3_config_data_add_dependent_routes(NML3ConfigData *self, + int addr_family, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf); + +/*****************************************************************************/ + +void nm_l3_config_data_log(const NML3ConfigData *self, + const char * title, + const char * prefix, + NMLogLevel log_level, + NMLogDomain log_domain); + +/*****************************************************************************/ + +int nm_l3_config_data_get_ifindex(const NML3ConfigData *self); + +static inline gboolean +NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self) +{ + /* NML3ConfigData is not an NMObject/GObject, so we cannot ask which type it has. + * This check here is really only useful for assertions, and there it is + * enough to check whether the pointer is not NULL. + * + * Additionally, also call nm_l3_config_data_get_ifindex(), which does more + * checks during nm_assert(). */ + nm_assert(nm_l3_config_data_get_ifindex(self) > 0); + return !!self; +} + +NMDedupMultiIndex *nm_l3_config_data_get_multi_idx(const NML3ConfigData *self); + +/*****************************************************************************/ + +int nm_l3_config_data_cmp(const NML3ConfigData *a, const NML3ConfigData *b); + +static inline gboolean +nm_l3_config_data_equal(const NML3ConfigData *a, const NML3ConfigData *b) +{ + return nm_l3_config_data_cmp(a, b) == 0; +} + +/*****************************************************************************/ + +const NMDedupMultiIdxType *nm_l3_config_data_lookup_index(const NML3ConfigData *self, + NMPObjectType obj_type); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_obj(const NML3ConfigData *self, + const NMPObject * obj); + +const NMPlatformIP6Address *nm_l3_config_data_lookup_address_6(const NML3ConfigData * self, + const struct in6_addr *addr); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_route_obj(const NML3ConfigData *self, + const NMPObject * needle); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_route(const NML3ConfigData * self, + int addr_family, + const NMPlatformIPRoute *needle); + +const NMDedupMultiHeadEntry *nm_l3_config_data_lookup_objs(const NML3ConfigData *self, + NMPObjectType obj_type); + +static inline const NMDedupMultiHeadEntry * +nm_l3_config_data_lookup_addresses(const NML3ConfigData *self, int addr_family) +{ + return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family))); +} + +static inline const NMDedupMultiHeadEntry * +nm_l3_config_data_lookup_routes(const NML3ConfigData *self, int addr_family) +{ + return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); +} + +#define nm_l3_config_data_iter_obj_for_each(iter, self, obj, type) \ + for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_objs((self), (type))); \ + nm_platform_dedup_multi_iter_next_obj((iter), (obj), (type));) + +#define nm_l3_config_data_iter_ip4_address_for_each(iter, self, address) \ + for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_addresses((self), AF_INET)); \ + nm_platform_dedup_multi_iter_next_ip4_address((iter), (address));) + +#define nm_l3_config_data_iter_ip6_address_for_each(iter, self, address) \ + for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_addresses((self), AF_INET6)); \ + nm_platform_dedup_multi_iter_next_ip6_address((iter), (address));) + +#define nm_l3_config_data_iter_ip4_route_for_each(iter, self, route) \ + for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_routes((self), AF_INET)); \ + nm_platform_dedup_multi_iter_next_ip4_route((iter), (route));) + +#define nm_l3_config_data_iter_ip6_route_for_each(iter, self, route) \ + for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_routes((self), AF_INET6)); \ + nm_platform_dedup_multi_iter_next_ip6_route((iter), (route));) + +static inline guint +nm_l3_config_data_get_num_objs(const NML3ConfigData *self, NMPObjectType obj_type) +{ + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_l3_config_data_lookup_objs(self, obj_type); + return head_entry ? head_entry->len : 0u; +} + +static inline guint +nm_l3_config_data_get_num_addresses(const NML3ConfigData *self, int addr_family) +{ + return nm_l3_config_data_get_num_objs(self, + NM_IS_IPv4(addr_family) ? NMP_OBJECT_TYPE_IP4_ADDRESS + : NMP_OBJECT_TYPE_IP6_ADDRESS); +} + +static inline guint +nm_l3_config_data_get_num_routes(const NML3ConfigData *self, int addr_family) +{ + return nm_l3_config_data_get_num_objs(self, + NM_IS_IPv4(addr_family) ? NMP_OBJECT_TYPE_IP4_ROUTE + : NMP_OBJECT_TYPE_IP6_ROUTE); +} + +gboolean nm_l3_config_data_has_routes_with_type_local(const NML3ConfigData *self, int addr_family); + +const NMPObject * +nmtst_l3_config_data_get_obj_at(const NML3ConfigData *self, NMPObjectType obj_type, guint i); + +static inline const NMPlatformIP4Address * +nmtst_l3_config_data_get_address_at_4(const NML3ConfigData *self, guint i) +{ + return NMP_OBJECT_CAST_IP4_ADDRESS( + nmtst_l3_config_data_get_obj_at(self, NMP_OBJECT_TYPE_IP4_ADDRESS, i)); +} + +static inline const NMPlatformIP6Address * +nmtst_l3_config_data_get_address_at_6(const NML3ConfigData *self, guint i) +{ + return NMP_OBJECT_CAST_IP6_ADDRESS( + nmtst_l3_config_data_get_obj_at(self, NMP_OBJECT_TYPE_IP6_ADDRESS, i)); +} + +static inline const NMPlatformIP4Route * +nmtst_l3_config_data_get_route_at_4(const NML3ConfigData *self, guint i) +{ + return NMP_OBJECT_CAST_IP4_ROUTE( + nmtst_l3_config_data_get_obj_at(self, NMP_OBJECT_TYPE_IP4_ROUTE, i)); +} + +static inline const NMPlatformIP6Route * +nmtst_l3_config_data_get_route_at_6(const NML3ConfigData *self, guint i) +{ + return NMP_OBJECT_CAST_IP6_ROUTE( + nmtst_l3_config_data_get_obj_at(self, NMP_OBJECT_TYPE_IP6_ROUTE, i)); +} + +/*****************************************************************************/ + +NML3ConfigDatFlags nm_l3_config_data_get_flags(const NML3ConfigData *self); + +void nm_l3_config_data_set_flags_full(NML3ConfigData * self, + NML3ConfigDatFlags flags, + NML3ConfigDatFlags mask); + +static inline void +nm_l3_config_data_set_flags(NML3ConfigData *self, NML3ConfigDatFlags flags) +{ + nm_l3_config_data_set_flags_full(self, flags, flags); +} + +static inline void +nm_l3_config_data_unset_flags(NML3ConfigData *self, NML3ConfigDatFlags flags) +{ + nm_l3_config_data_set_flags_full(self, NM_L3_CONFIG_DAT_FLAGS_NONE, flags); +} + +/*****************************************************************************/ + +gboolean nm_l3_config_data_set_source(NML3ConfigData *self, NMIPConfigSource source); + +const NMPObject *nm_l3_config_data_get_first_obj(const NML3ConfigData *self, + NMPObjectType obj_type, + gboolean (*predicate)(const NMPObject *obj)); + +gboolean nm_l3_config_data_add_address_full(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPAddress *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject ** out_obj_new); + +static inline gboolean +nm_l3_config_data_add_address(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPAddress *pl_new) +{ + return nm_l3_config_data_add_address_full(self, + addr_family, + obj_new, + pl_new, + NM_L3_CONFIG_ADD_FLAGS_MERGE, + NULL); +} + +static inline gboolean +nm_l3_config_data_add_address_4(NML3ConfigData *self, const NMPlatformIP4Address *addr) +{ + return nm_l3_config_data_add_address(self, AF_INET, NULL, NM_PLATFORM_IP_ADDRESS_CAST(addr)); +} + +static inline gboolean +nm_l3_config_data_add_address_6(NML3ConfigData *self, const NMPlatformIP6Address *addr) +{ + return nm_l3_config_data_add_address(self, AF_INET6, NULL, NM_PLATFORM_IP_ADDRESS_CAST(addr)); +} + +gboolean nm_l3_config_data_add_route_full(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPRoute *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject ** out_obj_new, + gboolean * out_changed_best_default_route); + +static inline gboolean +nm_l3_config_data_add_route(NML3ConfigData * self, + int addr_family, + const NMPObject * obj_new, + const NMPlatformIPRoute *pl_new) +{ + return nm_l3_config_data_add_route_full(self, + addr_family, + obj_new, + pl_new, + NM_L3_CONFIG_ADD_FLAGS_MERGE, + NULL, + NULL); +} + +static inline gboolean +nm_l3_config_data_add_route_4(NML3ConfigData *self, const NMPlatformIP4Route *rt) +{ + return nm_l3_config_data_add_route(self, AF_INET, NULL, NM_PLATFORM_IP_ROUTE_CAST(rt)); +} + +static inline gboolean +nm_l3_config_data_add_route_6(NML3ConfigData *self, const NMPlatformIP6Route *rt) +{ + return nm_l3_config_data_add_route(self, AF_INET6, NULL, NM_PLATFORM_IP_ROUTE_CAST(rt)); +} + +const NMPObject *nm_l3_config_data_get_best_default_route(const NML3ConfigData *self, + int addr_family); + +gboolean nm_l3_config_data_set_mdns(NML3ConfigData *self, NMSettingConnectionMdns mdns); + +gboolean nm_l3_config_data_set_llmnr(NML3ConfigData *self, NMSettingConnectionLlmnr llmnr); + +NMIPRouteTableSyncMode nm_l3_config_data_get_route_table_sync(const NML3ConfigData *self, + int addr_family); + +gboolean nm_l3_config_data_set_route_table_sync(NML3ConfigData * self, + int addr_family, + NMIPRouteTableSyncMode route_table_sync); + +NMTernary nm_l3_config_data_get_metered(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_metered(NML3ConfigData *self, NMTernary metered); + +guint32 nm_l3_config_data_get_mtu(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu); + +guint32 nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu); + +const in_addr_t *nm_l3_config_data_get_wins(const NML3ConfigData *self, guint *out_len); + +gboolean nm_l3_config_data_add_wins(NML3ConfigData *self, in_addr_t wins); + +gconstpointer +nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, guint *out_len); + +gboolean nm_l3_config_data_add_nameserver(NML3ConfigData * self, + int addr_family, + gconstpointer /* (const NMIPAddr *) */ nameserver); + +gboolean nm_l3_config_data_clear_nameservers(NML3ConfigData *self, int addr_family); + +gboolean nm_l3_config_data_add_nis_server(NML3ConfigData *self, in_addr_t nis_server); + +const char *const * +nm_l3_config_data_get_domains(const NML3ConfigData *self, int addr_family, guint *out_len); + +gboolean nm_l3_config_data_set_nis_domain(NML3ConfigData *self, const char *nis_domain); + +gboolean nm_l3_config_data_add_domain(NML3ConfigData *self, int addr_family, const char *domain); + +const char *const * +nm_l3_config_data_get_searches(const NML3ConfigData *self, int addr_family, guint *out_len); + +gboolean nm_l3_config_data_clear_searches(NML3ConfigData *self, int addr_family); + +gboolean nm_l3_config_data_add_search(NML3ConfigData *self, int addr_family, const char *search); + +gboolean +nm_l3_config_data_add_dns_option(NML3ConfigData *self, int addr_family, const char *dns_option); + +gboolean +nm_l3_config_data_set_dns_priority(NML3ConfigData *self, int addr_family, int dns_priority); + +NMSettingIP6ConfigPrivacy nm_l3_config_data_get_ip6_privacy(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_ip6_privacy(NML3ConfigData * self, + NMSettingIP6ConfigPrivacy ip6_privacy); + +gboolean nm_l3_config_data_get_ndisc_hop_limit(const NML3ConfigData *self, int *out_val); +gboolean nm_l3_config_data_set_ndisc_hop_limit(NML3ConfigData *self, int val); + +gboolean nm_l3_config_data_get_ndisc_reachable_time_msec(const NML3ConfigData *self, + guint32 * out_val); +gboolean nm_l3_config_data_set_ndisc_reachable_time_msec(NML3ConfigData *self, guint32 val); + +gboolean nm_l3_config_data_get_ndisc_retrans_timer_msec(const NML3ConfigData *self, + guint32 * out_val); +gboolean nm_l3_config_data_set_ndisc_retrans_timer_msec(NML3ConfigData *self, guint32 val); + +struct _NMDhcpLease *nm_l3_config_data_get_dhcp_lease(const NML3ConfigData *self, int addr_family); + +gboolean +nm_l3_config_data_set_dhcp_lease(NML3ConfigData *self, int addr_family, struct _NMDhcpLease *lease); + +gboolean nm_l3_config_data_set_dhcp_lease_from_options(NML3ConfigData *self, + int addr_family, + GHashTable * options_take); + +static inline const NMIPAddr * +nmtst_l3_config_data_get_best_gateway(const NML3ConfigData *self, int addr_family) +{ + const NMPObject *rt; + + rt = nm_l3_config_data_get_best_default_route(self, addr_family); + if (!rt) + return NULL; + + return nm_platform_ip_route_get_gateway(addr_family, NMP_OBJECT_CAST_IP_ROUTE(rt)); +} + +#endif /* __NM_L3_CONFIG_DATA_H__ */ diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c new file mode 100644 index 0000000..edd6671 --- /dev/null +++ b/src/core/nm-l3-ipv4ll.c @@ -0,0 +1,1059 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include "nm-l3-ipv4ll.h" + +#include +#include + +#include "n-acd/src/n-acd.h" +#include "nm-core-utils.h" + +#define ADDR_IPV4LL_PREFIX_LEN 16 + +#define TIMED_OUT_TIME_FACTOR 5u + +/*****************************************************************************/ + +typedef enum { + TIMED_OUT_STATE_IS_NOT_TIMED_OUT, + TIMED_OUT_STATE_IS_TIMED_OUT, + TIMED_OUT_STATE_HAVE_TIMER_RUNNING, +} TimedOutState; + +struct _NML3IPv4LLRegistration { + NML3IPv4LL *self; + CList reg_lst; + guint timeout_msec; +}; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LLRegistration, self) == 0); + +struct _NML3IPv4LL { + NML3Cfg * l3cfg; + int ref_count; + in_addr_t addr; + guint reg_timeout_msec; + CList reg_lst_head; + NML3CfgCommitTypeHandle *l3cfg_commit_handle; + GSource * state_change_on_idle_source; + GSource * timed_out_source; + const NML3ConfigData * l3cd; + const NMPObject * plobj; + struct { + nm_le64_t value; + nm_le64_t generation; + } seed; + gint64 timed_out_expiry_msec; + gulong l3cfg_signal_notify_id; + NML3IPv4LLState state; + NMEtherAddr seed_mac; + NMEtherAddr mac; + bool seed_set : 1; + bool mac_set : 1; + bool notify_on_idle : 1; + bool reg_changed : 1; + bool l3cd_timeout_msec_changed : 1; + + /* not yet used. */ + bool seed_reset_generation : 1; +}; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LL, ref_count) == sizeof(gpointer)); + +#define L3CD_TAG(self) (&(((const char *) self)[1])) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_IP4 +#define _NMLOG_PREFIX_NAME "ipv4ll" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(self), \ + nm_l3cfg_get_ifindex((self)->l3cfg) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _ipv4ll_state_change_on_idle(NML3IPv4LL *self); + +static void _ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler); + +static void _ipv4ll_set_timed_out_update(NML3IPv4LL *self, TimedOutState new_state); + +/*****************************************************************************/ + +NM_UTILS_ENUM2STR_DEFINE(nm_l3_ipv4ll_state_to_string, + NML3IPv4LLState, + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_UNKNOWN, "unknown"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DISABLED, "disabled"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_WAIT_FOR_LINK, "wait-for-link"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_EXTERNAL, "external"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_PROBING, "probing"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DEFENDING, "defending"), + NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_READY, "ready"), ); + +/*****************************************************************************/ + +#define _ASSERT(self) \ + G_STMT_START \ + { \ + NML3IPv4LL *const _self = (self); \ + \ + nm_assert(NM_IS_L3_IPV4LL(_self)); \ + if (NM_MORE_ASSERTS > 5) { \ + nm_assert(_self->addr == 0u || nm_utils_ip4_address_is_link_local(_self->addr)); \ + nm_assert(!_self->l3cd || NM_IS_L3_CONFIG_DATA(_self->l3cd)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +NML3Cfg * +nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return self->l3cfg; +} + +int +nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return nm_l3cfg_get_ifindex(self->l3cfg); +} + +NMPlatform * +nm_l3_ipv4ll_get_platform(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return nm_l3cfg_get_platform(self->l3cfg); +} + +/*****************************************************************************/ + +NML3IPv4LLState +nm_l3_ipv4ll_get_state(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return self->state; +} + +static gboolean +_ipv4ll_is_timed_out(NML3IPv4LL *self) +{ + _ASSERT(self); + + return self->timed_out_expiry_msec != 0 && !self->timed_out_source; +} + +gboolean +nm_l3_ipv4ll_is_timed_out(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return _ipv4ll_is_timed_out(self); +} + +in_addr_t +nm_l3_ipv4ll_get_addr(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return self->addr; +} + +const NML3ConfigData * +nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + return self->l3cd; +} + +static void +_ipv4ll_emit_signal_notify(NML3IPv4LL *self) +{ + NML3ConfigNotifyData notify_data; + + self->notify_on_idle = FALSE; + + notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT; + notify_data.ipv4ll_event = (typeof(notify_data.ipv4ll_event)){ + .ipv4ll = self, + }; + _nm_l3cfg_emit_signal_notify(self->l3cfg, ¬ify_data); +} + +/*****************************************************************************/ + +static NML3IPv4LLRegistration * +_registration_update(NML3IPv4LL * self, + NML3IPv4LLRegistration *reg, + gboolean add, + guint timeout_msec) +{ + nm_auto_unref_l3ipv4ll NML3IPv4LL *self_unref_on_exit = NULL; + gboolean changed = FALSE; + + if (reg) { + nm_assert(!self); + _ASSERT(reg->self); + self = reg->self; + nm_assert(c_list_contains(&self->reg_lst_head, ®->reg_lst)); + nm_assert(self == nm_l3_ipv4ll_register_get_instance(reg)); + } else { + nm_assert(add); + _ASSERT(self); + } + + if (!add) { + _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: remove", NM_HASH_OBFUSCATE_PTR(reg)); + c_list_unlink_stale(®->reg_lst); + if (c_list_is_empty(&self->reg_lst_head)) + self_unref_on_exit = self; + nm_g_slice_free(reg); + reg = NULL; + goto out; + } + + if (!reg) { + reg = g_slice_new(NML3IPv4LLRegistration); + *reg = (NML3IPv4LLRegistration){ + .self = self, + .timeout_msec = timeout_msec, + }; + + if (c_list_is_empty(&self->reg_lst_head)) + nm_l3_ipv4ll_ref(self); + c_list_link_tail(&self->reg_lst_head, ®->reg_lst); + changed = TRUE; + _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: add (timeout_msec=%u)", + NM_HASH_OBFUSCATE_PTR(reg), + timeout_msec); + } else { + if (reg->timeout_msec != timeout_msec) { + reg->timeout_msec = timeout_msec; + changed = TRUE; + } + if (changed) { + _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: update (timeout_msec=%u)", + NM_HASH_OBFUSCATE_PTR(reg), + timeout_msec); + } + } + +out: + if (changed) { + self->reg_changed = TRUE; + _ipv4ll_state_change(self, FALSE); + } + return reg; +} + +NML3IPv4LLRegistration * +nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec) +{ + return _registration_update(self, NULL, TRUE, timeout_msec); +} + +NML3IPv4LLRegistration * +nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg, guint timeout_msec) +{ + return _registration_update(NULL, reg, TRUE, timeout_msec); +} + +NML3IPv4LLRegistration * +nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg) +{ + return _registration_update(NULL, reg, FALSE, 0); +} + +/*****************************************************************************/ + +static gboolean +_ip4_address_is_link_local(const NMPlatformIP4Address *a) +{ + nm_assert(a); + + return nm_utils_ip4_address_is_link_local(a->address) && a->plen == ADDR_IPV4LL_PREFIX_LEN + && a->address == a->peer_address; +} + +static gboolean +_acd_info_is_good(const NML3AcdAddrInfo *acd_info) +{ + if (!acd_info) + return TRUE; + + switch (acd_info->state) { + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + return TRUE; + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + return FALSE; + } + nm_assert_not_reached(); + return FALSE; +} + +/*****************************************************************************/ + +static NMPlatformIP4Address * +_l3cd_config_plat_init_addr(NMPlatformIP4Address *a, int ifindex, in_addr_t addr) +{ + nm_assert(nm_utils_ip4_address_is_link_local(addr)); + + *a = (NMPlatformIP4Address){ + .ifindex = ifindex, + .address = addr, + .peer_address = addr, + .plen = ADDR_IPV4LL_PREFIX_LEN, + .addr_source = NM_IP_CONFIG_SOURCE_IP4LL, + }; + return a; +} + +static NMPlatformIP4Route * +_l3cd_config_plat_init_route(NMPlatformIP4Route *r, int ifindex) +{ + *r = (NMPlatformIP4Route){ + .ifindex = ifindex, + .network = htonl(0xE0000000u), + .plen = 4, + .rt_source = NM_IP_CONFIG_SOURCE_IP4LL, + .table_any = TRUE, + .metric_any = TRUE, + }; + return r; +} + +static const NML3ConfigData * +_l3cd_config_create(int ifindex, in_addr_t addr, NMDedupMultiIndex *multi_idx) +{ + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + NMPlatformIP4Address a; + NMPlatformIP4Route r; + + nm_assert(nm_utils_ip4_address_is_link_local(addr)); + nm_assert(ifindex > 0); + nm_assert(multi_idx); + + l3cd = nm_l3_config_data_new(multi_idx, ifindex); + nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_IP4LL); + + nm_l3_config_data_add_address_4(l3cd, _l3cd_config_plat_init_addr(&a, ifindex, addr)); + nm_l3_config_data_add_route_4(l3cd, _l3cd_config_plat_init_route(&r, ifindex)); + + return nm_l3_config_data_seal(g_steal_pointer(&l3cd)); +} + +static in_addr_t +_l3cd_config_get_addr(const NML3ConfigData *l3cd) +{ + NMDedupMultiIter iter; + const NMPlatformIP4Address *pladdr; + + if (!l3cd) + return 0u; + + nm_l3_config_data_iter_ip4_address_for_each (&iter, l3cd, &pladdr) { + const in_addr_t addr = pladdr->address; + + nm_assert(_ip4_address_is_link_local(pladdr)); +#if NM_MORE_ASSERTS > 10 + { + nm_auto_unref_l3cd const NML3ConfigData *l3cd2 = NULL; + + l3cd2 = _l3cd_config_create(nm_l3_config_data_get_ifindex(l3cd), + addr, + nm_l3_config_data_get_multi_idx(l3cd)); + nm_assert(nm_l3_config_data_equal(l3cd2, l3cd)); + } +#endif + return addr; + } + + return nm_assert_unreachable_val(0u); +} + +/*****************************************************************************/ + +static void +_ipv4ll_addrgen(NML3IPv4LL *self, gboolean generate_new_addr) +{ + CSipHash state; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + gboolean seed_changed = FALSE; + in_addr_t addr_new; + guint64 h; + + _ASSERT(self); + + /* MAC_HASH_KEY is the same as used by systemd. */ +#define MAC_HASH_KEY \ + ((const guint8[16]){0xdf, \ + 0x04, \ + 0x22, \ + 0x98, \ + 0x3f, \ + 0xad, \ + 0x14, \ + 0x52, \ + 0xf9, \ + 0x87, \ + 0x2e, \ + 0xd1, \ + 0x9c, \ + 0x70, \ + 0xe2, \ + 0xf2}) + + if (self->mac_set && (!self->seed_set || !nm_ether_addr_equal(&self->mac, &self->seed_mac))) { + /* systemd's ipv4ll library by default only hashes the MAC address (as we do here). + * This is also what previous versions of NetworkManager did (whenn using sd_ipv4ll). + * + * On the other hand, systemd-networkd uses net_get_name_persistent() of the device + * mixed with /etc/machine-id. + * + * See also: https://tools.ietf.org/html/rfc3927#section-2.1 + * + * FIXME(l3cfg): At this point, maybe we should also mix it with nm_utils_host_id_get(). + * This would get the behavior closer to what systemd-networkd does. + * Don't do that for now, because it would be a change in behavior compared + * to earlier versions of NetworkManager. */ + + c_siphash_init(&state, MAC_HASH_KEY); + c_siphash_append(&state, self->mac.ether_addr_octet, ETH_ALEN); + h = c_siphash_finalize(&state); + + _LOGT("addr-gen: %sset seed (for " NM_ETHER_ADDR_FORMAT_STR ")", + self->seed_set ? "re" : "", + NM_ETHER_ADDR_FORMAT_VAL(&self->mac)); + + self->seed_set = TRUE; + self->seed_mac = self->mac; + self->seed.generation = htole64(0); + self->seed.value = htole64(h); + self->seed_reset_generation = FALSE; + self->addr = 0u; + + seed_changed = TRUE; + } + + if (!self->seed_set) { + /* we have no seed set (and consequently no MAC address set either). + * We cannot generate an address. */ + nm_assert(self->addr == 0u); + return; + } + + nm_assert(seed_changed || self->seed.generation != htole64(0u)); + + if (self->seed_reset_generation) { + _LOGT("addr-gen: reset seed (generation only)"); + self->seed.generation = htole64(0); + self->addr = 0u; + seed_changed = TRUE; + } + + if (!seed_changed && !generate_new_addr) { + /* neither did the caller request a new address, nor was the seed changed. The current + * address is still to be used. */ + nm_assert(nm_utils_ip4_address_is_link_local(self->addr)); + return; + } + +gen_addr: + +#define PICK_HASH_KEY \ + ((const guint8[16]){0x15, \ + 0xac, \ + 0x82, \ + 0xa6, \ + 0xd6, \ + 0x3f, \ + 0x49, \ + 0x78, \ + 0x98, \ + 0x77, \ + 0x5d, \ + 0x0c, \ + 0x69, \ + 0x02, \ + 0x94, \ + 0x0b}) + + h = c_siphash_hash(PICK_HASH_KEY, (const guint8 *) &self->seed, sizeof(self->seed)); + + self->seed.generation = htole64(le64toh(self->seed.generation) + 1u); + + addr_new = htonl(h & UINT32_C(0x0000FFFF)) | NM_IPV4LL_NETWORK; + + if (self->addr == addr_new || NM_IN_SET(ntohl(addr_new) & 0x0000FF00u, 0x0000u, 0xFF00u)) + goto gen_addr; + + nm_assert(nm_utils_ip4_address_is_link_local(addr_new)); + + _LOGT("addr-gen: set address %s", _nm_utils_inet4_ntop(addr_new, sbuf_addr)); + self->addr = addr_new; +} + +/*****************************************************************************/ + +static void +_ipv4ll_update_link(NML3IPv4LL *self, const NMPObject *plobj) +{ + char sbuf[ETH_ALEN * 3]; + nm_auto_nmpobj const NMPObject *pllink_old = NULL; + const NMEtherAddr * mac_new; + gboolean changed; + + if (self->plobj == plobj) + return; + + pllink_old = g_steal_pointer(&self->plobj); + self->plobj = nmp_object_ref(plobj); + + mac_new = NULL; + if (plobj) { + const NMPlatformLink *pllink = NMP_OBJECT_CAST_LINK(plobj); + + if (pllink->l_address.len == ETH_ALEN) + mac_new = &pllink->l_address.ether_addr; + } + + changed = FALSE; + if (!mac_new) { + if (self->mac_set) { + changed = TRUE; + self->mac_set = FALSE; + } + } else { + if (!self->mac_set || !nm_ether_addr_equal(mac_new, &self->mac)) { + changed = TRUE; + self->mac_set = TRUE; + self->mac = *mac_new; + } + } + + if (changed) { + _LOGT("mac changed: %s", + self->mac_set ? _nm_utils_hwaddr_ntoa(&self->mac, ETH_ALEN, TRUE, sbuf, sizeof(sbuf)) + : "unset"); + } +} + +/*****************************************************************************/ + +static void +_l3cd_config_add(NML3IPv4LL *self) +{ + nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + gboolean changed; + + _ASSERT(self); + nm_assert(self->addr != 0u); + nm_assert(self->reg_timeout_msec > 0u); + + if (_l3cd_config_get_addr(self->l3cd) != self->addr) { + l3cd = _l3cd_config_create(nm_l3_ipv4ll_get_ifindex(self), + self->addr, + nm_l3cfg_get_multi_idx(self->l3cfg)); + nm_assert(!nm_l3_config_data_equal(l3cd, self->l3cd)); + changed = TRUE; + } else + changed = FALSE; + + if (!changed && !self->l3cd_timeout_msec_changed) + return; + + self->l3cd_timeout_msec_changed = FALSE; + + _LOGT("add l3cd config with %s (acd-timeout %u msec%s)", + _nm_utils_inet4_ntop(self->addr, sbuf_addr), + self->reg_timeout_msec, + changed ? "" : " changed"); + + if (changed) { + NM_SWAP(&l3cd, &self->l3cd); + self->notify_on_idle = TRUE; + } + + if (!nm_l3cfg_add_config(self->l3cfg, + L3CD_TAG(self), + TRUE, + self->l3cd, + NM_L3CFG_CONFIG_PRIORITY_IPV4LL, + 0, + 0, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + 0, + 0, + NM_L3_ACD_DEFEND_TYPE_ONCE, + self->reg_timeout_msec, + NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD)) + nm_assert_not_reached(); + + self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg, + NM_L3_CFG_COMMIT_TYPE_ASSUME, + self->l3cfg_commit_handle); + nm_l3cfg_commit_on_idle_schedule(self->l3cfg); +} + +static gboolean +_l3cd_config_remove(NML3IPv4LL *self) +{ + nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL; + + nm_assert(NM_IS_L3_IPV4LL(self)); + + if (!self->l3cd) + return FALSE; + + _LOGT("remove l3cd config"); + + self->notify_on_idle = TRUE; + + l3cd = g_steal_pointer(&self->l3cd); + if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd)) + nm_assert_not_reached(); + + nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle)); + nm_l3cfg_commit_on_idle_schedule(self->l3cfg); + return TRUE; +} + +/*****************************************************************************/ + +static const NMPlatformIP4Address * +_ipv4ll_platform_ip4_address_lookup(NML3IPv4LL *self, in_addr_t addr) +{ + const NMPlatformIP4Address *pladdr; + + if (addr == 0u) + return NULL; + + nm_assert(nm_utils_ip4_address_is_link_local(addr)); + + pladdr = nm_platform_ip4_address_get(nm_l3_ipv4ll_get_platform(self), + nm_l3_ipv4ll_get_ifindex(self), + addr, + ADDR_IPV4LL_PREFIX_LEN, + addr); + + nm_assert(!pladdr || pladdr->address == addr); + nm_assert(!pladdr || _ip4_address_is_link_local(pladdr)); + return pladdr; +} + +static const NML3AcdAddrInfo * +_ipv4ll_l3cfg_get_acd_addr_info(NML3IPv4LL *self, in_addr_t addr) +{ + if (addr == 0u) + return NULL; + + nm_assert(nm_utils_ip4_address_is_link_local(addr)); + return nm_l3cfg_get_acd_addr_info(self->l3cfg, addr); +} + +static const NMPlatformIP4Address * +_ipv4ll_platform_find_addr(NML3IPv4LL *self, const NML3AcdAddrInfo **out_acd_info) +{ + const NMPlatformIP4Address *addr_without_acd_info = NULL; + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject * obj; + const NML3AcdAddrInfo * acd_info; + const NMPlatformIP4Address *addr; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, nm_l3_ipv4ll_get_ifindex(self)); + nm_platform_iter_obj_for_each (&iter, nm_l3_ipv4ll_get_platform(self), &lookup, &obj) { + addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj); + if (!_ip4_address_is_link_local(addr)) + continue; + + acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, addr->address); + if (!_acd_info_is_good(acd_info)) + continue; + + if (acd_info) { + /* We have a good acd_info. We won't find a better one. Return it. */ + NM_SET_OUT(out_acd_info, acd_info); + return addr; + } + + if (!addr_without_acd_info) { + /* remember a potential candidate address that has no acd_info. */ + addr_without_acd_info = addr; + } + } + + if (addr_without_acd_info) { + NM_SET_OUT(out_acd_info, NULL); + return addr_without_acd_info; + } + + return NULL; +} + +/*****************************************************************************/ + +static gboolean +_ipv4ll_set_timed_out_timeout_cb(gpointer user_data) +{ + NML3IPv4LL *self = user_data; + + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_TIMED_OUT); + if (self->notify_on_idle) + _ipv4ll_emit_signal_notify(self); + return G_SOURCE_REMOVE; +} + +static void +_ipv4ll_set_timed_out_update(NML3IPv4LL *self, TimedOutState new_state) +{ + gboolean before; + + before = _ipv4ll_is_timed_out(self); + + switch (new_state) { + case TIMED_OUT_STATE_IS_TIMED_OUT: + if (self->timed_out_expiry_msec == 0) { + nm_assert(!self->timed_out_source); + self->timed_out_expiry_msec = 1; + } + nm_clear_g_source_inst(&self->timed_out_source); + break; + case TIMED_OUT_STATE_IS_NOT_TIMED_OUT: + self->timed_out_expiry_msec = 0; + nm_clear_g_source_inst(&self->timed_out_source); + break; + case TIMED_OUT_STATE_HAVE_TIMER_RUNNING: + { + gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + guint timeout_msec; + gint64 expiry_msec; + + nm_assert(self->reg_timeout_msec > 0u); + + timeout_msec = nm_mult_clamped_u(TIMED_OUT_TIME_FACTOR, self->reg_timeout_msec); + expiry_msec = now_msec + timeout_msec; + + if (self->timed_out_expiry_msec == 0 || self->timed_out_expiry_msec < expiry_msec) { + self->timed_out_expiry_msec = expiry_msec; + nm_clear_g_source_inst(&self->timed_out_source); + self->timed_out_source = nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _ipv4ll_set_timed_out_timeout_cb, + self, + NULL); + g_source_attach(self->timed_out_source, NULL); + } + break; + } + } + + if (before != _ipv4ll_is_timed_out(self)) { + self->notify_on_idle = TRUE; + _LOGT("state: set timed-out-is-bad=%d", (!before)); + } +} + +static gboolean +_ipv4ll_set_state(NML3IPv4LL *self, NML3IPv4LLState state) +{ + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + char sbuf100[100]; + + if (self->state == state) + return FALSE; + self->state = state; + self->notify_on_idle = TRUE; + _LOGT("state: set state %s (addr=%s)", + nm_l3_ipv4ll_state_to_string(state, sbuf100, sizeof(sbuf100)), + _nm_utils_inet4_ntop(self->addr, sbuf_addr)); + return TRUE; +} + +static void +_ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler) +{ + nm_auto_unref_l3ipv4ll NML3IPv4LL *self_keep_alive = NULL; + const NMPlatformIP4Address * pladdr; + const NML3AcdAddrInfo * acd_info; + gboolean generate_new_addr; + NML3IPv4LLState new_state; + in_addr_t addr0; + NML3IPv4LLRegistration * reg; + + _ASSERT(self); + + self_keep_alive = nm_l3_ipv4ll_ref(self); + + nm_clear_g_source_inst(&self->state_change_on_idle_source); + + addr0 = self->addr; + + if (self->reg_changed) { + guint timeout_msec = self->reg_timeout_msec; + + self->reg_changed = FALSE; + + if (c_list_is_empty(&self->reg_lst_head)) + timeout_msec = 0; + else { + timeout_msec = G_MAXUINT; + c_list_for_each_entry (reg, &self->reg_lst_head, reg_lst) { + if (reg->timeout_msec < timeout_msec) + timeout_msec = reg->timeout_msec; + if (reg->timeout_msec == 0) + break; + } + } + if (self->reg_timeout_msec != timeout_msec) { + self->reg_timeout_msec = timeout_msec; + self->l3cd_timeout_msec_changed = TRUE; + } + } + + if (self->reg_timeout_msec == 0) { + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT); + if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_DISABLED)) + _l3cd_config_remove(self); + goto out_notify; + } + + if (!self->mac_set) { + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING); + if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_WAIT_FOR_LINK)) + _l3cd_config_remove(self); + else + nm_assert(!self->l3cd); + goto out_notify; + } + + if (self->state <= NM_L3_IPV4LL_STATE_EXTERNAL) { + pladdr = _ipv4ll_platform_ip4_address_lookup(self, self->addr); + if (pladdr) { + if (!_acd_info_is_good(_ipv4ll_l3cfg_get_acd_addr_info(self, self->addr))) + pladdr = NULL; + } + if (!pladdr) + pladdr = _ipv4ll_platform_find_addr(self, NULL); + + if (pladdr) { + /* we have an externally configured address. Check whether we can use it. */ + self->addr = pladdr->address; + self->notify_on_idle = TRUE; + _ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_EXTERNAL); + _l3cd_config_add(self); + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT); + goto out_notify; + } + } + + generate_new_addr = FALSE; + while (TRUE) { + _ipv4ll_addrgen(self, generate_new_addr); + acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, self->addr); + if (_acd_info_is_good(acd_info)) + break; + generate_new_addr = TRUE; + } + + nm_assert(_acd_info_is_good(acd_info)); + switch (acd_info ? acd_info->state : NM_L3_ACD_ADDR_STATE_INIT) { + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_PROBING: + new_state = NM_L3_IPV4LL_STATE_PROBING; + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING); + goto out_is_good_1; + case NM_L3_ACD_ADDR_STATE_READY: + new_state = NM_L3_IPV4LL_STATE_READY; + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING); + goto out_is_good_1; + case NM_L3_ACD_ADDR_STATE_DEFENDING: + new_state = NM_L3_IPV4LL_STATE_DEFENDING; + _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT); + goto out_is_good_1; + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + nm_assert_not_reached(); + goto out_notify; + } + nm_assert_not_reached(); + goto out_notify; +out_is_good_1: + _ipv4ll_set_state(self, new_state); + _l3cd_config_add(self); + if (self->addr != addr0) + self->notify_on_idle = TRUE; + goto out_notify; + +out_notify: + if (self->notify_on_idle) { + if (is_on_idle_handler) + _ipv4ll_emit_signal_notify(self); + else + _ipv4ll_state_change_on_idle(self); + } +} + +static gboolean +_ipv4ll_state_change_on_idle_cb(gpointer user_data) +{ + NML3IPv4LL *self = user_data; + + _ipv4ll_state_change(self, TRUE); + return G_SOURCE_REMOVE; +} + +static void +_ipv4ll_state_change_on_idle(NML3IPv4LL *self) +{ + nm_assert(NM_IS_L3_IPV4LL(self)); + + if (!self->state_change_on_idle_source) { + self->state_change_on_idle_source = + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _ipv4ll_state_change_on_idle_cb, self, NULL); + g_source_attach(self->state_change_on_idle_source, NULL); + } +} + +/*****************************************************************************/ + +static void +_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv4LL *self) +{ + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) { + /* NMl3Cfg only reloads the platform link during the idle handler. Pick it up now. */ + _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE)); + + /* theoretically, this even is already on an idle handler. However, we share + * the call with other signal handlers, so at this point we don't want to + * emit additional signals. Thus pass %FALSE to _ipv4ll_state_change(). */ + _ipv4ll_state_change(self, FALSE); + return; + } + + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { + if (self->l3cd + && nm_l3_acd_addr_info_find_track_info(¬ify_data->acd_event.info, + L3CD_TAG(self), + self->l3cd, + NULL)) { + _ipv4ll_state_change(self, FALSE); + } + return; + } +} + +/*****************************************************************************/ + +NML3IPv4LL * +nm_l3_ipv4ll_new(NML3Cfg *l3cfg) +{ + NML3IPv4LL *self; + + g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL); + + self = g_slice_new(NML3IPv4LL); + *self = (NML3IPv4LL){ + .l3cfg = g_object_ref(l3cfg), + .ref_count = 1, + .reg_lst_head = C_LIST_INIT(self->reg_lst_head), + .l3cfg_commit_handle = NULL, + .state_change_on_idle_source = NULL, + .l3cd = NULL, + .plobj = NULL, + .addr = 0u, + .state = NM_L3_IPV4LL_STATE_DISABLED, + .reg_timeout_msec = 0, + .notify_on_idle = TRUE, + .l3cfg_signal_notify_id = + g_signal_connect(l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self), + .seed_set = FALSE, + .seed_reset_generation = FALSE, + }; + + _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(l3cfg)); + + _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE)); + _ipv4ll_state_change(self, FALSE); + return self; +} + +NML3IPv4LL * +nm_l3_ipv4ll_ref(NML3IPv4LL *self) +{ + if (!self) + return NULL; + + _ASSERT(self); + + nm_assert(self->ref_count < G_MAXINT); + self->ref_count++; + return self; +} + +void +nm_l3_ipv4ll_unref(NML3IPv4LL *self) +{ + if (!self) + return; + + _ASSERT(self); + + if (--self->ref_count > 0) + return; + + if (nm_l3cfg_get_ipv4ll(self->l3cfg) == self) + _nm_l3cfg_unregister_ipv4ll(self->l3cfg); + + _LOGT("finalize"); + + nm_assert(c_list_is_empty(&self->reg_lst_head)); + + if (self->l3cd) { + nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL; + + l3cd = g_steal_pointer(&self->l3cd); + if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd)) + nm_assert_not_reached(); + + nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle)); + nm_l3cfg_commit_on_idle_schedule(self->l3cfg); + } else + nm_assert(!self->l3cfg_commit_handle); + + nm_clear_g_source_inst(&self->state_change_on_idle_source); + nm_clear_g_source_inst(&self->timed_out_source); + + nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id); + + g_clear_object(&self->l3cfg); + nmp_object_unref(self->plobj); + nm_g_slice_free(self); +} diff --git a/src/core/nm-l3-ipv4ll.h b/src/core/nm-l3-ipv4ll.h new file mode 100644 index 0000000..b39762a --- /dev/null +++ b/src/core/nm-l3-ipv4ll.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_L3_IPV4LL_H__ +#define __NM_L3_IPV4LL_H__ + +#include "nm-l3cfg.h" + +/*****************************************************************************/ + +typedef enum _nm_packed { + NM_L3_IPV4LL_STATE_UNKNOWN, + NM_L3_IPV4LL_STATE_DISABLED, + NM_L3_IPV4LL_STATE_WAIT_FOR_LINK, + NM_L3_IPV4LL_STATE_EXTERNAL, + NM_L3_IPV4LL_STATE_PROBING, + NM_L3_IPV4LL_STATE_READY, + NM_L3_IPV4LL_STATE_DEFENDING, +} NML3IPv4LLState; + +const char *nm_l3_ipv4ll_state_to_string(NML3IPv4LLState val, char *buf, gsize len); + +static inline gboolean +nm_l3_ipv4ll_state_is_good(NML3IPv4LLState state) +{ + switch (state) { + case NM_L3_IPV4LL_STATE_UNKNOWN: + case NM_L3_IPV4LL_STATE_DISABLED: + case NM_L3_IPV4LL_STATE_WAIT_FOR_LINK: + case NM_L3_IPV4LL_STATE_PROBING: + return FALSE; + case NM_L3_IPV4LL_STATE_EXTERNAL: + case NM_L3_IPV4LL_STATE_READY: + case NM_L3_IPV4LL_STATE_DEFENDING: + return TRUE; + } + return nm_assert_unreachable_val(FALSE); +} + +/*****************************************************************************/ + +typedef struct _NML3IPv4LL NML3IPv4LL; + +static inline gboolean +NM_IS_L3_IPV4LL(const NML3IPv4LL *self) +{ + nm_assert(!self + || (NM_IS_L3CFG(*((NML3Cfg **) self)) + && (*((int *) (((char *) self) + sizeof(gpointer)))) > 0)); + return !!self; +} + +NML3IPv4LL *nm_l3_ipv4ll_new(NML3Cfg *self); + +NML3IPv4LL *nm_l3_ipv4ll_ref(NML3IPv4LL *self); +void nm_l3_ipv4ll_unref(NML3IPv4LL *self); + +NM_AUTO_DEFINE_FCN0(NML3IPv4LL *, _nm_auto_unref_l3ipv4ll, nm_l3_ipv4ll_unref); +#define nm_auto_unref_l3ipv4ll nm_auto(_nm_auto_unref_l3ipv4ll) + +/*****************************************************************************/ + +NML3Cfg *nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self); + +int nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self); + +NMPlatform *nm_l3_ipv4ll_get_platform(NML3IPv4LL *self); + +/*****************************************************************************/ + +/* By default, NML3IPv4LL is disabled. You also need to register (enable) it. + * The intent of this API is that multiple users can enable/register their own + * settings, and NML3IPv4LL will mediate the different requests. + * + * Also, by setting timeout_msec to zero, NML3IPv4LL is disabled again (zero + * wins over all timeouts). This is useful if you do DHCP and IPv4LL on the + * same interface. You possibly want to disable IPv4LL if you have a valid + * DHCP lease. By registering a timeout_msec to zero, you can disable IPv4LL. + * + * Also, a registration keeps the NML3IPv4LL instance alive (it also takes + * a reference). */ + +typedef struct _NML3IPv4LLRegistration NML3IPv4LLRegistration; + +NML3IPv4LLRegistration *nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec); + +NML3IPv4LLRegistration *nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg, + guint timeout_msec); + +NML3IPv4LLRegistration *nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg); + +NM_AUTO_DEFINE_FCN0(NML3IPv4LLRegistration *, + _nm_auto_remove_l3ipv4ll_registration, + nm_l3_ipv4ll_register_remove); +#define nm_auto_remove_l3ipv4ll_registration nm_auto(_nm_auto_remove_l3ipv4ll_registration) + +static inline NML3IPv4LL * +nm_l3_ipv4ll_register_get_instance(NML3IPv4LLRegistration *reg) +{ + NML3IPv4LL *ipv4ll; + + if (!reg) + return NULL; + ipv4ll = *((NML3IPv4LL **) reg); + nm_assert(NM_IS_L3_IPV4LL(ipv4ll)); + return ipv4ll; +} + +/*****************************************************************************/ + +NML3IPv4LLState nm_l3_ipv4ll_get_state(NML3IPv4LL *self); + +gboolean nm_l3_ipv4ll_is_timed_out(NML3IPv4LL *self); + +in_addr_t nm_l3_ipv4ll_get_addr(NML3IPv4LL *self); + +const NML3ConfigData *nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self); + +#endif /* __NM_L3_IPV4LL_H__ */ diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c new file mode 100644 index 0000000..44023aa --- /dev/null +++ b/src/core/nm-l3cfg.c @@ -0,0 +1,3805 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include "nm-l3cfg.h" + +#include +#include +#include +#include + +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "nm-netns.h" +#include "n-acd/src/n-acd.h" +#include "nm-l3-ipv4ll.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227); + +#define ACD_SUPPORTED_ETH_ALEN ETH_ALEN +#define ACD_ENSURE_RATELIMIT_MSEC ((guint32) 4000u) +#define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32)(1000u + ACD_ENSURE_RATELIMIT_MSEC)) +#define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC ((guint32) 1000u) +#define ACD_MAX_TIMEOUT_MSEC ((guint32) 30000u) +#define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u) +#define ACD_WAIT_TIME_CONFLICT_RESTART_MSEC ((guint32) 120000u) +#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 30000u) +#define ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC ((guint32) 30000u) + +static gboolean +ACD_ADDR_SKIP(in_addr_t addr) +{ + return addr == 0u; +} + +#define ACD_TRACK_FMT \ + "[l3cd=" NM_HASH_OBFUSCATE_PTR_FMT ",obj=" NM_HASH_OBFUSCATE_PTR_FMT \ + ",tag=" NM_HASH_OBFUSCATE_PTR_FMT "]" +#define ACD_TRACK_PTR2(l3cd, obj, tag) \ + NM_HASH_OBFUSCATE_PTR(l3cd), NM_HASH_OBFUSCATE_PTR(obj), NM_HASH_OBFUSCATE_PTR(tag) +#define ACD_TRACK_PTR(acd_track) \ + ACD_TRACK_PTR2((acd_track)->l3cd, (acd_track)->obj, (acd_track)->tag) + +typedef enum { + ACD_STATE_CHANGE_MODE_NACD_CONFLICT = N_ACD_EVENT_CONFLICT, + ACD_STATE_CHANGE_MODE_NACD_DEFENDED = N_ACD_EVENT_DEFENDED, + ACD_STATE_CHANGE_MODE_NACD_DOWN = N_ACD_EVENT_DOWN, + ACD_STATE_CHANGE_MODE_NACD_READY = N_ACD_EVENT_READY, + ACD_STATE_CHANGE_MODE_NACD_USED = N_ACD_EVENT_USED, + + ACD_STATE_CHANGE_MODE_INIT = _N_ACD_EVENT_N, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, + ACD_STATE_CHANGE_MODE_LINK_NOW_UP, + ACD_STATE_CHANGE_MODE_INSTANCE_RESET, + ACD_STATE_CHANGE_MODE_TIMEOUT, +} AcdStateChangeMode; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3AcdAddrInfo, addr) == 0); + +typedef struct { + NML3AcdAddrInfo info; + + CList acd_lst; + CList acd_event_notify_lst; + + NAcdProbe *nacd_probe; + + GSource *acd_data_timeout_source; + + /* see probing_timeout_msec. */ + gint64 probing_timestamp_msec; + + gint64 last_defendconflict_timestamp_msec; + + guint n_track_infos_alloc; + + /* This is only relevant while in state NM_L3_ACD_ADDR_STATE_PROBING. It's the + * duration for how long we probe, and @probing_timestamp_msec is the + * timestamp when we start probing. */ + guint32 probing_timeout_msec; + + NMEtherAddr last_conflict_addr; + + NML3AcdDefendType acd_defend_type_desired : 3; + NML3AcdDefendType acd_defend_type_current : 3; + bool acd_defend_type_is_active : 1; + + bool track_infos_changed : 1; +} AcdData; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0); + +struct _NML3CfgCommitTypeHandle { + CList commit_type_lst; + NML3CfgCommitType commit_type; +}; + +typedef struct { + const NML3ConfigData *l3cd; + NML3ConfigMergeFlags merge_flags; + union { + struct { + guint32 default_route_table_6; + guint32 default_route_table_4; + }; + guint32 default_route_table_x[2]; + }; + union { + struct { + guint32 default_route_metric_6; + guint32 default_route_metric_4; + }; + guint32 default_route_metric_x[2]; + }; + union { + struct { + guint32 default_route_penalty_6; + guint32 default_route_penalty_4; + }; + guint32 default_route_penalty_x[2]; + }; + gconstpointer tag_confdata; + guint64 pseudo_timestamp_confdata; + int priority_confdata; + guint32 acd_timeout_msec_confdata; + NML3AcdDefendType acd_defend_type_confdata : 3; + bool dirty_confdata : 1; +} L3ConfigData; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NML3Cfg, PROP_NETNS, PROP_IFINDEX, ); + +enum { + SIGNAL_NOTIFY, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct _NML3CfgPrivate { + GArray *property_emit_list; + GArray *l3_config_datas; + + NML3IPv4LL *ipv4ll; + + const NML3ConfigData *combined_l3cd_merged; + + const NML3ConfigData *combined_l3cd_commited; + + CList commit_type_lst_head; + + GHashTable *routes_temporary_not_available_hash; + + GHashTable *externally_removed_objs_hash; + + GHashTable *acd_ipv4_addresses_on_link; + + GHashTable *acd_lst_hash; + CList acd_lst_head; + + CList acd_event_notify_lst_head; + + NAcd * nacd; + GSource *nacd_source; + + GSource *nacd_event_down_source; + gint64 nacd_event_down_ratelimited_until_msec; + + /* This is for rate-limiting the creation of nacd instance. */ + GSource *nacd_instance_ensure_retry; + + GSource *commit_on_idle_source; + + guint64 pseudo_timestamp_counter; + + union { + struct { + guint externally_removed_objs_cnt_addresses_6; + guint externally_removed_objs_cnt_addresses_4; + }; + guint externally_removed_objs_cnt_addresses_x[2]; + }; + + union { + struct { + guint externally_removed_objs_cnt_routes_6; + guint externally_removed_objs_cnt_routes_4; + }; + guint externally_removed_objs_cnt_routes_x[2]; + }; + + union { + struct { + GPtrArray *last_addresses_6; + GPtrArray *last_addresses_4; + }; + GPtrArray *last_addresses_x[2]; + }; + + union { + struct { + GPtrArray *last_routes_6; + GPtrArray *last_routes_4; + }; + GPtrArray *last_routes_x[2]; + }; + + guint routes_temporary_not_available_id; + + gint8 commit_reentrant_count; + + bool commit_type_update_sticky : 1; + + bool acd_is_pending : 1; + + bool nacd_acd_not_supported : 1; + bool acd_ipv4_addresses_on_link_has : 1; + + bool changed_configs_configs : 1; + bool changed_configs_acd_state : 1; +} NML3CfgPrivate; + +struct _NML3CfgClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG_PREFIX_NAME "l3cfg" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(self), \ + nm_l3cfg_get_ifindex(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define _LOGT_acd(acd_data, ...) \ + G_STMT_START \ + { \ + char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + _LOGT("acd[%s, %s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _nm_utils_inet4_ntop((acd_data)->info.addr, _sbuf_acd), \ + _l3_acd_addr_state_to_string((acd_data)->info.state) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle); + +static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type); + +static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self); + +static gboolean _acd_has_valid_link(const NMPObject *obj, + const guint8 ** out_addr_bin, + gboolean * out_acd_not_supported); + +static void +_l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_data_notify); + +static void _l3_acd_data_state_change(NML3Cfg * self, + AcdData * acd_data, + AcdStateChangeMode mode, + const NMEtherAddr *sender, + gint64 * p_now_msec); + +static AcdData *_l3_acd_data_find(NML3Cfg *self, in_addr_t addr); + +/*****************************************************************************/ + +static NM_UTILS_ENUM2STR_DEFINE(_l3_cfg_commit_type_to_string, + NML3CfgCommitType, + NM_UTILS_ENUM2STR(NM_L3_CFG_COMMIT_TYPE_AUTO, "auto"), + NM_UTILS_ENUM2STR(NM_L3_CFG_COMMIT_TYPE_NONE, "none"), + NM_UTILS_ENUM2STR(NM_L3_CFG_COMMIT_TYPE_ASSUME, "assume"), + NM_UTILS_ENUM2STR(NM_L3_CFG_COMMIT_TYPE_UPDATE, "update"), + NM_UTILS_ENUM2STR(NM_L3_CFG_COMMIT_TYPE_REAPPLY, "reapply"), ); + +static NM_UTILS_ENUM2STR_DEFINE( + _l3_config_notify_type_to_string, + NML3ConfigNotifyType, + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, "ipv4ll-event"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, + "routes-temporary-not-available-expired"), + NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), ); + +static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string, + NML3AcdDefendType, + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ALWAYS, "always"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NEVER, "never"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NONE, "none"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), ); + +static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd, + NML3AcdDefendType, + int, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ALWAYS, + N_ACD_DEFEND_ALWAYS), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ONCE, N_ACD_DEFEND_ONCE), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_NEVER, N_ACD_DEFEND_NEVER), + NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); + +static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string, + NML3AcdAddrState, + const char *, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_CONFLICT, "conflict"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_READY, "ready"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_DEFENDING, "defending"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_INIT, "init"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_PROBING, "probing"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, + "external-removed"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_USED, "used"), ); + +/*****************************************************************************/ + +static const char * +_l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, + char * sbuf, + gsize sbuf_size) +{ + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + char sbuf100[100]; + char * s = sbuf; + gsize l = sbuf_size; + in_addr_t addr4; + + nm_assert(sbuf); + nm_assert(sbuf_size > 0); + + _l3_config_notify_type_to_string(notify_data->notify_type, s, l); + nm_utils_strbuf_seek_end(&s, &l); + + switch (notify_data->notify_type) { + case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: + nm_utils_strbuf_append(&s, + &l, + ", addr=%s, state=%s", + _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr), + _l3_acd_addr_state_to_string(notify_data->acd_event.info.state)); + break; + case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: + nm_utils_strbuf_append( + &s, + &l, + ", obj-type=%s, change=%s, obj=", + NMP_OBJECT_GET_CLASS(notify_data->platform_change.obj)->obj_type_name, + nm_platform_signal_change_type_to_string(notify_data->platform_change.change_type)); + nmp_object_to_string(notify_data->platform_change.obj, NMP_OBJECT_TO_STRING_PUBLIC, s, l); + break; + case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: + nm_utils_strbuf_append(&s, + &l, + ", obj-type-flags=0x%x", + notify_data->platform_change_on_idle.obj_type_flags); + break; + case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT: + nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll)); + addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll); + nm_utils_strbuf_append( + &s, + &l, + ", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s", + NM_HASH_OBFUSCATE_PTR(notify_data->ipv4ll_event.ipv4ll), + NM_PRINT_FMT_QUOTED2(addr4 != 0, ", addr=", _nm_utils_inet4_ntop(addr4, sbuf_addr), ""), + nm_l3_ipv4ll_state_to_string(nm_l3_ipv4ll_get_state(notify_data->ipv4ll_event.ipv4ll), + sbuf100, + sizeof(sbuf100))); + break; + default: + break; + } + + return sbuf; +} + +void +_nm_l3cfg_emit_signal_notify(NML3Cfg *self, const NML3ConfigNotifyData *notify_data) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + + nm_assert(notify_data); + nm_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type)); + nm_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM); + + _LOGT("emit signal (%s)", _l3_config_notify_data_to_string(notify_data, sbuf, sizeof(sbuf))); + + g_signal_emit(self, signals[SIGNAL_NOTIFY], 0, notify_data); +} + +static void +_nm_l3cfg_emit_signal_notify_simple(NML3Cfg *self, NML3ConfigNotifyType notify_type) +{ + NML3ConfigNotifyData notify_data; + + notify_data.notify_type = notify_type; + _nm_l3cfg_emit_signal_notify(self, ¬ify_data); +} + +/*****************************************************************************/ + +static void +_l3_changed_configs_set_dirty(NML3Cfg *self) +{ + _LOGT("IP configuration changed (mark dirty)"); + self->priv.p->changed_configs_configs = TRUE; + self->priv.p->changed_configs_acd_state = TRUE; +} + +/*****************************************************************************/ + +static void +_l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self, + in_addr_t addr, + gboolean add /* or else remove */) +{ + AcdData *acd_data; + + acd_data = _l3_acd_data_find(self, addr); + + if (add) { + if (self->priv.p->acd_ipv4_addresses_on_link) + g_hash_table_add(self->priv.p->acd_ipv4_addresses_on_link, GUINT_TO_POINTER(addr)); + else + self->priv.p->acd_ipv4_addresses_on_link_has = FALSE; + if (acd_data) + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + NULL, + NULL); + return; + } + + /* when we remove an IPv4 address from kernel, we cannot know whether the same address is still + * present (with a different prefix length or peer). So we cannot be sure whether we removed + * the only address, or whether more are still present. All we can do is forget about the + * cached addresses, and fetch them new the next time we need the information. */ + nm_clear_pointer(&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref); + self->priv.p->acd_ipv4_addresses_on_link_has = FALSE; + if (acd_data) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, + NULL, + NULL); + } +} + +static gboolean +_l3_acd_ipv4_addresses_on_link_contains(NML3Cfg *self, in_addr_t addr) +{ + if (!self->priv.p->acd_ipv4_addresses_on_link) { + if (self->priv.p->acd_ipv4_addresses_on_link_has) + return FALSE; + self->priv.p->acd_ipv4_addresses_on_link_has = TRUE; + self->priv.p->acd_ipv4_addresses_on_link = + nm_platform_ip4_address_addr_to_hash(self->priv.platform, self->priv.ifindex); + if (!self->priv.p->acd_ipv4_addresses_on_link) + return FALSE; + } + return g_hash_table_contains(self->priv.p->acd_ipv4_addresses_on_link, GUINT_TO_POINTER(addr)); +} + +/*****************************************************************************/ + +static NAcdProbe * +_nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gpointer user_data) +{ + nm_auto(n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL; + NAcdProbe * probe; + int r; + + nm_assert(self); + + if (!self->priv.p->nacd) + return NULL; + + if (addr == 0) + return nm_assert_unreachable_val(NULL); + + r = n_acd_probe_config_new(&probe_config); + if (r) + return NULL; + + n_acd_probe_config_set_ip(probe_config, (struct in_addr){addr}); + n_acd_probe_config_set_timeout(probe_config, timeout_msec); + + r = n_acd_probe(self->priv.p->nacd, &probe, probe_config); + if (r) + return NULL; + + n_acd_probe_set_userdata(probe, user_data); + return probe; +} + +/*****************************************************************************/ + +static guint * +_l3cfg_externally_removed_objs_counter(NML3Cfg *self, NMPObjectType obj_type) +{ + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + return &self->priv.p->externally_removed_objs_cnt_addresses_4; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return &self->priv.p->externally_removed_objs_cnt_addresses_6; + case NMP_OBJECT_TYPE_IP4_ROUTE: + return &self->priv.p->externally_removed_objs_cnt_routes_4; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return &self->priv.p->externally_removed_objs_cnt_routes_6; + default: + return nm_assert_unreachable_val(NULL); + } +} + +static void +_l3cfg_externally_removed_objs_drop(NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + self->priv.p->externally_removed_objs_cnt_addresses_4 = 0; + self->priv.p->externally_removed_objs_cnt_addresses_6 = 0; + self->priv.p->externally_removed_objs_cnt_routes_4 = 0; + self->priv.p->externally_removed_objs_cnt_routes_6 = 0; + if (nm_g_hash_table_size(self->priv.p->externally_removed_objs_hash) > 0) + _LOGD("externally-removed: untrack all"); + nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); +} + +static void +_l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self) +{ + GHashTableIter h_iter; + const NMPObject *obj; + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + + nm_assert(NM_IS_L3CFG(self)); + + if (!self->priv.p->externally_removed_objs_hash) + return; + + if (!self->priv.p->combined_l3cd_commited) { + _l3cfg_externally_removed_objs_drop(self); + return; + } + + g_hash_table_iter_init(&h_iter, self->priv.p->externally_removed_objs_hash); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj, NULL)) { + if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) { + /* The object is no longer tracked in the configuration. + * The externally_removed_objs_hash is to prevent adding entires that were + * removed externally, so if we don't plan to add the entry, we no longer need to track + * it. */ + _LOGD("externally-removed: untrack %s", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--; + g_hash_table_iter_remove(&h_iter); + } + } +} + +static void +_l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboolean is_removed) +{ + char sbuf[1000]; + + nm_assert(NM_IS_L3CFG(self)); + + if (!self->priv.p->combined_l3cd_commited) + return; + + if (!is_removed) { + /* the object is still (or again) present. It no longer gets hidden. */ + if (self->priv.p->externally_removed_objs_hash) { + const NMPObject *obj2; + gpointer x_val; + + if (g_hash_table_steal_extended(self->priv.p->externally_removed_objs_hash, + obj, + (gpointer *) &obj2, + &x_val)) { + (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj2))))--; + _LOGD("externally-removed: untrack %s", + nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + nmp_object_unref(obj2); + } + } + return; + } + + if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) { + /* we don't care about this object, so there is nothing to hide hide */ + return; + } + + if (G_UNLIKELY(!self->priv.p->externally_removed_objs_hash)) { + self->priv.p->externally_removed_objs_hash = + g_hash_table_new_full((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + NULL); + } + + if (g_hash_table_add(self->priv.p->externally_removed_objs_hash, + (gpointer) nmp_object_ref(obj))) { + (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))++; + _LOGD("externally-removed: track %s", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + } +} + +static void +_l3cfg_externally_removed_objs_pickup(NML3Cfg *self, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + NMDedupMultiIter iter; + const NMPObject *obj; + + if (!self->priv.p->combined_l3cd_commited) + return; + + nm_l3_config_data_iter_obj_for_each (&iter, + self->priv.p->combined_l3cd_commited, + &obj, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { + if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)) + _l3cfg_externally_removed_objs_track(self, obj, TRUE); + } + nm_l3_config_data_iter_obj_for_each (&iter, + self->priv.p->combined_l3cd_commited, + &obj, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { + if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)) + _l3cfg_externally_removed_objs_track(self, obj, TRUE); + } +} + +static gboolean +_l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointer o, + gpointer user_data) +{ + const NMPObject *obj = o; + GHashTable * externally_removed_objs_hash = user_data; + + if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS + && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) + return FALSE; + + return !nm_g_hash_table_contains(externally_removed_objs_hash, obj); +} + +/*****************************************************************************/ + +static void +_load_link(NML3Cfg *self, gboolean initial) +{ + nm_auto_nmpobj const NMPObject *obj_old = NULL; + const NMPObject * obj; + const char * ifname; + const char * ifname_old; + gboolean nacd_changed; + gboolean nacd_new_valid; + gboolean nacd_old_valid; + const guint8 * nacd_old_addr = NULL; + const guint8 * nacd_new_addr = NULL; + gboolean nacd_link_now_up; + AcdData * acd_data; + + if (initial) { + obj = nm_platform_link_get_obj(self->priv.platform, self->priv.ifindex, TRUE); + self->priv.plobj_next = nmp_object_ref(obj); + } else { + obj = self->priv.plobj_next; + nm_assert(obj == nm_platform_link_get_obj(self->priv.platform, self->priv.ifindex, TRUE)); + } + + if (initial && obj == self->priv.plobj) + return; + + obj_old = g_steal_pointer(&self->priv.plobj); + self->priv.plobj = nmp_object_ref(obj); + + if (obj && NM_FLAGS_HAS(NMP_OBJECT_CAST_LINK(obj)->n_ifi_flags, IFF_UP) + && (!obj_old || !NM_FLAGS_HAS(NMP_OBJECT_CAST_LINK(obj_old)->n_ifi_flags, IFF_UP))) + nacd_link_now_up = TRUE; + else + nacd_link_now_up = FALSE; + + nacd_changed = FALSE; + nacd_old_valid = _acd_has_valid_link(obj_old, &nacd_old_addr, NULL); + nacd_new_valid = _acd_has_valid_link(obj, &nacd_new_addr, NULL); + if (self->priv.p->nacd_instance_ensure_retry) { + if (nacd_new_valid + && (!nacd_old_valid + || memcmp(nacd_new_addr, nacd_old_addr, ACD_SUPPORTED_ETH_ALEN) == 0)) + nacd_changed = TRUE; + } else if (self->priv.p->nacd) { + if (!nacd_new_valid) + nacd_changed = TRUE; + else if (!nacd_old_valid) + nacd_changed = nm_assert_unreachable_val(TRUE); + else if (memcmp(nacd_old_addr, nacd_new_addr, ACD_SUPPORTED_ETH_ALEN) != 0) + nacd_changed = TRUE; + } else if (nacd_new_valid) + nacd_changed = TRUE; + ifname_old = nmp_object_link_get_ifname(obj_old); + ifname = nmp_object_link_get_ifname(self->priv.plobj); + + if (initial) { + _LOGT("link ifname changed: %s%s%s (initial)", NM_PRINT_FMT_QUOTE_STRING(ifname)); + } else if (!nm_streq0(ifname, ifname_old)) { + _LOGT("link ifname changed: %s%s%s (was %s%s%s)", + NM_PRINT_FMT_QUOTE_STRING(ifname), + NM_PRINT_FMT_QUOTE_STRING(ifname_old)); + } + + if (nacd_changed) { + if (!c_list_is_empty(&self->priv.p->acd_lst_head)) + _LOGT("acd: link change causes restart of ACD"); + _l3_acd_nacd_instance_reset(self, NM_TERNARY_FALSE, TRUE); + } else if (nacd_link_now_up) { + if (!c_list_is_empty(&self->priv.p->acd_lst_head)) { + gint64 now_msec = 0; + + _LOGT("acd: link up requires are re-initialize of ACD probes"); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_LINK_NOW_UP, + NULL, + &now_msec); + } + } + } +} + +/*****************************************************************************/ + +void +_nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags) +{ + NML3ConfigNotifyData notify_data; + + if (self->priv.plobj_next != self->priv.plobj) + _load_link(self, FALSE); + + notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE; + notify_data.platform_change_on_idle = (typeof(notify_data.platform_change_on_idle)){ + .obj_type_flags = obj_type_flags, + }; + _nm_l3cfg_emit_signal_notify(self, ¬ify_data); + + _nm_l3cfg_emit_signal_notify_acd_event_all(self); + + if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ROUTE))) + _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE); + if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ROUTE))) + _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE); +} + +void +_nm_l3cfg_notify_platform_change(NML3Cfg * self, + NMPlatformSignalChangeType change_type, + const NMPObject * obj) +{ + NML3ConfigNotifyData notify_data; + NMPObjectType obj_type; + + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + obj_type = NMP_OBJECT_GET_TYPE(obj); + + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + { + const NMPObject *plobj; + + plobj = (change_type != NM_PLATFORM_SIGNAL_REMOVED) ? obj : NULL; + nm_assert(plobj == nm_platform_link_get_obj(self->priv.platform, self->priv.ifindex, TRUE)); + nmp_object_ref_set(&self->priv.plobj_next, plobj); + break; + } + case NMP_OBJECT_TYPE_IP4_ADDRESS: + _l3_acd_ipv4_addresses_on_link_update(self, + NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address, + change_type != NM_PLATFORM_SIGNAL_REMOVED); + /* fall-through */ + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + _l3cfg_externally_removed_objs_track(self, obj, change_type == NM_PLATFORM_SIGNAL_REMOVED); + default: + break; + } + + notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE; + notify_data.platform_change = (typeof(notify_data.platform_change)){ + .obj = obj, + .change_type = change_type, + }; + _nm_l3cfg_emit_signal_notify(self, ¬ify_data); + + nm_assert(NMP_OBJECT_IS_VALID(obj)); +} + +/*****************************************************************************/ + +typedef struct { + GObject * target_obj; + const GParamSpec * target_property; + NML3CfgPropertyEmitType emit_type; +} PropertyEmitData; + +static void +_property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type) +{ + gs_free PropertyEmitData *collected_heap = NULL; + PropertyEmitData * collected = NULL; + PropertyEmitData * emit_data; + guint num; + guint i; + guint j; + + if (!self->priv.p->property_emit_list) + return; + + num = 0; + emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0); + for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) { + if (emit_data->emit_type == emit_type) { + collected = emit_data; + num++; + } + } + + if (num == 0) + return; + + if (num == 1) { + g_object_notify_by_pspec(collected->target_obj, (GParamSpec *) collected->target_property); + return; + } + + if (num < 300u / sizeof(*collected)) + collected = g_alloca(sizeof(PropertyEmitData) * num); + else { + collected_heap = g_new(PropertyEmitData, num); + collected = collected_heap; + } + + emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0); + for (i = 0, j = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) { + if (emit_data->emit_type == emit_type) { + collected[j++] = *emit_data; + g_object_ref(collected->target_obj); + } + } + + nm_assert(j == num); + + for (i = 0; i < num; i++) { + g_object_notify_by_pspec(collected[i].target_obj, + (GParamSpec *) collected[i].target_property); + if (i > 0) + g_object_unref(collected[i].target_obj); + } +} + +void +nm_l3cfg_property_emit_register(NML3Cfg * self, + GObject * target_obj, + const GParamSpec * target_property, + NML3CfgPropertyEmitType emit_type) +{ + PropertyEmitData *emit_data; + guint i; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(G_IS_OBJECT(target_obj)); + nm_assert(target_property); + nm_assert(NM_IN_SET(emit_type, + NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE, + NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE)); + nm_assert(target_property + == nm_g_object_class_find_property_from_gtype(G_OBJECT_TYPE(target_obj), + target_property->name)); + + if (!self->priv.p->property_emit_list) + self->priv.p->property_emit_list = g_array_new(FALSE, FALSE, sizeof(PropertyEmitData)); + else { + emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0); + for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) { + if (emit_data->target_obj != target_obj + || emit_data->target_property != target_property) + continue; + nm_assert(emit_data->emit_type == emit_type); + emit_data->emit_type = emit_type; + return; + } + } + + emit_data = nm_g_array_append_new(self->priv.p->property_emit_list, PropertyEmitData); + *emit_data = (PropertyEmitData){ + .target_obj = target_obj, + .target_property = target_property, + .emit_type = emit_type, + }; +} + +void +nm_l3cfg_property_emit_unregister(NML3Cfg * self, + GObject * target_obj, + const GParamSpec *target_property) +{ + PropertyEmitData *emit_data; + guint i; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(G_IS_OBJECT(target_obj)); + nm_assert(!target_property + || target_property + == nm_g_object_class_find_property_from_gtype(G_OBJECT_TYPE(target_obj), + target_property->name)); + + if (!self->priv.p->property_emit_list) + return; + + for (i = self->priv.p->property_emit_list->len; i > 0; i--) { + emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, i); + + if (emit_data->target_obj != target_obj) + continue; + if (target_property && emit_data->target_property != target_property) + continue; + + g_array_remove_index_fast(self->priv.p->property_emit_list, i); + + if (target_property) { + /* if a target-property is given, we don't have another entry in + * the list. */ + return; + } + } +} + +/*****************************************************************************/ + +gboolean +nm_l3cfg_get_acd_is_pending(NML3Cfg *self) +{ + g_return_val_if_fail(NM_IS_L3CFG(self), FALSE); + + return self->priv.p->acd_is_pending; +} + +static gboolean +_acd_track_data_is_not_dirty(const NML3AcdAddrTrackInfo *acd_track) +{ + return acd_track && !acd_track->_priv.acd_dirty_track; +} + +static void +_acd_track_data_clear(NML3AcdAddrTrackInfo *acd_track) +{ + nm_l3_config_data_unref(acd_track->l3cd); + nmp_object_unref(acd_track->obj); +} + +static void +_acd_data_free(AcdData *acd_data) +{ + nm_assert(acd_data->info.n_track_infos == 0u); + + n_acd_probe_free(acd_data->nacd_probe); + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + c_list_unlink_stale(&acd_data->acd_lst); + c_list_unlink_stale(&acd_data->acd_event_notify_lst); + g_free((NML3AcdAddrTrackInfo *) acd_data->info.track_infos); + nm_g_slice_free(acd_data); +} + +static guint +_acd_data_collect_tracks_data(const AcdData * acd_data, + NMTernary dirty_selector, + guint32 * out_best_acd_timeout_msec, + NML3AcdDefendType *out_best_acd_defend_type) +{ + NML3AcdDefendType best_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_NONE; + guint32 best_acd_timeout_msec = G_MAXUINT32; + guint n = 0; + guint i; + + for (i = 0; i < acd_data->info.n_track_infos; i++) { + const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i]; + + if (dirty_selector != NM_TERNARY_DEFAULT) { + if ((!!dirty_selector) != (!!acd_track->_priv.acd_dirty_track)) + continue; + } + n++; + if (best_acd_timeout_msec > acd_track->_priv.acd_timeout_msec_track) + best_acd_timeout_msec = acd_track->_priv.acd_timeout_msec_track; + if (best_acd_defend_type < acd_track->_priv.acd_defend_type_track) + best_acd_defend_type = acd_track->_priv.acd_defend_type_track; + } + + nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE); + nm_assert(best_acd_defend_type <= NM_L3_ACD_DEFEND_TYPE_ALWAYS); + + NM_SET_OUT(out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u); + NM_SET_OUT(out_best_acd_defend_type, best_acd_defend_type); + return n; +} + +static NML3AcdAddrTrackInfo * +_acd_data_find_track(const AcdData * acd_data, + const NML3ConfigData *l3cd, + const NMPObject * obj, + gconstpointer tag) +{ + guint i; + + for (i = 0; i < acd_data->info.n_track_infos; i++) { + const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i]; + + if (acd_track->obj == obj && acd_track->l3cd == l3cd && acd_track->tag == tag) + return (NML3AcdAddrTrackInfo *) acd_track; + } + + return NULL; +} + +/*****************************************************************************/ + +static gboolean +_acd_has_valid_link(const NMPObject *obj, + const guint8 ** out_addr_bin, + gboolean * out_acd_not_supported) +{ + const NMPlatformLink *link; + const guint8 * addr_bin; + gsize addr_len; + + if (!obj) { + NM_SET_OUT(out_acd_not_supported, FALSE); + return FALSE; + } + + link = NMP_OBJECT_CAST_LINK(obj); + + addr_bin = nmp_link_address_get(&link->l_address, &addr_len); + if (addr_len != ACD_SUPPORTED_ETH_ALEN) { + NM_SET_OUT(out_acd_not_supported, TRUE); + return FALSE; + } + + NM_SET_OUT(out_acd_not_supported, FALSE); + NM_SET_OUT(out_addr_bin, addr_bin); + return TRUE; +} + +static gboolean +_l3_acd_nacd_event_down_timeout_cb(gpointer user_data) +{ + NML3Cfg *self = user_data; + AcdData *acd_data; + gint64 now_msec = 0; + + _LOGT("acd: message possibly dropped due to device down (handle events)"); + nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL, &now_msec); + _nm_l3cfg_emit_signal_notify_acd_event_all(self); + return G_SOURCE_REMOVE; +} + +static gboolean +_l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) +{ + NML3Cfg *self = user_data; + gboolean success = FALSE; + int r; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(self->priv.p->nacd); + + r = n_acd_dispatch(self->priv.p->nacd); + if (!NM_IN_SET(r, 0, N_ACD_E_PREEMPTED)) { + _LOGT("acd: dispatch failed with error %d", r); + goto out; + } + + while (TRUE) { + NMEtherAddr sender_addr_data; + const NMEtherAddr *sender_addr; + AcdData * acd_data; + NAcdEvent * event; + + r = n_acd_pop_event(self->priv.p->nacd, &event); + if (r) { + _LOGT("acd: pop-event failed with error %d", r); + goto out; + } + if (!event) { + success = TRUE; + goto out; + } + + switch (event->event) { + case N_ACD_EVENT_READY: + n_acd_probe_get_userdata(event->ready.probe, (void **) &acd_data); + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, NULL, NULL); + break; + case N_ACD_EVENT_USED: + case N_ACD_EVENT_DEFENDED: + case N_ACD_EVENT_CONFLICT: + { +#define _acd_event_payload_with_sender(event) \ + ({ \ + NAcdEvent *_event = (event); \ + \ + nm_assert(event); \ + nm_assert(NM_IN_SET(event->event, \ + N_ACD_EVENT_USED, \ + N_ACD_EVENT_DEFENDED, \ + N_ACD_EVENT_CONFLICT)); \ + nm_assert(&_event->used == &_event->defended); \ + nm_assert(&_event->used == &_event->conflict); \ + &_event->used; \ + }) + + n_acd_probe_get_userdata(_acd_event_payload_with_sender(event)->probe, + (void **) &acd_data); + + if (_acd_event_payload_with_sender(event)->n_sender == ETH_ALEN) { + G_STATIC_ASSERT_EXPR(_nm_alignof(NMEtherAddr) == 1); + nm_assert(_acd_event_payload_with_sender(event)->sender); + memcpy(&sender_addr_data, _acd_event_payload_with_sender(event)->sender, ETH_ALEN); + sender_addr = &sender_addr_data; + } else { + nm_assert_not_reached(); + sender_addr = &nm_ether_addr_zero; + } + + _l3_acd_data_state_change(self, + acd_data, + (AcdStateChangeMode) event->event, + sender_addr, + NULL); + break; + } + case N_ACD_EVENT_DOWN: + if (!self->priv.p->nacd_event_down_source) { + gint64 now_msec; + guint32 timeout_msec; + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + if (self->priv.p->nacd_event_down_ratelimited_until_msec > 0 + && now_msec < self->priv.p->nacd_event_down_ratelimited_until_msec) + timeout_msec = self->priv.p->nacd_event_down_ratelimited_until_msec - now_msec; + else { + timeout_msec = 0; + self->priv.p->nacd_event_down_ratelimited_until_msec = now_msec + 2000; + } + _LOGT("acd: message possibly dropped due to device down (schedule handling event " + "in %u msec)", + timeout_msec); + self->priv.p->nacd_event_down_source = + nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_event_down_timeout_cb, + self, + NULL); + g_source_attach(self->priv.p->nacd_event_down_source, NULL); + } + break; + default: + _LOGE("acd: unexpected event %u. Ignore", event->event); + nm_assert_not_reached(); + break; + } + + /* We are on an idle handler, and the n-acd events are expected to be independent. So, after + * each event emit all queued AcdEvent signals. */ + _nm_l3cfg_emit_signal_notify_acd_event_all(self); + } + + nm_assert_not_reached(); + +out: + if (!success) { + /* Something is seriously wrong with our nacd instance. We handle that by resetting the + * ACD instance. */ + _l3_acd_nacd_instance_reset(self, NM_TERNARY_TRUE, TRUE); + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +_l3_acd_nacd_instance_ensure_retry_cb(gpointer user_data) +{ + NML3Cfg *self = user_data; + + nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); + + _l3_changed_configs_set_dirty(self); + nm_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO); + return G_SOURCE_REMOVE; +} + +static void +_l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_data_notify) +{ + nm_assert(NM_IS_L3CFG(self)); + + if (self->priv.p->nacd) { + _LOGT("acd: clear nacd instance"); + self->priv.p->nacd = n_acd_unref(self->priv.p->nacd); + } + nm_clear_g_source_inst(&self->priv.p->nacd_source); + nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); + + if (c_list_is_empty(&self->priv.p->acd_lst_head)) + start_timer = NM_TERNARY_DEFAULT; + + switch (start_timer) { + case NM_TERNARY_FALSE: + _l3_changed_configs_set_dirty(self); + nm_l3cfg_commit_on_idle_schedule(self); + break; + case NM_TERNARY_TRUE: + self->priv.p->nacd_instance_ensure_retry = + nm_g_timeout_source_new_seconds(ACD_ENSURE_RATELIMIT_MSEC / 1000u, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_instance_ensure_retry_cb, + self, + NULL); + g_source_attach(self->priv.p->nacd_instance_ensure_retry, NULL); + break; + case NM_TERNARY_DEFAULT: + break; + } + + if (acd_data_notify) { + AcdData *acd_data; + gint64 now_msec = 0; + + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_INSTANCE_RESET, + NULL, + &now_msec); + } + } +} + +static NAcd * +_l3_acd_nacd_instance_ensure(NML3Cfg *self, gboolean *out_acd_not_supported) +{ + nm_auto(n_acd_config_freep) NAcdConfig *config = NULL; + nm_auto(n_acd_unrefp) NAcd * nacd = NULL; + const guint8 * addr_bin; + gboolean acd_not_supported; + gboolean valid; + int fd; + int r; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(self->priv.ifindex > 0); + +again: + if (G_LIKELY(self->priv.p->nacd)) { + NM_SET_OUT(out_acd_not_supported, FALSE); + return self->priv.p->nacd; + } + + if (self->priv.p->nacd_instance_ensure_retry) { + /* we just tried to create an instance and failed. We are rate-limited, + * don't yet try again. */ + NM_SET_OUT(out_acd_not_supported, self->priv.p->nacd_acd_not_supported); + return NULL; + } + + valid = _acd_has_valid_link(self->priv.plobj, &addr_bin, &acd_not_supported); + if (!valid) + goto failed_create_acd; + + nm_assert(!acd_not_supported); + + r = n_acd_config_new(&config); + if (r) + goto failed_create_acd; + + n_acd_config_set_ifindex(config, self->priv.ifindex); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, addr_bin, ACD_SUPPORTED_ETH_ALEN); + + r = n_acd_new(&nacd, config); + if (r) + goto failed_create_acd; + + self->priv.p->nacd = g_steal_pointer(&nacd); + + n_acd_get_fd(self->priv.p->nacd, &fd); + + self->priv.p->nacd_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, _l3_acd_nacd_event, self, NULL); + nm_g_source_attach(self->priv.p->nacd_source, NULL); + + NM_SET_OUT(out_acd_not_supported, FALSE); + return self->priv.p->nacd; + +failed_create_acd: + /* is-internal-error means that we failed to create the NAcd instance. Most likely due + * to being unable to create a file descriptor. Anyway, something is seriously wrong here. + * + * Otherwise, the MAC address might just not be suitable (ETH_ALEN) or we might have + * not NMPlatformLink. In that case, it means the interface is currently not ready to + * do acd. */ + self->priv.p->nacd_acd_not_supported = acd_not_supported; + _l3_acd_nacd_instance_reset(self, NM_TERNARY_TRUE, FALSE); + goto again; +} + +static NAcdProbe * +_l3_acd_nacd_instance_create_probe(NML3Cfg * self, + in_addr_t addr, + guint32 timeout_msec, + gpointer user_data, + gboolean * out_acd_not_supported, + const char **out_failure_reason) +{ + gboolean acd_not_supported; + NAcdProbe *probe; + + if (!_l3_acd_nacd_instance_ensure(self, &acd_not_supported)) { + NM_SET_OUT(out_acd_not_supported, acd_not_supported); + if (acd_not_supported) + NM_SET_OUT(out_failure_reason, "interface not suitable for ACD"); + else + NM_SET_OUT(out_failure_reason, "failure to create nacd instance"); + return NULL; + } + + nm_assert(!acd_not_supported); + NM_SET_OUT(out_acd_not_supported, FALSE); + + probe = _nm_n_acd_data_probe_new(self, addr, timeout_msec, user_data); + if (!probe) { + NM_SET_OUT(out_failure_reason, "failure to create nacd probe"); + return NULL; + } + + NM_SET_OUT(out_failure_reason, NULL); + return probe; +} + +static void +_l3_acd_data_prune_one(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */) +{ + NML3AcdAddrTrackInfo *acd_tracks; + guint i; + guint j; + + acd_tracks = (NML3AcdAddrTrackInfo *) acd_data->info.track_infos; + j = 0; + for (i = 0; i < acd_data->info.n_track_infos; i++) { + NML3AcdAddrTrackInfo *acd_track = &acd_tracks[i]; + + /* If not "all" is requested, we only delete the dirty ones + * (and mark the survivors as dirty right away). */ + if (!all && !acd_track->_priv.acd_dirty_track) { + acd_track->_priv.acd_dirty_track = TRUE; + if (j != i) + acd_tracks[j] = *acd_track; + j++; + continue; + } + + _LOGT_acd(acd_data, "untrack " ACD_TRACK_FMT "", ACD_TRACK_PTR(acd_track)); + + _acd_track_data_clear(acd_track); + } + + acd_data->info.n_track_infos = j; + if (j > 0) + return; + + _LOGT_acd(acd_data, "removed"); + if (!g_hash_table_remove(self->priv.p->acd_lst_hash, acd_data)) + nm_assert_not_reached(); + _acd_data_free(acd_data); +} + +static void +_l3_acd_data_prune(NML3Cfg *self, gboolean all /* or only dirty */) +{ + AcdData *acd_data_safe; + AcdData *acd_data; + + c_list_for_each_entry_safe (acd_data, acd_data_safe, &self->priv.p->acd_lst_head, acd_lst) + _l3_acd_data_prune_one(self, acd_data, all); +} + +static AcdData * +_l3_acd_data_find(NML3Cfg *self, in_addr_t addr) +{ + return nm_g_hash_table_lookup(self->priv.p->acd_lst_hash, &addr); +} + +static gboolean +_l3_acd_data_defendconflict_warning_ratelimited(AcdData *acd_data, gint64 *p_now_msec) +{ + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + if (acd_data->last_defendconflict_timestamp_msec == 0 + || acd_data->last_defendconflict_timestamp_msec + > *p_now_msec - ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC) { + acd_data->last_defendconflict_timestamp_msec = *p_now_msec; + return FALSE; + } + return TRUE; +} + +static void +_l3_acd_data_add(NML3Cfg * self, + const NML3ConfigData *l3cd, + const NMPObject * obj, + gconstpointer tag, + NML3AcdDefendType acd_defend_type, + guint32 acd_timeout_msec) +{ + in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; + NML3AcdAddrTrackInfo *acd_track; + AcdData * acd_data; + const char * track_mode; + char sbuf100[100]; + + if (ACD_ADDR_SKIP(addr)) + return; + + acd_data = _l3_acd_data_find(self, addr); + + if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC) { + /* we limit the maximum timeout. Otherwise we have to handle integer overflow + * when adding timeouts. */ + acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC; + } + + if (!acd_data) { + if (G_UNLIKELY(!self->priv.p->acd_lst_hash)) { + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, info.addr) == 0); + self->priv.p->acd_lst_hash = g_hash_table_new(nm_puint32_hash, nm_puint32_equals); + } + + acd_data = g_slice_new(AcdData); + *acd_data = (AcdData){ + .info = + { + .l3cfg = self, + .addr = addr, + .state = NM_L3_ACD_ADDR_STATE_INIT, + .n_track_infos = 0, + .track_infos = NULL, + }, + .n_track_infos_alloc = 0, + .acd_event_notify_lst = C_LIST_INIT(acd_data->acd_event_notify_lst), + .probing_timestamp_msec = 0, + .acd_defend_type_desired = NM_L3_ACD_DEFEND_TYPE_NONE, + .acd_defend_type_current = NM_L3_ACD_DEFEND_TYPE_NONE, + .acd_defend_type_is_active = FALSE, + }; + c_list_link_tail(&self->priv.p->acd_lst_head, &acd_data->acd_lst); + if (!g_hash_table_add(self->priv.p->acd_lst_hash, acd_data)) + nm_assert_not_reached(); + acd_track = NULL; + } else + acd_track = _acd_data_find_track(acd_data, l3cd, obj, tag); + + if (!acd_track) { + if (acd_data->info.n_track_infos >= acd_data->n_track_infos_alloc) { + acd_data->n_track_infos_alloc = NM_MAX(2u, acd_data->n_track_infos_alloc * 2u); + acd_data->info.track_infos = + g_realloc((gpointer) acd_data->info.track_infos, + acd_data->n_track_infos_alloc * sizeof(acd_data->info.track_infos[0])); + } + acd_track = + (NML3AcdAddrTrackInfo *) &acd_data->info.track_infos[acd_data->info.n_track_infos++]; + *acd_track = (NML3AcdAddrTrackInfo){ + .l3cd = nm_l3_config_data_ref(l3cd), + .obj = nmp_object_ref(obj), + .tag = tag, + ._priv.acd_dirty_track = FALSE, + ._priv.acd_defend_type_track = acd_defend_type, + ._priv.acd_timeout_msec_track = acd_timeout_msec, + }; + track_mode = "new"; + } else { + nm_assert(acd_track->_priv.acd_dirty_track); + acd_track->_priv.acd_dirty_track = FALSE; + if (acd_track->_priv.acd_timeout_msec_track != acd_timeout_msec + || acd_track->_priv.acd_defend_type_track != acd_defend_type) { + acd_track->_priv.acd_defend_type_track = acd_defend_type; + acd_track->_priv.acd_timeout_msec_track = acd_timeout_msec; + track_mode = "update"; + } else + return; + } + + acd_data->track_infos_changed = TRUE; + _LOGT_acd(acd_data, + "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)", + ACD_TRACK_PTR(acd_track), + acd_timeout_msec, + _l3_acd_defend_type_to_string(acd_track->_priv.acd_defend_type_track, + sbuf100, + sizeof(sbuf100)), + track_mode); +} + +static void +_l3_acd_data_add_all(NML3Cfg * self, + const L3ConfigData *const *infos, + guint infos_len, + gboolean reapply) +{ + AcdData *acd_data; + guint i_info; + gint64 now_msec = 0; + guint i; + + if (NM_MORE_ASSERTS > 5) { + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + nm_assert(acd_data->info.n_track_infos > 0u); + for (i = 0; i < acd_data->info.n_track_infos; i++) + nm_assert(acd_data->info.track_infos[i]._priv.acd_dirty_track); + } + } + + /* First we add/track all the relevant addresses for ACD. */ + for (i_info = 0; i_info < infos_len; i_info++) { + const L3ConfigData *info = infos[i_info]; + NMDedupMultiIter iter; + const NMPObject * obj; + + nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) { + _l3_acd_data_add(self, + info->l3cd, + obj, + info->tag_confdata, + info->acd_defend_type_confdata, + info->acd_timeout_msec_confdata); + } + } + + /* Then we do a pre-flight check, whether some of the acd_data entries can already + * move forward to automatically pass ACD. That is the case if acd_timeout_msec + * is zero (to disable ACD) or if the address is already configured on the + * interface. */ + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + reapply ? ACD_STATE_CHANGE_MODE_INIT_REAPPLY + : ACD_STATE_CHANGE_MODE_INIT, + NULL, + &now_msec); + } +} + +static gboolean +_l3_acd_data_timeout_cb(gpointer user_data) +{ + AcdData *acd_data = user_data; + NML3Cfg *self = acd_data->info.l3cfg; + + nm_assert(NM_IS_L3CFG(self)); + + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL, NULL); + return G_SOURCE_REMOVE; +} + +static void +_l3_acd_data_timeout_schedule(AcdData *acd_data, gint64 timeout_msec) +{ + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + acd_data->acd_data_timeout_source = + nm_g_timeout_source_new(NM_CLAMP((gint64) 0, timeout_msec, (gint64) G_MAXUINT), + G_PRIORITY_DEFAULT, + _l3_acd_data_timeout_cb, + acd_data, + NULL); + g_source_attach(acd_data->acd_data_timeout_source, NULL); +} + +static void +_l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec) +{ + gint64 expiry_msec; + gint64 timeout_msec; + + nm_assert(acd_data); + nm_assert(now_msec > 0); + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING); + nm_assert(!acd_data->nacd_probe); + nm_assert(acd_data->probing_timeout_msec > 0); + nm_assert(acd_data->probing_timestamp_msec > 0); + + expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC; + + timeout_msec = NM_MAX(0, expiry_msec - now_msec); + + if (timeout_msec > 1500) { + /* we poll at least every 1.5 seconds to re-check the state. */ + timeout_msec = 1500; + } + + _l3_acd_data_timeout_schedule(acd_data, timeout_msec); +} + +static void +_nm_l3cfg_emit_signal_notify_acd_event(NML3Cfg *self, AcdData *acd_data) +{ + gs_free NML3AcdAddrTrackInfo *track_infos_clone = NULL; + NML3ConfigNotifyData notify_data; + NML3AcdAddrInfo * info; + guint i; + + nm_assert(acd_data); + nm_assert(acd_data->info.state > NM_L3_ACD_ADDR_STATE_INIT); + nm_assert(acd_data->info.n_track_infos > 0); + + notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT; + notify_data.acd_event = (typeof(notify_data.acd_event)){ + .info = acd_data->info, + }; + + /* we need to clone the track-data, because the callee is allowed to add/remove + * configs. This means, the event data is stale. If you need the current + * value, look it up with nm_l3cfg_get_acd_addr_info(). */ + info = ¬ify_data.acd_event.info; + info->track_infos = nm_memdup_maybe_a(300, + info->track_infos, + info->n_track_infos * sizeof(info->track_infos[0]), + &track_infos_clone); + + for (i = 0; i < info->n_track_infos; i++) { + NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i]; + + nmp_object_ref(ti->obj); + nm_l3_config_data_ref(ti->l3cd); + } + + _nm_l3cfg_emit_signal_notify(self, ¬ify_data); + + for (i = 0; i < info->n_track_infos; i++) { + NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i]; + + nmp_object_unref(ti->obj); + nm_l3_config_data_unref(ti->l3cd); + } +} + +static void +_nm_l3cfg_emit_signal_notify_acd_event_queue(NML3Cfg *self, AcdData *acd_data) +{ + if (!c_list_is_empty(&acd_data->acd_event_notify_lst)) { + nm_assert(c_list_contains(&self->priv.p->acd_event_notify_lst_head, + &acd_data->acd_event_notify_lst)); + return; + } + c_list_link_tail(&self->priv.p->acd_event_notify_lst_head, &acd_data->acd_event_notify_lst); +} + +static void +_nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self) +{ + gs_unref_object NML3Cfg *self_keep_alive = NULL; + AcdData * acd_data; + + while ((acd_data = c_list_first_entry(&self->priv.p->acd_event_notify_lst_head, + AcdData, + acd_event_notify_lst))) { + if (!self_keep_alive) + self_keep_alive = g_object_ref(self); + c_list_unlink(&acd_data->acd_event_notify_lst); + _nm_l3cfg_emit_signal_notify_acd_event(self, acd_data); + } +} + +_nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg * self, + AcdData * acd_data, + NML3AcdAddrState state, + gboolean allow_commit, + const char * format, + ...) +{ + NML3AcdAddrState old_state; + gboolean changed; + + if (acd_data->info.state == state) + return; + + /* in every state we only have one timer possibly running. Resetting + * the states makes the previous timeout obsolete. */ + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + + old_state = acd_data->info.state; + acd_data->info.state = state; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + + if (state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) + changed = FALSE; + else if (NM_IN_SET(old_state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING) + != NM_IN_SET(state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)) + changed = TRUE; + else + changed = FALSE; + + if (format) { + gs_free char *msg = NULL; + va_list args; + + va_start(args, format); + msg = g_strdup_vprintf(format, args); + va_end(args); + + _LOGT_acd(acd_data, "set state to %s (%s)", _l3_acd_addr_state_to_string(state), msg); + } else + _LOGT_acd(acd_data, "set state to %s", _l3_acd_addr_state_to_string(state)); + + if (changed && allow_commit) { + /* The availability of an address just changed (and we are instructed to + * trigger a new commit). Do it. */ + _l3_changed_configs_set_dirty(self); + nm_l3cfg_commit_on_idle_schedule(self); + } +} + +static void +_l3_acd_data_state_set(NML3Cfg * self, + AcdData * acd_data, + NML3AcdAddrState state, + gboolean allow_commit) +{ + _l3_acd_data_state_set_full(self, acd_data, state, allow_commit, NULL); +} + +static void +_l3_acd_data_state_change(NML3Cfg * self, + AcdData * acd_data, + AcdStateChangeMode state_change_mode, + const NMEtherAddr *sender_addr, + gint64 * p_now_msec) + +{ + guint32 acd_timeout_msec; + NML3AcdDefendType acd_defend_type; + gint64 now_msec; + const char * log_reason; + char sbuf256[256]; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + if (!p_now_msec) { + now_msec = 0; + p_now_msec = &now_msec; + } + + /* Keeping track of ACD inevitably requires keeping (and mutating) state. Then a multitude of + * things can happen, and depending on the state, we need to do something. + * + * Here, all the state for one address that we probe/announce is tracked in AcdData/acd_data. + * + * The acd_data has a list of NML3AcdAddrTrackInfo/acd_track_lst_head, which are configuration items + * that are interested in configuring this address. The "owners" of the ACD check for a certain + * address. + * + * We try to do all the state changes in this _l3_acd_data_state_change() function, where -- + * depending on the @state_change_mode -- we progress the state. + * + * It is complicated, but I think this is not really avoidable if you want to handle all + * the special things (state-changes) that can happen. + */ + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(acd_data); + nm_assert(acd_data->info.n_track_infos); + nm_assert(NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_CONFLICT, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING, + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, + NM_L3_ACD_ADDR_STATE_USED)); + nm_assert(!acd_data->track_infos_changed + || NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED)); + nm_assert((!!sender_addr) + == NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_NACD_USED, + ACD_STATE_CHANGE_MODE_NACD_CONFLICT, + ACD_STATE_CHANGE_MODE_NACD_DEFENDED)); + + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) { + /* once remove, the state can only change by external added or during + * the POST-COMMIT check. */ + if (!NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED)) + return; + } + + switch (state_change_mode) { + case ACD_STATE_CHANGE_MODE_INIT: + case ACD_STATE_CHANGE_MODE_INIT_REAPPLY: + + /* We are called right before commit. We check whether we have a acd_data + * in INIT or PROBING state. In that case, maybe the new configuration + * disables ACD, or we have the address already configured (which also let's + * us skip/cancel the probing). The point is that if the address would be ready + * already, we want to commit it right away. */ + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_USED: + goto handle_init; + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + if (state_change_mode != ACD_STATE_CHANGE_MODE_INIT_REAPPLY) + return; + goto handle_init; + } + nm_assert_not_reached(); + return; + +handle_init: + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_FALSE, + &acd_timeout_msec, + &acd_defend_type) + <= 0u) { + /* the acd_data has no active trackers. It will soon be pruned. */ + return; + } + + if (acd_timeout_msec == 0u) + log_reason = "acd disabled by configuration"; + else if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) + log_reason = "address already configured"; + else { + if (state_change_mode == ACD_STATE_CHANGE_MODE_INIT_REAPPLY) { + /* during a reapply, we forget all the state and start from scratch. */ + _LOGT_acd(acd_data, "reset state for reapply"); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_INIT, FALSE); + } + return; + } + + _LOGT_acd(acd_data, + "%s probing (%s, during pre-check)", + acd_data->info.state == NM_L3_ACD_ADDR_STATE_INIT ? "skip" : "cancel", + log_reason); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->acd_defend_type_desired = acd_defend_type; + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_READY, FALSE); + return; + + case ACD_STATE_CHANGE_MODE_POST_COMMIT: + + if (acd_data->track_infos_changed) { + acd_data->track_infos_changed = FALSE; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + } + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + log_reason = "address already configured"; + goto handle_probing_done; + } + + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) + return; + + /* we just did a commit of the IP configuration and now visit all ACD states + * and kick off the necessary actions... */ + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_TRUE, + &acd_timeout_msec, + &acd_defend_type) + <= 0) + nm_assert_not_reached(); + + acd_data->acd_defend_type_desired = acd_defend_type; + + if (acd_timeout_msec <= 0) { + log_reason = "acd disabled by configuration"; + goto handle_probing_done; + } + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert(!acd_data->nacd_probe); + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + log_reason = "initial post-commit"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_PROBING: + { + gint64 old_expiry_msec; + gint64 new_expiry_msec; + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + new_expiry_msec = (*p_now_msec) + acd_timeout_msec; + old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec; + + if (!acd_data->nacd_probe) { + /* we are currently waiting for restarting a probe. At this point, at most we have + * to adjust the timeout/timestamp and let the regular timeouts handle this. */ + + if (new_expiry_msec >= old_expiry_msec) { + /* the running timeout expires before the new timeout. We don't update the timestamp/timeout, + * because we don't want to prolong the overall probing time. */ + return; + } + /* update the timers after out timeout got reduced. Also, reschedule the timeout + * so that it expires immediately. */ + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + _l3_acd_data_timeout_schedule(acd_data, 0); + return; + } + + if (new_expiry_msec >= old_expiry_msec) { + /* we already have ACD running with a timeout that expires before the requested one. There + * is nothing to do at this time. */ + return; + } + + /* the timeout got reduced. We try to restart the probe. */ + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + log_reason = "post-commit timeout update"; + goto handle_start_probing; + } + + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + /* we are done for now. We however scheduled a timeout to restart. This + * will be handled with the ACD_STATE_CHANGE_MODE_TIMEOUT event. */ + return; + + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + nm_assert_not_reached(); + return; + + case ACD_STATE_CHANGE_MODE_TIMEOUT: + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + + case NM_L3_ACD_ADDR_STATE_PROBING: + if (acd_data->nacd_probe) { + /* we are already probing. There is nothing to do for this timeout. */ + return; + } + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC + >= (*p_now_msec)) { + /* hm. We failed to create a new probe too long. Something is really wrong + * internally, but let's ignore the issue and assume the address is good. What + * else would we do? Assume the address is USED? */ + _LOGT_acd(acd_data, + "probe-good (waiting for creating probe timed out. Assume good)"); + goto handle_start_defending; + } + + log_reason = "retry probing on timeout"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + + nm_assert(!acd_data->nacd_probe); + + /* after a timeout, re-probe the address. This only happens if the caller + * does not deconfigure the address after USED/CONFLICT. But in that case, + * we eventually want to retry. */ + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_TRUE, + &acd_timeout_msec, + &acd_defend_type) + <= 0) + nm_assert_not_reached(); + + acd_data->acd_defend_type_desired = acd_defend_type; + + if (acd_timeout_msec <= 0) { + log_reason = "acd disabled by configuration (restart after previous conflict)"; + goto handle_probing_done; + } + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + log_reason = "address already configured (restart after previous conflict)"; + goto handle_probing_done; + } + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_USED) + log_reason = "restart probing after previously used address"; + else + log_reason = "restart probing after previous conflict"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_READY: + nm_assert_not_reached(); + return; + + case NM_L3_ACD_ADDR_STATE_DEFENDING: + + nm_assert(!acd_data->nacd_probe); + _LOGT_acd(acd_data, "retry announcing address"); + goto handle_start_defending; + + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + + nm_assert_not_reached(); + return; + + case ACD_STATE_CHANGE_MODE_NACD_USED: + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING); + nm_assert(acd_data->nacd_probe); + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->last_conflict_addr = *sender_addr; + _l3_acd_data_state_set_full(self, + acd_data, + NM_L3_ACD_ADDR_STATE_USED, + TRUE, + "acd completed with address already in use by %s", + nm_ether_addr_to_string_a(sender_addr)); + + if (!acd_data->acd_data_timeout_source) + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC); + + if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) { + _LOGI("IPv4 address %s is used on network connected to interface %d%s%s%s from " + "host %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + self->priv.ifindex, + NM_PRINT_FMT_QUOTED(self->priv.plobj_next, + " (", + NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name, + ")", + ""), + nm_ether_addr_to_string_a(sender_addr)); + } + return; + + case ACD_STATE_CHANGE_MODE_NACD_DEFENDED: + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); + _LOGT_acd(acd_data, + "address %s defended from %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + /* we just log an info message. Nothing else to do. */ + return; + + case ACD_STATE_CHANGE_MODE_NACD_CONFLICT: + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); + + _LOGT_acd(acd_data, + "address conflict for %s detected with %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + + if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) { + _LOGW("IPv4 address collision detection sees conflict on interface %d%s%s%s for " + "address %s from host %s", + self->priv.ifindex, + NM_PRINT_FMT_QUOTED(self->priv.plobj_next, + " (", + NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name, + ")", + ""), + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + } + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->last_conflict_addr = *sender_addr; + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_CONFLICT, TRUE); + if (!acd_data->acd_data_timeout_source) + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_CONFLICT_RESTART_MSEC); + return; + + case ACD_STATE_CHANGE_MODE_NACD_READY: + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_PROBING: + nm_assert(acd_data->nacd_probe); + /* we theoretically could re-use this probe for defending. But as we + * may not start defending right away, it makes it more complicated. */ + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + log_reason = "acd indicates ready"; + goto handle_probing_done; + case NM_L3_ACD_ADDR_STATE_DEFENDING: + nm_assert(!acd_data->acd_defend_type_is_active); + acd_data->acd_defend_type_is_active = TRUE; + _LOGT_acd(acd_data, + "start announcing (defend=%s) (after new probe ready)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + if (n_acd_probe_announce(acd_data->nacd_probe, + _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current)) + != 0) + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + + nm_assert_not_reached(); + return; + + case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED: + + if (self->priv.p->commit_reentrant_count > 0) + return; + + _LOGT_acd(acd_data, "address was externally added"); + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + log_reason = "address configured on link"; + goto handle_probing_done; + } + + nm_assert_not_reached(); + return; + + case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED: + + if (self->priv.p->commit_reentrant_count > 0) + return; + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + /* this can happen, because there might still be the same address with different + * plen or peer_address. */ + return; + } + + _LOGT_acd(acd_data, "address was externally removed"); + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, FALSE); + return; + + case ACD_STATE_CHANGE_MODE_NACD_DOWN: + case ACD_STATE_CHANGE_MODE_LINK_NOW_UP: + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_PROBING: + + if (!acd_data->nacd_probe) { + /* we failed starting to probe before and have a timer running to + * restart. We don't do anything now, but let the timer handle it. + * This also implements some rate limiting for us. */ + _LOGT_acd(acd_data, + "ignore link %s event while we are waiting to start probing", + state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN ? "down" : "up"); + return; + } + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + if (acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec + + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC + >= (*p_now_msec)) { + /* The probing already started quite a while ago. We ignore the link event + * and let the probe come to it's natural end. */ + _LOGT_acd(acd_data, "ignore link up event for a probe started long ago"); + return; + } + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + if (state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN) + log_reason = "restart probing after down event"; + else + log_reason = "restart probing after link up"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + /* if the link was down/came up, it's no clear what we should do about these + * cases. Ignore the event. */ + return; + } + nm_assert_not_reached(); + return; + + case ACD_STATE_CHANGE_MODE_INSTANCE_RESET: + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + + if (!acd_data->nacd_probe) { + /* we failed starting to probe before and have a timer running to + * restart. We don't do anything now, but let the timer handle it. + * This also implements some rate limiting for us. */ + _LOGT_acd(acd_data, + "n-acd instance reset. Ignore event while restarting %s", + (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing" + : "defending"); + return; + } + + _LOGT_acd(acd_data, + "n-acd instance reset. Trigger a restart of the %s", + (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing" + : "defending"); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + _l3_acd_data_timeout_schedule(acd_data, 0); + return; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert(!acd_data->nacd_probe); + return; + } + nm_assert_not_reached(); + return; + } + + nm_assert_not_reached(); + return; + +handle_start_probing: + if (TRUE) { + const NML3AcdAddrState orig_state = acd_data->info.state; + nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL; + const char * failure_reason; + gboolean acd_not_supported; + + nm_assert(NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_USED, + NM_L3_ACD_ADDR_STATE_CONFLICT)); + + /* note that we reach this line also during a ACD_STATE_CHANGE_MODE_TIMEOUT, when + * or when we restart the probing (with a new timeout). In all cases, we still + * give the original timeout (acd_data->probing_timeout_msec), and not the remaining + * time. That means, the probing step might take longer then originally planned + * (e.g. if we initially cannot start probing right away). */ + + probe = _l3_acd_nacd_instance_create_probe(self, + acd_data->info.addr, + acd_data->probing_timeout_msec, + acd_data, + &acd_not_supported, + &failure_reason); + NM_SWAP(&probe, &acd_data->nacd_probe); + + if (acd_not_supported) { + nm_assert(!acd_data->nacd_probe); + _LOGT_acd(acd_data, + "probe-good (interface does not support acd%s, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" + : (state_change_mode != ACD_STATE_CHANGE_MODE_TIMEOUT) + ? " anymore" + : " anymore after timeout", + log_reason); + goto handle_start_defending; + } + + _l3_acd_data_state_set(self, + acd_data, + NM_L3_ACD_ADDR_STATE_PROBING, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT)); + + if (!acd_data->nacd_probe) { + _LOGT_acd(acd_data, + "probing currently %snot possible (timeout %u msec; %s, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still", + acd_data->probing_timeout_msec, + failure_reason, + log_reason); + _l3_acd_data_timeout_schedule_probing_restart(acd_data, (*p_now_msec)); + return; + } + + _LOGT_acd(acd_data, + "%sstart probing (timeout %u msec, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "re", + acd_data->probing_timeout_msec, + log_reason); + return; + } + +handle_probing_done: + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + _LOGT_acd(acd_data, "probe-done good (%s, initializing)", log_reason); + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_PROBING: + _LOGT_acd(acd_data, "probe-done good (%s, probing done)", log_reason); + if (state_change_mode != ACD_STATE_CHANGE_MODE_NACD_READY) + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_USED: + _LOGT_acd(acd_data, "probe-done good (%s, after probe failed)", log_reason); + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_CONFLICT: + return; + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + nm_assert_not_reached(); + return; + +handle_start_defending: + if (!_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + if (acd_data->info.state != NM_L3_ACD_ADDR_STATE_READY) { + _l3_acd_data_state_set_full(self, + acd_data, + NM_L3_ACD_ADDR_STATE_READY, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT), + "probe is ready, waiting for address to be configured"); + } + return; + } + + _l3_acd_data_state_set(self, + acd_data, + NM_L3_ACD_ADDR_STATE_DEFENDING, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT)); + + nm_assert(acd_data->acd_defend_type_desired > NM_L3_ACD_DEFEND_TYPE_NONE); + nm_assert(acd_data->acd_defend_type_desired <= NM_L3_ACD_DEFEND_TYPE_ALWAYS); + + if (acd_data->acd_defend_type_desired != acd_data->acd_defend_type_current) { + acd_data->acd_defend_type_current = acd_data->acd_defend_type_desired; + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + } + + if (!acd_data->nacd_probe) { + const char *failure_reason; + NAcdProbe * probe; + + if (acd_data->acd_data_timeout_source) { + /* we already failed to create a probe. We are ratelimited to retry, but + * we have a timer pending... */ + return; + } + + probe = _l3_acd_nacd_instance_create_probe(self, + acd_data->info.addr, + 0, + acd_data, + NULL, + &failure_reason); + if (!probe) { + /* we failed to create a probe for announcing the address. We log a + * warning and start a timer to retry. This way (of having a timer pending) + * we also back off and are rate limited from retrying too frequently. */ + _LOGT_acd(acd_data, "start announcing failed to create probe (%s)", failure_reason); + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC); + return; + } + + _LOGT_acd(acd_data, + "start announcing (defend=%s) (probe created)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + acd_data->acd_defend_type_is_active = FALSE; + acd_data->nacd_probe = probe; + return; + } + + if (!acd_data->acd_defend_type_is_active) { + acd_data->acd_defend_type_is_active = TRUE; + _LOGT_acd(acd_data, + "start announcing (defend=%s) (with existing probe)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + if (n_acd_probe_announce(acd_data->nacd_probe, + _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current)) + != 0) + nm_assert_not_reached(); + return; + } +} + +static void +_l3_acd_data_process_changes(NML3Cfg *self) +{ + gboolean acd_is_pending = FALSE; + gboolean acd_busy = FALSE; + AcdData *acd_data; + gint64 now_msec = 0; + + _l3_acd_data_prune(self, FALSE); + + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + NULL, + &now_msec); + if (acd_data->info.state <= NM_L3_ACD_ADDR_STATE_PROBING) + acd_is_pending = TRUE; + if (acd_data->nacd_probe) + acd_busy = TRUE; + } + + self->priv.p->acd_is_pending = acd_is_pending; + + if (!acd_busy) + _l3_acd_nacd_instance_reset(self, NM_TERNARY_DEFAULT, FALSE); + + _nm_l3cfg_emit_signal_notify_acd_event_all(self); +} + +/*****************************************************************************/ + +const NML3AcdAddrInfo * +nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr) +{ + AcdData *acd_data; + + nm_assert(NM_IS_L3CFG(self)); + + acd_data = _l3_acd_data_find(self, addr); + if (!acd_data) + return NULL; + + return &acd_data->info; +} + +/*****************************************************************************/ + +static gboolean +_l3_commit_on_idle_cb(gpointer user_data) +{ + NML3Cfg *self = user_data; + + nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); + + _LOGT("commit on idle"); + _l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE); + return G_SOURCE_REMOVE; +} + +void +nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + if (self->priv.p->commit_on_idle_source) + return; + + _LOGT("commit on idle (scheduled)"); + self->priv.p->commit_on_idle_source = + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _l3_commit_on_idle_cb, self, NULL); + g_source_attach(self->priv.p->commit_on_idle_source, NULL); +} + +/*****************************************************************************/ + +#define _l3_config_datas_at(l3_config_datas, idx) \ + (&g_array_index((l3_config_datas), L3ConfigData, (idx))) + +static gssize +_l3_config_datas_find_next(GArray * l3_config_datas, + guint start_idx, + gconstpointer needle_tag, + const NML3ConfigData *needle_l3cd) +{ + guint i; + + nm_assert(l3_config_datas); + nm_assert(start_idx <= l3_config_datas->len); + + for (i = start_idx; i < l3_config_datas->len; i++) { + const L3ConfigData *l3_config_data = _l3_config_datas_at(l3_config_datas, i); + + if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag_confdata) + && NM_IN_SET(needle_l3cd, NULL, l3_config_data->l3cd)) + return i; + } + return -1; +} + +static int +_l3_config_datas_get_sorted_cmp(gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const L3ConfigData *a = *((L3ConfigData **) p_a); + const L3ConfigData *b = *((L3ConfigData **) p_b); + + nm_assert(a); + nm_assert(b); + nm_assert(nm_l3_config_data_get_ifindex(a->l3cd) == nm_l3_config_data_get_ifindex(b->l3cd)); + + /* we sort the entries with higher priority (more important, lower numerical value) + * first. */ + NM_CMP_FIELD(a, b, priority_confdata); + + /* if the priority is not unique, we sort them in the order they were added, + * with the oldest first (lower numerical value). */ + NM_CMP_FIELD(a, b, pseudo_timestamp_confdata); + + return nm_assert_unreachable_val(0); +} + +static void +_l3_config_datas_remove_index_fast(GArray *arr, guint idx) +{ + L3ConfigData *l3_config_data; + + nm_assert(arr); + nm_assert(idx < arr->len); + + l3_config_data = _l3_config_datas_at(arr, idx); + + nm_l3_config_data_unref(l3_config_data->l3cd); + + g_array_remove_index_fast(arr, idx); +} + +void +nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty) +{ + gssize idx; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(tag); + + if (!self->priv.p->l3_config_datas) + return; + + nm_assert(self->priv.p->l3_config_datas->len > 0); + + idx = 0; + while (TRUE) { + idx = _l3_config_datas_find_next(self->priv.p->l3_config_datas, idx, tag, NULL); + if (idx < 0) + return; + + _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata = dirty; + idx++; + } +} + +gboolean +nm_l3cfg_add_config(NML3Cfg * self, + gconstpointer tag, + gboolean replace_same_tag, + const NML3ConfigData *l3cd, + int priority, + guint32 default_route_table_4, + guint32 default_route_table_6, + guint32 default_route_metric_4, + guint32 default_route_metric_6, + guint32 default_route_penalty_4, + guint32 default_route_penalty_6, + NML3AcdDefendType acd_defend_type, + guint32 acd_timeout_msec, + NML3ConfigMergeFlags merge_flags) +{ + L3ConfigData *l3_config_data; + gssize idx; + gboolean changed = FALSE; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(tag); + nm_assert(l3cd); + nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex); + + if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC) + acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC; + + nm_assert(NM_IN_SET(acd_defend_type, + NM_L3_ACD_DEFEND_TYPE_NEVER, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_L3_ACD_DEFEND_TYPE_ALWAYS)); + + nm_assert(default_route_metric_6 != 0u); /* IPv6 default route metric cannot be zero. */ + + if (default_route_table_4 == 0u) + default_route_table_4 = RT_TABLE_MAIN; + if (default_route_table_6 == 0u) + default_route_table_6 = RT_TABLE_MAIN; + + if (!self->priv.p->l3_config_datas) { + self->priv.p->l3_config_datas = g_array_new(FALSE, FALSE, sizeof(L3ConfigData)); + g_object_ref(self); + } else + nm_assert(self->priv.p->l3_config_datas->len > 0); + + idx = _l3_config_datas_find_next(self->priv.p->l3_config_datas, + 0, + tag, + replace_same_tag ? NULL : l3cd); + + if (replace_same_tag && idx >= 0) { + gssize idx2; + + idx2 = idx; + idx = -1; + while (TRUE) { + l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx2); + + if (l3_config_data->l3cd == l3cd) { + nm_assert(idx == -1); + idx = idx2; + idx2++; + } else { + changed = TRUE; + _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx2); + } + idx2 = _l3_config_datas_find_next(self->priv.p->l3_config_datas, idx2, tag, NULL); + if (idx2 < 0) + break; + } + } + + if (idx < 0) { + l3_config_data = nm_g_array_append_new(self->priv.p->l3_config_datas, L3ConfigData); + *l3_config_data = (L3ConfigData){ + .tag_confdata = tag, + .l3cd = nm_l3_config_data_ref_and_seal(l3cd), + .merge_flags = merge_flags, + .default_route_table_4 = default_route_table_4, + .default_route_table_6 = default_route_table_6, + .default_route_metric_4 = default_route_metric_4, + .default_route_metric_6 = default_route_metric_6, + .default_route_penalty_4 = default_route_penalty_4, + .default_route_penalty_6 = default_route_penalty_6, + .acd_defend_type_confdata = acd_defend_type, + .acd_timeout_msec_confdata = acd_timeout_msec, + .priority_confdata = priority, + .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter, + .dirty_confdata = FALSE, + }; + changed = TRUE; + } else { + l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx); + l3_config_data->dirty_confdata = FALSE; + nm_assert(l3_config_data->tag_confdata == tag); + nm_assert(l3_config_data->l3cd == l3cd); + if (l3_config_data->priority_confdata != priority) { + l3_config_data->priority_confdata = priority; + changed = TRUE; + } + if (l3_config_data->merge_flags != merge_flags) { + l3_config_data->merge_flags = merge_flags; + changed = TRUE; + } + if (l3_config_data->default_route_table_4 != default_route_table_4) { + l3_config_data->default_route_table_4 = default_route_table_4; + changed = TRUE; + } + if (l3_config_data->default_route_table_6 != default_route_table_6) { + l3_config_data->default_route_table_6 = default_route_table_6; + changed = TRUE; + } + if (l3_config_data->default_route_metric_4 != default_route_metric_4) { + l3_config_data->default_route_metric_4 = default_route_metric_4; + changed = TRUE; + } + if (l3_config_data->default_route_metric_6 != default_route_metric_6) { + l3_config_data->default_route_metric_6 = default_route_metric_6; + changed = TRUE; + } + if (l3_config_data->default_route_penalty_4 != default_route_penalty_4) { + l3_config_data->default_route_penalty_4 = default_route_penalty_4; + changed = TRUE; + } + if (l3_config_data->default_route_penalty_6 != default_route_penalty_6) { + l3_config_data->default_route_penalty_6 = default_route_penalty_6; + changed = TRUE; + } + if (l3_config_data->acd_defend_type_confdata != acd_defend_type) { + l3_config_data->acd_defend_type_confdata = acd_defend_type; + changed = TRUE; + } + if (l3_config_data->acd_timeout_msec_confdata != acd_timeout_msec) { + l3_config_data->acd_timeout_msec_confdata = acd_timeout_msec; + changed = TRUE; + } + } + + nm_assert(l3_config_data->acd_defend_type_confdata == acd_defend_type); + + if (changed) + _l3_changed_configs_set_dirty(self); + + return changed; +} + +static gboolean +_l3cfg_remove_config(NML3Cfg * self, + gconstpointer tag, + gboolean only_dirty, + const NML3ConfigData *l3cd) +{ + gboolean changed; + gssize idx; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(tag); + + if (!self->priv.p->l3_config_datas) + return FALSE; + + nm_assert(self->priv.p->l3_config_datas->len > 0); + + idx = 0; + changed = FALSE; + while (TRUE) { + idx = _l3_config_datas_find_next(self->priv.p->l3_config_datas, idx, tag, l3cd); + if (idx < 0) + break; + + if (only_dirty + && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata) { + idx++; + continue; + } + + _l3_changed_configs_set_dirty(self); + _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx); + changed = TRUE; + if (l3cd) { + /* only one was requested to be removed. We are done. */ + break; + } + } + + if (self->priv.p->l3_config_datas->len == 0) { + nm_assert(changed); + nm_clear_pointer(&self->priv.p->l3_config_datas, g_array_unref); + g_object_unref(self); + } + + return changed; +} + +gboolean +nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg) +{ + nm_assert(ifcfg); + + return _l3cfg_remove_config(self, tag, FALSE, ifcfg); +} + +gboolean +nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty) +{ + return _l3cfg_remove_config(self, tag, only_dirty, NULL); +} + +/*****************************************************************************/ + +typedef struct { + NML3Cfg * self; + gconstpointer tag; +} L3ConfigMergeHookAddObjData; + +static gboolean +_l3_hook_add_addr_cb(const NML3ConfigData *l3cd, + const NMPObject * obj, + NMTernary * out_ip4acd_not_ready, + gpointer user_data) +{ + const L3ConfigMergeHookAddObjData *hook_data = user_data; + NML3Cfg * self = hook_data->self; + AcdData * acd_data; + in_addr_t addr; + gboolean acd_bad = FALSE; + + nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT); + + if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS) + return TRUE; + + addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; + + if (ACD_ADDR_SKIP(addr)) + goto out; + + acd_data = _l3_acd_data_find(self, addr); + + if (!acd_data) { + /* we don't yet track an ACD state for this address. That can only + * happend during _l3cfg_update_combined_config() with !to_commit, + * where we didn't update the ACD state. + * + * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE) + * won't consider IPv4 addresses ready, that have no known ACD state yet. */ + nm_assert(self->priv.p->changed_configs_acd_state); + acd_bad = TRUE; + goto out; + } + + nm_assert( + _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag))); + if (!NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)) + acd_bad = TRUE; + +out: + *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; + return TRUE; +} + +static void +_l3cfg_update_combined_config(NML3Cfg * self, + gboolean to_commit, + gboolean reapply, + const NML3ConfigData **out_old /* transfer reference */, + gboolean * out_changed_combined_l3cd) +{ + nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + gs_free const L3ConfigData **l3_config_datas_free = NULL; + const L3ConfigData ** l3_config_datas_arr; + guint l3_config_datas_len; + guint i; + gboolean merged_changed = FALSE; + gboolean commited_changed = FALSE; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(!out_old || !*out_old); + + NM_SET_OUT(out_changed_combined_l3cd, FALSE); + + if (!self->priv.p->changed_configs_configs) { + if (!self->priv.p->changed_configs_acd_state) + goto out; + if (!to_commit) { + /* since we are not going to commit, we don't care about the + * ACD state. */ + goto out; + } + } + + self->priv.p->changed_configs_configs = FALSE; + + l3_config_datas_len = nm_g_array_len(self->priv.p->l3_config_datas); + l3_config_datas_arr = nm_malloc_maybe_a(300, + l3_config_datas_len * sizeof(l3_config_datas_arr[0]), + &l3_config_datas_free); + for (i = 0; i < l3_config_datas_len; i++) + l3_config_datas_arr[i] = _l3_config_datas_at(self->priv.p->l3_config_datas, i); + + if (l3_config_datas_len > 1) { + g_qsort_with_data(l3_config_datas_arr, + l3_config_datas_len, + sizeof(l3_config_datas_arr[0]), + _l3_config_datas_get_sorted_cmp, + NULL); + } + + if (!to_commit) { + /* we are not going to commit these changes. Hence, we don't update the + * ACD states, but we need to remember that we have to on the next commit. */ + self->priv.p->changed_configs_acd_state = TRUE; + } else { + _l3_acd_data_add_all(self, l3_config_datas_arr, l3_config_datas_len, reapply); + self->priv.p->changed_configs_acd_state = FALSE; + } + + if (l3_config_datas_len > 0) { + L3ConfigMergeHookAddObjData hook_data = { + .self = self, + }; + + l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(self->priv.platform), + self->priv.ifindex); + + for (i = 0; i < l3_config_datas_len; i++) { + const L3ConfigData *l3cd_data = l3_config_datas_arr[i]; + + if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD)) + continue; + + hook_data.tag = l3cd_data->tag_confdata; + nm_l3_config_data_merge(l3cd, + l3cd_data->l3cd, + l3cd_data->merge_flags, + l3cd_data->default_route_table_x, + l3cd_data->default_route_metric_x, + l3cd_data->default_route_penalty_x, + _l3_hook_add_addr_cb, + &hook_data); + } + + nm_assert(l3cd); + nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex); + + nm_l3_config_data_seal(l3cd); + } + + if (nm_l3_config_data_equal(l3cd, self->priv.p->combined_l3cd_merged)) + goto out; + + l3cd_old = g_steal_pointer(&self->priv.p->combined_l3cd_merged); + self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd)); + merged_changed = TRUE; + if (!to_commit) { + NM_SET_OUT(out_old, g_steal_pointer(&l3cd_old)); + NM_SET_OUT(out_changed_combined_l3cd, TRUE); + } + +out: + if (to_commit && self->priv.p->combined_l3cd_commited != self->priv.p->combined_l3cd_merged) { + l3cd_commited_old = g_steal_pointer(&self->priv.p->combined_l3cd_commited); + self->priv.p->combined_l3cd_commited = + nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged); + commited_changed = TRUE; + NM_SET_OUT(out_old, g_steal_pointer(&l3cd_commited_old)); + NM_SET_OUT(out_changed_combined_l3cd, TRUE); + } + + if ((merged_changed || commited_changed) && _LOGT_ENABLED()) { + char sbuf256[256]; + char sbuf30[30]; + + _LOGT("IP configuration changed (merged=%c%s, commited=%c%s)", + merged_changed ? '>' : '=', + NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_merged, sbuf256), + commited_changed ? '>' : '=', + NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_commited, sbuf30)); + + if (merged_changed) { + nm_l3_config_data_log(self->priv.p->combined_l3cd_merged, + NULL, + nm_sprintf_buf(sbuf256, + "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT + ",ifindex=%d]: ", + NM_HASH_OBFUSCATE_PTR(self), + nm_l3cfg_get_ifindex(self)), + LOGL_TRACE, + _NMLOG_DOMAIN); + } + } +} + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gint64 timestamp_msec; + bool dirty; +} RoutesTemporaryNotAvailableData; + +static void +_routes_temporary_not_available_data_free(gpointer user_data) +{ + RoutesTemporaryNotAvailableData *data = user_data; + + nmp_object_unref(data->obj); + nm_g_slice_free(data); +} + +#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000) + +static gboolean +_routes_temporary_not_available_timeout(gpointer user_data) +{ + RoutesTemporaryNotAvailableData *data; + NML3Cfg * self = NM_L3CFG(user_data); + GHashTableIter iter; + gint64 expiry_threshold_msec; + gboolean any_expired = FALSE; + gint64 now_msec; + gint64 oldest_msec; + + self->priv.p->routes_temporary_not_available_id = 0; + + if (!self->priv.p->routes_temporary_not_available_hash) + return G_SOURCE_REMOVE; + + /* we check the timeouts again. That is, because we allow to remove + * entries from routes_temporary_not_available_hash, without rescheduling + * out timeouts. */ + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; + oldest_msec = G_MAXINT64; + + g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) { + if (data->timestamp_msec >= expiry_threshold_msec) { + any_expired = TRUE; + break; + } + if (data->timestamp_msec < oldest_msec) + oldest_msec = data->timestamp_msec; + } + + if (any_expired) { + /* a route expired. We emit a signal, but we don't schedule it again. That will + * only happen if the user calls nm_l3cfg_commit() again. */ + _nm_l3cfg_emit_signal_notify_simple( + self, + NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED); + return G_SOURCE_REMOVE; + } + + if (oldest_msec != G_MAXINT64) { + /* we have a timeout still. Reschedule. */ + self->priv.p->routes_temporary_not_available_id = + g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec, + _routes_temporary_not_available_timeout, + self); + } + return G_SOURCE_REMOVE; +} + +static gboolean +_routes_temporary_not_available_update(NML3Cfg * self, + int addr_family, + GPtrArray *routes_temporary_not_available_arr) +{ + RoutesTemporaryNotAvailableData *data; + GHashTableIter iter; + gint64 oldest_msec; + gint64 now_msec; + gboolean prune_all = FALSE; + gboolean success = TRUE; + guint i; + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) { + prune_all = TRUE; + goto out_prune; + } + + if (self->priv.p->routes_temporary_not_available_hash) { + g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) { + if (NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family) + data->dirty = TRUE; + } + } else { + self->priv.p->routes_temporary_not_available_hash = + g_hash_table_new_full(nmp_object_indirect_id_hash, + nmp_object_indirect_id_equal, + _routes_temporary_not_available_data_free, + NULL); + } + + for (i = 0; i < routes_temporary_not_available_arr->len; i++) { + const NMPObject *o = routes_temporary_not_available_arr->pdata[i]; + char sbuf[1024]; + + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); + + data = g_hash_table_lookup(self->priv.p->routes_temporary_not_available_hash, &o); + + if (data) { + if (!data->dirty) + continue; + + nm_assert(data->timestamp_msec > 0 && data->timestamp_msec <= now_msec); + + if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { + /* timeout. Could not add this address. */ + _LOGW("failure to add IPv%c route: %s", + nm_utils_addr_family_to_char(addr_family), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + success = FALSE; + continue; + } + + data->dirty = FALSE; + continue; + } + + _LOGT("(temporarily) unable to add IPv%c route: %s", + nm_utils_addr_family_to_char(addr_family), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + + data = g_slice_new(RoutesTemporaryNotAvailableData); + *data = (RoutesTemporaryNotAvailableData){ + .obj = nmp_object_ref(o), + .timestamp_msec = now_msec, + .dirty = FALSE, + }; + g_hash_table_add(self->priv.p->routes_temporary_not_available_hash, data); + } + +out_prune: + oldest_msec = G_MAXINT64; + + if (self->priv.p->routes_temporary_not_available_hash) { + g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) { + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family || !data->dirty); + if (!prune_all && !data->dirty) { + if (data->timestamp_msec < oldest_msec) + oldest_msec = data->timestamp_msec; + continue; + } + g_hash_table_iter_remove(&iter); + } + if (oldest_msec != G_MAXINT64) + nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, + g_hash_table_unref); + } + + nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id); + if (oldest_msec != G_MAXINT64) { + nm_assert(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC < now_msec); + self->priv.p->routes_temporary_not_available_id = + g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec, + _routes_temporary_not_available_timeout, + self); + } + + return success; +} + +/*****************************************************************************/ + +static gboolean +_l3_commit_one(NML3Cfg * self, + int addr_family, + NML3CfgCommitType commit_type, + gboolean changed_combined_l3cd, + const NML3ConfigData *l3cd_old) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL; + NMIPRouteTableSyncMode route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE; + gboolean final_failure_for_temporary_not_available = FALSE; + char sbuf_commit_type[50]; + gboolean success = TRUE; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(NM_IN_SET(commit_type, + NM_L3_CFG_COMMIT_TYPE_NONE, + NM_L3_CFG_COMMIT_TYPE_REAPPLY, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NM_L3_CFG_COMMIT_TYPE_ASSUME)); + nm_assert_addr_family(addr_family); + + _LOGT("committing IPv%c configuration (%s)", + nm_utils_addr_family_to_char(addr_family), + _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type))); + + if (changed_combined_l3cd) { + /* our combined configuration changed. We may track entries in externally_removed_objs_hash, + * which are not longer to be considered by our configuration. We need to forget about them. */ + _l3cfg_externally_removed_objs_drop_unused(self); + } + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) { + /* we need to artificially pre-populate the externally remove hash. */ + _l3cfg_externally_removed_objs_pickup(self, addr_family); + } + + if (self->priv.p->combined_l3cd_commited) { + GHashTable * externally_removed_objs_hash; + NMDedupMultiFcnSelectPredicate predicate; + const NMDedupMultiHeadEntry * head_entry; + + if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY + && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) { + predicate = _l3cfg_externally_removed_objs_filter; + externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash; + } else { + if (IS_IPv4) + predicate = _l3cfg_externally_removed_objs_filter; + else + predicate = NULL; + externally_removed_objs_hash = NULL; + } + head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited, + NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)); + addresses = nm_dedup_multi_objs_to_ptr_array_head(head_entry, + predicate, + externally_removed_objs_hash); + + if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY + && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) { + predicate = _l3cfg_externally_removed_objs_filter; + externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash; + } else { + predicate = NULL; + externally_removed_objs_hash = NULL; + } + head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited, + NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)); + routes = nm_dedup_multi_objs_to_ptr_array_head(head_entry, + predicate, + externally_removed_objs_hash); + + route_table_sync = + nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited, + addr_family); + } + + if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) + route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL; + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) { + addresses_prune = nm_platform_ip_address_get_prune_list(self->priv.platform, + addr_family, + self->priv.ifindex, + TRUE); + routes_prune = nm_platform_ip_route_get_prune_list(self->priv.platform, + addr_family, + self->priv.ifindex, + route_table_sync); + } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) { + addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]); + routes_prune = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]); + } + + nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses); + nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes); + + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */ + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */ + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_mtu(). */ + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */ + + nm_platform_ip_address_sync(self->priv.platform, + addr_family, + self->priv.ifindex, + addresses, + addresses_prune); + + if (!nm_platform_ip_route_sync(self->priv.platform, + addr_family, + self->priv.ifindex, + routes, + routes_prune, + &routes_temporary_not_available_arr)) + success = FALSE; + + final_failure_for_temporary_not_available = FALSE; + if (!_routes_temporary_not_available_update(self, + addr_family, + routes_temporary_not_available_arr)) + final_failure_for_temporary_not_available = TRUE; + + /* FIXME(l3cfg) */ + (void) final_failure_for_temporary_not_available; + + return success; +} + +static void +_l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) +{ + nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + gboolean commit_type_detected = FALSE; + char sbuf_ct[30]; + gboolean changed_combined_l3cd; + + g_return_if_fail(NM_IS_L3CFG(self)); + nm_assert(NM_IN_SET(commit_type, + NM_L3_CFG_COMMIT_TYPE_NONE, + NM_L3_CFG_COMMIT_TYPE_AUTO, + NM_L3_CFG_COMMIT_TYPE_ASSUME, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NM_L3_CFG_COMMIT_TYPE_REAPPLY)); + nm_assert(self->priv.p->commit_reentrant_count == 0); + + switch (commit_type) { + case NM_L3_CFG_COMMIT_TYPE_AUTO: + /* if in "AUTO" mode we currently have commit-type "UPDATE", that + * causes also the following update to still be "UPDATE". Either + * the same commit */ + commit_type_detected = TRUE; + commit_type = nm_l3cfg_commit_type_get(self); + if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) + self->priv.p->commit_type_update_sticky = TRUE; + else if (self->priv.p->commit_type_update_sticky) { + self->priv.p->commit_type_update_sticky = FALSE; + commit_type = NM_L3_CFG_COMMIT_TYPE_UPDATE; + } + break; + case NM_L3_CFG_COMMIT_TYPE_ASSUME: + break; + case NM_L3_CFG_COMMIT_TYPE_REAPPLY: + case NM_L3_CFG_COMMIT_TYPE_UPDATE: + self->priv.p->commit_type_update_sticky = FALSE; + break; + case NM_L3_CFG_COMMIT_TYPE_NONE: + break; + } + + _LOGT("commit %s%s%s", + _l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)), + commit_type_detected ? " (auto)" : "", + is_idle ? " (idle handler)" : ""); + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) + return; + + self->priv.p->commit_reentrant_count++; + + nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) + _l3cfg_externally_removed_objs_drop(self); + + _l3cfg_update_combined_config(self, + TRUE, + commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY, + &l3cd_old, + &changed_combined_l3cd); + + /* FIXME(l3cfg): handle items currently not configurable in kernel. */ + + _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old); + _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old); + + _l3_acd_data_process_changes(self); + + nm_assert(self->priv.p->commit_reentrant_count == 1); + self->priv.p->commit_reentrant_count--; + + _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT); +} + +void +nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type) +{ + _l3_commit(self, commit_type, FALSE); +} + +/*****************************************************************************/ + +NML3CfgCommitType +nm_l3cfg_commit_type_get(NML3Cfg *self) +{ + NML3CfgCommitTypeHandle *handle; + + nm_assert(NM_IS_L3CFG(self)); + + handle = c_list_first_entry(&self->priv.p->commit_type_lst_head, + NML3CfgCommitTypeHandle, + commit_type_lst); + return handle ? handle->commit_type : NM_L3_CFG_COMMIT_TYPE_NONE; +} + +/** + * nm_l3cfg_commit_type_register: + * @self: the #NML3Cfg + * @commit_type: the commit type to register + * @existing_handle: instead of being a new registration, update an existing handle. + * This may be %NULL, which is like having no previous registration. + * + * NML3Cfg needs to know whether it is in charge of an interface (and how "much"). + * By default, it is not in charge, but various users can register themself with + * a certain @commit_type. The "higher" commit type is the used one when calling + * nm_l3cfg_commit() with %NM_L3_CFG_COMMIT_TYPE_AUTO. + * + * Returns: a handle tracking the registration, or %NULL of @commit_type + * is %NM_L3_CFG_COMMIT_TYPE_NONE. + */ +NML3CfgCommitTypeHandle * +nm_l3cfg_commit_type_register(NML3Cfg * self, + NML3CfgCommitType commit_type, + NML3CfgCommitTypeHandle *existing_handle) +{ + NML3CfgCommitTypeHandle *handle; + NML3CfgCommitTypeHandle *h; + gboolean linked; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(NM_IN_SET(commit_type, + NM_L3_CFG_COMMIT_TYPE_NONE, + NM_L3_CFG_COMMIT_TYPE_ASSUME, + NM_L3_CFG_COMMIT_TYPE_UPDATE)); + nm_assert( + !existing_handle + || c_list_contains(&self->priv.p->commit_type_lst_head, &existing_handle->commit_type_lst)); + + if (existing_handle) { + if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) { + nm_l3cfg_commit_type_unregister(self, existing_handle); + return NULL; + } + if (existing_handle->commit_type == commit_type) + return existing_handle; + c_list_unlink_stale(&existing_handle->commit_type_lst); + handle = existing_handle; + } else { + if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) + return NULL; + handle = g_slice_new(NML3CfgCommitTypeHandle); + handle->commit_type = commit_type; + if (c_list_is_empty(&self->priv.p->commit_type_lst_head)) + g_object_ref(self); + } + + linked = FALSE; + c_list_for_each_entry (h, &self->priv.p->commit_type_lst_head, commit_type_lst) { + if (handle->commit_type >= h->commit_type) { + c_list_link_before(&h->commit_type_lst, &handle->commit_type_lst); + linked = TRUE; + break; + } + } + if (!linked) + c_list_link_tail(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst); + + return handle; +} + +void +nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle) +{ + nm_assert(NM_IS_L3CFG(self)); + + if (!handle) + return; + + nm_assert(c_list_contains(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst)); + + c_list_unlink_stale(&handle->commit_type_lst); + if (c_list_is_empty(&self->priv.p->commit_type_lst_head)) + g_object_unref(self); + nm_g_slice_free(handle); +} + +/*****************************************************************************/ + +const NML3ConfigData * +nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited) +{ + nm_assert(NM_IS_L3CFG(self)); + + if (get_commited) + return self->priv.p->combined_l3cd_commited; + + _l3cfg_update_combined_config(self, FALSE, FALSE, NULL, NULL); + return self->priv.p->combined_l3cd_merged; +} + +const NMPObject * +nm_l3cfg_get_best_default_route(NML3Cfg *self, int addr_family, gboolean get_commited) +{ + const NML3ConfigData *l3cd; + + l3cd = nm_l3cfg_get_combined_l3cd(self, get_commited); + if (!l3cd) + return NULL; + + return nm_l3_config_data_get_best_default_route(l3cd, addr_family); +} + +/*****************************************************************************/ + +gboolean +nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self) +{ + const NML3ConfigData *l3cd; + const NMPObject * plat_obj; + NMPLookup plat_lookup; + NMDedupMultiIter iter; + + nm_assert(NM_IS_L3CFG(self)); + + l3cd = nm_l3cfg_get_combined_l3cd(self, TRUE); + if (!l3cd) + return FALSE; + + /* we iterate over all addresses in platform, and check whether the tentative + * addresses are tracked by our l3cd. Not the other way around, because we assume + * that there are few addresses in platform that are still tentative, so + * we only need to lookup few platform addresses in l3cd. + * + * Of course, all lookups are O(1) anyway, so in any case the operation is + * O(n) (once "n" being the addresses in platform, and once in l3cd). */ + + nmp_lookup_init_object(&plat_lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, self->priv.ifindex); + + nm_platform_iter_obj_for_each (&iter, self->priv.platform, &plat_lookup, &plat_obj) { + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj); + const NMDedupMultiEntry * l3cd_entry; + + if (!NM_FLAGS_HAS(plat_addr->n_ifa_flags, IFA_F_TENTATIVE) + || NM_FLAGS_ANY(plat_addr->n_ifa_flags, IFA_F_DADFAILED | IFA_F_OPTIMISTIC)) + continue; + + l3cd_entry = nm_l3_config_data_lookup_obj(l3cd, plat_obj); + + nm_assert(NMP_OBJECT_CAST_IP6_ADDRESS(nm_dedup_multi_entry_get_obj(l3cd_entry)) + == nm_l3_config_data_lookup_address_6(l3cd, &plat_addr->address)); + + if (l3cd_entry) + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +NML3IPv4LL * +nm_l3cfg_get_ipv4ll(NML3Cfg *self) +{ + g_return_val_if_fail(NM_IS_L3CFG(self), NULL); + + return self->priv.p->ipv4ll; +} + +NML3IPv4LL * +nm_l3cfg_access_ipv4ll(NML3Cfg *self) +{ + g_return_val_if_fail(NM_IS_L3CFG(self), NULL); + + if (self->priv.p->ipv4ll) + return nm_l3_ipv4ll_ref(self->priv.p->ipv4ll); + + /* We return the reference. But the NML3IPv4LL instance + * will call _nm_l3cfg_unregister_ipv4ll() when it gets + * destroyed. + * + * We don't have weak references, but NML3Cfg and NML3IPv4LL + * cooperate to handle this reference. */ + self->priv.p->ipv4ll = nm_l3_ipv4ll_new(self); + return self->priv.p->ipv4ll; +} + +void +_nm_l3cfg_unregister_ipv4ll(NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + /* we don't own the refernce to "self->priv.p->ipv4ll", but + * when that instance gets destroyed, we get called back to + * forget about it. Basically, it's like a weak pointer. */ + + nm_assert(self->priv.p->ipv4ll); + self->priv.p->ipv4ll = NULL; +} + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NML3Cfg *self = NM_L3CFG(object); + + switch (prop_id) { + case PROP_NETNS: + /* construct-only */ + self->priv.netns = g_object_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_NETNS(self->priv.netns)); + break; + case PROP_IFINDEX: + /* construct-only */ + self->priv.ifindex = g_value_get_int(value); + nm_assert(self->priv.ifindex > 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_l3cfg_init(NML3Cfg *self) +{ + self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate); + + c_list_init(&self->priv.p->acd_lst_head); + c_list_init(&self->priv.p->acd_event_notify_lst_head); + c_list_init(&self->priv.p->commit_type_lst_head); +} + +static void +constructed(GObject *object) +{ + NML3Cfg *self = NM_L3CFG(object); + + nm_assert(NM_IS_NETNS(self->priv.netns)); + nm_assert(self->priv.ifindex > 0); + + self->priv.platform = g_object_ref(nm_netns_get_platform(self->priv.netns)); + nm_assert(NM_IS_PLATFORM(self->priv.platform)); + + _LOGT("created (netns=" NM_HASH_OBFUSCATE_PTR_FMT ")", NM_HASH_OBFUSCATE_PTR(self->priv.netns)); + + G_OBJECT_CLASS(nm_l3cfg_parent_class)->constructed(object); + + _load_link(self, TRUE); +} + +NML3Cfg * +nm_l3cfg_new(NMNetns *netns, int ifindex) +{ + nm_assert(NM_IS_NETNS(netns)); + nm_assert(ifindex > 0); + + return g_object_new(NM_TYPE_L3CFG, NM_L3CFG_NETNS, netns, NM_L3CFG_IFINDEX, ifindex, NULL); +} + +static void +finalize(GObject *object) +{ + NML3Cfg *self = NM_L3CFG(object); + + nm_assert(!self->priv.p->l3_config_datas); + nm_assert(!self->priv.p->ipv4ll); + + nm_assert(c_list_is_empty(&self->priv.p->commit_type_lst_head)); + + nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); + + nm_assert(nm_g_array_len(self->priv.p->property_emit_list) == 0u); + + _l3_acd_data_prune(self, TRUE); + + nm_assert(c_list_is_empty(&self->priv.p->acd_lst_head)); + nm_assert(c_list_is_empty(&self->priv.p->acd_event_notify_lst_head)); + nm_assert(nm_g_hash_table_size(self->priv.p->acd_lst_hash) == 0); + + nm_clear_pointer(&self->priv.p->acd_lst_hash, g_hash_table_unref); + nm_clear_pointer(&self->priv.p->nacd, n_acd_unref); + nm_clear_g_source_inst(&self->priv.p->nacd_source); + nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); + + nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref); + + nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id); + nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref); + + nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); + + g_clear_object(&self->priv.netns); + g_clear_object(&self->priv.platform); + + nm_clear_l3cd(&self->priv.p->combined_l3cd_merged); + nm_clear_l3cd(&self->priv.p->combined_l3cd_commited); + + nm_clear_pointer(&self->priv.plobj, nmp_object_unref); + nm_clear_pointer(&self->priv.plobj_next, nmp_object_unref); + + nm_clear_pointer(&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref); + + _LOGT("finalized"); + + G_OBJECT_CLASS(nm_l3cfg_parent_class)->finalize(object); +} + +static void +nm_l3cfg_class_init(NML3CfgClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NML3CfgPrivate)); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + + obj_properties[PROP_NETNS] = + g_param_spec_pointer(NM_L3CFG_NETNS, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_L3CFG_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[SIGNAL_NOTIFY] = g_signal_new(NM_L3CFG_SIGNAL_NOTIFY, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER /* (const NML3ConfigNotifyData *) */); +} diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h new file mode 100644 index 0000000..5cfc93f --- /dev/null +++ b/src/core/nm-l3cfg.h @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_L3CFG_H__ +#define __NM_L3CFG_H__ + +#include "platform/nmp-object.h" +#include "nm-l3-config-data.h" + +#define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0 +#define NM_ACD_TIMEOUT_RFC5227_MSEC 9000u + +#define NM_TYPE_L3CFG (nm_l3cfg_get_type()) +#define NM_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_L3CFG, NML3Cfg)) +#define NM_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_L3CFG, NML3CfgClass)) +#define NM_IS_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_L3CFG)) +#define NM_IS_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_L3CFG)) +#define NM_L3CFG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_L3CFG, NML3CfgClass)) + +#define NM_L3CFG_NETNS "netns" +#define NM_L3CFG_IFINDEX "ifindex" + +#define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify" + +typedef enum _nm_packed { + NM_L3_ACD_DEFEND_TYPE_NONE, + NM_L3_ACD_DEFEND_TYPE_NEVER, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_L3_ACD_DEFEND_TYPE_ALWAYS, +} NML3AcdDefendType; + +typedef enum _nm_packed { + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_USED, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING, + NM_L3_ACD_ADDR_STATE_CONFLICT, + NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, +} NML3AcdAddrState; + +typedef struct { + const NMPObject * obj; + const NML3ConfigData *l3cd; + gconstpointer tag; + + struct { + guint32 acd_timeout_msec_track; + NML3AcdDefendType acd_defend_type_track; + bool acd_dirty_track : 1; + bool acd_failed_notified_track : 1; + } _priv; + +} NML3AcdAddrTrackInfo; + +typedef struct { + in_addr_t addr; + guint n_track_infos; + NML3AcdAddrState state; + NML3Cfg * l3cfg; + const NML3AcdAddrTrackInfo *track_infos; +} NML3AcdAddrInfo; + +static inline const NML3AcdAddrTrackInfo * +nm_l3_acd_addr_info_find_track_info(const NML3AcdAddrInfo *addr_info, + gconstpointer tag, + const NML3ConfigData * l3cd, + const NMPObject * obj) +{ + guint i; + const NML3AcdAddrTrackInfo *ti; + + nm_assert(addr_info); + + /* we always expect that the number n_track_infos is reasonably small. Hence, + * a naive linear search is simplest and fastest (e.g. we don't have a hash table). */ + + for (i = 0, ti = addr_info->track_infos; i < addr_info->n_track_infos; i++, ti++) { + if (l3cd && ti->l3cd != l3cd) + continue; + if (tag && ti->tag != tag) + continue; + if (obj && ti->obj != obj) + continue; + return ti; + } + + return NULL; +} + +typedef enum { + NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, + + NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, + + /* emitted at the end of nm_l3cfg_platform_commit(). */ + NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, + + /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes. + * It re-emits the platform signal. + * Contrary to NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, this even + * is re-emitted synchronously. */ + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, + + /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes. + * It re-emits the signal on an idle handler. The purpose is for something + * like NMDevice which is already subscribed to these signals, it can get the + * notifications without also subscribing directly to the platform. */ + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, + + NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, + + _NM_L3_CONFIG_NOTIFY_TYPE_NUM, +} NML3ConfigNotifyType; + +struct _NML3IPv4LL; + +typedef struct { + NML3ConfigNotifyType notify_type; + union { + struct { + NML3AcdAddrInfo info; + } acd_event; + + struct { + const NMPObject * obj; + NMPlatformSignalChangeType change_type; + } platform_change; + + struct { + guint32 obj_type_flags; + } platform_change_on_idle; + + struct { + struct _NML3IPv4LL *ipv4ll; + } ipv4ll_event; + }; +} NML3ConfigNotifyData; + +struct _NML3CfgPrivate; + +struct _NML3Cfg { + GObject parent; + struct { + struct _NML3CfgPrivate *p; + NMNetns * netns; + NMPlatform * platform; + const NMPObject * plobj; + const NMPObject * plobj_next; + int ifindex; + } priv; +}; + +typedef struct _NML3CfgClass NML3CfgClass; + +GType nm_l3cfg_get_type(void); + +NML3Cfg *nm_l3cfg_new(NMNetns *netns, int ifindex); + +/*****************************************************************************/ + +void _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags); + +void _nm_l3cfg_notify_platform_change(NML3Cfg * self, + NMPlatformSignalChangeType change_type, + const NMPObject * obj); + +/*****************************************************************************/ + +struct _NMDedupMultiIndex; + +struct _NMDedupMultiIndex *nm_netns_get_multi_idx(NMNetns *self); + +static inline struct _NMDedupMultiIndex * +nm_l3cfg_get_multi_idx(const NML3Cfg *self) +{ + return nm_netns_get_multi_idx(self->priv.netns); +} + +/*****************************************************************************/ + +static inline int +nm_l3cfg_get_ifindex(const NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + return self->priv.ifindex; +} + +static inline const NMPObject * +nm_l3cfg_get_plobj(const NML3Cfg *self, gboolean get_next) +{ + if (!self) + return NULL; + + nm_assert(NM_IS_L3CFG(self)); + + if (get_next) { + /* This is the instance that we just got reported in the last signal from + * the platform cache. It's probably exactly the same as if you would look + * into the platform cache. + * + * On the other hand, we pick up changes only on an idle handler. So the last + * decisions were not made based on this, but instead of "plobj". */ + return self->priv.plobj_next; + } + return self->priv.plobj; +} + +static inline const NMPlatformLink * +nm_l3cfg_get_pllink(const NML3Cfg *self, gboolean get_next) +{ + return NMP_OBJECT_CAST_LINK(nm_l3cfg_get_plobj(self, get_next)); +} + +static inline const char * +nm_l3cfg_get_ifname(const NML3Cfg *self, gboolean get_next) +{ + return nmp_object_link_get_ifname(nm_l3cfg_get_plobj(self, get_next)); +} + +static inline NMNetns * +nm_l3cfg_get_netns(const NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + return self->priv.netns; +} + +static inline NMPlatform * +nm_l3cfg_get_platform(const NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + return self->priv.platform; +} + +gboolean nm_l3cfg_get_acd_is_pending(NML3Cfg *self); + +/*****************************************************************************/ + +void _nm_l3cfg_emit_signal_notify(NML3Cfg *self, const NML3ConfigNotifyData *notify_data); + +/*****************************************************************************/ + +typedef enum { + NM_L3CFG_PROPERTY_EMIT_TYPE_ANY, + NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE, + NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE, +} NML3CfgPropertyEmitType; + +void nm_l3cfg_property_emit_register(NML3Cfg * self, + GObject * target_obj, + const GParamSpec * target_property, + NML3CfgPropertyEmitType emit_type); + +void nm_l3cfg_property_emit_unregister(NML3Cfg * self, + GObject * target_obj, + const GParamSpec *target_property); + +/*****************************************************************************/ + +void nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty); + +gboolean nm_l3cfg_add_config(NML3Cfg * self, + gconstpointer tag, + gboolean replace_same_tag, + const NML3ConfigData *l3cd, + int priority, + guint32 default_route_table_4, + guint32 default_route_table_6, + guint32 default_route_metric_4, + guint32 default_route_metric_6, + guint32 default_route_penalty_4, + guint32 default_route_penalty_6, + NML3AcdDefendType acd_defend_type, + guint32 acd_timeout_msec, + NML3ConfigMergeFlags merge_flags); + +gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg); + +gboolean nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty); + +/*****************************************************************************/ + +/* The numeric values of the enum matters: higher number mean more "important". + * E.g. "assume" tries to preserve the most settings, while "reapply" forces + * all configuration to match. */ +typedef enum _nm_packed { + + /* the NML3Cfg instance tracks with nm_l3cfg_commit_setup_register() the requested commit type. + * Use _NM_L3_CFG_COMMIT_TYPE_AUTO to automatically choose the level as requested. */ + NM_L3_CFG_COMMIT_TYPE_AUTO, + + /* Don't touch the interface. */ + NM_L3_CFG_COMMIT_TYPE_NONE, + + /* ASSUME means to keep any pre-existing extra routes/addresses, while + * also not adding routes/addresses that are not present yet. This is to + * gracefully take over after restart, where the existing IP configuration + * should not change. */ + NM_L3_CFG_COMMIT_TYPE_ASSUME, + + /* UPDATE means to add new addresses/routes, while also removing addresses/routes + * that are no longer present (but were previously configured by NetworkManager). + * Routes/addresses that were removed externally won't be re-added, and routes/addresses + * that are added externally won't be removed. */ + NM_L3_CFG_COMMIT_TYPE_UPDATE, + + /* This is a full sync. It configures the IP addresses/routes that are indicated, + * while removing the existing ones from the interface. */ + NM_L3_CFG_COMMIT_TYPE_REAPPLY, + +} NML3CfgCommitType; + +void nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type); + +void nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self); + +/*****************************************************************************/ + +const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr); + +/*****************************************************************************/ + +NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self); + +typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle; + +NML3CfgCommitTypeHandle *nm_l3cfg_commit_type_register(NML3Cfg * self, + NML3CfgCommitType commit_type, + NML3CfgCommitTypeHandle *existing_handle); + +void nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle); + +/*****************************************************************************/ + +const NML3ConfigData *nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited); + +const NMPObject * +nm_l3cfg_get_best_default_route(NML3Cfg *self, int addr_family, gboolean get_commited); + +/*****************************************************************************/ + +gboolean nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self); + +/*****************************************************************************/ + +struct _NML3IPv4LL *nm_l3cfg_get_ipv4ll(NML3Cfg *self); + +struct _NML3IPv4LL *nm_l3cfg_access_ipv4ll(NML3Cfg *self); + +void _nm_l3cfg_unregister_ipv4ll(NML3Cfg *self); + +/*****************************************************************************/ + +#endif /* __NM_L3CFG_H__ */ diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c new file mode 100644 index 0000000..5e5f4e7 --- /dev/null +++ b/src/core/nm-manager.c @@ -0,0 +1,8696 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2009 Novell, Inc. + * Copyright (C) 2007 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-c-list.h" + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-dbus-manager.h" +#include "vpn/nm-vpn-manager.h" +#include "devices/nm-device.h" +#include "devices/nm-device-generic.h" +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "nm-hostname-manager.h" +#include "nm-keep-alive.h" +#include "nm-rfkill-manager.h" +#include "dhcp/nm-dhcp-manager.h" +#include "settings/nm-settings.h" +#include "settings/nm-settings-connection.h" +#include "nm-auth-utils.h" +#include "nm-auth-manager.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-factory.h" +#include "nm-sleep-monitor.h" +#include "nm-connectivity.h" +#include "nm-policy.h" +#include "nm-session-monitor.h" +#include "nm-act-request.h" +#include "nm-core-internal.h" +#include "nm-config.h" +#include "nm-audit-manager.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-checkpoint.h" +#include "nm-checkpoint-manager.h" +#include "nm-dbus-object.h" +#include "nm-dispatcher.h" +#include "NetworkManagerUtils.h" + +#define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u + +/*****************************************************************************/ + +typedef struct { + gboolean user_enabled; + gboolean sw_enabled; + gboolean hw_enabled; + RfKillType rtype; + NMConfigRunStatePropertyType key; + const char * desc; + const char * prop; + const char * hw_prop; +} RadioState; + +typedef enum { + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL, + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2, +} AsyncOpType; + +typedef struct { + CList async_op_lst; + NMManager * self; + AsyncOpType async_op_type; + union { + struct { + NMActiveConnection *active; + union { + struct { + GDBusMethodInvocation *invocation; + } activate_user; + struct { + GDBusMethodInvocation * invocation; + NMConnection * connection; + NMSettingsConnectionPersistMode persist_mode; + bool is_volatile : 1; + } add_and_activate; + }; + } ac_auth; + }; +} AsyncOpData; + +enum { + DEVICE_ADDED, + INTERNAL_DEVICE_ADDED, + DEVICE_REMOVED, + INTERNAL_DEVICE_REMOVED, + ACTIVE_CONNECTION_ADDED, + ACTIVE_CONNECTION_REMOVED, + CONFIGURE_QUIT, + DEVICE_IFINDEX_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMManager, + PROP_VERSION, + PROP_CAPABILITIES, + PROP_STATE, + PROP_STARTUP, + PROP_NETWORKING_ENABLED, + PROP_WIRELESS_ENABLED, + PROP_WIRELESS_HARDWARE_ENABLED, + PROP_WWAN_ENABLED, + PROP_WWAN_HARDWARE_ENABLED, + PROP_WIMAX_ENABLED, + PROP_WIMAX_HARDWARE_ENABLED, + PROP_ACTIVE_CONNECTIONS, + PROP_CONNECTIVITY, + PROP_CONNECTIVITY_CHECK_AVAILABLE, + PROP_CONNECTIVITY_CHECK_ENABLED, + PROP_CONNECTIVITY_CHECK_URI, + PROP_PRIMARY_CONNECTION, + PROP_PRIMARY_CONNECTION_TYPE, + PROP_ACTIVATING_CONNECTION, + PROP_DEVICES, + PROP_METERED, + PROP_GLOBAL_DNS_CONFIGURATION, + PROP_ALL_DEVICES, + PROP_CHECKPOINTS, + + /* Not exported */ + PROP_SLEEPING, ); + +typedef struct { + NMPlatform *platform; + + GArray *capabilities; + + CList active_connections_lst_head; /* Oldest ACs at the beginning */ + CList async_op_lst_head; + guint ac_cleanup_id; + NMActiveConnection *primary_connection; + NMActiveConnection *activating_connection; + NMMetered metered; + + CList devices_lst_head; + + NMState state; + NMConfig * config; + NMConnectivity * concheck_mgr; + NMPolicy * policy; + NMHostnameManager *hostname_manager; + + struct { + GDBusConnection *connection; + guint id; + } prop_filter; + NMRfkillManager *rfkill_mgr; + + CList link_cb_lst; + + NMCheckpointManager *checkpoint_mgr; + + NMSettings *settings; + + RadioState radio_states[RFKILL_TYPE_MAX]; + NMVpnManager *vpn_manager; + + NMSleepMonitor *sleep_monitor; + + NMAuthManager *auth_mgr; + + GHashTable *device_route_metrics; + + CList auth_lst_head; + + GHashTable *sleep_devices; + + /* Firmware dir monitor */ + GFileMonitor *fw_monitor; + guint fw_changed_id; + + guint timestamp_update_id; + + guint devices_inited_id; + + NMConnectivityState connectivity_state; + + guint8 device_state_prune_ratelimit_count; + + bool startup : 1; + bool devices_inited : 1; + + bool sleeping : 1; + bool net_enabled : 1; + + unsigned connectivity_check_enabled_last : 2; + + guint delete_volatile_connection_idle_id; + CList delete_volatile_connection_lst_head; +} NMManagerPrivate; + +struct _NMManager { + NMDBusObject parent; + NMManagerPrivate _priv; +}; + +typedef struct { + NMDBusObjectClass parent; +} NMManagerClass; + +G_DEFINE_TYPE(NMManager, nm_manager, NM_TYPE_DBUS_OBJECT) + +#define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_INSTANCE(NMManager); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "manager" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, domain, device, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + const char *const _ifname = _nm_device_get_iface(device); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + _ifname, \ + NULL, \ + "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : ""), \ + NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, "): ", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG3(level, domain, connection, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + NMConnection *const _connection = (connection); \ + const char *const _con_id = _nm_connection_get_id(_connection); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + NULL, \ + _nm_connection_get_uuid(_connection), \ + "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : ""), \ + NM_PRINT_FMT_QUOTED(_con_id, "(", _con_id, ") ", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_manager; +static const GDBusSignalInfo signal_info_check_permissions; +static const GDBusSignalInfo signal_info_state_changed; +static const GDBusSignalInfo signal_info_device_added; +static const GDBusSignalInfo signal_info_device_removed; + +static void update_connectivity_value(NMManager *self); + +static gboolean add_device(NMManager *self, NMDevice *device, GError **error); + +static void _emit_device_added_removed(NMManager *self, NMDevice *device, gboolean is_added); + +static NMActiveConnection *_new_active_connection(NMManager * self, + gboolean is_vpn, + NMSettingsConnection * sett_conn, + NMConnection * incompl_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error); + +static void policy_activating_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data); + +static gboolean find_master(NMManager * self, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection **out_master_connection, + NMDevice ** out_master_device, + NMActiveConnection ** out_master_ac, + GError ** error); + +static void nm_manager_update_state(NMManager *manager); + +static void connection_changed(NMManager *self, NMSettingsConnection *sett_conn); +static void device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self); + +static void +settings_startup_complete_changed(NMSettings *settings, GParamSpec *pspec, NMManager *self); + +static void retry_connections_for_parent_device(NMManager *self, NMDevice *device); + +static void +active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self); +static void +active_connection_default_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self); +static void active_connection_parent_active(NMActiveConnection *active, + NMActiveConnection *parent_ac, + NMManager * self); + +static NMActiveConnection *active_connection_find(NMManager * self, + NMSettingsConnection * sett_conn, + const char * uuid, + NMActiveConnectionState max_state, + GPtrArray ** out_all_matching); + +static NMConnectivity *concheck_get_mgr(NMManager *self); + +static void _internal_activation_auth_done(NMManager * self, + NMActiveConnection *active, + gboolean success, + const char * error_desc); +static void _add_and_activate_auth_done(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active, + NMConnection * connection, + GDBusMethodInvocation * invocation, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile, + gboolean success, + const char * error_desc); +static void _activation_auth_done(NMManager * self, + NMActiveConnection * active, + GDBusMethodInvocation *invocation, + gboolean success, + const char * error_desc); + +/*****************************************************************************/ + +static NM_CACHED_QUARK_FCN("autoconnect-root", autoconnect_root_quark); + +/*****************************************************************************/ + +static gboolean +_connection_is_vpn(NMConnection *connection) +{ + const char *type; + + type = nm_connection_get_connection_type(connection); + if (type) + return nm_streq(type, NM_SETTING_VPN_SETTING_NAME); + + /* we have an incomplete (invalid) connection at hand. That can only + * happen during AddAndActivate. Determine whether it's VPN type based + * on the existence of a [vpn] section. */ + return !!nm_connection_get_setting_vpn(connection); +} + +/*****************************************************************************/ + +static gboolean +concheck_enabled(NMManager *self, gboolean *out_changed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint check_enabled; + + check_enabled = nm_connectivity_check_enabled(concheck_get_mgr(self)) ? 1 : 2; + if (priv->connectivity_check_enabled_last == check_enabled) + NM_SET_OUT(out_changed, FALSE); + else { + NM_SET_OUT(out_changed, TRUE); + priv->connectivity_check_enabled_last = check_enabled; + } + return check_enabled == 1; +} + +static void +concheck_config_changed_cb(NMConnectivity *connectivity, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + gboolean changed; + + concheck_enabled(self, &changed); + if (changed) + _notify(self, PROP_CONNECTIVITY_CHECK_ENABLED); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_check_connectivity_update_interval(device); +} + +static NMConnectivity * +concheck_get_mgr(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->concheck_mgr)) { + priv->concheck_mgr = g_object_ref(nm_connectivity_get()); + g_signal_connect(priv->concheck_mgr, + NM_CONNECTIVITY_CONFIG_CHANGED, + G_CALLBACK(concheck_config_changed_cb), + self); + } + return priv->concheck_mgr; +} + +/*****************************************************************************/ + +static AsyncOpData * +_async_op_data_new_authorize_activate_internal(NMManager *self, NMActiveConnection *active_take) +{ + AsyncOpData *async_op_data; + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static AsyncOpData * +_async_op_data_new_ac_auth_activate_user(NMManager * self, + NMActiveConnection * active_take, + GDBusMethodInvocation *invocation_take) +{ + AsyncOpData *async_op_data; + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + async_op_data->ac_auth.activate_user.invocation = invocation_take; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static AsyncOpData * +_async_op_data_new_ac_auth_add_and_activate(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active_take, + GDBusMethodInvocation * invocation_take, + NMConnection * connection_take, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile) +{ + AsyncOpData *async_op_data; + + nm_assert(NM_IN_SET(async_op_type, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2)); + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = async_op_type; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + async_op_data->ac_auth.add_and_activate.invocation = invocation_take; + async_op_data->ac_auth.add_and_activate.connection = connection_take; + async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode; + async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static void +_async_op_complete_ac_auth_cb(NMActiveConnection *active, + gboolean success, + const char * error_desc, + gpointer user_data) +{ + AsyncOpData *async_op_data = user_data; + + nm_assert(async_op_data); + nm_assert(NM_IS_MANAGER(async_op_data->self)); + nm_assert( + nm_c_list_contains_entry(&NM_MANAGER_GET_PRIVATE(async_op_data->self)->async_op_lst_head, + async_op_data, + async_op_lst)); + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(active == async_op_data->ac_auth.active); + + c_list_unlink(&async_op_data->async_op_lst); + + switch (async_op_data->async_op_type) { + case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL: + _internal_activation_auth_done(async_op_data->self, + async_op_data->ac_auth.active, + success, + error_desc); + break; + case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER: + _activation_auth_done(async_op_data->self, + async_op_data->ac_auth.active, + async_op_data->ac_auth.activate_user.invocation, + success, + error_desc); + break; + case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE: + case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2: + _add_and_activate_auth_done(async_op_data->self, + async_op_data->async_op_type, + async_op_data->ac_auth.active, + async_op_data->ac_auth.add_and_activate.connection, + async_op_data->ac_auth.add_and_activate.invocation, + async_op_data->ac_auth.add_and_activate.persist_mode, + async_op_data->ac_auth.add_and_activate.is_volatile, + success, + error_desc); + g_object_unref(async_op_data->ac_auth.add_and_activate.connection); + break; + default: + nm_assert_not_reached(); + break; + } + + g_object_unref(async_op_data->ac_auth.active); + g_object_unref(async_op_data->self); + g_slice_free(AsyncOpData, async_op_data); +} + +/*****************************************************************************/ + +typedef struct { + int ifindex; + guint32 aspired_metric; + guint32 effective_metric; +} DeviceRouteMetricData; + +static DeviceRouteMetricData * +_device_route_metric_data_new(int ifindex, guint32 aspired_metric, guint32 effective_metric) +{ + DeviceRouteMetricData *data; + + nm_assert(ifindex > 0); + + /* For IPv4, metrics can use the entire uint32 bit range. For IPv6, + * zero is treated like 1024. Since we handle IPv4 and IPv6 identically, + * we cannot allow a zero metric here. + */ + nm_assert(aspired_metric > 0); + nm_assert(effective_metric == 0 || aspired_metric <= effective_metric); + + data = g_slice_new0(DeviceRouteMetricData); + data->ifindex = ifindex; + data->aspired_metric = aspired_metric; + data->effective_metric = effective_metric ?: aspired_metric; + return data; +} + +static guint +_device_route_metric_data_by_ifindex_hash(gconstpointer p) +{ + const DeviceRouteMetricData *data = p; + NMHashState h; + + nm_hash_init(&h, 1030338191); + nm_hash_update_vals(&h, data->ifindex); + return nm_hash_complete(&h); +} + +static gboolean +_device_route_metric_data_by_ifindex_equal(gconstpointer pa, gconstpointer pb) +{ + const DeviceRouteMetricData *a = pa; + const DeviceRouteMetricData *b = pb; + + return a->ifindex == b->ifindex; +} + +static guint32 +_device_route_metric_get(NMManager * self, + int ifindex, + NMDeviceType device_type, + gboolean lookup_only, + guint32 * out_aspired_metric) +{ + NMManagerPrivate * priv; + const DeviceRouteMetricData *d2; + DeviceRouteMetricData * data; + DeviceRouteMetricData data_lookup; + const NMDedupMultiHeadEntry *all_links_head; + NMPObject links_needle; + guint n_links; + gboolean cleaned = FALSE; + GHashTableIter h_iter; + guint32 metric; + + g_return_val_if_fail(NM_IS_MANAGER(self), 0); + + NM_SET_OUT(out_aspired_metric, 0); + + if (ifindex <= 0) { + if (lookup_only) + return 0; + metric = nm_device_get_route_metric_default(device_type); + NM_SET_OUT(out_aspired_metric, metric); + return metric; + } + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (lookup_only && !priv->device_route_metrics) + return 0; + + if (G_UNLIKELY(!priv->device_route_metrics)) { + const GHashTable * h; + const NMConfigDeviceStateData *device_state; + + priv->device_route_metrics = + g_hash_table_new_full(_device_route_metric_data_by_ifindex_hash, + _device_route_metric_data_by_ifindex_equal, + NULL, + nm_g_slice_free_fcn(DeviceRouteMetricData)); + cleaned = TRUE; + + /* we need to pre-populate the cache for all (still existing) devices from the state-file */ + h = nm_config_device_state_get_all(priv->config); + if (!h) + goto initited; + + g_hash_table_iter_init(&h_iter, (GHashTable *) h); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &device_state)) { + if (!device_state->route_metric_default_effective) + continue; + if (!nm_platform_link_get(priv->platform, device_state->ifindex)) { + /* we have the entry in the state file, but (currently) no such + * ifindex exists in platform. Most likely the entry is obsolete, + * hence we skip it. */ + continue; + } + if (!g_hash_table_add( + priv->device_route_metrics, + _device_route_metric_data_new(device_state->ifindex, + device_state->route_metric_default_aspired, + device_state->route_metric_default_effective))) + nm_assert_not_reached(); + } + } + +initited: + data_lookup.ifindex = ifindex; + + data = g_hash_table_lookup(priv->device_route_metrics, &data_lookup); + if (data) + goto out; + if (lookup_only) + return 0; + + if (!cleaned) { + /* get the number of all links in the platform cache. */ + all_links_head = nm_platform_lookup_all(priv->platform, + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + nmp_object_stackinit_id_link(&links_needle, 1)); + n_links = all_links_head ? all_links_head->len : 0; + + /* on systems where a lot of devices are created and go away, the index contains + * a lot of stale entries. We must from time to time clean them up. + * + * Do do this cleanup, whenever we have more entries then 2 times the number of links. */ + if (G_UNLIKELY(g_hash_table_size(priv->device_route_metrics) > NM_MAX(20, n_links * 2))) { + /* from time to time, we need to do some house-keeping and prune stale entries. + * Otherwise, on a system where interfaces frequently come and go (docker), we + * keep growing this cache for ifindexes that no longer exist. */ + g_hash_table_iter_init(&h_iter, priv->device_route_metrics); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &d2)) { + if (!nm_platform_link_get(priv->platform, d2->ifindex)) + g_hash_table_iter_remove(&h_iter); + } + cleaned = TRUE; + } + } + + data = + _device_route_metric_data_new(ifindex, nm_device_get_route_metric_default(device_type), 0); + + /* unfortunately, there is no stright forward way to lookup all reserved metrics. + * Note, that we don't only have to know which metrics are currently reserved, + * but also, which metrics are now seemingly un-used but caused another reserved + * metric to be bumped. Hence, the naive O(n^2) search :( + * + * Well, technically, since we limit bumping the metric to 50, this entire + * loop runs at most 50 times, so it's still O(n). Let's just say, it's not + * very efficient. */ +again: + g_hash_table_iter_init(&h_iter, priv->device_route_metrics); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &d2)) { + if (data->effective_metric < d2->aspired_metric + || data->effective_metric > d2->effective_metric) { + /* no overlap. Skip. */ + continue; + } + if (!cleaned && !nm_platform_link_get(priv->platform, d2->ifindex)) { + /* the metric seems taken, but there is no such interface. This entry + * is stale, forget about it. */ + g_hash_table_iter_remove(&h_iter); + continue; + } + + if (d2->effective_metric == G_MAXUINT32) { + /* we cannot bump the metric any further. Done. + * + * Actually, this can currently not happen because the aspired_metric + * are small numbers and we limit the bumping to 50. Still, for + * completeness... */ + data->effective_metric = G_MAXUINT32; + break; + } + + if (d2->effective_metric - data->aspired_metric >= 50) { + /* as one active interface reserves an entire range of metrics + * (from aspired_metric to effective_metric), that means if you + * alternatingly activate two interfaces, their metric will + * bump each other. + * + * Limit this, bump the metric at most 50 points. */ + data->effective_metric = data->aspired_metric + 50; + break; + } + + /* bump the metric, and search again. */ + data->effective_metric = d2->effective_metric + 1; + goto again; + } + + _LOGT(LOGD_DEVICE, + "default-route-metric: ifindex %d reserves metric %u (aspired %u)", + data->ifindex, + data->effective_metric, + data->aspired_metric); + + if (!g_hash_table_add(priv->device_route_metrics, data)) + nm_assert_not_reached(); + +out: + NM_SET_OUT(out_aspired_metric, data->aspired_metric); + return data->effective_metric; +} + +guint32 +nm_manager_device_route_metric_reserve(NMManager *self, int ifindex, NMDeviceType device_type) +{ + guint32 metric; + + metric = _device_route_metric_get(self, ifindex, device_type, FALSE, NULL); + nm_assert(metric != 0); + return metric; +} + +void +nm_manager_device_route_metric_clear(NMManager *self, int ifindex) +{ + NMManagerPrivate * priv; + DeviceRouteMetricData data_lookup; + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (!priv->device_route_metrics) + return; + data_lookup.ifindex = ifindex; + if (g_hash_table_remove(priv->device_route_metrics, &data_lookup)) { + _LOGT(LOGD_DEVICE, "default-route-metric: ifindex %d released", ifindex); + } +} + +/*****************************************************************************/ + +static void +_delete_volatile_connection_do(NMManager *self, NMSettingsConnection *connection) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (!NM_FLAGS_ANY(nm_settings_connection_get_flags(connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + if (!nm_settings_has_connection(priv->settings, connection)) + return; + if (active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NULL)) + return; + + _LOGD(LOGD_DEVICE, + "volatile connection disconnected. Deleting connection '%s' (%s)", + nm_settings_connection_get_id(connection), + nm_settings_connection_get_uuid(connection)); + nm_settings_connection_delete(connection, FALSE); +} + +/* Returns: whether to notify D-Bus of the removal or not */ +static gboolean +active_connection_remove(NMManager *self, NMActiveConnection *active) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMSettingsConnection *connection = NULL; + gboolean notify; + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(c_list_contains(&priv->active_connections_lst_head, &active->active_connections_lst)); + + notify = nm_dbus_object_is_exported(NM_DBUS_OBJECT(active)); + + c_list_unlink(&active->active_connections_lst); + g_signal_emit(self, signals[ACTIVE_CONNECTION_REMOVED], 0, active); + g_signal_handlers_disconnect_by_func(active, active_connection_state_changed, self); + g_signal_handlers_disconnect_by_func(active, active_connection_default_changed, self); + g_signal_handlers_disconnect_by_func(active, active_connection_parent_active, self); + + connection = nm_g_object_ref(nm_active_connection_get_settings_connection(active)); + + nm_dbus_object_clear_and_unexport(&active); + + if (connection) + _delete_volatile_connection_do(self, connection); + + return notify; +} + +static gboolean +_active_connection_cleanup(gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac, *ac_safe; + + priv->ac_cleanup_id = 0; + + g_object_freeze_notify(G_OBJECT(self)); + c_list_for_each_entry_safe (ac, + ac_safe, + &priv->active_connections_lst_head, + active_connections_lst) { + if (nm_active_connection_get_state(ac) == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + if (active_connection_remove(self, ac)) + _notify(self, PROP_ACTIVE_CONNECTIONS); + } + } + g_object_thaw_notify(G_OBJECT(self)); + + return FALSE; +} + +static void +active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnectionState state; + NMSettingsConnection * con; + + state = nm_active_connection_get_state(active); + if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + /* Destroy active connections from an idle handler to ensure that + * their last property change notifications go out, which wouldn't + * happen if we destroyed them immediately when their state was set + * to DEACTIVATED. + */ + if (!priv->ac_cleanup_id) + priv->ac_cleanup_id = g_idle_add(_active_connection_cleanup, self); + + con = nm_active_connection_get_settings_connection(active); + if (con) + g_object_set_qdata(G_OBJECT(con), autoconnect_root_quark(), NULL); + } + + nm_manager_update_state(self); +} + +static void +active_connection_default_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self) +{ + nm_manager_update_state(self); +} + +/** + * active_connection_add(): + * @self: the #NMManager + * @active: the #NMActiveConnection to manage + * + * Begins to track and manage @active. Increases the refcount of @active. + */ +static void +active_connection_add(NMManager *self, NMActiveConnection *active) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(!c_list_is_linked(&active->active_connections_lst)); + + c_list_link_tail(&priv->active_connections_lst_head, &active->active_connections_lst); + g_object_ref(active); + + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(active_connection_state_changed), + self); + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_DEFAULT, + G_CALLBACK(active_connection_default_changed), + self); + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_DEFAULT6, + G_CALLBACK(active_connection_default_changed), + self); + + if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(active))) + nm_dbus_object_export(NM_DBUS_OBJECT(active)); + + g_signal_emit(self, signals[ACTIVE_CONNECTION_ADDED], 0, active); + + _notify(self, PROP_ACTIVE_CONNECTIONS); +} + +const CList * +nm_manager_get_active_connections(NMManager *manager) +{ + return &NM_MANAGER_GET_PRIVATE(manager)->active_connections_lst_head; +} + +static NMActiveConnection * +active_connection_find( + NMManager * self, + NMSettingsConnection * sett_conn, + const char * uuid, + NMActiveConnectionState max_state /* candidates in state @max_state will be found */, + GPtrArray ** out_all_matching) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + NMActiveConnection *best_ac = NULL; + GPtrArray * all = NULL; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!out_all_matching || !*out_all_matching); + + c_list_for_each_entry_prev (ac, &priv->active_connections_lst_head, active_connections_lst) { + NMSettingsConnection *ac_conn; + + ac_conn = nm_active_connection_get_settings_connection(ac); + if (sett_conn && sett_conn != ac_conn) + continue; + if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn))) + continue; + if (nm_active_connection_get_state(ac) > max_state) + continue; + + if (!out_all_matching) + return ac; + + if (!best_ac) { + best_ac = ac; + continue; + } + + if (!all) { + all = g_ptr_array_new_with_free_func(g_object_unref); + g_ptr_array_add(all, g_object_ref(best_ac)); + } + g_ptr_array_add(all, g_object_ref(ac)); + } + + if (!best_ac) { + AsyncOpData *async_op_data; + + c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) { + NMSettingsConnection *ac_conn; + + ac = async_op_data->ac_auth.active; + ac_conn = nm_active_connection_get_settings_connection(ac); + if (sett_conn && sett_conn != ac_conn) + continue; + if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn))) + continue; + + if (!out_all_matching) + return ac; + + if (!best_ac) { + best_ac = ac; + continue; + } + + if (!all) { + all = g_ptr_array_new_with_free_func(g_object_unref); + g_ptr_array_add(all, g_object_ref(best_ac)); + } + g_ptr_array_add(all, g_object_ref(ac)); + } + + if (!best_ac) + return NULL; + } + + /* as an optimization, we only allocate out_all_matching, if there are more + * than one result. If there is only one result, we only return the single + * element and don't bother allocating an array. That's the common case. + * + * Also, in case we have multiple results, we return the *first* one + * as @best_ac. */ + nm_assert(!all || (all->len >= 2 && all->pdata[0] == best_ac)); + + *out_all_matching = all; + return best_ac; +} + +static NMActiveConnection * +active_connection_find_by_connection(NMManager * self, + NMSettingsConnection * sett_conn, + NMConnection * connection, + NMActiveConnectionState max_state, + GPtrArray ** out_all_matching) +{ + nm_assert(NM_IS_MANAGER(self)); + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + + /* Depending on whether connection is a settings connection, + * either lookup by object-identity of @connection, or compare the UUID */ + return active_connection_find(self, + sett_conn, + sett_conn ? NULL : nm_connection_get_uuid(connection), + max_state, + out_all_matching); +} + +typedef struct { + NMManager *self; + gboolean for_auto_activation; +} GetActivatableConnectionsFilterData; + +static gboolean +_get_activatable_connections_filter(NMSettings * settings, + NMSettingsConnection *sett_conn, + gpointer user_data) +{ + const GetActivatableConnectionsFilterData *d = user_data; + NMConnectionMultiConnect multi_connect; + + if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return FALSE; + + multi_connect = + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(sett_conn)); + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE + || (multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE + && !d->for_auto_activation)) + return TRUE; + + /* the connection is activatable, if it has no active-connections that are in state + * activated, activating, or waiting to be activated. */ + return !active_connection_find(d->self, + sett_conn, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL); +} + +NMSettingsConnection ** +nm_manager_get_activatable_connections(NMManager *manager, + gboolean for_auto_activation, + gboolean sort, + guint * out_len) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + const GetActivatableConnectionsFilterData d = { + .self = manager, + .for_auto_activation = for_auto_activation, + }; + + return nm_settings_get_connections_clone( + priv->settings, + out_len, + _get_activatable_connections_filter, + (gpointer) &d, + sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL, + NULL); +} + +static NMActiveConnection * +active_connection_get_by_path(NMManager *self, const char *path) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + + ac = nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path); + if (!ac || !NM_IS_ACTIVE_CONNECTION(ac) || c_list_is_empty(&ac->active_connections_lst)) + return NULL; + + nm_assert(c_list_contains(&priv->active_connections_lst_head, &ac->active_connections_lst)); + return ac; +} + +/*****************************************************************************/ + +static void +_config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMManager * self) +{ + g_object_freeze_notify(G_OBJECT(self)); + + if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) + _notify(self, PROP_GLOBAL_DNS_CONFIGURATION); + + if (!nm_streq0(nm_config_data_get_connectivity_uri(config_data), + nm_config_data_get_connectivity_uri(old_data))) { + if ((!nm_config_data_get_connectivity_uri(config_data)) + != (!nm_config_data_get_connectivity_uri(old_data))) + _notify(self, PROP_CONNECTIVITY_CHECK_AVAILABLE); + _notify(self, PROP_CONNECTIVITY_CHECK_URI); + } + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_reload_auth_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + GError * ret_error = NULL; + NMAuthCallResult result; + guint32 flags; + NMAuthSubject * subject; + char s_buf[60]; + NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + flags = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "flags")); + + subject = nm_auth_chain_get_subject(chain); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_RELOAD); + if (result != NM_AUTH_CALL_RESULT_YES) { + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to reload configuration"); + } else { + if (NM_FLAGS_ANY(flags, ~NM_MANAGER_RELOAD_FLAG_ALL)) { + /* invalid flags */ + } else if (flags == 0) + reload_type = NM_CONFIG_CHANGE_CAUSE_SIGHUP; + else { + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_CONF)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_CONF; + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_DNS_RC)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_RC; + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_DNS_FULL)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_FULL; + } + + if (reload_type == NM_CONFIG_CHANGE_NONE) { + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Invalid flags for reload"); + } + } + + nm_audit_log_control_op(NM_AUDIT_OP_RELOAD, + nm_sprintf_buf(s_buf, "%u", flags), + ret_error == NULL, + subject, + ret_error ? ret_error->message : NULL); + + if (ret_error) { + g_dbus_method_invocation_take_error(context, ret_error); + return; + } + + nm_config_reload(priv->config, reload_type, TRUE); + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_manager_reload(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + guint32 flags; + + g_variant_get(parameters, "(u)", &flags); + + chain = nm_auth_chain_new_context(invocation, _reload_auth_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "flags", GUINT_TO_POINTER(flags), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_RELOAD, TRUE); +} + +/*****************************************************************************/ + +NMDevice * +nm_manager_get_device_by_path(NMManager *self, const char *path) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(path, NULL); + + device = nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path); + if (!device || !NM_IS_DEVICE(device) || c_list_is_empty(&device->devices_lst)) + return NULL; + + nm_assert(c_list_contains(&priv->devices_lst_head, &device->devices_lst)); + return device; +} + +NMDevice * +nm_manager_get_device_by_ifindex(NMManager *self, int ifindex) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + if (ifindex > 0) { + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_ifindex(device) == ifindex) + return device; + } + } + + return NULL; +} + +static NMDevice * +find_device_by_permanent_hw_addr(NMManager *self, const char *hwaddr) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + const char * device_addr; + guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX]; + gsize hwaddr_len; + + g_return_val_if_fail(hwaddr != NULL, NULL); + + if (!_nm_utils_hwaddr_aton(hwaddr, hwaddr_bin, sizeof(hwaddr_bin), &hwaddr_len)) + return NULL; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + device_addr = nm_device_get_permanent_hw_address(device); + if (device_addr && nm_utils_hwaddr_matches(hwaddr_bin, hwaddr_len, device_addr, -1)) + return device; + } + return NULL; +} + +static NMDevice * +find_device_by_ip_iface(NMManager *self, const char *iface) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(iface, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_real(device) && nm_streq0(nm_device_get_ip_iface(device), iface)) + return device; + } + return NULL; +} + +/** + * find_device_by_iface: + * @self: the #NMManager + * @iface: the device interface to find + * @connection: a connection to ensure the returned device is compatible with + * @slave: a slave connection to ensure a master is compatible with + * + * Finds a device by interface name, preferring realized devices. If @slave + * is given, this function will only return master devices and will ensure + * @slave, when activated, can be a slave of the returned master device. If + * @connection is given, this function will only consider devices that are + * compatible with @connection. + * + * Returns: the matching #NMDevice + */ +static NMDevice * +find_device_by_iface(NMManager * self, + const char * iface, + NMConnection *connection, + NMConnection *slave) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * fallback = NULL; + NMDevice * candidate; + + g_return_val_if_fail(iface != NULL, NULL); + + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (!nm_streq(nm_device_get_iface(candidate), iface)) + continue; + if (connection && !nm_device_check_connection_compatible(candidate, connection, NULL)) + continue; + if (slave) { + if (!nm_device_is_master(candidate)) + continue; + if (!nm_device_check_slave_connection_compatible(candidate, slave)) + continue; + } + + if (nm_device_is_real(candidate)) + return candidate; + else if (!fallback) + fallback = candidate; + } + return fallback; +} + +static gboolean +manager_sleeping(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->sleeping || !priv->net_enabled) + return TRUE; + return FALSE; +} + +static const char * +_nm_state_to_string(NMState state) +{ + switch (state) { + case NM_STATE_ASLEEP: + return "ASLEEP"; + case NM_STATE_DISCONNECTED: + return "DISCONNECTED"; + case NM_STATE_DISCONNECTING: + return "DISCONNECTING"; + case NM_STATE_CONNECTING: + return "CONNECTING"; + case NM_STATE_CONNECTED_LOCAL: + return "CONNECTED_LOCAL"; + case NM_STATE_CONNECTED_SITE: + return "CONNECTED_SITE"; + case NM_STATE_CONNECTED_GLOBAL: + return "CONNECTED_GLOBAL"; + case NM_STATE_UNKNOWN: + default: + return "UNKNOWN"; + } +} + +static NMState +find_best_device_state(NMManager *manager) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + NMState best_state = NM_STATE_DISCONNECTED; + NMActiveConnection *ac; + + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + NMActiveConnectionState ac_state = nm_active_connection_get_state(ac); + + switch (ac_state) { + case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + if (nm_active_connection_get_default(ac, AF_UNSPEC)) { + if (priv->connectivity_state == NM_CONNECTIVITY_FULL) + return NM_STATE_CONNECTED_GLOBAL; + + best_state = NM_STATE_CONNECTED_SITE; + } else { + if (best_state < NM_STATE_CONNECTING) + best_state = NM_STATE_CONNECTED_LOCAL; + } + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + if (!NM_IN_SET(nm_active_connection_get_activation_type(ac), + NM_ACTIVATION_TYPE_EXTERNAL, + NM_ACTIVATION_TYPE_ASSUME)) { + if (best_state != NM_STATE_CONNECTED_GLOBAL) + best_state = NM_STATE_CONNECTING; + } + break; + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: + if (!NM_IN_SET(nm_active_connection_get_activation_type(ac), + NM_ACTIVATION_TYPE_EXTERNAL, + NM_ACTIVATION_TYPE_ASSUME)) { + if (best_state < NM_STATE_DISCONNECTING) + best_state = NM_STATE_DISCONNECTING; + } + break; + default: + break; + } + } + + return best_state; +} + +static void +nm_manager_update_metered(NMManager *self) +{ + NMManagerPrivate *priv; + NMDevice * device; + NMMetered value = NM_METERED_UNKNOWN; + + g_return_if_fail(NM_IS_MANAGER(self)); + priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->primary_connection) { + device = nm_active_connection_get_device(priv->primary_connection); + if (device) + value = nm_device_get_metered(device); + } + + if (value != priv->metered) { + priv->metered = value; + _LOGD(LOGD_CORE, "new metered value: %d", (int) priv->metered); + _notify(self, PROP_METERED); + } +} + +NMMetered +nm_manager_get_metered(NMManager *self) +{ + g_return_val_if_fail(NM_IS_MANAGER(self), NM_METERED_UNKNOWN); + + return NM_MANAGER_GET_PRIVATE(self)->metered; +} + +static void +nm_manager_update_state(NMManager *self) +{ + NMManagerPrivate *priv; + NMState new_state = NM_STATE_DISCONNECTED; + + g_return_if_fail(NM_IS_MANAGER(self)); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (manager_sleeping(self)) + new_state = NM_STATE_ASLEEP; + else + new_state = find_best_device_state(self); + + if (new_state >= NM_STATE_CONNECTED_LOCAL && priv->connectivity_state == NM_CONNECTIVITY_FULL) { + new_state = NM_STATE_CONNECTED_GLOBAL; + } + + if (priv->state == new_state) + return; + + priv->state = new_state; + + _LOGI(LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string(new_state)); + + _notify(self, PROP_STATE); + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_manager, + &signal_info_state_changed, + "(u)", + (guint32) priv->state); +} + +static void +manager_device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (old_state == NM_DEVICE_STATE_UNMANAGED && new_state > NM_DEVICE_STATE_UNMANAGED) + retry_connections_for_parent_device(self, device); + + if (NM_IN_SET(new_state, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_PREPARE, + NM_DEVICE_STATE_FAILED)) + _notify(self, PROP_ACTIVE_CONNECTIONS); + + if (NM_IN_SET(new_state, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_ACTIVATED)) { + nm_manager_write_device_state(self, device, NULL); + + G_STATIC_ASSERT_EXPR(DEVICE_STATE_PRUNE_RATELIMIT_MAX < G_MAXUINT8); + if (priv->device_state_prune_ratelimit_count++ > DEVICE_STATE_PRUNE_RATELIMIT_MAX) { + /* We write the device state to /run. The state files are named after the + * ifindex (which is assumed to be unique and not repeat -- in practice + * it may repeat). So from time to time, we prune device state files + * for interfaces that no longer exist. + * + * Otherwise, the files might pile up if you create (and destroy) a large + * number of software devices. */ + priv->device_state_prune_ratelimit_count = 0; + nm_config_device_state_prune_stale(NULL, priv->platform); + } + } + + if (NM_IN_SET(new_state, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_DISCONNECTED)) + nm_settings_device_added(priv->settings, device); +} + +static void device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self); + +static void +check_if_startup_complete(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + const char * reason; + + if (!priv->startup) + return; + + if (!priv->devices_inited) + return; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + reason = nm_device_has_pending_action_reason(device); + if (reason) { + _LOGD(LOGD_CORE, + "startup complete is waiting for device '%s' (%s)", + nm_device_get_iface(device), + reason); + return; + } + } + + /* All NMDevice must be ready. But also NMSettings tracks profiles that wait for + * ready devices via "connection.wait-device-timeout". + * + * Note that we only re-check nm_settings_get_startup_complete_blocked_reason() when + * all of the devices become ready (again). + * + * For example, assume we have device "eth1" and "profile-eth2" which waits for "eth2". + * If "eth1" is ready (no pending action), we only need to re-evaluate "profile-eth2" + * if we have another device ("eth2"), that becomes non-ready (had pending actions) + * and again become ready. We don't need to check "profile-eth2" until "eth2" becomes + * non-ready. + * That is why nm_settings_get_startup_complete_blocked_reason() only has any significance + * if all devices are ready too. It allows us to cut down the number of checks whether + * NMSettings is ready. That's because we don't need to re-evaluate on minor changes of + * a device, only when all devices become managed and ready. */ + + g_signal_handlers_block_by_func(priv->settings, settings_startup_complete_changed, self); + reason = nm_settings_get_startup_complete_blocked_reason(priv->settings, TRUE); + g_signal_handlers_unblock_by_func(priv->settings, settings_startup_complete_changed, self); + if (reason) { + _LOGD(LOGD_CORE, "startup complete is waiting for connection (%s)", reason); + return; + } + + _LOGI(LOGD_CORE, "startup complete"); + + priv->startup = FALSE; + + /* we no longer care about these signals. Startup-complete only + * happens once. */ + g_signal_handlers_disconnect_by_func(priv->settings, + G_CALLBACK(settings_startup_complete_changed), + self); + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + g_signal_handlers_disconnect_by_func(device, + G_CALLBACK(device_has_pending_action_changed), + self); + } + + _notify(self, PROP_STARTUP); + + if (nm_config_get_configure_and_quit(priv->config)) + g_signal_emit(self, signals[CONFIGURE_QUIT], 0); +} + +static void +device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + check_if_startup_complete(self); +} + +static void +settings_startup_complete_changed(NMSettings *settings, GParamSpec *pspec, NMManager *self) +{ + check_if_startup_complete(self); +} + +static void +_parent_notify_changed(NMManager *self, NMDevice *device, gboolean device_removed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * candidate; + + nm_assert(NM_IS_DEVICE(device)); + +again: + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_parent_notify_changed(candidate, device, device_removed)) { + /* in the unlikely event that this changes anything, we start iterating + * again, to be sure that the device list is up-to-date. */ + goto again; + } + } +} + +static gboolean +device_is_wake_on_lan(NMPlatform *platform, NMDevice *device) +{ + int ifindex; + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex <= 0) + return FALSE; + return nm_platform_link_get_wake_on_lan(platform, ifindex); +} + +static void +remove_device(NMManager *self, NMDevice *device, gboolean quitting) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean unmanage = FALSE; + + _LOG2D(LOGD_DEVICE, + device, + "removing device (managed %d, wol %d)", + nm_device_get_managed(device, FALSE), + device_is_wake_on_lan(priv->platform, device)); + + if (nm_device_get_managed(device, FALSE)) { + if (quitting) { + /* Leave configured if wo(w)lan and quitting */ + if (device_is_wake_on_lan(priv->platform, device)) + unmanage = FALSE; + else + unmanage = nm_device_unmanage_on_quit(device); + } else { + /* the device is already gone. Unmanage it. */ + unmanage = TRUE; + } + + if (unmanage) { + if (quitting) + nm_device_set_unmanaged_by_quitting(device); + else { + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PLATFORM_INIT, + TRUE, + NM_DEVICE_STATE_REASON_REMOVED); + } + } else if (quitting + && nm_config_get_configure_and_quit(priv->config) + == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { + nm_device_spawn_iface_helper(device); + } + } + + g_signal_handlers_disconnect_matched(device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); + + nm_settings_device_removed(priv->settings, device, quitting); + + c_list_unlink(&device->devices_lst); + + _parent_notify_changed(self, device, TRUE); + + if (nm_device_is_real(device)) { + gboolean unconfigure_ip_config = !quitting || unmanage; + + /* When we don't unmanage the device on shutdown, we want to preserve the DNS + * configuration in resolv.conf. For that, we must leak the configuration + * in NMPolicy/NMDnsManager. We do that, by emitting the device-removed signal + * with device's ip-config object still uncleared. In that case, NMPolicy + * never learns to unconfigure the ip-config objects and does not remove them + * from DNS on shutdown (which is ugly, because we don't cleanup the memory + * properly). + * + * Control that by passing @unconfigure_ip_config. */ + nm_device_removed(device, unconfigure_ip_config); + + _emit_device_added_removed(self, device, FALSE); + } else { + /* unrealize() does not release a slave device from master and + * clear IP configurations, do it here */ + nm_device_removed(device, TRUE); + } + + g_signal_emit(self, signals[INTERNAL_DEVICE_REMOVED], 0, device); + _notify(self, PROP_ALL_DEVICES); + + update_connectivity_value(self); + + nm_dbus_object_clear_and_unexport(&device); + + check_if_startup_complete(self); +} + +static void +device_removed_cb(NMDevice *device, gpointer user_data) +{ + remove_device(NM_MANAGER(user_data), device, FALSE); +} + +NMState +nm_manager_get_state(NMManager *manager) +{ + g_return_val_if_fail(NM_IS_MANAGER(manager), NM_STATE_UNKNOWN); + + return NM_MANAGER_GET_PRIVATE(manager)->state; +} + +/*****************************************************************************/ + +static NMDevice * +find_parent_device_for_connection(NMManager * self, + NMConnection * connection, + NMDeviceFactory *cached_factory, + const char ** out_parent_spec) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + const char * parent_name = NULL; + NMSettingsConnection *parent_connection; + NMDevice * parent, *first_compatible = NULL; + NMDevice * candidate; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + NM_SET_OUT(out_parent_spec, NULL); + + if (!cached_factory) { + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) + return NULL; + } else + factory = cached_factory; + + parent_name = nm_device_factory_get_connection_parent(factory, connection); + if (!parent_name) + return NULL; + + NM_SET_OUT(out_parent_spec, parent_name); + + /* Try as an interface name of a parent device */ + parent = find_device_by_iface(self, parent_name, NULL, NULL); + if (parent) + return parent; + + /* Maybe a hardware address */ + parent = find_device_by_permanent_hw_addr(self, parent_name); + if (parent) + return parent; + + /* Maybe a connection UUID */ + parent_connection = nm_settings_get_connection_by_uuid(priv->settings, parent_name); + if (!parent_connection) + return NULL; + + /* Check if the parent connection is currently activated or is compatible + * with some known device. + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + /* Unmanaged devices are not compatible with any connection */ + if (!nm_device_get_managed(candidate, FALSE)) + continue; + + if (nm_device_get_settings_connection(candidate) == parent_connection) + return candidate; + + if (!first_compatible + && nm_device_check_connection_compatible( + candidate, + nm_settings_connection_get_connection(parent_connection), + NULL)) + first_compatible = candidate; + } + + return first_compatible; +} + +/** + * nm_manager_get_connection_iface: + * @self: the #NMManager + * @connection: the #NMConnection to get the interface for + * @out_parent: on success, the parent device if any + * @out_parent_spec: on return, a string specifying the parent device + * in the connection. This can be a device name, a MAC address or a + * connection UUID. + * @error: an error if determining the virtual interface name failed + * + * Given @connection, returns the interface name that the connection + * would need to use when activated. %NULL is returned if the name + * is not specified in connection or a the name for a virtual device + * could not be generated. + * + * Returns: the expected interface name (caller takes ownership), or %NULL + */ +char * +nm_manager_get_connection_iface(NMManager * self, + NMConnection *connection, + NMDevice ** out_parent, + const char ** out_parent_spec, + GError ** error) +{ + NMDeviceFactory *factory; + char * iface = NULL; + NMDevice * parent = NULL; + + NM_SET_OUT(out_parent, NULL); + NM_SET_OUT(out_parent_spec, NULL); + + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) { + if (nm_streq0(nm_connection_get_connection_type(connection), + NM_SETTING_GENERIC_SETTING_NAME)) { + /* the generic type doesn't have a factory. */ + goto return_ifname_fom_connection; + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "NetworkManager plugin for '%s' unavailable", + nm_connection_get_connection_type(connection)); + return NULL; + } + + if (!out_parent && !NM_DEVICE_FACTORY_GET_CLASS(factory)->get_connection_iface) { + /* optimization. Shortcut lookup of the partent device. */ + goto return_ifname_fom_connection; + } + + parent = find_parent_device_for_connection(self, connection, factory, out_parent_spec); + iface = nm_device_factory_get_connection_iface(factory, + connection, + parent ? nm_device_get_ip_iface(parent) : NULL, + error); + if (!iface) + return NULL; + + if (out_parent) + *out_parent = parent; + return iface; + +return_ifname_fom_connection: + iface = g_strdup(nm_connection_get_interface_name(connection)); + if (!iface) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "failed to determine interface name: error determine name for %s", + nm_connection_get_connection_type(connection)); + } + return iface; +} + +/** + * nm_manager_iface_for_uuid: + * @self: the #NMManager + * @uuid: the connection uuid + * + * Gets a link name for the given UUID. Useful for the settings plugins that + * wish to write configuration files compatible with tooling that can't + * interpret our UUIDs. + * + * Returns: An interface name; %NULL if none matches + */ +const char * +nm_manager_iface_for_uuid(NMManager *self, const char *uuid) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSettingsConnection *sett_conn; + + sett_conn = nm_settings_get_connection_by_uuid(priv->settings, uuid); + if (!sett_conn) + return NULL; + + return nm_connection_get_interface_name(nm_settings_connection_get_connection(sett_conn)); +} + +NMDevice * +nm_manager_get_device(NMManager *self, const char *ifname, NMDeviceType device_type) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(ifname, NULL); + g_return_val_if_fail(device_type != NM_DEVICE_TYPE_UNKNOWN, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_device_type(device) == device_type + && nm_streq0(nm_device_get_iface(device), ifname)) + return device; + } + + return NULL; +} + +gboolean +nm_manager_remove_device(NMManager *self, const char *ifname, NMDeviceType device_type) +{ + NMDevice *d; + + d = nm_manager_get_device(self, ifname, device_type); + if (!d) + return FALSE; + + remove_device(self, d, FALSE); + return TRUE; +} + +/** + * system_create_virtual_device: + * @self: the #NMManager + * @connection: the connection which might require a virtual device + * + * If @connection requires a virtual device and one does not yet exist for it, + * creates that device. + * + * Returns: A #NMDevice that was just realized; %NULL if none + */ +static NMDevice * +system_create_virtual_device(NMManager *self, NMConnection *connection) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + gs_free NMSettingsConnection **connections = NULL; + guint i; + gs_free char * iface = NULL; + const char * parent_spec; + NMDevice * device = NULL, *parent = NULL; + NMDevice * dev_candidate; + GError * error = NULL; + NMLogLevel log_level; + + g_return_val_if_fail(NM_IS_MANAGER(self), NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, &error); + if (!iface) { + _LOG3D(LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message); + g_error_free(error); + return NULL; + } + + if (parent_spec && !parent) { + /* parent is not ready, wait */ + return NULL; + } + + /* See if there's a device that is already compatible with this connection */ + c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_check_connection_compatible(dev_candidate, connection, NULL)) { + if (nm_device_is_real(dev_candidate)) { + _LOG3D(LOGD_DEVICE, connection, "already created virtual interface name %s", iface); + return NULL; + } + + device = dev_candidate; + break; + } + } + + if (!device) { + /* No matching device found. Proceed creating a new one. */ + + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) { + _LOG3E(LOGD_DEVICE, + connection, + "(%s) NetworkManager plugin for '%s' unavailable", + iface, + nm_connection_get_connection_type(connection)); + return NULL; + } + + device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, &error); + if (!device) { + _LOG3W(LOGD_DEVICE, connection, "factory can't create the device: %s", error->message); + g_error_free(error); + return NULL; + } + + _LOG3D(LOGD_DEVICE, connection, "create virtual device %s", nm_device_get_iface(device)); + + if (!add_device(self, device, &error)) { + _LOG3W(LOGD_DEVICE, + connection, + "can't register the device with manager: %s", + error->message); + g_error_free(error); + g_object_unref(device); + return NULL; + } + + /* Add device takes a reference that NMManager still owns, so it's + * safe to unref here and still return @device. + */ + g_object_unref(device); + } + + if (!nm_device_check_unrealized_device_managed(device)) { + _LOG3D(LOGD_DEVICE, + connection, + "skip activation because virtual device '%s' is unmanaged", + nm_device_get_iface(device)); + return device; + } + + if (!find_master(self, connection, device, NULL, NULL, NULL, &error)) { + _LOG3D(LOGD_DEVICE, connection, "skip activation: %s", error->message); + g_error_free(error); + return device; + } + + /* Create backing resources if the device has any autoconnect connections */ + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) { + NMConnection * candidate = nm_settings_connection_get_connection(connections[i]); + NMSettingConnection *s_con; + + if (!nm_device_check_connection_compatible(device, candidate, NULL)) + continue; + + s_con = nm_connection_get_setting_connection(candidate); + g_assert(s_con); + if (!nm_setting_connection_get_autoconnect(s_con) + || nm_settings_connection_autoconnect_is_blocked(connections[i])) + continue; + + /* Create any backing resources the device needs */ + if (!nm_device_create_and_realize(device, connection, parent, &error)) { + log_level = + g_error_matches(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES) + ? LOGL_DEBUG + : LOGL_ERR; + _NMLOG3(log_level, + LOGD_DEVICE, + connection, + "couldn't create the device: %s", + error->message); + g_error_free(error); + return NULL; + } + + retry_connections_for_parent_device(self, device); + break; + } + + return device; +} + +static void +retry_connections_for_parent_device(NMManager *self, NMDevice *device) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free NMSettingsConnection **connections = NULL; + guint i; + + g_return_if_fail(device); + + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMConnection * connection = nm_settings_connection_get_connection(sett_conn); + gs_free_error GError *error = NULL; + gs_free char * ifname = NULL; + NMDevice * parent; + + parent = find_parent_device_for_connection(self, connection, NULL, NULL); + if (parent == device) { + /* Only try to activate devices that don't already exist */ + ifname = nm_manager_get_connection_iface(self, connection, &parent, NULL, &error); + if (ifname) { + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, ifname)) + connection_changed(self, sett_conn); + } + } + } +} + +static void +connection_changed(NMManager *self, NMSettingsConnection *sett_conn) +{ + NMConnection *connection; + NMDevice * device; + + if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + + connection = nm_settings_connection_get_connection(sett_conn); + + if (!nm_connection_is_virtual(connection)) + return; + + device = system_create_virtual_device(self, connection); + if (!device) + return; + + /* Maybe the device that was created was needed by some other + * connection's device (parent of a VLAN). Let the connections + * can use the newly created device as a parent know. */ + retry_connections_for_parent_device(self, device); +} + +static void +connection_added_cb(NMSettings *settings, NMSettingsConnection *sett_conn, NMManager *self) +{ + connection_changed(self, sett_conn); +} + +static void +connection_updated_cb(NMSettings * settings, + NMSettingsConnection *sett_conn, + guint update_reason_u, + NMManager * self) +{ + connection_changed(self, sett_conn); +} + +/*****************************************************************************/ + +static void +_delete_volatile_connection_all(NMManager *self, gboolean do_delete) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMCListElem * elem; + + while ( + (elem = c_list_first_entry(&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) { + gs_unref_object NMSettingsConnection *connection = NULL; + + connection = nm_c_list_elem_free_steal(elem); + if (do_delete) + _delete_volatile_connection_do(self, connection); + } +} + +static gboolean +_delete_volatile_connection_cb(gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + priv->delete_volatile_connection_idle_id = 0; + _delete_volatile_connection_all(self, TRUE); + return G_SOURCE_REMOVE; +} + +static void +connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (!NM_FLAGS_ANY(nm_settings_connection_get_flags(connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + + if (active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NULL)) { + /* the connection still has an active-connection. It will be purged + * when the active connection(s) get(s) removed. */ + return; + } + + c_list_link_tail(&priv->delete_volatile_connection_lst_head, + &nm_c_list_elem_new_stale(g_object_ref(connection))->lst); + if (!priv->delete_volatile_connection_idle_id) + priv->delete_volatile_connection_idle_id = g_idle_add(_delete_volatile_connection_cb, self); +} + +/*****************************************************************************/ + +static void +system_unmanaged_devices_changed_cb(NMSettings *settings, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_set_unmanaged_by_user_settings(device); +} + +static void +hostname_changed_cb(NMHostnameManager *hostname_manager, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * hostname; + + hostname = nm_hostname_manager_get_hostname(priv->hostname_manager); + + nm_dispatcher_call_hostname(NULL, NULL, NULL); + nm_dhcp_manager_set_default_hostname(nm_dhcp_manager_get(), hostname); +} + +/*****************************************************************************/ +/* General NMManager stuff */ +/*****************************************************************************/ + +static gboolean +radio_enabled_for_rstate(RadioState *rstate, gboolean check_changeable) +{ + gboolean enabled; + + enabled = rstate->user_enabled && rstate->hw_enabled; + if (check_changeable) + enabled &= rstate->sw_enabled; + return enabled; +} + +static gboolean +radio_enabled_for_type(NMManager *self, RfKillType rtype, gboolean check_changeable) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + return radio_enabled_for_rstate(&priv->radio_states[rtype], check_changeable); +} + +static void +manager_update_radio_enabled(NMManager *self, RadioState *rstate, gboolean enabled) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + /* Do nothing for radio types not yet implemented */ + if (!rstate->prop) + return; + + g_object_notify(G_OBJECT(self), rstate->prop); + + /* Don't touch devices if asleep/networking disabled */ + if (manager_sleeping(self)) + return; + + /* enable/disable wireless devices as required */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_rfkill_type(device) == rstate->rtype) { + _LOG2D(LOGD_RFKILL, + device, + "rfkill: setting radio %s", + enabled ? "enabled" : "disabled"); + nm_device_set_enabled(device, enabled); + } + } +} + +static void +update_rstate_from_rfkill(NMRfkillManager *rfkill_mgr, RadioState *rstate) +{ + switch (nm_rfkill_manager_get_rfkill_state(rfkill_mgr, rstate->rtype)) { + case RFKILL_UNBLOCKED: + rstate->sw_enabled = TRUE; + rstate->hw_enabled = TRUE; + break; + case RFKILL_SOFT_BLOCKED: + rstate->sw_enabled = FALSE; + rstate->hw_enabled = TRUE; + break; + case RFKILL_HARD_BLOCKED: + rstate->sw_enabled = FALSE; + rstate->hw_enabled = FALSE; + break; + default: + g_warn_if_reached(); + break; + } +} + +static void +manager_rfkill_update_one_type(NMManager *self, RadioState *rstate, RfKillType rtype) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled, old_hwe; + + old_enabled = radio_enabled_for_rstate(rstate, TRUE); + old_rfkilled = rstate->hw_enabled && rstate->sw_enabled; + old_hwe = rstate->hw_enabled; + + /* recheck kernel rfkill state */ + update_rstate_from_rfkill(priv->rfkill_mgr, rstate); + + /* Print out all states affecting device enablement */ + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: %s hw-enabled %d sw-enabled %d", + rstate->desc, + rstate->hw_enabled, + rstate->sw_enabled); + } + + /* Log new killswitch state */ + new_rfkilled = rstate->hw_enabled && rstate->sw_enabled; + if (old_rfkilled != new_rfkilled) { + _LOGI(LOGD_RFKILL, + "rfkill: %s now %s by radio killswitch", + rstate->desc, + new_rfkilled ? "enabled" : "disabled"); + } + + /* Send out property changed signal for HW enabled */ + if (rstate->hw_enabled != old_hwe) { + if (rstate->hw_prop) + g_object_notify(G_OBJECT(self), rstate->hw_prop); + } + + /* And finally update the actual device radio state itself; respect the + * daemon state here because this is never called from user-triggered + * radio changes and we only want to ignore the daemon enabled state when + * handling user radio change requests. + */ + new_enabled = radio_enabled_for_rstate(rstate, TRUE); + if (new_enabled != old_enabled) + manager_update_radio_enabled(self, rstate, new_enabled); +} + +static void +nm_manager_rfkill_update(NMManager *self, RfKillType rtype) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint i; + + if (rtype != RFKILL_TYPE_UNKNOWN) + manager_rfkill_update_one_type(self, &priv->radio_states[rtype], rtype); + else { + /* Otherwise, sync all radio types */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) + manager_rfkill_update_one_type(self, &priv->radio_states[i], i); + } +} + +static void +device_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + gs_free_error GError * error = NULL; + NMAuthCallResult result; + NMDevice * device; + GCancellable * cancellable; + const char * permission; + NMManagerDeviceAuthRequestFunc callback; + NMAuthSubject * subject; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + permission = nm_auth_chain_get_data(chain, "perm"); + nm_assert(permission); + callback = nm_auth_chain_get_data(chain, "callback"); + nm_assert(callback); + device = nm_auth_chain_get_data(chain, "device"); + nm_assert(NM_IS_DEVICE(device)); + + cancellable = nm_auth_chain_get_cancellable(chain); + nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); + + result = nm_auth_chain_get_result(chain, permission); + subject = nm_auth_chain_get_subject(chain); + + if (cancellable && g_cancellable_set_error_if_cancelled(cancellable, &error)) { + /* pass. */ + } else { + if (result != NM_AUTH_CALL_RESULT_YES) { + _LOGD(LOGD_CORE, "%s request failed: not authorized", permission); + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "%s request failed: not authorized", + permission); + } + + nm_assert(error || (result == NM_AUTH_CALL_RESULT_YES)); + } + + callback(device, context, subject, error, nm_auth_chain_get_data(chain, "user-data")); +} + +static void +_device_auth_done_fail_on_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMManager *self = NULL; + gs_unref_object NMDevice *device = NULL; + gs_unref_object GDBusMethodInvocation *context = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + gs_free_error GError *error_original = NULL; + gs_free_error GError * error_cancelled = NULL; + NMManagerDeviceAuthRequestFunc callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, + &self, + &device, + &context, + &subject, + &error_original, + &callback, + &callback_user_data); + + g_cancellable_set_error_if_cancelled(cancellable, &error_cancelled); + + callback(device, context, subject, error_cancelled ?: error_original, callback_user_data); +} + +void +nm_manager_device_auth_request(NMManager * self, + NMDevice * device, + GDBusMethodInvocation * context, + NMConnection * connection, + const char * permission, + gboolean allow_interaction, + GCancellable * cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMAuthChain * chain; + char * permission_dup; + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_set_error_literal(&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto fail_on_idle; + } + + /* Ensure the subject has permissions for this connection */ + if (connection + && !nm_auth_is_subject_in_acl_set_error(connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + &error)) + goto fail_on_idle; + + chain = nm_auth_chain_new_subject(subject, context, device_auth_done_cb, self); + if (cancellable) + nm_auth_chain_set_cancellable(chain, cancellable); + + permission_dup = g_strdup(permission); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "device", g_object_ref(device), g_object_unref); + nm_auth_chain_set_data(chain, "callback", callback, NULL); + nm_auth_chain_set_data(chain, "user-data", user_data, NULL); + nm_auth_chain_set_data(chain, "perm", permission_dup /* transfer ownership */, g_free); + nm_auth_chain_add_call_unsafe(chain, permission_dup, allow_interaction); + return; + +fail_on_idle: + nm_utils_invoke_on_idle(cancellable, + _device_auth_done_fail_on_idle, + nm_utils_user_data_pack(g_object_ref(self), + g_object_ref(device), + g_object_ref(context), + g_steal_pointer(&subject), + g_steal_pointer(&error), + callback, + user_data)); +} + +static gboolean +new_activation_allowed_for_connection(NMManager *self, NMSettingsConnection *connection) +{ + if (NM_IN_SET( + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(connection)), + NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE)) + return TRUE; + + return !active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL); +} + +/** + * get_existing_connection: + * @manager: #NMManager instance + * @device: #NMDevice instance + * @out_generated: (allow-none): return TRUE, if the connection was generated. + * + * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if + * the device does not support assuming existing connections. + */ +static NMSettingsConnection * +get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generated) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMConnection *connection = NULL; + NMSettingsConnection * added; + GError * error = NULL; + gs_free_error GError *gen_error = NULL; + NMDevice * master = NULL; + int ifindex = nm_device_get_ifindex(device); + NMSettingsConnection *matched = NULL; + NMSettingsConnection *connection_checked = NULL; + gboolean assume_state_guess_assume = FALSE; + const char * assume_state_connection_uuid = NULL; + gboolean maybe_later, only_by_uuid = FALSE; + + if (out_generated) + *out_generated = FALSE; + + nm_device_capture_initial_config(device); + + if (ifindex) { + int master_ifindex = nm_platform_link_get_master(priv->platform, ifindex); + + /* Check that the master is activating before assuming a + * slave connection. However, ignore ovs-system master as + * we never manage it. + */ + if (master_ifindex + && nm_platform_link_get_type(priv->platform, master_ifindex) + != NM_LINK_TYPE_OPENVSWITCH) { + master = nm_manager_get_device_by_ifindex(self, master_ifindex); + if (!master) { + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume because " + "cannot generate connection for slave before its master (%s/%d)", + nm_platform_link_get_name(priv->platform, master_ifindex), + master_ifindex); + return NULL; + } + if (!nm_device_get_act_request(master)) { + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume because " + "cannot generate connection for slave before master %s activates", + nm_device_get_iface(master)); + return NULL; + } + } + } + + /* The core of the API is nm_device_generate_connection() function and + * update_connection() virtual method and the convenient connection_type + * class attribute. Subclasses supporting the new API must have + * update_connection() implemented, otherwise nm_device_generate_connection() + * returns NULL. + */ + connection = nm_device_generate_connection(device, master, &maybe_later, &gen_error); + if (!connection) { + if (maybe_later) { + /* The device can generate a connection, but it failed for now. + * Give it a chance to match a connection from the state file. */ + only_by_uuid = TRUE; + } else { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, + device, + "assume: cannot generate connection: %s", + gen_error->message); + return NULL; + } + } + + nm_device_assume_state_get(device, &assume_state_guess_assume, &assume_state_connection_uuid); + + /* Now we need to compare the generated connection to each configured + * connection. The comparison function is the heart of the connection + * assumption implementation and it must compare the connections very + * carefully to sort out various corner cases. Also, the comparison is + * not entirely symmetric. + * + * When no configured connection matches the generated connection, we keep + * the generated connection instead. + */ + if (assume_state_connection_uuid + && (connection_checked = + nm_settings_get_connection_by_uuid(priv->settings, assume_state_connection_uuid)) + && new_activation_allowed_for_connection(self, connection_checked) + && nm_device_check_connection_compatible( + device, + nm_settings_connection_get_connection(connection_checked), + NULL)) { + if (connection) { + NMConnection *con = nm_settings_connection_get_connection(connection_checked); + + if (nm_utils_match_connection((NMConnection *[]){con, NULL}, + connection, + TRUE, + nm_device_has_carrier(device), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_metric(device, AF_INET6), + NULL, + NULL)) + matched = connection_checked; + } else + matched = connection_checked; + } + + if (!matched && only_by_uuid) { + _LOG2D(LOGD_DEVICE, device, "assume: cannot generate connection: %s", gen_error->message); + return NULL; + } + + if (!matched && assume_state_guess_assume) { + gs_free NMSettingsConnection **sett_conns = NULL; + guint len, i, j; + + /* the state file doesn't indicate a connection UUID to assume. Search the + * persistent connections for a matching candidate. */ + sett_conns = nm_manager_get_activatable_connections(self, FALSE, FALSE, &len); + if (len > 0) { + for (i = 0, j = 0; i < len; i++) { + NMSettingsConnection *sett_conn = sett_conns[i]; + + if (sett_conn != connection_checked + && nm_device_check_connection_compatible( + device, + nm_settings_connection_get_connection(sett_conn), + NULL)) + sett_conns[j++] = sett_conn; + } + sett_conns[j] = NULL; + len = j; + if (len > 0) { + gs_free NMConnection **conns = NULL; + NMConnection * con; + + g_qsort_with_data(sett_conns, + len, + sizeof(sett_conns[0]), + nm_settings_connection_cmp_timestamp_p_with_data, + NULL); + + conns = nm_settings_connections_array_to_connections(sett_conns, len); + + con = nm_utils_match_connection(conns, + connection, + FALSE, + nm_device_has_carrier(device), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_metric(device, AF_INET6), + NULL, + NULL); + if (con) { + for (i = 0; i < len; i++) { + if (conns[i] == con) { + matched = sett_conns[i]; + break; + } + } + nm_assert(matched); + } + } + } + } + + if (matched) { + if (NM_FLAGS_HAS(nm_settings_connection_get_flags(matched), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + _LOG2D(LOGD_DEVICE, + device, + "assume: take over previous connection '%s' (%s)", + nm_settings_connection_get_id(matched), + nm_settings_connection_get_uuid(matched)); + NM_SET_OUT(out_generated, TRUE); + } else { + _LOG2I(LOGD_DEVICE, + device, + "assume: will attempt to assume matching connection '%s' (%s)%s", + nm_settings_connection_get_id(matched), + nm_settings_connection_get_uuid(matched), + assume_state_connection_uuid + && nm_streq(assume_state_connection_uuid, + nm_settings_connection_get_uuid(matched)) + ? " (indicated)" + : " (guessed)"); + } + nm_device_assume_state_reset(device); + return matched; + } + + _LOG2D(LOGD_DEVICE, + device, + "assume: generated connection '%s' (%s)", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + nm_device_assume_state_reset(device); + + if (!nm_settings_add_connection(priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + &added, + &error)) { + _LOG2W(LOGD_SETTINGS, + device, + "assume: failure to save generated connection '%s': %s", + nm_connection_get_id(connection), + error->message); + g_error_free(error); + return NULL; + } + + NM_SET_OUT(out_generated, TRUE); + return added; +} + +static gboolean +copy_lease(const char *src, const char *dst) +{ + nm_auto_close int src_fd = -1; + int dst_fd; + ssize_t res, size = SSIZE_MAX; + + src_fd = open(src, O_RDONLY | O_CLOEXEC); + if (src_fd < 0) + return FALSE; + + dst_fd = open(dst, O_CREAT | O_EXCL | O_CLOEXEC | O_WRONLY, 0644); + if (dst_fd < 0) + return FALSE; + + while ((res = sendfile(dst_fd, src_fd, NULL, size)) > 0) + size -= res; + + nm_close(dst_fd); + + if (res != 0) { + unlink(dst); + return FALSE; + } + + return TRUE; +} + +static gboolean +recheck_assume_connection(NMManager *self, NMDevice *device) +{ + NMSettingsConnection *sett_conn; + gboolean was_unmanaged = FALSE; + gboolean generated = FALSE; + NMDeviceState state; + gboolean activation_type_assume; + + g_return_val_if_fail(NM_IS_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + if (!nm_device_get_managed(device, FALSE)) { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, device, "assume: don't assume because %s", "not managed"); + return FALSE; + } + + state = nm_device_get_state(device); + if (state > NM_DEVICE_STATE_DISCONNECTED) { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume due to device state %s", + nm_device_state_to_str(state)); + return FALSE; + } + + sett_conn = get_existing_connection(self, device, &generated); + /* log no reason. get_existing_connection() already does it. */ + if (!sett_conn) + return FALSE; + + activation_type_assume = !generated; + + if (state == NM_DEVICE_STATE_UNMANAGED) { + gs_free char *initramfs_lease = + g_strdup_printf(RUNSTATEDIR "/initramfs/net.%s.lease", nm_device_get_iface(device)); + gs_free char *connection_lease = g_strdup_printf(NMRUNDIR "/dhclient-%s-%s.lease", + nm_settings_connection_get_uuid(sett_conn), + nm_device_get_iface(device)); + + if (copy_lease(initramfs_lease, connection_lease)) { + unlink(initramfs_lease); + /* + * We've managed to steal the lease used by initramfs before it + * killed off the dhclient. We need to take ownership of the configured + * connection and act like the device was configured by us. + * Otherwise, the address would just expire. + */ + _LOG2I(LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection"); + activation_type_assume = TRUE; + + if (generated) { + gs_unref_object NMConnection *con2 = NULL; + + con2 = nm_simple_connection_new_clone( + nm_settings_connection_get_connection(sett_conn)); + + /* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */ + nm_connection_add_setting(con2, + g_object_new(NM_TYPE_SETTING_IP4_CONFIG, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL)); + + nm_settings_connection_update(sett_conn, + con2, + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, + "assume-initrd", + NULL); + } + } + } + + nm_device_sys_iface_state_set(device, + activation_type_assume ? NM_DEVICE_SYS_IFACE_STATE_ASSUME + : NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + + /* Move device to DISCONNECTED to activate the connection */ + if (state == NM_DEVICE_STATE_UNMANAGED) { + was_unmanaged = TRUE; + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNAVAILABLE) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + + g_return_val_if_fail(nm_device_get_state(device) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + { + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMActiveConnection * master_ac; + GError * error = NULL; + + subject = nm_auth_subject_new_internal(); + + /* Note: the lifetime of the activation connection is always bound to the profiles visibility + * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY. + * + * This only makes a difference, if the profile actually has "connection.permissions" + * set to limit visibility (which is not the case for externally managed, generated profiles). + * + * If we assume a previously active connection whose lifetime was unbound, we now bind it + * after restart. That is not correct, and can mean that the profile becomes subject to + * deactivation after restart (if the user logs out). + * + * This should be improved, but it's unclear how. */ + active = _new_active_connection( + self, + FALSE, + sett_conn, + NULL, + NULL, + NULL, + device, + subject, + activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL, + activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + &error); + + if (!active) { + _LOGW(LOGD_DEVICE, + "assume: assumed connection %s failed to activate: %s", + nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)), + error->message); + g_error_free(error); + + if (was_unmanaged) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + } + + if (generated && !activation_type_assume) { + _LOG2D(LOGD_DEVICE, + device, + "assume: deleting generated connection after assuming failed"); + nm_settings_connection_delete(sett_conn, FALSE); + } else { + if (nm_device_sys_iface_state_get(device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME) + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + } + return FALSE; + } + + /* If the device is a slave or VLAN, find the master ActiveConnection */ + master_ac = NULL; + if (find_master(self, + nm_settings_connection_get_connection(sett_conn), + device, + NULL, + NULL, + &master_ac, + NULL) + && master_ac) + nm_active_connection_set_master(active, master_ac); + + active_connection_add(self, active); + nm_device_queue_activation(device, NM_ACT_REQUEST(active)); + } + + return TRUE; +} + +static void +recheck_assume_connection_cb(NMManager *self, NMDevice *device) +{ + recheck_assume_connection(self, device); +} + +static void +device_ifindex_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + _parent_notify_changed(self, device, FALSE); +} + +static void +device_ip_iface_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * ip_iface = nm_device_get_ip_iface(device); + NMDeviceType device_type = nm_device_get_device_type(device); + NMDevice * candidate; + + /* Remove NMDevice objects that are actually child devices of others, + * when the other device finally knows its IP interface name. For example, + * remove the PPP interface that's a child of a WWAN device, since it's + * not really a standalone NMDevice. + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate != device && nm_streq0(nm_device_get_iface(candidate), ip_iface) + && nm_device_get_device_type(candidate) == device_type + && nm_device_is_real(candidate)) { + remove_device(self, candidate, FALSE); + break; + } + } +} + +static void +device_iface_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + /* Virtual connections may refer to the new device name as + * parent device, retry to activate them. + */ + retry_connections_for_parent_device(self, device); +} + +static void +_emit_device_added_removed(NMManager *self, NMDevice *device, gboolean is_added) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_manager, + is_added ? &signal_info_device_added : &signal_info_device_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(device))); + g_signal_emit(self, signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED], 0, device); + _notify(self, PROP_DEVICES); +} + +static void +device_realized(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + _emit_device_added_removed(self, device, nm_device_is_real(device)); +} + +static NMConnectivityState +_get_best_connectivity(NMManager *self, int addr_family) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConnectivityState best_state; + NMDevice * dev; + gint64 best_metric; + + if (addr_family == AF_UNSPEC) { + best_state = _get_best_connectivity(self, AF_INET); + if (nm_connectivity_state_cmp(best_state, NM_CONNECTIVITY_FULL) >= 0) { + /* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get + * better. */ + return best_state; + } + return NM_MAX_WITH_CMP(nm_connectivity_state_cmp, + best_state, + _get_best_connectivity(self, AF_INET6)); + } + + nm_assert_addr_family(addr_family); + + best_state = NM_CONNECTIVITY_UNKNOWN; + best_metric = G_MAXINT64; + c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) { + const NMPObject * r; + NMConnectivityState state; + gint64 metric; + + r = nm_device_get_best_default_route(dev, addr_family); + if (r) + metric = NMP_OBJECT_CAST_IP_ROUTE(r)->metric; + else { + /* if all devices have no default-route, we still include the best + * of all connectivity state of all the devices. */ + metric = G_MAXINT64; + } + + if (metric > best_metric) { + /* we already have a default route with better metric. The connectivity state + * of this device is irreleavnt. */ + continue; + } + + state = nm_device_get_connectivity_state(dev, addr_family); + if (metric < best_metric) { + /* this device has a better default route. It wins. */ + best_metric = metric; + best_state = state; + } else { + best_state = NM_MAX_WITH_CMP(nm_connectivity_state_cmp, best_state, state); + } + + if (nm_connectivity_state_cmp(best_state, NM_CONNECTIVITY_FULL) >= 0) { + /* it doesn't get better than FULL. We are done. */ + break; + } + } + + return best_state; +} + +static void +device_connectivity_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + update_connectivity_value(self); +} + +static void +update_connectivity_value(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConnectivityState best_state; + + best_state = _get_best_connectivity(self, AF_UNSPEC); + if (best_state == priv->connectivity_state) + return; + + priv->connectivity_state = best_state; + + _LOGD(LOGD_CORE, + "connectivity checking indicates %s", + nm_connectivity_state_to_string(priv->connectivity_state)); + + nm_manager_update_state(self); + _notify(self, PROP_CONNECTIVITY); + nm_dispatcher_call_connectivity(priv->connectivity_state, NULL, NULL, NULL); +} + +static void +_device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink *plink) +{ + g_return_if_fail(NM_IS_MANAGER(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + + nm_device_realize_finish(device, plink); + + if (!nm_device_get_managed(device, FALSE)) { + nm_device_assume_state_reset(device); + return; + } + + if (recheck_assume_connection(self, device)) + return; + + /* if we failed to assume a connection for the managed device, but the device + * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */ + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + nm_device_emit_recheck_auto_activate(device); +} + +/** + * add_device: + * @self: the #NMManager + * @device: the #NMDevice to add + * @error: (out): the #GError + * + * If successful, this function will increase the references count of @device. + * Callers should decrease the reference count. + */ +static gboolean +add_device(NMManager *self, NMDevice *device, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * iface, *type_desc; + RfKillType rtype; + GSList * iter, *remove = NULL; + int ifindex; + const char * dbus_path; + NMDevice * candidate; + + /* No duplicates */ + ifindex = nm_device_get_ifindex(device); + if (ifindex > 0 && nm_manager_get_device_by_ifindex(self, ifindex)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "A device with ifindex %d already exists", + ifindex); + return FALSE; + } + + /* Remove existing devices owned by the new device; eg remove ethernet + * ports that are owned by a WWAN modem, since udev may announce them + * before the modem is fully discovered. + * + * FIXME: use parent/child device relationships instead of removing + * the child NMDevice entirely + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_real(candidate) && (iface = nm_device_get_ip_iface(candidate)) + && nm_device_owns_iface(device, iface)) + remove = g_slist_prepend(remove, candidate); + } + for (iter = remove; iter; iter = iter->next) + remove_device(self, NM_DEVICE(iter->data), FALSE); + g_slist_free(remove); + + g_object_ref(device); + + nm_assert(c_list_is_empty(&device->devices_lst)); + c_list_link_tail(&priv->devices_lst_head, &device->devices_lst); + + g_signal_connect(device, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(manager_device_state_changed), + self); + + g_signal_connect(device, NM_DEVICE_REMOVED, G_CALLBACK(device_removed_cb), self); + + g_signal_connect_data(device, + NM_DEVICE_RECHECK_ASSUME, + G_CALLBACK(recheck_assume_connection_cb), + self, + NULL, + G_CONNECT_SWAPPED); + + g_signal_connect(device, + "notify::" NM_DEVICE_IP_IFACE, + G_CALLBACK(device_ip_iface_changed), + self); + + g_signal_connect(device, + "notify::" NM_DEVICE_IFINDEX, + G_CALLBACK(device_ifindex_changed), + self); + + g_signal_connect(device, "notify::" NM_DEVICE_IFACE, G_CALLBACK(device_iface_changed), self); + + g_signal_connect(device, "notify::" NM_DEVICE_REAL, G_CALLBACK(device_realized), self); + + g_signal_connect(device, + "notify::" NM_DEVICE_IP4_CONNECTIVITY, + G_CALLBACK(device_connectivity_changed), + self); + g_signal_connect(device, + "notify::" NM_DEVICE_IP6_CONNECTIVITY, + G_CALLBACK(device_connectivity_changed), + self); + + if (priv->startup) { + g_signal_connect(device, + "notify::" NM_DEVICE_HAS_PENDING_ACTION, + G_CALLBACK(device_has_pending_action_changed), + self); + } + + /* Update global rfkill state for this device type with the device's + * rfkill state, and then set this device's rfkill state based on the + * global state. + */ + rtype = nm_device_get_rfkill_type(device); + if (rtype != RFKILL_TYPE_UNKNOWN) { + nm_manager_rfkill_update(self, rtype); + nm_device_set_enabled(device, radio_enabled_for_type(self, rtype, TRUE)); + } + + iface = nm_device_get_iface(device); + g_assert(iface); + type_desc = nm_device_get_type_desc(device); + g_assert(type_desc); + + nm_device_set_unmanaged_by_user_settings(device); + + nm_device_set_unmanaged_flags(device, NM_UNMANAGED_SLEEPING, manager_sleeping(self)); + + dbus_path = nm_dbus_object_export(NM_DBUS_OBJECT(device)); + _LOG2I(LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path); + + nm_settings_device_added(priv->settings, device); + g_signal_emit(self, signals[INTERNAL_DEVICE_ADDED], 0, device); + _notify(self, PROP_ALL_DEVICES); + + _parent_notify_changed(self, device, FALSE); + + return TRUE; +} + +/*****************************************************************************/ + +static void +factory_device_added_cb(NMDeviceFactory *factory, NMDevice *device, gpointer user_data) +{ + NMManager *self = user_data; + GError * error = NULL; + + g_return_if_fail(NM_IS_MANAGER(self)); + + if (nm_device_realize_start(device, + NULL, + FALSE, /* assume_state_guess_assume */ + NULL, /* assume_state_connection_uuid */ + FALSE, /* set_nm_owned */ + NM_UNMAN_FLAG_OP_FORGET, + NULL, + &error)) { + add_device(self, device, NULL); + _device_realize_finish(self, device, NULL); + retry_connections_for_parent_device(self, device); + } else { + _LOG2W(LOGD_DEVICE, device, "failed to realize device: %s", error->message); + g_error_free(error); + } +} + +static void +_register_device_factory(NMDeviceFactory *factory, gpointer user_data) +{ + NMManager *self = NM_MANAGER(user_data); + + g_signal_connect(factory, + NM_DEVICE_FACTORY_DEVICE_ADDED, + G_CALLBACK(factory_device_added_cb), + self); +} + +/*****************************************************************************/ + +void +nm_manager_notify_device_availability_maybe_changed(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_notify_availability_maybe_changed(device); +} + +/*****************************************************************************/ + +static void +platform_link_added(NMManager * self, + int ifindex, + const NMPlatformLink * plink, + gboolean guess_assume, + const NMConfigDeviceStateData *dev_state) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + NMDevice * device = NULL; + NMDevice * candidate; + + g_return_if_fail(ifindex > 0); + + if (nm_manager_get_device_by_ifindex(self, ifindex)) + return; + + /* Let unrealized devices try to realize themselves with the link */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + gboolean compatible = TRUE; + gs_free_error GError *error = NULL; + + if (nm_device_get_link_type(candidate) != plink->type) + continue; + + if (!nm_streq(nm_device_get_iface(candidate), plink->name)) + continue; + + if (nm_device_is_real(candidate)) { + /* There's already a realized device with the link's name + * and a different ifindex. + */ + if (nm_device_get_ifindex(candidate) <= 0) + nm_device_update_from_platform_link(candidate, plink); + else { + /* The ifindex of a device can't be changed after + * initialization because it is used as a key by + * the dns-manager. + */ + _LOGD(LOGD_DEVICE, + "(%s): removing old device %p after ifindex change from %d to %d", + plink->name, + candidate, + nm_device_get_ifindex(candidate), + ifindex); + remove_device(self, candidate, FALSE); + goto add; + } + return; + } else if (nm_device_realize_start(candidate, + plink, + FALSE, /* assume_state_guess_assume */ + NULL, /* assume_state_connection_uuid */ + FALSE, /* set_nm_owned */ + NM_UNMAN_FLAG_OP_FORGET, + &compatible, + &error)) { + _device_realize_finish(self, candidate, plink); + return; + } + + _LOGD(LOGD_DEVICE, "(%s): failed to realize from plink: '%s'", plink->name, error->message); + + /* Try next unrealized device */ + } + +add: + /* Try registered device factories */ + factory = nm_device_factory_manager_find_factory_for_link_type(plink->type); + if (factory) { + gboolean ignore = FALSE; + gs_free_error GError *error = NULL; + + device = + nm_device_factory_create_device(factory, plink->name, plink, NULL, &ignore, &error); + if (!device) { + if (!ignore) { + _LOGW(LOGD_PLATFORM, + "%s: factory failed to create device: %s", + plink->name, + error->message); + } else { + _LOGD(LOGD_PLATFORM, + "%s: factory failed to create device: %s", + plink->name, + error->message); + } + return; + } + } + + if (device == NULL) { + gboolean nm_plugin_missing = FALSE; + + switch (plink->type) { + case NM_LINK_TYPE_WWAN_NET: + case NM_LINK_TYPE_BNEP: + case NM_LINK_TYPE_OLPC_MESH: + case NM_LINK_TYPE_TEAM: + case NM_LINK_TYPE_WIFI: + _LOGI(LOGD_PLATFORM, + "(%s): '%s' plugin not available; creating generic device", + plink->name, + nm_link_type_to_string(plink->type)); + nm_plugin_missing = TRUE; + /* fall-through */ + default: + device = nm_device_generic_new(plink, nm_plugin_missing); + break; + } + } + + if (device) { + gs_free_error GError *error = NULL; + NMUnmanFlagOp unmanaged_user_explicit = NM_UNMAN_FLAG_OP_FORGET; + + if (dev_state) { + switch (dev_state->managed) { + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED: + unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_MANAGED; + break; + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED: + unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_UNMANAGED; + break; + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN: + break; + } + } + + if (nm_device_realize_start(device, + plink, + guess_assume, + dev_state ? dev_state->connection_uuid : NULL, + dev_state ? (dev_state->nm_owned == 1) : FALSE, + unmanaged_user_explicit, + NULL, + &error)) { + add_device(self, device, NULL); + _device_realize_finish(self, device, plink); + retry_connections_for_parent_device(self, device); + } else { + _LOGW(LOGD_DEVICE, "%s: failed to realize device: %s", plink->name, error->message); + } + g_object_unref(device); + } +} + +typedef struct { + CList lst; + NMManager *self; + int ifindex; + guint idle_id; +} PlatformLinkCbData; + +static gboolean +_platform_link_cb_idle(PlatformLinkCbData *data) +{ + int ifindex = data->ifindex; + NMManager * self = data->self; + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + const NMPlatformLink *plink; + + c_list_unlink_stale(&data->lst); + g_slice_free(PlatformLinkCbData, data); + + plink = nm_platform_link_get(priv->platform, ifindex); + if (plink) { + const NMPObject *plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink)); + + platform_link_added(self, ifindex, plink, FALSE, NULL); + nmp_object_unref(plink_keep_alive); + } else { + NMDevice *device; + GError * error = NULL; + + device = nm_manager_get_device_by_ifindex(self, ifindex); + if (device) { + if (nm_device_is_software(device)) { + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); + /* Our software devices stick around until their connection is removed */ + if (!nm_device_unrealize(device, FALSE, &error)) { + _LOG2W(LOGD_DEVICE, device, "failed to unrealize: %s", error->message); + g_clear_error(&error); + remove_device(self, device, FALSE); + } else { + nm_device_update_from_platform_link(device, NULL); + } + } else { + /* Hardware and external devices always get removed when their kernel link is gone */ + remove_device(self, device, FALSE); + } + } + } + + return G_SOURCE_REMOVE; +} + +static void +platform_link_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *plink, + int change_type_i, + gpointer user_data) +{ + NMManager * self; + NMManagerPrivate * priv; + const NMPlatformSignalChangeType change_type = change_type_i; + PlatformLinkCbData * data; + + switch (change_type) { + case NM_PLATFORM_SIGNAL_ADDED: + case NM_PLATFORM_SIGNAL_REMOVED: + self = NM_MANAGER(user_data); + priv = NM_MANAGER_GET_PRIVATE(self); + + data = g_slice_new(PlatformLinkCbData); + data->self = self; + data->ifindex = ifindex; + c_list_link_tail(&priv->link_cb_lst, &data->lst); + data->idle_id = g_idle_add((GSourceFunc) _platform_link_cb_idle, data); + break; + default: + break; + } +} + +static void +platform_query_devices(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *links = NULL; + int i; + gboolean guess_assume; + gs_free char * order = NULL; + + guess_assume = nm_config_get_first_start(nm_config_get()); + order = nm_config_data_get_value(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, + NM_CONFIG_GET_VALUE_STRIP); + links = nm_platform_link_get_all(priv->platform, !nm_streq0(order, "index")); + if (!links) + return; + for (i = 0; i < links->len; i++) { + const NMPlatformLink * link = NMP_OBJECT_CAST_LINK(links->pdata[i]); + const NMConfigDeviceStateData *dev_state; + + dev_state = nm_config_device_state_get(priv->config, link->ifindex); + platform_link_added(self, + link->ifindex, + link, + guess_assume && (!dev_state || !dev_state->connection_uuid), + dev_state); + } +} + +static void +rfkill_manager_rfkill_changed_cb(NMRfkillManager *rfkill_mgr, + RfKillType rtype, + RfKillState udev_state, + gpointer user_data) +{ + nm_manager_rfkill_update(NM_MANAGER(user_data), rtype); +} + +const CList * +nm_manager_get_devices(NMManager *manager) +{ + g_return_val_if_fail(NM_IS_MANAGER(manager), NULL); + + return &NM_MANAGER_GET_PRIVATE(manager)->devices_lst_head; +} + +typedef enum { + DEVICE_ACTIVATION_PRIO_NONE, + DEVICE_ACTIVATION_PRIO_UNMANAGED, + DEVICE_ACTIVATION_PRIO_UNAVAILABLE, + DEVICE_ACTIVATION_PRIO_DEACTIVATING, + DEVICE_ACTIVATION_PRIO_ACTIVATING, + DEVICE_ACTIVATION_PRIO_ACTIVATED, + DEVICE_ACTIVATION_PRIO_DISCONNECTED, + + _DEVICE_ACTIVATION_PRIO_BEST = DEVICE_ACTIVATION_PRIO_DISCONNECTED, +} DeviceActivationPrio; + +static DeviceActivationPrio +_device_get_activation_prio(NMDevice *device) +{ + if (!nm_device_get_managed(device, TRUE)) + return DEVICE_ACTIVATION_PRIO_NONE; + + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_DISCONNECTED: + return DEVICE_ACTIVATION_PRIO_DISCONNECTED; + case NM_DEVICE_STATE_ACTIVATED: + return DEVICE_ACTIVATION_PRIO_ACTIVATED; + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + return DEVICE_ACTIVATION_PRIO_ACTIVATING; + case NM_DEVICE_STATE_DEACTIVATING: + case NM_DEVICE_STATE_FAILED: + return DEVICE_ACTIVATION_PRIO_DEACTIVATING; + case NM_DEVICE_STATE_UNAVAILABLE: + return DEVICE_ACTIVATION_PRIO_UNAVAILABLE; + case NM_DEVICE_STATE_UNKNOWN: + case NM_DEVICE_STATE_UNMANAGED: + return DEVICE_ACTIVATION_PRIO_UNMANAGED; + } + + g_return_val_if_reached(DEVICE_ACTIVATION_PRIO_UNAVAILABLE); +} + +static NMDevice * +nm_manager_get_best_device_for_connection(NMManager * self, + NMSettingsConnection *sett_conn, + NMConnection * connection, + gboolean for_user_request, + GHashTable * unavailable_devices, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnectionState ac_state; + NMActiveConnection * ac; + NMDevice * ac_device; + NMDevice * device; + struct { + NMDevice * device; + DeviceActivationPrio prio; + } best = { + .device = NULL, + .prio = DEVICE_ACTIVATION_PRIO_NONE, + }; + NMDeviceCheckConAvailableFlags flags; + gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; + gs_free_error GError * local_best = NULL; + NMConnectionMultiConnect multi_connect; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + + if (!connection) + connection = nm_settings_connection_get_connection(sett_conn); + + multi_connect = _nm_connection_get_multi_connect(connection); + + if (!for_user_request) + flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE; + else { + /* if the profile is multi-connect=single, we also consider devices which + * are marked as unmanaged. And explicit user-request shows sufficient user + * intent to make the device managed. + * That is also, because we expect that such profile is suitably tied + * to the intended device. So when an unmanaged device matches, the user's + * intent is clear. + * + * For multi-connect != single devices that is different. The profile + * is not restricted to a particular device. + * For that reason, plain `nmcli connection up "$MULIT_PROFILE"` seems + * less suitable for multi-connect profiles, because the target device is + * left unspecified. Anyway, if a user issues + * + * $ nmcli device set "$DEVICE" managed no + * $ nmcli connection up "$MULIT_PROFILE" + * + * then it is reasonable for multi-connect profiles to not consider + * the device a suitable candidate. + * + * This may be seen inconsistent, but I think that it makes a lot of + * sense. Also note that "connection.multi-connect" work quite differently + * in aspects like activation. E.g. `nmcli connection up` of multi-connect + * "single" profile, will deactivate the profile if it is active already. + * That is different from multi-connect profiles, where it will aim to + * activate the profile one more time on an hitherto disconnected device. + */ + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE) + flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST; + else + flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST + & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED; + } + + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE + && (ac = active_connection_find_by_connection(self, + sett_conn, + connection, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, + &all_ac_arr))) { + /* if we have a profile which may activate on only one device (multi-connect single), then + * we prefer the device on which the profile is already active. It means to reactivate + * the profile on the same device. + * + * If the profile can be activated on multiple devices, we don't do this. In fact, the + * check below for the DeviceActivationPrio will prefer devices which are not already + * activated (with this or another) profile. */ + + ac_device = nm_active_connection_get_device(ac); + if (ac_device + && ((unavailable_devices && g_hash_table_contains(unavailable_devices, ac_device)) + || !nm_device_check_connection_available(ac_device, connection, flags, NULL, NULL))) + ac_device = NULL; + + if (all_ac_arr) { + guint i; + + ac_state = nm_active_connection_get_state(ac); + + /* we found several active connections. See which one is the most suitable... */ + nm_assert(ac == all_ac_arr->pdata[0]); + for (i = 1; i < all_ac_arr->len; i++) { + NMActiveConnection * ac2 = all_ac_arr->pdata[i]; + NMDevice * ac_device2 = nm_active_connection_get_device(ac2); + NMActiveConnectionState ac_state2; + + if (!ac_device2 + || (unavailable_devices + && g_hash_table_contains(unavailable_devices, ac_device2)) + || !nm_device_check_connection_available(ac_device2, + connection, + flags, + NULL, + NULL)) + continue; + + ac_state2 = nm_active_connection_get_state(ac2); + + if (!ac_device) + goto found_better; + + if (ac_state == ac_state2) { + /* active-connections are in their list in the order in which they are connected. + * If we have two with same state, the later (newer) one is preferred. */ + goto found_better; + } + + switch (ac_state) { + case NM_ACTIVE_CONNECTION_STATE_UNKNOWN: + if (NM_IN_SET(ac_state2, + NM_ACTIVE_CONNECTION_STATE_ACTIVATING, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)) + goto found_better; + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + if (NM_IN_SET(ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) + goto found_better; + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + break; + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: + if (NM_IN_SET(ac_state2, + NM_ACTIVE_CONNECTION_STATE_ACTIVATING, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) + goto found_better; + break; + default: + nm_assert_not_reached(); + goto found_better; + } + + continue; +found_better: + ac = ac2; + ac_state = ac_state2; + ac_device = ac_device2; + } + } + + if (ac_device) + return ac_device; + } + + /* Pick the first device that's compatible with the connection. */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + GError * local = NULL; + DeviceActivationPrio prio; + + if (unavailable_devices && g_hash_table_contains(unavailable_devices, device)) + continue; + + /* determine the priority of this device. Currently, this priority is independent + * of the profile (connection) and the device's details (aside the state). + * + * Maybe nm_device_check_connection_available() should instead return a priority, + * as it has more information available. + * + * For example, if you have multiple Wi-Fi devices, currently a user-request would + * also select the device if the AP is not visible. Optimally, if one of the two + * devices sees the AP and the other one doesn't, the former would be preferred. + * For that, the priority would need to be determined by nm_device_check_connection_available(). */ + prio = _device_get_activation_prio(device); + if (prio <= best.prio && best.device) { + /* we already have a matching device with a better priority. This candidate + * cannot be better. Skip the check. + * + * Also note, that below we collect the best error message @local_best. + * Since we already have best.device, the error message does not matter + * either, and we can skip nm_device_check_connection_available() altogether. */ + continue; + } + + if (nm_device_check_connection_available(device, + connection, + flags, + NULL, + error ? &local : NULL)) { + if (prio == _DEVICE_ACTIVATION_PRIO_BEST) { + /* this device already has the best priority. It cannot get better + * and finish the search. */ + return device; + } + best.prio = prio; + best.device = device; + continue; + } + + if (error) { + gboolean reset_error; + + if (!local_best) + reset_error = TRUE; + else if (local_best->domain != NM_UTILS_ERROR) + reset_error = (local->domain == NM_UTILS_ERROR); + else { + reset_error = (local->domain == NM_UTILS_ERROR && local_best->code < local->code); + } + + if (reset_error) { + g_clear_error(&local_best); + g_set_error(&local_best, + local->domain, + local->code, + "device %s not available because %s", + nm_device_get_iface(device), + local->message); + } + g_error_free(local); + } + } + + if (best.device) + return best.device; + + if (error) { + if (local_best) + g_propagate_error(error, g_steal_pointer(&local_best)); + else { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "no suitable device found"); + } + } + return NULL; +} + +static const char ** +_get_devices_paths(NMManager *self, gboolean all_devices) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char ** paths = NULL; + guint i; + NMDevice * device; + + paths = g_new(const char *, c_list_length(&priv->devices_lst_head) + 1); + + i = 0; + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + const char *path; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device)); + if (!path) + continue; + + if (!all_devices && !nm_device_is_real(device)) + continue; + + paths[i++] = path; + } + paths[i++] = NULL; + + return paths; +} + +static void +impl_manager_get_devices(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + gs_free const char **paths = NULL; + + paths = _get_devices_paths(self, FALSE); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", (char **) paths)); +} + +static void +impl_manager_get_all_devices(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + gs_free const char **paths = NULL; + + paths = _get_devices_paths(self, TRUE); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", (char **) paths)); +} + +static void +impl_manager_get_device_by_ip_iface(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMDevice * device; + const char *path = NULL; + const char *iface; + + g_variant_get(parameters, "(&s)", &iface); + + device = find_device_by_ip_iface(self, iface); + if (device) + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device)); + + if (!path) { + g_dbus_method_invocation_return_error(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No device found for the requested iface."); + return; + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(o)", path)); +} + +static gboolean +is_compatible_with_slave(NMConnection *master, NMConnection *slave) +{ + NMSettingConnection *s_con; + + g_return_val_if_fail(master, FALSE); + g_return_val_if_fail(slave, FALSE); + + s_con = nm_connection_get_setting_connection(slave); + g_assert(s_con); + + return nm_connection_is_type(master, nm_setting_connection_get_slave_type(s_con)); +} + +/** + * find_master: + * @self: #NMManager object + * @connection: the #NMConnection to find the master connection and device for + * @device: the #NMDevice, if any, which will activate @connection + * @out_master_connection: on success, the master connection of @connection if + * that master connection was found + * @out_master_device: on success, the master device of @connection if that + * master device was found + * @out_master_ac: on success, the master ActiveConnection of @connection if + * there already is one + * @error: the error, if an error occurred + * + * Given an #NMConnection, attempts to find its master. If @connection has + * no master, this will return %TRUE and @out_master_connection and + * @out_master_device will be untouched. + * + * If @connection does have a master, then the outputs depend on what is in its + * #NMSettingConnection:master property: + * + * If "master" is the ifname of an existing #NMDevice, and that device has a + * compatible master connection activated or activating on it, then + * @out_master_device, @out_master_connection, and @out_master_ac will all be + * set. If the device exists and is idle, only @out_master_device will be set. + * If the device exists and has an incompatible connection on it, an error + * will be returned. + * + * If "master" is the ifname of a non-existent device, then @out_master_device + * will be %NULL, and @out_master_connection will be a connection whose + * activation would cause the creation of that device. @out_master_ac MAY be + * set in this case as well (if the connection has started activating, but has + * not yet created its device). + * + * If "master" is the UUID of a compatible master connection, then + * @out_master_connection will be the identified connection, and @out_master_device + * and/or @out_master_ac will be set if the connection is currently activating. + * (@out_master_device will not be set if the device exists but does not have + * @out_master_connection active/activating on it.) + * + * Returns: %TRUE if the master device and/or connection could be found or if + * the connection did not require a master, %FALSE otherwise + **/ +static gboolean +find_master(NMManager * self, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection **out_master_connection, + NMDevice ** out_master_device, + NMActiveConnection ** out_master_ac, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSettingConnection * s_con; + const char * master; + NMDevice * master_device = NULL; + NMSettingsConnection *master_connection; + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + master = nm_setting_connection_get_master(s_con); + + if (master == NULL) + return TRUE; /* success, but no master */ + + /* Try as an interface name first */ + master_device = find_device_by_iface(self, master, NULL, connection); + if (master_device) { + if (master_device == device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Device cannot be its own master"); + return FALSE; + } + + master_connection = nm_device_get_settings_connection(master_device); + if (master_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection), + connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The active connection on %s is not compatible", + nm_device_get_iface(master_device)); + return FALSE; + } + } else { + /* Try master as a connection UUID */ + master_connection = nm_settings_get_connection_by_uuid(priv->settings, master); + if (master_connection) { + NMDevice *candidate; + + /* Check if the master connection is activated on some device already */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate == device) + continue; + + if (nm_device_get_settings_connection(candidate) == master_connection) { + master_device = candidate; + break; + } + } + } + } + + if (out_master_connection) + *out_master_connection = master_connection; + if (out_master_device) + *out_master_device = master_device; + if (out_master_ac && master_connection) { + *out_master_ac = active_connection_find(self, + master_connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, + NULL); + } + + if (master_device || master_connection) + return TRUE; + else { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Master connection not found or invalid"); + return FALSE; + } +} + +/** + * ensure_master_active_connection: + * @self: the #NMManager + * @subject: the #NMAuthSubject representing the requestor of this activation + * @connection: the connection that should depend on @master_connection + * @device: the #NMDevice, if any, which will activate @connection + * @master_connection: the master connection, or %NULL + * @master_device: the master device, or %NULL + * @activation_reason: the reason for activation + * @error: the error, if an error occurred + * + * Determines whether a given #NMConnection depends on another connection to + * be activated, and if so, finds that master connection or creates it. + * + * If @master_device and @master_connection are both set then @master_connection + * MUST already be activated or activating on @master_device, and the function will + * return the existing #NMActiveConnection. + * + * If only @master_device is set, and it has an #NMActiveConnection, then the + * function will return it if it is a compatible master, or an error if not. If it + * doesn't have an AC, then the function will create one if a compatible master + * connection exists, or return an error if not. + * + * If only @master_connection is set, then this will try to find or create a compatible + * #NMDevice, and either activate @master_connection on that device or return an error. + * + * Returns: the master #NMActiveConnection that the caller should depend on, or + * %NULL if an error occurred + */ +static NMActiveConnection * +ensure_master_active_connection(NMManager * self, + NMAuthSubject * subject, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection *master_connection, + NMDevice * master_device, + NMActivationReason activation_reason, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + NMActiveConnection *master_ac = NULL; + NMDeviceState master_state; + gboolean bind_lifetime_to_profile_visibility; + + g_return_val_if_fail(connection, NULL); + g_return_val_if_fail(master_connection || master_device, FALSE); + + bind_lifetime_to_profile_visibility = + NM_FLAGS_HAS(nm_device_get_activation_state_flags(device), + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + + /* If the master device isn't activated then we need to activate it using + * compatible connection. If it's already activating we can just proceed. + */ + if (master_device) { + NMSettingsConnection *device_connection = nm_device_get_settings_connection(master_device); + + /* If we're passed a connection and a device, we require that connection + * be already activated on the device, eg returned from find_master(). + */ + g_assert(!master_connection || master_connection == device_connection); + if (device_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(device_connection), + connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The active connection %s is not compatible", + nm_connection_get_id(connection)); + return NULL; + } + + master_state = nm_device_get_state(master_device); + if ((master_state == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating(master_device)) { + /* Device already using master_connection */ + ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(master_device)); + g_return_val_if_fail(device_connection, ac); + + if (!bind_lifetime_to_profile_visibility) { + /* unbind the lifetime. */ + nm_active_connection_set_state_flags_clear( + ac, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + } + + return ac; + } + + /* If the device is disconnected, find a compatible connection and + * activate it on the device. + */ + if (master_state == NM_DEVICE_STATE_DISCONNECTED || !nm_device_is_real(master_device)) { + gs_free NMSettingsConnection **connections = NULL; + guint i; + + g_assert(master_connection == NULL); + + /* Find a compatible connection and activate this device using it */ + connections = nm_manager_get_activatable_connections(self, FALSE, TRUE, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *candidate = connections[i]; + NMConnection * cand_conn = nm_settings_connection_get_connection(candidate); + + /* Ensure eg bond/team slave and the candidate master is a + * bond/team master + */ + if (!is_compatible_with_slave(cand_conn, connection)) + continue; + + if (nm_device_check_connection_available( + master_device, + cand_conn, + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + NULL)) { + master_ac = nm_manager_activate_connection( + self, + candidate, + NULL, + NULL, + master_device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + activation_reason, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + error); + return master_ac; + } + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "No compatible connection found."); + return NULL; + } + + /* Otherwise, the device is unmanaged, unavailable, or disconnecting */ + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Device unmanaged or not available for activation"); + } else if (master_connection) { + NMDevice *candidate; + + /* Find a compatible device and activate it using this connection */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate == device) { + /* A device obviously can't be its own master */ + continue; + } + + if (!nm_device_check_connection_available( + candidate, + nm_settings_connection_get_connection(master_connection), + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + NULL)) + continue; + + if (!nm_device_is_software(candidate)) { + master_state = nm_device_get_state(candidate); + if (nm_device_is_real(candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED) + continue; + } + + master_ac = nm_manager_activate_connection( + self, + master_connection, + NULL, + NULL, + candidate, + subject, + NM_ACTIVATION_TYPE_MANAGED, + activation_reason, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + error); + return master_ac; + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No device available"); + } else + g_assert_not_reached(); + + return NULL; +} + +typedef struct { + NMSettingsConnection *connection; + NMDevice * device; +} SlaveConnectionInfo; + +/** + * find_slaves: + * @manager: #NMManager object + * @sett_conn: the master #NMSettingsConnection to find slave connections for + * @device: the master #NMDevice for the @sett_conn + * @out_n_slaves: on return, the number of slaves found + * + * Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not + * master, or has not any slaves, this will return %NULL. + * + * Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL + **/ +static SlaveConnectionInfo * +find_slaves(NMManager * manager, + NMSettingsConnection *sett_conn, + NMDevice * device, + guint * out_n_slaves, + gboolean for_user_request) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(manager); + gs_free NMSettingsConnection **all_connections = NULL; + guint n_all_connections; + guint i; + SlaveConnectionInfo * slaves = NULL; + guint n_slaves = 0; + NMSettingConnection * s_con; + gs_unref_hashtable GHashTable *devices = NULL; + + nm_assert(out_n_slaves); + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + g_return_val_if_fail(s_con, NULL); + + devices = g_hash_table_new(nm_direct_hash, NULL); + + /* Search through all connections, not only inactive ones, because + * even if a slave was already active, it might be deactivated during + * master reactivation. + */ + all_connections = nm_settings_get_connections_clone( + priv->settings, + &n_all_connections, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; i < n_all_connections; i++) { + NMSettingsConnection *master_connection = NULL; + NMDevice * master_device = NULL, *slave_device; + NMSettingsConnection *candidate = all_connections[i]; + + find_master(manager, + nm_settings_connection_get_connection(candidate), + NULL, + &master_connection, + &master_device, + NULL, + NULL); + if ((master_connection && master_connection == sett_conn) + || (master_device && master_device == device)) { + slave_device = nm_manager_get_best_device_for_connection(manager, + candidate, + NULL, + for_user_request, + devices, + NULL); + + if (!slaves) { + /* what we allocate is quite likely much too large. Don't bother, it is only + * a temporary buffer. */ + slaves = g_new(SlaveConnectionInfo, n_all_connections); + } + + nm_assert(n_slaves < n_all_connections); + slaves[n_slaves].connection = candidate, slaves[n_slaves].device = slave_device, + n_slaves++; + + if (slave_device) + g_hash_table_add(devices, slave_device); + } + } + + *out_n_slaves = n_slaves; + + /* Warning: returns NULL if n_slaves is zero. */ + return slaves; +} + +static gboolean +should_connect_slaves(NMConnection *connection, NMDevice *device) +{ + NMSettingConnection * s_con; + NMSettingConnectionAutoconnectSlaves val; + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + val = nm_setting_connection_get_autoconnect_slaves(s_con); + if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT) + goto out; + + val = + nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.autoconnect-slaves"), + device, + 0, + 1, + -1); + +out: + if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO) + return FALSE; + if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES) + return TRUE; + return FALSE; +} + +static int +compare_slaves(gconstpointer a, gconstpointer b, gpointer sort_by_name) +{ + const SlaveConnectionInfo *a_info = a; + const SlaveConnectionInfo *b_info = b; + + /* Slaves without a device at the end */ + if (!a_info->device) + return 1; + if (!b_info->device) + return -1; + + if (GPOINTER_TO_INT(sort_by_name)) { + return nm_strcmp0(nm_device_get_iface(a_info->device), nm_device_get_iface(b_info->device)); + } + + return nm_device_get_ifindex(a_info->device) - nm_device_get_ifindex(b_info->device); +} + +static void +autoconnect_slaves(NMManager * self, + NMSettingsConnection *master_connection, + NMDevice * master_device, + NMAuthSubject * subject, + gboolean for_user_request) +{ + GError *local_err = NULL; + + if (should_connect_slaves(nm_settings_connection_get_connection(master_connection), + master_device)) { + gs_free SlaveConnectionInfo *slaves = NULL; + guint i, n_slaves = 0; + gboolean bind_lifetime_to_profile_visibility; + + slaves = find_slaves(self, master_connection, master_device, &n_slaves, for_user_request); + if (n_slaves > 1) { + gs_free char *value = NULL; + + value = nm_config_data_get_value(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, + NM_CONFIG_GET_VALUE_STRIP); + g_qsort_with_data(slaves, + n_slaves, + sizeof(slaves[0]), + compare_slaves, + GINT_TO_POINTER(!nm_streq0(value, "index"))); + } + + bind_lifetime_to_profile_visibility = + n_slaves > 0 + && NM_FLAGS_HAS(nm_device_get_activation_state_flags(master_device), + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + + for (i = 0; i < n_slaves; i++) { + SlaveConnectionInfo *slave = &slaves[i]; + const char * uuid; + + /* To avoid loops when autoconnecting slaves, we propagate + * the UUID of the initial connection down to slaves until + * the same connection is found. + */ + uuid = g_object_get_qdata(G_OBJECT(master_connection), autoconnect_root_quark()); + if (nm_streq0(nm_settings_connection_get_uuid(slave->connection), uuid)) { + _LOGI(LOGD_CORE, + "will NOT activate slave connection '%s' (%s) as a dependency for master " + "'%s' (%s): " + "circular dependency detected", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + continue; + } + + if (!uuid) + uuid = nm_settings_connection_get_uuid(master_connection); + g_object_set_qdata_full(G_OBJECT(slave->connection), + autoconnect_root_quark(), + g_strdup(uuid), + g_free); + + if (!slave->device) { + _LOGD(LOGD_CORE, + "will NOT activate slave connection '%s' (%s) as a dependency for master " + "'%s' (%s): " + "no compatible device found", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + continue; + } + + _LOGD(LOGD_CORE, + "will activate slave connection '%s' (%s) as a dependency for master '%s' (%s)", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + + /* Schedule slave activation */ + nm_manager_activate_connection( + self, + slave->connection, + NULL, + NULL, + slave->device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + &local_err); + if (local_err) { + _LOGW(LOGD_CORE, "Slave connection activation failed: %s", local_err->message); + g_clear_error(&local_err); + } + } + } +} + +static gboolean +_internal_activate_vpn(NMManager *self, NMActiveConnection *active, GError **error) +{ + nm_assert(NM_IS_VPN_CONNECTION(active)); + + nm_dbus_object_export(NM_DBUS_OBJECT(active)); + if (!nm_vpn_manager_activate_connection(NM_MANAGER_GET_PRIVATE(self)->vpn_manager, + NM_VPN_CONNECTION(active), + error)) { + nm_dbus_object_unexport(NM_DBUS_OBJECT(active)); + return FALSE; + } + + active_connection_add(self, active); + return TRUE; +} + +/* Traverse the device to disconnected state. This means that the device is ready + * for connection and will proceed activating if there's an activation request + * enqueued. + */ +static void +unmanaged_to_disconnected(NMDevice *device) +{ + /* when creating the software device, it can happen that the device is + * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet + * get the udev event. At this point, we can no longer delay the activation + * and force the device to be managed. */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PLATFORM_INIT, + FALSE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_USER_EXPLICIT, + FALSE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + + if (!nm_device_get_managed(device, FALSE)) { + /* the device is still marked as unmanaged. Nothing to do. */ + return; + } + + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNAVAILABLE + && nm_device_is_available(device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } +} + +static NMActivationStateFlags +_activation_bind_lifetime_to_profile_visibility(NMAuthSubject *subject) +{ + if (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL + || nm_auth_subject_get_unix_process_uid(subject) == 0) { + /* internal requests and requests from root are always unbound. */ + return NM_ACTIVATION_STATE_FLAG_NONE; + } + + /* if the activation was not done by internal decision nor root, there + * are the following cases: + * + * - the connection has "connection.permissions" unset and the profile + * is not restricted to a user and commonly always visible. It does + * not hurt to bind the lifetime, because we expect the profile to be + * visible at the moment. If the profile changes (while still being active), + * we want to pick-up changes to the visibility and possibly disconnect. + * + * - the connection has "connection.permissions" set, and the current user + * is the owner: + * + * - Usually, we would expect that the profile is visible at the moment, + * and of course we want to bind the lifetime. The moment the user + * logs out, the connection becomes invisible and disconnects. + * + * - the profile at this time could already be invisible (e.g. if the + * user didn't create a proper session (sudo) and manually activates + * an invisible profile. In this case, we still want to bind the + * lifetime, and it will disconnect after the user logs in and logs + * out again. NMKeepAlive takes care of that. + */ + return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY; +} + +/* The parent connection is ready; we can proceed realizing the device and + * progressing the device to disconencted state. + */ +static void +active_connection_parent_active(NMActiveConnection *active, + NMActiveConnection *parent_ac, + NMManager * self) +{ + NMDevice * device = nm_active_connection_get_device(active); + GError * error = NULL; + NMSettingsConnection *sett_conn; + NMDevice * parent; + + g_signal_handlers_disconnect_by_func(active, (GCallback) active_connection_parent_active, self); + + if (!parent_ac) { + _LOGW(LOGD_CORE, + "The parent connection device '%s' depended on disappeared.", + nm_device_get_iface(device)); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED, + "parent device disappeared"); + return; + } + + sett_conn = nm_active_connection_get_settings_connection(active); + parent = nm_active_connection_get_device(parent_ac); + + if (!nm_device_create_and_realize(device, + nm_settings_connection_get_connection(sett_conn), + parent, + &error)) { + _LOGW(LOGD_CORE, + "Could not realize device '%s': %s", + nm_device_get_iface(device), + error->message); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED, + "failure to realize device"); + return; + } + + /* We can now proceed to disconnected state so that activation proceeds. */ + unmanaged_to_disconnected(device); +} + +static gboolean +_internal_activate_device(NMManager *self, NMActiveConnection *active, GError **error) +{ + NMDevice * device, *master_device = NULL; + NMConnection * applied; + NMSettingsConnection * sett_conn; + NMSettingsConnection * master_connection = NULL; + NMConnection * existing_connection = NULL; + NMActiveConnection * master_ac = NULL; + NMAuthSubject * subject; + GError * local = NULL; + NMConnectionMultiConnect multi_connect; + const char * parent_spec; + + g_return_val_if_fail(NM_IS_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(active), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_assert(NM_IS_VPN_CONNECTION(active) == FALSE); + + device = nm_active_connection_get_device(active); + g_return_val_if_fail(device != NULL, FALSE); + + sett_conn = nm_active_connection_get_settings_connection(active); + nm_assert(sett_conn); + + applied = nm_active_connection_get_applied_connection(active); + + /* If the device is active and its connection is not visible to the + * user that's requesting this new activation, fail, since other users + * should not be allowed to implicitly deactivate private connections + * by activating a connection of their own. + */ + existing_connection = nm_device_get_applied_connection(device); + subject = nm_active_connection_get_subject(active); + if (existing_connection + && !nm_auth_is_subject_in_acl_set_error(existing_connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) { + g_prefix_error(error, "Private connection already active on the device: "); + return FALSE; + } + + /* Final connection must be available on device */ + if (!nm_device_check_connection_available(device, + applied, + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + &local)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Connection '%s' is not available on device %s because %s", + nm_settings_connection_get_id(sett_conn), + nm_device_get_iface(device), + local->message); + g_error_free(local); + return FALSE; + } + + if (nm_active_connection_get_activation_type(active) == NM_ACTIVATION_TYPE_MANAGED) + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + + /* Try to find the master connection/device if the connection has a dependency */ + if (!find_master(self, + applied, + device, + &master_connection, + &master_device, + &master_ac, + error)) { + g_prefix_error(error, + "Can not find a master for %s: ", + nm_settings_connection_get_id(sett_conn)); + return FALSE; + } + + /* Create any backing resources the device needs */ + if (!nm_device_is_real(device)) { + NMDevice *parent; + + parent = find_parent_device_for_connection(self, + nm_settings_connection_get_connection(sett_conn), + NULL, + &parent_spec); + + if (parent_spec && !parent) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "parent device '%s' not found", + parent_spec); + return FALSE; + } + + if (parent && !nm_device_is_real(parent)) { + NMSettingsConnection *parent_con; + NMActiveConnection * parent_ac; + + parent_con = nm_device_get_best_connection(parent, NULL, error); + if (!parent_con) { + g_prefix_error(error, "%s failed to create parent: ", nm_device_get_iface(device)); + return FALSE; + } + + if (nm_active_connection_get_activation_reason(active) + == NM_ACTIVATION_REASON_AUTOCONNECT + && NM_FLAGS_HAS(nm_settings_connection_autoconnect_blocked_reason_get(parent_con), + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "the parent connection of %s cannot autoactivate because it is blocked " + "due to user request", + nm_device_get_iface(device)); + return FALSE; + } + + parent_ac = nm_manager_activate_connection( + self, + parent_con, + NULL, + NULL, + parent, + subject, + NM_ACTIVATION_TYPE_MANAGED, + nm_active_connection_get_activation_reason(active), + nm_active_connection_get_state_flags(active) + & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + error); + if (!parent_ac) { + g_prefix_error(error, + "%s failed to activate parent: ", + nm_device_get_iface(device)); + return FALSE; + } + + /* We can't realize now; defer until the parent device is ready. */ + g_signal_connect(active, + NM_ACTIVE_CONNECTION_PARENT_ACTIVE, + (GCallback) active_connection_parent_active, + self); + nm_active_connection_set_parent(active, parent_ac); + } else { + /* We can realize now; no need to wait for a parent device. */ + if (!nm_device_create_and_realize(device, + nm_settings_connection_get_connection(sett_conn), + parent, + error)) { + g_prefix_error(error, + "%s failed to create resources: ", + nm_device_get_iface(device)); + return FALSE; + } + } + } + + /* Ensure there's a master active connection the new connection we're + * activating can depend on. + */ + if (master_connection || master_device) { + if (master_connection) { + _LOGD(LOGD_CORE, + "Activation of '%s' requires master connection '%s'", + nm_settings_connection_get_id(sett_conn), + nm_settings_connection_get_id(master_connection)); + } + if (master_device) { + _LOGD(LOGD_CORE, + "Activation of '%s' requires master device '%s'", + nm_settings_connection_get_id(sett_conn), + nm_device_get_ip_iface(master_device)); + } + + /* Ensure eg bond slave and the candidate master is a bond master */ + if (master_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection), + applied)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The master connection '%s' is not compatible with '%s'", + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_id(sett_conn)); + return FALSE; + } + + if (!master_ac) { + master_ac = + ensure_master_active_connection(self, + nm_active_connection_get_subject(active), + applied, + device, + master_connection, + master_device, + nm_active_connection_get_activation_reason(active), + error); + if (!master_ac) { + if (master_device) { + g_prefix_error(error, + "Master device '%s' can't be activated: ", + nm_device_get_ip_iface(master_device)); + } else { + g_prefix_error(error, + "Master connection '%s' can't be activated: ", + nm_settings_connection_get_id(master_connection)); + } + return FALSE; + } + } + + /* Now that we're activating a slave for that master, make sure the master just + * decides to go unmanaged while we're activating (perhaps because other slaves + * go away leaving him with no kids). + */ + if (master_device) { + nm_device_set_unmanaged_by_flags(master_device, + NM_UNMANAGED_EXTERNAL_DOWN, + NM_UNMAN_FLAG_OP_FORGET, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + + nm_active_connection_set_master(active, master_ac); + _LOGD(LOGD_CORE, + "Activation of '%s' depends on active connection %p %s", + nm_settings_connection_get_id(sett_conn), + master_ac, + nm_dbus_object_get_path(NM_DBUS_OBJECT(master_ac)) ?: ""); + } + + /* Check slaves for master connection and possibly activate them */ + autoconnect_slaves(self, + sett_conn, + device, + nm_active_connection_get_subject(active), + nm_active_connection_get_activation_reason(active) + == NM_ACTIVATION_REASON_USER_REQUEST); + + multi_connect = + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(sett_conn)); + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE + || (multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE + && NM_IN_SET(nm_active_connection_get_activation_reason(active), + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, + NM_ACTIVATION_REASON_USER_REQUEST))) { + /* the profile can be activated multiple times. Proceed. */ + } else { + gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; + NMActiveConnection * ac; + guint i, n_all; + + /* Disconnect the connection if already connected or queued for activation. + * The connection cannot be active multiple times (at the same time). */ + ac = active_connection_find(self, + sett_conn, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + &all_ac_arr); + if (ac) { + n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1); + for (i = 0; i < n_all; i++) { + nm_device_disconnect_active_connection(all_ac_arr ? all_ac_arr->pdata[i] : ac, + NM_DEVICE_STATE_REASON_NEW_ACTIVATION, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + } + } + } + + /* If the device is there, we can ready it for the activation. */ + if (nm_device_is_real(device)) { + unmanaged_to_disconnected(device); + + if (!nm_device_get_managed(device, FALSE)) { + /* Unexpectedly, the device is still unmanaged. That can happen for example, + * if the device is forcibly unmanaged due to NM_UNMANAGED_USER_SETTINGS. */ + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Activation failed because the device is unmanaged"); + return FALSE; + } + } + + /* Export the new ActiveConnection to clients and start it on the device */ + active_connection_add(self, active); + nm_device_queue_activation(device, NM_ACT_REQUEST(active)); + return TRUE; +} + +static gboolean +_internal_activate_generic(NMManager *self, NMActiveConnection *active, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean success = FALSE; + + /* Ensure activation request is still valid, eg that its device hasn't gone + * away or that some other dependency has not failed. + */ + if (nm_active_connection_get_state(active) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Activation failed because dependencies failed."); + return FALSE; + } + + if (NM_IS_VPN_CONNECTION(active)) + success = _internal_activate_vpn(self, active, error); + else + success = _internal_activate_device(self, active, error); + + if (success) { + /* Force an update of the Manager's activating-connection property. + * The device changes state before the AC gets exported, which causes + * the manager's 'activating-connection' property to be NULL since the + * AC only gets a D-Bus path when it's exported. So now that the AC + * is exported, make sure the manager's activating-connection property + * is up-to-date. + */ + policy_activating_ac_changed(G_OBJECT(priv->policy), NULL, self); + } + + return success; +} + +static NMActiveConnection * +_new_active_connection(NMManager * self, + gboolean is_vpn, + NMSettingsConnection * sett_conn, + NMConnection * incompl_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * parent_device; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!incompl_conn || NM_IS_CONNECTION(incompl_conn)); + nm_assert((!incompl_conn) ^ (!sett_conn)); + nm_assert(NM_IS_AUTH_SUBJECT(subject)); + nm_assert(is_vpn + == _connection_is_vpn(sett_conn ? nm_settings_connection_get_connection(sett_conn) + : incompl_conn)); + nm_assert(is_vpn || NM_IS_DEVICE(device)); + nm_assert(!nm_streq0(specific_object, "/")); + nm_assert(!applied || NM_IS_CONNECTION(applied)); + nm_assert(!is_vpn || !applied); + + if (is_vpn) { + NMActiveConnection *parent; + + /* FIXME: for VPN connections, we don't allow re-activating an + * already active connection. It's a bug, and should be fixed together + * when reworking VPN handling. */ + if (active_connection_find_by_connection(self, + sett_conn, + incompl_conn, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, + "Connection '%s' is already active", + sett_conn ? nm_settings_connection_get_id(sett_conn) + : nm_connection_get_id(incompl_conn)); + return NULL; + } + + if (activation_type != NM_ACTIVATION_TYPE_MANAGED) + g_return_val_if_reached(NULL); + + if (specific_object) { + /* Find the specific connection the client requested we use */ + parent = active_connection_get_by_path(self, specific_object); + if (!parent) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "Base connection for VPN connection not active."); + return NULL; + } + } else + parent = priv->primary_connection; + + if (!parent) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Could not find source connection."); + return NULL; + } + + parent_device = nm_active_connection_get_device(parent); + if (!parent_device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Source connection had no active device"); + return NULL; + } + + if (device && device != parent_device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "The device doesn't match the active connection."); + return NULL; + } + + return (NMActiveConnection *) nm_vpn_connection_new( + sett_conn, + parent_device, + nm_dbus_object_get_path(NM_DBUS_OBJECT(parent)), + activation_reason, + initial_state_flags, + subject); + } + + return (NMActiveConnection *) nm_act_request_new(sett_conn, + applied, + specific_object, + subject, + activation_type, + activation_reason, + initial_state_flags, + device); +} + +static void +_internal_activation_auth_done(NMManager * self, + NMActiveConnection *active, + gboolean success, + const char * error_desc) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + gs_free_error GError *error = NULL; + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + + if (!success) + goto fail; + + /* Don't continue with an autoconnect-activation if a more important activation + * already exists. + * We also check this earlier, but there we may fail to detect a duplicate + * if the existing active connection was undergoing authorization. + */ + if (NM_IN_SET(nm_active_connection_get_activation_reason(active), + NM_ACTIVATION_REASON_EXTERNAL, + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_REASON_AUTOCONNECT)) { + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + if (nm_active_connection_get_device(ac) == nm_active_connection_get_device(active) + && nm_active_connection_get_settings_connection(ac) + == nm_active_connection_get_settings_connection(active) + && nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + g_set_error(&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, + "Connection '%s' is already active", + nm_active_connection_get_settings_connection_id(active)); + goto fail; + } + } + } + + if (_internal_activate_generic(self, active, &error)) + return; + +fail: + _delete_volatile_connection_do(self, nm_active_connection_get_settings_connection(active)); + nm_assert(error_desc || error); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error_desc ?: error->message); +} + +/** + * nm_manager_activate_connection(): + * @self: the #NMManager + * @sett_conn: the #NMSettingsConnection to activate on @device + * @applied: (allow-none): the applied connection to activate on @device + * @specific_object: the specific object path, if any, for the activation + * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs. + * @subject: the subject which requested activation + * @activation_type: whether to assume the connection. That is, take over gracefully, + * non-destructible. + * @activation_reason: the reason for activation + * @initial_state_flags: the initial state flags for the activation. + * @error: return location for an error + * + * Begins a new internally-initiated activation of @sett_conn on @device. + * @subject should be the subject of the activation that triggered this + * one, or if this is an autoconnect request, a new internal subject. + * The returned #NMActiveConnection is owned by the Manager and should be + * referenced by the caller if the caller continues to use it. If @applied + * is supplied, it shall not be modified by the caller afterwards. + * + * Returns: (transfer none): the new #NMActiveConnection that tracks + * activation of @sett_conn on @device + */ +NMActiveConnection * +nm_manager_activate_connection(NMManager * self, + NMSettingsConnection * sett_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error) +{ + NMManagerPrivate * priv; + NMActiveConnection *active; + AsyncOpData * async_op_data; + gboolean is_vpn; + + g_return_val_if_fail(NM_IS_MANAGER(self), NULL); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), NULL); + is_vpn = _connection_is_vpn(nm_settings_connection_get_connection(sett_conn)); + g_return_val_if_fail(is_vpn || NM_IS_DEVICE(device), NULL); + g_return_val_if_fail(!error || !*error, NULL); + nm_assert(!nm_streq0(specific_object, "/")); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn), + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) + return NULL; + + /* Look for a active connection that's equivalent and is already pending authorization + * and eventual activation. This is used to de-duplicate concurrent activations which would + * otherwise race and cause the device to disconnect and reconnect repeatedly. + * In particular, this allows the master and multiple slaves to concurrently auto-activate + * while all the slaves would use the same active-connection. */ + c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) { + if (async_op_data->async_op_type != ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL) + continue; + + active = async_op_data->ac_auth.active; + if (sett_conn == nm_active_connection_get_settings_connection(active) + && nm_streq0(nm_active_connection_get_specific_object(active), specific_object) + && (!device || nm_active_connection_get_device(active) == device) + && nm_auth_subject_get_subject_type(nm_active_connection_get_subject(active)) + == NM_AUTH_SUBJECT_TYPE_INTERNAL + && nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL + && nm_active_connection_get_activation_reason(active) == activation_reason) + return active; + } + + active = _new_active_connection(self, + is_vpn, + sett_conn, + NULL, + applied, + specific_object, + device, + subject, + activation_type, + activation_reason, + initial_state_flags, + error); + if (!active) + return NULL; + + nm_active_connection_authorize(active, + NULL, + _async_op_complete_ac_auth_cb, + _async_op_data_new_authorize_activate_internal(self, active)); + return active; +} + +/** + * validate_activation_request: + * @self: the #NMManager + * @context: the D-Bus context of the requestor + * @sett_conn: the #NMSettingsConnection to be activated, or %NULL if there + * is only a partial activation. + * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified) + * @device_path: the object path of the device to be activated, or NULL + * @out_device: on successful return, the #NMDevice to be activated with @connection + * The caller may pass in a device which shortcuts the lookup by path. + * In this case, the passed in device must have the matching @device_path + * already. + * @out_is_vpn: on successful return, %TRUE if @connection is a VPN connection + * @error: location to store an error on failure + * + * Performs basic validation on an activation request, including ensuring that + * the requestor is a valid Unix process, is not disallowed in @connection + * permissions, and that a device exists that can activate @connection. + * + * Returns: on success, the #NMAuthSubject representing the requestor, or + * %NULL on error + */ +static NMAuthSubject * +validate_activation_request(NMManager * self, + GDBusMethodInvocation *context, + NMSettingsConnection * sett_conn, + NMConnection * connection, + const char * device_path, + NMDevice ** out_device, + gboolean * out_is_vpn, + GError ** error) +{ + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + gs_unref_object NMAuthSubject *subject = NULL; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + nm_assert(out_device); + nm_assert(out_is_vpn); + + if (!connection) + connection = nm_settings_connection_get_connection(sett_conn); + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + return NULL; + } + + if (!nm_auth_is_subject_in_acl_set_error(connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) + return NULL; + + is_vpn = _connection_is_vpn(connection); + + if (*out_device) { + device = *out_device; + nm_assert(NM_IS_DEVICE(device)); + nm_assert(device_path); + nm_assert(nm_streq0(device_path, nm_dbus_object_get_path(NM_DBUS_OBJECT(device)))); + nm_assert(device == nm_manager_get_device_by_path(self, device_path)); + } else if (device_path) { + device = nm_manager_get_device_by_path(self, device_path); + if (!device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Device not found"); + return NULL; + } + } else if (!is_vpn) { + gs_free_error GError *local = NULL; + + device = nm_manager_get_best_device_for_connection(self, + sett_conn, + connection, + TRUE, + NULL, + &local); + if (!device) { + gs_free char *iface = NULL; + + /* VPN and software-device connections don't need a device yet, + * but non-virtual connections do ... */ + if (!nm_connection_is_virtual(connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No suitable device found for this connection (%s).", + local->message); + return NULL; + } + + /* Look for an existing device with the connection's interface name */ + iface = nm_manager_get_connection_iface(self, connection, NULL, NULL, error); + if (!iface) + return NULL; + + device = find_device_by_iface(self, iface, connection, NULL); + if (!device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Failed to find a compatible device for this connection"); + return NULL; + } + } + } + + nm_assert(is_vpn || NM_IS_DEVICE(device)); + + *out_device = device; + *out_is_vpn = is_vpn; + return g_steal_pointer(&subject); +} + +/*****************************************************************************/ + +static void +_activation_auth_done(NMManager * self, + NMActiveConnection * active, + GDBusMethodInvocation *invocation, + gboolean success, + const char * error_desc) +{ + GError * error = NULL; + NMAuthSubject * subject; + NMSettingsConnection *connection; + + subject = nm_active_connection_get_subject(active); + connection = nm_active_connection_get_settings_connection(active); + + if (!success) { + error = + g_error_new_literal(NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); + goto fail; + } + + if (!_internal_activate_generic(self, active, &error)) + goto fail; + + nm_settings_connection_autoconnect_blocked_reason_set( + connection, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + FALSE); + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(active)))); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL, subject, NULL); + return; + +fail: + _delete_volatile_connection_do(self, connection); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, + connection, + FALSE, + NULL, + subject, + error->message); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error->message); + + g_dbus_method_invocation_take_error(invocation, error); +} + +static void +impl_manager_activate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMSettingsConnection * sett_conn = NULL; + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + GError * error = NULL; + const char * connection_path; + const char * device_path; + const char * specific_object_path; + + g_variant_get(parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path); + + connection_path = nm_dbus_path_not_empty(connection_path); + specific_object_path = nm_dbus_path_not_empty(specific_object_path); + device_path = nm_dbus_path_not_empty(device_path); + + /* If the connection path is given and valid, that connection is activated. + * Otherwise, the "best" connection for the device is chosen and activated, + * regardless of whether that connection is autoconnect-enabled or not + * (since this is an explicit request, not an auto-activation request). + */ + if (connection_path) { + sett_conn = nm_settings_get_connection_by_path(priv->settings, connection_path); + if (!sett_conn) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Connection could not be found."); + goto error; + } + } else { + /* If no connection is given, find a suitable connection for the given device path */ + if (!device_path) { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Only devices may be activated without a specifying a connection"); + goto error; + } + device = nm_manager_get_device_by_path(self, device_path); + if (!device) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Can not activate an unknown device '%s'", + device_path); + goto error; + } + + sett_conn = nm_device_get_best_connection(device, specific_object_path, &error); + if (!sett_conn) + goto error; + } + + subject = validate_activation_request(self, + invocation, + sett_conn, + NULL, + device_path, + &device, + &is_vpn, + &error); + if (!subject) + goto error; + + active = _new_active_connection(self, + is_vpn, + sett_conn, + NULL, + NULL, + specific_object_path, + device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_USER_REQUEST, + _activation_bind_lifetime_to_profile_visibility(subject), + &error); + if (!active) + goto error; + + nm_active_connection_authorize( + active, + NULL, + _async_op_complete_ac_auth_cb, + _async_op_data_new_ac_auth_activate_user(self, active, invocation)); + + /* we passed the pointer on to _async_op_data_new_ac_auth_activate_user() */ + g_steal_pointer(&active); + + return; + +error: + if (sett_conn) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, + sett_conn, + FALSE, + NULL, + subject, + error->message); + } + g_dbus_method_invocation_take_error(invocation, error); +} + +/*****************************************************************************/ + +static void +activation_add_done(NMSettings * settings, + NMSettingsConnection * new_connection, + GError * error, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + gpointer user_data) +{ + NMManager * self; + gs_unref_object NMActiveConnection *active = NULL; + gs_free_error GError *local = NULL; + gpointer async_op_type_ptr; + AsyncOpType async_op_type; + GVariant * result_floating; + + nm_utils_user_data_unpack(user_data, &self, &active, &async_op_type_ptr); + async_op_type = GPOINTER_TO_INT(async_op_type_ptr); + + if (error) + goto fail; + + nm_active_connection_set_settings_connection(active, new_connection); + + if (!_internal_activate_generic(self, active, &local)) + goto fail; + + if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) { + result_floating = g_variant_new("(oo)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(new_connection)), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active))); + } else { + result_floating = g_variant_new("(oo@a{sv})", + nm_dbus_object_get_path(NM_DBUS_OBJECT(new_connection)), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active)), + g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); + } + g_dbus_method_invocation_return_value(context, result_floating); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + nm_active_connection_get_settings_connection(active), + TRUE, + NULL, + nm_active_connection_get_subject(active), + NULL); + return; + +fail: + if (local) { + nm_assert(!error); + error = local; + } else + nm_assert(error); + + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error->message); + if (new_connection) + nm_settings_connection_delete(new_connection, FALSE); + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + nm_active_connection_get_subject(active), + error->message); +} + +static void +_add_and_activate_auth_done(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active, + NMConnection * connection, + GDBusMethodInvocation * invocation, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile, + gboolean success, + const char * error_desc) +{ + NMManagerPrivate *priv; + GError * error = NULL; + + if (!success) { + error = + g_error_new_literal(NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + nm_active_connection_get_subject(active), + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + priv = NM_MANAGER_GET_PRIVATE(self); + + /* FIXME(shutdown): nm_settings_add_connection_dbus() cannot be cancelled. It should be made + * cancellable and tracked via AsyncOpData to be able to do a clean + * shutdown. */ + nm_settings_add_connection_dbus( + priv->settings, + connection, + persist_mode, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + (is_volatile ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE), + nm_active_connection_get_subject(active), + invocation, + activation_add_done, + nm_utils_user_data_pack(self, g_object_ref(active), GINT_TO_POINTER(async_op_type))); +} + +static void +impl_manager_add_and_activate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMConnection *incompl_conn = NULL; + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + gs_unref_variant GVariant *settings = NULL; + gs_unref_variant GVariant *options = NULL; + const char * device_path; + const char * specific_object_path; + gs_free NMConnection ** conns = NULL; + NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + gboolean is_volatile = FALSE; + gboolean bind_dbus_client = FALSE; + AsyncOpType async_op_type; + + if (nm_streq(method_info->parent.name, "AddAndActivateConnection2")) { + async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2; + g_variant_get(parameters, + "(@a{sa{sv}}&o&o@a{sv})", + &settings, + &device_path, + &specific_object_path, + &options); + } else { + nm_assert(nm_streq(method_info->parent.name, "AddAndActivateConnection")); + async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE; + g_variant_get(parameters, + "(@a{sa{sv}}&o&o)", + &settings, + &device_path, + &specific_object_path); + } + + if (options) { + GVariantIter iter; + const char * option_name; + GVariant * option_value; + + g_variant_iter_init(&iter, options); + while (g_variant_iter_next(&iter, "{&sv}", &option_name, &option_value)) { + gs_unref_variant GVariant *option_value_free = NULL; + const char * s; + + option_value_free = option_value; + + if (nm_streq(option_name, "persist") + && g_variant_is_of_type(option_value, G_VARIANT_TYPE_STRING)) { + s = g_variant_get_string(option_value, NULL); + + is_volatile = FALSE; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + + if (nm_streq(s, "volatile")) { + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + is_volatile = TRUE; + } else if (nm_streq(s, "memory")) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + else if (nm_streq(s, "disk")) { + /* pass */ + } else { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\""); + goto error; + } + } else if (nm_streq(option_name, "bind-activation") + && g_variant_is_of_type(option_value, G_VARIANT_TYPE_STRING)) { + s = g_variant_get_string(option_value, NULL); + + if (nm_streq(s, "dbus-client")) + bind_dbus_client = TRUE; + else if (nm_streq(s, "none")) + bind_dbus_client = FALSE; + else { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\""); + goto error; + } + } else { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Unknown extra option passed"); + goto error; + } + } + } + + specific_object_path = nm_dbus_path_not_empty(specific_object_path); + device_path = nm_dbus_path_not_empty(device_path); + + /* Try to create a new connection with the given settings. + * We allow empty settings for AddAndActivateConnection(). In that case, + * the connection will be completed in nm_utils_complete_generic() or + * nm_device_complete_connection() below. Just make sure we don't expect + * specific data being in the connection till then (especially in + * validate_activation_request()). + */ + incompl_conn = nm_simple_connection_new(); + if (settings && g_variant_n_children(settings)) + _nm_connection_replace_settings(incompl_conn, + settings, + NM_SETTING_PARSE_FLAGS_STRICT, + NULL); + + subject = validate_activation_request(self, + invocation, + NULL, + incompl_conn, + device_path, + &device, + &is_vpn, + &error); + if (!subject) + goto error; + + if (is_vpn) { + /* Try to fill the VPN's connection setting and name at least */ + if (!nm_connection_get_setting_vpn(incompl_conn)) { + error = g_error_new_literal(NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + "VPN connections require a 'vpn' setting"); + g_prefix_error(&error, "%s: ", NM_SETTING_VPN_SETTING_NAME); + goto error; + } + + conns = nm_settings_connections_array_to_connections( + nm_settings_get_connections(priv->settings, NULL), + -1); + + nm_utils_complete_generic(priv->platform, + incompl_conn, + NM_SETTING_VPN_SETTING_NAME, + conns, + NULL, + _("VPN connection"), + NULL, + NULL, + FALSE); /* No IPv6 by default for now */ + } else { + conns = nm_settings_connections_array_to_connections( + nm_settings_get_connections(priv->settings, NULL), + -1); + /* Let each device subclass complete the connection */ + if (!nm_device_complete_connection(device, + incompl_conn, + specific_object_path, + conns, + &error)) + goto error; + } + + nm_assert(_nm_connection_verify(incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS); + + active = _new_active_connection(self, + is_vpn, + NULL, + incompl_conn, + NULL, + specific_object_path, + device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_USER_REQUEST, + _activation_bind_lifetime_to_profile_visibility(subject), + &error); + if (!active) + goto error; + + if (bind_dbus_client) { + NMKeepAlive *keep_alive; + + keep_alive = nm_active_connection_get_keep_alive(active); + nm_keep_alive_set_dbus_client_watch(keep_alive, dbus_connection, sender); + nm_keep_alive_arm(keep_alive); + } + + nm_active_connection_authorize(active, + incompl_conn, + _async_op_complete_ac_auth_cb, + _async_op_data_new_ac_auth_add_and_activate(self, + async_op_type, + active, + invocation, + incompl_conn, + persist_mode, + is_volatile)); + + /* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */ + g_steal_pointer(&incompl_conn); + g_steal_pointer(&active); + return; + +error: + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + subject, + error->message); + g_dbus_method_invocation_take_error(invocation, error); +} + +/*****************************************************************************/ + +gboolean +nm_manager_deactivate_connection(NMManager * manager, + NMActiveConnection *active, + NMDeviceStateReason reason, + GError ** error) +{ + if (NM_IS_VPN_CONNECTION(active)) { + NMActiveConnectionStateReason vpn_reason = + NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED; + + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED) + vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED; + + if (!nm_vpn_connection_deactivate(NM_VPN_CONNECTION(active), vpn_reason, FALSE)) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The VPN connection was not active."); + return FALSE; + } + } else { + nm_assert(NM_IS_ACT_REQUEST(active)); + nm_device_disconnect_active_connection(active, + reason, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + } + + _notify(manager, PROP_ACTIVE_CONNECTIONS); + return TRUE; +} + +static void +deactivate_net_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + GError * error = NULL; + NMAuthCallResult result; + NMActiveConnection *active; + char * path; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + path = nm_auth_chain_get_data(chain, "path"); + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); + active = active_connection_get_by_path(self, path); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to deactivate connections"); + } else if (!active) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The connection was not active."); + } else { + /* success; deactivation allowed */ + if (!nm_manager_deactivate_connection(self, + active, + NM_DEVICE_STATE_REASON_USER_REQUESTED, + &error)) + nm_assert(error); + } + + if (active) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DEACTIVATE, + nm_active_connection_get_settings_connection(active), + !error, + NULL, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + } + + if (error) + g_dbus_method_invocation_take_error(context, error); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_manager_deactivate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection * ac; + NMSettingsConnection *sett_conn = NULL; + GError * error = NULL; + NMAuthSubject * subject = NULL; + NMAuthChain * chain; + const char * active_path; + + g_variant_get(parameters, "(&o)", &active_path); + + /* Find the connection by its object path */ + ac = active_connection_get_by_path(self, active_path); + if (ac) + sett_conn = nm_active_connection_get_settings_connection(ac); + + if (!sett_conn) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The connection was not active."); + goto done; + } + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto done; + } + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn), + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + &error)) + goto done; + + chain = nm_auth_chain_new_subject(subject, invocation, deactivate_net_auth_done_cb, self); + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "path", g_strdup(active_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); + +done: + if (error) { + if (sett_conn) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DEACTIVATE, + sett_conn, + FALSE, + NULL, + subject, + error->message); + } + g_dbus_method_invocation_take_error(invocation, error); + } + g_clear_object(&subject); +} + +static gboolean +sleep_devices_add(NMManager *self, NMDevice *device, gboolean suspending) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSleepMonitorInhibitorHandle *handle = NULL; + + if (g_hash_table_lookup_extended(priv->sleep_devices, device, NULL, (gpointer *) &handle)) { + if (suspending) { + /* if we are suspending, always insert a new handle in sleep_devices. + * Even if we had an old handle, it might be stale by now. */ + g_hash_table_insert(priv->sleep_devices, + device, + nm_sleep_monitor_inhibit_take(priv->sleep_monitor)); + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + } + return FALSE; + } + + g_hash_table_insert(priv->sleep_devices, + g_object_ref(device), + suspending ? nm_sleep_monitor_inhibit_take(priv->sleep_monitor) : NULL); + g_signal_connect(device, "notify::" NM_DEVICE_STATE, (GCallback) device_sleep_cb, self); + return TRUE; +} + +static gboolean +sleep_devices_remove(NMManager *self, NMDevice *device) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSleepMonitorInhibitorHandle *handle; + + if (!g_hash_table_lookup_extended(priv->sleep_devices, device, NULL, (gpointer *) &handle)) + return FALSE; + + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + + /* Remove device from hash */ + g_signal_handlers_disconnect_by_func(device, device_sleep_cb, self); + g_hash_table_remove(priv->sleep_devices, device); + g_object_unref(device); + return TRUE; +} + +static void +sleep_devices_clear(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + NMSleepMonitorInhibitorHandle *handle; + GHashTableIter iter; + + if (!priv->sleep_devices) + return; + + g_hash_table_iter_init(&iter, priv->sleep_devices); + while (g_hash_table_iter_next(&iter, (gpointer *) &device, (gpointer *) &handle)) { + g_signal_handlers_disconnect_by_func(device, device_sleep_cb, self); + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + g_object_unref(device); + g_hash_table_iter_remove(&iter); + } +} + +static void +device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_DISCONNECTED: + _LOGD(LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface(device)); + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + break; + case NM_DEVICE_STATE_UNMANAGED: + _LOGD(LOGD_SUSPEND, "sleep: device %s is ready", nm_device_get_ip_iface(device)); + + if (!sleep_devices_remove(self, device)) + g_return_if_reached(); + + break; + default: + return; + } +} + +static void +do_sleep_wake(NMManager *self, gboolean sleeping_changed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean suspending, waking_from_suspend; + NMDevice * device; + + suspending = sleeping_changed && priv->sleeping; + waking_from_suspend = sleeping_changed && !priv->sleeping; + + if (manager_sleeping(self)) { + _LOGD(LOGD_SUSPEND, "sleep: %s...", suspending ? "sleeping" : "disabling"); + + /* FIXME: are there still hardware devices that need to be disabled around + * suspend/resume? + */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_software(device)) { + /* If a user disables networking we consider that as an + * indication that also software devices must be disconnected. + * But we don't want to destroy them for external events as + * a system suspend. + */ + if (suspending) + continue; + } + /* Wake-on-LAN devices will be taken down post-suspend rather than pre- */ + if (suspending && device_is_wake_on_lan(priv->platform, device)) { + _LOGD(LOGD_SUSPEND, + "sleep: device %s has wake-on-lan, skipping", + nm_device_get_ip_iface(device)); + continue; + } + + if (nm_device_is_activating(device) + || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) { + _LOGD(LOGD_SUSPEND, + "sleep: wait disconnection of device %s", + nm_device_get_ip_iface(device)); + + if (sleep_devices_add(self, device, suspending)) + nm_device_queue_state(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_SLEEPING); + } else { + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + } + } + } else { + _LOGD(LOGD_SUSPEND, "sleep: %s...", waking_from_suspend ? "waking up" : "re-enabling"); + + sleep_devices_clear(self); + + if (waking_from_suspend) { + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_software(device)) + continue; + + /* Belatedly take down Wake-on-LAN devices; ideally we wouldn't have to do this + * but for now it's the only way to make sure we re-check their connectivity. + */ + if (device_is_wake_on_lan(priv->platform, device)) + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + + /* Check if the device is unmanaged but the state transition is still pending. + * If so, change state now so that later we re-manage the device forcing a + * re-check of available connections. + */ + if (!nm_device_get_managed(device, FALSE) + && nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_REASON_SLEEPING); + } + } + } + + /* Ensure rfkill state is up-to-date since we don't respond to state + * changes during sleep. + */ + nm_manager_rfkill_update(self, RFKILL_TYPE_UNKNOWN); + + /* Re-manage managed devices */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + guint i; + + if (nm_device_is_software(device) + && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_SLEEPING)) { + /* DHCP leases of software devices could have gone stale + * so we need to renew them. */ + nm_device_update_dynamic_ip_setup(device); + continue; + } + + /* enable/disable wireless devices since that we don't respond + * to killswitch changes during sleep. + */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + RadioState *rstate = &priv->radio_states[i]; + gboolean enabled = radio_enabled_for_rstate(rstate, TRUE); + + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: %s %s devices (hw_enabled %d, sw_enabled %d, user_enabled %d)", + enabled ? "enabling" : "disabling", + rstate->desc, + rstate->hw_enabled, + rstate->sw_enabled, + rstate->user_enabled); + } + if (nm_device_get_rfkill_type(device) == rstate->rtype) + nm_device_set_enabled(device, enabled); + } + + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + FALSE, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + } + + nm_manager_update_state(self); +} + +static void +_internal_sleep(NMManager *self, gboolean do_sleep) +{ + NMManagerPrivate *priv; + + g_return_if_fail(NM_IS_MANAGER(self)); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->sleeping == do_sleep) + return; + + _LOGI(LOGD_SUSPEND, + "sleep: %s requested (sleeping: %s enabled: %s)", + do_sleep ? "sleep" : "wake", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->sleeping = do_sleep; + + do_sleep_wake(self, TRUE); + + _notify(self, PROP_SLEEPING); +} + +static void +impl_manager_sleep(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + GError * error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + gboolean do_sleep; + + g_variant_get(parameters, "(b)", &do_sleep); + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + + if (priv->sleeping == do_sleep) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, + "Already %s", + do_sleep ? "asleep" : "awake"); + nm_audit_log_control_op(NM_AUDIT_OP_SLEEP_CONTROL, + do_sleep ? "on" : "off", + FALSE, + subject, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + /* Unconditionally allow the request. Previously it was polkit protected + * but unfortunately that doesn't work for short-lived processes like + * pm-utils. It uses dbus-send without --print-reply, which quits + * immediately after sending the request, and NM is unable to obtain the + * sender's UID as dbus-send has already dropped off the bus. Thus NM + * fails the request. Instead, don't validate the request, but rely on + * D-Bus permissions to restrict the call to root. + */ + _internal_sleep(self, do_sleep); + nm_audit_log_control_op(NM_AUDIT_OP_SLEEP_CONTROL, + do_sleep ? "on" : "off", + TRUE, + subject, + NULL); + g_dbus_method_invocation_return_value(invocation, NULL); + return; +} + +static void +sleeping_cb(NMSleepMonitor *monitor, gboolean is_about_to_suspend, gpointer user_data) +{ + NMManager *self = user_data; + + _LOGT(LOGD_SUSPEND, "sleep: received %s signal", is_about_to_suspend ? "sleeping" : "resuming"); + _internal_sleep(self, is_about_to_suspend); +} + +static void +_internal_enable(NMManager *self, gboolean enable) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_config_state_set(priv->config, + TRUE, + FALSE, + NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, + enable); + + _LOGI(LOGD_SUSPEND, + "%s requested (sleeping: %s enabled: %s)", + enable ? "enable" : "disable", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->net_enabled = enable; + + do_sleep_wake(self, FALSE); + + _notify(self, PROP_NETWORKING_ENABLED); +} + +static void +enable_net_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMAuthCallResult result; + gboolean enable; + NMAuthSubject * subject; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + enable = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "enable")); + subject = nm_auth_chain_get_subject(chain); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK); + if (result != NM_AUTH_CALL_RESULT_YES) { + GError *ret_error; + + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to enable/disable networking"); + nm_audit_log_control_op(NM_AUDIT_OP_NET_CONTROL, + enable ? "on" : "off", + FALSE, + subject, + ret_error->message); + g_dbus_method_invocation_take_error(context, ret_error); + return; + } + + _internal_enable(self, enable); + g_dbus_method_invocation_return_value(context, NULL); + nm_audit_log_control_op(NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE, subject, NULL); +} + +static void +impl_manager_enable(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + GError * error = NULL; + gboolean enable; + + g_variant_get(parameters, "(b)", &enable); + + if (priv->net_enabled == enable) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, + "Already %s", + enable ? "enabled" : "disabled"); + goto done; + } + + chain = nm_auth_chain_new_context(invocation, enable_net_done_cb, self); + if (!chain) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + goto done; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "enable", GUINT_TO_POINTER(enable), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE); + +done: + if (error) + g_dbus_method_invocation_take_error(invocation, error); +} + +/* Permissions */ + +static void +get_permissions_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + GVariantBuilder results; + int i; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + g_variant_builder_init(&results, G_VARIANT_TYPE("a{ss}")); + + for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) { + const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; + NMAuthCallResult result; + const char * result_str; + + result = nm_auth_chain_get_result(chain, permission); + result_str = nm_client_permission_result_to_string(nm_auth_call_result_to_client(result)); + g_variant_builder_add(&results, "{ss}", permission, result_str); + } + + g_dbus_method_invocation_return_value(context, g_variant_new("(a{ss})", &results)); +} + +static void +impl_manager_get_permissions(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + int i; + + chain = nm_auth_chain_new_context(invocation, get_permissions_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + + for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) { + const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; + + nm_auth_chain_add_call_unsafe(chain, permission, FALSE); + } +} + +static void +impl_manager_state(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager *self = NM_MANAGER(obj); + + nm_manager_update_state(self); + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(u)", NM_MANAGER_GET_PRIVATE(self)->state)); +} + +static void +impl_manager_set_logging(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + GError * error = NULL; + const char *level; + const char *domains; + + /* The permission is already enforced by the D-Bus daemon, but we ensure + * that the caller is still alive so that clients are forced to wait and + * we'll be able to switch to polkit without breaking behavior. + */ + if (!nm_dbus_manager_ensure_uid(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), + invocation, + G_MAXULONG, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED)) + return; + + g_variant_get(parameters, "(&s&s)", &level, &domains); + + if (nm_logging_setup(level, domains, NULL, &error)) { + _LOGI(LOGD_CORE, + "logging: level '%s' domains '%s'", + nm_logging_level_to_string(), + nm_logging_domains_to_string()); + } + + if (error) + g_dbus_method_invocation_take_error(invocation, error); + else + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static void +impl_manager_get_logging(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(ss)", nm_logging_level_to_string(), nm_logging_domains_to_string())); +} + +typedef struct { + NMManager * self; + GDBusMethodInvocation *context; + guint remaining; +} ConnectivityCheckData; + +static void +device_connectivity_done(NMDevice * device, + NMDeviceConnectivityHandle *handle, + NMConnectivityState state, + GError * error, + gpointer user_data) +{ + ConnectivityCheckData *data = user_data; + NMManager * self; + NMManagerPrivate * priv; + + nm_assert(data); + nm_assert(data->remaining > 0); + nm_assert(NM_IS_MANAGER(data->self)); + + data->remaining--; + + self = data->self; + priv = NM_MANAGER_GET_PRIVATE(self); + + if (data->context + && (data->remaining == 0 + || (state == NM_CONNECTIVITY_FULL + && priv->connectivity_state == NM_CONNECTIVITY_FULL))) { + /* despite having a @handle and @state returned by the requests, we always + * return the current connectivity_state. That is, because the connectivity_state + * and the answer to the connectivity check shall agree. + * + * However, if one of the requests (early) returns full connectivity and agrees with + * the accumulated connectivity state, we no longer have to wait. The result is set. + * + * This also works well, because NMDevice first emits change signals to its own + * connectivity state, which is then taken into account for the accumulated global + * state. All this happens, before the callback is invoked. */ + g_dbus_method_invocation_return_value( + g_steal_pointer(&data->context), + g_variant_new("(u)", (guint) priv->connectivity_state)); + } + + if (data->remaining == 0) { + g_object_unref(self); + g_slice_free(ConnectivityCheckData, data); + } +} + +static void +check_connectivity_auth_done_cb(NMAuthChain * chain, + GDBusMethodInvocation *context, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + GError * error = NULL; + NMAuthCallResult result; + ConnectivityCheckData *data; + NMDevice * device; + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to recheck connectivity"); + } + if (error) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + data = g_slice_new(ConnectivityCheckData); + data->self = g_object_ref(self); + data->context = context; + data->remaining = 0; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_check_connectivity(device, AF_INET, device_connectivity_done, data)) + data->remaining++; + if (nm_device_check_connectivity(device, AF_INET6, device_connectivity_done, data)) + data->remaining++; + } + + if (data->remaining == 0) { + /* call the handler at least once. */ + data->remaining = 1; + device_connectivity_done(NULL, NULL, NM_CONNECTIVITY_UNKNOWN, NULL, data); + /* @data got destroyed. */ + } +} + +static void +impl_manager_check_connectivity(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + + chain = nm_auth_chain_new_context(invocation, check_connectivity_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); +} + +static void +start_factory(NMDeviceFactory *factory, gpointer user_data) +{ + nm_device_factory_start(factory); +} + +gboolean +nm_manager_write_device_state(NMManager *self, NMDevice *device, int *out_ifindex) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + int ifindex; + gboolean managed; + NMConfigDeviceStateManagedType managed_type; + const char * uuid = NULL; + const char * perm_hw_addr_fake = NULL; + gboolean perm_hw_addr_is_fake; + guint32 route_metric_default_aspired; + guint32 route_metric_default_effective; + NMTernary nm_owned; + NMDhcpConfig * dhcp_config; + const char * next_server = NULL; + const char * root_path = NULL; + + NM_SET_OUT(out_ifindex, 0); + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex <= 0) + return FALSE; + if (ifindex == 1) { + /* ignore loopback */ + return FALSE; + } + + if (!nm_platform_link_get(priv->platform, ifindex)) + return FALSE; + + managed = nm_device_get_managed(device, FALSE); + if (managed) { + NMSettingsConnection *sett_conn = NULL; + + if (nm_device_get_state(device) <= NM_DEVICE_STATE_ACTIVATED) + sett_conn = nm_device_get_settings_connection(device); + if (sett_conn) + uuid = nm_settings_connection_get_uuid(sett_conn); + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED; + } else if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT)) + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED; + else + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN; + + perm_hw_addr_fake = + nm_device_get_permanent_hw_address_full(device, FALSE, &perm_hw_addr_is_fake); + if (perm_hw_addr_fake && !perm_hw_addr_is_fake) + perm_hw_addr_fake = NULL; + + nm_owned = nm_device_is_software(device) ? nm_device_is_nm_owned(device) : NM_TERNARY_DEFAULT; + + route_metric_default_effective = _device_route_metric_get(self, + ifindex, + NM_DEVICE_TYPE_UNKNOWN, + TRUE, + &route_metric_default_aspired); + + dhcp_config = nm_device_get_dhcp_config(device, AF_INET); + if (dhcp_config) { + root_path = nm_dhcp_config_get_option(dhcp_config, "root_path"); + next_server = nm_dhcp_config_get_option(dhcp_config, "next_server"); + } + + if (!nm_config_device_state_write(ifindex, + managed_type, + perm_hw_addr_fake, + uuid, + nm_owned, + route_metric_default_aspired, + route_metric_default_effective, + next_server, + root_path)) + return FALSE; + + NM_SET_OUT(out_ifindex, ifindex); + return TRUE; +} + +void +nm_manager_write_device_state_all(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *preserve_ifindexes = NULL; + NMDevice * device; + + preserve_ifindexes = g_hash_table_new(nm_direct_hash, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + int ifindex; + + if (nm_manager_write_device_state(self, device, &ifindex)) { + g_hash_table_add(preserve_ifindexes, GINT_TO_POINTER(ifindex)); + } + } + + nm_config_device_state_prune_stale(preserve_ifindexes, NULL); +} + +static gboolean +devices_inited_cb(gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + priv->devices_inited_id = 0; + priv->devices_inited = TRUE; + check_if_startup_complete(self); + return G_SOURCE_REMOVE; +} + +gboolean +nm_manager_start(NMManager *self, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free NMSettingsConnection **connections = NULL; + guint i; + + nm_device_factory_manager_load_factories(_register_device_factory, self); + + nm_device_factory_manager_for_each_factory(start_factory, NULL); + + /* Set initial radio enabled/disabled state */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + RadioState *rstate = &priv->radio_states[i]; + gboolean enabled; + + if (!rstate->desc) + continue; + + /* recheck kernel rfkill state */ + update_rstate_from_rfkill(priv->rfkill_mgr, rstate); + + if (rstate->desc) { + _LOGI(LOGD_RFKILL, + "rfkill: %s %s by radio killswitch; %s by state file", + rstate->desc, + (rstate->hw_enabled && rstate->sw_enabled) ? "enabled" : "disabled", + rstate->user_enabled ? "enabled" : "disabled"); + } + enabled = radio_enabled_for_rstate(rstate, TRUE); + manager_update_radio_enabled(self, rstate, enabled); + } + + _LOGI(LOGD_CORE, "Networking is %s by state file", priv->net_enabled ? "enabled" : "disabled"); + + system_unmanaged_devices_changed_cb(priv->settings, NULL, self); + + hostname_changed_cb(priv->hostname_manager, NULL, self); + + if (!nm_settings_start(priv->settings, error)) + return FALSE; + + nm_platform_process_events(priv->platform); + + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(platform_link_cb), + self); + + platform_query_devices(self); + + /* Load VPN plugins */ + priv->vpn_manager = g_object_ref(nm_vpn_manager_get()); + + _LOGD(LOGD_CORE, "creating virtual devices..."); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + G_CALLBACK(connection_added_cb), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK(connection_updated_cb), + self); + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) + connection_changed(self, connections[i]); + + nm_clear_g_source(&priv->devices_inited_id); + priv->devices_inited_id = g_idle_add_full(G_PRIORITY_LOW + 10, devices_inited_cb, self, NULL); + + return TRUE; +} + +void +nm_manager_stop(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + /* FIXME(shutdown): we don't do a proper shutdown yet: + * - need to ensure that all pending async operations are cancelled + * - e.g. operations in priv->async_op_lst_head + * - need to ensure that no more asynchronous requests are started, + * or that they complete quickly, or that they fail quickly. + * - note that cancelling some operations is not possible synchronously. + * Hence, stop() only prepares shutdown and tells everybody to not + * accept new work, and to complete in a timely manner. + * We need to still iterate the mainloop for a bit, to give everybody + * the chance to complete. + * - e.g. see comment at nm_auth_manager_force_shutdown() + */ + + nm_dbus_manager_stop(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self))); + + while ((device = c_list_first_entry(&priv->devices_lst_head, NMDevice, devices_lst))) + remove_device(self, device, TRUE); + + _active_connection_cleanup(self); + + nm_clear_g_source(&priv->devices_inited_id); +} + +static gboolean +handle_firmware_changed(gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + priv->fw_changed_id = 0; + + /* Try to re-enable devices with missing firmware */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + NMDeviceState state = nm_device_get_state(device); + + if (nm_device_get_firmware_missing(device) && (state == NM_DEVICE_STATE_UNAVAILABLE)) { + _LOG2I(LOGD_CORE, device, "firmware may now be available"); + + /* Re-set unavailable state to try bringing the device up again */ + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + } + } + + return FALSE; +} + +static void +firmware_dir_changed(GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (!priv->fw_changed_id) { + priv->fw_changed_id = g_timeout_add_seconds(4, handle_firmware_changed, self); + _LOGI(LOGD_CORE, "kernel firmware directory '%s' changed", KERNEL_FIRMWARE_DIR); + } + break; + default: + break; + } +} + +static void +connection_metered_changed(GObject *object, NMMetered metered, gpointer user_data) +{ + nm_manager_update_metered(NM_MANAGER(user_data)); +} + +static void +policy_default_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + + /* Note: this assumes that it's not possible for the IP4 default + * route to be going over the default-ip6-device. If that changes, + * we need something more complicated here. + */ + ac = nm_policy_get_default_ip4_ac(priv->policy); + if (!ac) + ac = nm_policy_get_default_ip6_ac(priv->policy); + + if (ac != priv->primary_connection) { + if (priv->primary_connection) { + g_signal_handlers_disconnect_by_func(priv->primary_connection, + G_CALLBACK(connection_metered_changed), + self); + g_clear_object(&priv->primary_connection); + } + + priv->primary_connection = ac ? g_object_ref(ac) : NULL; + + if (priv->primary_connection) { + g_signal_connect(priv->primary_connection, + NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED, + G_CALLBACK(connection_metered_changed), + self); + } + _LOGD(LOGD_CORE, + "PrimaryConnection now %s", + ac ? nm_active_connection_get_settings_connection_id(ac) : "(none)"); + _notify(self, PROP_PRIMARY_CONNECTION); + _notify(self, PROP_PRIMARY_CONNECTION_TYPE); + nm_manager_update_metered(self); + } +} + +static void +policy_activating_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *activating, *best; + + /* We only look at activating-ip6-ac if activating-ip4-ac + * AND default-ip4-ac are NULL; if default-ip4-ac is + * non-NULL, then activating-ip6-ac is irrelevant, since while + * that AC might become the new default-ip6-ac, it can't + * become primary-connection while default-ip4-ac is set to + * something else. + */ + activating = nm_policy_get_activating_ip4_ac(priv->policy); + best = nm_policy_get_default_ip4_ac(priv->policy); + if (!activating && !best) + activating = nm_policy_get_activating_ip6_ac(priv->policy); + + if (nm_g_object_ref_set(&priv->activating_connection, activating)) { + _LOGD(LOGD_CORE, + "ActivatingConnection now %s", + activating ? nm_active_connection_get_settings_connection_id(activating) : "(none)"); + _notify(self, PROP_ACTIVATING_CONNECTION); + } +} + +/*****************************************************************************/ + +typedef struct { + NMManager * self; + NMDBusObject * obj; + const NMDBusInterfaceInfoExtended *interface_info; + const NMDBusPropertyInfoExtended * property_info; + GVariant * value; + guint64 export_version_id; +} DBusSetPropertyHandle; + +#define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied" + +static void +_dbus_set_property_auth_cb(NMAuthChain * chain, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + DBusSetPropertyHandle *handle_data = user_data; + gs_unref_object NMDBusObject * obj = handle_data->obj; + const NMDBusInterfaceInfoExtended *interface_info = handle_data->interface_info; + const NMDBusPropertyInfoExtended * property_info = handle_data->property_info; + gs_unref_variant GVariant *value = handle_data->value; + guint64 export_version_id = handle_data->export_version_id; + gs_unref_object NMManager *self = handle_data->self; + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthCallResult result; + gs_free_error GError *local = NULL; + const char * error_name = NULL; + const char * error_message = NULL; + GValue gvalue; + + g_slice_free(DBusSetPropertyHandle, handle_data); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + result = nm_auth_chain_get_result(chain, property_info->writable.permission); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error_name = NM_PERM_DENIED_ERROR; + error_message = "Not authorized to perform this operation"; + goto out; + } + + if (export_version_id != nm_dbus_object_get_export_version_id(obj)) { + error_name = "org.freedesktop.DBus.Error.UnknownObject"; + error_message = "Object was deleted while authenticating"; + goto out; + } + + /* Handle some properties specially *sigh* */ + if (interface_info == &interface_info_manager + && nm_streq(property_info->property_name, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) { + const NMGlobalDnsConfig *global_dns; + + global_dns = nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)); + if (global_dns && !nm_global_dns_config_is_internal(global_dns)) { + error_name = NM_PERM_DENIED_ERROR; + error_message = "Global DNS configuration already set via configuration file"; + goto out; + } + } + + g_dbus_gvariant_to_gvalue(value, &gvalue); + if (!nm_g_object_set_property(G_OBJECT(obj), property_info->property_name, &gvalue, &local)) { + error_name = "org.freedesktop.DBus.Error.InvalidArgs"; + error_message = local->message; + } + g_value_unset(&gvalue); + +out: + nm_audit_log_control_op(property_info->writable.audit_op, + property_info->property_name, + !error_message, + nm_auth_chain_get_subject(chain), + error_message); + if (error_message) + g_dbus_method_invocation_return_dbus_error(invocation, error_name, error_message); + else + g_dbus_method_invocation_return_value(invocation, NULL); +} + +void +nm_manager_dbus_set_property_handle(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusPropertyInfoExtended * property_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * value, + gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * error_message = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + DBusSetPropertyHandle * handle_data; + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN; + goto err; + } + + handle_data = g_slice_new0(DBusSetPropertyHandle); + handle_data->self = g_object_ref(self); + handle_data->obj = g_object_ref(obj); + handle_data->interface_info = interface_info; + handle_data->property_info = property_info; + handle_data->value = g_variant_ref(value); + handle_data->export_version_id = nm_dbus_object_get_export_version_id(obj); + + chain = nm_auth_chain_new_subject(subject, invocation, _dbus_set_property_auth_cb, handle_data); + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_add_call_unsafe(chain, property_info->writable.permission, TRUE); + return; + +err: + nm_audit_log_control_op(property_info->writable.audit_op, + property_info->property_name, + FALSE, + invocation, + error_message); + g_dbus_method_invocation_return_error_literal(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_AUTH_FAILED, + error_message); +} + +/*****************************************************************************/ + +static NMCheckpointManager * +_checkpoint_mgr_get(NMManager *self, gboolean create_as_needed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->checkpoint_mgr) && create_as_needed) + priv->checkpoint_mgr = nm_checkpoint_manager_new(self, obj_properties[PROP_CHECKPOINTS]); + return priv->checkpoint_mgr; +} + +static void +checkpoint_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + char * op; + char * checkpoint_path = NULL; + char ** devices; + NMCheckpoint * checkpoint; + NMAuthCallResult result; + guint32 timeout, flags; + GVariant * variant = NULL; + GError * error = NULL; + const char * arg = NULL; + guint32 add_timeout; + + op = nm_auth_chain_get_data(chain, "audit-op"); + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK); + + if (NM_IN_STRSET(op, + NM_AUDIT_OP_CHECKPOINT_DESTROY, + NM_AUDIT_OP_CHECKPOINT_ROLLBACK, + NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) + arg = checkpoint_path = nm_auth_chain_get_data(chain, "checkpoint_path"); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to checkpoint/rollback"); + } else { + if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_CREATE)) { + timeout = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "timeout")); + flags = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "flags")); + devices = nm_auth_chain_get_data(chain, "devices"); + + checkpoint = nm_checkpoint_manager_create(_checkpoint_mgr_get(self, TRUE), + (const char *const *) devices, + timeout, + (NMCheckpointCreateFlags) flags, + &error); + if (checkpoint) { + arg = nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint)); + variant = g_variant_new("(o)", arg); + } + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_DESTROY)) { + nm_checkpoint_manager_destroy(_checkpoint_mgr_get(self, TRUE), checkpoint_path, &error); + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) { + nm_checkpoint_manager_rollback(_checkpoint_mgr_get(self, TRUE), + checkpoint_path, + &variant, + &error); + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) { + add_timeout = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "add_timeout")); + nm_checkpoint_manager_adjust_rollback_timeout(_checkpoint_mgr_get(self, TRUE), + checkpoint_path, + add_timeout, + &error); + } else + g_return_if_reached(); + } + + nm_audit_log_checkpoint_op(op, + arg ?: "", + !error, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + + if (error) + g_dbus_method_invocation_take_error(context, error); + else + g_dbus_method_invocation_return_value(context, variant); +} + +static void +impl_manager_checkpoint_create(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + char ** devices; + guint32 rollback_timeout; + guint32 flags; + + G_STATIC_ASSERT_EXPR(sizeof(flags) <= sizeof(NMCheckpointCreateFlags)); + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(^aouu)", &devices, &rollback_timeout, &flags); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL); + nm_auth_chain_set_data(chain, "devices", devices, (GDestroyNotify) g_strfreev); + nm_auth_chain_set_data(chain, "flags", GUINT_TO_POINTER(flags), NULL); + nm_auth_chain_set_data(chain, "timeout", GUINT_TO_POINTER(rollback_timeout), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_destroy(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&o)", &checkpoint_path); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_DESTROY, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_rollback(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&o)", &checkpoint_path); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_adjust_rollback_timeout(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + guint32 add_timeout; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&ou)", &checkpoint_path, &add_timeout); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_set_data(chain, "add_timeout", GUINT_TO_POINTER(add_timeout), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +/*****************************************************************************/ + +static void +auth_mgr_changed(NMAuthManager *auth_manager, gpointer user_data) +{ + /* Let clients know they should re-check their authorization */ + nm_dbus_object_emit_signal(user_data, + &interface_info_manager, + &signal_info_check_permissions, + "()"); +} + +#define KERN_RFKILL_OP_CHANGE_ALL 3 +#define KERN_RFKILL_TYPE_WLAN 1 +#define KERN_RFKILL_TYPE_WWAN 5 +struct rfkill_event { + uint32_t idx; + uint8_t type; + uint8_t op; + uint8_t soft; + uint8_t hard; +} _nm_packed; + +static void +rfkill_change(NMManager *self, const char *desc, RfKillType rtype, gboolean enabled) +{ + int fd; + struct rfkill_event event; + ssize_t len; + int errsv; + + g_return_if_fail(rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN); + + fd = open("/dev/rfkill", O_RDWR | O_CLOEXEC); + if (fd < 0) { + if (errno == EACCES) + _LOGW(LOGD_RFKILL, "rfkill: (%s): failed to open killswitch device", desc); + return; + } + + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + _LOGW(LOGD_RFKILL, + "rfkill: (%s): failed to set killswitch device for " + "non-blocking operation", + desc); + nm_close(fd); + return; + } + + memset(&event, 0, sizeof(event)); + event.op = KERN_RFKILL_OP_CHANGE_ALL; + switch (rtype) { + case RFKILL_TYPE_WLAN: + event.type = KERN_RFKILL_TYPE_WLAN; + break; + case RFKILL_TYPE_WWAN: + event.type = KERN_RFKILL_TYPE_WWAN; + break; + default: + g_assert_not_reached(); + } + event.soft = enabled ? 0 : 1; + + len = write(fd, &event, sizeof(event)); + if (len < 0) { + errsv = errno; + _LOGW(LOGD_RFKILL, + "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s", + desc, + errsv, + nm_strerror_native(errsv)); + } else if (len == sizeof(event)) { + _LOGI(LOGD_RFKILL, + "rfkill: %s hardware radio set %s", + desc, + enabled ? "enabled" : "disabled"); + } else { + /* Failed to write full structure */ + _LOGW(LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc); + } + + nm_close(fd); +} + +static void +manager_radio_user_toggled(NMManager *self, RadioState *rstate, gboolean enabled) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean old_enabled, new_enabled; + + /* Don't touch devices if asleep/networking disabled */ + if (manager_sleeping(self)) + return; + + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: (%s): setting radio %s by user", + rstate->desc, + enabled ? "enabled" : "disabled"); + } + + /* Update enabled key in state file */ + nm_config_state_set(priv->config, TRUE, FALSE, rstate->key, enabled); + + /* When the user toggles the radio, their request should override any + * daemon (like ModemManager) enabled state that can be changed. For WWAN + * for example, we want the WwanEnabled property to reflect the daemon state + * too so that users can toggle the modem powered, but we don't want that + * daemon state to affect whether or not the user *can* turn it on, which is + * what the kernel rfkill state does. So we ignore daemon enabled state + * when determining what the new state should be since it shouldn't block + * the user's request. + */ + old_enabled = radio_enabled_for_rstate(rstate, TRUE); + rstate->user_enabled = enabled; + new_enabled = radio_enabled_for_rstate(rstate, FALSE); + if (new_enabled != old_enabled) { + /* Try to change the kernel rfkill state */ + if (rstate->rtype == RFKILL_TYPE_WLAN || rstate->rtype == RFKILL_TYPE_WWAN) + rfkill_change(self, rstate->desc, rstate->rtype, new_enabled); + + manager_update_radio_enabled(self, rstate, new_enabled); + } +} + +static gboolean +periodic_update_active_connection_timestamps(gpointer user_data) +{ + NMManager * manager = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + NMActiveConnection *ac; + gboolean has_time = FALSE; + guint64 t = 0; + + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + if (nm_active_connection_get_state(ac) != NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + continue; + + if (!has_time) { + t = time(NULL); + has_time = TRUE; + } + nm_settings_connection_update_timestamp(nm_active_connection_get_settings_connection(ac), + t); + } + return G_SOURCE_CONTINUE; +} + +void +nm_manager_unblock_failed_ovs_interfaces(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_policy_unblock_failed_ovs_interfaces(priv->policy); +} + +/*****************************************************************************/ + +void +nm_manager_set_capability(NMManager *self, NMCapability cap) +{ + NMManagerPrivate *priv; + guint32 cap_i; + gssize idx; + + g_return_if_fail(NM_IS_MANAGER(self)); + if (cap < 1 || cap > _NM_CAPABILITY_MAX) + g_return_if_reached(); + + cap_i = (guint32) cap; + + priv = NM_MANAGER_GET_PRIVATE(self); + + idx = nm_utils_array_find_binary_search(&g_array_index(priv->capabilities, guint32, 0), + sizeof(guint32), + priv->capabilities->len, + &cap_i, + nm_cmp_uint32_p_with_data, + NULL); + if (idx >= 0) + return; + + nm_assert((~idx) <= (gssize) priv->capabilities->len); + + g_array_insert_val(priv->capabilities, ~idx, cap_i); + _notify(self, PROP_CAPABILITIES); +} + +void +nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device) +{ + g_signal_emit(self, signals[DEVICE_IFINDEX_CHANGED], 0, device); +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_REGISTER(NMManager); + +NMManager * +nm_manager_get(void) +{ + g_return_val_if_fail(singleton_instance, NULL); + return singleton_instance; +} + +NMSettings * +nm_settings_get(void) +{ + g_return_val_if_fail(singleton_instance, NULL); + + return NM_MANAGER_GET_PRIVATE(singleton_instance)->settings; +} + +NMManager * +nm_manager_setup(void) +{ + NMManager *self; + + g_return_val_if_fail(!singleton_instance, singleton_instance); + + self = g_object_new(NM_TYPE_MANAGER, NULL); + nm_assert(NM_IS_MANAGER(self)); + singleton_instance = self; + + nm_singleton_instance_register(); + nm_log_dbg(LOGD_CORE, + "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + "NMManager", + NM_HASH_OBFUSCATE_PTR(singleton_instance)); + + nm_dbus_object_export(NM_DBUS_OBJECT(self)); + return self; +} + +static void +constructed(GObject *object) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + const NMConfigState *state; + + G_OBJECT_CLASS(nm_manager_parent_class)->constructed(object); + + priv->settings = nm_settings_new(self); + + nm_dbus_object_export(NM_DBUS_OBJECT(priv->settings)); + + g_signal_connect(priv->settings, + "notify::" NM_SETTINGS_STARTUP_COMPLETE, + G_CALLBACK(settings_startup_complete_changed), + self); + g_signal_connect(priv->settings, + "notify::" NM_SETTINGS_UNMANAGED_SPECS, + G_CALLBACK(system_unmanaged_devices_changed_cb), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, + G_CALLBACK(connection_flags_changed), + self); + + priv->hostname_manager = g_object_ref(nm_hostname_manager_get()); + g_signal_connect(priv->hostname_manager, + "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, + G_CALLBACK(hostname_changed_cb), + self); + + /* + * Do not delete existing virtual devices to keep connectivity up. + * Virtual devices are reused when NetworkManager is restarted. + * Hence, don't react on NM_SETTINGS_SIGNAL_CONNECTION_REMOVED. + */ + + priv->policy = nm_policy_new(self, priv->settings); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_DEFAULT_IP4_AC, + G_CALLBACK(policy_default_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_DEFAULT_IP6_AC, + G_CALLBACK(policy_default_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_ACTIVATING_IP4_AC, + G_CALLBACK(policy_activating_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_ACTIVATING_IP6_AC, + G_CALLBACK(policy_activating_ac_changed), + self); + + priv->config = g_object_ref(nm_config_get()); + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_config_changed_cb), + self); + + state = nm_config_state_get(priv->config); + + priv->net_enabled = state->net_enabled; + + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = state->wifi_enabled; + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = state->wwan_enabled; + + priv->rfkill_mgr = nm_rfkill_manager_new(); + g_signal_connect(priv->rfkill_mgr, + NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED, + G_CALLBACK(rfkill_manager_rfkill_changed_cb), + self); + + /* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state + * in case the BIOS doesn't save rfkill state, and to be consistent with user + * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel + * rfkill. + */ + rfkill_change(self, + priv->radio_states[RFKILL_TYPE_WLAN].desc, + RFKILL_TYPE_WLAN, + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled); + rfkill_change(self, + priv->radio_states[RFKILL_TYPE_WWAN].desc, + RFKILL_TYPE_WWAN, + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled); +} + +static void +nm_manager_init(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint i; + GFile * file; + + c_list_init(&priv->auth_lst_head); + c_list_init(&priv->link_cb_lst); + c_list_init(&priv->devices_lst_head); + c_list_init(&priv->active_connections_lst_head); + c_list_init(&priv->async_op_lst_head); + c_list_init(&priv->delete_volatile_connection_lst_head); + + priv->platform = g_object_ref(NM_PLATFORM_GET); + + priv->capabilities = g_array_new(FALSE, FALSE, sizeof(guint32)); + + /* Initialize rfkill structures and states */ + memset(priv->radio_states, 0, sizeof(priv->radio_states)); + + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = TRUE; + priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi"; + priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN; + + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE; + priv->radio_states[RFKILL_TYPE_WWAN].key = NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].prop = NM_MANAGER_WWAN_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].hw_prop = NM_MANAGER_WWAN_HARDWARE_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].desc = "WWAN"; + priv->radio_states[RFKILL_TYPE_WWAN].rtype = RFKILL_TYPE_WWAN; + + for (i = 0; i < RFKILL_TYPE_MAX; i++) + priv->radio_states[i].hw_enabled = TRUE; + + priv->sleeping = FALSE; + priv->state = NM_STATE_DISCONNECTED; + priv->startup = TRUE; + + /* sleep/wake handling */ + priv->sleep_monitor = nm_sleep_monitor_new(); + g_signal_connect(priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING, G_CALLBACK(sleeping_cb), self); + + /* Listen for authorization changes */ + priv->auth_mgr = g_object_ref(nm_auth_manager_get()); + g_signal_connect(priv->auth_mgr, + NM_AUTH_MANAGER_SIGNAL_CHANGED, + G_CALLBACK(auth_mgr_changed), + self); + + /* Monitor the firmware directory */ + if (strlen(KERNEL_FIRMWARE_DIR)) { + file = g_file_new_for_path(KERNEL_FIRMWARE_DIR "/"); + priv->fw_monitor = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + } + + if (priv->fw_monitor) { + g_signal_connect(priv->fw_monitor, "changed", G_CALLBACK(firmware_dir_changed), self); + _LOGI(LOGD_CORE, "monitoring kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); + } else { + _LOGW(LOGD_CORE, "failed to monitor kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); + } + + /* Update timestamps in active connections */ + priv->timestamp_update_id = + g_timeout_add_seconds(300, + (GSourceFunc) periodic_update_active_connection_timestamps, + self); + + priv->metered = NM_METERED_UNKNOWN; + priv->sleep_devices = g_hash_table_new(nm_direct_hash, NULL); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConfigData * config_data; + const NMGlobalDnsConfig *dns_config; + const char * type; + const char * path; + NMActiveConnection * ac; + GPtrArray * ptrarr; + + switch (prop_id) { + case PROP_VERSION: + g_value_set_string(value, VERSION); + break; + case PROP_CAPABILITIES: + g_value_set_variant(value, + g_variant_new_fixed_array(G_VARIANT_TYPE("u"), + priv->capabilities->data, + priv->capabilities->len, + sizeof(guint32))); + break; + case PROP_STATE: + g_value_set_uint(value, priv->state); + break; + case PROP_STARTUP: + g_value_set_boolean(value, priv->startup); + break; + case PROP_NETWORKING_ENABLED: + g_value_set_boolean(value, priv->net_enabled); + break; + case PROP_WIRELESS_ENABLED: + g_value_set_boolean(value, radio_enabled_for_type(self, RFKILL_TYPE_WLAN, TRUE)); + break; + case PROP_WIRELESS_HARDWARE_ENABLED: + g_value_set_boolean(value, priv->radio_states[RFKILL_TYPE_WLAN].hw_enabled); + break; + case PROP_WWAN_ENABLED: + g_value_set_boolean(value, radio_enabled_for_type(self, RFKILL_TYPE_WWAN, TRUE)); + break; + case PROP_WWAN_HARDWARE_ENABLED: + g_value_set_boolean(value, priv->radio_states[RFKILL_TYPE_WWAN].hw_enabled); + break; + case PROP_WIMAX_ENABLED: + g_value_set_boolean(value, FALSE); + break; + case PROP_WIMAX_HARDWARE_ENABLED: + g_value_set_boolean(value, FALSE); + break; + case PROP_ACTIVE_CONNECTIONS: + ptrarr = g_ptr_array_new(); + c_list_for_each_entry_prev (ac, + &priv->active_connections_lst_head, + active_connections_lst) { + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(ac)); + if (path) + g_ptr_array_add(ptrarr, g_strdup(path)); + } + g_ptr_array_add(ptrarr, NULL); + g_value_take_boxed(value, g_ptr_array_free(ptrarr, FALSE)); + break; + case PROP_CONNECTIVITY: + g_value_set_uint(value, priv->connectivity_state); + break; + case PROP_CONNECTIVITY_CHECK_AVAILABLE: + config_data = nm_config_get_data(priv->config); + g_value_set_boolean(value, nm_config_data_get_connectivity_uri(config_data) != NULL); + break; + case PROP_CONNECTIVITY_CHECK_ENABLED: + g_value_set_boolean(value, concheck_enabled(self, NULL)); + break; + case PROP_CONNECTIVITY_CHECK_URI: + config_data = nm_config_get_data(priv->config); + g_value_set_string(value, nm_config_data_get_connectivity_uri(config_data)); + break; + case PROP_PRIMARY_CONNECTION: + nm_dbus_utils_g_value_set_object_path(value, priv->primary_connection); + break; + case PROP_PRIMARY_CONNECTION_TYPE: + type = NULL; + if (priv->primary_connection) { + NMConnection *con; + + con = nm_active_connection_get_applied_connection(priv->primary_connection); + if (con) + type = nm_connection_get_connection_type(con); + } + g_value_set_string(value, type ?: ""); + break; + case PROP_ACTIVATING_CONNECTION: + nm_dbus_utils_g_value_set_object_path(value, priv->activating_connection); + break; + case PROP_SLEEPING: + g_value_set_boolean(value, priv->sleeping); + break; + case PROP_DEVICES: + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, FALSE))); + break; + case PROP_METERED: + g_value_set_uint(value, priv->metered); + break; + case PROP_GLOBAL_DNS_CONFIGURATION: + config_data = nm_config_get_data(priv->config); + dns_config = nm_config_data_get_global_dns_config(config_data); + nm_global_dns_config_to_dbus(dns_config, value); + break; + case PROP_ALL_DEVICES: + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, TRUE))); + break; + case PROP_CHECKPOINTS: + g_value_take_boxed( + value, + priv->checkpoint_mgr ? nm_utils_strv_make_deep_copied( + nm_checkpoint_manager_get_checkpoint_paths(priv->checkpoint_mgr, NULL)) + : NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMGlobalDnsConfig *dns_config; + GError * error = NULL; + + switch (prop_id) { + case PROP_WIRELESS_ENABLED: + manager_radio_user_toggled(NM_MANAGER(object), + &priv->radio_states[RFKILL_TYPE_WLAN], + g_value_get_boolean(value)); + break; + case PROP_WWAN_ENABLED: + manager_radio_user_toggled(NM_MANAGER(object), + &priv->radio_states[RFKILL_TYPE_WWAN], + g_value_get_boolean(value)); + break; + case PROP_WIMAX_ENABLED: + /* WIMAX is deprecated. This does nothing. */ + break; + case PROP_CONNECTIVITY_CHECK_ENABLED: + nm_config_set_connectivity_check_enabled(priv->config, g_value_get_boolean(value)); + break; + case PROP_GLOBAL_DNS_CONFIGURATION: + dns_config = nm_global_dns_config_from_dbus(value, &error); + if (!error) + nm_config_set_global_dns(priv->config, dns_config, &error); + + nm_global_dns_config_free(dns_config); + + if (error) { + _LOGD(LOGD_CORE, "set global DNS failed with error: %s", error->message); + g_error_free(error); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +_deinit_device_factory(NMDeviceFactory *factory, gpointer user_data) +{ + g_signal_handlers_disconnect_matched(factory, + G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + NULL, + NM_MANAGER(user_data)); +} + +static void +dispose(GObject *object) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + CList * iter; + + nm_assert(c_list_is_empty(&priv->async_op_lst_head)); + + g_signal_handlers_disconnect_by_func(priv->platform, G_CALLBACK(platform_link_cb), self); + while ((iter = c_list_first(&priv->link_cb_lst))) { + PlatformLinkCbData *data = c_list_entry(iter, PlatformLinkCbData, lst); + + g_source_remove(data->idle_id); + c_list_unlink_stale(&data->lst); + g_slice_free(PlatformLinkCbData, data); + } + + while ((iter = c_list_first(&priv->auth_lst_head))) + nm_auth_chain_destroy(nm_auth_chain_parent_lst_entry(iter)); + + nm_clear_g_source(&priv->devices_inited_id); + + nm_clear_pointer(&priv->checkpoint_mgr, nm_checkpoint_manager_free); + + if (priv->concheck_mgr) { + g_signal_handlers_disconnect_by_func(priv->concheck_mgr, + G_CALLBACK(concheck_config_changed_cb), + self); + g_clear_object(&priv->concheck_mgr); + } + + if (priv->auth_mgr) { + g_signal_handlers_disconnect_by_func(priv->auth_mgr, G_CALLBACK(auth_mgr_changed), self); + g_clear_object(&priv->auth_mgr); + } + + nm_assert(c_list_is_empty(&priv->devices_lst_head)); + + nm_clear_g_source(&priv->ac_cleanup_id); + + while ((iter = c_list_first(&priv->active_connections_lst_head))) + active_connection_remove(self, + c_list_entry(iter, NMActiveConnection, active_connections_lst)); + + nm_assert(c_list_is_empty(&priv->active_connections_lst_head)); + g_clear_object(&priv->primary_connection); + g_clear_object(&priv->activating_connection); + + if (priv->config) { + g_signal_handlers_disconnect_by_func(priv->config, _config_changed_cb, self); + g_clear_object(&priv->config); + } + + if (priv->policy) { + g_signal_handlers_disconnect_by_func(priv->policy, policy_default_ac_changed, self); + g_signal_handlers_disconnect_by_func(priv->policy, policy_activating_ac_changed, self); + g_clear_object(&priv->policy); + } + + if (priv->settings) { + g_signal_handlers_disconnect_by_func(priv->settings, + settings_startup_complete_changed, + self); + g_signal_handlers_disconnect_by_func(priv->settings, + system_unmanaged_devices_changed_cb, + self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_added_cb, self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_updated_cb, self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_flags_changed, self); + g_clear_object(&priv->settings); + } + + if (priv->hostname_manager) { + g_signal_handlers_disconnect_by_func(priv->hostname_manager, hostname_changed_cb, self); + g_clear_object(&priv->hostname_manager); + } + + g_clear_object(&priv->vpn_manager); + + sleep_devices_clear(self); + nm_clear_pointer(&priv->sleep_devices, g_hash_table_unref); + + if (priv->sleep_monitor) { + g_signal_handlers_disconnect_by_func(priv->sleep_monitor, sleeping_cb, self); + g_clear_object(&priv->sleep_monitor); + } + + if (priv->fw_monitor) { + g_signal_handlers_disconnect_by_func(priv->fw_monitor, firmware_dir_changed, self); + + nm_clear_g_source(&priv->fw_changed_id); + + g_file_monitor_cancel(priv->fw_monitor); + g_clear_object(&priv->fw_monitor); + } + + if (priv->rfkill_mgr) { + g_signal_handlers_disconnect_by_func(priv->rfkill_mgr, + rfkill_manager_rfkill_changed_cb, + self); + g_clear_object(&priv->rfkill_mgr); + } + + nm_clear_g_source(&priv->delete_volatile_connection_idle_id); + _delete_volatile_connection_all(self, FALSE); + nm_assert(!priv->delete_volatile_connection_idle_id); + nm_assert(c_list_is_empty(&priv->delete_volatile_connection_lst_head)); + + nm_device_factory_manager_for_each_factory(_deinit_device_factory, self); + + nm_clear_g_source(&priv->timestamp_update_id); + + nm_clear_pointer(&priv->device_route_metrics, g_hash_table_destroy); + + G_OBJECT_CLASS(nm_manager_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(object); + + g_array_free(priv->capabilities, TRUE); + + G_OBJECT_CLASS(nm_manager_parent_class)->finalize(object); + + g_object_unref(priv->platform); +} + +static const GDBusSignalInfo signal_info_check_permissions = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("CheckPermissions", ); + +static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "StateChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ); + +static const GDBusSignalInfo signal_info_device_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "DeviceAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device_path", "o"), ), ); + +static const GDBusSignalInfo signal_info_device_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "DeviceRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device_path", "o"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_manager = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Reload", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ), + .handle = impl_manager_reload, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetDevices", + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), ), ), + .handle = impl_manager_get_devices, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetAllDevices", + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), ), ), + .handle = impl_manager_get_all_devices, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetDeviceByIpIface", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("iface", "s"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device", "o"), ), ), + .handle = impl_manager_get_device_by_ip_iface, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "ActivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddAndActivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_add_and_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddAndActivateConnection2", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), + NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), + NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ), + .handle = impl_manager_add_and_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "DeactivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_deactivate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Sleep", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("sleep", "b"), ), ), + .handle = impl_manager_sleep, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Enable", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("enable", "b"), ), ), + .handle = impl_manager_enable, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetPermissions", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("permissions", "a{ss}"), ), ), + .handle = impl_manager_get_permissions, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetLogging", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("level", "s"), + NM_DEFINE_GDBUS_ARG_INFO("domains", "s"), ), ), + .handle = impl_manager_set_logging, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("GetLogging", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("level", "s"), + NM_DEFINE_GDBUS_ARG_INFO("domains", "s"), ), ), + .handle = impl_manager_get_logging, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckConnectivity", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connectivity", "u"), ), ), + .handle = impl_manager_check_connectivity, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("state", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ), + .handle = impl_manager_state, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointCreate", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), + NM_DEFINE_GDBUS_ARG_INFO("rollback_timeout", "u"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), ), + .handle = impl_manager_checkpoint_create, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointDestroy", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), ), + .handle = impl_manager_checkpoint_destroy, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointRollback", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("result", "a{su}"), ), ), + .handle = impl_manager_checkpoint_rollback, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointAdjustRollbackTimeout", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), + NM_DEFINE_GDBUS_ARG_INFO("add_timeout", "u"), ), ), + .handle = impl_manager_checkpoint_adjust_rollback_timeout, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_check_permissions, + &signal_info_state_changed, + &signal_info_device_added, + &signal_info_device_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Devices", "ao", NM_MANAGER_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AllDevices", + "ao", + NM_MANAGER_ALL_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Checkpoints", + "ao", + NM_MANAGER_CHECKPOINTS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NetworkingEnabled", + "b", + NM_MANAGER_NETWORKING_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WirelessEnabled", + "b", + NM_MANAGER_WIRELESS_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WirelessHardwareEnabled", + "b", + NM_MANAGER_WIRELESS_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WwanEnabled", + "b", + NM_MANAGER_WWAN_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WwanHardwareEnabled", + "b", + NM_MANAGER_WWAN_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WimaxEnabled", + "b", + NM_MANAGER_WIMAX_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WimaxHardwareEnabled", + "b", + NM_MANAGER_WIMAX_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveConnections", + "ao", + NM_MANAGER_ACTIVE_CONNECTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PrimaryConnection", + "o", + NM_MANAGER_PRIMARY_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PrimaryConnectionType", + "s", + NM_MANAGER_PRIMARY_CONNECTION_TYPE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Metered", "u", NM_MANAGER_METERED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActivatingConnection", + "o", + NM_MANAGER_ACTIVATING_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Startup", "b", NM_MANAGER_STARTUP), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Version", "s", NM_MANAGER_VERSION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Capabilities", + "au", + NM_MANAGER_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", "u", NM_MANAGER_STATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Connectivity", + "u", + NM_MANAGER_CONNECTIVITY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "ConnectivityCheckAvailable", + "b", + NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "ConnectivityCheckEnabled", + "b", + NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + NM_AUDIT_OP_NET_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ConnectivityCheckUri", + "s", + NM_MANAGER_CONNECTIVITY_CHECK_URI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "GlobalDnsConfiguration", + "a{sv}", + NM_MANAGER_GLOBAL_DNS_CONFIGURATION, + NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + NM_AUDIT_OP_NET_CONTROL), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_manager_class_init(NMManagerClass *manager_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(manager_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(manager_class); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_manager); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_VERSION] = g_param_spec_string(NM_MANAGER_VERSION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_variant(NM_MANAGER_CAPABILITIES, + "", + "", + G_VARIANT_TYPE("au"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE] = g_param_spec_uint(NM_MANAGER_STATE, + "", + "", + 0, + NM_STATE_DISCONNECTED, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STARTUP] = g_param_spec_boolean(NM_MANAGER_STARTUP, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NETWORKING_ENABLED] = + g_param_spec_boolean(NM_MANAGER_NETWORKING_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIRELESS_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIRELESS_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIRELESS_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIRELESS_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WWAN_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WWAN_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WWAN_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WWAN_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIMAX_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIMAX_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIMAX_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIMAX_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVE_CONNECTIONS] = + g_param_spec_boxed(NM_MANAGER_ACTIVE_CONNECTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY] = + g_param_spec_uint(NM_MANAGER_CONNECTIVITY, + "", + "", + NM_CONNECTIVITY_UNKNOWN, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] = + g_param_spec_boolean(NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] = + g_param_spec_boolean(NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_URI] = + g_param_spec_string(NM_MANAGER_CONNECTIVITY_CHECK_URI, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PRIMARY_CONNECTION] = + g_param_spec_string(NM_MANAGER_PRIMARY_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PRIMARY_CONNECTION_TYPE] = + g_param_spec_string(NM_MANAGER_PRIMARY_CONNECTION_TYPE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVATING_CONNECTION] = + g_param_spec_string(NM_MANAGER_ACTIVATING_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /* Sleeping is not exported over D-Bus */ + obj_properties[PROP_SLEEPING] = g_param_spec_boolean(NM_MANAGER_SLEEPING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICES] = g_param_spec_boxed(NM_MANAGER_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:metered: + * + * Whether the connectivity is metered. + * + * Since: 1.2 + **/ + obj_properties[PROP_METERED] = g_param_spec_uint(NM_MANAGER_METERED, + "", + "", + 0, + G_MAXUINT32, + NM_METERED_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:global-dns-configuration: + * + * The global DNS configuration. + * + * Since: 1.2 + **/ + obj_properties[PROP_GLOBAL_DNS_CONFIGURATION] = + g_param_spec_variant(NM_MANAGER_GLOBAL_DNS_CONFIGURATION, + "", + "", + G_VARIANT_TYPE("a{sv}"), + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:all-devices: + * + * All devices, including those that are not realized. + * + * Since: 1.2 + **/ + obj_properties[PROP_ALL_DEVICES] = + g_param_spec_boxed(NM_MANAGER_ALL_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CHECKPOINTS] = + g_param_spec_boxed(NM_MANAGER_CHECKPOINTS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + /* signals */ + + /* emitted only for realized devices */ + signals[DEVICE_ADDED] = g_signal_new(NM_MANAGER_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); + + /* Emitted for both realized devices and placeholder devices */ + signals[INTERNAL_DEVICE_ADDED] = g_signal_new(NM_MANAGER_INTERNAL_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + /* emitted only for realized devices when a device + * becomes unrealized or removed */ + signals[DEVICE_REMOVED] = g_signal_new(NM_MANAGER_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); + + /* Emitted for both realized devices and placeholder devices */ + signals[INTERNAL_DEVICE_REMOVED] = g_signal_new(NM_MANAGER_INTERNAL_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + signals[ACTIVE_CONNECTION_ADDED] = g_signal_new(NM_MANAGER_ACTIVE_CONNECTION_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_ACTIVE_CONNECTION); + + signals[ACTIVE_CONNECTION_REMOVED] = g_signal_new(NM_MANAGER_ACTIVE_CONNECTION_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_ACTIVE_CONNECTION); + + signals[CONFIGURE_QUIT] = g_signal_new(NM_MANAGER_CONFIGURE_QUIT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[DEVICE_IFINDEX_CHANGED] = g_signal_new(NM_MANAGER_DEVICE_IFINDEX_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); +} diff --git a/src/core/nm-manager.h b/src/core/nm-manager.h new file mode 100644 index 0000000..7a8b3a1 --- /dev/null +++ b/src/core/nm-manager.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2010 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_MANAGER_H__ +#define __NETWORKMANAGER_MANAGER_H__ + +#include "settings/nm-settings-connection.h" +#include "c-list/src/c-list.h" +#include "nm-dbus-manager.h" + +#define NM_TYPE_MANAGER (nm_manager_get_type()) +#define NM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MANAGER, NMManager)) +#define NM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MANAGER, NMManagerClass)) +#define NM_IS_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MANAGER)) +#define NM_IS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MANAGER)) +#define NM_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MANAGER, NMManagerClass)) + +#define NM_MANAGER_VERSION "version" +#define NM_MANAGER_CAPABILITIES "capabilities" +#define NM_MANAGER_STATE "state" +#define NM_MANAGER_STARTUP "startup" +#define NM_MANAGER_NETWORKING_ENABLED "networking-enabled" +#define NM_MANAGER_WIRELESS_ENABLED "wireless-enabled" +#define NM_MANAGER_WIRELESS_HARDWARE_ENABLED "wireless-hardware-enabled" +#define NM_MANAGER_WWAN_ENABLED "wwan-enabled" +#define NM_MANAGER_WWAN_HARDWARE_ENABLED "wwan-hardware-enabled" +#define NM_MANAGER_WIMAX_ENABLED "wimax-enabled" +#define NM_MANAGER_WIMAX_HARDWARE_ENABLED "wimax-hardware-enabled" +#define NM_MANAGER_ACTIVE_CONNECTIONS "active-connections" +#define NM_MANAGER_CONNECTIVITY "connectivity" +#define NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE "connectivity-check-available" +#define NM_MANAGER_CONNECTIVITY_CHECK_ENABLED "connectivity-check-enabled" +#define NM_MANAGER_CONNECTIVITY_CHECK_URI "connectivity-check-uri" +#define NM_MANAGER_PRIMARY_CONNECTION "primary-connection" +#define NM_MANAGER_PRIMARY_CONNECTION_TYPE "primary-connection-type" +#define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection" +#define NM_MANAGER_DEVICES "devices" +#define NM_MANAGER_METERED "metered" +#define NM_MANAGER_GLOBAL_DNS_CONFIGURATION "global-dns-configuration" +#define NM_MANAGER_ALL_DEVICES "all-devices" +#define NM_MANAGER_CHECKPOINTS "checkpoints" + +/* Not exported */ +#define NM_MANAGER_SLEEPING "sleeping" + +/* Signals */ +#define NM_MANAGER_DEVICE_ADDED "device-added" +#define NM_MANAGER_DEVICE_REMOVED "device-removed" +#define NM_MANAGER_DEVICE_IFINDEX_CHANGED "device-ifindex-changed" +#define NM_MANAGER_USER_PERMISSIONS_CHANGED "user-permissions-changed" + +#define NM_MANAGER_ACTIVE_CONNECTION_ADDED "active-connection-added" +#define NM_MANAGER_ACTIVE_CONNECTION_REMOVED "active-connection-removed" +#define NM_MANAGER_CONFIGURE_QUIT "configure-quit" +#define NM_MANAGER_INTERNAL_DEVICE_ADDED "internal-device-added" +#define NM_MANAGER_INTERNAL_DEVICE_REMOVED "internal-device-removed" + +GType nm_manager_get_type(void); + +/* nm_manager_setup() should only be used by main.c */ +NMManager *nm_manager_setup(void); + +NMManager *nm_manager_get(void); +#define NM_MANAGER_GET (nm_manager_get()) + +gboolean nm_manager_start(NMManager *manager, GError **error); +void nm_manager_stop(NMManager *manager); +NMState nm_manager_get_state(NMManager *manager); + +const CList *nm_manager_get_active_connections(NMManager *manager); + +/* From least recently activated */ +#define nm_manager_for_each_active_connection(manager, iter, tmp_list) \ + for (tmp_list = nm_manager_get_active_connections(manager), \ + iter = c_list_entry(tmp_list->next, NMActiveConnection, active_connections_lst); \ + ({ \ + const gboolean _has_next = (&iter->active_connections_lst != tmp_list); \ + \ + if (!_has_next) \ + iter = NULL; \ + _has_next; \ + }); \ + iter = c_list_entry(iter->active_connections_lst.next, \ + NMActiveConnection, \ + active_connections_lst)) + +/* From most recently activated */ +#define nm_manager_for_each_active_connection_prev(manager, iter, tmp_list) \ + for (tmp_list = nm_manager_get_active_connections(manager), \ + iter = c_list_entry(tmp_list->prev, NMActiveConnection, active_connections_lst); \ + ({ \ + const gboolean _has_prev = (&iter->active_connections_lst != tmp_list); \ + \ + if (!_has_prev) \ + iter = NULL; \ + _has_prev; \ + }); \ + iter = c_list_entry(iter->active_connections_lst.prev, \ + NMActiveConnection, \ + active_connections_lst)) + +/* From least recently activated */ +#define nm_manager_for_each_active_connection_safe(manager, iter, tmp_list, iter_safe) \ + for (tmp_list = nm_manager_get_active_connections(manager), iter_safe = tmp_list->next; ({ \ + if (iter_safe != tmp_list) { \ + iter = c_list_entry(iter_safe, NMActiveConnection, active_connections_lst); \ + iter_safe = iter_safe->next; \ + } else \ + iter = NULL; \ + (iter != NULL); \ + });) + +NMSettingsConnection **nm_manager_get_activatable_connections(NMManager *manager, + gboolean for_auto_activation, + gboolean sort, + guint * out_len); + +void nm_manager_write_device_state_all(NMManager *manager); +gboolean nm_manager_write_device_state(NMManager *manager, NMDevice *device, int *out_ifindex); + +/* Device handling */ + +const CList *nm_manager_get_devices(NMManager *manager); + +#define nm_manager_for_each_device(manager, iter, tmp_list) \ + for (tmp_list = nm_manager_get_devices(manager), \ + iter = c_list_entry(tmp_list->next, NMDevice, devices_lst); \ + ({ \ + const gboolean _has_next = (&iter->devices_lst != tmp_list); \ + \ + if (!_has_next) \ + iter = NULL; \ + _has_next; \ + }); \ + iter = c_list_entry(iter->devices_lst.next, NMDevice, devices_lst)) + +#define nm_manager_for_each_device_safe(manager, iter, tmp_list, iter_safe) \ + for (tmp_list = nm_manager_get_devices(manager), iter_safe = tmp_list->next; ({ \ + if (iter_safe != tmp_list) { \ + iter = c_list_entry(iter_safe, NMDevice, devices_lst); \ + iter_safe = iter_safe->next; \ + } else \ + iter = NULL; \ + (iter != NULL); \ + });) + +NMDevice *nm_manager_get_device_by_ifindex(NMManager *manager, int ifindex); +NMDevice *nm_manager_get_device_by_path(NMManager *manager, const char *path); + +guint32 +nm_manager_device_route_metric_reserve(NMManager *self, int ifindex, NMDeviceType device_type); + +void nm_manager_device_route_metric_clear(NMManager *self, int ifindex); + +char *nm_manager_get_connection_iface(NMManager * self, + NMConnection *connection, + NMDevice ** out_parent, + const char ** out_parent_spec, + GError ** error); + +const char *nm_manager_iface_for_uuid(NMManager *self, const char *uuid); + +NMActiveConnection *nm_manager_activate_connection(NMManager * manager, + NMSettingsConnection * connection, + NMConnection * applied_connection, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error); + +gboolean nm_manager_deactivate_connection(NMManager * manager, + NMActiveConnection *active, + NMDeviceStateReason reason, + GError ** error); + +void nm_manager_set_capability(NMManager *self, NMCapability cap); +void nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device); + +NMDevice *nm_manager_get_device(NMManager *self, const char *ifname, NMDeviceType device_type); +gboolean nm_manager_remove_device(NMManager *self, const char *ifname, NMDeviceType device_type); + +void nm_manager_dbus_set_property_handle(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusPropertyInfoExtended * property_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * value, + gpointer user_data); + +NMMetered nm_manager_get_metered(NMManager *self); + +void nm_manager_notify_device_availability_maybe_changed(NMManager *self); + +/*****************************************************************************/ + +void nm_manager_device_auth_request(NMManager * self, + NMDevice * device, + GDBusMethodInvocation * context, + NMConnection * connection, + const char * permission, + gboolean allow_interaction, + GCancellable * cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data); + +void nm_manager_unblock_failed_ovs_interfaces(NMManager *self); + +#endif /* __NETWORKMANAGER_MANAGER_H__ */ diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c new file mode 100644 index 0000000..9f62643 --- /dev/null +++ b/src/core/nm-netns.c @@ -0,0 +1,493 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-netns.h" + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-glib-aux/nm-c-list.h" + +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-l3cfg.h" +#include "platform/nm-platform.h" +#include "nm-platform/nmp-netns.h" +#include "platform/nmp-rules-manager.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, ); + +typedef struct { + NMNetns * _self_signal_user_data; + NMPlatform * platform; + NMPNetns * platform_netns; + NMPRulesManager *rules_manager; + GHashTable * l3cfgs; + GHashTable * shared_ips; + CList l3cfg_signal_pending_lst_head; + guint signal_pending_idle_id; +} NMNetnsPrivate; + +struct _NMNetns { + GObject parent; + NMNetnsPrivate _priv; +}; + +struct _NMNetnsClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMNetns, nm_netns, G_TYPE_OBJECT); + +#define NM_NETNS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMNetns, NM_IS_NETNS) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG_PREFIX_NAME "netns" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "netns[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS); + +/*****************************************************************************/ + +NMPNetns * +nm_netns_get_platform_netns(NMNetns *self) +{ + return NM_NETNS_GET_PRIVATE(self)->platform_netns; +} + +NMPlatform * +nm_netns_get_platform(NMNetns *self) +{ + return NM_NETNS_GET_PRIVATE(self)->platform; +} + +NMPRulesManager * +nm_netns_get_rules_manager(NMNetns *self) +{ + return NM_NETNS_GET_PRIVATE(self)->rules_manager; +} + +NMDedupMultiIndex * +nm_netns_get_multi_idx(NMNetns *self) +{ + return nm_platform_get_multi_idx(NM_NETNS_GET_PRIVATE(self)->platform); +} + +/*****************************************************************************/ + +typedef struct { + int ifindex; + guint32 signal_pending_obj_type_flags; + NML3Cfg *l3cfg; + CList signal_pending_lst; +} L3CfgData; + +static void +_l3cfg_data_free(gpointer ptr) +{ + L3CfgData *l3cfg_data = ptr; + + c_list_unlink_stale(&l3cfg_data->signal_pending_lst); + + nm_g_slice_free(l3cfg_data); +} + +static void +_l3cfg_weak_notify(gpointer data, GObject *where_the_object_was) +{ + NMNetns * self = NM_NETNS(data); + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(data); + NML3Cfg * l3cfg = NM_L3CFG(where_the_object_was); + int ifindex = nm_l3cfg_get_ifindex(l3cfg); + + if (!g_hash_table_remove(priv->l3cfgs, &ifindex)) + nm_assert_not_reached(); + + if (NM_UNLIKELY(g_hash_table_size(priv->l3cfgs) == 0)) + g_object_unref(self); +} + +NML3Cfg * +nm_netns_get_l3cfg(NMNetns *self, int ifindex) +{ + NMNetnsPrivate *priv; + + g_return_val_if_fail(NM_IS_NETNS(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + priv = NM_NETNS_GET_PRIVATE(self); + + return g_hash_table_lookup(priv->l3cfgs, &ifindex); +} + +NML3Cfg * +nm_netns_access_l3cfg(NMNetns *self, int ifindex) +{ + NMNetnsPrivate *priv; + L3CfgData * l3cfg_data; + + g_return_val_if_fail(NM_IS_NETNS(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + priv = NM_NETNS_GET_PRIVATE(self); + + l3cfg_data = g_hash_table_lookup(priv->l3cfgs, &ifindex); + + if (l3cfg_data) { + nm_log_trace(LOGD_CORE, + "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT ",ifindex=%d] %s", + NM_HASH_OBFUSCATE_PTR(l3cfg_data->l3cfg), + ifindex, + "referenced"); + return g_object_ref(l3cfg_data->l3cfg); + } + + l3cfg_data = g_slice_new(L3CfgData); + *l3cfg_data = (L3CfgData){ + .ifindex = ifindex, + .l3cfg = nm_l3cfg_new(self, ifindex), + .signal_pending_lst = C_LIST_INIT(l3cfg_data->signal_pending_lst), + }; + + if (!g_hash_table_add(priv->l3cfgs, l3cfg_data)) + nm_assert_not_reached(); + + if (NM_UNLIKELY(g_hash_table_size(priv->l3cfgs) == 1)) + g_object_ref(self); + + g_object_weak_ref(G_OBJECT(l3cfg_data->l3cfg), _l3cfg_weak_notify, self); + + /* Transfer ownership! We keep only a weak ref. */ + return l3cfg_data->l3cfg; +} + +/*****************************************************************************/ + +static gboolean +_platform_signal_on_idle_cb(gpointer user_data) +{ + gs_unref_object NMNetns *self = g_object_ref(NM_NETNS(user_data)); + NMNetnsPrivate * priv = NM_NETNS_GET_PRIVATE(self); + L3CfgData * l3cfg_data; + CList work_list; + + priv->signal_pending_idle_id = 0; + + /* we emit all queued signals together. However, we don't want to hook the + * main loop for longer than the currently queued elements. + * + * If we catch more change events, they will be queued and processed by a future + * idle handler. + * + * Hence, move the list to a temporary list. Isn't CList great? */ + + c_list_init(&work_list); + c_list_splice(&work_list, &priv->l3cfg_signal_pending_lst_head); + + while ((l3cfg_data = c_list_first_entry(&work_list, L3CfgData, signal_pending_lst))) { + nm_assert(NM_IS_L3CFG(l3cfg_data->l3cfg)); + c_list_unlink(&l3cfg_data->signal_pending_lst); + _nm_l3cfg_notify_platform_change_on_idle( + l3cfg_data->l3cfg, + nm_steal_int(&l3cfg_data->signal_pending_obj_type_flags)); + } + + return G_SOURCE_REMOVE; +} + +static void +_platform_signal_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + gconstpointer platform_object, + int change_type_i, + NMNetns ** p_self) +{ + NMNetns * self = NM_NETNS(*p_self); + NMNetnsPrivate * priv = NM_NETNS_GET_PRIVATE(self); + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + L3CfgData * l3cfg_data; + + l3cfg_data = g_hash_table_lookup(priv->l3cfgs, &ifindex); + if (!l3cfg_data) + return; + + l3cfg_data->signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type); + + if (c_list_is_empty(&l3cfg_data->signal_pending_lst)) { + c_list_link_tail(&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst); + if (priv->signal_pending_idle_id == 0) + priv->signal_pending_idle_id = g_idle_add(_platform_signal_on_idle_cb, self); + } + + _nm_l3cfg_notify_platform_change(l3cfg_data->l3cfg, + change_type, + NMP_OBJECT_UP_CAST(platform_object)); +} + +/*****************************************************************************/ + +NMNetnsSharedIPHandle * +nm_netns_shared_ip_reserve(NMNetns *self) +{ + NMNetnsPrivate * priv; + NMNetnsSharedIPHandle *handle; + const in_addr_t addr_start = ntohl(0x0a2a0001u); /* 10.42.0.1 */ + in_addr_t addr; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + /* Find an unused address in the 10.42.x.x range */ + + g_return_val_if_fail(NM_IS_NETNS(self), NULL); + + priv = NM_NETNS_GET_PRIVATE(self); + + if (!priv->shared_ips) { + addr = addr_start; + priv->shared_ips = g_hash_table_new(nm_puint32_hash, nm_puint32_equals); + g_object_ref(self); + } else { + guint32 count; + + nm_assert(g_hash_table_size(priv->shared_ips) > 0); + + count = 0u; + for (;;) { + addr = addr_start + htonl(count << 8u); + + handle = g_hash_table_lookup(priv->shared_ips, &addr); + if (!handle) + break; + + count++; + + if (count > 0xFFu) { + if (handle->_ref_count == 1) { + _LOGE("shared-ip4: ran out of shared IP addresses. Reuse %s/24", + _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + } else { + _LOGD("shared-ip4: reserved IP address range %s/24 (duplicate)", + _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + } + handle->_ref_count++; + return handle; + } + } + } + + handle = g_slice_new(NMNetnsSharedIPHandle); + *handle = (NMNetnsSharedIPHandle){ + .addr = addr, + ._ref_count = 1, + ._self = self, + }; + + g_hash_table_add(priv->shared_ips, handle); + + _LOGD("shared-ip4: reserved IP address range %s/24", + _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + return handle; +} + +void +nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle) +{ + NMNetns * self; + NMNetnsPrivate *priv; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_if_fail(handle); + + self = handle->_self; + + g_return_if_fail(NM_IS_NETNS(self)); + + priv = NM_NETNS_GET_PRIVATE(self); + + nm_assert(handle->_ref_count > 0); + nm_assert(handle == nm_g_hash_table_lookup(priv->shared_ips, handle)); + + if (handle->_ref_count > 1) { + nm_assert(handle->addr == ntohl(0x0A2AFF01u)); /* 10.42.255.1 */ + handle->_ref_count--; + _LOGD("shared-ip4: release IP address range %s/24 (%d more references held)", + _nm_utils_inet4_ntop(handle->addr, sbuf_addr), + handle->_ref_count); + return; + } + + if (!g_hash_table_remove(priv->shared_ips, handle)) + nm_assert_not_reached(); + + if (g_hash_table_size(priv->shared_ips) == 0) { + nm_clear_pointer(&priv->shared_ips, g_hash_table_unref); + g_object_unref(self); + } + + _LOGD("shared-ip4: release IP address range %s/24", + _nm_utils_inet4_ntop(handle->addr, sbuf_addr)); + + handle->_self = NULL; + nm_g_slice_free(handle); +} + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMNetns * self = NM_NETNS(object); + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_PLATFORM: + /* construct-only */ + priv->platform = g_value_get_object(value) ?: NM_PLATFORM_GET; + if (!priv->platform) + g_return_if_reached(); + g_object_ref(priv->platform); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_netns_init(NMNetns *self) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + priv->_self_signal_user_data = self; + c_list_init(&priv->l3cfg_signal_pending_lst_head); +} + +static void +constructed(GObject *object) +{ + NMNetns * self = NM_NETNS(object); + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + if (!priv->platform) + g_return_if_reached(); + + priv->l3cfgs = g_hash_table_new_full(nm_pint_hash, nm_pint_equals, _l3cfg_data_free, NULL); + + priv->platform_netns = nm_platform_netns_get(priv->platform); + + priv->rules_manager = nmp_rules_manager_new(priv->platform); + + /* Weakly track the default rules with a dummy user-tag. These + * rules are always weekly tracked... */ + nmp_rules_manager_track_default(priv->rules_manager, + AF_UNSPEC, + 0, + nm_netns_parent_class /* static dummy user-tag */); + + /* Also weakly track all existing rules. These were added before NetworkManager + * starts, so they are probably none of NetworkManager's business. + * + * However note that during service restart, devices may stay up and rules kept. + * That means, after restart such rules may have been added by a previous run + * of NetworkManager, we just don't know. + * + * For that reason, whenever we will touch such rules later one, we make them + * fully owned and no longer weekly tracked. See %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. */ + nmp_rules_manager_track_from_platform(priv->rules_manager, + NULL, + AF_UNSPEC, + 0, + NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG); + + G_OBJECT_CLASS(nm_netns_parent_class)->constructed(object); + + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(_platform_signal_cb), + &priv->_self_signal_user_data); + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + G_CALLBACK(_platform_signal_cb), + &priv->_self_signal_user_data); + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + G_CALLBACK(_platform_signal_cb), + &priv->_self_signal_user_data); + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + G_CALLBACK(_platform_signal_cb), + &priv->_self_signal_user_data); + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK(_platform_signal_cb), + &priv->_self_signal_user_data); +} + +NMNetns * +nm_netns_new(NMPlatform *platform) +{ + return g_object_new(NM_TYPE_NETNS, NM_NETNS_PLATFORM, platform, NULL); +} + +static void +dispose(GObject *object) +{ + NMNetns * self = NM_NETNS(object); + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0); + nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head)); + nm_assert(!priv->shared_ips); + + nm_clear_g_source(&priv->signal_pending_idle_id); + + if (priv->platform) + g_signal_handlers_disconnect_by_data(priv->platform, &priv->_self_signal_user_data); + + g_clear_object(&priv->platform); + nm_clear_pointer(&priv->l3cfgs, g_hash_table_unref); + + nm_clear_pointer(&priv->rules_manager, nmp_rules_manager_unref); + + G_OBJECT_CLASS(nm_netns_parent_class)->dispose(object); +} + +static void +nm_netns_class_init(NMNetnsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->dispose = dispose; + + obj_properties[PROP_PLATFORM] = + g_param_spec_object(NM_NETNS_PLATFORM, + "", + "", + NM_TYPE_PLATFORM, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h new file mode 100644 index 0000000..aab3b52 --- /dev/null +++ b/src/core/nm-netns.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_NETNS_H__ +#define __NM_NETNS_H__ + +#include "nm-platform/nmp-base.h" + +#define NM_TYPE_NETNS (nm_netns_get_type()) +#define NM_NETNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_NETNS, NMNetns)) +#define NM_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_NETNS, NMNetnsClass)) +#define NM_IS_NETNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_NETNS)) +#define NM_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_NETNS)) +#define NM_NETNS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_NETNS, NMNetnsClass)) + +#define NM_NETNS_PLATFORM "platform" + +typedef struct _NMNetnsClass NMNetnsClass; + +GType nm_netns_get_type(void); + +NMNetns *nm_netns_get(void); +NMNetns *nm_netns_new(NMPlatform *platform); + +NMPlatform *nm_netns_get_platform(NMNetns *self); +NMPNetns * nm_netns_get_platform_netns(NMNetns *self); + +struct _NMPRulesManager *nm_netns_get_rules_manager(NMNetns *self); + +struct _NMDedupMultiIndex *nm_netns_get_multi_idx(NMNetns *self); + +#define NM_NETNS_GET (nm_netns_get()) + +NML3Cfg *nm_netns_get_l3cfg(NMNetns *self, int ifindex); + +NML3Cfg *nm_netns_access_l3cfg(NMNetns *netns, int ifindex); + +/*****************************************************************************/ + +typedef struct { + in_addr_t addr; + int _ref_count; + NMNetns * _self; +} NMNetnsSharedIPHandle; + +NMNetnsSharedIPHandle *nm_netns_shared_ip_reserve(NMNetns *self); + +void nm_netns_shared_ip_release(NMNetnsSharedIPHandle *handle); + +#endif /* __NM_NETNS_H__ */ diff --git a/src/core/nm-pacrunner-manager.c b/src/core/nm-pacrunner-manager.c new file mode 100644 index 0000000..5055111 --- /dev/null +++ b/src/core/nm-pacrunner-manager.c @@ -0,0 +1,591 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Atul Anand . + */ + +#include "nm-default.h" + +#include "nm-pacrunner-manager.h" + +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-dbus-manager.h" +#include "nm-proxy-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "c-list/src/c-list.h" +#include "nm-glib-aux/nm-dbus-aux.h" + +#define PACRUNNER_DBUS_SERVICE "org.pacrunner" +#define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager" +#define PACRUNNER_DBUS_PATH "/org/pacrunner/manager" + +/*****************************************************************************/ + +struct _NMPacrunnerConfId { + CList conf_id_lst; + + NMPacrunnerManager *self; + + GVariant *parameters; + + char * path; + guint64 log_id; + guint refcount; +}; + +typedef struct { + GDBusConnection *dbus_connection; + GCancellable * cancellable; + CList conf_id_lst_head; + guint64 log_id_counter; + guint name_owner_changed_id; + bool dbus_initied : 1; + bool has_name_owner : 1; + bool try_start_blocked : 1; +} NMPacrunnerManagerPrivate; + +struct _NMPacrunnerManager { + GObject parent; + NMPacrunnerManagerPrivate _priv; +}; + +struct _NMPacrunnerManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMPacrunnerManager, nm_pacrunner_manager, G_TYPE_OBJECT) + +#define NM_PACRUNNER_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMPacrunnerManager, NM_IS_PACRUNNER_MANAGER) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMPacrunnerManager, nm_pacrunner_manager_get, NM_TYPE_PACRUNNER_MANAGER); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PROXY +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "pacrunner", __VA_ARGS__) + +#define _NMLOG2_PREFIX_NAME "pacrunner" +#define _NMLOG2(level, conf_id, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s%" G_GUINT64_FORMAT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG2_PREFIX_NAME ": call[", \ + (conf_id)->log_id _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _call_destroy_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + const char * path, + gboolean verbose_log); + +/*****************************************************************************/ + +static NMPacrunnerConfId * +conf_id_ref(NMPacrunnerConfId *conf_id) +{ + nm_assert(conf_id); + nm_assert(conf_id->refcount > 0); + + conf_id->refcount++; + return conf_id; +} + +static void +conf_id_unref(NMPacrunnerConfId *conf_id) +{ + nm_assert(conf_id); + nm_assert(conf_id->refcount > 0); + + if (conf_id->refcount == 1) { + g_variant_unref(conf_id->parameters); + g_free(conf_id->path); + c_list_unlink_stale(&conf_id->conf_id_lst); + g_object_unref(conf_id->self); + g_slice_free(NMPacrunnerConfId, conf_id); + } else + conf_id->refcount--; +} + +NM_AUTO_DEFINE_FCN0(NMPacrunnerConfId *, _nm_auto_unref_conf_id, conf_id_unref); +#define nm_auto_unref_conf_id nm_auto(_nm_auto_unref_conf_id) + +/*****************************************************************************/ + +static void +get_ip_domains(GPtrArray *domains, NMIPConfig *ip_config) +{ + NMDedupMultiIter ipconf_iter; + char * cidr; + guint i, num; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + int addr_family; + const NMPlatformIPAddress *address; + const NMPlatformIPRoute * routes; + + if (!ip_config) + return; + + addr_family = nm_ip_config_get_addr_family(ip_config); + + num = nm_ip_config_get_num_searches(ip_config); + for (i = 0; i < num; i++) + g_ptr_array_add(domains, g_strdup(nm_ip_config_get_search(ip_config, i))); + + num = nm_ip_config_get_num_domains(ip_config); + for (i = 0; i < num; i++) + g_ptr_array_add(domains, g_strdup(nm_ip_config_get_domain(ip_config, i))); + + nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) { + cidr = g_strdup_printf("%s/%u", + nm_utils_inet_ntop(addr_family, address->address_ptr, sbuf), + address->plen); + g_ptr_array_add(domains, cidr); + } + + nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &routes) { + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(routes)) + continue; + cidr = g_strdup_printf("%s/%u", + nm_utils_inet_ntop(addr_family, routes->network_ptr, sbuf), + routes->plen); + g_ptr_array_add(domains, cidr); + } +} + +static GVariant * +_make_request_create_proxy_configuration(NMProxyConfig *proxy_config, + const char * iface, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config) +{ + GVariantBuilder builder; + NMProxyConfigMethod method; + const char * pac_url; + const char * pac_script; + + nm_assert(NM_IS_PROXY_CONFIG(proxy_config)); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + if (iface) { + g_variant_builder_add(&builder, "{sv}", "Interface", g_variant_new_string(iface)); + } + + method = nm_proxy_config_get_method(proxy_config); + switch (method) { + case NM_PROXY_CONFIG_METHOD_AUTO: + g_variant_builder_add(&builder, "{sv}", "Method", g_variant_new_string("auto")); + + pac_url = nm_proxy_config_get_pac_url(proxy_config); + if (pac_url) { + g_variant_builder_add(&builder, "{sv}", "URL", g_variant_new_string(pac_url)); + } + + pac_script = nm_proxy_config_get_pac_script(proxy_config); + if (pac_script) { + g_variant_builder_add(&builder, "{sv}", "Script", g_variant_new_string(pac_script)); + } + break; + case NM_PROXY_CONFIG_METHOD_NONE: + g_variant_builder_add(&builder, "{sv}", "Method", g_variant_new_string("direct")); + break; + } + + g_variant_builder_add(&builder, + "{sv}", + "BrowserOnly", + g_variant_new_boolean(nm_proxy_config_get_browser_only(proxy_config))); + + if (ip4_config || ip6_config) { + gs_unref_ptrarray GPtrArray *domains = NULL; + + domains = g_ptr_array_new_with_free_func(g_free); + + get_ip_domains(domains, NM_IP_CONFIG_CAST(ip4_config)); + get_ip_domains(domains, NM_IP_CONFIG_CAST(ip6_config)); + + if (domains->len > 0) { + g_variant_builder_add( + &builder, + "{sv}", + "Domains", + g_variant_new_strv((const char *const *) domains->pdata, domains->len)); + } + } + + return g_variant_new("(a{sv})", &builder); +} + +/*****************************************************************************/ + +static void +_call_destroy_proxy_configuration_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (!ret) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOG2T(conf_id, "destroy proxy configuration: failed with %s", error->message); + else + _LOG2T(conf_id, "destroy proxy configuration: cancelled"); + return; + } + _LOG2T(conf_id, "destroy proxy configuration: success"); +} + +static void +_call_create_proxy_configuration_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data; + NMPacrunnerManager * self = NM_PACRUNNER_MANAGER(conf_id->self); + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *variant = NULL; + const char * path = NULL; + + nm_assert(!conf_id->path); + + variant = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + if (!variant) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOG2T(conf_id, "create proxy configuration failed: %s", error->message); + else + _LOG2T(conf_id, "create proxy configuration cancelled"); + return; + } + + g_variant_get(variant, "(&o)", &path); + + if (c_list_is_empty(&conf_id->conf_id_lst)) { + _LOG2T(conf_id, + "create proxy configuration succeeded (%s), but destroy it right away", + path); + _call_destroy_proxy_configuration(self, conf_id, path, FALSE); + } else { + _LOG2T(conf_id, "create proxy configuration succeeded (%s)", path); + conf_id->path = g_strdup(path); + } +} + +static void +_call_destroy_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + const char * path, + gboolean verbose_log) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (verbose_log) + _LOG2T(conf_id, "destroy proxy configuration %s...", path); + + g_dbus_connection_call(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + PACRUNNER_DBUS_PATH, + PACRUNNER_DBUS_INTERFACE, + "DestroyProxyConfiguration", + g_variant_new("(o)", path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + NM_SHUTDOWN_TIMEOUT_MS, + priv->cancellable, + _call_destroy_proxy_configuration_cb, + conf_id_ref(conf_id)); +} + +static void +_call_create_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + gboolean verbose_log) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (verbose_log) + _LOG2T(conf_id, "create proxy configuration..."); + + g_dbus_connection_call(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + PACRUNNER_DBUS_PATH, + PACRUNNER_DBUS_INTERFACE, + "CreateProxyConfiguration", + conf_id->parameters, + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + NM_SHUTDOWN_TIMEOUT_MS, + priv->cancellable, + _call_create_proxy_configuration_cb, + conf_id_ref(conf_id)); +} + +static gboolean +_try_start_service_by_name(NMPacrunnerManager *self) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (priv->try_start_blocked || !priv->dbus_initied) + return FALSE; + + _LOGD("try D-Bus activating pacrunner..."); + priv->try_start_blocked = TRUE; + nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + -1, + NULL, + NULL, + NULL); + return TRUE; +} + +/*****************************************************************************/ + +/** + * nm_pacrunner_manager_add: + * @self: the #NMPacrunnerManager + * @proxy_config: proxy config of the connection + * @iface: the iface for the connection or %NULL + * @ip4_config: IP4 config of the connection to extract domain info from + * @ip6_config: IP6 config of the connection to extract domain info from + * + * Returns: a #NMPacrunnerConfId id. The function cannot + * fail and always returns a non NULL pointer. The conf-id may + * be used to remove the configuration later via nm_pacrunner_manager_remove(). + * Note that the conf-id keeps the @self instance alive. + */ +NMPacrunnerConfId * +nm_pacrunner_manager_add(NMPacrunnerManager *self, + NMProxyConfig * proxy_config, + const char * iface, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config) +{ + NMPacrunnerManagerPrivate *priv; + NMPacrunnerConfId * conf_id; + gs_free char * log_msg = NULL; + + g_return_val_if_fail(NM_IS_PACRUNNER_MANAGER(self), NULL); + g_return_val_if_fail(proxy_config, NULL); + + priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + conf_id = g_slice_new(NMPacrunnerConfId); + *conf_id = (NMPacrunnerConfId){ + .log_id = ++priv->log_id_counter, + .refcount = 1, + .self = g_object_ref(self), + .parameters = g_variant_ref_sink( + _make_request_create_proxy_configuration(proxy_config, iface, ip4_config, ip6_config)), + }; + c_list_link_tail(&priv->conf_id_lst_head, &conf_id->conf_id_lst); + + if (!priv->has_name_owner) { + _LOG2T(conf_id, + "add config: %s (%s)", + (log_msg = g_variant_print(conf_id->parameters, FALSE)), + "pacrunner D-Bus service not running"); + _try_start_service_by_name(self); + } else { + _LOG2T(conf_id, + "add config: %s (%s)", + (log_msg = g_variant_print(conf_id->parameters, FALSE)), + "create proxy configuration"); + _call_create_proxy_configuration(self, conf_id, FALSE); + } + + return conf_id; +} + +/** + * nm_pacrunner_manager_remove: + * @conf_id: the conf id obtained from nm_pacrunner_manager_add() + */ +void +nm_pacrunner_manager_remove(NMPacrunnerConfId *conf_id) +{ + _nm_unused nm_auto_unref_conf_id NMPacrunnerConfId *conf_id_free = conf_id; + NMPacrunnerManager * self; + NMPacrunnerManagerPrivate * priv; + + g_return_if_fail(conf_id); + + self = conf_id->self; + + g_return_if_fail(NM_IS_PACRUNNER_MANAGER(self)); + + priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + _LOG2T(conf_id, "removing..."); + + nm_assert(c_list_contains(&priv->conf_id_lst_head, &conf_id->conf_id_lst)); + + c_list_unlink(&conf_id->conf_id_lst); + + if (!conf_id->path) { + /* There is no ID to destroy the configuration. + * + * That can happen because: + * + * - pacrunner D-Bus service is not running (no name owner) and we didn't call CreateProxyConfiguration. + * - CreateProxyConfiguration failed. + * - CreateProxyConfiguration is in progress. + * + * In all cases there is nothing to do. Note that if CreateProxyConfiguration is in progress + * it has a reference on the conf-id and it will automatically destroy the configuration + * when it completes. + */ + return; + } + + _call_destroy_proxy_configuration(self, conf_id, conf_id->path, TRUE); +} + +gboolean +nm_pacrunner_manager_remove_clear(NMPacrunnerConfId **p_conf_id) +{ + g_return_val_if_fail(p_conf_id, FALSE); + + if (!*p_conf_id) + return FALSE; + nm_pacrunner_manager_remove(g_steal_pointer(p_conf_id)); + return TRUE; +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMPacrunnerManager *self, const char *name_owner) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + NMPacrunnerConfId * conf_id; + gboolean has_name_owner; + + has_name_owner = (name_owner && name_owner[0]); + + if (priv->dbus_initied && priv->has_name_owner == has_name_owner) + return; + + priv->has_name_owner = has_name_owner; + + nm_clear_g_cancellable(&priv->cancellable); + + if (has_name_owner) { + priv->dbus_initied = TRUE; + priv->try_start_blocked = FALSE; + _LOGD("pacrunner appeared on D-Bus (%s)", name_owner); + priv->cancellable = g_cancellable_new(); + c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst) + _call_create_proxy_configuration(self, conf_id, TRUE); + } else { + if (!priv->dbus_initied) { + priv->dbus_initied = TRUE; + nm_assert(!priv->try_start_blocked); + _LOGD("pacrunner not on D-Bus"); + } else + _LOGD("pacrunner disappeared from D-Bus"); + if (!c_list_is_empty(&priv->conf_id_lst_head)) { + c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst) + nm_clear_g_free(&conf_id->path); + _try_start_service_by_name(self); + } + } +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + const char *new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(user_data, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + name_owner_changed(user_data, name_owner); +} + +/*****************************************************************************/ + +static void +nm_pacrunner_manager_init(NMPacrunnerManager *self) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + c_list_init(&priv->conf_id_lst_head); + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!priv->dbus_connection) { + _LOGD("no D-Bus connection to talk to pacrunner"); + return; + } + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + priv->cancellable = g_cancellable_new(); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + -1, + priv->cancellable, + get_name_owner_cb, + self); +} + +static void +dispose(GObject *object) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(object); + + nm_assert(c_list_is_empty(&priv->conf_id_lst_head)); + + /* we cancel all pending operations. Note that pacrunner automatically + * removes all configuration once NetworkManager disconnects from + * the bus -- which happens soon after we destroy the pacrunner manager. + */ + nm_clear_g_cancellable(&priv->cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + g_clear_object(&priv->dbus_connection); + + G_OBJECT_CLASS(nm_pacrunner_manager_parent_class)->dispose(object); +} + +static void +nm_pacrunner_manager_class_init(NMPacrunnerManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; +} diff --git a/src/core/nm-pacrunner-manager.h b/src/core/nm-pacrunner-manager.h new file mode 100644 index 0000000..fd40f12 --- /dev/null +++ b/src/core/nm-pacrunner-manager.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Atul Anand . + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_PACRUNNER_MANAGER_H__ +#define __NETWORKMANAGER_PACRUNNER_MANAGER_H__ + +#define NM_TYPE_PACRUNNER_MANAGER (nm_pacrunner_manager_get_type()) +#define NM_PACRUNNER_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PACRUNNER_MANAGER, NMPacrunnerManager)) +#define NM_PACRUNNER_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PACRUNNER_MANAGER, NMPacrunnerManagerClass)) +#define NM_IS_PACRUNNER_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PACRUNNER_MANAGER)) +#define NM_IS_PACRUNNER_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PACRUNNER_MANAGER)) +#define NM_PACRUNNER_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PACRUNNER_MANAGER, NMPacrunnerManagerClass)) + +typedef struct _NMPacrunnerManagerClass NMPacrunnerManagerClass; + +typedef struct _NMPacrunnerConfId NMPacrunnerConfId; + +GType nm_pacrunner_manager_get_type(void); + +NMPacrunnerManager *nm_pacrunner_manager_get(void); + +NMPacrunnerConfId *nm_pacrunner_manager_add(NMPacrunnerManager *self, + NMProxyConfig * proxy_config, + const char * iface, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config); + +void nm_pacrunner_manager_remove(NMPacrunnerConfId *conf_id); + +gboolean nm_pacrunner_manager_remove_clear(NMPacrunnerConfId **p_conf_id); + +#endif /* __NETWORKMANAGER_PACRUNNER_MANAGER_H__ */ diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c new file mode 100644 index 0000000..98ff704 --- /dev/null +++ b/src/core/nm-policy.c @@ -0,0 +1,3022 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-policy.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "nm-act-request.h" +#include "nm-keep-alive.h" +#include "devices/nm-device.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-connection.h" +#include "platform/nm-platform.h" +#include "dns/nm-dns-manager.h" +#include "vpn/nm-vpn-manager.h" +#include "nm-auth-utils.h" +#include "nm-firewall-manager.h" +#include "nm-dispatcher.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-manager.h" +#include "settings/nm-settings.h" +#include "settings/nm-settings-connection.h" +#include "settings/nm-agent-manager.h" +#include "nm-dhcp-config.h" +#include "nm-config.h" +#include "nm-netns.h" +#include "nm-hostname-manager.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMPolicy, + PROP_MANAGER, + PROP_SETTINGS, + PROP_DEFAULT_IP4_AC, + PROP_DEFAULT_IP6_AC, + PROP_ACTIVATING_IP4_AC, + PROP_ACTIVATING_IP6_AC, ); + +typedef struct { + NMManager * manager; + NMNetns * netns; + NMFirewallManager *firewall_manager; + CList pending_activation_checks; + + NMAgentManager *agent_mgr; + + GHashTable *devices; + GHashTable *pending_active_connections; + + GSList *pending_secondaries; + + NMSettings *settings; + + NMHostnameManager *hostname_manager; + + NMActiveConnection *default_ac4, *activating_ac4; + NMActiveConnection *default_ac6, *activating_ac6; + + NMDnsManager *dns_manager; + gulong config_changed_id; + + guint reset_retries_id; /* idle handler for resetting the retries count */ + + guint schedule_activate_all_id; /* idle handler for schedule_activate_all(). */ + + NMPolicyHostnameMode hostname_mode; + char * orig_hostname; /* hostname at NM start time */ + char * cur_hostname; /* hostname we want to assign */ + char * + last_hostname; /* last hostname NM set (to detect if someone else changed it in the meanwhile) */ + + bool changing_hostname : 1; /* hostname set operation in progress */ + bool dhcp_hostname : 1; /* current hostname was set from dhcp */ + bool updating_dns : 1; + + GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */ +} NMPolicyPrivate; + +struct _NMPolicy { + GObject parent; + NMPolicyPrivate _priv; +}; + +struct _NMPolicyClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMPolicy, nm_policy, G_TYPE_OBJECT) + +#define NM_POLICY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMPolicy, NM_IS_POLICY) + +static NMPolicy * +_PRIV_TO_SELF(NMPolicyPrivate *priv) +{ + NMPolicy *self; + + nm_assert(priv); + + self = (NMPolicy *) (((char *) priv) - G_STRUCT_OFFSET(NMPolicy, _priv)); + + nm_assert(NM_IS_POLICY(self)); + return self; +} + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "policy" +#undef _NMLOG_ENABLED +#define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain))) +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void update_system_hostname(NMPolicy *self, const char *msg); +static void schedule_activate_all(NMPolicy *self); +static void schedule_activate_check(NMPolicy *self, NMDevice *device); +static NMDevice *get_default_device(NMPolicy *self, int addr_family); + +/*****************************************************************************/ + +static void +_dns_manager_set_ip_config(NMDnsManager * dns_manager, + NMIPConfig * ip_config, + NMDnsIPConfigType ip_config_type, + NMDevice * device) +{ + if (device && nm_device_sys_iface_state_is_external(device)) { + nm_dns_manager_set_ip_config(dns_manager, ip_config, NM_DNS_IP_CONFIG_TYPE_REMOVED); + return; + } + + if (NM_IN_SET(ip_config_type, NM_DNS_IP_CONFIG_TYPE_DEFAULT, NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) + && device + && nm_device_get_route_metric_default(nm_device_get_device_type(device)) + == NM_VPN_ROUTE_METRIC_DEFAULT) { + /* some device types are inherently VPN. */ + ip_config_type = NM_DNS_IP_CONFIG_TYPE_VPN; + } + + nm_dns_manager_set_ip_config(dns_manager, ip_config, ip_config_type); +} + +/*****************************************************************************/ + +typedef struct { + NMPlatformIP6Address prefix; + NMDevice * device; /* The requesting ("uplink") device */ + guint64 next_subnet; /* Cache of the next subnet number to be + * assigned from this prefix */ + GHashTable * subnets; /* ifindex -> NMPlatformIP6Address */ +} IP6PrefixDelegation; + +static void +_clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data) +{ + NMPlatformIP6Address *subnet = value; + NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(key)); + + if (device) { + /* We can not remove a subnet we already started announcing. + * Just un-prefer it. */ + subnet->preferred = 0; + nm_device_use_ip6_subnet(device, subnet); + } + g_slice_free(NMPlatformIP6Address, subnet); +} + +static void +clear_ip6_prefix_delegation(gpointer data) +{ + IP6PrefixDelegation *delegation = data; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD(LOGD_IP6, + "ipv6-pd: undelegating prefix %s/%d", + _nm_utils_inet6_ntop(&delegation->prefix.address, sbuf), + delegation->prefix.plen); + + g_hash_table_foreach(delegation->subnets, _clear_ip6_subnet, NULL); + g_hash_table_destroy(delegation->subnets); +} + +static void +expire_ip6_delegations(NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + guint32 now = nm_utils_get_monotonic_timestamp_sec(); + IP6PrefixDelegation *delegation = NULL; + guint i; + + for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { + delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + if (delegation->prefix.timestamp + delegation->prefix.lifetime < now) + g_array_remove_index_fast(priv->ip6_prefix_delegations, i); + } +} + +/* + * Try to obtain a new subnet for a particular active connection from given + * delegated prefix, possibly reusing the existing subnet. + * Return value of FALSE indicates no more subnets are available from + * this prefix (and other prefix should be used -- and requested if necessary). + */ +static gboolean +ip6_subnet_from_delegation(IP6PrefixDelegation *delegation, NMDevice *device) +{ + NMPlatformIP6Address *subnet; + int ifindex = nm_device_get_ifindex(device); + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + subnet = g_hash_table_lookup(delegation->subnets, GINT_TO_POINTER(ifindex)); + if (!subnet) { + /* Check for out-of-prefixes condition. */ + if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) { + _LOGD(LOGD_IP6, + "ipv6-pd: no more prefixes in %s/%d", + _nm_utils_inet6_ntop(&delegation->prefix.address, sbuf), + delegation->prefix.plen); + return FALSE; + } + + /* Allocate a new subnet. */ + subnet = g_slice_new0(NMPlatformIP6Address); + g_hash_table_insert(delegation->subnets, GINT_TO_POINTER(ifindex), subnet); + + subnet->plen = 64; + subnet->address.s6_addr32[0] = + delegation->prefix.address.s6_addr32[0] | htonl(delegation->next_subnet >> 32); + subnet->address.s6_addr32[1] = + delegation->prefix.address.s6_addr32[1] | htonl(delegation->next_subnet); + + /* Out subnet pool management is pretty unsophisticated. We only add + * the subnets and index them by ifindex. That keeps the implementation + * simple and the dead entries make it easy to reuse the same subnet on + * subsequent activations. On the other hand they may waste the subnet + * space. */ + delegation->next_subnet++; + } + + subnet->timestamp = delegation->prefix.timestamp; + subnet->lifetime = delegation->prefix.lifetime; + subnet->preferred = delegation->prefix.preferred; + + _LOGD(LOGD_IP6, + "ipv6-pd: %s allocated from a /%d prefix on %s", + _nm_utils_inet6_ntop(&subnet->address, sbuf), + delegation->prefix.plen, + nm_device_get_iface(device)); + + nm_device_use_ip6_subnet(device, subnet); + + return TRUE; +} + +/* + * Try to obtain a subnet from each prefix delegated to given requesting + * ("uplink") device and assign it to the downlink device. + * Requests a new prefix if no subnet could be found. + */ +static void +ip6_subnet_from_device(NMPolicy *self, NMDevice *from_device, NMDevice *device) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + IP6PrefixDelegation *delegation = NULL; + gboolean got_subnet = FALSE; + guint have_prefixes = 0; + guint i; + + expire_ip6_delegations(self); + + for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { + delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + + if (delegation->device != from_device) + continue; + + if (ip6_subnet_from_delegation(delegation, device)) + got_subnet = TRUE; + have_prefixes++; + } + + if (!got_subnet) { + _LOGI(LOGD_IP6, + "ipv6-pd: none of %u prefixes of %s can be shared on %s", + have_prefixes, + nm_device_get_iface(from_device), + nm_device_get_iface(device)); + nm_device_request_ip6_prefixes(from_device, have_prefixes + 1); + } +} + +static void +ip6_remove_device_prefix_delegations(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + IP6PrefixDelegation *delegation = NULL; + guint i; + + for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { + delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + if (delegation->device == device) + g_array_remove_index_fast(priv->ip6_prefix_delegations, i); + } +} + +static void +device_ip6_prefix_delegated(NMDevice *device, NMPlatformIP6Address *prefix, gpointer user_data) +{ + NMPolicyPrivate * priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + IP6PrefixDelegation *delegation = NULL; + guint i; + const CList * tmp_list; + NMActiveConnection * ac; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGI(LOGD_IP6, + "ipv6-pd: received a prefix %s/%d from %s", + _nm_utils_inet6_ntop(&prefix->address, sbuf), + prefix->plen, + nm_device_get_iface(device)); + + expire_ip6_delegations(self); + + for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { + /* Look for an already known prefix to update. */ + delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + if (IN6_ARE_ADDR_EQUAL(&delegation->prefix.address, &prefix->address)) + break; + } + + if (i == priv->ip6_prefix_delegations->len) { + /* Allocate a delegation for new prefix. */ + g_array_set_size(priv->ip6_prefix_delegations, i + 1); + delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + delegation->subnets = g_hash_table_new(nm_direct_hash, NULL); + delegation->next_subnet = 0; + } + + delegation->device = device; + delegation->prefix = *prefix; + + /* The newly activated connections are added to the end of the list, + * so traversing it from the end makes it likely for newly + * activated connections that have no subnet assigned to be served + * first. That is a simple yet fair policy, which is good. */ + nm_manager_for_each_active_connection_prev (priv->manager, ac, tmp_list) { + NMDevice *to_device; + + to_device = nm_active_connection_get_device(ac); + if (nm_device_needs_ip6_subnet(to_device)) + ip6_subnet_from_delegation(delegation, to_device); + } +} + +static void +device_ip6_subnet_needed(NMDevice *device, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + _LOGD(LOGD_IP6, "ipv6-pd: %s needs a subnet", nm_device_get_iface(device)); + + if (!priv->default_ac6) { + /* We request the prefixes when the default IPv6 device is set. */ + _LOGI(LOGD_IP6, + "ipv6-pd: no device to obtain a subnet to share on %s from", + nm_device_get_iface(device)); + return; + } + ip6_subnet_from_device(self, get_default_device(self, AF_INET6), device); + nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6)); +} + +/*****************************************************************************/ + +static NMDevice * +get_default_device(NMPolicy *self, int addr_family) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMActiveConnection *ac; + + nm_assert_addr_family(addr_family); + + ac = (addr_family == AF_INET) ? priv->default_ac4 : priv->default_ac6; + + return ac ? nm_active_connection_get_device(ac) : NULL; +} + +static NMActiveConnection * +get_best_active_connection(NMPolicy *self, int addr_family, gboolean fully_activated) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_lst; + NMDevice * device; + guint32 best_metric = G_MAXUINT32; + gboolean best_is_fully_activated = FALSE; + NMActiveConnection *best_ac, *prev_ac; + + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + /* we prefer the current AC in case of identical metric. + * Hence, try that one first. */ + prev_ac = addr_family == AF_INET ? (fully_activated ? priv->default_ac4 : priv->activating_ac4) + : (fully_activated ? priv->default_ac6 : priv->activating_ac6); + best_ac = NULL; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + NMDeviceState state; + const NMPObject * r; + NMActiveConnection *ac; + NMConnection * connection; + guint32 metric; + gboolean is_fully_activated; + + state = nm_device_get_state(device); + if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING) + continue; + + if (nm_device_sys_iface_state_is_external(device)) + continue; + + r = nm_device_get_best_default_route(device, addr_family); + if (r) { + /* NOTE: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN, + * which means it was injected by a VPN, not added by device. + * + * In this case, is it really the best device? Why do we even need the best + * device?? */ + metric = NMP_OBJECT_CAST_IP_ROUTE(r)->metric; + is_fully_activated = TRUE; + } else if (!fully_activated && (connection = nm_device_get_applied_connection(device)) + && nm_utils_connection_has_default_route(connection, addr_family, NULL)) { + metric = nm_device_get_route_metric(device, addr_family); + is_fully_activated = FALSE; + } else + continue; + + ac = (NMActiveConnection *) nm_device_get_act_request(device); + nm_assert(ac); + + if (!best_ac || (!best_is_fully_activated && is_fully_activated) + || (metric < best_metric || (metric == best_metric && ac == prev_ac))) { + best_ac = ac; + best_metric = metric; + best_is_fully_activated = is_fully_activated; + } + } + + if (!fully_activated && best_ac && best_is_fully_activated) { + /* There's a best activating AC only if the best device + * among all activating and already-activated devices is a + * still-activating one. */ + return NULL; + } + + return best_ac; +} + +static gboolean +all_devices_not_active(NMPolicy *self) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_lst; + NMDevice * device; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + NMDeviceState state; + + state = nm_device_get_state(device); + if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING) { + continue; + } + return FALSE; + } + return TRUE; +} + +#define FALLBACK_HOSTNAME4 "localhost.localdomain" + +static void +settings_set_hostname_cb(const char *hostname, gboolean result, gpointer user_data) +{ + NMPolicy * self = NM_POLICY(user_data); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + int ret = 0; + int errsv; + + if (!result) { + _LOGT(LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\""); + ret = sethostname(hostname, strlen(hostname)); + if (ret != 0) { + errsv = errno; + _LOGW(LOGD_DNS, + "set-hostname: couldn't set the system hostname to '%s': (%d) %s", + hostname, + errsv, + nm_strerror_native(errsv)); + if (errsv == EPERM) + _LOGW( + LOGD_DNS, + "set-hostname: you should use hostnamed when systemd hardening is in effect!"); + } + } + + priv->changing_hostname = FALSE; + if (!ret) + nm_dispatcher_call_hostname(NULL, NULL, NULL); + g_object_unref(self); +} + +#define HOST_NAME_BUFSIZE (HOST_NAME_MAX + 2) + +static char * +_get_hostname(NMPolicy *self) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + char * hostname = NULL; + int errsv; + + /* If there is an in-progress hostname change, return + * the last hostname set as would be set soon... + */ + if (priv->changing_hostname) { + _LOGT(LOGD_DNS, "get-hostname: \"%s\" (last on set)", priv->last_hostname); + return g_strdup(priv->last_hostname); + } + + /* try to get the hostname via dbus... */ + if (nm_hostname_manager_get_transient_hostname(priv->hostname_manager, &hostname)) { + _LOGT(LOGD_DNS, "get-hostname: \"%s\" (from dbus)", hostname); + return hostname; + } + + /* ...or retrieve it by yourself */ + hostname = g_malloc(HOST_NAME_BUFSIZE); + if (gethostname(hostname, HOST_NAME_BUFSIZE - 1) != 0) { + errsv = errno; + _LOGT(LOGD_DNS, + "get-hostname: couldn't get the system hostname: (%d) %s", + errsv, + nm_strerror_native(errsv)); + g_free(hostname); + return NULL; + } + + /* the name may be truncated... */ + hostname[HOST_NAME_BUFSIZE - 1] = '\0'; + if (strlen(hostname) >= HOST_NAME_BUFSIZE - 1) { + _LOGT(LOGD_DNS, "get-hostname: system hostname too long: \"%s\"", hostname); + g_free(hostname); + return NULL; + } + + _LOGT(LOGD_DNS, "get-hostname: \"%s\"", hostname); + return hostname; +} + +static void +_set_hostname(NMPolicy *self, const char *new_hostname, const char *msg) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + gs_free char * old_hostname = NULL; + const char * name; + + /* The incoming hostname *can* be NULL, which will get translated to + * 'localhost.localdomain' or such in the hostname policy code, but we + * keep cur_hostname = NULL in the case because we need to know that + * there was no valid hostname to start with. + */ + + /* Update the DNS only if the hostname is actually + * going to change. + */ + if (!nm_streq0(priv->cur_hostname, new_hostname)) { + g_free(priv->cur_hostname); + priv->cur_hostname = g_strdup(new_hostname); + + /* Notify the DNS manager of the hostname change so that the domain part, if + * present, can be added to the search list. Set the @updating_dns flag + * so that dns_config_changed() doesn't try again to restart DNS lookup. + */ + priv->updating_dns = TRUE; + nm_dns_manager_set_hostname(priv->dns_manager, + priv->cur_hostname, + all_devices_not_active(self)); + priv->updating_dns = FALSE; + } + + /* Finally, set kernel hostname */ + if (!new_hostname) + name = FALLBACK_HOSTNAME4; + else if (!new_hostname[0]) { + g_warn_if_reached(); + name = FALLBACK_HOSTNAME4; + } else + name = new_hostname; + + /* Don't set the hostname if it isn't actually changing */ + if ((old_hostname = _get_hostname(self)) && (nm_streq(name, old_hostname))) { + _LOGT(LOGD_DNS, "set-hostname: hostname already set to '%s' (%s)", name, msg); + return; + } + + /* Keep track of the last set hostname */ + g_free(priv->last_hostname); + priv->last_hostname = g_strdup(name); + priv->changing_hostname = TRUE; + + _LOGI(LOGD_DNS, "set-hostname: set hostname to '%s' (%s)", name, msg); + + /* Ask NMSettings to update the transient hostname using its + * systemd-hostnamed proxy */ + nm_hostname_manager_set_transient_hostname(priv->hostname_manager, + name, + settings_set_hostname_cb, + g_object_ref(self)); +} + +typedef struct { + NMDevice *device; + int priority; + bool from_dhcp : 1; + bool from_dns : 1; + bool IS_IPv4 : 1; + bool is_default : 1; +} DeviceHostnameInfo; + +static int +device_hostname_info_compare(gconstpointer a, gconstpointer b) +{ + const DeviceHostnameInfo *info1 = a; + const DeviceHostnameInfo *info2 = b; + + NM_CMP_FIELD(info1, info2, priority); + NM_CMP_FIELD_UNSAFE(info2, info1, is_default); + + return 0; +} + +NM_CON_DEFAULT_NOP("hostname.from-dhcp"); +NM_CON_DEFAULT_NOP("hostname.from-dns-lookup"); +NM_CON_DEFAULT_NOP("hostname.only-from-default"); + +static gboolean +device_get_hostname_property_boolean(NMDevice *device, const char *name) +{ + NMSettingHostname *s_hostname; + char buf[128]; + int value; + NMTernary default_value; + + nm_assert(NM_IN_STRSET(name, + NM_SETTING_HOSTNAME_FROM_DHCP, + NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP, + NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT)); + + s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME); + + if (s_hostname) { + g_object_get(s_hostname, name, &value, NULL); + if (NM_IN_SET(value, NM_TERNARY_FALSE, NM_TERNARY_TRUE)) + return value; + } + + if (nm_streq(name, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT)) + default_value = NM_TERNARY_FALSE; + else + default_value = NM_TERNARY_TRUE; + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + nm_sprintf_buf(buf, "hostname.%s", name), + device, + NM_TERNARY_FALSE, + NM_TERNARY_TRUE, + default_value); +} + +static int +device_get_hostname_priority(NMDevice *device) +{ + NMSettingHostname *s_hostname; + int priority; + + s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME); + if (s_hostname) { + priority = nm_setting_hostname_get_priority(s_hostname); + if (priority != 0) + return priority; + } + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("hostname.priority"), + device, + G_MININT, + G_MAXINT, + 100); +} + +static GArray * +build_device_hostname_infos(NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_clist; + NMActiveConnection *ac; + GArray * array = NULL; + + nm_manager_for_each_active_connection (priv->manager, ac, tmp_clist) { + DeviceHostnameInfo *info; + NMDevice * device; + gboolean only_from_default; + gboolean is_default; + int IS_IPv4; + + device = nm_active_connection_get_device(ac); + if (!device) + continue; + + only_from_default = + device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT); + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + is_default = (ac == (IS_IPv4 ? priv->default_ac4 : priv->default_ac6)); + if (only_from_default && !is_default) + continue; + + if (!array) + array = g_array_sized_new(FALSE, FALSE, sizeof(DeviceHostnameInfo), 4); + + info = nm_g_array_append_new(array, DeviceHostnameInfo); + *info = (DeviceHostnameInfo){ + .device = device, + .priority = device_get_hostname_priority(device), + .from_dhcp = + device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_FROM_DHCP), + .from_dns = + device_get_hostname_property_boolean(device, + NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP), + .IS_IPv4 = IS_IPv4, + .is_default = is_default, + }; + } + } + + if (array && array->len > 1) { + const DeviceHostnameInfo *info0; + guint i; + + g_array_sort(array, device_hostname_info_compare); + + info0 = &g_array_index(array, DeviceHostnameInfo, 0); + if (info0->priority < 0) { + for (i = 1; i < array->len; i++) { + const DeviceHostnameInfo *info = &g_array_index(array, DeviceHostnameInfo, i); + + if (info->priority > info0->priority) { + g_array_set_size(array, i); + break; + } + } + } + } + + return array; +} + +static void +device_dns_lookup_done(NMDevice *device, gpointer user_data) +{ + NMPolicy *self = user_data; + + g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self); + + update_system_hostname(self, "lookup finished"); +} + +static void +update_system_hostname(NMPolicy *self, const char *msg) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + const char * configured_hostname; + gs_free char * temp_hostname = NULL; + const char * dhcp_hostname, *p; + gboolean external_hostname = FALSE; + NMDhcpConfig * dhcp_config; + gs_unref_array GArray *infos = NULL; + DeviceHostnameInfo * info; + guint i; + int addr_family; + + g_return_if_fail(self != NULL); + + if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_NONE) { + _LOGT(LOGD_DNS, "set-hostname: hostname is unmanaged"); + return; + } + + _LOGT(LOGD_DNS, "set-hostname: updating hostname (%s)", msg); + + /* Check if the hostname was set externally to NM, so that in that case + * we can avoid to fallback to the one we got when we started. + * Consider "not specific" hostnames as equal. */ + if ((temp_hostname = _get_hostname(self)) && !nm_streq0(temp_hostname, priv->last_hostname) + && (nm_utils_is_specific_hostname(temp_hostname) + || nm_utils_is_specific_hostname(priv->last_hostname))) { + external_hostname = TRUE; + _LOGI(LOGD_DNS, + "set-hostname: current hostname was changed outside NetworkManager: '%s'", + temp_hostname); + priv->dhcp_hostname = FALSE; + + if (!nm_utils_is_specific_hostname(temp_hostname)) + nm_clear_g_free(&temp_hostname); + if (!nm_streq0(temp_hostname, priv->orig_hostname)) { + /* Update original (fallback) hostname */ + g_free(priv->orig_hostname); + priv->orig_hostname = g_steal_pointer(&temp_hostname); + _LOGT(LOGD_DNS, + "hostname-original: update to %s%s%s", + NM_PRINT_FMT_QUOTE_STRING(priv->orig_hostname)); + } + } + + /* Hostname precedence order: + * + * 1) a configured hostname (from settings) + * 2) automatic hostname from DHCP of eligible interfaces + * 3) reverse-DNS lookup of the first address on eligible interfaces + * 4) the last hostname set outside NM + */ + + /* Try a persistent hostname first */ + configured_hostname = nm_hostname_manager_get_hostname(priv->hostname_manager); + if (configured_hostname && nm_utils_is_specific_hostname(configured_hostname)) { + _set_hostname(self, configured_hostname, "from system configuration"); + priv->dhcp_hostname = FALSE; + return; + } + + infos = build_device_hostname_infos(self); + + if (infos && _LOGT_ENABLED(LOGD_DNS)) { + _LOGT(LOGD_DNS, "device hostname info:"); + for (i = 0; i < infos->len; i++) { + info = &g_array_index(infos, DeviceHostnameInfo, i); + _LOGT(LOGD_DNS, + " - prio:%5d ipv%c%s %s %s dev:%s", + info->priority, + info->IS_IPv4 ? '4' : '6', + info->is_default ? " (def)" : " ", + info->from_dhcp ? "dhcp " : " ", + info->from_dns ? "dns " : " ", + nm_device_get_iface(info->device)); + } + } + + for (i = 0; infos && i < infos->len; i++) { + info = &g_array_index(infos, DeviceHostnameInfo, i); + addr_family = info->IS_IPv4 ? AF_INET : AF_INET6; + g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self); + + if (info->from_dhcp) { + dhcp_config = nm_device_get_dhcp_config(info->device, addr_family); + if (dhcp_config) { + dhcp_hostname = + nm_dhcp_config_get_option(dhcp_config, + info->IS_IPv4 ? "host_name" : "fqdn_fqdn"); + if (dhcp_hostname && dhcp_hostname[0]) { + p = nm_str_skip_leading_spaces(dhcp_hostname); + if (p[0]) { + _set_hostname(self, p, info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6"); + priv->dhcp_hostname = TRUE; + return; + } + _LOGW(LOGD_DNS, + "set-hostname: DHCPv%c-provided hostname '%s' looks invalid; " + "ignoring it", + nm_utils_addr_family_to_char(addr_family), + dhcp_hostname); + } + } + } + + if (priv->hostname_mode != NM_POLICY_HOSTNAME_MODE_DHCP) { + if (info->from_dns) { + const char *result; + gboolean wait = FALSE; + + result = nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait); + if (result) { + _set_hostname(self, result, "from address lookup"); + return; + } + if (wait) { + g_signal_connect(info->device, + NM_DEVICE_DNS_LOOKUP_DONE, + (GCallback) device_dns_lookup_done, + self); + return; + } + } + } + } + + /* If an hostname was set outside NetworkManager keep it */ + if (external_hostname) + return; + + if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_DHCP) { + /* In dhcp hostname-mode, the hostname is updated only if it comes from + * a DHCP host-name option: if last set was from a host-name option and + * we are here than that connection is gone (with its host-name option), + * so reset the hostname to the previous value + */ + if (priv->dhcp_hostname) { + _set_hostname(self, priv->orig_hostname, "reset dhcp hostname"); + priv->dhcp_hostname = FALSE; + } + return; + } + + priv->dhcp_hostname = FALSE; + + /* If no automatically-configured hostname, try using the last hostname + * set externally to NM + */ + if (priv->orig_hostname) { + _set_hostname(self, priv->orig_hostname, "from system startup"); + return; + } + + _set_hostname(self, NULL, "no hostname found"); +} + +static void +update_default_ac(NMPolicy *self, int addr_family, NMActiveConnection *best) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_list; + NMActiveConnection *ac; + + /* Clear the 'default[6]' flag on all active connections that aren't the new + * default active connection. We'll set the new default after; this ensures + * we don't ever have two marked 'default[6]' simultaneously. + */ + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + if (ac != best) + nm_active_connection_set_default(ac, addr_family, FALSE); + } + + /* Mark new default active connection */ + if (best) + nm_active_connection_set_default(best, addr_family, TRUE); +} + +static gpointer +get_best_ip_config(NMPolicy * self, + int addr_family, + const char ** out_ip_iface, + NMActiveConnection **out_ac, + NMDevice ** out_device, + NMVpnConnection ** out_vpn) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + gpointer conf, best_conf = NULL; + const CList * tmp_list; + NMActiveConnection *ac; + guint64 best_metric = G_MAXUINT64; + NMVpnConnection * best_vpn = NULL; + + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + NMVpnConnection * candidate; + NMVpnConnectionState vpn_state; + const NMPObject * obj; + guint32 metric; + + if (!NM_IS_VPN_CONNECTION(ac)) + continue; + + candidate = NM_VPN_CONNECTION(ac); + + vpn_state = nm_vpn_connection_get_vpn_state(candidate); + if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED) + continue; + + if (addr_family == AF_INET) + conf = nm_vpn_connection_get_ip4_config(candidate); + else + conf = nm_vpn_connection_get_ip6_config(candidate); + if (!conf) + continue; + + if (addr_family == AF_INET) + obj = nm_ip4_config_best_default_route_get(conf); + else + obj = nm_ip6_config_best_default_route_get(conf); + if (!obj) + continue; + + metric = NMP_OBJECT_CAST_IPX_ROUTE(obj)->rx.metric; + if (metric <= best_metric) { + best_metric = metric; + best_conf = conf; + best_vpn = candidate; + } + } + + if (best_metric != G_MAXUINT64) { + NM_SET_OUT(out_device, NULL); + NM_SET_OUT(out_vpn, best_vpn); + NM_SET_OUT(out_ac, NM_ACTIVE_CONNECTION(best_vpn)); + NM_SET_OUT(out_ip_iface, nm_vpn_connection_get_ip_iface(best_vpn, TRUE)); + return best_conf; + } + + ac = get_best_active_connection(self, addr_family, TRUE); + if (ac) { + NMDevice *device = nm_active_connection_get_device(ac); + + nm_assert(device); + + if (addr_family == AF_INET) + conf = nm_device_get_ip4_config(device); + else + conf = nm_device_get_ip6_config(device); + + NM_SET_OUT(out_device, device); + NM_SET_OUT(out_vpn, NULL); + NM_SET_OUT(out_ac, ac); + NM_SET_OUT(out_ip_iface, nm_device_get_ip_iface(device)); + return conf; + } + + NM_SET_OUT(out_device, NULL); + NM_SET_OUT(out_vpn, NULL); + NM_SET_OUT(out_ac, NULL); + NM_SET_OUT(out_ip_iface, NULL); + return NULL; +} + +static void +update_ip4_routing(NMPolicy *self, gboolean force_update) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMDevice * best = NULL; + NMVpnConnection * vpn = NULL; + NMActiveConnection *best_ac = NULL; + const char * ip_iface = NULL; + const CList * tmp_list; + NMActiveConnection *ac; + + /* Note that we might have an IPv4 VPN tunneled over an IPv6-only device, + * so we can get (vpn != NULL && best == NULL). + */ + if (!get_best_ip_config(self, AF_INET, &ip_iface, &best_ac, &best, &vpn)) { + if (nm_clear_g_object(&priv->default_ac4)) { + _LOGt(LOGD_DNS, "set-default-ac-4: %p", NULL); + _notify(self, PROP_DEFAULT_IP4_AC); + } + return; + } + g_assert((best || vpn) && best_ac); + + if (!force_update && best_ac && best_ac == priv->default_ac4) + return; + + if (best) { + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + if (NM_IS_VPN_CONNECTION(ac) && nm_vpn_connection_get_ip4_config(NM_VPN_CONNECTION(ac)) + && !nm_active_connection_get_device(ac)) + nm_active_connection_set_device(ac, best); + } + } + + update_default_ac(self, AF_INET, best_ac); + + if (!nm_g_object_ref_set(&priv->default_ac4, best_ac)) + return; + _LOGt(LOGD_DNS, "set-default-ac-4: %p", priv->default_ac4); + + _LOGI(LOGD_CORE, + "set '%s' (%s) as default for IPv4 routing and DNS", + nm_connection_get_id(nm_active_connection_get_applied_connection(best_ac)), + ip_iface); + _notify(self, PROP_DEFAULT_IP4_AC); +} + +static void +update_ip6_prefix_delegation(NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMDevice * device; + NMActiveConnection *ac; + const CList * tmp_list; + + /* There's new default IPv6 connection, try to get a prefix for everyone. */ + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + device = nm_active_connection_get_device(ac); + if (device && nm_device_needs_ip6_subnet(device)) + ip6_subnet_from_device(self, get_default_device(self, AF_INET6), device); + } +} + +static void +update_ip6_routing(NMPolicy *self, gboolean force_update) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMDevice * best = NULL; + NMVpnConnection * vpn = NULL; + NMActiveConnection *best_ac = NULL; + const char * ip_iface = NULL; + NMActiveConnection *ac; + const CList * tmp_list; + + /* Note that we might have an IPv6 VPN tunneled over an IPv4-only device, + * so we can get (vpn != NULL && best == NULL). + */ + if (!get_best_ip_config(self, AF_INET6, &ip_iface, &best_ac, &best, &vpn)) { + if (nm_clear_g_object(&priv->default_ac6)) { + _LOGt(LOGD_DNS, "set-default-ac-6: %p", NULL); + _notify(self, PROP_DEFAULT_IP6_AC); + } + return; + } + g_assert((best || vpn) && best_ac); + + if (!force_update && best_ac && best_ac == priv->default_ac6) + return; + + if (best) { + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + if (NM_IS_VPN_CONNECTION(ac) && nm_vpn_connection_get_ip6_config(NM_VPN_CONNECTION(ac)) + && !nm_active_connection_get_device(ac)) + nm_active_connection_set_device(ac, best); + } + } + + update_default_ac(self, AF_INET6, best_ac); + + if (!nm_g_object_ref_set(&priv->default_ac6, best_ac)) + return; + _LOGt(LOGD_DNS, "set-default-ac-6: %p", priv->default_ac6); + + update_ip6_prefix_delegation(self); + + _LOGI(LOGD_CORE, + "set '%s' (%s) as default for IPv6 routing and DNS", + nm_connection_get_id(nm_active_connection_get_applied_connection(best_ac)), + ip_iface); + _notify(self, PROP_DEFAULT_IP6_AC); +} + +static void +update_ip_dns(NMPolicy *self, int addr_family, NMDevice *changed_device) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + gpointer ip_config; + const char * ip_iface = NULL; + NMVpnConnection *vpn = NULL; + NMDevice * device = NULL; + + nm_assert_addr_family(addr_family); + + ip_config = get_best_ip_config(self, addr_family, &ip_iface, NULL, &device, &vpn); + if (ip_config) { + /* Tell the DNS manager this config is preferred by re-adding it with + * a different IP config type. + */ + _dns_manager_set_ip_config(NM_POLICY_GET_PRIVATE(self)->dns_manager, + ip_config, + (vpn || (device && nm_device_is_vpn(device))) + ? NM_DNS_IP_CONFIG_TYPE_VPN + : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, + device); + } + + if (addr_family == AF_INET6) { + NMActiveConnection *ac; + const CList * tmp_list; + + /* Tell devices needing a subnet about the new DNS configuration */ + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + device = nm_active_connection_get_device(ac); + if (device && device != changed_device && nm_device_needs_ip6_subnet(device)) + nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6)); + } + } +} + +static void +update_routing_and_dns(NMPolicy *self, gboolean force_update, NMDevice *changed_device) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + nm_dns_manager_begin_updates(priv->dns_manager, __func__); + + update_ip_dns(self, AF_INET, changed_device); + update_ip_dns(self, AF_INET6, changed_device); + + update_ip4_routing(self, force_update); + update_ip6_routing(self, force_update); + + /* Update the system hostname */ + update_system_hostname(self, "routing and dns"); + + nm_dns_manager_end_updates(priv->dns_manager, __func__); +} + +static void +check_activating_active_connections(NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMActiveConnection *best4, *best6 = NULL; + + best4 = get_best_active_connection(self, AF_INET, FALSE); + best6 = get_best_active_connection(self, AF_INET6, FALSE); + + g_object_freeze_notify(G_OBJECT(self)); + + if (nm_g_object_ref_set(&priv->activating_ac4, best4)) { + _LOGt(LOGD_DNS, "set-activating-ac-4: %p", priv->activating_ac4); + _notify(self, PROP_ACTIVATING_IP4_AC); + } + if (nm_g_object_ref_set(&priv->activating_ac6, best6)) { + _LOGt(LOGD_DNS, "set-activating-ac-6: %p", priv->activating_ac6); + _notify(self, PROP_ACTIVATING_IP6_AC); + } + + g_object_thaw_notify(G_OBJECT(self)); +} + +typedef struct { + CList pending_lst; + NMPolicy *policy; + NMDevice *device; + guint autoactivate_id; +} ActivateData; + +static void +activate_data_free(ActivateData *data) +{ + nm_device_remove_pending_action(data->device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); + c_list_unlink_stale(&data->pending_lst); + nm_clear_g_source(&data->autoactivate_id); + g_object_unref(data->device); + g_slice_free(ActivateData, data); +} + +static void +pending_ac_gone(gpointer data, GObject *where_the_object_was) +{ + NMPolicy * self = NM_POLICY(data); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + /* Active connections should reach the DEACTIVATED state + * before disappearing. */ + nm_assert_not_reached(); + + if (g_hash_table_remove(priv->pending_active_connections, where_the_object_was)) + g_object_unref(self); +} + +static void +pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMSettingsConnection *con; + + if (state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + /* The AC is being deactivated before the device had a chance + * to move to PREPARE. Schedule a new auto-activation on the + * device, but block the current connection to avoid an activation + * loop. + */ + if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED) { + con = nm_active_connection_get_settings_connection(ac); + nm_settings_connection_autoconnect_blocked_reason_set( + con, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + TRUE); + schedule_activate_check(self, nm_active_connection_get_device(ac)); + } + + /* Cleanup */ + g_signal_handlers_disconnect_by_func(ac, pending_ac_state_changed, self); + if (!g_hash_table_remove(priv->pending_active_connections, ac)) + nm_assert_not_reached(); + g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self); + g_object_unref(self); + } +} + +static void +auto_activate_device(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate * priv; + NMSettingsConnection *best_connection; + gs_free char * specific_object = NULL; + gs_free NMSettingsConnection **connections = NULL; + guint i, len; + gs_free_error GError *error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMActiveConnection * ac; + + nm_assert(NM_IS_POLICY(self)); + nm_assert(NM_IS_DEVICE(device)); + + priv = NM_POLICY_GET_PRIVATE(self); + + // FIXME: if a device is already activating (or activated) with a connection + // but another connection now overrides the current one for that device, + // deactivate the device and activate the new connection instead of just + // bailing if the device is already active + if (nm_device_get_act_request(device)) + return; + + if (!nm_device_autoconnect_allowed(device)) + return; + + connections = nm_manager_get_activatable_connections(priv->manager, TRUE, TRUE, &len); + if (!connections[0]) + return; + + /* Find the first connection that should be auto-activated */ + best_connection = NULL; + for (i = 0; i < len; i++) { + NMSettingsConnection *candidate = connections[i]; + NMConnection * cand_conn; + NMSettingConnection * s_con; + const char * permission; + + if (nm_settings_connection_autoconnect_is_blocked(candidate)) + continue; + + cand_conn = nm_settings_connection_get_connection(candidate); + + s_con = nm_connection_get_setting_connection(cand_conn); + if (!nm_setting_connection_get_autoconnect(s_con)) + continue; + + permission = nm_utils_get_shared_wifi_permission(cand_conn); + if (permission && !nm_settings_connection_check_permission(candidate, permission)) + continue; + + if (nm_device_can_auto_connect(device, candidate, &specific_object)) { + best_connection = candidate; + break; + } + } + + if (!best_connection) + return; + + _LOGI(LOGD_DEVICE, + "auto-activating connection '%s' (%s)", + nm_settings_connection_get_id(best_connection), + nm_settings_connection_get_uuid(best_connection)); + + subject = nm_auth_subject_new_internal(); + ac = nm_manager_activate_connection( + priv->manager, + best_connection, + NULL, + specific_object, + device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_AUTOCONNECT, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + &error); + if (!ac) { + _LOGI(LOGD_DEVICE, + "connection '%s' auto-activation failed: %s", + nm_settings_connection_get_id(best_connection), + error->message); + nm_settings_connection_autoconnect_blocked_reason_set( + best_connection, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + TRUE); + schedule_activate_check(self, device); + return; + } + + /* Subscribe to AC state-changed signal to detect when the + * activation fails in early stages without changing device + * state. + */ + if (g_hash_table_add(priv->pending_active_connections, ac)) { + g_signal_connect(ac, + NM_ACTIVE_CONNECTION_STATE_CHANGED, + G_CALLBACK(pending_ac_state_changed), + g_object_ref(self)); + g_object_weak_ref(G_OBJECT(ac), (GWeakNotify) pending_ac_gone, self); + } +} + +static gboolean +auto_activate_device_cb(gpointer user_data) +{ + ActivateData *data = user_data; + + g_assert(data); + g_assert(NM_IS_POLICY(data->policy)); + g_assert(NM_IS_DEVICE(data->device)); + + data->autoactivate_id = 0; + auto_activate_device(data->policy, data->device); + activate_data_free(data); + return G_SOURCE_REMOVE; +} + +static ActivateData * +find_pending_activation(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + ActivateData * data; + + c_list_for_each_entry (data, &priv->pending_activation_checks, pending_lst) { + if (data->device == device) + return data; + } + return NULL; +} + +/*****************************************************************************/ + +typedef struct { + NMDevice *device; + GSList * secondaries; +} PendingSecondaryData; + +static PendingSecondaryData * +pending_secondary_data_new(NMDevice *device, GSList *secondaries) +{ + PendingSecondaryData *data; + + data = g_slice_new(PendingSecondaryData); + data->device = g_object_ref(device); + data->secondaries = secondaries; + return data; +} + +static void +pending_secondary_data_free(PendingSecondaryData *data) +{ + g_object_unref(data->device); + g_slist_free_full(data->secondaries, g_object_unref); + g_slice_free(PendingSecondaryData, data); +} + +static void +process_secondaries(NMPolicy *self, NMActiveConnection *active, gboolean connected) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + GSList * iter, *iter2, *next, *next2; + + /* Loop through devices waiting for secondary connections to activate */ + for (iter = priv->pending_secondaries; iter; iter = next) { + PendingSecondaryData *secondary_data = (PendingSecondaryData *) iter->data; + NMDevice * item_device = secondary_data->device; + + next = g_slist_next(iter); + + /* Look for 'active' in each device's secondary connections list */ + for (iter2 = secondary_data->secondaries; iter2; iter2 = next2) { + NMActiveConnection *secondary_active = NM_ACTIVE_CONNECTION(iter2->data); + + next2 = g_slist_next(iter2); + + if (active != secondary_active) + continue; + + if (connected) { + _LOGD(LOGD_DEVICE, + "secondary connection '%s' succeeded; active path '%s'", + nm_active_connection_get_settings_connection_id(active), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active))); + + /* Secondary connection activated */ + secondary_data->secondaries = + g_slist_remove(secondary_data->secondaries, secondary_active); + g_object_unref(secondary_active); + if (!secondary_data->secondaries) { + /* No secondary UUID remained -> remove the secondary data item */ + priv->pending_secondaries = + g_slist_remove(priv->pending_secondaries, secondary_data); + pending_secondary_data_free(secondary_data); + if (nm_device_get_state(item_device) == NM_DEVICE_STATE_SECONDARIES) + nm_device_state_changed(item_device, + NM_DEVICE_STATE_ACTIVATED, + NM_DEVICE_STATE_REASON_NONE); + break; + } + } else { + _LOGD(LOGD_DEVICE, + "secondary connection '%s' failed; active path '%s'", + nm_active_connection_get_settings_connection_id(active), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active))); + + /* Secondary connection failed -> do not watch other connections */ + priv->pending_secondaries = + g_slist_remove(priv->pending_secondaries, secondary_data); + pending_secondary_data_free(secondary_data); + if (nm_device_get_state(item_device) == NM_DEVICE_STATE_SECONDARIES + || nm_device_get_state(item_device) == NM_DEVICE_STATE_ACTIVATED) + nm_device_state_changed(item_device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED); + break; + } + } + } +} + +static void +hostname_changed(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + update_system_hostname(self, "hostname changed"); +} + +void +nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMSettingsConnection *const *connections = NULL; + guint i; + + _LOGT(LOGD_DEVICE, "unblocking failed OVS interfaces"); + + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMConnection * connection = nm_settings_connection_get_connection(sett_conn); + + if (nm_connection_get_setting_ovs_interface(connection)) { + nm_settings_connection_autoconnect_retries_reset(sett_conn); + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + FALSE); + } + } +} + +static gboolean +reset_autoconnect_all( + NMPolicy *self, + NMDevice *device, /* if present, only reset connections compatible with @device */ + gboolean only_no_secrets) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMSettingsConnection *const *connections = NULL; + guint i; + gboolean changed = FALSE; + + _LOGD(LOGD_DEVICE, + "re-enabling autoconnect for all connections%s%s%s", + device ? " on " : "", + device ? nm_device_get_iface(device) : "", + only_no_secrets ? " (only clear no-secrets flag)" : ""); + + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + + if (device + && !nm_device_check_connection_compatible( + device, + nm_settings_connection_get_connection(sett_conn), + NULL)) + continue; + + if (only_no_secrets) { + /* we only reset the no-secrets blocked flag. */ + if (nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, + FALSE)) { + /* maybe the connection is still blocked afterwards for other reasons + * and in the larger picture nothing changed. But it's too complicated + * to find out exactly. Just assume, something changed to be sure. */ + if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) + changed = TRUE; + } + } else { + /* we reset the tries-count and any blocked-reason */ + if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0) + changed = TRUE; + nm_settings_connection_autoconnect_retries_reset(sett_conn); + + if (nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL + & ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + FALSE)) { + if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) + changed = TRUE; + } + } + } + return changed; +} + +static void +sleeping_changed(NMManager *manager, GParamSpec *pspec, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + gboolean sleeping = FALSE, enabled = FALSE; + + g_object_get(G_OBJECT(manager), NM_MANAGER_SLEEPING, &sleeping, NULL); + g_object_get(G_OBJECT(manager), NM_MANAGER_NETWORKING_ENABLED, &enabled, NULL); + + /* Reset retries on all connections so they'll checked on wakeup */ + if (sleeping || !enabled) + reset_autoconnect_all(self, NULL, FALSE); +} + +static void +schedule_activate_check(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + ActivateData * data; + NMActiveConnection *ac; + const CList * tmp_list; + + if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP) + return; + + if (!nm_device_autoconnect_allowed(device)) + return; + + if (find_pending_activation(self, device)) + return; + + nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) { + if (nm_active_connection_get_device(ac) == device) + return; + } + + nm_device_add_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE); + + data = g_slice_new0(ActivateData); + data->policy = self; + data->device = g_object_ref(device); + data->autoactivate_id = g_idle_add(auto_activate_device_cb, data); + c_list_link_tail(&priv->pending_activation_checks, &data->pending_lst); +} + +static gboolean +reset_connections_retries(gpointer user_data) +{ + NMPolicy * self = (NMPolicy *) user_data; + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMSettingsConnection *const *connections = NULL; + guint i; + gint32 con_stamp, min_stamp, now; + gboolean changed = FALSE; + + priv->reset_retries_id = 0; + + min_stamp = 0; + now = nm_utils_get_monotonic_timestamp_sec(); + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *connection = connections[i]; + + con_stamp = nm_settings_connection_autoconnect_retries_blocked_until(connection); + if (con_stamp == 0) + continue; + + if (con_stamp <= now) { + nm_settings_connection_autoconnect_retries_reset(connection); + changed = TRUE; + } else if (min_stamp == 0 || min_stamp > con_stamp) + min_stamp = con_stamp; + } + + /* Schedule the handler again if there are some stamps left */ + if (min_stamp != 0) + priv->reset_retries_id = + g_timeout_add_seconds(min_stamp - now, reset_connections_retries, self); + + /* If anything changed, try to activate the newly re-enabled connections */ + if (changed) + schedule_activate_all(self); + + return FALSE; +} + +static void +_connection_autoconnect_retries_set(NMPolicy *self, NMSettingsConnection *connection, int tries) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + nm_assert(NM_IS_SETTINGS_CONNECTION(connection)); + nm_assert(tries >= 0); + + nm_settings_connection_autoconnect_retries_set(connection, tries); + + if (tries == 0) { + /* Schedule a handler to reset retries count */ + if (!priv->reset_retries_id) { + gint32 retry_time = + nm_settings_connection_autoconnect_retries_blocked_until(connection); + + g_warn_if_fail(retry_time != 0); + priv->reset_retries_id = + g_timeout_add_seconds(MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()), + reset_connections_retries, + self); + } + } +} + +static void +activate_slave_connections(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + const char * master_device; + const char * master_uuid_settings = NULL; + const char * master_uuid_applied = NULL; + guint i; + NMActRequest * req; + gboolean internal_activation = FALSE; + NMSettingsConnection *const *connections; + gboolean changed; + + master_device = nm_device_get_iface(device); + g_assert(master_device); + + req = nm_device_get_act_request(device); + if (req) { + NMConnection * connection; + NMSettingsConnection *sett_conn; + NMAuthSubject * subject; + + connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req)); + if (connection) + master_uuid_applied = nm_connection_get_uuid(connection); + + sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req)); + if (sett_conn) { + master_uuid_settings = nm_settings_connection_get_uuid(sett_conn); + if (nm_streq0(master_uuid_settings, master_uuid_applied)) + master_uuid_settings = NULL; + } + + subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)); + internal_activation = + subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL); + } + + changed = FALSE; + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMSettingConnection * s_slave_con; + const char * slave_master; + + s_slave_con = + nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + slave_master = nm_setting_connection_get_master(s_slave_con); + if (!slave_master) + continue; + if (!NM_IN_STRSET(slave_master, master_device, master_uuid_applied, master_uuid_settings)) + continue; + + if (!internal_activation) { + if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0) + changed = TRUE; + nm_settings_connection_autoconnect_retries_reset(sett_conn); + } + if (nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + FALSE)) { + if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) + changed = TRUE; + } + } + + if (changed) + schedule_activate_all(self); +} + +static gboolean +activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevice *device) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMSettingConnection * s_con; + NMActiveConnection * ac; + PendingSecondaryData * secondary_data; + GSList * secondary_ac_list = NULL; + GError * error = NULL; + guint32 i; + gboolean success = TRUE; + NMActivationStateFlags initial_state_flags; + + s_con = nm_connection_get_setting_connection(connection); + nm_assert(NM_IS_SETTING_CONNECTION(s_con)); + + /* we propagate the activation's state flags. */ + initial_state_flags = nm_device_get_activation_state_flags(device) + & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY; + + for (i = 0; i < nm_setting_connection_get_num_secondaries(s_con); i++) { + NMSettingsConnection *sett_conn; + const char * sec_uuid = nm_setting_connection_get_secondary(s_con, i); + NMActRequest * req; + + sett_conn = nm_settings_get_connection_by_uuid(priv->settings, sec_uuid); + if (!sett_conn) { + _LOGW(LOGD_DEVICE, + "secondary connection '%s' auto-activation failed: The connection doesn't exist.", + sec_uuid); + success = FALSE; + break; + } + + if (!nm_connection_is_type(nm_settings_connection_get_connection(sett_conn), + NM_SETTING_VPN_SETTING_NAME)) { + _LOGW(LOGD_DEVICE, + "secondary connection '%s (%s)' auto-activation failed: The connection is not a " + "VPN.", + nm_settings_connection_get_id(sett_conn), + sec_uuid); + success = FALSE; + break; + } + + req = nm_device_get_act_request(device); + + _LOGD(LOGD_DEVICE, + "activating secondary connection '%s (%s)' for base connection '%s (%s)'", + nm_settings_connection_get_id(sett_conn), + sec_uuid, + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + ac = nm_manager_activate_connection( + priv->manager, + sett_conn, + NULL, + nm_dbus_object_get_path(NM_DBUS_OBJECT(req)), + device, + nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)), + NM_ACTIVATION_TYPE_MANAGED, + nm_active_connection_get_activation_reason(NM_ACTIVE_CONNECTION(req)), + initial_state_flags, + &error); + if (ac) + secondary_ac_list = g_slist_append(secondary_ac_list, g_object_ref(ac)); + else { + _LOGW(LOGD_DEVICE, + "secondary connection '%s (%s)' auto-activation failed: (%d) %s", + nm_settings_connection_get_id(sett_conn), + sec_uuid, + error->code, + error->message); + g_clear_error(&error); + success = FALSE; + break; + } + } + + if (success && secondary_ac_list != NULL) { + secondary_data = pending_secondary_data_new(device, secondary_ac_list); + priv->pending_secondaries = g_slist_append(priv->pending_secondaries, secondary_data); + } else + g_slist_free_full(secondary_ac_list, g_object_unref); + + return success; +} + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMPolicyPrivate * priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + NMActiveConnection * ac; + NMSettingsConnection *sett_conn = nm_device_get_settings_connection(device); + NMIP4Config * ip4_config; + NMIP6Config * ip6_config; + NMSettingConnection * s_con = NULL; + + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + /* Block autoconnection at settings level if there is any settings-specific + * error reported by the modem (e.g. wrong SIM-PIN or wrong APN). Do not block + * autoconnection at settings level for errors in the device domain (e.g. + * a missing SIM or wrong modem initialization). + */ + if (sett_conn) { + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + TRUE); + } + break; + default: + break; + } + + switch (new_state) { + case NM_DEVICE_STATE_FAILED: + g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self); + + /* Mark the connection invalid if it failed during activation so that + * it doesn't get automatically chosen over and over and over again. + */ + if (sett_conn && old_state >= NM_DEVICE_STATE_PREPARE + && old_state <= NM_DEVICE_STATE_ACTIVATED) { + gboolean blocked = FALSE; + int tries; + guint64 con_v; + + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) { + /* we want to block the connection from auto-connect if it failed due to no-secrets. + * However, if a secret-agent registered, since the connection made the last + * secret-request, we do not block it. The new secret-agent might not yet + * been consulted, and it may be able to provide the secrets. + * + * We detect this by using a version-id of the agent-manager, which increments + * whenever new agents register. Note that the agent-manager's version-id is + * never zero and strictly increasing. + * + * A connection's version-id of zero means that the connection never tried to request secrets. + * That can happen when nm_settings_connection_get_secrets() fails early without actually + * consulting any agents. + */ + con_v = nm_settings_connection_get_last_secret_agent_version_id(sett_conn); + if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) { + _LOGD(LOGD_DEVICE, + "connection '%s' now blocked from autoconnect due to no secrets", + nm_settings_connection_get_id(sett_conn)); + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, + TRUE); + blocked = TRUE; + } + } else if (nm_device_state_reason_check(reason) + == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) { + /* A connection that fails due to dependency-failed is not + * able to reconnect until the master connection activates + * again; when this happens, the master clears the blocked + * reason for all its slaves in activate_slave_connections() + * and tries to reconnect them. For this to work, the slave + * should be marked as blocked when it fails with + * dependency-failed. + */ + _LOGD(LOGD_DEVICE, + "connection '%s' now blocked from autoconnect due to failed dependency", + nm_settings_connection_get_id(sett_conn)); + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, + TRUE); + blocked = TRUE; + } + + if (!blocked) { + tries = nm_settings_connection_autoconnect_retries_get(sett_conn); + if (tries > 0) { + _LOGD(LOGD_DEVICE, + "connection '%s' failed to autoconnect; %d tries left", + nm_settings_connection_get_id(sett_conn), + tries - 1); + _connection_autoconnect_retries_set(self, sett_conn, tries - 1); + } else if (tries != 0) { + _LOGD(LOGD_DEVICE, + "connection '%s' failed to autoconnect; infinite tries left", + nm_settings_connection_get_id(sett_conn)); + } + } + + nm_settings_connection_clear_secrets(sett_conn, FALSE, FALSE); + } + break; + case NM_DEVICE_STATE_ACTIVATED: + if (sett_conn) { + /* Reset auto retries back to default since connection was successful */ + nm_settings_connection_autoconnect_retries_reset(sett_conn); + + /* And clear secrets so they will always be requested from the + * settings service when the next connection is made. + */ + nm_settings_connection_clear_secrets(sett_conn, FALSE, FALSE); + } + + /* Add device's new IPv4 and IPv6 configs to DNS */ + + nm_dns_manager_begin_updates(priv->dns_manager, __func__); + + ip4_config = nm_device_get_ip4_config(device); + if (ip4_config) + _dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip4_config), + NM_DNS_IP_CONFIG_TYPE_DEFAULT, + device); + ip6_config = nm_device_get_ip6_config(device); + if (ip6_config) + _dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip6_config), + NM_DNS_IP_CONFIG_TYPE_DEFAULT, + device); + + update_routing_and_dns(self, FALSE, device); + + nm_dns_manager_end_updates(priv->dns_manager, __func__); + break; + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + if (old_state > NM_DEVICE_STATE_DISCONNECTED) + update_routing_and_dns(self, FALSE, device); + break; + case NM_DEVICE_STATE_DEACTIVATING: + if (sett_conn) { + NMSettingsAutoconnectBlockedReason blocked_reason = + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE; + + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_USER_REQUESTED: + blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST; + break; + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: + blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED; + break; + default: + break; + } + if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) { + _LOGD(LOGD_DEVICE, + "blocking autoconnect of connection '%s': %s", + nm_settings_connection_get_id(sett_conn), + NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, + nm_device_state_reason_check(reason))); + nm_settings_connection_autoconnect_blocked_reason_set(sett_conn, + blocked_reason, + TRUE); + } + } + ip6_remove_device_prefix_delegations(self, device); + break; + case NM_DEVICE_STATE_DISCONNECTED: + g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self); + + /* Reset retry counts for a device's connections when carrier on; if cable + * was unplugged and plugged in again, we should try to reconnect. + */ + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CARRIER + && old_state == NM_DEVICE_STATE_UNAVAILABLE) + reset_autoconnect_all(self, device, FALSE); + + if (old_state > NM_DEVICE_STATE_DISCONNECTED) + update_routing_and_dns(self, FALSE, device); + + /* Device is now available for auto-activation */ + schedule_activate_check(self, device); + break; + + case NM_DEVICE_STATE_PREPARE: + /* Reset auto-connect retries of all slaves and schedule them for + * activation. */ + activate_slave_connections(self, device); + + /* Now that the device state is progressing, we don't care + * anymore for the AC state. */ + ac = (NMActiveConnection *) nm_device_get_act_request(device); + if (ac && g_hash_table_remove(priv->pending_active_connections, ac)) { + g_signal_handlers_disconnect_by_func(ac, pending_ac_state_changed, self); + g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self); + g_object_unref(self); + } + break; + case NM_DEVICE_STATE_IP_CONFIG: + /* We must have secrets if we got here. */ + if (sett_conn) + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL, + FALSE); + break; + case NM_DEVICE_STATE_SECONDARIES: + if (sett_conn) + s_con = nm_connection_get_setting_connection( + nm_settings_connection_get_connection(sett_conn)); + if (s_con && nm_setting_connection_get_num_secondaries(s_con) > 0) { + /* Make routes and DNS up-to-date before activating dependent connections */ + update_routing_and_dns(self, FALSE, device); + + /* Activate secondary (VPN) connections */ + if (!activate_secondary_connections(self, + nm_settings_connection_get_connection(sett_conn), + device)) { + nm_device_queue_state(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED); + } + } else + nm_device_queue_state(device, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE); + break; + + default: + break; + } + + check_activating_active_connections(self); +} + +static void +device_ip_config_changed(NMDevice * device, + NMIPConfig *new_config, + NMIPConfig *old_config, + gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + int addr_family; + + nm_assert(new_config || old_config); + nm_assert(!new_config || NM_IS_IP_CONFIG(new_config)); + nm_assert(!old_config || NM_IS_IP_CONFIG(old_config)); + + if (new_config) { + addr_family = nm_ip_config_get_addr_family(new_config); + nm_assert(!old_config || addr_family == nm_ip_config_get_addr_family(old_config)); + } else + addr_family = nm_ip_config_get_addr_family(old_config); + + nm_dns_manager_begin_updates(priv->dns_manager, __func__); + + /* We catch already all the IP events registering on the device state changes but + * the ones where the IP changes but the device state keep stable (i.e., activated): + * ignore IP config changes but when the device is in activated state. + * Prevents unnecessary changes to DNS information. + */ + if (nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) { + if (old_config != new_config) { + if (new_config) + _dns_manager_set_ip_config(priv->dns_manager, + new_config, + NM_DNS_IP_CONFIG_TYPE_DEFAULT, + device); + if (old_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + old_config, + NM_DNS_IP_CONFIG_TYPE_REMOVED); + } + update_ip_dns(self, addr_family, device); + if (addr_family == AF_INET) + update_ip4_routing(self, TRUE); + else + update_ip6_routing(self, TRUE); + update_system_hostname(self, addr_family == AF_INET ? "ip4 conf" : "ip6 conf"); + } else { + /* Old configs get removed immediately */ + if (old_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + old_config, + NM_DNS_IP_CONFIG_TYPE_REMOVED); + } + + nm_dns_manager_end_updates(priv->dns_manager, __func__); +} + +/*****************************************************************************/ + +static void +device_autoconnect_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + schedule_activate_check(self, device); +} + +static void +device_recheck_auto_activate(NMDevice *device, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + schedule_activate_check(self, device); +} + +static void +devices_list_unregister(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + g_signal_handlers_disconnect_by_data((GObject *) device, priv); +} + +static void +devices_list_register(NMPolicy *self, NMDevice *device) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + /* Connect state-changed with _after, so that the handler is invoked after other handlers. */ + g_signal_connect_after(device, NM_DEVICE_STATE_CHANGED, (GCallback) device_state_changed, priv); + g_signal_connect(device, + NM_DEVICE_IP4_CONFIG_CHANGED, + (GCallback) device_ip_config_changed, + priv); + g_signal_connect(device, + NM_DEVICE_IP6_CONFIG_CHANGED, + (GCallback) device_ip_config_changed, + priv); + g_signal_connect(device, + NM_DEVICE_IP6_PREFIX_DELEGATED, + (GCallback) device_ip6_prefix_delegated, + priv); + g_signal_connect(device, + NM_DEVICE_IP6_SUBNET_NEEDED, + (GCallback) device_ip6_subnet_needed, + priv); + g_signal_connect(device, + "notify::" NM_DEVICE_AUTOCONNECT, + (GCallback) device_autoconnect_changed, + priv); + g_signal_connect(device, + NM_DEVICE_RECHECK_AUTO_ACTIVATE, + (GCallback) device_recheck_auto_activate, + priv); +} + +static void +device_added(NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + g_return_if_fail(NM_IS_POLICY(self)); + + priv = NM_POLICY_GET_PRIVATE(self); + + if (!g_hash_table_add(priv->devices, device)) + g_return_if_reached(); + + devices_list_register(self, device); +} + +static void +device_removed(NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + ActivateData * data; + + /* TODO: is this needed? The delegations are cleaned up + * on transition to deactivated too. */ + ip6_remove_device_prefix_delegations(self, device); + + /* Clear any idle callbacks for this device */ + data = find_pending_activation(self, device); + if (data && data->autoactivate_id) + activate_data_free(data); + + if (g_hash_table_remove(priv->devices, device)) + devices_list_unregister(self, device); + + /* Don't update routing and DNS here as we've already handled that + * for devices that need it when the device's state changed to UNMANAGED. + */ +} + +/*****************************************************************************/ + +static void +vpn_connection_activated(NMPolicy *self, NMVpnConnection *vpn) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + NMIP4Config * ip4_config; + NMIP6Config * ip6_config; + + nm_dns_manager_begin_updates(priv->dns_manager, __func__); + + ip4_config = nm_vpn_connection_get_ip4_config(vpn); + if (ip4_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip4_config), + NM_DNS_IP_CONFIG_TYPE_VPN); + + ip6_config = nm_vpn_connection_get_ip6_config(vpn); + if (ip6_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip6_config), + NM_DNS_IP_CONFIG_TYPE_VPN); + + update_routing_and_dns(self, TRUE, NULL); + + nm_dns_manager_end_updates(priv->dns_manager, __func__); +} + +static void +vpn_connection_deactivated(NMPolicy *self, NMVpnConnection *vpn) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + NMIP4Config * ip4_config; + NMIP6Config * ip6_config; + + nm_dns_manager_begin_updates(priv->dns_manager, __func__); + + ip4_config = nm_vpn_connection_get_ip4_config(vpn); + if (ip4_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip4_config), + NM_DNS_IP_CONFIG_TYPE_REMOVED); + + ip6_config = nm_vpn_connection_get_ip6_config(vpn); + if (ip6_config) + nm_dns_manager_set_ip_config(priv->dns_manager, + NM_IP_CONFIG_CAST(ip6_config), + NM_DNS_IP_CONFIG_TYPE_REMOVED); + + update_routing_and_dns(self, TRUE, NULL); + + nm_dns_manager_end_updates(priv->dns_manager, __func__); +} + +static void +vpn_connection_state_changed(NMVpnConnection * vpn, + NMVpnConnectionState new_state, + NMVpnConnectionState old_state, + NMActiveConnectionStateReason reason, + NMPolicy * self) +{ + if (new_state == NM_VPN_CONNECTION_STATE_ACTIVATED) + vpn_connection_activated(self, vpn); + else if (new_state >= NM_VPN_CONNECTION_STATE_FAILED) { + /* Only clean up IP/DNS if the connection ever got past IP_CONFIG */ + if (old_state >= NM_VPN_CONNECTION_STATE_IP_CONFIG_GET + && old_state <= NM_VPN_CONNECTION_STATE_ACTIVATED) + vpn_connection_deactivated(self, vpn); + } +} + +static void +vpn_connection_retry_after_failure(NMVpnConnection *vpn, NMPolicy *self) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMActiveConnection * ac = NM_ACTIVE_CONNECTION(vpn); + NMSettingsConnection *connection = nm_active_connection_get_settings_connection(ac); + GError * error = NULL; + + /* Attempt to reconnect VPN connections that failed after being connected */ + if (!nm_manager_activate_connection( + priv->manager, + connection, + NULL, + NULL, + NULL, + nm_active_connection_get_subject(ac), + NM_ACTIVATION_TYPE_MANAGED, + nm_active_connection_get_activation_reason(ac), + (nm_active_connection_get_state_flags(ac) + & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY), + &error)) { + _LOGW(LOGD_DEVICE, + "VPN '%s' reconnect failed: %s", + nm_settings_connection_get_id(connection), + error->message ?: "unknown"); + g_clear_error(&error); + } +} + +static void +active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMPolicy *self) +{ + NMActiveConnectionState state = nm_active_connection_get_state(active); + + if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + process_secondaries(self, active, TRUE); + else if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + process_secondaries(self, active, FALSE); +} + +static void +active_connection_keep_alive_changed(NMKeepAlive *keep_alive, GParamSpec *pspec, NMPolicy *self) +{ + NMPolicyPrivate * priv; + NMActiveConnection *ac; + GError * error = NULL; + + nm_assert(NM_IS_POLICY(self)); + nm_assert(NM_IS_KEEP_ALIVE(keep_alive)); + nm_assert(NM_IS_ACTIVE_CONNECTION(nm_keep_alive_get_owner(keep_alive))); + + if (nm_keep_alive_is_alive(keep_alive)) + return; + + ac = nm_keep_alive_get_owner(keep_alive); + + if (nm_active_connection_get_state(ac) > NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + return; + + priv = NM_POLICY_GET_PRIVATE(self); + + if (!nm_manager_deactivate_connection(priv->manager, + ac, + NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, + &error)) { + _LOGW(LOGD_DEVICE, + "connection '%s' is no longer kept alive, but error deactivating it: %s", + nm_active_connection_get_settings_connection_id(ac), + error->message); + g_clear_error(&error); + } +} + +static void +active_connection_added(NMManager *manager, NMActiveConnection *active, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + NMKeepAlive * keep_alive; + + if (NM_IS_VPN_CONNECTION(active)) { + g_signal_connect(active, + NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED, + G_CALLBACK(vpn_connection_state_changed), + self); + g_signal_connect(active, + NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE, + G_CALLBACK(vpn_connection_retry_after_failure), + self); + } + + keep_alive = nm_active_connection_get_keep_alive(active); + + nm_keep_alive_arm(keep_alive); + + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(active_connection_state_changed), + self); + g_signal_connect(keep_alive, + "notify::" NM_KEEP_ALIVE_ALIVE, + G_CALLBACK(active_connection_keep_alive_changed), + self); + active_connection_keep_alive_changed(keep_alive, NULL, self); +} + +static void +active_connection_removed(NMManager *manager, NMActiveConnection *active, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + g_signal_handlers_disconnect_by_func(active, vpn_connection_state_changed, self); + g_signal_handlers_disconnect_by_func(active, vpn_connection_retry_after_failure, self); + g_signal_handlers_disconnect_by_func(active, active_connection_state_changed, self); + g_signal_handlers_disconnect_by_func(nm_active_connection_get_keep_alive(active), + active_connection_keep_alive_changed, + self); +} + +/*****************************************************************************/ + +static gboolean +schedule_activate_all_cb(gpointer user_data) +{ + NMPolicy * self = user_data; + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_lst; + NMDevice * device; + + priv->schedule_activate_all_id = 0; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) + schedule_activate_check(self, device); + + return G_SOURCE_REMOVE; +} + +static void +schedule_activate_all(NMPolicy *self) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + /* always restart the idle handler. That way, we settle + * all other events before restarting to activate them. */ + nm_clear_g_source(&priv->schedule_activate_all_id); + priv->schedule_activate_all_id = g_idle_add(schedule_activate_all_cb, self); +} + +static void +connection_added(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + schedule_activate_all(self); +} + +static void +firewall_state_changed(NMFirewallManager *manager, gboolean initialized_now, gpointer user_data) +{ + NMPolicy * self = (NMPolicy *) user_data; + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + const CList * tmp_lst; + NMDevice * device; + + if (initialized_now) { + /* the firewall manager was initializing, but all requests + * so fare were queued and are already sent. No need to + * re-update the firewall zone of the devices. */ + return; + } + + if (!nm_firewall_manager_get_running(manager)) + return; + + /* add interface of each device to correct zone */ + nm_manager_for_each_device (priv->manager, device, tmp_lst) + nm_device_update_firewall_zone(device); +} + +static void +dns_config_changed(NMDnsManager *dns_manager, gpointer user_data) +{ + NMPolicy * self = (NMPolicy *) user_data; + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + NMDevice * device; + const CList * tmp_lst; + + /* We are currently updating the hostname in the DNS manager. + * This doesn't warrant a new DNS lookup.*/ + if (priv->updating_dns) + return; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + nm_device_clear_dns_lookup_data(device); + } + + update_system_hostname(self, "DNS configuration changed"); +} + +static void +connection_updated(NMSettings * settings, + NMSettingsConnection *connection, + guint update_reason_u, + gpointer user_data) +{ + NMPolicyPrivate * priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + NMSettingsConnectionUpdateReason update_reason = update_reason_u; + + if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) { + const CList *tmp_lst; + NMDevice * device; + + /* find device with given connection */ + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (nm_device_get_settings_connection(device) == connection) + nm_device_reapply_settings_immediately(device); + } + } + + schedule_activate_all(self); +} + +static void +_deactivate_if_active(NMPolicy *self, NMSettingsConnection *connection) +{ + NMPolicyPrivate * priv = NM_POLICY_GET_PRIVATE(self); + NMActiveConnection *ac; + const CList * tmp_list, *tmp_safe; + GError * error = NULL; + + nm_assert(NM_IS_SETTINGS_CONNECTION(connection)); + + nm_manager_for_each_active_connection_safe (priv->manager, ac, tmp_list, tmp_safe) { + if (nm_active_connection_get_settings_connection(ac) == connection + && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) { + if (!nm_manager_deactivate_connection(priv->manager, + ac, + NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, + &error)) { + _LOGW(LOGD_DEVICE, + "connection '%s' disappeared, but error deactivating it: (%d) %s", + nm_settings_connection_get_id(connection), + error ? error->code : -1, + error ? error->message : "(unknown)"); + g_clear_error(&error); + } + } + } +} + +static void +connection_removed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + _deactivate_if_active(self, connection); +} + +static void +connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy * self = _PRIV_TO_SELF(priv); + + if (NM_FLAGS_HAS(nm_settings_connection_get_flags(connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { + if (!nm_settings_connection_autoconnect_is_blocked(connection)) + schedule_activate_all(self); + } +} + +static void +secret_agent_registered(NMSettings *settings, NMSecretAgent *agent, gpointer user_data) +{ + NMPolicy *self = NM_POLICY(user_data); + + /* The registered secret agent may provide some missing secrets. Thus we + * reset retries count here and schedule activation, so that the + * connections failed due to missing secrets may re-try auto-connection. + */ + if (reset_autoconnect_all(self, NULL, TRUE)) + schedule_activate_all(self); +} + +NMActiveConnection * +nm_policy_get_default_ip4_ac(NMPolicy *self) +{ + return NM_POLICY_GET_PRIVATE(self)->default_ac4; +} + +NMActiveConnection * +nm_policy_get_default_ip6_ac(NMPolicy *self) +{ + return NM_POLICY_GET_PRIVATE(self)->default_ac6; +} + +NMActiveConnection * +nm_policy_get_activating_ip4_ac(NMPolicy *self) +{ + return NM_POLICY_GET_PRIVATE(self)->activating_ac4; +} + +NMActiveConnection * +nm_policy_get_activating_ip6_ac(NMPolicy *self) +{ + return NM_POLICY_GET_PRIVATE(self)->activating_ac6; +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE(_hostname_mode_to_string, + NMPolicyHostnameMode, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_NONE, "none"), + NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_DHCP, "dhcp"), + NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_FULL, "full"), ); + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMPolicy * self = NM_POLICY(object); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_DEFAULT_IP4_AC: + g_value_set_object(value, priv->default_ac4); + break; + case PROP_DEFAULT_IP6_AC: + g_value_set_object(value, priv->default_ac6); + break; + case PROP_ACTIVATING_IP4_AC: + g_value_set_object(value, priv->activating_ac4); + break; + case PROP_ACTIVATING_IP6_AC: + g_value_set_object(value, priv->activating_ac6); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMPolicy * self = NM_POLICY(object); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MANAGER: + /* construct-only */ + priv->manager = g_value_get_object(value); + g_return_if_fail(NM_IS_MANAGER(priv->manager)); + break; + case PROP_SETTINGS: + /* construct-only */ + priv->settings = g_value_dup_object(value); + g_return_if_fail(NM_IS_SETTINGS(priv->settings)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_policy_init(NMPolicy *self) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + gs_free char * hostname_mode = NULL; + + c_list_init(&priv->pending_activation_checks); + + priv->netns = g_object_ref(nm_netns_get()); + + priv->hostname_manager = g_object_ref(nm_hostname_manager_get()); + + hostname_mode = + nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + if (nm_streq0(hostname_mode, "none")) + priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_NONE; + else if (nm_streq0(hostname_mode, "dhcp")) + priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_DHCP; + else /* default - full mode */ + priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_FULL; + + priv->devices = g_hash_table_new(nm_direct_hash, NULL); + priv->pending_active_connections = g_hash_table_new(nm_direct_hash, NULL); + priv->ip6_prefix_delegations = g_array_new(FALSE, FALSE, sizeof(IP6PrefixDelegation)); + g_array_set_clear_func(priv->ip6_prefix_delegations, clear_ip6_prefix_delegation); +} + +static void +constructed(GObject *object) +{ + NMPolicy * self = NM_POLICY(object); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + char * hostname = NULL; + + /* Grab hostname on startup and use that if nothing provides one */ + if ((hostname = _get_hostname(self))) { + /* init last_hostname */ + priv->last_hostname = hostname; + + /* only cache it if it's a valid hostname */ + if (nm_utils_is_specific_hostname(hostname)) + priv->orig_hostname = g_strdup(hostname); + } + _LOGT(LOGD_DNS, + "hostname-original: set to %s%s%s", + NM_PRINT_FMT_QUOTE_STRING(priv->orig_hostname)); + + priv->agent_mgr = g_object_ref(nm_agent_manager_get()); + + priv->firewall_manager = g_object_ref(nm_firewall_manager_get()); + g_signal_connect(priv->firewall_manager, + NM_FIREWALL_MANAGER_STATE_CHANGED, + G_CALLBACK(firewall_state_changed), + self); + + priv->dns_manager = g_object_ref(nm_dns_manager_get()); + nm_dns_manager_set_initial_hostname(priv->dns_manager, priv->orig_hostname); + priv->config_changed_id = g_signal_connect(priv->dns_manager, + NM_DNS_MANAGER_CONFIG_CHANGED, + G_CALLBACK(dns_config_changed), + self); + + g_signal_connect(priv->hostname_manager, + "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, + (GCallback) hostname_changed, + priv); + + g_signal_connect(priv->manager, + "notify::" NM_MANAGER_SLEEPING, + (GCallback) sleeping_changed, + priv); + g_signal_connect(priv->manager, + "notify::" NM_MANAGER_NETWORKING_ENABLED, + (GCallback) sleeping_changed, + priv); + g_signal_connect(priv->manager, + NM_MANAGER_INTERNAL_DEVICE_ADDED, + (GCallback) device_added, + priv); + g_signal_connect(priv->manager, + NM_MANAGER_INTERNAL_DEVICE_REMOVED, + (GCallback) device_removed, + priv); + g_signal_connect(priv->manager, + NM_MANAGER_ACTIVE_CONNECTION_ADDED, + (GCallback) active_connection_added, + priv); + g_signal_connect(priv->manager, + NM_MANAGER_ACTIVE_CONNECTION_REMOVED, + (GCallback) active_connection_removed, + priv); + + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + (GCallback) connection_added, + priv); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + (GCallback) connection_updated, + priv); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, + (GCallback) connection_removed, + priv); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, + (GCallback) connection_flags_changed, + priv); + + g_signal_connect(priv->agent_mgr, + NM_AGENT_MANAGER_AGENT_REGISTERED, + G_CALLBACK(secret_agent_registered), + self); + + G_OBJECT_CLASS(nm_policy_parent_class)->constructed(object); + + _LOGD(LOGD_DNS, "hostname-mode: %s", _hostname_mode_to_string(priv->hostname_mode)); +} + +NMPolicy * +nm_policy_new(NMManager *manager, NMSettings *settings) +{ + g_return_val_if_fail(NM_IS_MANAGER(manager), NULL); + g_return_val_if_fail(NM_IS_SETTINGS(settings), NULL); + + return g_object_new(NM_TYPE_POLICY, + NM_POLICY_MANAGER, + manager, + NM_POLICY_SETTINGS, + settings, + NULL); +} + +static void +dispose(GObject *object) +{ + NMPolicy * self = NM_POLICY(object); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + GHashTableIter h_iter; + NMDevice * device; + ActivateData * data, *data_safe; + + nm_clear_g_object(&priv->default_ac4); + nm_clear_g_object(&priv->default_ac6); + nm_clear_g_object(&priv->activating_ac4); + nm_clear_g_object(&priv->activating_ac6); + nm_clear_pointer(&priv->pending_active_connections, g_hash_table_unref); + + c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst) + activate_data_free(data); + + g_slist_free_full(priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free); + priv->pending_secondaries = NULL; + + if (priv->firewall_manager) { + g_signal_handlers_disconnect_by_func(priv->firewall_manager, firewall_state_changed, self); + g_clear_object(&priv->firewall_manager); + } + + if (priv->agent_mgr) { + g_signal_handlers_disconnect_by_func(priv->agent_mgr, secret_agent_registered, self); + g_clear_object(&priv->agent_mgr); + } + + if (priv->dns_manager) { + nm_clear_g_signal_handler(priv->dns_manager, &priv->config_changed_id); + g_clear_object(&priv->dns_manager); + } + + g_hash_table_iter_init(&h_iter, priv->devices); + if (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) { + g_hash_table_iter_remove(&h_iter); + devices_list_unregister(self, device); + } + + /* The manager should have disposed of ActiveConnections already, which + * will have called active_connection_removed() and thus we don't need + * to clean anything up. Assert that this is TRUE. + */ + nm_assert(c_list_is_empty(nm_manager_get_active_connections(priv->manager))); + + nm_clear_g_source(&priv->reset_retries_id); + nm_clear_g_source(&priv->schedule_activate_all_id); + + nm_clear_g_free(&priv->orig_hostname); + nm_clear_g_free(&priv->cur_hostname); + nm_clear_g_free(&priv->last_hostname); + + if (priv->hostname_manager) { + g_signal_handlers_disconnect_by_data(priv->hostname_manager, priv); + g_clear_object(&priv->hostname_manager); + } + + if (priv->settings) { + g_signal_handlers_disconnect_by_data(priv->settings, priv); + g_clear_object(&priv->settings); + + /* we don't clear priv->manager as we don't own a reference to it, + * that is, NMManager must outlive NMPolicy anyway. + * + * Hence, we unsubscribe the signals here together with the signals + * for settings. */ + g_signal_handlers_disconnect_by_data(priv->manager, priv); + } + + if (priv->ip6_prefix_delegations) { + g_array_free(priv->ip6_prefix_delegations, TRUE); + priv->ip6_prefix_delegations = NULL; + } + + nm_assert(NM_IS_MANAGER(priv->manager)); + + G_OBJECT_CLASS(nm_policy_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMPolicy * self = NM_POLICY(object); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + g_hash_table_unref(priv->devices); + + G_OBJECT_CLASS(nm_policy_parent_class)->finalize(object); + + g_object_unref(priv->netns); +} + +static void +nm_policy_class_init(NMPolicyClass *policy_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(policy_class); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_MANAGER] = + g_param_spec_object(NM_POLICY_MANAGER, + "", + "", + NM_TYPE_MANAGER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_SETTINGS] = + g_param_spec_object(NM_POLICY_SETTINGS, + "", + "", + NM_TYPE_SETTINGS, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DEFAULT_IP4_AC] = + g_param_spec_object(NM_POLICY_DEFAULT_IP4_AC, + "", + "", + NM_TYPE_ACTIVE_CONNECTION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DEFAULT_IP6_AC] = + g_param_spec_object(NM_POLICY_DEFAULT_IP6_AC, + "", + "", + NM_TYPE_DEVICE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ACTIVATING_IP4_AC] = + g_param_spec_object(NM_POLICY_ACTIVATING_IP4_AC, + "", + "", + NM_TYPE_DEVICE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ACTIVATING_IP6_AC] = + g_param_spec_object(NM_POLICY_ACTIVATING_IP6_AC, + "", + "", + NM_TYPE_DEVICE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/nm-policy.h b/src/core/nm-policy.h new file mode 100644 index 0000000..fa216db --- /dev/null +++ b/src/core/nm-policy.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2004 - 2010 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_POLICY_H__ +#define __NETWORKMANAGER_POLICY_H__ + +#define NM_TYPE_POLICY (nm_policy_get_type()) +#define NM_POLICY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_POLICY, NMPolicy)) +#define NM_POLICY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_POLICY, NMPolicyClass)) +#define NM_IS_POLICY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_POLICY)) +#define NM_IS_POLICY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_POLICY)) +#define NM_POLICY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_POLICY, NMPolicyClass)) + +#define NM_POLICY_MANAGER "manager" +#define NM_POLICY_SETTINGS "settings" +#define NM_POLICY_DEFAULT_IP4_AC "default-ip4-ac" +#define NM_POLICY_DEFAULT_IP6_AC "default-ip6-ac" +#define NM_POLICY_ACTIVATING_IP4_AC "activating-ip4-ac" +#define NM_POLICY_ACTIVATING_IP6_AC "activating-ip6-ac" + +typedef struct _NMPolicyClass NMPolicyClass; + +GType nm_policy_get_type(void); + +NMPolicy *nm_policy_new(NMManager *manager, NMSettings *settings); + +NMActiveConnection *nm_policy_get_default_ip4_ac(NMPolicy *policy); +NMActiveConnection *nm_policy_get_default_ip6_ac(NMPolicy *policy); +NMActiveConnection *nm_policy_get_activating_ip4_ac(NMPolicy *policy); +NMActiveConnection *nm_policy_get_activating_ip6_ac(NMPolicy *policy); + +void nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self); + +/** + * NMPolicyHostnameMode + * @NM_POLICY_HOSTNAME_MODE_NONE: never update the transient hostname. + * @NM_POLICY_HOSTNAME_MODE_DHCP: only hostname from DHCP hostname + * options are eligible to be set as transient hostname. + * @NM_POLICY_HOSTNAME_MODE_FULL: NM will try to update the hostname looking + * to current static hostname, DHCP options, reverse IP lookup and externally + * set hostnames. + * + * NMPolicy's hostname update policy + */ +typedef enum { + NM_POLICY_HOSTNAME_MODE_NONE, + NM_POLICY_HOSTNAME_MODE_DHCP, + NM_POLICY_HOSTNAME_MODE_FULL, +} NMPolicyHostnameMode; + +#endif /* __NETWORKMANAGER_POLICY_H__ */ diff --git a/src/core/nm-proxy-config.c b/src/core/nm-proxy-config.c new file mode 100644 index 0000000..85bd047 --- /dev/null +++ b/src/core/nm-proxy-config.c @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Atul Anand . + */ + +#include "nm-default.h" + +#include "nm-proxy-config.h" + +#include + +#include "nm-core-internal.h" + +/*****************************************************************************/ + +typedef struct { + NMProxyConfigMethod method; + gboolean browser_only; + char * pac_url; + char * pac_script; +} NMProxyConfigPrivate; + +struct _NMProxyConfig { + GObject parent; + NMProxyConfigPrivate _priv; +}; + +struct _NMProxyConfigClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMProxyConfig, nm_proxy_config, G_TYPE_OBJECT) + +#define NM_PROXY_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMProxyConfig, NM_IS_PROXY_CONFIG) + +/*****************************************************************************/ + +void +nm_proxy_config_set_method(NMProxyConfig *config, NMProxyConfigMethod method) +{ + NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + priv->method = method; +} + +NMProxyConfigMethod +nm_proxy_config_get_method(const NMProxyConfig *config) +{ + const NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + return priv->method; +} + +void +nm_proxy_config_merge_setting(NMProxyConfig *config, NMSettingProxy *setting) +{ + const char * tmp = NULL; + NMProxyConfigPrivate *priv; + NMSettingProxyMethod method; + + if (!setting) + return; + + g_return_if_fail(NM_IS_SETTING_PROXY(setting)); + + priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + nm_clear_g_free(&priv->pac_script); + + method = nm_setting_proxy_get_method(setting); + switch (method) { + case NM_SETTING_PROXY_METHOD_AUTO: + priv->method = NM_PROXY_CONFIG_METHOD_AUTO; + + /* Free DHCP Obtained PAC Url (i.e Option 252) + * only when libnm overrides it. + */ + tmp = nm_setting_proxy_get_pac_url(setting); + if (tmp) { + g_free(priv->pac_url); + priv->pac_url = g_strdup(tmp); + } + + tmp = nm_setting_proxy_get_pac_script(setting); + priv->pac_script = g_strdup(tmp); + + break; + case NM_SETTING_PROXY_METHOD_NONE: + priv->method = NM_PROXY_CONFIG_METHOD_NONE; + break; + } + + priv->browser_only = nm_setting_proxy_get_browser_only(setting); +} + +gboolean +nm_proxy_config_get_browser_only(const NMProxyConfig *config) +{ + const NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + return priv->browser_only; +} + +void +nm_proxy_config_set_pac_url(NMProxyConfig *config, const char *url) +{ + NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + g_free(priv->pac_url); + priv->pac_url = g_strdup(url); +} + +const char * +nm_proxy_config_get_pac_url(const NMProxyConfig *config) +{ + const NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + return priv->pac_url; +} + +void +nm_proxy_config_set_pac_script(NMProxyConfig *config, const char *script) +{ + NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + g_free(priv->pac_script); + priv->pac_script = g_strdup(script); +} + +const char * +nm_proxy_config_get_pac_script(const NMProxyConfig *config) +{ + const NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + return priv->pac_script; +} + +/*****************************************************************************/ + +static void +nm_proxy_config_init(NMProxyConfig *config) +{ + NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(config); + + priv->method = NM_PROXY_CONFIG_METHOD_NONE; +} + +NMProxyConfig * +nm_proxy_config_new(void) +{ + return NM_PROXY_CONFIG(g_object_new(NM_TYPE_PROXY_CONFIG, NULL)); +} + +static void +finalize(GObject *object) +{ + NMProxyConfig * self = NM_PROXY_CONFIG(object); + NMProxyConfigPrivate *priv = NM_PROXY_CONFIG_GET_PRIVATE(self); + + g_free(priv->pac_url); + g_free(priv->pac_script); + + G_OBJECT_CLASS(nm_proxy_config_parent_class)->finalize(object); +} + +static void +nm_proxy_config_class_init(NMProxyConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = finalize; +} diff --git a/src/core/nm-proxy-config.h b/src/core/nm-proxy-config.h new file mode 100644 index 0000000..7aa587d --- /dev/null +++ b/src/core/nm-proxy-config.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Atul Anand . + */ + +#ifndef __NETWORKMANAGER_PROXY_CONFIG_H__ +#define __NETWORKMANAGER_PROXY_CONFIG_H__ + +#include "nm-setting-proxy.h" + +typedef enum { NM_PROXY_CONFIG_METHOD_AUTO = 0, NM_PROXY_CONFIG_METHOD_NONE } NMProxyConfigMethod; + +#define NM_TYPE_PROXY_CONFIG (nm_proxy_config_get_type()) +#define NM_PROXY_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PROXY_CONFIG, NMProxyConfig)) +#define NM_PROXY_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PROXY_CONFIG, NMProxyConfigClass)) +#define NM_IS_PROXY_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PROXY_CONFIG)) +#define NM_IS_PROXY_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PROXY_CONFIG)) +#define NM_PROXY_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PROXY_CONFIG, NMProxyConfigClass)) + +typedef struct _NMProxyConfigClass NMProxyConfigClass; + +GType nm_proxy_config_get_type(void); + +NMProxyConfig *nm_proxy_config_new(void); + +void nm_proxy_config_set_method(NMProxyConfig *config, NMProxyConfigMethod method); +NMProxyConfigMethod nm_proxy_config_get_method(const NMProxyConfig *config); + +void nm_proxy_config_merge_setting(NMProxyConfig *config, NMSettingProxy *setting); + +gboolean nm_proxy_config_get_browser_only(const NMProxyConfig *config); + +void nm_proxy_config_set_pac_url(NMProxyConfig *config, const char *url); +const char *nm_proxy_config_get_pac_url(const NMProxyConfig *config); + +void nm_proxy_config_set_pac_script(NMProxyConfig *config, const char *script); +const char *nm_proxy_config_get_pac_script(const NMProxyConfig *config); + +#endif /* __NETWORKMANAGER_PROXY_CONFIG_H__ */ diff --git a/src/core/nm-rfkill-manager.c b/src/core/nm-rfkill-manager.c new file mode 100644 index 0000000..8fd9dbf --- /dev/null +++ b/src/core/nm-rfkill-manager.c @@ -0,0 +1,431 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-rfkill-manager.h" + +#include + +#include "nm-udev-aux/nm-udev-utils.h" + +/*****************************************************************************/ + +enum { + RFKILL_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + NMUdevClient *udev_client; + + /* Authoritative rfkill state (RFKILL_* enum) */ + RfKillState rfkill_states[RFKILL_TYPE_MAX]; + GSList * killswitches; +} NMRfkillManagerPrivate; + +struct _NMRfkillManager { + GObject parent; + NMRfkillManagerPrivate _priv; +}; + +struct _NMRfkillManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMRfkillManager, nm_rfkill_manager, G_TYPE_OBJECT) + +#define NM_RFKILL_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMRfkillManager, NM_IS_RFKILL_MANAGER) + +/*****************************************************************************/ + +typedef struct { + char * name; + guint64 seqnum; + char * path; + char * driver; + RfKillType rtype; + int state; + gboolean platform; +} Killswitch; + +RfKillState +nm_rfkill_manager_get_rfkill_state(NMRfkillManager *self, RfKillType rtype) +{ + g_return_val_if_fail(self != NULL, RFKILL_UNBLOCKED); + g_return_val_if_fail(rtype < RFKILL_TYPE_MAX, RFKILL_UNBLOCKED); + + return NM_RFKILL_MANAGER_GET_PRIVATE(self)->rfkill_states[rtype]; +} + +static const char * +rfkill_type_to_desc(RfKillType rtype) +{ + if (rtype == 0) + return "Wi-Fi"; + else if (rtype == 1) + return "WWAN"; + else if (rtype == 2) + return "WiMAX"; + return "unknown"; +} + +static const char * +rfkill_state_to_desc(RfKillState rstate) +{ + if (rstate == 0) + return "unblocked"; + else if (rstate == 1) + return "soft-blocked"; + else if (rstate == 2) + return "hard-blocked"; + return "unknown"; +} + +static Killswitch * +killswitch_new(struct udev_device *device, RfKillType rtype) +{ + Killswitch * ks; + struct udev_device *parent = NULL, *grandparent = NULL; + const char * driver, *subsys, *parent_subsys = NULL; + + ks = g_malloc0(sizeof(Killswitch)); + ks->name = g_strdup(udev_device_get_sysname(device)); + ks->seqnum = udev_device_get_seqnum(device); + ks->path = g_strdup(udev_device_get_syspath(device)); + ks->rtype = rtype; + + driver = udev_device_get_property_value(device, "DRIVER"); + subsys = udev_device_get_subsystem(device); + + /* Check parent for various attributes */ + parent = udev_device_get_parent(device); + if (parent) { + parent_subsys = udev_device_get_subsystem(parent); + if (!driver) + driver = udev_device_get_property_value(parent, "DRIVER"); + if (!driver) { + /* Sigh; try the grandparent */ + grandparent = udev_device_get_parent(parent); + if (grandparent) + driver = udev_device_get_property_value(grandparent, "DRIVER"); + } + } + + if (!driver) + driver = "(unknown)"; + ks->driver = g_strdup(driver); + + if (g_strcmp0(subsys, "platform") == 0 || g_strcmp0(parent_subsys, "platform") == 0 + || g_strcmp0(subsys, "acpi") == 0 || g_strcmp0(parent_subsys, "acpi") == 0) + ks->platform = TRUE; + + return ks; +} + +static void +killswitch_destroy(Killswitch *ks) +{ + g_return_if_fail(ks != NULL); + + g_free(ks->name); + g_free(ks->path); + g_free(ks->driver); + memset(ks, 0, sizeof(Killswitch)); + g_free(ks); +} + +static RfKillState +sysfs_state_to_nm_state(int sysfs_state) +{ + switch (sysfs_state) { + case 0: + return RFKILL_SOFT_BLOCKED; + case 1: + return RFKILL_UNBLOCKED; + case 2: + return RFKILL_HARD_BLOCKED; + default: + nm_log_warn(LOGD_RFKILL, "unhandled rfkill state %d", sysfs_state); + break; + } + return RFKILL_UNBLOCKED; +} + +static void +recheck_killswitches(NMRfkillManager *self) +{ + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + GSList * iter; + RfKillState poll_states[RFKILL_TYPE_MAX]; + RfKillState platform_states[RFKILL_TYPE_MAX]; + gboolean platform_checked[RFKILL_TYPE_MAX]; + int i; + + /* Default state is unblocked */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + poll_states[i] = RFKILL_UNBLOCKED; + platform_states[i] = RFKILL_UNBLOCKED; + platform_checked[i] = FALSE; + } + + /* Poll the states of all killswitches */ + for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) { + Killswitch * ks = iter->data; + struct udev_device *device; + RfKillState dev_state; + int sysfs_state; + + device = udev_device_new_from_subsystem_sysname(nm_udev_client_get_udev(priv->udev_client), + "rfkill", + ks->name); + if (!device) + continue; + sysfs_state = + _nm_utils_ascii_str_to_int64(udev_device_get_property_value(device, "RFKILL_STATE"), + 10, + G_MININT, + G_MAXINT, + -1); + dev_state = sysfs_state_to_nm_state(sysfs_state); + + nm_log_dbg(LOGD_RFKILL, + "%s rfkill%s switch %s state now %d/%u", + rfkill_type_to_desc(ks->rtype), + ks->platform ? " platform" : "", + ks->name, + sysfs_state, + dev_state); + + if (ks->platform == FALSE) { + if (dev_state > poll_states[ks->rtype]) + poll_states[ks->rtype] = dev_state; + } else { + platform_checked[ks->rtype] = TRUE; + if (dev_state > platform_states[ks->rtype]) + platform_states[ks->rtype] = dev_state; + } + udev_device_unref(device); + } + + /* Log and emit change signal for final rfkill states */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + if (platform_checked[i] == TRUE) { + /* blocked platform switch state overrides device state, otherwise + * let the device state stand. (bgo #655773) + */ + if (platform_states[i] != RFKILL_UNBLOCKED) + poll_states[i] = platform_states[i]; + } + + if (poll_states[i] != priv->rfkill_states[i]) { + nm_log_dbg(LOGD_RFKILL, + "%s rfkill state now '%s'", + rfkill_type_to_desc(i), + rfkill_state_to_desc(poll_states[i])); + + priv->rfkill_states[i] = poll_states[i]; + g_signal_emit(self, signals[RFKILL_CHANGED], 0, i, priv->rfkill_states[i]); + } + } +} + +static Killswitch * +killswitch_find_by_name(NMRfkillManager *self, const char *name) +{ + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + GSList * iter; + + g_return_val_if_fail(name != NULL, NULL); + + for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) { + Killswitch *candidate = iter->data; + + if (!strcmp(name, candidate->name)) + return candidate; + } + return NULL; +} + +static RfKillType +rfkill_type_to_enum(const char *str) +{ + g_return_val_if_fail(str != NULL, RFKILL_TYPE_UNKNOWN); + + if (!strcmp(str, "wlan")) + return RFKILL_TYPE_WLAN; + else if (!strcmp(str, "wwan")) + return RFKILL_TYPE_WWAN; + + return RFKILL_TYPE_UNKNOWN; +} + +static void +add_one_killswitch(NMRfkillManager *self, struct udev_device *device) +{ + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + const char * str_type; + RfKillType rtype; + Killswitch * ks; + + str_type = udev_device_get_property_value(device, "RFKILL_TYPE"); + rtype = rfkill_type_to_enum(str_type); + if (rtype == RFKILL_TYPE_UNKNOWN) + return; + + ks = killswitch_new(device, rtype); + priv->killswitches = g_slist_prepend(priv->killswitches, ks); + + nm_log_info(LOGD_RFKILL, + "%s: found %s radio killswitch (at %s) (%sdriver %s)", + ks->name, + rfkill_type_to_desc(rtype), + ks->path, + ks->platform ? "platform " : "", + ks->driver ?: ""); +} + +static void +rfkill_add(NMRfkillManager *self, struct udev_device *device) +{ + const char *name; + + g_return_if_fail(device != NULL); + name = udev_device_get_sysname(device); + g_return_if_fail(name != NULL); + + if (!killswitch_find_by_name(self, name)) + add_one_killswitch(self, device); +} + +static void +rfkill_remove(NMRfkillManager *self, struct udev_device *device) +{ + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + GSList * iter; + const char * name; + + g_return_if_fail(device != NULL); + name = udev_device_get_sysname(device); + g_return_if_fail(name != NULL); + + for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) { + Killswitch *ks = iter->data; + + if (!strcmp(ks->name, name)) { + nm_log_info(LOGD_RFKILL, "radio killswitch %s disappeared", ks->path); + priv->killswitches = g_slist_remove(priv->killswitches, ks); + killswitch_destroy(ks); + break; + } + } +} + +static void +handle_uevent(NMUdevClient *client, struct udev_device *device, gpointer user_data) +{ + NMRfkillManager *self = NM_RFKILL_MANAGER(user_data); + const char * subsys; + const char * action; + + action = udev_device_get_action(device); + + g_return_if_fail(action != NULL); + + /* A bit paranoid */ + subsys = udev_device_get_subsystem(device); + g_return_if_fail(!g_strcmp0(subsys, "rfkill")); + + nm_log_dbg(LOGD_PLATFORM, + "udev rfkill event: action '%s' device '%s'", + action, + udev_device_get_sysname(device)); + + if (!strcmp(action, "add")) + rfkill_add(self, device); + else if (!strcmp(action, "remove")) + rfkill_remove(self, device); + + recheck_killswitches(self); +} + +/*****************************************************************************/ + +static void +nm_rfkill_manager_init(NMRfkillManager *self) +{ + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + struct udev_enumerate * enumerate; + struct udev_list_entry *iter; + guint i; + + for (i = 0; i < RFKILL_TYPE_MAX; i++) + priv->rfkill_states[i] = RFKILL_UNBLOCKED; + + priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("rfkill"), handle_uevent, self); + + enumerate = nm_udev_client_enumerate_new(priv->udev_client); + udev_enumerate_scan_devices(enumerate); + iter = udev_enumerate_get_list_entry(enumerate); + for (; iter; iter = udev_list_entry_get_next(iter)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(iter)); + if (!udevice) + continue; + + add_one_killswitch(self, udevice); + udev_device_unref(udevice); + } + udev_enumerate_unref(enumerate); + + recheck_killswitches(self); +} + +NMRfkillManager * +nm_rfkill_manager_new(void) +{ + return NM_RFKILL_MANAGER(g_object_new(NM_TYPE_RFKILL_MANAGER, NULL)); +} + +static void +dispose(GObject *object) +{ + NMRfkillManager * self = NM_RFKILL_MANAGER(object); + NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self); + + if (priv->killswitches) { + g_slist_free_full(priv->killswitches, (GDestroyNotify) killswitch_destroy); + priv->killswitches = NULL; + } + + priv->udev_client = nm_udev_client_destroy(priv->udev_client); + + G_OBJECT_CLASS(nm_rfkill_manager_parent_class)->dispose(object); +} + +static void +nm_rfkill_manager_class_init(NMRfkillManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + + signals[RFKILL_CHANGED] = g_signal_new(NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT, + G_TYPE_UINT); +} diff --git a/src/core/nm-rfkill-manager.h b/src/core/nm-rfkill-manager.h new file mode 100644 index 0000000..d3f6634 --- /dev/null +++ b/src/core/nm-rfkill-manager.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#ifndef __NM_RFKILL_MANAGER_H__ +#define __NM_RFKILL_MANAGER_H__ + +typedef enum { /*< skip >*/ + RFKILL_UNBLOCKED = 0, + RFKILL_SOFT_BLOCKED = 1, + RFKILL_HARD_BLOCKED = 2 +} RfKillState; + +typedef enum { /*< skip >*/ + RFKILL_TYPE_WLAN = 0, + RFKILL_TYPE_WWAN = 1, + + /* UNKNOWN and MAX should always be 1 more than + * the last rfkill type since RFKILL_TYPE_MAX is + * used as an array size. + */ + RFKILL_TYPE_UNKNOWN, /* KEEP LAST */ + RFKILL_TYPE_MAX = RFKILL_TYPE_UNKNOWN +} RfKillType; + +#define NM_TYPE_RFKILL_MANAGER (nm_rfkill_manager_get_type()) +#define NM_RFKILL_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_RFKILL_MANAGER, NMRfkillManager)) +#define NM_RFKILL_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_RFKILL_MANAGER, NMRfkillManagerClass)) +#define NM_IS_RFKILL_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_RFKILL_MANAGER)) +#define NM_IS_RFKILL_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_RFKILL_MANAGER)) +#define NM_RFKILL_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_RFKILL_MANAGER, NMRfkillManagerClass)) + +#define NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED "rfkill-changed" + +typedef struct _NMRfkillManagerClass NMRfkillManagerClass; + +GType nm_rfkill_manager_get_type(void); + +NMRfkillManager *nm_rfkill_manager_new(void); + +RfKillState nm_rfkill_manager_get_rfkill_state(NMRfkillManager *manager, RfKillType rtype); + +#endif /* __NM_RFKILL_MANAGER_H__ */ diff --git a/src/core/nm-session-monitor.c b/src/core/nm-session-monitor.c new file mode 100644 index 0000000..8e66dbe --- /dev/null +++ b/src/core/nm-session-monitor.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2015 Red Hat, Inc. + * Author: David Zeuthen + * Author: Dan Williams + * Author: Matthias Clasen + * Author: Pavel Šimerda + */ +#include "nm-default.h" + +#include "nm-session-monitor.h" + +#include +#include + +#if SESSION_TRACKING_SYSTEMD && SESSION_TRACKING_ELOGIND + #error Cannot build both systemd-logind and elogind support +#endif + +#if SESSION_TRACKING_SYSTEMD + #include + #define LOGIND_NAME "systemd-logind" +#endif + +#if SESSION_TRACKING_ELOGIND + #include + #define LOGIND_NAME "elogind" +#endif + +#include "NetworkManagerUtils.h" + +#define SESSION_TRACKING_XLOGIND (SESSION_TRACKING_SYSTEMD || SESSION_TRACKING_ELOGIND) + +#define CKDB_PATH "/run/ConsoleKit/database" + +/*****************************************************************************/ + +enum { + CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +struct _NMSessionMonitor { + GObject parent; + +#if SESSION_TRACKING_XLOGIND + struct { + sd_login_monitor *monitor; + GSource * watch; + } sd; +#endif + +#if SESSION_TRACKING_CONSOLEKIT + struct { + GFileMonitor *monitor; + GHashTable * cache; + time_t timestamp; + } ck; +#endif +}; + +struct _NMSessionMonitorClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSessionMonitor, nm_session_monitor, G_TYPE_OBJECT); + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "session-monitor", __VA_ARGS__) + +/*****************************************************************************/ + +#if SESSION_TRACKING_XLOGIND +static gboolean +st_sd_session_exists(NMSessionMonitor *monitor, uid_t uid, gboolean active) +{ + int status; + + if (!monitor->sd.monitor) + return FALSE; + + status = sd_uid_get_sessions(uid, active, NULL); + + if (status < 0) + _LOGE("failed to get " LOGIND_NAME " sessions for uid %d: %d", uid, status); + + return status > 0; +} + +static gboolean +st_sd_changed(int fd, GIOCondition condition, gpointer user_data) +{ + NMSessionMonitor *monitor = user_data; + + g_signal_emit(monitor, signals[CHANGED], 0); + + sd_login_monitor_flush(monitor->sd.monitor); + + return G_SOURCE_CONTINUE; +} + +static void +st_sd_init(NMSessionMonitor *monitor) +{ + int status; + + if (!g_file_test("/run/systemd/seats/", G_FILE_TEST_EXISTS)) + return; + + if ((status = sd_login_monitor_new(NULL, &monitor->sd.monitor)) < 0) { + _LOGE("failed to create " LOGIND_NAME " monitor: %d", status); + return; + } + + monitor->sd.watch = nm_g_unix_fd_source_new(sd_login_monitor_get_fd(monitor->sd.monitor), + G_IO_IN, + G_PRIORITY_DEFAULT, + st_sd_changed, + monitor, + NULL); + g_source_attach(monitor->sd.watch, NULL); +} + +static void +st_sd_finalize(NMSessionMonitor *monitor) +{ + if (monitor->sd.monitor) { + sd_login_monitor_unref(monitor->sd.monitor); + monitor->sd.monitor = NULL; + } + nm_clear_g_source_inst(&monitor->sd.watch); +} +#endif /* SESSION_TRACKING_XLOGIND */ + +/*****************************************************************************/ + +#if SESSION_TRACKING_CONSOLEKIT +typedef struct { + gboolean active; +} CkSession; + +static gboolean +ck_load_cache(GHashTable *cache) +{ + GKeyFile *keyfile = g_key_file_new(); + char ** groups = NULL; + GError * error = NULL; + gsize i, len; + gboolean finished = FALSE; + + if (!g_key_file_load_from_file(keyfile, CKDB_PATH, G_KEY_FILE_NONE, &error)) + goto out; + + if (!(groups = g_key_file_get_groups(keyfile, &len))) { + _LOGE("could not load groups from " CKDB_PATH); + goto out; + } + + g_hash_table_remove_all(cache); + + for (i = 0; i < len; i++) { + guint uid = G_MAXUINT; + CkSession session = {.active = FALSE}; + + if (!g_str_has_prefix(groups[i], "Session ")) + continue; + + uid = g_key_file_get_integer(keyfile, groups[i], "uid", &error); + if (error) + goto out; + + session.active = g_key_file_get_boolean(keyfile, groups[i], "is_active", &error); + if (error) + goto out; + + g_hash_table_insert(cache, GUINT_TO_POINTER(uid), nm_memdup(&session, sizeof session)); + } + + finished = TRUE; +out: + if (error) + _LOGE("failed to load ConsoleKit database: %s", error->message); + g_clear_error(&error); + nm_clear_pointer(&groups, g_strfreev); + nm_clear_pointer(&keyfile, g_key_file_free); + + return finished; +} + +static gboolean +ck_update_cache(NMSessionMonitor *monitor) +{ + struct stat statbuf; + int errsv; + + if (!monitor->ck.cache) + return FALSE; + + /* Check the database file */ + if (stat(CKDB_PATH, &statbuf) != 0) { + errsv = errno; + _LOGE("failed to check ConsoleKit timestamp: %s", nm_strerror_native(errsv)); + return FALSE; + } + if (statbuf.st_mtime == monitor->ck.timestamp) + return TRUE; + + /* Update the cache */ + if (!ck_load_cache(monitor->ck.cache)) + return FALSE; + + monitor->ck.timestamp = statbuf.st_mtime; + + return TRUE; +} + +static gboolean +ck_session_exists(NMSessionMonitor *monitor, uid_t uid, gboolean active) +{ + CkSession *session; + + if (!ck_update_cache(monitor)) + return FALSE; + + session = g_hash_table_lookup(monitor->ck.cache, GUINT_TO_POINTER(uid)); + + if (!session) + return FALSE; + if (active && !session->active) + return FALSE; + + return TRUE; +} + +static void +ck_changed(GFileMonitor * file_monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + g_signal_emit(user_data, signals[CHANGED], 0); +} + +static void +ck_init(NMSessionMonitor *monitor) +{ + GFile * file = g_file_new_for_path(CKDB_PATH); + GError *error = NULL; + + if (g_file_query_exists(file, NULL)) { + if ((monitor->ck.monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, &error))) { + monitor->ck.cache = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_free); + g_signal_connect(monitor->ck.monitor, "changed", G_CALLBACK(ck_changed), monitor); + } else { + _LOGE("error monitoring " CKDB_PATH ": %s", error->message); + g_clear_error(&error); + } + } + + g_object_unref(file); +} + +static void +ck_finalize(NMSessionMonitor *monitor) +{ + nm_clear_pointer(&monitor->ck.cache, g_hash_table_unref); + g_clear_object(&monitor->ck.monitor); +} +#endif /* SESSION_TRACKING_CONSOLEKIT */ + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMSessionMonitor, nm_session_monitor_get, NM_TYPE_SESSION_MONITOR); + +/** + * nm_session_monitor_session_exists: + * @self: the session monitor + * @uid: A user ID. + * @active: Ignore inactive sessions. + * + * Checks whether the given @uid is logged into an active session. Don't + * use this feature for security purposes. It is there just to allow you + * to prefer an agent from an active session over an agent from an + * inactive one. + * + * Returns: %FALSE if @error is set otherwise %TRUE if the given @uid is + * logged into an active session. + */ +gboolean +nm_session_monitor_session_exists(NMSessionMonitor *self, uid_t uid, gboolean active) +{ + g_return_val_if_fail(NM_IS_SESSION_MONITOR(self), FALSE); + +#if SESSION_TRACKING_XLOGIND + if (st_sd_session_exists(self, uid, active)) + return TRUE; +#endif + +#if SESSION_TRACKING_CONSOLEKIT + if (ck_session_exists(self, uid, active)) + return TRUE; +#endif + + return FALSE; +} + +/*****************************************************************************/ + +static void +nm_session_monitor_init(NMSessionMonitor *monitor) +{ +#if SESSION_TRACKING_XLOGIND + st_sd_init(monitor); + _LOGD("using " LOGIND_NAME " session tracking"); +#endif + +#if SESSION_TRACKING_CONSOLEKIT + ck_init(monitor); + _LOGD("using ConsoleKit session tracking"); +#endif +} + +static void +finalize(GObject *object) +{ +#if SESSION_TRACKING_XLOGIND + st_sd_finalize(NM_SESSION_MONITOR(object)); +#endif + +#if SESSION_TRACKING_CONSOLEKIT + ck_finalize(NM_SESSION_MONITOR(object)); +#endif + + G_OBJECT_CLASS(nm_session_monitor_parent_class)->finalize(object); +} + +static void +nm_session_monitor_class_init(NMSessionMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->finalize = finalize; + + /** + * NMSessionMonitor::changed: + * @monitor: A #NMSessionMonitor + * + * Emitted when something changes. + */ + signals[CHANGED] = g_signal_new(NM_SESSION_MONITOR_CHANGED, + NM_TYPE_SESSION_MONITOR, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/nm-session-monitor.h b/src/core/nm-session-monitor.h new file mode 100644 index 0000000..63a3136 --- /dev/null +++ b/src/core/nm-session-monitor.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2010 Red Hat, Inc. + * Author: David Zeuthen + * Author: Dan Williams + */ + +#ifndef __NETWORKMANAGER_SESSION_MONITOR_H__ +#define __NETWORKMANAGER_SESSION_MONITOR_H__ + +#define NM_TYPE_SESSION_MONITOR (nm_session_monitor_get_type()) +#define NM_SESSION_MONITOR(o) \ + (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_SESSION_MONITOR, NMSessionMonitor)) +#define NM_SESSION_MONITOR_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_SESSION_MONITOR, NMSessionMonitorClass)) +#define NM_SESSION_MONITOR_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_SESSION_MONITOR, NMSessionMonitorClass)) +#define NM_IS_SESSION_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_SESSION_MONITOR)) +#define NM_IS_SESSION_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), NM_TYPE_SESSION_MONITOR)) + +#define NM_SESSION_MONITOR_CHANGED "changed" + +typedef struct _NMSessionMonitorClass NMSessionMonitorClass; + +GType nm_session_monitor_get_type(void) G_GNUC_CONST; + +NMSessionMonitor *nm_session_monitor_get(void); + +gboolean nm_session_monitor_session_exists(NMSessionMonitor *self, uid_t uid, gboolean active); + +#endif /* __NETWORKMANAGER_SESSION_MONITOR_H__ */ diff --git a/src/core/nm-sleep-monitor.c b/src/core/nm-sleep-monitor.c new file mode 100644 index 0000000..cc3d72e --- /dev/null +++ b/src/core/nm-sleep-monitor.c @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2016 Red Hat, Inc. + * Author: Matthias Clasen + */ + +#include "nm-default.h" + +#include "nm-sleep-monitor.h" + +#include +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +#if defined(SUSPEND_RESUME_UPOWER) + + #define SUSPEND_DBUS_NAME "org.freedesktop.UPower" + #define SUSPEND_DBUS_PATH "/org/freedesktop/UPower" + #define SUSPEND_DBUS_INTERFACE "org.freedesktop.UPower" + #define USE_UPOWER 1 + #define _NMLOG_PREFIX_NAME "sleep-monitor-up" + +#elif defined(SUSPEND_RESUME_SYSTEMD) || defined(SUSPEND_RESUME_ELOGIND) + + #define SUSPEND_DBUS_NAME "org.freedesktop.login1" + #define SUSPEND_DBUS_PATH "/org/freedesktop/login1" + #define SUSPEND_DBUS_INTERFACE "org.freedesktop.login1.Manager" + #define USE_UPOWER 0 + #if defined(SUSPEND_RESUME_SYSTEMD) + #define _NMLOG_PREFIX_NAME "sleep-monitor-sd" + #else + #define _NMLOG_PREFIX_NAME "sleep-monitor-el" + #endif + +#elif defined(SUSPEND_RESUME_CONSOLEKIT) + +/* ConsoleKit2 has added the same suspend/resume DBUS API that Systemd + * uses. http://consolekit2.github.io/ConsoleKit2/#Manager.Inhibit + */ + + #define SUSPEND_DBUS_NAME "org.freedesktop.ConsoleKit" + #define SUSPEND_DBUS_PATH "/org/freedesktop/ConsoleKit/Manager" + #define SUSPEND_DBUS_INTERFACE "org.freedesktop.ConsoleKit.Manager" + #define USE_UPOWER 0 + #define _NMLOG_PREFIX_NAME "sleep-monitor-ck" + +#else + + #error define one of SUSPEND_RESUME_SYSTEMD, SUSPEND_RESUME_ELOGIND, SUSPEND_RESUME_CONSOLEKIT, or SUSPEND_RESUME_UPOWER + +#endif + +/*****************************************************************************/ + +enum { + SLEEPING, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +struct _NMSleepMonitor { + GObject parent; + + GDBusProxy *proxy; + + /* used both during construction of proxy and during Inhibit call. */ + GCancellable *cancellable; + + int inhibit_fd; + GSList *handles_active; + GSList *handles_stale; + + gulong sig_id_1; + gulong sig_id_2; +}; + +struct _NMSleepMonitorClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSleepMonitor, nm_sleep_monitor, G_TYPE_OBJECT); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SUSPEND +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, _NMLOG_PREFIX_NAME, __VA_ARGS__) + +/*****************************************************************************/ + +static void sleep_signal(NMSleepMonitor *self, gboolean is_about_to_suspend); + +/*****************************************************************************/ + +#if USE_UPOWER + +static void +upower_sleeping_cb(GDBusProxy *proxy, gpointer user_data) +{ + sleep_signal(user_data, TRUE); +} + +static void +upower_resuming_cb(GDBusProxy *proxy, gpointer user_data) +{ + sleep_signal(user_data, FALSE); +} + +#else /* USE_UPOWER */ + +static void +drop_inhibitor(NMSleepMonitor *self, gboolean force) +{ + if (!force && self->handles_active) + return; + + if (self->inhibit_fd >= 0) { + _LOGD("inhibit: dropping sleep inhibitor %d", self->inhibit_fd); + nm_close(self->inhibit_fd); + self->inhibit_fd = -1; + } + + if (self->handles_active) { + self->handles_stale = g_slist_concat(self->handles_stale, self->handles_active); + self->handles_active = NULL; + } + + nm_clear_g_cancellable(&self->cancellable); +} + +static void +inhibit_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + GDBusProxy * proxy = G_DBUS_PROXY(source); + NMSleepMonitor *self = user_data; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *res = NULL; + gs_unref_object GUnixFDList *fd_list = NULL; + + res = g_dbus_proxy_call_with_unix_fd_list_finish(proxy, &fd_list, result, &error); + if (!res) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_clear_object(&self->cancellable); + _LOGW("inhibit: failed (%s)", error->message); + } + return; + } + + g_clear_object(&self->cancellable); + + if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1) { + _LOGW("inhibit: didn't get a single fd back"); + return; + } + + self->inhibit_fd = g_unix_fd_list_get(fd_list, 0, NULL); + _LOGD("inhibit: inhibitor fd is %d", self->inhibit_fd); +} + +static void +take_inhibitor(NMSleepMonitor *self) +{ + g_return_if_fail(NM_IS_SLEEP_MONITOR(self)); + g_return_if_fail(G_IS_DBUS_PROXY(self->proxy)); + + drop_inhibitor(self, TRUE); + + _LOGD("inhibit: taking sleep inhibitor..."); + self->cancellable = g_cancellable_new(); + g_dbus_proxy_call_with_unix_fd_list(self->proxy, + "Inhibit", + g_variant_new("(ssss)", + "sleep", + "NetworkManager", + "NetworkManager needs to turn off networks", + "delay"), + 0, + G_MAXINT, + NULL, + self->cancellable, + inhibit_done, + self); +} + +static void +prepare_for_sleep_cb(GDBusProxy *proxy, gboolean is_about_to_suspend, gpointer data) +{ + sleep_signal(data, is_about_to_suspend); +} + +static void +name_owner_cb(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + GDBusProxy * proxy = G_DBUS_PROXY(object); + NMSleepMonitor *self = NM_SLEEP_MONITOR(user_data); + char * owner; + + g_assert(proxy == self->proxy); + + owner = g_dbus_proxy_get_name_owner(proxy); + if (owner) + take_inhibitor(self); + else + drop_inhibitor(self, TRUE); + g_free(owner); +} +#endif /* USE_UPOWER */ + +static void +sleep_signal(NMSleepMonitor *self, gboolean is_about_to_suspend) +{ + g_return_if_fail(NM_IS_SLEEP_MONITOR(self)); + + _LOGD("received %s signal", is_about_to_suspend ? "SLEEP" : "RESUME"); + +#if !USE_UPOWER + if (!is_about_to_suspend) + take_inhibitor(self); +#endif + + g_signal_emit(self, signals[SLEEPING], 0, is_about_to_suspend); + +#if !USE_UPOWER + if (is_about_to_suspend) + drop_inhibitor(self, FALSE); +#endif +} + +/** + * nm_sleep_monitor_inhibit_take: + * @self: the #NMSleepMonitor instance + * + * Prevent the release of inhibitor lock + * + * Returns: an inhibitor handle that must be returned via + * nm_sleep_monitor_inhibit_release(). + **/ +NMSleepMonitorInhibitorHandle * +nm_sleep_monitor_inhibit_take(NMSleepMonitor *self) +{ + g_return_val_if_fail(NM_IS_SLEEP_MONITOR(self), NULL); + + self->handles_active = g_slist_prepend(self->handles_active, NULL); + return (NMSleepMonitorInhibitorHandle *) self->handles_active; +} + +/** + * nm_sleep_monitor_inhibit_release: + * @self: the #NMSleepMonitor instance + * @handle: the #NMSleepMonitorInhibitorHandle inhibitor handle. + * + * Allow again the release of inhibitor lock + **/ +void +nm_sleep_monitor_inhibit_release(NMSleepMonitor *self, NMSleepMonitorInhibitorHandle *handle) +{ + GSList *l; + + g_return_if_fail(NM_IS_SLEEP_MONITOR(self)); + g_return_if_fail(handle); + + l = (GSList *) handle; + + if (g_slist_position(self->handles_active, l) < 0) { + if (g_slist_position(self->handles_stale, l) < 0) + g_return_if_reached(); + self->handles_stale = g_slist_delete_link(self->handles_stale, l); + return; + } + + self->handles_active = g_slist_delete_link(self->handles_active, l); + +#if !USE_UPOWER + drop_inhibitor(self, FALSE); +#endif +} + +static void +on_proxy_acquired(GObject *object, GAsyncResult *res, NMSleepMonitor *self) +{ + GError * error = NULL; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(res, &error); + if (!proxy) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOGW("failed to acquire D-Bus proxy: %s", error->message); + g_clear_error(&error); + return; + } + self->proxy = proxy; + g_clear_object(&self->cancellable); + +#if USE_UPOWER + self->sig_id_1 = _nm_dbus_signal_connect(self->proxy, + "Sleeping", + NULL, + G_CALLBACK(upower_sleeping_cb), + self); + self->sig_id_2 = _nm_dbus_signal_connect(self->proxy, + "Resuming", + NULL, + G_CALLBACK(upower_resuming_cb), + self); +#else + self->sig_id_1 = + g_signal_connect(self->proxy, "notify::g-name-owner", G_CALLBACK(name_owner_cb), self); + self->sig_id_2 = _nm_dbus_signal_connect(self->proxy, + "PrepareForSleep", + G_VARIANT_TYPE("(b)"), + G_CALLBACK(prepare_for_sleep_cb), + self); + { + gs_free char *owner = NULL; + + owner = g_dbus_proxy_get_name_owner(self->proxy); + if (owner) + take_inhibitor(self); + } +#endif +} + +/*****************************************************************************/ + +static void +nm_sleep_monitor_init(NMSleepMonitor *self) +{ + self->inhibit_fd = -1; + self->cancellable = g_cancellable_new(); + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START + | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + SUSPEND_DBUS_NAME, + SUSPEND_DBUS_PATH, + SUSPEND_DBUS_INTERFACE, + self->cancellable, + (GAsyncReadyCallback) on_proxy_acquired, + self); +} + +NMSleepMonitor * +nm_sleep_monitor_new(void) +{ + return g_object_new(NM_TYPE_SLEEP_MONITOR, NULL); +} + +static void +dispose(GObject *object) +{ + NMSleepMonitor *self = NM_SLEEP_MONITOR(object); + +#if !USE_UPOWER + drop_inhibitor(self, TRUE); +#endif + + nm_clear_g_cancellable(&self->cancellable); + + if (self->proxy) { + nm_clear_g_signal_handler(self->proxy, &self->sig_id_1); + nm_clear_g_signal_handler(self->proxy, &self->sig_id_2); + g_clear_object(&self->proxy); + } + + G_OBJECT_CLASS(nm_sleep_monitor_parent_class)->dispose(object); +} + +static void +nm_sleep_monitor_class_init(NMSleepMonitorClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->dispose = dispose; + + signals[SLEEPING] = g_signal_new(NM_SLEEP_MONITOR_SLEEPING, + NM_TYPE_SLEEP_MONITOR, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} diff --git a/src/core/nm-sleep-monitor.h b/src/core/nm-sleep-monitor.h new file mode 100644 index 0000000..7d45065 --- /dev/null +++ b/src/core/nm-sleep-monitor.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2016 Red Hat, Inc. + * Author: Matthias Clasen + */ + +#ifndef __NETWORKMANAGER_SLEEP_MONITOR_H__ +#define __NETWORKMANAGER_SLEEP_MONITOR_H__ + +#define NM_TYPE_SLEEP_MONITOR (nm_sleep_monitor_get_type()) +#define NM_SLEEP_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_SLEEP_MONITOR, NMSleepMonitor)) +#define NM_SLEEP_MONITOR_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_SLEEP_MONITOR, NMSleepMonitorClass)) +#define NM_SLEEP_MONITOR_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), NM_TYPE_SLEEP_MONITOR, NMSleepMonitorClass)) +#define NM_IS_SLEEP_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_SLEEP_MONITOR)) +#define NM_IS_SLEEP_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), NM_TYPE_SLEEP_MONITOR)) + +#define NM_SLEEP_MONITOR_SLEEPING "sleeping" + +typedef struct _NMSleepMonitorClass NMSleepMonitorClass; + +GType nm_sleep_monitor_get_type(void) G_GNUC_CONST; + +NMSleepMonitor *nm_sleep_monitor_new(void); + +typedef struct _NMSleepMonitorInhibitorHandle NMSleepMonitorInhibitorHandle; + +NMSleepMonitorInhibitorHandle *nm_sleep_monitor_inhibit_take(NMSleepMonitor *self); +void nm_sleep_monitor_inhibit_release(NMSleepMonitor *self, NMSleepMonitorInhibitorHandle *handle); + +#endif /* __NETWORKMANAGER_SLEEP_MONITOR_H__ */ diff --git a/src/core/nm-test-utils-core.h b/src/core/nm-test-utils-core.h new file mode 100644 index 0000000..71d19cc --- /dev/null +++ b/src/core/nm-test-utils-core.h @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#ifndef __NM_TEST_UTILS_CORE_H__ +#define __NM_TEST_UTILS_CORE_H__ + +#include "NetworkManagerUtils.h" +#include "nm-keyfile/nm-keyfile-internal.h" + +#define _NMTST_INSIDE_CORE 1 + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +#define NMTST_EXPECT_NM(level, msg) NMTST_EXPECT("NetworkManager", level, msg) + +#define NMTST_EXPECT_NM_ERROR(msg) NMTST_EXPECT_NM(G_LOG_LEVEL_MESSAGE, "* [*] " msg) +#define NMTST_EXPECT_NM_WARN(msg) NMTST_EXPECT_NM(G_LOG_LEVEL_MESSAGE, "* [*] " msg) +#define NMTST_EXPECT_NM_INFO(msg) NMTST_EXPECT_NM(G_LOG_LEVEL_INFO, "* [*] " msg) +#define NMTST_EXPECT_NM_DEBUG(msg) NMTST_EXPECT_NM(G_LOG_LEVEL_DEBUG, "* [*] " msg) +#define NMTST_EXPECT_NM_TRACE(msg) NMTST_EXPECT_NM(G_LOG_LEVEL_DEBUG, "* [*] " msg) + +static inline void +nmtst_init_with_logging(int *argc, char ***argv, const char *log_level, const char *log_domains) +{ + __nmtst_init(argc, argv, FALSE, log_level, log_domains, NULL); +} +static inline void +nmtst_init_assert_logging(int *argc, char ***argv, const char *log_level, const char *log_domains) +{ + gboolean set_logging; + + __nmtst_init(argc, argv, TRUE, NULL, NULL, &set_logging); + + if (!set_logging) { + gboolean success; + + success = nm_logging_setup(log_level, log_domains, NULL, NULL); + g_assert(success); + } +} + +/*****************************************************************************/ + +#ifdef __NETWORKMANAGER_PLATFORM_H__ + +static inline NMPlatformIP4Address * +nmtst_platform_ip4_address(const char *address, const char *peer_address, guint plen) +{ + static NMPlatformIP4Address addr; + + g_assert(plen <= 32); + + memset(&addr, 0, sizeof(addr)); + addr.address = nmtst_inet4_from_string(address); + if (peer_address) + addr.peer_address = nmtst_inet4_from_string(peer_address); + else + addr.peer_address = addr.address; + addr.plen = plen; + + return &addr; +} + +static inline NMPlatformIP4Address * +nmtst_platform_ip4_address_full(const char * address, + const char * peer_address, + guint plen, + int ifindex, + NMIPConfigSource source, + guint32 timestamp, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char * label) +{ + NMPlatformIP4Address *addr = nmtst_platform_ip4_address(address, peer_address, plen); + + G_STATIC_ASSERT(NMP_IFNAMSIZ == sizeof(addr->label)); + g_assert(!label || strlen(label) < NMP_IFNAMSIZ); + + addr->ifindex = ifindex; + addr->addr_source = source; + addr->timestamp = timestamp; + addr->lifetime = lifetime; + addr->preferred = preferred; + addr->n_ifa_flags = flags; + if (label) + g_strlcpy(addr->label, label, sizeof(addr->label)); + + return addr; +} + +static inline NMPlatformIP6Address * +nmtst_platform_ip6_address(const char *address, const char *peer_address, guint plen) +{ + static NMPlatformIP6Address addr; + + g_assert(plen <= 128); + + memset(&addr, 0, sizeof(addr)); + addr.address = *nmtst_inet6_from_string(address); + addr.peer_address = *nmtst_inet6_from_string(peer_address); + addr.plen = plen; + + return &addr; +} + +static inline NMPlatformIP6Address * +nmtst_platform_ip6_address_full(const char * address, + const char * peer_address, + guint plen, + int ifindex, + NMIPConfigSource source, + guint32 timestamp, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + NMPlatformIP6Address *addr = nmtst_platform_ip6_address(address, peer_address, plen); + + addr->ifindex = ifindex; + addr->addr_source = source; + addr->timestamp = timestamp; + addr->lifetime = lifetime; + addr->preferred = preferred; + addr->n_ifa_flags = flags; + + return addr; +} + +static inline NMPlatformIP4Route * +nmtst_platform_ip4_route(const char *network, guint plen, const char *gateway) +{ + static NMPlatformIP4Route route; + + g_assert(plen <= 32); + + memset(&route, 0, sizeof(route)); + route.network = nmtst_inet4_from_string(network); + route.plen = plen; + route.gateway = nmtst_inet4_from_string(gateway); + + return &route; +} + +static inline NMPlatformIP4Route * +nmtst_platform_ip4_route_full(const char * network, + guint plen, + const char * gateway, + int ifindex, + NMIPConfigSource source, + guint metric, + guint mss, + guint8 scope, + const char * pref_src) +{ + NMPlatformIP4Route *route = nmtst_platform_ip4_route(network, plen, gateway); + + route->ifindex = ifindex; + route->rt_source = source; + route->metric = metric; + route->mss = mss; + route->scope_inv = nm_platform_route_scope_inv(scope); + route->pref_src = nmtst_inet4_from_string(pref_src); + + return route; +} + +static inline NMPlatformIP6Route * +nmtst_platform_ip6_route(const char *network, guint plen, const char *gateway, const char *pref_src) +{ + static NMPlatformIP6Route route; + + nm_assert(plen <= 128); + + memset(&route, 0, sizeof(route)); + route.network = *nmtst_inet6_from_string(network); + route.plen = plen; + route.gateway = *nmtst_inet6_from_string(gateway); + route.pref_src = *nmtst_inet6_from_string(pref_src); + + return &route; +} + +static inline NMPlatformIP6Route * +nmtst_platform_ip6_route_full(const char * network, + guint plen, + const char * gateway, + int ifindex, + NMIPConfigSource source, + guint metric, + guint mss) +{ + NMPlatformIP6Route *route = nmtst_platform_ip6_route(network, plen, gateway, NULL); + + route->ifindex = ifindex; + route->rt_source = source; + route->metric = metric; + route->mss = mss; + + return route; +} + +static inline int +_nmtst_platform_ip4_routes_equal_sort(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return nm_platform_ip4_route_cmp_full((const NMPlatformIP4Route *) a, + (const NMPlatformIP4Route *) b); +} + +static inline void +nmtst_platform_ip4_routes_equal(const NMPlatformIP4Route *a, + const NMPlatformIP4Route *b, + gsize len, + gboolean ignore_order) +{ + gsize i; + gs_free const NMPlatformIP4Route *c_a = NULL, *c_b = NULL; + + g_assert(a); + g_assert(b); + + if (ignore_order) { + a = c_a = nm_memdup(a, sizeof(NMPlatformIP4Route) * len); + b = c_b = nm_memdup(b, sizeof(NMPlatformIP4Route) * len); + g_qsort_with_data(c_a, + len, + sizeof(NMPlatformIP4Route), + _nmtst_platform_ip4_routes_equal_sort, + NULL); + g_qsort_with_data(c_b, + len, + sizeof(NMPlatformIP4Route), + _nmtst_platform_ip4_routes_equal_sort, + NULL); + } + + for (i = 0; i < len; i++) { + if (nm_platform_ip4_route_cmp_full(&a[i], &b[i]) != 0) { + char buf[sizeof(_nm_utils_to_string_buffer)]; + + g_error("Error comparing IPv4 route[%lu]: %s vs %s", + (unsigned long) i, + nm_platform_ip4_route_to_string(&a[i], NULL, 0), + nm_platform_ip4_route_to_string(&b[i], buf, sizeof(buf))); + g_assert_not_reached(); + } + } +} + + #ifdef __NMP_OBJECT_H__ + +static inline void +nmtst_platform_ip4_routes_equal_aptr(const NMPObject *const * a, + const NMPlatformIP4Route *b, + gsize len, + gboolean ignore_order) +{ + gsize i; + gs_free NMPlatformIP4Route *c_a = NULL; + + g_assert(len > 0); + g_assert(a); + + c_a = g_new(NMPlatformIP4Route, len); + for (i = 0; i < len; i++) + c_a[i] = *NMP_OBJECT_CAST_IP4_ROUTE(a[i]); + nmtst_platform_ip4_routes_equal(c_a, b, len, ignore_order); +} + + #endif + +static inline int +_nmtst_platform_ip6_routes_equal_sort(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return nm_platform_ip6_route_cmp_full((const NMPlatformIP6Route *) a, + (const NMPlatformIP6Route *) b); +} + +static inline void +nmtst_platform_ip6_routes_equal(const NMPlatformIP6Route *a, + const NMPlatformIP6Route *b, + gsize len, + gboolean ignore_order) +{ + gsize i; + gs_free const NMPlatformIP6Route *c_a = NULL, *c_b = NULL; + + g_assert(a); + g_assert(b); + + if (ignore_order) { + a = c_a = nm_memdup(a, sizeof(NMPlatformIP6Route) * len); + b = c_b = nm_memdup(b, sizeof(NMPlatformIP6Route) * len); + g_qsort_with_data(c_a, + len, + sizeof(NMPlatformIP6Route), + _nmtst_platform_ip6_routes_equal_sort, + NULL); + g_qsort_with_data(c_b, + len, + sizeof(NMPlatformIP6Route), + _nmtst_platform_ip6_routes_equal_sort, + NULL); + } + + for (i = 0; i < len; i++) { + if (nm_platform_ip6_route_cmp_full(&a[i], &b[i]) != 0) { + char buf[sizeof(_nm_utils_to_string_buffer)]; + + g_error("Error comparing IPv6 route[%lu]: %s vs %s", + (unsigned long) i, + nm_platform_ip6_route_to_string(&a[i], NULL, 0), + nm_platform_ip6_route_to_string(&b[i], buf, sizeof(buf))); + g_assert_not_reached(); + } + } +} + + #ifdef __NMP_OBJECT_H__ + +static inline void +nmtst_platform_ip6_routes_equal_aptr(const NMPObject *const * a, + const NMPlatformIP6Route *b, + gsize len, + gboolean ignore_order) +{ + gsize i; + gs_free NMPlatformIP6Route *c_a = NULL; + + g_assert(len > 0); + g_assert(a); + + c_a = g_new(NMPlatformIP6Route, len); + for (i = 0; i < len; i++) + c_a[i] = *NMP_OBJECT_CAST_IP6_ROUTE(a[i]); + nmtst_platform_ip6_routes_equal(c_a, b, len, ignore_order); +} + + #endif + +#endif + +#ifdef __NETWORKMANAGER_IP4_CONFIG_H__ + + #include "nm-glib-aux/nm-dedup-multi.h" + +static inline NMIP4Config * +nmtst_ip4_config_new(int ifindex) +{ + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new(); + + return nm_ip4_config_new(multi_idx, ifindex); +} + +#endif + +#ifdef __NETWORKMANAGER_IP6_CONFIG_H__ + + #include "nm-glib-aux/nm-dedup-multi.h" + +static inline NMIP6Config * +nmtst_ip6_config_new(int ifindex) +{ + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new(); + + return nm_ip6_config_new(multi_idx, ifindex); +} + +#endif + +#endif /* __NM_TEST_UTILS_CORE_H__ */ diff --git a/src/core/nm-types.h b/src/core/nm-types.h new file mode 100644 index 0000000..ab23145 --- /dev/null +++ b/src/core/nm-types.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2018 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_TYPES_H__ +#define __NETWORKMANAGER_TYPES_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON) + #error Cannot use this header. +#endif + +#define _NM_SD_MAX_CLIENT_ID_LEN (sizeof(guint32) + 128) + +/* core */ +typedef struct _NMDBusObject NMDBusObject; +typedef struct _NMActiveConnection NMActiveConnection; +typedef struct _NMAuditManager NMAuditManager; +typedef struct _NMVpnConnection NMVpnConnection; +typedef struct _NMActRequest NMActRequest; +typedef struct _NMAuthSubject NMAuthSubject; +typedef struct _NMDBusManager NMDBusManager; +typedef struct _NMConfig NMConfig; +typedef struct _NMConfigData NMConfigData; +typedef struct _NMConnectivity NMConnectivity; +typedef struct _NML3Cfg NML3Cfg; +typedef struct _NML3ConfigData NML3ConfigData; +typedef struct _NMDevice NMDevice; +typedef struct _NMDhcpConfig NMDhcpConfig; +typedef struct _NMProxyConfig NMProxyConfig; +typedef struct _NMIPConfig NMIPConfig; +typedef struct _NMIP4Config NMIP4Config; +typedef struct _NMIP6Config NMIP6Config; +typedef struct _NMManager NMManager; +typedef struct _NMNetns NMNetns; +typedef struct _NMPolicy NMPolicy; +typedef struct _NMRfkillManager NMRfkillManager; +typedef struct _NMPacrunnerManager NMPacrunnerManager; +typedef struct _NMSessionMonitor NMSessionMonitor; +typedef struct _NMKeepAlive NMKeepAlive; +typedef struct _NMSleepMonitor NMSleepMonitor; +typedef struct _NMLldpListener NMLldpListener; +typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData; + +typedef void (*NMManagerDeviceAuthRequestFunc)(NMDevice * device, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data); + +struct _NMDedupMultiIndex; + +typedef struct _NMRefString NMRefString; + +/*****************************************************************************/ + +typedef enum { + /* Do a full activation. */ + NM_ACTIVATION_TYPE_MANAGED = 0, + + /* gracefully/seamlessly take over the device. This leaves additional + * IP addresses and does not restore missing manual addresses. */ + NM_ACTIVATION_TYPE_ASSUME = 1, + + /* external activation. This device is not managed by NM, instead + * a in-memory connection is generated and NM pretends the device + * to be active, but it doesn't do anything really. */ + NM_ACTIVATION_TYPE_EXTERNAL = 2, +} NMActivationType; + +typedef enum { + NM_ACTIVATION_REASON_UNSET, + NM_ACTIVATION_REASON_EXTERNAL, + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_REASON_AUTOCONNECT, + NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, + NM_ACTIVATION_REASON_USER_REQUEST, +} NMActivationReason; + +/* platform */ +typedef struct _NMPlatform NMPlatform; +typedef struct _NMPlatformObject NMPlatformObject; +typedef struct _NMPlatformObjWithIfindex NMPlatformObjWithIfindex; +typedef struct _NMPlatformIP4Address NMPlatformIP4Address; +typedef struct _NMPlatformIP4Route NMPlatformIP4Route; +typedef struct _NMPlatformIP6Address NMPlatformIP6Address; +typedef struct _NMPlatformIP6Route NMPlatformIP6Route; +typedef struct _NMPlatformLink NMPlatformLink; +typedef struct _NMPObject NMPObject; + +typedef enum { + + /* No type, used as error value */ + NM_LINK_TYPE_NONE, + + NM_LINK_TYPE_UNKNOWN, + + NM_LINK_TYPE_ANY, + +#define _NM_LINK_TYPE_REAL_FIRST NM_LINK_TYPE_ETHERNET + +/* Hardware types */ +#define _NM_LINK_TYPE_HW_FIRST NM_LINK_TYPE_ETHERNET + NM_LINK_TYPE_ETHERNET, + NM_LINK_TYPE_INFINIBAND, + NM_LINK_TYPE_OLPC_MESH, + NM_LINK_TYPE_WIFI, + NM_LINK_TYPE_WWAN_NET, /* WWAN kernel netdevice */ + NM_LINK_TYPE_WIMAX, + NM_LINK_TYPE_WPAN, + NM_LINK_TYPE_6LOWPAN, + NM_LINK_TYPE_WIFI_P2P, +#define _NM_LINK_TYPE_HW_LAST NM_LINK_TYPE_WIFI_P2P + +/* Software types */ +#define _NM_LINK_TYPE_SW_FIRST NM_LINK_TYPE_BNEP + NM_LINK_TYPE_BNEP, /* Bluetooth Ethernet emulation */ + NM_LINK_TYPE_DUMMY, + NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_IFB, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP, + NM_LINK_TYPE_IPIP, + NM_LINK_TYPE_LOOPBACK, + NM_LINK_TYPE_MACSEC, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_MACVTAP, + NM_LINK_TYPE_OPENVSWITCH, + NM_LINK_TYPE_PPP, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_TUN, + NM_LINK_TYPE_VETH, + NM_LINK_TYPE_VLAN, + NM_LINK_TYPE_VRF, + NM_LINK_TYPE_VXLAN, + NM_LINK_TYPE_WIREGUARD, +#define _NM_LINK_TYPE_SW_LAST NM_LINK_TYPE_WIREGUARD + +/* Software types with slaves */ +#define _NM_LINK_TYPE_SW_MASTER_FIRST NM_LINK_TYPE_BRIDGE + NM_LINK_TYPE_BRIDGE, + NM_LINK_TYPE_BOND, + NM_LINK_TYPE_TEAM, +#define _NM_LINK_TYPE_SW_MASTER_LAST NM_LINK_TYPE_TEAM + +#define _NM_LINK_TYPE_REAL_LAST NM_LINK_TYPE_TEAM + +#define _NM_LINK_TYPE_REAL_NUM ((int) (_NM_LINK_TYPE_REAL_LAST - _NM_LINK_TYPE_REAL_FIRST + 1)) + +} NMLinkType; + +static inline gboolean +nm_link_type_is_software(NMLinkType link_type) +{ + G_STATIC_ASSERT(_NM_LINK_TYPE_SW_LAST + 1 == _NM_LINK_TYPE_SW_MASTER_FIRST); + + return link_type >= _NM_LINK_TYPE_SW_FIRST && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST; +} + +static inline gboolean +nm_link_type_supports_slaves(NMLinkType link_type) +{ + return link_type >= _NM_LINK_TYPE_SW_MASTER_FIRST && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST; +} + +typedef enum { + NMP_OBJECT_TYPE_UNKNOWN, + NMP_OBJECT_TYPE_LINK, + +#define NMP_OBJECT_TYPE_IP_ADDRESS(is_ipv4) \ + ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS) + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + +#define NMP_OBJECT_TYPE_IP_ROUTE(is_ipv4) \ + ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE) + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + + NMP_OBJECT_TYPE_ROUTING_RULE, + + NMP_OBJECT_TYPE_QDISC, + + NMP_OBJECT_TYPE_TFILTER, + + NMP_OBJECT_TYPE_LNK_BRIDGE, + NMP_OBJECT_TYPE_LNK_GRE, + NMP_OBJECT_TYPE_LNK_GRETAP, + NMP_OBJECT_TYPE_LNK_INFINIBAND, + NMP_OBJECT_TYPE_LNK_IP6TNL, + NMP_OBJECT_TYPE_LNK_IP6GRE, + NMP_OBJECT_TYPE_LNK_IP6GRETAP, + NMP_OBJECT_TYPE_LNK_IPIP, + NMP_OBJECT_TYPE_LNK_MACSEC, + NMP_OBJECT_TYPE_LNK_MACVLAN, + NMP_OBJECT_TYPE_LNK_MACVTAP, + NMP_OBJECT_TYPE_LNK_SIT, + NMP_OBJECT_TYPE_LNK_TUN, + NMP_OBJECT_TYPE_LNK_VLAN, + NMP_OBJECT_TYPE_LNK_VRF, + NMP_OBJECT_TYPE_LNK_VXLAN, + NMP_OBJECT_TYPE_LNK_WIREGUARD, + + __NMP_OBJECT_TYPE_LAST, + NMP_OBJECT_TYPE_MAX = __NMP_OBJECT_TYPE_LAST - 1, +} NMPObjectType; + +static inline guint32 +nmp_object_type_to_flags(NMPObjectType obj_type) +{ + G_STATIC_ASSERT_EXPR(NMP_OBJECT_TYPE_MAX < 32); + + nm_assert(_NM_INT_NOT_NEGATIVE(obj_type)); + nm_assert(obj_type < NMP_OBJECT_TYPE_MAX); + + return ((guint32) 1u) << obj_type; +} + +/** + * NMIPConfigMergeFlags: + * @NM_IP_CONFIG_MERGE_DEFAULT: no flags set + * @NM_IP_CONFIG_MERGE_NO_ROUTES: don't merge routes + * @NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES: don't merge default routes. + * Note that if the source IP config has NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES + * set, this flag gets ignored during merge. + * @NM_IP_CONFIG_MERGE_NO_DNS: don't merge DNS information + * @NM_IP_CONFIG_MERGE_EXTERNAL: mark new addresses as external + */ +typedef enum { + NM_IP_CONFIG_MERGE_DEFAULT = 0, + NM_IP_CONFIG_MERGE_NO_ROUTES = (1LL << 0), + NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES = (1LL << 1), + NM_IP_CONFIG_MERGE_NO_DNS = (1LL << 2), + NM_IP_CONFIG_MERGE_EXTERNAL = (1LL << 3), +} NMIPConfigMergeFlags; + +/** + * NMIPRouteTableSyncMode: + * @NM_IP_ROUTE_TABLE_SYNC_MODE_NONE: indicate an invalid setting. + * @NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN: only the main table is synced. For all + * other tables, NM won't delete any extra routes. + * @NM_IP_ROUTE_TABLE_SYNC_MODE_FULL: NM will sync all tables, except the + * local table (255). + * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL: NM will sync all tables, including the + * local table (255). + */ +typedef enum { + NM_IP_ROUTE_TABLE_SYNC_MODE_NONE = 0, + NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN = 1, + NM_IP_ROUTE_TABLE_SYNC_MODE_FULL = 2, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL = 3, +} NMIPRouteTableSyncMode; + +/* settings */ +typedef struct _NMAgentManager NMAgentManager; +typedef struct _NMSecretAgent NMSecretAgent; +typedef struct _NMSettings NMSettings; +typedef struct _NMSettingsConnection NMSettingsConnection; + +/* utils */ +typedef struct _NMUtilsIPv6IfaceId NMUtilsIPv6IfaceId; + +#define NM_SETTING_CONNECTION_MDNS_UNKNOWN ((NMSettingConnectionMdns) -42) + +#endif /* NM_TYPES_H */ diff --git a/src/core/org.freedesktop.NetworkManager.conf b/src/core/org.freedesktop.NetworkManager.conf new file mode 100644 index 0000000..5c2af2e --- /dev/null +++ b/src/core/org.freedesktop.NetworkManager.conf @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1024 + 2048 + diff --git a/src/core/platform/linux/nl802154.h b/src/core/platform/linux/nl802154.h new file mode 100644 index 0000000..c21bd07 --- /dev/null +++ b/src/core/platform/linux/nl802154.h @@ -0,0 +1,450 @@ +#ifndef __NL802154_H +#define __NL802154_H +/* + * 802.15.4 netlink interface public header + * + * Copyright (C) 2014 Alexander Aring + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#define NL802154_GENL_NAME "nl802154" + +enum nl802154_commands { + /* don't change the order or add anything between, this is ABI! */ + /* currently we don't shipping this file via uapi, ignore the above one */ + NL802154_CMD_UNSPEC, + + NL802154_CMD_GET_WPAN_PHY, /* can dump */ + NL802154_CMD_SET_WPAN_PHY, + NL802154_CMD_NEW_WPAN_PHY, + NL802154_CMD_DEL_WPAN_PHY, + + NL802154_CMD_GET_INTERFACE, /* can dump */ + NL802154_CMD_SET_INTERFACE, + NL802154_CMD_NEW_INTERFACE, + NL802154_CMD_DEL_INTERFACE, + + NL802154_CMD_SET_CHANNEL, + + NL802154_CMD_SET_PAN_ID, + NL802154_CMD_SET_SHORT_ADDR, + + NL802154_CMD_SET_TX_POWER, + NL802154_CMD_SET_CCA_MODE, + NL802154_CMD_SET_CCA_ED_LEVEL, + + NL802154_CMD_SET_MAX_FRAME_RETRIES, + + NL802154_CMD_SET_BACKOFF_EXPONENT, + NL802154_CMD_SET_MAX_CSMA_BACKOFFS, + + NL802154_CMD_SET_LBT_MODE, + + NL802154_CMD_SET_ACKREQ_DEFAULT, + + NL802154_CMD_SET_WPAN_PHY_NETNS, + +/* add new commands above here */ + +#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL + NL802154_CMD_SET_SEC_PARAMS, + NL802154_CMD_GET_SEC_KEY, /* can dump */ + NL802154_CMD_NEW_SEC_KEY, + NL802154_CMD_DEL_SEC_KEY, + NL802154_CMD_GET_SEC_DEV, /* can dump */ + NL802154_CMD_NEW_SEC_DEV, + NL802154_CMD_DEL_SEC_DEV, + NL802154_CMD_GET_SEC_DEVKEY, /* can dump */ + NL802154_CMD_NEW_SEC_DEVKEY, + NL802154_CMD_DEL_SEC_DEVKEY, + NL802154_CMD_GET_SEC_LEVEL, /* can dump */ + NL802154_CMD_NEW_SEC_LEVEL, + NL802154_CMD_DEL_SEC_LEVEL, +#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */ + + /* used to define NL802154_CMD_MAX below */ + __NL802154_CMD_AFTER_LAST, + NL802154_CMD_MAX = __NL802154_CMD_AFTER_LAST - 1 +}; + +enum nl802154_attrs { + /* don't change the order or add anything between, this is ABI! */ + /* currently we don't shipping this file via uapi, ignore the above one */ + NL802154_ATTR_UNSPEC, + + NL802154_ATTR_WPAN_PHY, + NL802154_ATTR_WPAN_PHY_NAME, + + NL802154_ATTR_IFINDEX, + NL802154_ATTR_IFNAME, + NL802154_ATTR_IFTYPE, + + NL802154_ATTR_WPAN_DEV, + + NL802154_ATTR_PAGE, + NL802154_ATTR_CHANNEL, + + NL802154_ATTR_PAN_ID, + NL802154_ATTR_SHORT_ADDR, + + NL802154_ATTR_TX_POWER, + + NL802154_ATTR_CCA_MODE, + NL802154_ATTR_CCA_OPT, + NL802154_ATTR_CCA_ED_LEVEL, + + NL802154_ATTR_MAX_FRAME_RETRIES, + + NL802154_ATTR_MAX_BE, + NL802154_ATTR_MIN_BE, + NL802154_ATTR_MAX_CSMA_BACKOFFS, + + NL802154_ATTR_LBT_MODE, + + NL802154_ATTR_GENERATION, + + NL802154_ATTR_CHANNELS_SUPPORTED, + NL802154_ATTR_SUPPORTED_CHANNEL, + + NL802154_ATTR_EXTENDED_ADDR, + + NL802154_ATTR_WPAN_PHY_CAPS, + + NL802154_ATTR_SUPPORTED_COMMANDS, + + NL802154_ATTR_ACKREQ_DEFAULT, + + NL802154_ATTR_PAD, + + NL802154_ATTR_PID, + NL802154_ATTR_NETNS_FD, + +/* add attributes here, update the policy in nl802154.c */ + +#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL + NL802154_ATTR_SEC_ENABLED, + NL802154_ATTR_SEC_OUT_LEVEL, + NL802154_ATTR_SEC_OUT_KEY_ID, + NL802154_ATTR_SEC_FRAME_COUNTER, + + NL802154_ATTR_SEC_LEVEL, + NL802154_ATTR_SEC_DEVICE, + NL802154_ATTR_SEC_DEVKEY, + NL802154_ATTR_SEC_KEY, +#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */ + + __NL802154_ATTR_AFTER_LAST, + NL802154_ATTR_MAX = __NL802154_ATTR_AFTER_LAST - 1 +}; + +enum nl802154_iftype { + /* for backwards compatibility TODO */ + NL802154_IFTYPE_UNSPEC = -1, + + NL802154_IFTYPE_NODE, + NL802154_IFTYPE_MONITOR, + NL802154_IFTYPE_COORD, + + /* keep last */ + NUM_NL802154_IFTYPES, + NL802154_IFTYPE_MAX = NUM_NL802154_IFTYPES - 1 +}; + +/** + * enum nl802154_wpan_phy_capability_attr - wpan phy capability attributes + * + * @__NL802154_CAP_ATTR_INVALID: attribute number 0 is reserved + * @NL802154_CAP_ATTR_CHANNELS: a nested attribute for nl802154_channel_attr + * @NL802154_CAP_ATTR_TX_POWERS: a nested attribute for nl802154_wpan_phy_tx_power + * @NL802154_CAP_ATTR_MIN_CCA_ED_LEVEL: minimum value for cca_ed_level + * @NL802154_CAP_ATTR_MAX_CCA_ED_LEVEL: maxmimum value for cca_ed_level + * @NL802154_CAP_ATTR_CCA_MODES: nl802154_cca_modes flags + * @NL802154_CAP_ATTR_CCA_OPTS: nl802154_cca_opts flags + * @NL802154_CAP_ATTR_MIN_MINBE: minimum of minbe value + * @NL802154_CAP_ATTR_MAX_MINBE: maximum of minbe value + * @NL802154_CAP_ATTR_MIN_MAXBE: minimum of maxbe value + * @NL802154_CAP_ATTR_MAX_MINBE: maximum of maxbe value + * @NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS: minimum of csma backoff value + * @NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS: maximum of csma backoffs value + * @NL802154_CAP_ATTR_MIN_FRAME_RETRIES: minimum of frame retries value + * @NL802154_CAP_ATTR_MAX_FRAME_RETRIES: maximum of frame retries value + * @NL802154_CAP_ATTR_IFTYPES: nl802154_iftype flags + * @NL802154_CAP_ATTR_LBT: nl802154_supported_bool_states flags + * @NL802154_CAP_ATTR_MAX: highest cap attribute currently defined + * @__NL802154_CAP_ATTR_AFTER_LAST: internal use + */ +enum nl802154_wpan_phy_capability_attr { + __NL802154_CAP_ATTR_INVALID, + + NL802154_CAP_ATTR_IFTYPES, + + NL802154_CAP_ATTR_CHANNELS, + NL802154_CAP_ATTR_TX_POWERS, + + NL802154_CAP_ATTR_CCA_ED_LEVELS, + NL802154_CAP_ATTR_CCA_MODES, + NL802154_CAP_ATTR_CCA_OPTS, + + NL802154_CAP_ATTR_MIN_MINBE, + NL802154_CAP_ATTR_MAX_MINBE, + + NL802154_CAP_ATTR_MIN_MAXBE, + NL802154_CAP_ATTR_MAX_MAXBE, + + NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS, + NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS, + + NL802154_CAP_ATTR_MIN_FRAME_RETRIES, + NL802154_CAP_ATTR_MAX_FRAME_RETRIES, + + NL802154_CAP_ATTR_LBT, + + /* keep last */ + __NL802154_CAP_ATTR_AFTER_LAST, + NL802154_CAP_ATTR_MAX = __NL802154_CAP_ATTR_AFTER_LAST - 1 +}; + +/** + * enum nl802154_cca_modes - cca modes + * + * @__NL802154_CCA_INVALID: cca mode number 0 is reserved + * @NL802154_CCA_ENERGY: Energy above threshold + * @NL802154_CCA_CARRIER: Carrier sense only + * @NL802154_CCA_ENERGY_CARRIER: Carrier sense with energy above threshold + * @NL802154_CCA_ALOHA: CCA shall always report an idle medium + * @NL802154_CCA_UWB_SHR: UWB preamble sense based on the SHR of a frame + * @NL802154_CCA_UWB_MULTIPLEXED: UWB preamble sense based on the packet with the multiplexed preamble + * @__NL802154_CCA_ATTR_AFTER_LAST: Internal + * @NL802154_CCA_ATTR_MAX: Maximum CCA attribute number + */ +enum nl802154_cca_modes { + __NL802154_CCA_INVALID, + NL802154_CCA_ENERGY, + NL802154_CCA_CARRIER, + NL802154_CCA_ENERGY_CARRIER, + NL802154_CCA_ALOHA, + NL802154_CCA_UWB_SHR, + NL802154_CCA_UWB_MULTIPLEXED, + + /* keep last */ + __NL802154_CCA_ATTR_AFTER_LAST, + NL802154_CCA_ATTR_MAX = __NL802154_CCA_ATTR_AFTER_LAST - 1 +}; + +/** + * enum nl802154_cca_opts - additional options for cca modes + * + * @NL802154_CCA_OPT_ENERGY_CARRIER_OR: NL802154_CCA_ENERGY_CARRIER with OR + * @NL802154_CCA_OPT_ENERGY_CARRIER_AND: NL802154_CCA_ENERGY_CARRIER with AND + */ +enum nl802154_cca_opts { + NL802154_CCA_OPT_ENERGY_CARRIER_AND, + NL802154_CCA_OPT_ENERGY_CARRIER_OR, + + /* keep last */ + __NL802154_CCA_OPT_ATTR_AFTER_LAST, + NL802154_CCA_OPT_ATTR_MAX = __NL802154_CCA_OPT_ATTR_AFTER_LAST - 1 +}; + +/** + * enum nl802154_supported_bool_states - bool states for bool capability entry + * + * @NL802154_SUPPORTED_BOOL_FALSE: indicates to set false + * @NL802154_SUPPORTED_BOOL_TRUE: indicates to set true + * @__NL802154_SUPPORTED_BOOL_INVALD: reserved + * @NL802154_SUPPORTED_BOOL_BOTH: indicates to set true and false + * @__NL802154_SUPPORTED_BOOL_AFTER_LAST: Internal + * @NL802154_SUPPORTED_BOOL_MAX: highest value for bool states + */ +enum nl802154_supported_bool_states { + NL802154_SUPPORTED_BOOL_FALSE, + NL802154_SUPPORTED_BOOL_TRUE, + /* to handle them in a mask */ + __NL802154_SUPPORTED_BOOL_INVALD, + NL802154_SUPPORTED_BOOL_BOTH, + + /* keep last */ + __NL802154_SUPPORTED_BOOL_AFTER_LAST, + NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1 +}; + +#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL + +enum nl802154_dev_addr_modes { + NL802154_DEV_ADDR_NONE, + __NL802154_DEV_ADDR_INVALID, + NL802154_DEV_ADDR_SHORT, + NL802154_DEV_ADDR_EXTENDED, + + /* keep last */ + __NL802154_DEV_ADDR_AFTER_LAST, + NL802154_DEV_ADDR_MAX = __NL802154_DEV_ADDR_AFTER_LAST - 1 +}; + +enum nl802154_dev_addr_attrs { + NL802154_DEV_ADDR_ATTR_UNSPEC, + + NL802154_DEV_ADDR_ATTR_PAN_ID, + NL802154_DEV_ADDR_ATTR_MODE, + NL802154_DEV_ADDR_ATTR_SHORT, + NL802154_DEV_ADDR_ATTR_EXTENDED, + NL802154_DEV_ADDR_ATTR_PAD, + + /* keep last */ + __NL802154_DEV_ADDR_ATTR_AFTER_LAST, + NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1 +}; + +enum nl802154_key_id_modes { + NL802154_KEY_ID_MODE_IMPLICIT, + NL802154_KEY_ID_MODE_INDEX, + NL802154_KEY_ID_MODE_INDEX_SHORT, + NL802154_KEY_ID_MODE_INDEX_EXTENDED, + + /* keep last */ + __NL802154_KEY_ID_MODE_AFTER_LAST, + NL802154_KEY_ID_MODE_MAX = __NL802154_KEY_ID_MODE_AFTER_LAST - 1 +}; + +enum nl802154_key_id_attrs { + NL802154_KEY_ID_ATTR_UNSPEC, + + NL802154_KEY_ID_ATTR_MODE, + NL802154_KEY_ID_ATTR_INDEX, + NL802154_KEY_ID_ATTR_IMPLICIT, + NL802154_KEY_ID_ATTR_SOURCE_SHORT, + NL802154_KEY_ID_ATTR_SOURCE_EXTENDED, + NL802154_KEY_ID_ATTR_PAD, + + /* keep last */ + __NL802154_KEY_ID_ATTR_AFTER_LAST, + NL802154_KEY_ID_ATTR_MAX = __NL802154_KEY_ID_ATTR_AFTER_LAST - 1 +}; + +enum nl802154_seclevels { + NL802154_SECLEVEL_NONE, + NL802154_SECLEVEL_MIC32, + NL802154_SECLEVEL_MIC64, + NL802154_SECLEVEL_MIC128, + NL802154_SECLEVEL_ENC, + NL802154_SECLEVEL_ENC_MIC32, + NL802154_SECLEVEL_ENC_MIC64, + NL802154_SECLEVEL_ENC_MIC128, + + /* keep last */ + __NL802154_SECLEVEL_AFTER_LAST, + NL802154_SECLEVEL_MAX = __NL802154_SECLEVEL_AFTER_LAST - 1 +}; + +enum nl802154_frames { + NL802154_FRAME_BEACON, + NL802154_FRAME_DATA, + NL802154_FRAME_ACK, + NL802154_FRAME_CMD, + + /* keep last */ + __NL802154_FRAME_AFTER_LAST, + NL802154_FRAME_MAX = __NL802154_FRAME_AFTER_LAST - 1 +}; + +enum nl802154_cmd_frames { + __NL802154_CMD_FRAME_INVALID, + NL802154_CMD_FRAME_ASSOC_REQUEST, + NL802154_CMD_FRAME_ASSOC_RESPONSE, + NL802154_CMD_FRAME_DISASSOC_NOTIFY, + NL802154_CMD_FRAME_DATA_REQUEST, + NL802154_CMD_FRAME_PAN_ID_CONFLICT_NOTIFY, + NL802154_CMD_FRAME_ORPHAN_NOTIFY, + NL802154_CMD_FRAME_BEACON_REQUEST, + NL802154_CMD_FRAME_COORD_REALIGNMENT, + NL802154_CMD_FRAME_GTS_REQUEST, + + /* keep last */ + __NL802154_CMD_FRAME_AFTER_LAST, + NL802154_CMD_FRAME_MAX = __NL802154_CMD_FRAME_AFTER_LAST - 1 +}; + +enum nl802154_seclevel_attrs { + NL802154_SECLEVEL_ATTR_UNSPEC, + + NL802154_SECLEVEL_ATTR_LEVELS, + NL802154_SECLEVEL_ATTR_FRAME, + NL802154_SECLEVEL_ATTR_CMD_FRAME, + NL802154_SECLEVEL_ATTR_DEV_OVERRIDE, + + /* keep last */ + __NL802154_SECLEVEL_ATTR_AFTER_LAST, + NL802154_SECLEVEL_ATTR_MAX = __NL802154_SECLEVEL_ATTR_AFTER_LAST - 1 +}; + +/* TODO what is this? couldn't find in mib */ +enum { + NL802154_DEVKEY_IGNORE, + NL802154_DEVKEY_RESTRICT, + NL802154_DEVKEY_RECORD, + + /* keep last */ + __NL802154_DEVKEY_AFTER_LAST, + NL802154_DEVKEY_MAX = __NL802154_DEVKEY_AFTER_LAST - 1 +}; + +enum nl802154_dev { + NL802154_DEV_ATTR_UNSPEC, + + NL802154_DEV_ATTR_FRAME_COUNTER, + NL802154_DEV_ATTR_PAN_ID, + NL802154_DEV_ATTR_SHORT_ADDR, + NL802154_DEV_ATTR_EXTENDED_ADDR, + NL802154_DEV_ATTR_SECLEVEL_EXEMPT, + NL802154_DEV_ATTR_KEY_MODE, + NL802154_DEV_ATTR_PAD, + + /* keep last */ + __NL802154_DEV_ATTR_AFTER_LAST, + NL802154_DEV_ATTR_MAX = __NL802154_DEV_ATTR_AFTER_LAST - 1 +}; + +enum nl802154_devkey { + NL802154_DEVKEY_ATTR_UNSPEC, + + NL802154_DEVKEY_ATTR_FRAME_COUNTER, + NL802154_DEVKEY_ATTR_EXTENDED_ADDR, + NL802154_DEVKEY_ATTR_ID, + NL802154_DEVKEY_ATTR_PAD, + + /* keep last */ + __NL802154_DEVKEY_ATTR_AFTER_LAST, + NL802154_DEVKEY_ATTR_MAX = __NL802154_DEVKEY_ATTR_AFTER_LAST - 1 +}; + +enum nl802154_key { + NL802154_KEY_ATTR_UNSPEC, + + NL802154_KEY_ATTR_ID, + NL802154_KEY_ATTR_USAGE_FRAMES, + NL802154_KEY_ATTR_USAGE_CMDS, + NL802154_KEY_ATTR_BYTES, + + /* keep last */ + __NL802154_KEY_ATTR_AFTER_LAST, + NL802154_KEY_ATTR_MAX = __NL802154_KEY_ATTR_AFTER_LAST - 1 +}; + + #define NL802154_KEY_SIZE 16 + #define NL802154_CMD_FRAME_NR_IDS 256 + +#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */ + +#endif /* __NL802154_H */ diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c new file mode 100644 index 0000000..fbd3e3f --- /dev/null +++ b/src/core/platform/nm-fake-platform.c @@ -0,0 +1,1403 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-fake-platform.h" + +#include +#include +#include +#include +#include + +#include "nm-utils.h" + +#include "nm-core-utils.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-platform-private.h" +#include "nmp-object.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + char * udi; + struct in6_addr ip6_lladdr; +} NMFakePlatformLink; + +typedef struct { + GHashTable *options; + GArray * links; +} NMFakePlatformPrivate; + +struct _NMFakePlatform { + NMPlatform parent; + NMFakePlatformPrivate _priv; +}; + +struct _NMFakePlatformClass { + NMPlatformClass parent; +}; + +G_DEFINE_TYPE(NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM) + +#define NM_FAKE_PLATFORM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMFakePlatform, NM_IS_FAKE_PLATFORM, NMPlatform) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "platform-fake" +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) + +#define _LOG(level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + char __prefix[32]; \ + const char * __p_prefix = _NMLOG_PREFIX_NAME; \ + NMPlatform *const __self = (self); \ + \ + if (__self && nm_platform_get_log_with_ptr(self)) { \ + g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + __domain, \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void link_changed(NMPlatform * platform, + NMFakePlatformLink *device, + NMPCacheOpsType cache_op, + const NMPObject * obj_old); + +static gboolean ipx_address_delete(NMPlatform * platform, + int addr_family, + int ifindex, + gconstpointer addr, + const guint8 *plen, + gconstpointer peer_addr); + +static gboolean +ipx_route_delete(NMPlatform *platform, int addr_family, int ifindex, const NMPObject *obj); + +static gboolean ip6_address_add(NMPlatform * platform, + int ifindex, + struct in6_addr addr, + guint8 plen, + struct in6_addr peer_addr, + guint32 lifetime, + guint32 preferred, + guint flags); +static gboolean +ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen); + +/*****************************************************************************/ + +#define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \ + G_STMT_START \ + { \ + const char *const _pathid = (pathid); \ + const int _dirfd = (dirfd); \ + const char *const _path = (path); \ + \ + g_assert(_path &&_path[0]); \ + g_assert(!strstr(_path, "/../")); \ + if (_dirfd < 0) { \ + g_assert(!_pathid); \ + g_assert(_path[0] == '/'); \ + g_assert(g_str_has_prefix(_path, "/proc/sys/") || g_str_has_prefix(_path, "/sys/")); \ + } else { \ + g_assert_not_reached(); \ + } \ + } \ + G_STMT_END + +static gboolean +sysctl_set(NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform); + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + g_hash_table_insert(priv->options, g_strdup(path), g_strdup(value)); + + return TRUE; +} + +static char * +sysctl_get(NMPlatform *platform, const char *pathid, int dirfd, const char *path) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform); + const char * v; + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + v = g_hash_table_lookup(priv->options, path); + if (!v) { + errno = ENOENT; + return NULL; + } + + return g_strdup(v); +} + +static NMFakePlatformLink * +link_get(NMPlatform *platform, int ifindex) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform); + NMFakePlatformLink * device; + int idx; + + if (ifindex <= 0) + g_return_val_if_reached(NULL); + + idx = ifindex - 1; + if (idx >= priv->links->len) + goto not_found; + + device = &g_array_index(priv->links, NMFakePlatformLink, idx); + if (!device->obj) + goto not_found; + + g_assert(ifindex == NMP_OBJECT_CAST_LINK(device->obj)->ifindex); + g_assert(device->obj == nm_platform_link_get_obj(platform, ifindex, FALSE)); + + return device; +not_found: + _LOGD("link not found: %d", ifindex); + return NULL; +} + +static void +link_add_prepare(NMPlatform *platform, NMFakePlatformLink *device, NMPObject *obj_tmp) +{ + gboolean connected; + + /* we must clear the driver, because platform cache wants to set it */ + g_assert(obj_tmp->link.driver == g_intern_string(obj_tmp->link.driver)); + obj_tmp->link.driver = NULL; + + if (NM_IN_SET(obj_tmp->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND)) { + connected = FALSE; + if (NM_FLAGS_HAS(obj_tmp->link.n_ifi_flags, IFF_UP)) { + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPObject *slave_candidate = NULL; + + nmp_cache_iter_for_each ( + &iter, + nmp_cache_lookup(nm_platform_get_cache(platform), + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK)), + &slave_candidate) { + if (nmp_cache_link_connected_for_slave(obj_tmp->link.ifindex, slave_candidate)) { + connected = TRUE; + break; + } + } + } + } else + connected = NM_FLAGS_HAS(obj_tmp->link.n_ifi_flags, IFF_UP); + + obj_tmp->link.n_ifi_flags = NM_FLAGS_ASSIGN(obj_tmp->link.n_ifi_flags, IFF_LOWER_UP, connected); + obj_tmp->link.connected = connected; +} + +static NMFakePlatformLink * +link_add_pre(NMPlatform *platform, + const char *name, + NMLinkType type, + const void *address, + size_t address_len, + guint32 mtu) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform); + NMFakePlatformLink * device; + int ifindex; + NMPObject * o; + NMPlatformLink * link; + gs_free char * ip6_lladdr = NULL; + + g_assert(!name || strlen(name) < IFNAMSIZ); + + g_array_set_size(priv->links, priv->links->len + 1); + device = &g_array_index(priv->links, NMFakePlatformLink, priv->links->len - 1); + ifindex = priv->links->len; + + memset(device, 0, sizeof(*device)); + + o = nmp_object_new_link(ifindex); + link = NMP_OBJECT_CAST_LINK(o); + + ip6_lladdr = + ifindex > 0 ? g_strdup_printf("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL; + + link->ifindex = name ? ifindex : 0; + link->type = type; + link->kind = g_intern_string(nm_link_type_to_string(type)); + link->mtu = mtu; + link->initialized = TRUE; + if (name) + strcpy(link->name, name); + switch (link->type) { + case NM_LINK_TYPE_DUMMY: + link->n_ifi_flags = NM_FLAGS_SET(link->n_ifi_flags, IFF_NOARP); + break; + default: + link->n_ifi_flags = NM_FLAGS_UNSET(link->n_ifi_flags, IFF_NOARP); + break; + } + + o->_link.netlink.is_in_netlink = TRUE; + + if (address) { + g_assert(address_len > 0 && address_len <= sizeof(link->l_address.data)); + memcpy(link->l_address.data, address, address_len); + link->l_address.len = address_len; + } else + g_assert(address_len == 0); + + device->obj = o; + device->ip6_lladdr = *nmtst_inet6_from_string(ip6_lladdr); + + return device; +} + +static int +link_add(NMPlatform * platform, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link) +{ + NMFakePlatformLink * device; + NMFakePlatformLink * device_veth = NULL; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *obj_old_veth = NULL; + nm_auto_nmpobj const NMPObject *obj_new_veth = NULL; + NMPCacheOpsType cache_op; + NMPCacheOpsType cache_op_veth = NMP_CACHE_OPS_UNCHANGED; + const char * veth_peer = NULL; + NMPObject * dev_obj; + NMPObject * dev_lnk = NULL; + + device = link_add_pre(platform, name, type, address, address_len, mtu); + + g_assert(device); + + dev_obj = (NMPObject *) device->obj; + + if (parent > 0) + dev_obj->link.parent = parent; + else + g_assert(parent == 0); + + g_assert((parent != 0) == NM_IN_SET(type, NM_LINK_TYPE_VLAN)); + + switch (type) { + case NM_LINK_TYPE_BRIDGE: + { + const NMPlatformLnkBridge *props = extra_data; + + g_assert(props); + + dev_lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_BRIDGE, props); + break; + } + case NM_LINK_TYPE_VETH: + veth_peer = extra_data; + g_assert(veth_peer); + device_veth = link_add_pre(platform, veth_peer, type, NULL, 0, 0); + break; + case NM_LINK_TYPE_VLAN: + { + const NMPlatformLnkVlan *props = extra_data; + + g_assert(props); + + dev_lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_VLAN, props); + break; + } + case NM_LINK_TYPE_VXLAN: + { + const NMPlatformLnkVxlan *props = extra_data; + + g_assert(props); + + dev_lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_VXLAN, props); + break; + } + default: + g_assert(!extra_data); + break; + } + + if (dev_lnk) + dev_obj->_link.netlink.lnk = dev_lnk; + + link_add_prepare(platform, device, (NMPObject *) device->obj); + cache_op = nmp_cache_update_netlink(nm_platform_get_cache(platform), + (NMPObject *) device->obj, + FALSE, + &obj_old, + &obj_new); + g_assert(cache_op == NMP_CACHE_OPS_ADDED); + nmp_object_unref(device->obj); + device->obj = nmp_object_ref(obj_new); + if (veth_peer) { + link_add_prepare(platform, device_veth, (NMPObject *) device_veth->obj); + cache_op_veth = nmp_cache_update_netlink(nm_platform_get_cache(platform), + (NMPObject *) device_veth->obj, + FALSE, + &obj_old_veth, + &obj_new_veth); + g_assert(cache_op == NMP_CACHE_OPS_ADDED); + nmp_object_unref(device->obj); + device->obj = nmp_object_ref(obj_new); + } + + if (out_link) + *out_link = NMP_OBJECT_CAST_LINK(device->obj); + + link_changed(platform, device, cache_op, NULL); + if (veth_peer) + link_changed(platform, device_veth, cache_op_veth, NULL); + + return 0; +} + +static NMFakePlatformLink * +link_add_one(NMPlatform *platform, + const char *name, + NMLinkType link_type, + void (*prepare_fcn)(NMPlatform * platform, + NMFakePlatformLink *device, + gconstpointer user_data), + gconstpointer user_data, + const NMPlatformLink **out_link) +{ + NMFakePlatformLink * device; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCacheOpsType cache_op; + int ifindex; + + device = link_add_pre(platform, name, NM_LINK_TYPE_VLAN, NULL, 0, 0); + + ifindex = NMP_OBJECT_CAST_LINK(device->obj)->ifindex; + + if (prepare_fcn) + prepare_fcn(platform, device, user_data); + + link_add_prepare(platform, device, (NMPObject *) device->obj); + cache_op = nmp_cache_update_netlink(nm_platform_get_cache(platform), + (NMPObject *) device->obj, + FALSE, + &obj_old, + &obj_new); + g_assert(cache_op == NMP_CACHE_OPS_ADDED); + nmp_object_unref(device->obj); + device->obj = nmp_object_ref(obj_new); + + link_changed(platform, device, cache_op, obj_old); + + device = link_get(platform, ifindex); + if (!device) + g_assert_not_reached(); + + NM_SET_OUT(out_link, NMP_OBJECT_CAST_LINK(device->obj)); + return device; +} + +static gboolean +link_delete(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink * device = link_get(platform, ifindex); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_old2 = NULL; + NMPCacheOpsType cache_op; + + if (!device) + return FALSE; + + obj_old = g_steal_pointer(&device->obj); + + cache_op = nmp_cache_remove(nm_platform_get_cache(platform), obj_old, FALSE, FALSE, &obj_old2); + g_assert(cache_op == NMP_CACHE_OPS_REMOVED); + g_assert(obj_old2); + g_assert(obj_old == obj_old2); + + /* Remove addresses and routes which belong to the deleted interface */ + ipx_address_delete(platform, AF_INET, ifindex, NULL, NULL, NULL); + ipx_address_delete(platform, AF_INET6, ifindex, NULL, NULL, NULL); + ipx_route_delete(platform, AF_INET, ifindex, NULL); + ipx_route_delete(platform, AF_INET6, ifindex, NULL); + + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old2, NULL); + return TRUE; +} + +static void +link_set_obj(NMPlatform *platform, NMFakePlatformLink *device, NMPObject *obj_tmp) +{ + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj NMPObject *obj_tmp_tmp = NULL; + NMPCacheOpsType cache_op; + + g_assert(device); + g_assert(NMP_OBJECT_GET_TYPE(device->obj) == NMP_OBJECT_TYPE_LINK); + + if (!obj_tmp) { + obj_tmp_tmp = nmp_object_clone(device->obj, FALSE); + obj_tmp = obj_tmp_tmp; + } + + g_assert(NMP_OBJECT_GET_TYPE(obj_tmp) == NMP_OBJECT_TYPE_LINK); + + link_add_prepare(platform, device, obj_tmp); + cache_op = nmp_cache_update_netlink(nm_platform_get_cache(platform), + obj_tmp, + FALSE, + &obj_old, + &obj_new); + g_assert(NM_IN_SET(cache_op, NMP_CACHE_OPS_UNCHANGED, NMP_CACHE_OPS_UPDATED)); + g_assert(obj_old == device->obj); + g_assert(obj_new); + + nmp_object_unref(device->obj); + device->obj = nmp_object_ref(obj_new); + + link_changed(platform, device, cache_op, obj_old); +} + +static void +link_set_flags(NMPlatform *platform, NMFakePlatformLink *device, guint n_ifi_flags) +{ + nm_auto_nmpobj NMPObject *obj_tmp = NULL; + + g_assert(device); + g_assert(NMP_OBJECT_GET_TYPE(device->obj) == NMP_OBJECT_TYPE_LINK); + + obj_tmp = nmp_object_clone(device->obj, FALSE); + obj_tmp->link.n_ifi_flags = n_ifi_flags; + link_set_obj(platform, device, obj_tmp); +} + +static void +link_changed(NMPlatform * platform, + NMFakePlatformLink *device, + NMPCacheOpsType cache_op, + const NMPObject * obj_old) +{ + g_assert(device->obj); + + g_assert(!nmp_cache_link_connected_needs_toggle(nm_platform_get_cache(platform), + device->obj, + NULL, + NULL)); + + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, device->obj); + + if (!IN6_IS_ADDR_UNSPECIFIED(&device->ip6_lladdr)) { + if (device->obj->link.connected) + ip6_address_add(platform, + device->obj->link.ifindex, + device->ip6_lladdr, + 64, + in6addr_any, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0); + else + ip6_address_delete(platform, device->obj->link.ifindex, device->ip6_lladdr, 64); + } + + if (device->obj->link.master) { + NMFakePlatformLink *master; + + master = link_get(platform, device->obj->link.master); + link_set_obj(platform, master, NULL); + } +} + +static gboolean +link_set_up(NMPlatform *platform, int ifindex, gboolean *out_no_firmware) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (out_no_firmware) + *out_no_firmware = FALSE; + + if (!device) { + _LOGE("failure changing link: netlink error (No such device)"); + return FALSE; + } + + link_set_flags(platform, device, NM_FLAGS_ASSIGN(device->obj->link.n_ifi_flags, IFF_UP, TRUE)); + return TRUE; +} + +static gboolean +link_set_down(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) { + _LOGE("failure changing link: netlink error (No such device)"); + return FALSE; + } + + link_set_flags(platform, device, NM_FLAGS_UNSET(device->obj->link.n_ifi_flags, IFF_UP)); + return TRUE; +} + +static gboolean +link_set_arp(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) { + _LOGE("failure changing link: netlink error (No such device)"); + return FALSE; + } + + link_set_flags(platform, device, NM_FLAGS_UNSET(device->obj->link.n_ifi_flags, IFF_NOARP)); + return TRUE; +} + +static gboolean +link_set_noarp(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) { + _LOGE("failure changing link: netlink error (No such device)"); + return FALSE; + } + + link_set_flags(platform, device, NM_FLAGS_SET(device->obj->link.n_ifi_flags, IFF_NOARP)); + return TRUE; +} + +static int +link_set_address(NMPlatform *platform, int ifindex, gconstpointer addr, size_t len) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + nm_auto_nmpobj NMPObject *obj_tmp = NULL; + + if (len == 0 || len > NM_UTILS_HWADDR_LEN_MAX || !addr) + g_return_val_if_reached(-NME_BUG); + + if (!device) + return -NME_PL_EXISTS; + + obj_tmp = nmp_object_clone(device->obj, FALSE); + obj_tmp->link.l_address.len = len; + memset(obj_tmp->link.l_address.data, 0, sizeof(obj_tmp->link.l_address.data)); + memcpy(obj_tmp->link.l_address.data, addr, len); + + link_set_obj(platform, device, obj_tmp); + return 0; +} + +static int +link_set_mtu(NMPlatform *platform, int ifindex, guint32 mtu) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + nm_auto_nmpobj NMPObject *obj_tmp = NULL; + + if (!device) { + _LOGE("failure changing link: netlink error (No such device)"); + return -NME_PL_EXISTS; + } + + obj_tmp = nmp_object_clone(device->obj, FALSE); + obj_tmp->link.mtu = mtu; + link_set_obj(platform, device, obj_tmp); + return 0; +} + +static gboolean +link_get_driver_info(NMPlatform *platform, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version) +{ + if (out_driver_name) + *out_driver_name = NULL; + if (out_driver_version) + *out_driver_version = NULL; + if (out_fw_version) + *out_fw_version = NULL; + + return TRUE; +} + +static gboolean +link_supports_carrier_detect(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) + return FALSE; + + switch (device->obj->link.type) { + case NM_LINK_TYPE_DUMMY: + return FALSE; + default: + return TRUE; + } +} + +static gboolean +link_supports_vlans(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) + return FALSE; + + switch (device->obj->link.type) { + case NM_LINK_TYPE_LOOPBACK: + return FALSE; + default: + return TRUE; + } +} + +static gboolean +link_supports_sriov(NMPlatform *platform, int ifindex) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + if (!device) + return FALSE; + + switch (device->obj->link.type) { + case NM_LINK_TYPE_LOOPBACK: + return FALSE; + default: + return TRUE; + } +} + +static gboolean +link_enslave(NMPlatform *platform, int master, int slave) +{ + NMFakePlatformLink *device = link_get(platform, slave); + NMFakePlatformLink *master_device = link_get(platform, master); + + g_return_val_if_fail(device, FALSE); + g_return_val_if_fail(master_device, FALSE); + + if (device->obj->link.master != master) { + nm_auto_nmpobj NMPObject *obj_tmp = NULL; + + obj_tmp = nmp_object_clone(device->obj, FALSE); + obj_tmp->link.master = master; + if (NM_IN_SET(master_device->obj->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) + obj_tmp->link.n_ifi_flags = NM_FLAGS_SET(device->obj->link.n_ifi_flags, IFF_UP); + link_set_obj(platform, device, obj_tmp); + } + + return TRUE; +} + +static gboolean +link_release(NMPlatform *platform, int master_idx, int slave_idx) +{ + NMFakePlatformLink *master = link_get(platform, master_idx); + NMFakePlatformLink *slave = link_get(platform, slave_idx); + nm_auto_nmpobj NMPObject *obj_tmp = NULL; + + g_return_val_if_fail(master, FALSE); + g_return_val_if_fail(slave, FALSE); + + if (slave->obj->link.master != master->obj->link.ifindex) + return FALSE; + + obj_tmp = nmp_object_clone(slave->obj, FALSE); + obj_tmp->link.master = 0; + link_set_obj(platform, slave, obj_tmp); + return TRUE; +} + +static gboolean +link_vlan_change(NMPlatform * platform, + int ifindex, + NMVlanFlags flags_mask, + NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map) +{ + return FALSE; +} + +struct infiniband_add_data { + int parent; + int p_key; +}; + +static void +_infiniband_add_prepare(NMPlatform *platform, NMFakePlatformLink *device, gconstpointer user_data) +{ + const struct infiniband_add_data *d = user_data; + NMPObject * obj_tmp; + NMPObject * lnk; + + obj_tmp = (NMPObject *) device->obj; + + lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL); + lnk->lnk_infiniband.p_key = d->p_key; + lnk->lnk_infiniband.mode = "datagram"; + + obj_tmp->link.parent = d->parent; + obj_tmp->_link.netlink.lnk = lnk; +} + +static gboolean +infiniband_partition_add(NMPlatform * platform, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + NMFakePlatformLink * parent_device; + char name[IFNAMSIZ]; + const struct infiniband_add_data d = { + .parent = parent, + .p_key = p_key, + }; + + parent_device = link_get(platform, parent); + g_return_val_if_fail(parent_device != NULL, FALSE); + + nm_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key); + + link_add_one(platform, name, NM_LINK_TYPE_INFINIBAND, _infiniband_add_prepare, &d, out_link); + return TRUE; +} + +static gboolean +infiniband_partition_delete(NMPlatform *platform, int parent, int p_key) +{ + NMFakePlatformLink *parent_device; + gs_free char * name = NULL; + + parent_device = link_get(platform, parent); + g_return_val_if_fail(parent_device != NULL, FALSE); + + nm_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key); + return link_delete(platform, nm_platform_link_get_ifindex(platform, name)); +} + +static gboolean +wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps) +{ + NMFakePlatformLink *device = link_get(platform, ifindex); + + g_return_val_if_fail(device, FALSE); + + if (device->obj->link.type != NM_LINK_TYPE_WIFI) + return FALSE; + + if (caps) { + *caps = (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104 + | NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP + | NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN | NM_WIFI_DEVICE_CAP_AP + | NM_WIFI_DEVICE_CAP_ADHOC); + } + return TRUE; +} + +static gboolean +wifi_get_bssid(NMPlatform *platform, int ifindex, guint8 *bssid) +{ + return FALSE; +} + +static guint32 +wifi_get_frequency(NMPlatform *platform, int ifindex) +{ + return 0; +} + +static int +wifi_get_quality(NMPlatform *platform, int ifindex) +{ + return 0; +} + +static guint32 +wifi_get_rate(NMPlatform *platform, int ifindex) +{ + return 0; +} + +static NM80211Mode +wifi_get_mode(NMPlatform *platform, int ifindex) +{ + return NM_802_11_MODE_UNKNOWN; +} + +static void +wifi_set_mode(NMPlatform *platform, int ifindex, NM80211Mode mode) +{ + ; +} + +static guint32 +wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs) +{ + return freqs[0]; +} + +static void +wifi_indicate_addressing_running(NMPlatform *platform, int ifindex, gboolean running) +{} + +static guint32 +mesh_get_channel(NMPlatform *platform, int ifindex) +{ + return 0; +} + +static gboolean +mesh_set_channel(NMPlatform *platform, int ifindex, guint32 channel) +{ + return FALSE; +} + +static gboolean +mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) +{ + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +ipx_address_add(NMPlatform *platform, int addr_family, const NMPlatformObject *address) +{ + nm_auto_nmpobj NMPObject *obj = NULL; + NMPCacheOpsType cache_op; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCache * cache = nm_platform_get_cache(platform); + + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + obj = nmp_object_new(addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS + : NMP_OBJECT_TYPE_IP6_ADDRESS, + address); + + cache_op = nmp_cache_update_netlink(cache, obj, FALSE, &obj_old, &obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + return TRUE; +} + +static gboolean +ip4_address_add(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_addr, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + NMPlatformIP4Address address; + + address = (NMPlatformIP4Address){ + .addr_source = NM_IP_CONFIG_SOURCE_KERNEL, + .ifindex = ifindex, + .address = addr, + .plen = plen, + .peer_address = peer_addr, + .broadcast_address = broadcast_address, + .use_ip4_broadcast_address = TRUE, + .timestamp = nm_utils_get_monotonic_timestamp_sec(), + .lifetime = lifetime, + .preferred = preferred, + .n_ifa_flags = flags, + }; + if (label) + g_strlcpy(address.label, label, sizeof(address.label)); + + return ipx_address_add(platform, AF_INET, (const NMPlatformObject *) &address); +} + +static gboolean +ip6_address_add(NMPlatform * platform, + int ifindex, + struct in6_addr addr, + guint8 plen, + struct in6_addr peer_addr, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + NMPlatformIP6Address address; + + memset(&address, 0, sizeof(address)); + address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + address.ifindex = ifindex; + address.address = addr; + address.peer_address = + (IN6_IS_ADDR_UNSPECIFIED(&peer_addr) || IN6_ARE_ADDR_EQUAL(&addr, &peer_addr)) ? in6addr_any + : peer_addr; + address.plen = plen; + address.timestamp = nm_utils_get_monotonic_timestamp_sec(); + address.lifetime = lifetime; + address.preferred = preferred; + address.n_ifa_flags = flags; + + return ipx_address_add(platform, AF_INET6, (const NMPlatformObject *) &address); +} + +static gboolean +ipx_address_delete(NMPlatform * platform, + int addr_family, + int ifindex, + gconstpointer addr, + const guint8 *plen, + gconstpointer peer_addr) +{ + gs_unref_ptrarray GPtrArray *objs = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + NMDedupMultiIter iter; + const NMPObject *o = NULL; + guint i; + guint32 peer_addr_i; + + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + peer_addr_i = peer_addr ? *((guint32 *) peer_addr) : 0; + + nmp_cache_iter_for_each (&iter, + nm_platform_lookup_object(platform, + addr_family == AF_INET + ? NMP_OBJECT_TYPE_IP4_ADDRESS + : NMP_OBJECT_TYPE_IP6_ADDRESS, + 0), + &o) { + const NMPObject *obj_old = NULL; + + if (addr_family == AF_INET) { + const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS(o); + + if (address->ifindex != ifindex || (addr && address->address != *((guint32 *) addr)) + || (plen && address->plen != *plen) + || (peer_addr + && (((peer_addr_i ^ address->peer_address) + & _nm_utils_ip4_prefix_to_netmask(address->plen)) + != 0))) + continue; + } else { + const NMPlatformIP6Address *address = NMP_OBJECT_CAST_IP6_ADDRESS(o); + + g_assert(!peer_addr); + if (address->ifindex != ifindex + || (addr && !IN6_ARE_ADDR_EQUAL(&address->address, addr)) + || (plen && address->plen != *plen)) + continue; + } + + if (nmp_cache_remove(nm_platform_get_cache(platform), o, TRUE, FALSE, &obj_old) + != NMP_CACHE_OPS_REMOVED) + g_assert_not_reached(); + g_assert(obj_old); + g_ptr_array_add(objs, (gpointer) obj_old); + } + + for (i = 0; i < objs->len; i++) { + nm_platform_cache_update_emit_signal(platform, NMP_CACHE_OPS_REMOVED, objs->pdata[i], NULL); + } + return TRUE; +} + +static gboolean +ip4_address_delete(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_address) +{ + return ipx_address_delete(platform, AF_INET, ifindex, &addr, &plen, &peer_address); +} + +static gboolean +ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen) +{ + return ipx_address_delete(platform, AF_INET6, ifindex, &addr, &plen, NULL); +} + +/*****************************************************************************/ + +static gboolean +ipx_route_delete(NMPlatform *platform, int addr_family, int ifindex, const NMPObject *obj) +{ + gs_unref_ptrarray GPtrArray *objs = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + NMDedupMultiIter iter; + const NMPObject *o = NULL; + guint i; + NMPObjectType obj_type; + + if (addr_family == AF_UNSPEC) { + g_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + g_assert(ifindex == -1); + ifindex = NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex; + obj_type = NMP_OBJECT_GET_TYPE(obj); + } else { + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + g_assert(!obj); + g_assert(ifindex > 0); + obj_type = addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE; + } + + nmp_cache_iter_for_each (&iter, nm_platform_lookup_object(platform, obj_type, ifindex), &o) { + const NMPObject *obj_old = NULL; + + if (obj) { + if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) { + const NMPlatformIP4Route *route = NMP_OBJECT_CAST_IP4_ROUTE(o); + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(obj); + + if (route->network != r->network || route->plen != r->plen + || route->metric != r->metric) + continue; + } else { + const NMPlatformIP6Route *route = NMP_OBJECT_CAST_IP6_ROUTE(o); + const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE(obj); + + if (!IN6_ARE_ADDR_EQUAL(&route->network, &r->network) || route->plen != r->plen + || route->metric != r->metric) + continue; + } + } + + if (nmp_cache_remove(nm_platform_get_cache(platform), o, TRUE, FALSE, &obj_old) + != NMP_CACHE_OPS_REMOVED) + g_assert_not_reached(); + g_assert(obj_old); + g_ptr_array_add(objs, (gpointer) obj_old); + } + + for (i = 0; i < objs->len; i++) { + nm_platform_cache_update_emit_signal(platform, NMP_CACHE_OPS_REMOVED, objs->pdata[i], NULL); + } + return TRUE; +} + +static gboolean +object_delete(NMPlatform *platform, const NMPObject *obj) +{ + g_assert(NM_IS_FAKE_PLATFORM(platform)); + g_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + + return ipx_route_delete(platform, AF_UNSPEC, -1, obj); +} + +static int +ip_route_add(NMPlatform * platform, + NMPNlmFlags flags, + int addr_family, + const NMPlatformIPRoute *route) +{ + NMDedupMultiIter iter; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPCacheOpsType cache_op; + const NMPObject * o = NULL; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *obj_replace = NULL; + NMPCache * cache = nm_platform_get_cache(platform); + gboolean has_gateway = FALSE; + NMPlatformIPRoute * r = NULL; + NMPlatformIP4Route * r4 = NULL; + NMPlatformIP6Route * r6 = NULL; + gboolean has_same_weak_id; + gboolean only_dirty; + guint16 nlmsgflags; + + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + flags = NM_FLAGS_UNSET(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE); + + /* currently, only replace is implemented. */ + g_assert(flags == NMP_NLM_FLAG_REPLACE); + + obj = nmp_object_new(addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE + : NMP_OBJECT_TYPE_IP6_ROUTE, + (const NMPlatformObject *) route); + r = NMP_OBJECT_CAST_IP_ROUTE(obj); + nm_platform_ip_route_normalize(addr_family, r); + + switch (addr_family) { + case AF_INET: + r4 = NMP_OBJECT_CAST_IP4_ROUTE(obj); + if (r4->gateway) + has_gateway = TRUE; + break; + case AF_INET6: + r6 = NMP_OBJECT_CAST_IP6_ROUTE(obj); + if (!IN6_IS_ADDR_UNSPECIFIED(&r6->gateway)) + has_gateway = TRUE; + break; + default: + nm_assert_not_reached(); + } + + if (has_gateway) { + gboolean has_route_to_gw = FALSE; + + nmp_cache_iter_for_each (&iter, + nm_platform_lookup_object(platform, NMP_OBJECT_GET_TYPE(obj), 0), + &o) { + if (addr_family == AF_INET) { + const NMPlatformIP4Route *item = NMP_OBJECT_CAST_IP4_ROUTE(o); + guint32 n = nm_utils_ip4_address_clear_host_address(item->network, item->plen); + guint32 g = nm_utils_ip4_address_clear_host_address(r4->gateway, item->plen); + + if (r->ifindex == item->ifindex && n == g) { + has_route_to_gw = TRUE; + break; + } + } else { + const NMPlatformIP6Route *item = NMP_OBJECT_CAST_IP6_ROUTE(o); + + if (r->ifindex == item->ifindex + && nm_utils_ip6_address_same_prefix(&r6->gateway, &item->network, item->plen)) { + has_route_to_gw = TRUE; + break; + } + } + } + if (!has_route_to_gw) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (addr_family == AF_INET) { + nm_log_warn( + LOGD_PLATFORM, + "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable", + r->ifindex, + _nm_utils_inet4_ntop(r4->network, sbuf), + r->plen, + r->metric); + } else { + nm_log_warn( + LOGD_PLATFORM, + "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable", + r->ifindex, + _nm_utils_inet6_ntop(&r6->network, sbuf), + r->plen, + r->metric); + } + return -NME_UNSPEC; + } + } + + has_same_weak_id = FALSE; + nmp_cache_iter_for_each ( + &iter, + nm_platform_lookup_all(platform, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj), + &o) { + if (addr_family == AF_INET) { + if (nm_platform_ip4_route_cmp(NMP_OBJECT_CAST_IP4_ROUTE(o), + r4, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) + == 0) + continue; + } else { + if (nm_platform_ip6_route_cmp(NMP_OBJECT_CAST_IP6_ROUTE(o), + r6, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) + == 0) + continue; + } + has_same_weak_id = TRUE; + } + + nlmsgflags = 0; + if (has_same_weak_id) { + switch (flags) { + case NMP_NLM_FLAG_REPLACE: + nlmsgflags = NLM_F_REPLACE; + break; + default: + g_assert_not_reached(); + break; + } + } + + /* we manipulate the cache the same was as NMLinuxPlatform does it. */ + cache_op = nmp_cache_update_netlink_route(cache, + obj, + FALSE, + nlmsgflags, + &obj_old, + &obj_new, + &obj_replace, + NULL); + only_dirty = FALSE; + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + if (obj_replace) { + const NMDedupMultiEntry *entry_replace; + + entry_replace = nmp_cache_lookup_entry(cache, obj_replace); + nm_assert(entry_replace && entry_replace->obj == obj_replace); + nm_dedup_multi_entry_set_dirty(entry_replace, TRUE); + only_dirty = TRUE; + } + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + + if (obj_replace) { + cache_op = nmp_cache_remove(cache, obj_replace, TRUE, only_dirty, NULL); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_assert(cache_op == NMP_CACHE_OPS_REMOVED); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_replace, NULL); + } + } + + return 0; +} + +/*****************************************************************************/ + +static void +nm_fake_platform_init(NMFakePlatform *fake_platform) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(fake_platform); + + priv->options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + priv->links = g_array_new(TRUE, TRUE, sizeof(NMFakePlatformLink)); +} + +void +nm_fake_platform_setup(void) +{ + NMPlatform *platform; + + platform = g_object_new(NM_TYPE_FAKE_PLATFORM, NM_PLATFORM_LOG_WITH_PTR, FALSE, NULL); + + nm_platform_setup(platform); + + link_add(platform, NM_LINK_TYPE_LOOPBACK, "lo", 0, NULL, 0, 0, NULL, NULL); + link_add(platform, NM_LINK_TYPE_ETHERNET, "eth0", 0, NULL, 0, 0, NULL, NULL); + link_add(platform, NM_LINK_TYPE_ETHERNET, "eth1", 0, NULL, 0, 0, NULL, NULL); + link_add(platform, NM_LINK_TYPE_ETHERNET, "eth2", 0, NULL, 0, 0, NULL, NULL); +} + +static void +finalize(GObject *object) +{ + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(object); + int i; + + g_hash_table_unref(priv->options); + for (i = 0; i < priv->links->len; i++) { + NMFakePlatformLink *device = &g_array_index(priv->links, NMFakePlatformLink, i); + + nm_clear_pointer(&device->obj, nmp_object_unref); + } + g_array_unref(priv->links); + + G_OBJECT_CLASS(nm_fake_platform_parent_class)->finalize(object); +} + +static void +nm_fake_platform_class_init(NMFakePlatformClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMPlatformClass * platform_class = NM_PLATFORM_CLASS(klass); + NMPlatformKernelSupportType kernel_support; + + for (kernel_support = 0; kernel_support < _NM_PLATFORM_KERNEL_SUPPORT_NUM; kernel_support++) + _nm_platform_kernel_support_init(kernel_support, -1); + + object_class->finalize = finalize; + + platform_class->sysctl_set = sysctl_set; + platform_class->sysctl_get = sysctl_get; + + platform_class->link_add = link_add; + platform_class->link_delete = link_delete; + + platform_class->link_set_up = link_set_up; + platform_class->link_set_down = link_set_down; + platform_class->link_set_arp = link_set_arp; + platform_class->link_set_noarp = link_set_noarp; + + platform_class->link_set_address = link_set_address; + platform_class->link_set_mtu = link_set_mtu; + + platform_class->link_get_driver_info = link_get_driver_info; + + platform_class->link_supports_carrier_detect = link_supports_carrier_detect; + platform_class->link_supports_vlans = link_supports_vlans; + platform_class->link_supports_sriov = link_supports_sriov; + + platform_class->link_enslave = link_enslave; + platform_class->link_release = link_release; + + platform_class->link_vlan_change = link_vlan_change; + + platform_class->infiniband_partition_add = infiniband_partition_add; + platform_class->infiniband_partition_delete = infiniband_partition_delete; + + platform_class->wifi_get_capabilities = wifi_get_capabilities; + platform_class->wifi_get_bssid = wifi_get_bssid; + platform_class->wifi_get_frequency = wifi_get_frequency; + platform_class->wifi_get_quality = wifi_get_quality; + platform_class->wifi_get_rate = wifi_get_rate; + platform_class->wifi_get_mode = wifi_get_mode; + platform_class->wifi_set_mode = wifi_set_mode; + platform_class->wifi_find_frequency = wifi_find_frequency; + platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running; + + platform_class->mesh_get_channel = mesh_get_channel; + platform_class->mesh_set_channel = mesh_set_channel; + platform_class->mesh_set_ssid = mesh_set_ssid; + + platform_class->ip4_address_add = ip4_address_add; + platform_class->ip6_address_add = ip6_address_add; + platform_class->ip4_address_delete = ip4_address_delete; + platform_class->ip6_address_delete = ip6_address_delete; + + platform_class->ip_route_add = ip_route_add; + platform_class->object_delete = object_delete; +} diff --git a/src/core/platform/nm-fake-platform.h b/src/core/platform/nm-fake-platform.h new file mode 100644 index 0000000..a1d44f8 --- /dev/null +++ b/src/core/platform/nm-fake-platform.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_FAKE_PLATFORM_H__ +#define __NETWORKMANAGER_FAKE_PLATFORM_H__ + +#include "nm-platform.h" + +#define NM_TYPE_FAKE_PLATFORM (nm_fake_platform_get_type()) +#define NM_FAKE_PLATFORM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_FAKE_PLATFORM, NMFakePlatform)) +#define NM_FAKE_PLATFORM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_FAKE_PLATFORM, NMFakePlatformClass)) +#define NM_IS_FAKE_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_FAKE_PLATFORM)) +#define NM_IS_FAKE_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_FAKE_PLATFORM)) +#define NM_FAKE_PLATFORM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_FAKE_PLATFORM, NMFakePlatformClass)) + +typedef struct _NMFakePlatform NMFakePlatform; +typedef struct _NMFakePlatformClass NMFakePlatformClass; + +GType nm_fake_platform_get_type(void); + +void nm_fake_platform_setup(void); + +#endif /* __NETWORKMANAGER_FAKE_PLATFORM_H__ */ diff --git a/src/core/platform/nm-linux-platform.c b/src/core/platform/nm-linux-platform.c new file mode 100644 index 0000000..ae6c4f4 --- /dev/null +++ b/src/core/platform/nm-linux-platform.c @@ -0,0 +1,9696 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-linux-platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-std-aux/unaligned.h" + +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-setting-vlan.h" + +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-platform/nm-netlink.h" +#include "nm-core-utils.h" +#include "nmp-object.h" +#include "nm-platform/nmp-netns.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-platform-private.h" +#include "wifi/nm-wifi-utils.h" +#include "wifi/nm-wifi-utils-wext.h" +#include "wpan/nm-wpan-utils.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-udev-aux/nm-udev-utils.h" + +/*****************************************************************************/ + +/* re-implement to build against kernel + * headers that lack this. */ + +#include + +struct tc_defact { + tc_gen; +}; + +enum { TCA_DEF_UNSPEC, TCA_DEF_TM, TCA_DEF_PARMS, TCA_DEF_DATA, TCA_DEF_PAD, __TCA_DEF_MAX }; +#define TCA_DEF_MAX (__TCA_DEF_MAX - 1) + +/*****************************************************************************/ + +/* Compat with older kernels. */ + +#define TCA_FQ_CODEL_CE_THRESHOLD 7 +#define TCA_FQ_CODEL_MEMORY_LIMIT 9 + +/*****************************************************************************/ + +#define VLAN_FLAG_MVRP 0x8 + +/*****************************************************************************/ + +#define IFQDISCSIZ 32 + +/*****************************************************************************/ + +#ifndef IFLA_PROMISCUITY + #define IFLA_PROMISCUITY 30 +#endif +#define IFLA_NUM_TX_QUEUES 31 +#define IFLA_NUM_RX_QUEUES 32 +#define IFLA_CARRIER 33 +#define IFLA_PHYS_PORT_ID 34 +#define IFLA_LINK_NETNSID 37 +#define __IFLA_MAX 39 + +#define IFLA_INET6_TOKEN 7 +#define IFLA_INET6_ADDR_GEN_MODE 8 +#define __IFLA_INET6_MAX 9 + +#define IFLA_VLAN_PROTOCOL 5 +#define __IFLA_VLAN_MAX 6 + +#define IFA_FLAGS 8 +#define __IFA_MAX 9 + +#define IFLA_MACVLAN_FLAGS 2 +#define __IFLA_MACVLAN_MAX 3 + +#define IFLA_IPTUN_LINK 1 +#define IFLA_IPTUN_LOCAL 2 +#define IFLA_IPTUN_REMOTE 3 +#define IFLA_IPTUN_TTL 4 +#define IFLA_IPTUN_TOS 5 +#define IFLA_IPTUN_ENCAP_LIMIT 6 +#define IFLA_IPTUN_FLOWINFO 7 +#define IFLA_IPTUN_FLAGS 8 +#define IFLA_IPTUN_PROTO 9 +#define IFLA_IPTUN_PMTUDISC 10 +#define __IFLA_IPTUN_MAX 19 +#ifndef IFLA_IPTUN_MAX + #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) +#endif + +#define IFLA_TUN_UNSPEC 0 +#define IFLA_TUN_OWNER 1 +#define IFLA_TUN_GROUP 2 +#define IFLA_TUN_TYPE 3 +#define IFLA_TUN_PI 4 +#define IFLA_TUN_VNET_HDR 5 +#define IFLA_TUN_PERSIST 6 +#define IFLA_TUN_MULTI_QUEUE 7 +#define IFLA_TUN_NUM_QUEUES 8 +#define IFLA_TUN_NUM_DISABLED_QUEUES 9 +#define __IFLA_TUN_MAX 10 +#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1) + +G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1)); +#define RTA_PREF 20 +#undef RTA_MAX +#define RTA_MAX (MAX((__RTA_MAX - 1), RTA_PREF)) + +#ifndef MACVLAN_FLAG_NOPROMISC + #define MACVLAN_FLAG_NOPROMISC 1 +#endif + +#define IP6_FLOWINFO_TCLASS_MASK 0x0FF00000 +#define IP6_FLOWINFO_TCLASS_SHIFT 20 +#define IP6_FLOWINFO_FLOWLABEL_MASK 0x000FFFFF + +#define IFLA_BR_VLAN_STATS_ENABLED 41 + +/*****************************************************************************/ + +/* Appeared in the kernel prior to 3.13 dated 19 January, 2014 */ +#ifndef ARPHRD_6LOWPAN + #define ARPHRD_6LOWPAN 825 +#endif + +/*****************************************************************************/ + +#define FRA_TUN_ID 12 +#define FRA_SUPPRESS_IFGROUP 13 +#define FRA_SUPPRESS_PREFIXLEN 14 +#define FRA_PAD 18 +#define FRA_L3MDEV 19 +#define FRA_UID_RANGE 20 +#define FRA_PROTOCOL 21 +#define FRA_IP_PROTO 22 +#define FRA_SPORT_RANGE 23 +#define FRA_DPORT_RANGE 24 + +/*****************************************************************************/ + +#define IFLA_MACSEC_UNSPEC 0 +#define IFLA_MACSEC_SCI 1 +#define IFLA_MACSEC_PORT 2 +#define IFLA_MACSEC_ICV_LEN 3 +#define IFLA_MACSEC_CIPHER_SUITE 4 +#define IFLA_MACSEC_WINDOW 5 +#define IFLA_MACSEC_ENCODING_SA 6 +#define IFLA_MACSEC_ENCRYPT 7 +#define IFLA_MACSEC_PROTECT 8 +#define IFLA_MACSEC_INC_SCI 9 +#define IFLA_MACSEC_ES 10 +#define IFLA_MACSEC_SCB 11 +#define IFLA_MACSEC_REPLAY_PROTECT 12 +#define IFLA_MACSEC_VALIDATION 13 +#define IFLA_MACSEC_PAD 14 +#define __IFLA_MACSEC_MAX 15 + +/*****************************************************************************/ + +#define WG_CMD_GET_DEVICE 0 +#define WG_CMD_SET_DEVICE 1 + +#define WGDEVICE_F_REPLACE_PEERS ((guint32)(1U << 0)) + +#define WGPEER_F_REMOVE_ME ((guint32)(1U << 0)) +#define WGPEER_F_REPLACE_ALLOWEDIPS ((guint32)(1U << 1)) + +#define WGDEVICE_A_UNSPEC 0 +#define WGDEVICE_A_IFINDEX 1 +#define WGDEVICE_A_IFNAME 2 +#define WGDEVICE_A_PRIVATE_KEY 3 +#define WGDEVICE_A_PUBLIC_KEY 4 +#define WGDEVICE_A_FLAGS 5 +#define WGDEVICE_A_LISTEN_PORT 6 +#define WGDEVICE_A_FWMARK 7 +#define WGDEVICE_A_PEERS 8 +#define WGDEVICE_A_MAX 8 + +#define WGPEER_A_UNSPEC 0 +#define WGPEER_A_PUBLIC_KEY 1 +#define WGPEER_A_PRESHARED_KEY 2 +#define WGPEER_A_FLAGS 3 +#define WGPEER_A_ENDPOINT 4 +#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5 +#define WGPEER_A_LAST_HANDSHAKE_TIME 6 +#define WGPEER_A_RX_BYTES 7 +#define WGPEER_A_TX_BYTES 8 +#define WGPEER_A_ALLOWEDIPS 9 +#define WGPEER_A_MAX 9 + +#define WGALLOWEDIP_A_UNSPEC 0 +#define WGALLOWEDIP_A_FAMILY 1 +#define WGALLOWEDIP_A_IPADDR 2 +#define WGALLOWEDIP_A_CIDR_MASK 3 +#define WGALLOWEDIP_A_MAX 3 + +/*****************************************************************************/ + +/* Redefine VF enums and structures that are not available on older kernels. */ + +#define IFLA_VF_UNSPEC 0 +#define IFLA_VF_MAC 1 +#define IFLA_VF_VLAN 2 +#define IFLA_VF_TX_RATE 3 +#define IFLA_VF_SPOOFCHK 4 +#define IFLA_VF_LINK_STATE 5 +#define IFLA_VF_RATE 6 +#define IFLA_VF_RSS_QUERY_EN 7 +#define IFLA_VF_STATS 8 +#define IFLA_VF_TRUST 9 +#define IFLA_VF_IB_NODE_GUID 10 +#define IFLA_VF_IB_PORT_GUID 11 +#define IFLA_VF_VLAN_LIST 12 + +#define IFLA_VF_VLAN_INFO_UNSPEC 0 +#define IFLA_VF_VLAN_INFO 1 + +/* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */ +struct _ifla_vf_setting { + guint32 vf; + guint32 setting; +}; + +struct _ifla_vf_rate { + guint32 vf; + guint32 min_tx_rate; + guint32 max_tx_rate; +}; + +struct _ifla_vf_vlan_info { + guint32 vf; + guint32 vlan; /* 0 - 4095, 0 disables VLAN filter */ + guint32 qos; + guint16 vlan_proto; /* VLAN protocol, either 802.1Q or 802.1ad */ +}; + +/*****************************************************************************/ + +/* Appeared in the kernel 4.0 dated April 12, 2015 */ +#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN + #define BRIDGE_VLAN_INFO_RANGE_BEGIN (1 << 3) /* VLAN is start of vlan range */ + #define BRIDGE_VLAN_INFO_RANGE_END (1 << 4) /* VLAN is end of vlan range */ +#endif + +/*****************************************************************************/ + +#define PSCHED_TIME_UNITS_PER_SEC 1000000 + +/*****************************************************************************/ + +typedef enum { + INFINIBAND_ACTION_CREATE_CHILD, + INFINIBAND_ACTION_DELETE_CHILD, +} InfinibandAction; + +typedef enum { + CHANGE_LINK_TYPE_UNSPEC, + CHANGE_LINK_TYPE_SET_MTU, + CHANGE_LINK_TYPE_SET_ADDRESS, +} ChangeLinkType; + +typedef struct { + union { + struct { + gconstpointer address; + gsize length; + } set_address; + }; +} ChangeLinkData; + +typedef enum { + _REFRESH_ALL_TYPE_FIRST = 0, + + REFRESH_ALL_TYPE_LINKS = 0, + REFRESH_ALL_TYPE_IP4_ADDRESSES = 1, + REFRESH_ALL_TYPE_IP6_ADDRESSES = 2, + REFRESH_ALL_TYPE_IP4_ROUTES = 3, + REFRESH_ALL_TYPE_IP6_ROUTES = 4, + REFRESH_ALL_TYPE_ROUTING_RULES_IP4 = 5, + REFRESH_ALL_TYPE_ROUTING_RULES_IP6 = 6, + REFRESH_ALL_TYPE_QDISCS = 7, + REFRESH_ALL_TYPE_TFILTERS = 8, + + _REFRESH_ALL_TYPE_NUM, +} RefreshAllType; + +typedef struct { + NMPObjectType obj_type; + + /* for NLM_F_DUMP, which address family to request. */ + int addr_family; +} RefreshAllInfo; + +typedef enum { + DELAYED_ACTION_TYPE_NONE = 0, + +#define F(val, name) ((sizeof(char[(((val)) == (name)) ? 1 : -1]) * 0) + (val)) + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS = 1 << F(0, REFRESH_ALL_TYPE_LINKS), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES = 1 << F(1, REFRESH_ALL_TYPE_IP4_ADDRESSES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES = 1 << F(2, REFRESH_ALL_TYPE_IP6_ADDRESSES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES = 1 << F(3, REFRESH_ALL_TYPE_IP4_ROUTES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES = 1 << F(4, REFRESH_ALL_TYPE_IP6_ROUTES), + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 = 1 + << F(5, REFRESH_ALL_TYPE_ROUTING_RULES_IP4), + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6 = 1 + << F(6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6), + DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS = 1 << F(7, REFRESH_ALL_TYPE_QDISCS), + DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS = 1 << F(8, REFRESH_ALL_TYPE_TFILTERS), +#undef F + + DELAYED_ACTION_TYPE_REFRESH_LINK = 1 << 9, + DELAYED_ACTION_TYPE_MASTER_CONNECTED = 1 << 10, + DELAYED_ACTION_TYPE_READ_NETLINK = 1 << 11, + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE = 1 << 12, + + __DELAYED_ACTION_TYPE_MAX, + + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL = + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + + DELAYED_ACTION_TYPE_REFRESH_ALL = + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + + DELAYED_ACTION_TYPE_MAX = __DELAYED_ACTION_TYPE_MAX - 1, +} DelayedActionType; + +#define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \ + for ((iflags) = (DelayedActionType) 0x1LL; ({ \ + gboolean _good = FALSE; \ + \ + nm_assert(nm_utils_is_power_of_two(iflags)); \ + \ + while ((iflags) <= DELAYED_ACTION_TYPE_MAX) { \ + if (NM_FLAGS_ANY((flags_all), (iflags))) { \ + _good = TRUE; \ + break; \ + } \ + (iflags) <<= 1; \ + } \ + _good; \ + }); \ + (iflags) <<= 1) + +typedef enum { + /* Negative values are errors from kernel. Add dummy member to + * make enum signed. */ + _WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT, + + WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0, + WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK, + WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS, +} WaitForNlResponseResult; + +typedef enum { + DELAYED_ACTION_RESPONSE_TYPE_VOID = 0, + DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS = 1, + DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET = 2, +} DelayedActionWaitForNlResponseType; + +typedef struct { + guint32 seq_number; + WaitForNlResponseResult seq_result; + DelayedActionWaitForNlResponseType response_type; + gint64 timeout_abs_ns; + WaitForNlResponseResult * out_seq_result; + char ** out_errmsg; + union { + int * out_refresh_all_in_progress; + NMPObject **out_route_get; + gpointer out_data; + } response; +} DelayedActionWaitForNlResponseData; + +/*****************************************************************************/ + +typedef struct { + struct nl_sock *genl; + + struct nl_sock *nlh; + + GSource *event_source; + + guint32 nlh_seq_next; +#if NM_MORE_LOGGING + guint32 nlh_seq_last_handled; +#endif + guint32 nlh_seq_last_seen; + + guint32 pruning[_REFRESH_ALL_TYPE_NUM]; + + GHashTable *sysctl_get_prev_values; + CList sysctl_list; + + NMUdevClient *udev_client; + + struct { + /* which delayed actions are scheduled, as marked in @flags. + * Some types have additional arguments in the fields below. */ + DelayedActionType flags; + + /* counter that a refresh all action is in progress, separated + * by type. */ + int refresh_all_in_progress[_REFRESH_ALL_TYPE_NUM]; + + GPtrArray *list_master_connected; + GPtrArray *list_refresh_link; + GArray * list_wait_for_nl_response; + + int is_handling; + } delayed_action; +} NMLinuxPlatformPrivate; + +struct _NMLinuxPlatform { + NMPlatform parent; + NMLinuxPlatformPrivate _priv; +}; + +struct _NMLinuxPlatformClass { + NMPlatformClass parent; +}; + +G_DEFINE_TYPE(NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM) + +#define NM_LINUX_PLATFORM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "platform-linux" +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG2_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG_err(errsv, level, ...) _LOG_err(errsv, level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG2(level, ...) _LOG(level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__) +#define _NMLOG2_err(errsv, level, ...) _LOG_err(errsv, level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__) + +#define _LOG_print(__level, __domain, __errsv, self, ...) \ + G_STMT_START \ + { \ + char __prefix[32]; \ + const char * __p_prefix = _NMLOG_PREFIX_NAME; \ + NMPlatform *const __self = (self); \ + \ + if (__self && nm_platform_get_log_with_ptr(__self)) { \ + g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + __domain, \ + __errsv, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define _LOG(level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + _LOG_print(__level, __domain, 0, self, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _LOG_err(errsv, level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + int __errsv = (errsv); \ + \ + /* The %m format specifier (GNU extension) would already allow you to specify the error + * message conveniently (and nm_log would get that right too). But we don't want to depend + * on that, so instead append the message at the end. + * Currently, users are expected not to use %m in the format string. */ \ + _LOG_print( \ + __level, \ + __domain, \ + __errsv, \ + self, \ + _NM_UTILS_MACRO_FIRST(__VA_ARGS__) ": %s (%d)" _NM_UTILS_MACRO_REST(__VA_ARGS__), \ + nm_strerror_native(__errsv), \ + __errsv); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data); +static gboolean delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink); +static void do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name); +static void do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type); +static void cache_on_change(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new); +static void cache_prune_all(NMPlatform *platform); +static gboolean event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks); +static struct nl_sock *_genl_sock(NMLinuxPlatform *platform); + +/*****************************************************************************/ + +static int +wait_for_nl_response_to_nmerr(WaitForNlResponseResult seq_result) +{ + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return (int) seq_result; + return -NME_PL_NETLINK; +} + +static const char * +wait_for_nl_response_to_string(WaitForNlResponseResult seq_result, + const char * errmsg, + char * buf, + gsize buf_size) +{ + char *buf0 = buf; + + switch (seq_result) { + case WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN: + nm_utils_strbuf_append_str(&buf, &buf_size, "unknown"); + break; + case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK: + nm_utils_strbuf_append_str(&buf, &buf_size, "success"); + break; + case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN: + nm_utils_strbuf_append_str(&buf, &buf_size, "failure"); + break; + default: + if (seq_result < 0) { + nm_utils_strbuf_append(&buf, + &buf_size, + "failure %d (%s%s%s)", + -((int) seq_result), + nm_strerror_native(-((int) seq_result)), + errmsg ? " - " : "", + errmsg ?: ""); + } else + nm_utils_strbuf_append(&buf, &buf_size, "internal failure %d", (int) seq_result); + break; + } + return buf0; +} + +/****************************************************************** + * Various utilities + ******************************************************************/ + +static int +_vlan_qos_mapping_cmp_from(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMVlanQosMapping *map_a = a; + const NMVlanQosMapping *map_b = b; + + if (map_a->from != map_b->from) + return map_a->from < map_b->from ? -1 : 1; + return 0; +} + +static int +_vlan_qos_mapping_cmp_from_ptr(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return _vlan_qos_mapping_cmp_from(*((const NMVlanQosMapping **) a), + *((const NMVlanQosMapping **) b), + NULL); +} + +/****************************************************************** + * NMLinkType functions + ******************************************************************/ + +typedef struct { + const char *type_string; + + /* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type + * should only be specified if the device type can be created without + * additional parameters, and if the device type can be determined from + * the rtnl_type. eg, tun/tap should not be specified since both + * tun and tap devices use "tun", and InfiniBand should not be + * specified because a PKey is required at creation. Drivers set this + * value from their 'struct rtnl_link_ops' structure. + */ + const char *rtnl_type; + + /* uevent DEVTYPE where applicable, from /sys/class/net//uevent; + * drivers set this value from their SET_NETDEV_DEV() call and the + * 'struct device_type' name member. + */ + const char *devtype; +} LinkDesc; + +static const LinkDesc link_descs[] = { + [NM_LINK_TYPE_NONE] = {"none", NULL, NULL}, + [NM_LINK_TYPE_UNKNOWN] = {"unknown", NULL, NULL}, + [NM_LINK_TYPE_ANY] = {"any", NULL, NULL}, + + [NM_LINK_TYPE_ETHERNET] = {"ethernet", NULL, NULL}, + [NM_LINK_TYPE_INFINIBAND] = {"infiniband", NULL, NULL}, + [NM_LINK_TYPE_OLPC_MESH] = {"olpc-mesh", NULL, NULL}, + [NM_LINK_TYPE_WIFI] = {"wifi", NULL, "wlan"}, + [NM_LINK_TYPE_WWAN_NET] = {"wwan", NULL, "wwan"}, + [NM_LINK_TYPE_WIMAX] = {"wimax", "wimax", "wimax"}, + [NM_LINK_TYPE_WPAN] = {"wpan", NULL, NULL}, + [NM_LINK_TYPE_6LOWPAN] = {"6lowpan", NULL, NULL}, + + [NM_LINK_TYPE_BNEP] = {"bluetooth", NULL, "bluetooth"}, + [NM_LINK_TYPE_DUMMY] = {"dummy", "dummy", NULL}, + [NM_LINK_TYPE_GRE] = {"gre", "gre", NULL}, + [NM_LINK_TYPE_GRETAP] = {"gretap", "gretap", NULL}, + [NM_LINK_TYPE_IFB] = {"ifb", "ifb", NULL}, + [NM_LINK_TYPE_IP6TNL] = {"ip6tnl", "ip6tnl", NULL}, + [NM_LINK_TYPE_IP6GRE] = {"ip6gre", "ip6gre", NULL}, + [NM_LINK_TYPE_IP6GRETAP] = {"ip6gretap", "ip6gretap", NULL}, + [NM_LINK_TYPE_IPIP] = {"ipip", "ipip", NULL}, + [NM_LINK_TYPE_LOOPBACK] = {"loopback", NULL, NULL}, + [NM_LINK_TYPE_MACSEC] = {"macsec", "macsec", NULL}, + [NM_LINK_TYPE_MACVLAN] = {"macvlan", "macvlan", NULL}, + [NM_LINK_TYPE_MACVTAP] = {"macvtap", "macvtap", NULL}, + [NM_LINK_TYPE_OPENVSWITCH] = {"openvswitch", "openvswitch", NULL}, + [NM_LINK_TYPE_PPP] = {"ppp", NULL, "ppp"}, + [NM_LINK_TYPE_SIT] = {"sit", "sit", NULL}, + [NM_LINK_TYPE_TUN] = {"tun", "tun", NULL}, + [NM_LINK_TYPE_VETH] = {"veth", "veth", NULL}, + [NM_LINK_TYPE_VLAN] = {"vlan", "vlan", "vlan"}, + [NM_LINK_TYPE_VRF] = {"vrf", "vrf", "vrf"}, + [NM_LINK_TYPE_VXLAN] = {"vxlan", "vxlan", "vxlan"}, + [NM_LINK_TYPE_WIREGUARD] = {"wireguard", "wireguard", "wireguard"}, + + [NM_LINK_TYPE_BRIDGE] = {"bridge", "bridge", "bridge"}, + [NM_LINK_TYPE_BOND] = {"bond", "bond", "bond"}, + [NM_LINK_TYPE_TEAM] = {"team", "team", NULL}, +}; + +static const LinkDesc * +_link_desc_from_link_type(NMLinkType link_type) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(link_type)); + nm_assert(link_type < G_N_ELEMENTS(link_descs)); + nm_assert(link_descs[link_type].type_string); + + return &link_descs[link_type]; +} + +static NMLinkType +_link_type_from_rtnl_type(const char *name) +{ + static const NMLinkType LIST[] = { + NM_LINK_TYPE_BOND, /* "bond" */ + NM_LINK_TYPE_BRIDGE, /* "bridge" */ + NM_LINK_TYPE_DUMMY, /* "dummy" */ + NM_LINK_TYPE_GRE, /* "gre" */ + NM_LINK_TYPE_GRETAP, /* "gretap" */ + NM_LINK_TYPE_IFB, /* "ifb" */ + NM_LINK_TYPE_IP6GRE, /* "ip6gre" */ + NM_LINK_TYPE_IP6GRETAP, /* "ip6gretap" */ + NM_LINK_TYPE_IP6TNL, /* "ip6tnl" */ + NM_LINK_TYPE_IPIP, /* "ipip" */ + NM_LINK_TYPE_MACSEC, /* "macsec" */ + NM_LINK_TYPE_MACVLAN, /* "macvlan" */ + NM_LINK_TYPE_MACVTAP, /* "macvtap" */ + NM_LINK_TYPE_OPENVSWITCH, /* "openvswitch" */ + NM_LINK_TYPE_SIT, /* "sit" */ + NM_LINK_TYPE_TEAM, /* "team" */ + NM_LINK_TYPE_TUN, /* "tun" */ + NM_LINK_TYPE_VETH, /* "veth" */ + NM_LINK_TYPE_VLAN, /* "vlan" */ + NM_LINK_TYPE_VRF, /* "vrf" */ + NM_LINK_TYPE_VXLAN, /* "vxlan" */ + NM_LINK_TYPE_WIMAX, /* "wimax" */ + NM_LINK_TYPE_WIREGUARD, /* "wireguard" */ + }; + + nm_assert(name); + + if (NM_MORE_ASSERT_ONCE(5)) { + int i, j, k; + + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { + nm_assert(_link_desc_from_link_type(LIST[i]) == &link_descs[LIST[i]]); + nm_assert(link_descs[LIST[i]].rtnl_type); + if (i > 0) + nm_assert(strcmp(link_descs[LIST[i - 1]].rtnl_type, link_descs[LIST[i]].rtnl_type) + < 0); + } + for (i = 0; i < G_N_ELEMENTS(link_descs); i++) { + if (!link_descs[i].rtnl_type) + continue; + for (j = 0, k = 0; j < G_N_ELEMENTS(LIST); j++) + k += (LIST[j] == i); + nm_assert(k == 1); + } + } + + { + int imin = 0; + int imax = (G_N_ELEMENTS(LIST) - 1); + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; + + for (;;) { + const int cmp = strcmp(link_descs[LIST[imid]].rtnl_type, name); + + if (G_UNLIKELY(cmp == 0)) + return LIST[imid]; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + + if (G_UNLIKELY(imin > imax)) + return NM_LINK_TYPE_NONE; + + imid = (imin + imax) / 2; + } + } +} + +static NMLinkType +_link_type_from_devtype(const char *name) +{ + static const NMLinkType LIST[] = { + NM_LINK_TYPE_BNEP, /* "bluetooth" */ + NM_LINK_TYPE_BOND, /* "bond" */ + NM_LINK_TYPE_BRIDGE, /* "bridge" */ + NM_LINK_TYPE_PPP, /* "ppp" */ + NM_LINK_TYPE_VLAN, /* "vlan" */ + NM_LINK_TYPE_VRF, /* "vrf" */ + NM_LINK_TYPE_VXLAN, /* "vxlan" */ + NM_LINK_TYPE_WIMAX, /* "wimax" */ + NM_LINK_TYPE_WIREGUARD, /* "wireguard" */ + NM_LINK_TYPE_WIFI, /* "wlan" */ + NM_LINK_TYPE_WWAN_NET, /* "wwan" */ + }; + + nm_assert(name); + + if (NM_MORE_ASSERT_ONCE(5)) { + int i, j, k; + + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { + nm_assert(_link_desc_from_link_type(LIST[i]) == &link_descs[LIST[i]]); + nm_assert(link_descs[LIST[i]].devtype); + if (i > 0) + nm_assert(strcmp(link_descs[LIST[i - 1]].devtype, link_descs[LIST[i]].devtype) < 0); + } + for (i = 0; i < G_N_ELEMENTS(link_descs); i++) { + if (!link_descs[i].devtype) + continue; + for (j = 0, k = 0; j < G_N_ELEMENTS(LIST); j++) + k += (LIST[j] == i); + nm_assert(k == 1); + } + } + + { + int imin = 0; + int imax = (G_N_ELEMENTS(LIST) - 1); + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; + + for (;;) { + const int cmp = strcmp(link_descs[LIST[imid]].devtype, name); + + if (G_UNLIKELY(cmp == 0)) + return LIST[imid]; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + + if (G_UNLIKELY(imin > imax)) + return NM_LINK_TYPE_NONE; + + imid = (imin + imax) / 2; + } + } +} + +static const char * +nm_link_type_to_rtnl_type_string(NMLinkType link_type) +{ + return _link_desc_from_link_type(link_type)->rtnl_type; +} + +const char * +nm_link_type_to_string(NMLinkType link_type) +{ + return _link_desc_from_link_type(link_type)->type_string; +} + +/****************************************************************** + * Utilities + ******************************************************************/ + +/* _timestamp_nl_to_ms: + * @timestamp_nl: a timestamp from ifa_cacheinfo. + * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current + * uptime and how often timestamp_nl wrapped. + * + * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds. + * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts + * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However, + * the uint32 counter wraps every 497 days of uptime, so we have to compensate + * for that. */ +static gint64 +_timestamp_nl_to_ms(guint32 timestamp_nl, gint64 monotonic_ms) +{ + const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100); + gint64 timestamp_nl_ms; + + /* convert timestamp from 1/100th of a second to msec. */ + timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100); + + /* timestamp wraps every 497 days. Try to compensate for that.*/ + if (timestamp_nl_ms > monotonic_ms) { + /* timestamp_nl_ms is in the future. Truncate it to *now* */ + timestamp_nl_ms = monotonic_ms; + } else if (monotonic_ms >= WRAP_INTERVAL) { + timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL; + if (timestamp_nl_ms > monotonic_ms) + timestamp_nl_ms -= WRAP_INTERVAL; + } + + return timestamp_nl_ms; +} + +static guint32 +_addrtime_timestamp_to_nm(guint32 timestamp, gint32 *out_now_nm) +{ + gint64 now_nl; + gint64 now_nm; + gint64 result; + + /* timestamp is unset. Default to 1. */ + if (!timestamp) { + NM_SET_OUT(out_now_nm, 0); + return 1; + } + + /* do all the calculations in milliseconds scale */ + + now_nm = nm_utils_get_monotonic_timestamp_msec(); + now_nl = nm_utils_clock_gettime_msec(CLOCK_MONOTONIC); + + nm_assert(now_nm >= 1000); + nm_assert(now_nl >= 0); + + result = now_nm - (now_nl - _timestamp_nl_to_ms(timestamp, now_nl)); + + NM_SET_OUT(out_now_nm, now_nm / 1000); + + /* converting the timestamp into nm_utils_get_monotonic_timestamp_msec() scale is + * a good guess but fails in the following situations: + * + * - If the address existed before start of the process, the timestamp in nm scale would + * be negative or zero. In this case we default to 1. + * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from + * nm_utils_get_monotonic_timestamp_msec() scale. + */ + if (result <= 1000) + return 1; + + if (result > now_nm) + return now_nm / 1000; + + return result / 1000; +} + +static guint32 +_addrtime_extend_lifetime(guint32 lifetime, guint32 seconds) +{ + guint64 v; + + if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT || seconds == 0) + return lifetime; + + v = (guint64) lifetime + (guint64) seconds; + return MIN(v, NM_PLATFORM_LIFETIME_PERMANENT - 1); +} + +/* The rtnl_addr object contains relative lifetimes @valid and @preferred + * that count in seconds, starting from the moment when the kernel constructed + * the netlink message. + * + * There is also a field rtnl_addr_last_update_time(), which is the absolute + * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address + * was modified (wrapping every 497 days). + * Immediately at the time when the address was last modified, #NOW and @last_update_time + * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time. + * However, this is not true in general. As time goes by, whenever kernel sends a new address + * via netlink, the lifetimes keep counting down. + **/ +static void +_addrtime_get_lifetimes(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + guint32 *out_timestamp, + guint32 *out_lifetime, + guint32 *out_preferred) +{ + gint32 now; + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { + if (preferred > lifetime) + preferred = lifetime; + timestamp = _addrtime_timestamp_to_nm(timestamp, &now); + + if (now == 0) { + /* strange. failed to detect the last-update time and assumed that timestamp is 1. */ + nm_assert(timestamp == 1); + now = nm_utils_get_monotonic_timestamp_sec(); + } + if (timestamp < now) { + guint32 diff = now - timestamp; + + lifetime = _addrtime_extend_lifetime(lifetime, diff); + preferred = _addrtime_extend_lifetime(preferred, diff); + } else + nm_assert(timestamp == now); + } else + timestamp = 0; + *out_timestamp = timestamp; + *out_lifetime = lifetime; + *out_preferred = preferred; +} + +/*****************************************************************************/ + +static const NMPObject * +_lookup_cached_link(const NMPCache * cache, + int ifindex, + gboolean * completed_from_cache, + const NMPObject **link_cached) +{ + const NMPObject *obj; + + nm_assert(completed_from_cache && link_cached); + + if (!*completed_from_cache) { + obj = ifindex > 0 && cache ? nmp_cache_lookup_link(cache, ifindex) : NULL; + + *link_cached = obj; + *completed_from_cache = TRUE; + } + return *link_cached; +} + +/*****************************************************************************/ + +#define DEVTYPE_PREFIX "DEVTYPE=" + +static char * +_linktype_read_devtype(int dirfd) +{ + gs_free char *contents = NULL; + char * cont, *end; + + nm_assert(dirfd >= 0); + + if (!nm_utils_file_get_contents(dirfd, + "uevent", + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + NULL)) + return NULL; + for (cont = contents; cont; cont = end) { + end = strpbrk(cont, "\r\n"); + if (end) + *end++ = '\0'; + if (strncmp(cont, DEVTYPE_PREFIX, NM_STRLEN(DEVTYPE_PREFIX)) == 0) { + cont += NM_STRLEN(DEVTYPE_PREFIX); + memmove(contents, cont, strlen(cont) + 1); + return g_steal_pointer(&contents); + } + } + return NULL; +} + +static NMLinkType +_linktype_get_type(NMPlatform * platform, + const NMPCache * cache, + const char * kind, + int ifindex, + const char * ifname, + unsigned flags, + unsigned arptype, + gboolean * completed_from_cache, + const NMPObject **link_cached, + const char ** out_kind) +{ + NMLinkType link_type; + + NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform); + nm_assert(ifname); + nm_assert(_link_type_from_devtype("wlan") == NM_LINK_TYPE_WIFI); + nm_assert(_link_type_from_rtnl_type("bond") == NM_LINK_TYPE_BOND); + + if (completed_from_cache) { + const NMPObject *obj; + + obj = _lookup_cached_link(cache, ifindex, completed_from_cache, link_cached); + + /* If we detected the link type before, we stick to that + * decision unless the "kind" or "name" changed. If "name" changed, + * it means that their type may not have been determined correctly + * due to race conditions while accessing sysfs. + * + * This way, we save additional ethtool/sysctl lookups, but moreover, + * we keep the linktype stable and don't change it as long as the link + * exists. + * + * Note that kernel *can* reuse the ifindex (on integer overflow, and + * when moving interface to other netns). Thus here there is a tiny potential + * of messing stuff up. */ + if (obj && obj->_link.netlink.is_in_netlink + && !NM_IN_SET(obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE) + && nm_streq(ifname, obj->link.name) && (!kind || nm_streq0(kind, obj->link.kind))) { + nm_assert(obj->link.kind == g_intern_string(obj->link.kind)); + *out_kind = obj->link.kind; + return obj->link.type; + } + } + + /* we intern kind to not require us to keep the pointer alive. Essentially + * leaking it in a global cache. That should be safe enough, because the + * kind comes only from kernel messages, which depend on the number of + * available drivers. So, there is not the danger that we leak uncontrolled + * many kinds. */ + *out_kind = g_intern_string(kind); + + if (kind) { + link_type = _link_type_from_rtnl_type(kind); + if (link_type != NM_LINK_TYPE_NONE) + return link_type; + } + + if (arptype == ARPHRD_LOOPBACK) + return NM_LINK_TYPE_LOOPBACK; + else if (arptype == ARPHRD_INFINIBAND) + return NM_LINK_TYPE_INFINIBAND; + else if (arptype == ARPHRD_SIT) + return NM_LINK_TYPE_SIT; + else if (arptype == ARPHRD_TUNNEL6) + return NM_LINK_TYPE_IP6TNL; + else if (arptype == ARPHRD_PPP) + return NM_LINK_TYPE_PPP; + else if (arptype == ARPHRD_IEEE802154) + return NM_LINK_TYPE_WPAN; + else if (arptype == ARPHRD_6LOWPAN) + return NM_LINK_TYPE_6LOWPAN; + + { + NMPUtilsEthtoolDriverInfo driver_info; + + /* Fallback OVS detection for kernel <= 3.16 */ + if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (nm_streq(driver_info.driver, "openvswitch")) + return NM_LINK_TYPE_OPENVSWITCH; + + if (arptype == 256) { + /* Some s390 CTC-type devices report 256 for the encapsulation type + * for some reason, but we need to call them Ethernet. + */ + if (nm_streq(driver_info.driver, "ctcm")) + return NM_LINK_TYPE_ETHERNET; + } + } + } + + { + nm_auto_close int dirfd = -1; + gs_free char * devtype = NULL; + char ifname_verified[IFNAMSIZ]; + + dirfd = nmp_utils_sysctl_open_netdir(ifindex, ifname, ifname_verified); + if (dirfd >= 0) { + if (faccessat(dirfd, "anycast_mask", F_OK, 0) == 0) + return NM_LINK_TYPE_OLPC_MESH; + + devtype = _linktype_read_devtype(dirfd); + if (devtype) { + link_type = _link_type_from_devtype(devtype); + if (link_type != NM_LINK_TYPE_NONE) { + if (link_type == NM_LINK_TYPE_BNEP && arptype != ARPHRD_ETHER) { + /* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must + * use arptype to distinguish between them. + */ + } else + return link_type; + } + } + + /* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */ + if (nm_wifi_utils_is_wifi(dirfd, ifname_verified)) + return NM_LINK_TYPE_WIFI; + } + + if (arptype == ARPHRD_ETHER) { + /* Misc non-upstream WWAN drivers. rmnet is Qualcomm's proprietary + * modem interface, ccmni is MediaTek's. FIXME: these drivers should + * really set devtype=WWAN. + */ + if (g_str_has_prefix(ifname, "rmnet") || g_str_has_prefix(ifname, "rev_rmnet") + || g_str_has_prefix(ifname, "ccmni")) + return NM_LINK_TYPE_WWAN_NET; + + /* Standard wired ethernet interfaces don't report an rtnl_link_type, so + * only allow fallback to Ethernet if no type is given. This should + * prevent future virtual network drivers from being treated as Ethernet + * when they should be Generic instead. + */ + if (!kind && !devtype) + return NM_LINK_TYPE_ETHERNET; + + /* The USB gadget interfaces behave and look like ordinary ethernet devices + * aside from the DEVTYPE. */ + if (nm_streq0(devtype, "gadget")) + return NM_LINK_TYPE_ETHERNET; + + /* Distributed Switch Architecture switch chips */ + if (nm_streq0(devtype, "dsa")) + return NM_LINK_TYPE_ETHERNET; + } + } + + return NM_LINK_TYPE_UNKNOWN; +} + +/****************************************************************** + * libnl unility functions and wrappers + ******************************************************************/ + +#define NLMSG_TAIL(nmsg) ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/* copied from iproute2's addattr_l(). */ +static gboolean +_nl_addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) + return FALSE; + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return TRUE; +} + +/****************************************************************** + * NMPObject/netlink functions + ******************************************************************/ + +#define _check_addr_or_return_val(tb, attr, addr_len, ret_val) \ + ({ \ + const struct nlattr *__t = (tb)[(attr)]; \ + \ + if (__t) { \ + if (nla_len(__t) != (addr_len)) { \ + return ret_val; \ + } \ + } \ + !!__t; \ + }) + +#define _check_addr_or_return_null(tb, attr, addr_len) \ + _check_addr_or_return_val(tb, attr, addr_len, NULL) + +/*****************************************************************************/ + +/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */ +static gboolean +_parse_af_inet6(NMPlatform * platform, + struct nlattr * attr, + NMUtilsIPv6IfaceId *out_token, + gboolean * out_token_valid, + guint8 * out_addr_gen_mode_inv, + gboolean * out_addr_gen_mode_valid) +{ + static const struct nla_policy policy[] = { + [IFLA_INET6_FLAGS] = {.type = NLA_U32}, + [IFLA_INET6_CACHEINFO] = {.minlen = nm_offsetofend(struct ifla_cacheinfo, retrans_time)}, + [IFLA_INET6_CONF] = {.minlen = 4}, + [IFLA_INET6_STATS] = {.minlen = 8}, + [IFLA_INET6_ICMP6STATS] = {.minlen = 8}, + [IFLA_INET6_TOKEN] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_INET6_ADDR_GEN_MODE] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + struct in6_addr i6_token; + gboolean token_valid = FALSE; + gboolean addr_gen_mode_valid = FALSE; + guint8 i6_addr_gen_mode_inv = 0; + + if (nla_parse_nested_arr(tb, attr, policy) < 0) + return FALSE; + + if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4) + return FALSE; + if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8) + return FALSE; + if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8) + return FALSE; + + if (_check_addr_or_return_val(tb, IFLA_INET6_TOKEN, sizeof(struct in6_addr), FALSE)) { + nla_memcpy(&i6_token, tb[IFLA_INET6_TOKEN], sizeof(struct in6_addr)); + token_valid = TRUE; + } + + /* Hack to detect support addrgenmode of the kernel. We only parse + * netlink messages that we receive from kernel, hence this check + * is valid. */ + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + /* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL, + tb[IFLA_INET6_ADDR_GEN_MODE] ? 1 : -1); + } + + if (tb[IFLA_INET6_ADDR_GEN_MODE]) { + i6_addr_gen_mode_inv = _nm_platform_uint8_inv(nla_get_u8(tb[IFLA_INET6_ADDR_GEN_MODE])); + if (i6_addr_gen_mode_inv == 0) { + /* an inverse addrgenmode of zero is unexpected. We need to reserve zero + * to signal "unset". */ + return FALSE; + } + addr_gen_mode_valid = TRUE; + } + + if (token_valid) { + *out_token_valid = token_valid; + nm_utils_ipv6_interface_identifier_get_from_addr(out_token, &i6_token); + } + if (addr_gen_mode_valid) { + *out_addr_gen_mode_valid = addr_gen_mode_valid; + *out_addr_gen_mode_inv = i6_addr_gen_mode_inv; + } + return TRUE; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_bridge(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_BR_FORWARD_DELAY] = {.type = NLA_U32}, + [IFLA_BR_HELLO_TIME] = {.type = NLA_U32}, + [IFLA_BR_MAX_AGE] = {.type = NLA_U32}, + [IFLA_BR_AGEING_TIME] = {.type = NLA_U32}, + [IFLA_BR_STP_STATE] = {.type = NLA_U32}, + [IFLA_BR_PRIORITY] = {.type = NLA_U16}, + [IFLA_BR_VLAN_PROTOCOL] = {.type = NLA_U16}, + [IFLA_BR_VLAN_STATS_ENABLED] = {.type = NLA_U8}, + [IFLA_BR_GROUP_FWD_MASK] = {.type = NLA_U16}, + [IFLA_BR_GROUP_ADDR] = {.minlen = sizeof(NMEtherAddr)}, + [IFLA_BR_MCAST_SNOOPING] = {.type = NLA_U8}, + [IFLA_BR_MCAST_ROUTER] = {.type = NLA_U8}, + [IFLA_BR_MCAST_QUERY_USE_IFADDR] = {.type = NLA_U8}, + [IFLA_BR_MCAST_QUERIER] = {.type = NLA_U8}, + [IFLA_BR_MCAST_HASH_MAX] = {.type = NLA_U32}, + [IFLA_BR_MCAST_LAST_MEMBER_CNT] = {.type = NLA_U32}, + [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = {.type = NLA_U32}, + [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERIER_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERY_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = {.type = NLA_U64}, + }; + NMPlatformLnkBridge *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "bridge")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_BRIDGE, NULL); + + props = &obj->lnk_bridge; + *props = nm_platform_lnk_bridge_default; + + if (!_nm_platform_kernel_support_detected( + NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED)) { + /* IFLA_BR_VLAN_STATS_ENABLED was added in kernel 4.10 on April 30, 2016. + * See commit 6dada9b10a0818ba72c249526a742c8c41274a73. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED, + tb[IFLA_BR_VLAN_STATS_ENABLED] ? 1 : -1); + } + + if (tb[IFLA_BR_FORWARD_DELAY]) + props->forward_delay = nla_get_u32(tb[IFLA_BR_FORWARD_DELAY]); + if (tb[IFLA_BR_HELLO_TIME]) + props->hello_time = nla_get_u32(tb[IFLA_BR_HELLO_TIME]); + if (tb[IFLA_BR_MAX_AGE]) + props->max_age = nla_get_u32(tb[IFLA_BR_MAX_AGE]); + if (tb[IFLA_BR_AGEING_TIME]) + props->ageing_time = nla_get_u32(tb[IFLA_BR_AGEING_TIME]); + if (tb[IFLA_BR_STP_STATE]) + props->stp_state = !!nla_get_u32(tb[IFLA_BR_STP_STATE]); + if (tb[IFLA_BR_PRIORITY]) + props->priority = nla_get_u16(tb[IFLA_BR_PRIORITY]); + if (tb[IFLA_BR_VLAN_PROTOCOL]) + props->vlan_protocol = ntohs(nla_get_u16(tb[IFLA_BR_VLAN_PROTOCOL])); + if (tb[IFLA_BR_VLAN_STATS_ENABLED]) + props->vlan_stats_enabled = nla_get_u8(tb[IFLA_BR_VLAN_STATS_ENABLED]); + if (tb[IFLA_BR_GROUP_FWD_MASK]) + props->group_fwd_mask = nla_get_u16(tb[IFLA_BR_GROUP_FWD_MASK]); + if (tb[IFLA_BR_GROUP_ADDR]) + props->group_addr = *nla_data_as(NMEtherAddr, tb[IFLA_BR_GROUP_ADDR]); + if (tb[IFLA_BR_MCAST_SNOOPING]) + props->mcast_snooping = !!nla_get_u8(tb[IFLA_BR_MCAST_SNOOPING]); + if (tb[IFLA_BR_MCAST_ROUTER]) + props->mcast_router = nla_get_u8(tb[IFLA_BR_MCAST_ROUTER]); + if (tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]) + props->mcast_query_use_ifaddr = !!nla_get_u8(tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]); + if (tb[IFLA_BR_MCAST_QUERIER]) + props->mcast_querier = nla_get_u8(tb[IFLA_BR_MCAST_QUERIER]); + if (tb[IFLA_BR_MCAST_HASH_MAX]) + props->mcast_hash_max = nla_get_u32(tb[IFLA_BR_MCAST_HASH_MAX]); + if (tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]) + props->mcast_last_member_count = nla_get_u32(tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]); + if (tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]) + props->mcast_startup_query_count = nla_get_u32(tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]); + if (tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]) + props->mcast_last_member_interval = nla_get_u64(tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]); + if (tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]) + props->mcast_membership_interval = nla_get_u64(tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]); + if (tb[IFLA_BR_MCAST_QUERIER_INTVL]) + props->mcast_querier_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERIER_INTVL]); + if (tb[IFLA_BR_MCAST_QUERY_INTVL]) + props->mcast_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_INTVL]); + if (tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]) + props->mcast_query_response_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]); + if (tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]) + props->mcast_startup_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]); + + return obj; +} + +/***********************************************************************************/ + +static NMPObject * +_parse_lnk_gre(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_GRE_LINK] = {.type = NLA_U32}, + [IFLA_GRE_IFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_OFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_IKEY] = {.type = NLA_U32}, + [IFLA_GRE_OKEY] = {.type = NLA_U32}, + [IFLA_GRE_LOCAL] = {.type = NLA_U32}, + [IFLA_GRE_REMOTE] = {.type = NLA_U32}, + [IFLA_GRE_TTL] = {.type = NLA_U8}, + [IFLA_GRE_TOS] = {.type = NLA_U8}, + [IFLA_GRE_PMTUDISC] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkGre *props; + gboolean is_tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "gretap")) + is_tap = TRUE; + else if (nm_streq(kind, "gre")) + is_tap = FALSE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL); + props = &obj->lnk_gre; + + props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32(tb[IFLA_GRE_LINK]) : 0; + props->input_flags = tb[IFLA_GRE_IFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS])) : 0; + props->output_flags = tb[IFLA_GRE_OFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS])) : 0; + props->input_key = tb[IFLA_GRE_IKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_IKEY])) : 0; + props->output_key = tb[IFLA_GRE_OKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_OKEY])) : 0; + props->local = tb[IFLA_GRE_LOCAL] ? nla_get_u32(tb[IFLA_GRE_LOCAL]) : 0; + props->remote = tb[IFLA_GRE_REMOTE] ? nla_get_u32(tb[IFLA_GRE_REMOTE]) : 0; + props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8(tb[IFLA_GRE_TOS]) : 0; + props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8(tb[IFLA_GRE_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8(tb[IFLA_GRE_PMTUDISC]); + props->is_tap = is_tap; + + return obj; +} + +/*****************************************************************************/ + +/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers + * we're building against might not have those properties even though the + * running kernel might. + */ +#define IFLA_IPOIB_UNSPEC 0 +#define IFLA_IPOIB_PKEY 1 +#define IFLA_IPOIB_MODE 2 +#define IFLA_IPOIB_UMCAST 3 +#undef IFLA_IPOIB_MAX +#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST + +#define IPOIB_MODE_DATAGRAM 0 /* using unreliable datagram QPs */ +#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */ + +static NMPObject * +_parse_lnk_infiniband(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPOIB_PKEY] = {.type = NLA_U16}, + [IFLA_IPOIB_MODE] = {.type = NLA_U16}, + [IFLA_IPOIB_UMCAST] = {.type = NLA_U16}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPlatformLnkInfiniband *info; + NMPObject * obj; + const char * mode; + + if (!info_data || !nm_streq0(kind, "ipoib")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE]) + return NULL; + + switch (nla_get_u16(tb[IFLA_IPOIB_MODE])) { + case IPOIB_MODE_DATAGRAM: + mode = "datagram"; + break; + case IPOIB_MODE_CONNECTED: + mode = "connected"; + break; + default: + return NULL; + } + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL); + info = &obj->lnk_infiniband; + + info->p_key = nla_get_u16(tb[IFLA_IPOIB_PKEY]); + info->mode = mode; + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_ip6tnl(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_IPTUN_REMOTE] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_ENCAP_LIMIT] = {.type = NLA_U8}, + [IFLA_IPTUN_FLOWINFO] = {.type = NLA_U32}, + [IFLA_IPTUN_PROTO] = {.type = NLA_U8}, + [IFLA_IPTUN_FLAGS] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIp6Tnl *props; + guint32 flowinfo; + + if (!info_data || !nm_streq0(kind, "ip6tnl")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_IP6TNL, NULL); + props = &obj->lnk_ip6tnl; + + if (tb[IFLA_IPTUN_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_IPTUN_LINK]); + if (tb[IFLA_IPTUN_LOCAL]) + props->local = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_LOCAL]); + if (tb[IFLA_IPTUN_REMOTE]) + props->remote = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_REMOTE]); + if (tb[IFLA_IPTUN_TTL]) + props->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]); + if (tb[IFLA_IPTUN_ENCAP_LIMIT]) + props->encap_limit = nla_get_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]); + if (tb[IFLA_IPTUN_FLOWINFO]) { + flowinfo = ntohl(nla_get_u32(tb[IFLA_IPTUN_FLOWINFO])); + props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK; + props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT; + } + if (tb[IFLA_IPTUN_PROTO]) + props->proto = nla_get_u8(tb[IFLA_IPTUN_PROTO]); + if (tb[IFLA_IPTUN_FLAGS]) + props->flags = nla_get_u32(tb[IFLA_IPTUN_FLAGS]); + + return obj; +} + +static NMPObject * +_parse_lnk_ip6gre(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_GRE_LINK] = {.type = NLA_U32}, + [IFLA_GRE_IFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_OFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_IKEY] = {.type = NLA_U32}, + [IFLA_GRE_OKEY] = {.type = NLA_U32}, + [IFLA_GRE_LOCAL] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_GRE_REMOTE] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_GRE_TTL] = {.type = NLA_U8}, + [IFLA_GRE_ENCAP_LIMIT] = {.type = NLA_U8}, + [IFLA_GRE_FLOWINFO] = {.type = NLA_U32}, + [IFLA_GRE_FLAGS] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIp6Tnl *props; + guint32 flowinfo; + gboolean is_tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "ip6gre")) + is_tap = FALSE; + else if (nm_streq(kind, "ip6gretap")) + is_tap = TRUE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL); + props = &obj->lnk_ip6tnl; + props->is_gre = TRUE; + props->is_tap = is_tap; + + if (tb[IFLA_GRE_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_GRE_LINK]); + if (tb[IFLA_GRE_IFLAGS]) + props->input_flags = ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS])); + if (tb[IFLA_GRE_OFLAGS]) + props->output_flags = ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS])); + if (tb[IFLA_GRE_IKEY]) + props->input_key = ntohl(nla_get_u32(tb[IFLA_GRE_IKEY])); + if (tb[IFLA_GRE_OKEY]) + props->output_key = ntohl(nla_get_u32(tb[IFLA_GRE_OKEY])); + if (tb[IFLA_GRE_LOCAL]) + props->local = *nla_data_as(struct in6_addr, tb[IFLA_GRE_LOCAL]); + if (tb[IFLA_GRE_REMOTE]) + props->remote = *nla_data_as(struct in6_addr, tb[IFLA_GRE_REMOTE]); + if (tb[IFLA_GRE_TTL]) + props->ttl = nla_get_u8(tb[IFLA_GRE_TTL]); + if (tb[IFLA_GRE_ENCAP_LIMIT]) + props->encap_limit = nla_get_u8(tb[IFLA_GRE_ENCAP_LIMIT]); + if (tb[IFLA_GRE_FLOWINFO]) { + flowinfo = ntohl(nla_get_u32(tb[IFLA_GRE_FLOWINFO])); + props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK; + props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT; + } + if (tb[IFLA_GRE_FLAGS]) + props->flags = nla_get_u32(tb[IFLA_GRE_FLAGS]); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_ipip(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.type = NLA_U32}, + [IFLA_IPTUN_REMOTE] = {.type = NLA_U32}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_TOS] = {.type = NLA_U8}, + [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIpIp *props; + + if (!info_data || !nm_streq0(kind, "ipip")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_IPIP, NULL); + props = &obj->lnk_ipip; + + props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0; + props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0; + props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0; + props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0; + props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_macvlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_MACVLAN_MODE] = {.type = NLA_U32}, + [IFLA_MACVLAN_FLAGS] = {.type = NLA_U16}, + }; + NMPlatformLnkMacvlan *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + gboolean tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "macvlan")) + tap = FALSE; + else if (nm_streq(kind, "macvtap")) + tap = TRUE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_MACVLAN_MODE]) + return NULL; + + obj = nmp_object_new(tap ? NMP_OBJECT_TYPE_LNK_MACVTAP : NMP_OBJECT_TYPE_LNK_MACVLAN, NULL); + props = &obj->lnk_macvlan; + props->mode = nla_get_u32(tb[IFLA_MACVLAN_MODE]); + props->tap = tap; + + if (tb[IFLA_MACVLAN_FLAGS]) + props->no_promisc = + NM_FLAGS_HAS(nla_get_u16(tb[IFLA_MACVLAN_FLAGS]), MACVLAN_FLAG_NOPROMISC); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_macsec(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_MACSEC_SCI] = {.type = NLA_U64}, + [IFLA_MACSEC_ICV_LEN] = {.type = NLA_U8}, + [IFLA_MACSEC_CIPHER_SUITE] = {.type = NLA_U64}, + [IFLA_MACSEC_WINDOW] = {.type = NLA_U32}, + [IFLA_MACSEC_ENCODING_SA] = {.type = NLA_U8}, + [IFLA_MACSEC_ENCRYPT] = {.type = NLA_U8}, + [IFLA_MACSEC_PROTECT] = {.type = NLA_U8}, + [IFLA_MACSEC_INC_SCI] = {.type = NLA_U8}, + [IFLA_MACSEC_ES] = {.type = NLA_U8}, + [IFLA_MACSEC_SCB] = {.type = NLA_U8}, + [IFLA_MACSEC_REPLAY_PROTECT] = {.type = NLA_U8}, + [IFLA_MACSEC_VALIDATION] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkMacsec *props; + + if (!info_data || !nm_streq0(kind, "macsec")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_MACSEC, NULL); + props = &obj->lnk_macsec; + + if (tb[IFLA_MACSEC_SCI]) { + props->sci = nla_get_be64(tb[IFLA_MACSEC_SCI]); + } + if (tb[IFLA_MACSEC_ICV_LEN]) { + props->icv_length = nla_get_u8(tb[IFLA_MACSEC_ICV_LEN]); + } + if (tb[IFLA_MACSEC_CIPHER_SUITE]) { + props->cipher_suite = nla_get_u64(tb[IFLA_MACSEC_CIPHER_SUITE]); + } + if (tb[IFLA_MACSEC_WINDOW]) { + props->window = nla_get_u32(tb[IFLA_MACSEC_WINDOW]); + } + if (tb[IFLA_MACSEC_ENCODING_SA]) { + props->encoding_sa = !!nla_get_u8(tb[IFLA_MACSEC_ENCODING_SA]); + } + if (tb[IFLA_MACSEC_ENCRYPT]) { + props->encrypt = !!nla_get_u8(tb[IFLA_MACSEC_ENCRYPT]); + } + if (tb[IFLA_MACSEC_PROTECT]) { + props->protect = !!nla_get_u8(tb[IFLA_MACSEC_PROTECT]); + } + if (tb[IFLA_MACSEC_INC_SCI]) { + props->include_sci = !!nla_get_u8(tb[IFLA_MACSEC_INC_SCI]); + } + if (tb[IFLA_MACSEC_ES]) { + props->es = !!nla_get_u8(tb[IFLA_MACSEC_ES]); + } + if (tb[IFLA_MACSEC_SCB]) { + props->scb = !!nla_get_u8(tb[IFLA_MACSEC_SCB]); + } + if (tb[IFLA_MACSEC_REPLAY_PROTECT]) { + props->replay_protect = !!nla_get_u8(tb[IFLA_MACSEC_REPLAY_PROTECT]); + } + if (tb[IFLA_MACSEC_VALIDATION]) { + props->validation = nla_get_u8(tb[IFLA_MACSEC_VALIDATION]); + } + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_sit(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.type = NLA_U32}, + [IFLA_IPTUN_REMOTE] = {.type = NLA_U32}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_TOS] = {.type = NLA_U8}, + [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8}, + [IFLA_IPTUN_FLAGS] = {.type = NLA_U16}, + [IFLA_IPTUN_PROTO] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkSit *props; + + if (!info_data || !nm_streq0(kind, "sit")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_SIT, NULL); + props = &obj->lnk_sit; + + props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0; + props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0; + props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0; + props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0; + props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]); + props->flags = tb[IFLA_IPTUN_FLAGS] ? nla_get_u16(tb[IFLA_IPTUN_FLAGS]) : 0; + props->proto = tb[IFLA_IPTUN_PROTO] ? nla_get_u8(tb[IFLA_IPTUN_PROTO]) : 0; + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_tun(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_TUN_OWNER] = {.type = NLA_U32}, + [IFLA_TUN_GROUP] = {.type = NLA_U32}, + [IFLA_TUN_TYPE] = {.type = NLA_U8}, + [IFLA_TUN_PI] = {.type = NLA_U8}, + [IFLA_TUN_VNET_HDR] = {.type = NLA_U8}, + [IFLA_TUN_PERSIST] = {.type = NLA_U8}, + [IFLA_TUN_MULTI_QUEUE] = {.type = NLA_U8}, + [IFLA_TUN_NUM_QUEUES] = {.type = NLA_U32}, + [IFLA_TUN_NUM_DISABLED_QUEUES] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkTun *props; + + if (!info_data || !nm_streq0(kind, "tun")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_TUN_TYPE]) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_TUN, NULL); + props = &obj->lnk_tun; + + props->type = nla_get_u8(tb[IFLA_TUN_TYPE]); + + props->pi = !!nla_get_u8_cond(tb, IFLA_TUN_PI, FALSE); + props->vnet_hdr = !!nla_get_u8_cond(tb, IFLA_TUN_VNET_HDR, FALSE); + props->multi_queue = !!nla_get_u8_cond(tb, IFLA_TUN_MULTI_QUEUE, FALSE); + props->persist = !!nla_get_u8_cond(tb, IFLA_TUN_PERSIST, FALSE); + + if (tb[IFLA_TUN_OWNER]) { + props->owner_valid = TRUE; + props->owner = nla_get_u32(tb[IFLA_TUN_OWNER]); + } + if (tb[IFLA_TUN_GROUP]) { + props->group_valid = TRUE; + props->group = nla_get_u32(tb[IFLA_TUN_GROUP]); + } + return obj; +} + +/*****************************************************************************/ + +static gboolean +_vlan_qos_mapping_from_nla(struct nlattr * nlattr, + const NMVlanQosMapping **out_map, + guint * out_n_map) +{ + struct nlattr * nla; + int remaining; + gs_unref_ptrarray GPtrArray *array = NULL; + + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == sizeof(struct ifla_vlan_qos_mapping)); + G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->to) + == sizeof(((struct ifla_vlan_qos_mapping *) 0)->to)); + G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->from) + == sizeof(((struct ifla_vlan_qos_mapping *) 0)->from)); + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) + == sizeof(((NMVlanQosMapping *) 0)->from) + + sizeof(((NMVlanQosMapping *) 0)->to)); + + nm_assert(out_map && !*out_map); + nm_assert(out_n_map && !*out_n_map); + + if (!nlattr) + return TRUE; + + array = g_ptr_array_new(); + nla_for_each_nested (nla, nlattr, remaining) { + if (nla_len(nla) < sizeof(NMVlanQosMapping)) + return FALSE; + g_ptr_array_add(array, nla_data(nla)); + } + + if (array->len > 0) { + NMVlanQosMapping *list; + guint i, j; + + /* The sorting is necessary, because for egress mapping, kernel + * doesn't sent the items strictly sorted by the from field. */ + g_ptr_array_sort_with_data(array, _vlan_qos_mapping_cmp_from_ptr, NULL); + + list = g_new(NMVlanQosMapping, array->len); + + for (i = 0, j = 0; i < array->len; i++) { + NMVlanQosMapping *map; + + map = array->pdata[i]; + + /* kernel doesn't really send us duplicates. Just be extra cautious + * because we want strong guarantees about the sort order and uniqueness + * of our mapping list (for simpler equality comparison). */ + if (j > 0 && list[j - 1].from == map->from) + list[j - 1] = *map; + else + list[j++] = *map; + } + + *out_n_map = j; + *out_map = list; + } + + return TRUE; +} + +/* Copied and heavily modified from libnl3's vlan_parse() */ +static NMPObject * +_parse_lnk_vlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VLAN_ID] = {.type = NLA_U16}, + [IFLA_VLAN_FLAGS] = {.minlen = nm_offsetofend(struct ifla_vlan_flags, flags)}, + [IFLA_VLAN_INGRESS_QOS] = {.type = NLA_NESTED}, + [IFLA_VLAN_EGRESS_QOS] = {.type = NLA_NESTED}, + [IFLA_VLAN_PROTOCOL] = {.type = NLA_U16}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject * obj_result; + + if (!info_data || !nm_streq0(kind, "vlan")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_VLAN_ID]) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VLAN, NULL); + obj->lnk_vlan.id = nla_get_u16(tb[IFLA_VLAN_ID]); + + if (tb[IFLA_VLAN_FLAGS]) { + struct ifla_vlan_flags flags; + + nla_memcpy(&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags)); + + obj->lnk_vlan.flags = flags.flags; + } + + if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_INGRESS_QOS], + &obj->_lnk_vlan.ingress_qos_map, + &obj->_lnk_vlan.n_ingress_qos_map)) + return NULL; + + if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_EGRESS_QOS], + &obj->_lnk_vlan.egress_qos_map, + &obj->_lnk_vlan.n_egress_qos_map)) + return NULL; + + obj_result = obj; + obj = NULL; + return obj_result; +} + +/*****************************************************************************/ + +/* The installed kernel headers might not have VXLAN stuff at all, or + * they might have the original properties, but not PORT, GROUP6, or LOCAL6. + * So until we depend on kernel >= 3.11, we just ignore the actual enum + * in if_link.h and define the values ourselves. + */ +#define IFLA_VXLAN_UNSPEC 0 +#define IFLA_VXLAN_ID 1 +#define IFLA_VXLAN_GROUP 2 +#define IFLA_VXLAN_LINK 3 +#define IFLA_VXLAN_LOCAL 4 +#define IFLA_VXLAN_TTL 5 +#define IFLA_VXLAN_TOS 6 +#define IFLA_VXLAN_LEARNING 7 +#define IFLA_VXLAN_AGEING 8 +#define IFLA_VXLAN_LIMIT 9 +#define IFLA_VXLAN_PORT_RANGE 10 +#define IFLA_VXLAN_PROXY 11 +#define IFLA_VXLAN_RSC 12 +#define IFLA_VXLAN_L2MISS 13 +#define IFLA_VXLAN_L3MISS 14 +#define IFLA_VXLAN_PORT 15 +#define IFLA_VXLAN_GROUP6 16 +#define IFLA_VXLAN_LOCAL6 17 +#undef IFLA_VXLAN_MAX +#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6 + +#define IFLA_VRF_TABLE 1 + +/* older kernel header might not contain 'struct ifla_vxlan_port_range'. + * Redefine it. */ +struct nm_ifla_vxlan_port_range { + guint16 low; + guint16 high; +}; + +static NMPObject * +_parse_lnk_vxlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VXLAN_ID] = {.type = NLA_U32}, + [IFLA_VXLAN_GROUP] = {.type = NLA_U32}, + [IFLA_VXLAN_GROUP6] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_VXLAN_LINK] = {.type = NLA_U32}, + [IFLA_VXLAN_LOCAL] = {.type = NLA_U32}, + [IFLA_VXLAN_LOCAL6] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_VXLAN_TOS] = {.type = NLA_U8}, + [IFLA_VXLAN_TTL] = {.type = NLA_U8}, + [IFLA_VXLAN_LEARNING] = {.type = NLA_U8}, + [IFLA_VXLAN_AGEING] = {.type = NLA_U32}, + [IFLA_VXLAN_LIMIT] = {.type = NLA_U32}, + [IFLA_VXLAN_PORT_RANGE] = {.type = NLA_UNSPEC, + .minlen = sizeof(struct nm_ifla_vxlan_port_range)}, + [IFLA_VXLAN_PROXY] = {.type = NLA_U8}, + [IFLA_VXLAN_RSC] = {.type = NLA_U8}, + [IFLA_VXLAN_L2MISS] = {.type = NLA_U8}, + [IFLA_VXLAN_L3MISS] = {.type = NLA_U8}, + [IFLA_VXLAN_PORT] = {.type = NLA_U16}, + }; + NMPlatformLnkVxlan *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "vxlan")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VXLAN, NULL); + + props = &obj->lnk_vxlan; + + if (tb[IFLA_VXLAN_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_VXLAN_LINK]); + if (tb[IFLA_VXLAN_ID]) + props->id = nla_get_u32(tb[IFLA_VXLAN_ID]); + if (tb[IFLA_VXLAN_GROUP]) + props->group = nla_get_u32(tb[IFLA_VXLAN_GROUP]); + if (tb[IFLA_VXLAN_LOCAL]) + props->local = nla_get_u32(tb[IFLA_VXLAN_LOCAL]); + if (tb[IFLA_VXLAN_LOCAL6]) + props->local6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_LOCAL6]); + if (tb[IFLA_VXLAN_GROUP6]) + props->group6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_GROUP6]); + + if (tb[IFLA_VXLAN_AGEING]) + props->ageing = nla_get_u32(tb[IFLA_VXLAN_AGEING]); + if (tb[IFLA_VXLAN_LIMIT]) + props->limit = nla_get_u32(tb[IFLA_VXLAN_LIMIT]); + if (tb[IFLA_VXLAN_TOS]) + props->tos = nla_get_u8(tb[IFLA_VXLAN_TOS]); + if (tb[IFLA_VXLAN_TTL]) + props->ttl = nla_get_u8(tb[IFLA_VXLAN_TTL]); + + if (tb[IFLA_VXLAN_PORT]) + props->dst_port = ntohs(nla_get_u16(tb[IFLA_VXLAN_PORT])); + + if (tb[IFLA_VXLAN_PORT_RANGE]) { + struct nm_ifla_vxlan_port_range *range; + + range = nla_data_as(struct nm_ifla_vxlan_port_range, tb[IFLA_VXLAN_PORT_RANGE]); + props->src_port_min = ntohs(range->low); + props->src_port_max = ntohs(range->high); + } + + if (tb[IFLA_VXLAN_LEARNING]) + props->learning = !!nla_get_u8(tb[IFLA_VXLAN_LEARNING]); + if (tb[IFLA_VXLAN_PROXY]) + props->proxy = !!nla_get_u8(tb[IFLA_VXLAN_PROXY]); + if (tb[IFLA_VXLAN_RSC]) + props->rsc = !!nla_get_u8(tb[IFLA_VXLAN_RSC]); + if (tb[IFLA_VXLAN_L2MISS]) + props->l2miss = !!nla_get_u8(tb[IFLA_VXLAN_L2MISS]); + if (tb[IFLA_VXLAN_L3MISS]) + props->l3miss = !!nla_get_u8(tb[IFLA_VXLAN_L3MISS]); + + return obj; +} + +static NMPObject * +_parse_lnk_vrf(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VRF_TABLE] = {.type = NLA_U32}, + }; + NMPlatformLnkVrf *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "vrf")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VRF, NULL); + + props = &obj->lnk_vrf; + + if (tb[IFLA_VRF_TABLE]) + props->table = nla_get_u32(tb[IFLA_VRF_TABLE]); + + return obj; +} + +/*****************************************************************************/ + +static gboolean +_wireguard_update_from_allowed_ips_nla(NMPWireGuardAllowedIP *allowed_ip, struct nlattr *nlattr) +{ + static const struct nla_policy policy[] = { + [WGALLOWEDIP_A_FAMILY] = {.type = NLA_U16}, + [WGALLOWEDIP_A_IPADDR] = {.minlen = sizeof(struct in_addr)}, + [WGALLOWEDIP_A_CIDR_MASK] = {.type = NLA_U8}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + int family; + int addr_len; + + if (nla_parse_nested_arr(tb, nlattr, policy) < 0) + return FALSE; + + if (!tb[WGALLOWEDIP_A_FAMILY]) + return FALSE; + + family = nla_get_u16(tb[WGALLOWEDIP_A_FAMILY]); + if (family == AF_INET) + addr_len = sizeof(in_addr_t); + else if (family == AF_INET6) + addr_len = sizeof(struct in6_addr); + else + return FALSE; + + _check_addr_or_return_val(tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE); + + *allowed_ip = (NMPWireGuardAllowedIP){ + .family = family, + }; + + nm_assert((int) allowed_ip->family == family); + + if (tb[WGALLOWEDIP_A_IPADDR]) + nla_memcpy(&allowed_ip->addr, tb[WGALLOWEDIP_A_IPADDR], addr_len); + if (tb[WGALLOWEDIP_A_CIDR_MASK]) + allowed_ip->mask = nla_get_u8(tb[WGALLOWEDIP_A_CIDR_MASK]); + + return TRUE; +} + +typedef struct { + CList lst; + NMPWireGuardPeer data; +} WireGuardPeerConstruct; + +static gboolean +_wireguard_update_from_peers_nla(CList *peers, GArray **p_allowed_ips, struct nlattr *peer_attr) +{ + static const struct nla_policy policy[] = { + [WGPEER_A_PUBLIC_KEY] = {.minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN}, + [WGPEER_A_PRESHARED_KEY] = {}, + [WGPEER_A_FLAGS] = {.type = NLA_U32}, + [WGPEER_A_ENDPOINT] = {}, + [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = {.type = NLA_U16}, + [WGPEER_A_LAST_HANDSHAKE_TIME] = {}, + [WGPEER_A_RX_BYTES] = {.type = NLA_U64}, + [WGPEER_A_TX_BYTES] = {.type = NLA_U64}, + [WGPEER_A_ALLOWEDIPS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + WireGuardPeerConstruct *peer_c; + + if (nla_parse_nested_arr(tb, peer_attr, policy) < 0) + return FALSE; + + if (!tb[WGPEER_A_PUBLIC_KEY]) + return FALSE; + + /* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */ + peer_c = c_list_last_entry(peers, WireGuardPeerConstruct, lst); + if (peer_c + && !memcmp(nla_data(tb[WGPEER_A_PUBLIC_KEY]), + peer_c->data.public_key, + NMP_WIREGUARD_PUBLIC_KEY_LEN)) { + G_STATIC_ASSERT_EXPR(NMP_WIREGUARD_PUBLIC_KEY_LEN == sizeof(peer_c->data.public_key)); + /* this message is a continuation of the previous peer. + * Only parse WGPEER_A_ALLOWEDIPS below. */ + } else { + /* otherwise, start a new peer */ + peer_c = g_slice_new0(WireGuardPeerConstruct); + c_list_link_tail(peers, &peer_c->lst); + + nla_memcpy(&peer_c->data.public_key, + tb[WGPEER_A_PUBLIC_KEY], + sizeof(peer_c->data.public_key)); + + if (tb[WGPEER_A_PRESHARED_KEY]) { + nla_memcpy(&peer_c->data.preshared_key, + tb[WGPEER_A_PRESHARED_KEY], + sizeof(peer_c->data.preshared_key)); + /* FIXME(netlink-bzero-secret) */ + nm_explicit_bzero(nla_data(tb[WGPEER_A_PRESHARED_KEY]), + nla_len(tb[WGPEER_A_PRESHARED_KEY])); + } + + nm_sock_addr_union_cpy_untrusted( + &peer_c->data.endpoint, + tb[WGPEER_A_ENDPOINT] ? nla_data(tb[WGPEER_A_ENDPOINT]) : NULL, + tb[WGPEER_A_ENDPOINT] ? nla_len(tb[WGPEER_A_ENDPOINT]) : 0); + + if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]) + peer_c->data.persistent_keepalive_interval = + nla_get_u16(tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]); + if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) { + if (nla_len(tb[WGPEER_A_LAST_HANDSHAKE_TIME]) + >= sizeof(peer_c->data.last_handshake_time)) + nla_memcpy(&peer_c->data.last_handshake_time, + tb[WGPEER_A_LAST_HANDSHAKE_TIME], + sizeof(peer_c->data.last_handshake_time)); + } + if (tb[WGPEER_A_RX_BYTES]) + peer_c->data.rx_bytes = nla_get_u64(tb[WGPEER_A_RX_BYTES]); + if (tb[WGPEER_A_TX_BYTES]) + peer_c->data.tx_bytes = nla_get_u64(tb[WGPEER_A_TX_BYTES]); + } + + if (tb[WGPEER_A_ALLOWEDIPS]) { + struct nlattr *attr; + int rem; + GArray * allowed_ips = *p_allowed_ips; + + nla_for_each_nested (attr, tb[WGPEER_A_ALLOWEDIPS], rem) { + if (!allowed_ips) { + allowed_ips = g_array_new(FALSE, FALSE, sizeof(NMPWireGuardAllowedIP)); + *p_allowed_ips = allowed_ips; + g_array_set_size(allowed_ips, 1); + } else + g_array_set_size(allowed_ips, allowed_ips->len + 1); + + if (!_wireguard_update_from_allowed_ips_nla( + &g_array_index(allowed_ips, NMPWireGuardAllowedIP, allowed_ips->len - 1), + attr)) { + /* we ignore the error of parsing one allowed-ip. */ + g_array_set_size(allowed_ips, allowed_ips->len - 1); + continue; + } + + if (!peer_c->data._construct_idx_end) + peer_c->data._construct_idx_start = allowed_ips->len - 1; + peer_c->data._construct_idx_end = allowed_ips->len; + } + } + + return TRUE; +} + +typedef struct { + const int ifindex; + NMPObject *obj; + CList peers; + GArray * allowed_ips; +} WireGuardParseData; + +static int +_wireguard_get_device_cb(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy policy[] = { + [WGDEVICE_A_IFINDEX] = {.type = NLA_U32}, + [WGDEVICE_A_IFNAME] = {.type = NLA_NUL_STRING, .maxlen = IFNAMSIZ}, + [WGDEVICE_A_PRIVATE_KEY] = {}, + [WGDEVICE_A_PUBLIC_KEY] = {}, + [WGDEVICE_A_FLAGS] = {.type = NLA_U32}, + [WGDEVICE_A_LISTEN_PORT] = {.type = NLA_U16}, + [WGDEVICE_A_FWMARK] = {.type = NLA_U32}, + [WGDEVICE_A_PEERS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + WireGuardParseData *parse_data = arg; + + if (genlmsg_parse_arr(nlmsg_hdr(msg), 0, tb, policy) < 0) + return NL_SKIP; + + if (tb[WGDEVICE_A_IFINDEX]) { + int ifindex; + + ifindex = (int) nla_get_u32(tb[WGDEVICE_A_IFINDEX]); + if (ifindex <= 0 || parse_data->ifindex != ifindex) + return NL_SKIP; + } else { + if (!parse_data->obj) + return NL_SKIP; + } + + if (parse_data->obj) { + /* we already have an object instance. This means the netlink message + * is a continuation, only providing more WGDEVICE_A_PEERS data below. */ + } else { + NMPObject * obj; + NMPlatformLnkWireGuard *props; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL); + props = &obj->lnk_wireguard; + + if (tb[WGDEVICE_A_PRIVATE_KEY]) { + nla_memcpy(props->private_key, tb[WGDEVICE_A_PRIVATE_KEY], sizeof(props->private_key)); + /* FIXME(netlink-bzero-secret): extend netlink library to wipe memory. For now, + * just hack it here (yes, this does not cover all places where the + * private key was copied). */ + nm_explicit_bzero(nla_data(tb[WGDEVICE_A_PRIVATE_KEY]), + nla_len(tb[WGDEVICE_A_PRIVATE_KEY])); + } + if (tb[WGDEVICE_A_PUBLIC_KEY]) + nla_memcpy(props->public_key, tb[WGDEVICE_A_PUBLIC_KEY], sizeof(props->public_key)); + if (tb[WGDEVICE_A_LISTEN_PORT]) + props->listen_port = nla_get_u16(tb[WGDEVICE_A_LISTEN_PORT]); + if (tb[WGDEVICE_A_FWMARK]) + props->fwmark = nla_get_u32(tb[WGDEVICE_A_FWMARK]); + + parse_data->obj = obj; + } + + if (tb[WGDEVICE_A_PEERS]) { + struct nlattr *attr; + int rem; + + nla_for_each_nested (attr, tb[WGDEVICE_A_PEERS], rem) { + if (!_wireguard_update_from_peers_nla(&parse_data->peers, + &parse_data->allowed_ips, + attr)) { + /* we ignore the error of parsing one peer. + * _wireguard_update_from_peers_nla() leaves the @peers array in the + * desired state. */ + } + } + } + + return NL_OK; +} + +static const NMPObject * +_wireguard_read_info(NMPlatform * platform /* used only as logging context */, + struct nl_sock *genl, + int wireguard_family_id, + int ifindex) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + NMPObject * obj = NULL; + WireGuardPeerConstruct * peer_c; + WireGuardPeerConstruct * peer_c_safe; + gs_unref_array GArray *allowed_ips = NULL; + WireGuardParseData parse_data = { + .ifindex = ifindex, + }; + guint i; + + nm_assert(genl); + nm_assert(wireguard_family_id >= 0); + nm_assert(ifindex > 0); + + _LOGT("wireguard: fetching information for ifindex %d (genl-id %d)...", + ifindex, + wireguard_family_id); + + msg = nlmsg_alloc(); + + if (!genlmsg_put(msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + wireguard_family_id, + 0, + NLM_F_DUMP, + WG_CMD_GET_DEVICE, + 1)) + return NULL; + + NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex); + + if (nl_send_auto(genl, msg) < 0) + return NULL; + + c_list_init(&parse_data.peers); + + /* we ignore errors, and return whatever we could successfully + * parse. */ + nl_recvmsgs(genl, + &((const struct nl_cb){ + .valid_cb = _wireguard_get_device_cb, + .valid_arg = (gpointer) &parse_data, + })); + + /* unpack: transfer ownership */ + obj = parse_data.obj; + allowed_ips = parse_data.allowed_ips; + + if (!obj) { + while ((peer_c = c_list_first_entry(&parse_data.peers, WireGuardPeerConstruct, lst))) { + c_list_unlink_stale(&peer_c->lst); + nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key)); + g_slice_free(WireGuardPeerConstruct, peer_c); + } + return NULL; + } + + /* we receive peers/allowed-ips possibly in separate netlink messages. Hence, while + * parsing the dump, we don't know upfront how many peers/allowed-ips we will receive. + * + * We solve that, by collecting all peers with a CList. It's done this way, + * because a GArray would require growing the array, but we want to bzero() + * the preshared-key of each peer while reallocating. The CList apprach avoids + * that. + * + * For allowed-ips, we instead track one GArray, which are all appended + * there. The realloc/resize of the GArray is fine there. However, + * while we build the GArray, we don't yet have the final pointers. + * Hence, while constructing, we track the indexes with peer->_construct_idx_* + * fields. These indexes must be converted to actual pointers blow. + * + * This is all done during parsing. In the final NMPObjectLnkWireGuard we + * don't want the CList anymore and repackage the NMPObject tightly. The + * reason is, that NMPObject instances are immutable and long-living. Spend + * a bit effort below during construction to obtain a most suitable representation + * in this regard. */ + obj->_lnk_wireguard.peers_len = c_list_length(&parse_data.peers); + obj->_lnk_wireguard.peers = obj->_lnk_wireguard.peers_len > 0 + ? g_new(NMPWireGuardPeer, obj->_lnk_wireguard.peers_len) + : NULL; + + /* duplicate allowed_ips instead of using the pointer. The GArray possibly has more + * space allocated then we need, and we want to get rid of this excess buffer. + * Note that NMPObject instance is possibly put into the cache and long-living. */ + obj->_lnk_wireguard._allowed_ips_buf_len = allowed_ips ? allowed_ips->len : 0u; + obj->_lnk_wireguard._allowed_ips_buf = + obj->_lnk_wireguard._allowed_ips_buf_len > 0 + ? (NMPWireGuardAllowedIP *) nm_memdup(allowed_ips->data, + sizeof(NMPWireGuardAllowedIP) * allowed_ips->len) + : NULL; + + i = 0; + c_list_for_each_entry_safe (peer_c, peer_c_safe, &parse_data.peers, lst) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &obj->_lnk_wireguard.peers[i++]; + + *peer = peer_c->data; + + c_list_unlink_stale(&peer_c->lst); + nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key)); + g_slice_free(WireGuardPeerConstruct, peer_c); + + if (peer->_construct_idx_end != 0) { + guint len; + + nm_assert(obj->_lnk_wireguard._allowed_ips_buf); + nm_assert(peer->_construct_idx_end > peer->_construct_idx_start); + nm_assert(peer->_construct_idx_start < obj->_lnk_wireguard._allowed_ips_buf_len); + nm_assert(peer->_construct_idx_end <= obj->_lnk_wireguard._allowed_ips_buf_len); + + len = peer->_construct_idx_end - peer->_construct_idx_start; + peer->allowed_ips = &obj->_lnk_wireguard._allowed_ips_buf[peer->_construct_idx_start]; + peer->allowed_ips_len = len; + } else { + nm_assert(!peer->_construct_idx_start); + nm_assert(!peer->_construct_idx_end); + peer->allowed_ips = NULL; + peer->allowed_ips_len = 0; + } + } + + return obj; + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static int +_wireguard_get_family_id(NMPlatform *platform, int ifindex_try) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int wireguard_family_id = -1; + + if (ifindex_try > 0) { + const NMPlatformLink *plink; + + if (nm_platform_link_get_lnk_wireguard(platform, ifindex_try, &plink)) + wireguard_family_id = NMP_OBJECT_UP_CAST(plink)->_link.wireguard_family_id; + } + if (wireguard_family_id < 0) + wireguard_family_id = genl_ctrl_resolve(priv->genl, "wireguard"); + return wireguard_family_id; +} + +static const NMPObject * +_wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifindex) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *lnk_new = NULL; + NMPCacheOpsType cache_op; + const NMPObject * plink = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + + nm_assert(wireguard_family_id >= 0); + nm_assert(ifindex > 0); + + nm_platform_process_events(platform); + + plink = nm_platform_link_get_obj(platform, ifindex, TRUE); + + if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD) { + nm_platform_link_refresh(platform, ifindex); + plink = nm_platform_link_get_obj(platform, ifindex, TRUE); + if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD) + return NULL; + if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } else { + lnk_new = _wireguard_read_info(platform, priv->genl, wireguard_family_id, ifindex); + if (!lnk_new) { + if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } else if (nmp_object_equal(plink->_link.netlink.lnk, lnk_new)) { + nmp_object_unref(lnk_new); + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } + } + + if (plink->_link.wireguard_family_id == wireguard_family_id + && plink->_link.netlink.lnk == lnk_new) + return plink; + + /* we use nmp_cache_update_netlink() to re-inject the new object into the cache. + * For that, we need to clone it, and tweak it so that it's suitable. It's a bit + * of a hack, in particular that we need to clear driver and udev-device. */ + obj = nmp_object_clone(plink, FALSE); + obj->_link.wireguard_family_id = wireguard_family_id; + nmp_object_unref(obj->_link.netlink.lnk); + obj->_link.netlink.lnk = g_steal_pointer(&lnk_new); + obj->link.driver = NULL; + nm_clear_pointer(&obj->_link.udev.device, udev_device_unref); + + cache_op = + nmp_cache_update_netlink(nm_platform_get_cache(platform), obj, FALSE, &obj_old, &obj_new); + nm_assert(NM_IN_SET(cache_op, NMP_CACHE_OPS_UPDATED)); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + + nm_assert(!obj_new + || (NMP_OBJECT_GET_TYPE(obj_new) == NMP_OBJECT_TYPE_LINK + && obj_new->link.type == NM_LINK_TYPE_WIREGUARD + && (!obj_new->_link.netlink.lnk + || NMP_OBJECT_GET_TYPE(obj_new->_link.netlink.lnk) + == NMP_OBJECT_TYPE_LNK_WIREGUARD))); + return obj_new; +} + +static int +_wireguard_create_change_nlmsgs(NMPlatform * platform, + int ifindex, + int wireguard_family_id, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags, + GPtrArray ** out_msgs) +{ + gs_unref_ptrarray GPtrArray * msgs = NULL; + nm_auto_nlmsg struct nl_msg * msg = NULL; + const guint IDX_NIL = G_MAXUINT; + guint idx_peer_curr; + guint idx_allowed_ips_curr; + struct nlattr * nest_peers; + struct nlattr * nest_curr_peer; + struct nlattr * nest_allowed_ips; + struct nlattr * nest_curr_allowed_ip; + NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT; + +#define _nla_nest_end(msg, nest_start) \ + G_STMT_START \ + { \ + if (nla_nest_end((msg), (nest_start)) < 0) \ + g_return_val_if_reached(-NME_BUG); \ + } \ + G_STMT_END + + /* Adapted from LGPL-2.1+ code [1]. + * + * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */ + + idx_peer_curr = IDX_NIL; + idx_allowed_ips_curr = IDX_NIL; + + /* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS). + * The platform API should be extended to also support partial updates. In particular, configuring the same configuration + * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */ + +again: + + msg = nlmsg_alloc(); + if (!genlmsg_put(msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + wireguard_family_id, + 0, + NLM_F_REQUEST, + WG_CMD_SET_DEVICE, + 1)) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex); + + if (idx_peer_curr == IDX_NIL) { + guint32 flags; + + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY)) + NLA_PUT(msg, + WGDEVICE_A_PRIVATE_KEY, + sizeof(lnk_wireguard->private_key), + lnk_wireguard->private_key); + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT)) + NLA_PUT_U16(msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port); + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK)) + NLA_PUT_U32(msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark); + + flags = 0; + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS)) + flags |= WGDEVICE_F_REPLACE_PEERS; + NLA_PUT_U32(msg, WGDEVICE_A_FLAGS, flags); + } + + if (peers_len == 0) + goto send; + + nest_curr_peer = NULL; + nest_allowed_ips = NULL; + nest_curr_allowed_ip = NULL; + + nest_peers = nla_nest_start(msg, WGDEVICE_A_PEERS); + if (!nest_peers) + g_return_val_if_reached(-NME_BUG); + + if (idx_peer_curr == IDX_NIL) + idx_peer_curr = 0; + for (; idx_peer_curr < peers_len; idx_peer_curr++) { + const NMPWireGuardPeer *p = &peers[idx_peer_curr]; + + if (peer_flags) { + p_flags = peer_flags[idx_peer_curr]; + if (!NM_FLAGS_ANY(p_flags, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)) { + /* no flags set. We take that as indication to skip configuring the peer + * entirely. */ + nm_assert(p_flags == NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE); + continue; + } + } + + nest_curr_peer = nla_nest_start(msg, 0); + if (!nest_curr_peer) + goto toobig_peers; + + if (nla_put(msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME)) { + /* all other p_flags are silently ignored. */ + if (nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME) < 0) + goto toobig_peers; + } else { + if (idx_allowed_ips_curr == IDX_NIL) { + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY) + && nla_put(msg, + WGPEER_A_PRESHARED_KEY, + sizeof(p->preshared_key), + p->preshared_key) + < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL) + && nla_put_uint16(msg, + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + p->persistent_keepalive_interval) + < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS) + && nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT)) { + if (NM_IN_SET(p->endpoint.sa.sa_family, AF_INET, AF_INET6)) { + if (nla_put(msg, + WGPEER_A_ENDPOINT, + p->endpoint.sa.sa_family == AF_INET ? sizeof(p->endpoint.in) + : sizeof(p->endpoint.in6), + &p->endpoint) + < 0) + goto toobig_peers; + } else { + /* I think there is no way to clear an endpoint, though there should be. */ + nm_assert(p->endpoint.sa.sa_family == AF_UNSPEC); + } + } + } + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS) + && p->allowed_ips_len > 0) { + if (idx_allowed_ips_curr == IDX_NIL) + idx_allowed_ips_curr = 0; + + nest_allowed_ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS); + if (!nest_allowed_ips) + goto toobig_allowedips; + + for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) { + const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr]; + + nest_curr_allowed_ip = nla_nest_start(msg, 0); + if (!nest_curr_allowed_ip) + goto toobig_allowedips; + + g_return_val_if_fail(NM_IN_SET(aip->family, AF_INET, AF_INET6), -NME_BUG); + + if (nla_put_uint16(msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0) + goto toobig_allowedips; + if (nla_put(msg, + WGALLOWEDIP_A_IPADDR, + nm_utils_addr_family_to_size(aip->family), + &aip->addr) + < 0) + goto toobig_allowedips; + if (nla_put_uint8(msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0) + goto toobig_allowedips; + + _nla_nest_end(msg, nest_curr_allowed_ip); + nest_curr_allowed_ip = NULL; + } + idx_allowed_ips_curr = IDX_NIL; + + _nla_nest_end(msg, nest_allowed_ips); + nest_allowed_ips = NULL; + } + } + + _nla_nest_end(msg, nest_curr_peer); + nest_curr_peer = NULL; + } + + _nla_nest_end(msg, nest_peers); + goto send; + +toobig_allowedips: + if (nest_curr_allowed_ip) + nla_nest_cancel(msg, nest_curr_allowed_ip); + if (nest_allowed_ips) + _nla_nest_end(msg, nest_allowed_ips); + _nla_nest_end(msg, nest_curr_peer); + _nla_nest_end(msg, nest_peers); + goto send; + +toobig_peers: + if (nest_curr_peer) + nla_nest_cancel(msg, nest_curr_peer); + _nla_nest_end(msg, nest_peers); + goto send; + +send: + if (!msgs) + msgs = g_ptr_array_new_with_free_func((GDestroyNotify) nlmsg_free); + g_ptr_array_add(msgs, g_steal_pointer(&msg)); + + if (idx_peer_curr != IDX_NIL && idx_peer_curr < peers_len) + goto again; + + NM_SET_OUT(out_msgs, g_steal_pointer(&msgs)); + return 0; + +nla_put_failure: + g_return_val_if_reached(-NME_BUG); + +#undef _nla_nest_end +} + +static int +link_wireguard_change(NMPlatform * platform, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gs_unref_ptrarray GPtrArray *msgs = NULL; + int wireguard_family_id; + guint i; + int r; + + wireguard_family_id = _wireguard_get_family_id(platform, ifindex); + if (wireguard_family_id < 0) + return -NME_PL_NO_FIRMWARE; + + r = _wireguard_create_change_nlmsgs(platform, + ifindex, + wireguard_family_id, + lnk_wireguard, + peers, + peer_flags, + peers_len, + change_flags, + &msgs); + if (r < 0) { + _LOGW("wireguard: set-device, cannot construct netlink message: %s", nm_strerror(r)); + return r; + } + + for (i = 0; i < msgs->len; i++) { + r = nl_send_auto(priv->genl, msgs->pdata[i]); + if (r < 0) { + _LOGW("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror(r)); + return r; + } + + do { + r = nl_recvmsgs(priv->genl, NULL); + } while (r == -EAGAIN); + if (r < 0) { + _LOGW("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror(r)); + return r; + } + + _LOGT("wireguard: set-device, message #%u sent and confirmed", i); + } + + _wireguard_refresh_link(platform, wireguard_family_id, ifindex); + + return 0; +} + +/*****************************************************************************/ + +static void +_nmp_link_address_set(NMPLinkAddress *dst, const struct nlattr *nla) +{ + *dst = (NMPLinkAddress){ + .len = 0, + }; + if (nla) { + int l = nla_len(nla); + + if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) { + G_STATIC_ASSERT_EXPR(sizeof(dst->data) == NM_UTILS_HWADDR_LEN_MAX); + memcpy(dst->data, nla_data(nla), l); + dst->len = l; + } + } +} + +/* Copied and heavily modified from libnl3's link_msg_parser(). */ +static NMPObject * +_new_from_nl_link(NMPlatform * platform, + const NMPCache * cache, + struct nlmsghdr *nlh, + gboolean id_only) +{ + static const struct nla_policy policy[] = { + [IFLA_IFNAME] = {.type = NLA_STRING, .maxlen = IFNAMSIZ}, + [IFLA_MTU] = {.type = NLA_U32}, + [IFLA_TXQLEN] = {.type = NLA_U32}, + [IFLA_LINK] = {.type = NLA_U32}, + [IFLA_WEIGHT] = {.type = NLA_U32}, + [IFLA_MASTER] = {.type = NLA_U32}, + [IFLA_OPERSTATE] = {.type = NLA_U8}, + [IFLA_LINKMODE] = {.type = NLA_U8}, + [IFLA_LINKINFO] = {.type = NLA_NESTED}, + [IFLA_QDISC] = {.type = NLA_STRING, .maxlen = IFQDISCSIZ}, + [IFLA_STATS] = {.minlen = nm_offsetofend(struct rtnl_link_stats, tx_compressed)}, + [IFLA_STATS64] = {.minlen = nm_offsetofend(struct rtnl_link_stats64, tx_compressed)}, + [IFLA_MAP] = {.minlen = nm_offsetofend(struct rtnl_link_ifmap, port)}, + [IFLA_IFALIAS] = {.type = NLA_STRING, .maxlen = IFALIASZ}, + [IFLA_NUM_VF] = {.type = NLA_U32}, + [IFLA_AF_SPEC] = {.type = NLA_NESTED}, + [IFLA_PROMISCUITY] = {.type = NLA_U32}, + [IFLA_NUM_TX_QUEUES] = {.type = NLA_U32}, + [IFLA_NUM_RX_QUEUES] = {.type = NLA_U32}, + [IFLA_GROUP] = {.type = NLA_U32}, + [IFLA_CARRIER] = {.type = NLA_U8}, + [IFLA_PHYS_PORT_ID] = {.type = NLA_UNSPEC}, + [IFLA_NET_NS_PID] = {.type = NLA_U32}, + [IFLA_NET_NS_FD] = {.type = NLA_U32}, + [IFLA_LINK_NETNSID] = {}, + }; + const struct ifinfomsg *ifi; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + struct nlattr * nl_info_data = NULL; + const char * nl_info_kind = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + gboolean completed_from_cache_val = FALSE; + gboolean * completed_from_cache = cache ? &completed_from_cache_val : NULL; + const NMPObject * link_cached = NULL; + const NMPObject * lnk_data = NULL; + gboolean address_complete_from_cache = TRUE; + gboolean broadcast_complete_from_cache = TRUE; + gboolean lnk_data_complete_from_cache = TRUE; + gboolean need_ext_data = FALSE; + gboolean af_inet6_token_valid = FALSE; + gboolean af_inet6_addr_gen_mode_valid = FALSE; + + if (!nlmsg_valid_hdr(nlh, sizeof(*ifi))) + return NULL; + + ifi = nlmsg_data(nlh); + + if (ifi->ifi_family != AF_UNSPEC) + return NULL; + if (ifi->ifi_index <= 0) + return NULL; + + obj = nmp_object_new_link(ifi->ifi_index); + + if (id_only) + return g_steal_pointer(&obj); + + if (nlmsg_parse_arr(nlh, sizeof(*ifi), tb, policy) < 0) + return NULL; + + if (!tb[IFLA_IFNAME]) + return NULL; + nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ); + if (!obj->link.name[0]) + return NULL; + + if (!tb[IFLA_MTU]) { + /* Kernel has two places that send RTM_GETLINK messages: + * net/core/rtnetlink.c and net/wireless/ext-core.c. + * Unfortunately ext-core.c sets only IFLA_WIRELESS and + * IFLA_IFNAME. This confuses code in this function, because + * it cannot get complete set of data for the interface and + * later incomplete object this function creates is used to + * overwrite existing data in NM's cache. + * Since ext-core.c doesn't set IFLA_MTU we can use it as a + * signal to ignore incoming message. + * To some extent this is a hack and correct approach is to + * merge objects per-field. + */ + return NULL; + } + obj->link.mtu = nla_get_u32(tb[IFLA_MTU]); + + if (tb[IFLA_LINKINFO]) { + static const struct nla_policy policy_link_info[] = { + [IFLA_INFO_KIND] = {.type = NLA_STRING}, + [IFLA_INFO_DATA] = {.type = NLA_NESTED}, + [IFLA_INFO_XSTATS] = {.type = NLA_NESTED}, + }; + struct nlattr *li[G_N_ELEMENTS(policy_link_info)]; + + if (nla_parse_nested_arr(li, tb[IFLA_LINKINFO], policy_link_info) < 0) + return NULL; + + if (li[IFLA_INFO_KIND]) + nl_info_kind = nla_get_string(li[IFLA_INFO_KIND]); + + nl_info_data = li[IFLA_INFO_DATA]; + } + + if (tb[IFLA_STATS64]) { + const char *stats = nla_data(tb[IFLA_STATS64]); + + obj->link.rx_packets = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_packets)]); + obj->link.rx_bytes = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_bytes)]); + obj->link.tx_packets = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_packets)]); + obj->link.tx_bytes = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_bytes)]); + } + + obj->link.n_ifi_flags = ifi->ifi_flags; + obj->link.connected = NM_FLAGS_HAS(obj->link.n_ifi_flags, IFF_LOWER_UP); + obj->link.arptype = ifi->ifi_type; + + obj->link.type = _linktype_get_type(platform, + cache, + nl_info_kind, + obj->link.ifindex, + obj->link.name, + obj->link.n_ifi_flags, + obj->link.arptype, + completed_from_cache, + &link_cached, + &obj->link.kind); + + if (tb[IFLA_MASTER]) + obj->link.master = nla_get_u32(tb[IFLA_MASTER]); + + if (tb[IFLA_LINK]) { + if (!tb[IFLA_LINK_NETNSID]) + obj->link.parent = nla_get_u32(tb[IFLA_LINK]); + else + obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS; + } + + if (tb[IFLA_ADDRESS]) { + _nmp_link_address_set(&obj->link.l_address, tb[IFLA_ADDRESS]); + address_complete_from_cache = FALSE; + } + + if (tb[IFLA_BROADCAST]) { + _nmp_link_address_set(&obj->link.l_broadcast, tb[IFLA_BROADCAST]); + broadcast_complete_from_cache = FALSE; + } + + if (tb[IFLA_AF_SPEC]) { + struct nlattr *af_attr; + int remaining; + + nla_for_each_nested (af_attr, tb[IFLA_AF_SPEC], remaining) { + switch (nla_type(af_attr)) { + case AF_INET6: + _parse_af_inet6(platform, + af_attr, + &obj->link.inet6_token, + &af_inet6_token_valid, + &obj->link.inet6_addr_gen_mode_inv, + &af_inet6_addr_gen_mode_valid); + break; + } + } + } + + switch (obj->link.type) { + case NM_LINK_TYPE_BRIDGE: + lnk_data = _parse_lnk_bridge(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + lnk_data = _parse_lnk_gre(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_INFINIBAND: + lnk_data = _parse_lnk_infiniband(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IP6TNL: + lnk_data = _parse_lnk_ip6tnl(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + lnk_data = _parse_lnk_ip6gre(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IPIP: + lnk_data = _parse_lnk_ipip(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_MACSEC: + lnk_data = _parse_lnk_macsec(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_MACVLAN: + case NM_LINK_TYPE_MACVTAP: + lnk_data = _parse_lnk_macvlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_SIT: + lnk_data = _parse_lnk_sit(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_TUN: + lnk_data = _parse_lnk_tun(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VLAN: + lnk_data = _parse_lnk_vlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VRF: + lnk_data = _parse_lnk_vrf(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VXLAN: + lnk_data = _parse_lnk_vxlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_WIFI: + case NM_LINK_TYPE_OLPC_MESH: + case NM_LINK_TYPE_WPAN: + need_ext_data = TRUE; + lnk_data_complete_from_cache = FALSE; + break; + case NM_LINK_TYPE_WIREGUARD: + lnk_data_complete_from_cache = TRUE; + break; + default: + lnk_data_complete_from_cache = FALSE; + break; + } + + if (completed_from_cache + && (lnk_data_complete_from_cache || need_ext_data || address_complete_from_cache + || broadcast_complete_from_cache || !af_inet6_token_valid + || !af_inet6_addr_gen_mode_valid || !tb[IFLA_STATS64])) { + _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached); + if (link_cached && link_cached->_link.netlink.is_in_netlink) { + if (lnk_data_complete_from_cache && link_cached->link.type == obj->link.type + && link_cached->_link.netlink.lnk + && (!lnk_data || nmp_object_equal(lnk_data, link_cached->_link.netlink.lnk))) { + /* We always try to look into the cache and reuse the object there. + * We do that, because we consider the lnk object as immutable and don't + * modify it after creating. Hence we can share it and reuse. + * + * Also, sometimes the info-data is missing for updates. In this case + * we want to keep the previously received lnk_data. */ + nmp_object_unref(lnk_data); + lnk_data = nmp_object_ref(link_cached->_link.netlink.lnk); + } + + if (need_ext_data && link_cached->link.type == obj->link.type + && link_cached->_link.ext_data) { + /* Prefer reuse of existing ext_data object */ + obj->_link.ext_data = g_object_ref(link_cached->_link.ext_data); + } + + if (address_complete_from_cache) + obj->link.l_address = link_cached->link.l_address; + if (broadcast_complete_from_cache) + obj->link.l_broadcast = link_cached->link.l_broadcast; + if (!af_inet6_token_valid) + obj->link.inet6_token = link_cached->link.inet6_token; + if (!af_inet6_addr_gen_mode_valid) + obj->link.inet6_addr_gen_mode_inv = link_cached->link.inet6_addr_gen_mode_inv; + if (!tb[IFLA_STATS64]) { + obj->link.rx_packets = link_cached->link.rx_packets; + obj->link.rx_bytes = link_cached->link.rx_bytes; + obj->link.tx_packets = link_cached->link.tx_packets; + obj->link.tx_bytes = link_cached->link.tx_bytes; + } + } + } + + obj->_link.netlink.lnk = lnk_data; + + if (need_ext_data && obj->_link.ext_data == NULL) { + switch (obj->link.type) { + case NM_LINK_TYPE_WIFI: + case NM_LINK_TYPE_OLPC_MESH: + obj->_link.ext_data = + (GObject *) nm_wifi_utils_new(ifi->ifi_index, + _genl_sock(NM_LINUX_PLATFORM(platform)), + TRUE); + break; + case NM_LINK_TYPE_WPAN: + obj->_link.ext_data = + (GObject *) nm_wpan_utils_new(ifi->ifi_index, + _genl_sock(NM_LINUX_PLATFORM(platform)), + TRUE); + break; + default: + g_assert_not_reached(); + } + } + + if (obj->link.type == NM_LINK_TYPE_WIREGUARD) { + const NMPObject *lnk_data_new = NULL; + struct nl_sock * genl = NM_LINUX_PLATFORM_GET_PRIVATE(platform)->genl; + + /* The WireGuard kernel module does not yet send link update + * notifications, so we don't actually update the cache. For + * now, always refetch link data here. */ + + _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached); + if (link_cached && link_cached->_link.netlink.is_in_netlink + && link_cached->link.type == NM_LINK_TYPE_WIREGUARD) + obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id; + else + obj->_link.wireguard_family_id = -1; + + if (obj->_link.wireguard_family_id < 0) + obj->_link.wireguard_family_id = genl_ctrl_resolve(genl, "wireguard"); + + if (obj->_link.wireguard_family_id >= 0) { + lnk_data_new = _wireguard_read_info(platform, + genl, + obj->_link.wireguard_family_id, + obj->link.ifindex); + } + + if (lnk_data_new && obj->_link.netlink.lnk + && nmp_object_equal(obj->_link.netlink.lnk, lnk_data_new)) + nmp_object_unref(lnk_data_new); + else { + nmp_object_unref(obj->_link.netlink.lnk); + obj->_link.netlink.lnk = lnk_data_new; + } + } + + obj->_link.netlink.is_in_netlink = TRUE; + return g_steal_pointer(&obj); +} + +/* Copied and heavily modified from libnl3's addr_msg_parser(). */ +static NMPObject * +_new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [IFA_LABEL] = {.type = NLA_STRING, .maxlen = IFNAMSIZ}, + [IFA_CACHEINFO] = {.minlen = nm_offsetofend(struct ifa_cacheinfo, tstamp)}, + [IFA_FLAGS] = {}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct ifaddrmsg *ifa; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_len; + guint32 lifetime, preferred, timestamp; + + if (!nlmsg_valid_hdr(nlh, sizeof(*ifa))) + return NULL; + + ifa = nlmsg_data(nlh); + + if (!NM_IN_SET(ifa->ifa_family, AF_INET, AF_INET6)) + return NULL; + + is_v4 = ifa->ifa_family == AF_INET; + + if (nlmsg_parse_arr(nlh, sizeof(*ifa), tb, policy) < 0) + return NULL; + + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128)) + return NULL; + + /*****************************************************************/ + + obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); + + obj->ip_address.ifindex = ifa->ifa_index; + obj->ip_address.plen = ifa->ifa_prefixlen; + + _check_addr_or_return_null(tb, IFA_ADDRESS, addr_len); + _check_addr_or_return_null(tb, IFA_LOCAL, addr_len); + if (is_v4) { + /* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they + * are effectively 0.0.0.0 (all-zero). */ + if (tb[IFA_LOCAL]) + memcpy(&obj->ip4_address.address, nla_data(tb[IFA_LOCAL]), addr_len); + if (tb[IFA_ADDRESS]) + memcpy(&obj->ip4_address.peer_address, nla_data(tb[IFA_ADDRESS]), addr_len); + + _check_addr_or_return_null(tb, IFA_BROADCAST, addr_len); + obj->ip4_address.broadcast_address = + tb[IFA_BROADCAST] ? nla_get_u32(tb[IFA_BROADCAST]) : 0u; + obj->ip4_address.use_ip4_broadcast_address = TRUE; + } else { + /* For IPv6, IFA_ADDRESS is always present. + * + * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address + * is :: (all-zero). + * + * If unexpectedly IFA_ADDRESS is missing, make the best of it -- but it _should_ + * actually be there. */ + if (tb[IFA_ADDRESS] || tb[IFA_LOCAL]) { + if (tb[IFA_LOCAL]) { + memcpy(&obj->ip6_address.address, nla_data(tb[IFA_LOCAL]), addr_len); + if (tb[IFA_ADDRESS]) + memcpy(&obj->ip6_address.peer_address, nla_data(tb[IFA_ADDRESS]), addr_len); + else + obj->ip6_address.peer_address = obj->ip6_address.address; + } else + memcpy(&obj->ip6_address.address, nla_data(tb[IFA_ADDRESS]), addr_len); + } + } + + obj->ip_address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + + obj->ip_address.n_ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : ifa->ifa_flags; + + if (is_v4) { + if (tb[IFA_LABEL]) { + char label[IFNAMSIZ]; + + nla_strlcpy(label, tb[IFA_LABEL], IFNAMSIZ); + + /* Check for ':'; we're only interested in labels used as interface aliases */ + if (strchr(label, ':')) + g_strlcpy(obj->ip4_address.label, label, sizeof(obj->ip4_address.label)); + } + } + + lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + preferred = NM_PLATFORM_LIFETIME_PERMANENT; + timestamp = 0; + /* IPv6 only */ + if (tb[IFA_CACHEINFO]) { + const struct ifa_cacheinfo *ca; + + ca = nla_data_as(struct ifa_cacheinfo, tb[IFA_CACHEINFO]); + lifetime = ca->ifa_valid; + preferred = ca->ifa_prefered; + timestamp = ca->tstamp; + } + _addrtime_get_lifetimes(timestamp, + lifetime, + preferred, + &obj->ip_address.timestamp, + &obj->ip_address.lifetime, + &obj->ip_address.preferred); + + return g_steal_pointer(&obj); +} + +/* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */ +static NMPObject * +_new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [RTA_TABLE] = {.type = NLA_U32}, + [RTA_IIF] = {.type = NLA_U32}, + [RTA_OIF] = {.type = NLA_U32}, + [RTA_PRIORITY] = {.type = NLA_U32}, + [RTA_PREF] = {.type = NLA_U8}, + [RTA_FLOW] = {.type = NLA_U32}, + [RTA_CACHEINFO] = {.minlen = nm_offsetofend(struct rta_cacheinfo, rta_tsage)}, + [RTA_METRICS] = {.type = NLA_NESTED}, + [RTA_MULTIPATH] = {.type = NLA_NESTED}, + }; + const struct rtmsg *rtm; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_len; + struct { + gboolean is_present; + int ifindex; + NMIPAddr gateway; + } nh = { + .is_present = FALSE, + }; + guint32 mss; + guint32 window = 0; + guint32 cwnd = 0; + guint32 initcwnd = 0; + guint32 initrwnd = 0; + guint32 mtu = 0; + guint32 lock = 0; + + if (!nlmsg_valid_hdr(nlh, sizeof(*rtm))) + return NULL; + + rtm = nlmsg_data(nlh); + + /***************************************************************** + * only handle ~supported~ routes. + *****************************************************************/ + + if (!NM_IN_SET(rtm->rtm_family, AF_INET, AF_INET6)) + return NULL; + + if (!NM_IN_SET(rtm->rtm_type, RTN_UNICAST, RTN_LOCAL)) + return NULL; + + if (nlmsg_parse_arr(nlh, sizeof(struct rtmsg), tb, policy) < 0) + return NULL; + + /*****************************************************************/ + + is_v4 = rtm->rtm_family == AF_INET; + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (rtm->rtm_dst_len > (is_v4 ? 32 : 128)) + return NULL; + + /***************************************************************** + * parse nexthops. Only handle routes with one nh. + *****************************************************************/ + + if (tb[RTA_MULTIPATH]) { + size_t tlen = nla_len(tb[RTA_MULTIPATH]); + struct rtnexthop *rtnh; + + if (tlen < sizeof(*rtnh)) + goto rta_multipath_done; + + rtnh = nla_data_as(struct rtnexthop, tb[RTA_MULTIPATH]); + + if (tlen < rtnh->rtnh_len) + goto rta_multipath_done; + + while (TRUE) { + if (nh.is_present) { + /* we don't support multipath routes. */ + return NULL; + } + + nh.is_present = TRUE; + nh.ifindex = rtnh->rtnh_ifindex; + + if (rtnh->rtnh_len > sizeof(*rtnh)) { + struct nlattr *ntb[G_N_ELEMENTS(policy)]; + + if (nla_parse_arr(ntb, + (struct nlattr *) RTNH_DATA(rtnh), + rtnh->rtnh_len - sizeof(*rtnh), + policy) + < 0) + return NULL; + + if (_check_addr_or_return_null(ntb, RTA_GATEWAY, addr_len)) + memcpy(&nh.gateway, nla_data(ntb[RTA_GATEWAY]), addr_len); + } + + if (tlen < RTNH_ALIGN(rtnh->rtnh_len) + sizeof(*rtnh)) + goto rta_multipath_done; + + tlen -= RTNH_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } +rta_multipath_done:; + } + + if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW]) { + int ifindex = 0; + NMIPAddr gateway = {}; + + if (tb[RTA_OIF]) + ifindex = nla_get_u32(tb[RTA_OIF]); + if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len)) + memcpy(&gateway, nla_data(tb[RTA_GATEWAY]), addr_len); + + if (!nh.is_present) { + /* If no nexthops have been provided via RTA_MULTIPATH + * we add it as regular nexthop to maintain backwards + * compatibility */ + nh.ifindex = ifindex; + nh.gateway = gateway; + } else { + /* Kernel supports new style nexthop configuration, + * verify that it is a duplicate and ignore old-style nexthop. */ + if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0) + return NULL; + } + } else if (!nh.is_present) + return NULL; + + /*****************************************************************/ + + mss = 0; + if (tb[RTA_METRICS]) { + static const struct nla_policy rtax_policy[] = { + [RTAX_LOCK] = {.type = NLA_U32}, + [RTAX_ADVMSS] = {.type = NLA_U32}, + [RTAX_WINDOW] = {.type = NLA_U32}, + [RTAX_CWND] = {.type = NLA_U32}, + [RTAX_INITCWND] = {.type = NLA_U32}, + [RTAX_INITRWND] = {.type = NLA_U32}, + [RTAX_MTU] = {.type = NLA_U32}, + }; + struct nlattr *mtb[G_N_ELEMENTS(rtax_policy)]; + + if (nla_parse_nested_arr(mtb, tb[RTA_METRICS], rtax_policy) < 0) + return NULL; + + if (mtb[RTAX_LOCK]) + lock = nla_get_u32(mtb[RTAX_LOCK]); + if (mtb[RTAX_ADVMSS]) + mss = nla_get_u32(mtb[RTAX_ADVMSS]); + if (mtb[RTAX_WINDOW]) + window = nla_get_u32(mtb[RTAX_WINDOW]); + if (mtb[RTAX_CWND]) + cwnd = nla_get_u32(mtb[RTAX_CWND]); + if (mtb[RTAX_INITCWND]) + initcwnd = nla_get_u32(mtb[RTAX_INITCWND]); + if (mtb[RTAX_INITRWND]) + initrwnd = nla_get_u32(mtb[RTAX_INITRWND]); + if (mtb[RTAX_MTU]) + mtu = nla_get_u32(mtb[RTAX_MTU]); + } + + /*****************************************************************/ + + obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + + obj->ip_route.type_coerced = nm_platform_route_type_coerce(rtm->rtm_type); + obj->ip_route.table_coerced = nm_platform_route_table_coerce( + tb[RTA_TABLE] ? nla_get_u32(tb[RTA_TABLE]) : (guint32) rtm->rtm_table); + + obj->ip_route.ifindex = nh.ifindex; + + if (_check_addr_or_return_null(tb, RTA_DST, addr_len)) + memcpy(obj->ip_route.network_ptr, nla_data(tb[RTA_DST]), addr_len); + + obj->ip_route.plen = rtm->rtm_dst_len; + + if (tb[RTA_PRIORITY]) + obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]); + + if (is_v4) + obj->ip4_route.gateway = nh.gateway.addr4; + else + obj->ip6_route.gateway = nh.gateway.addr6; + + if (is_v4) + obj->ip4_route.scope_inv = nm_platform_route_scope_inv(rtm->rtm_scope); + + if (_check_addr_or_return_null(tb, RTA_PREFSRC, addr_len)) { + if (is_v4) + memcpy(&obj->ip4_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); + else + memcpy(&obj->ip6_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); + } + + if (is_v4) + obj->ip4_route.tos = rtm->rtm_tos; + else { + if (tb[RTA_SRC]) { + _check_addr_or_return_null(tb, RTA_SRC, addr_len); + memcpy(&obj->ip6_route.src, nla_data(tb[RTA_SRC]), addr_len); + } + obj->ip6_route.src_plen = rtm->rtm_src_len; + } + + obj->ip_route.mss = mss; + obj->ip_route.window = window; + obj->ip_route.cwnd = cwnd; + obj->ip_route.initcwnd = initcwnd; + obj->ip_route.initrwnd = initrwnd; + obj->ip_route.mtu = mtu; + obj->ip_route.lock_window = NM_FLAGS_HAS(lock, 1 << RTAX_WINDOW); + obj->ip_route.lock_cwnd = NM_FLAGS_HAS(lock, 1 << RTAX_CWND); + obj->ip_route.lock_initcwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITCWND); + obj->ip_route.lock_initrwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITRWND); + obj->ip_route.lock_mtu = NM_FLAGS_HAS(lock, 1 << RTAX_MTU); + + if (!is_v4) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)) { + /* Detect support for RTA_PREF by inspecting the netlink message. + * RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF, + tb[RTA_PREF] ? 1 : -1); + } + + if (tb[RTA_PREF]) + obj->ip6_route.rt_pref = nla_get_u8(tb[RTA_PREF]); + } + + obj->ip_route.r_rtm_flags = rtm->rtm_flags; + obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot(rtm->rtm_protocol); + + return g_steal_pointer(&obj); +} + +static NMPObject * +_new_from_nl_routing_rule(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [FRA_UNSPEC] = {}, + [FRA_DST] = {/* struct in_addr, struct in6_addr */}, + [FRA_SRC] = {/* struct in_addr, struct in6_addr */}, + [FRA_IIFNAME] = + { + .type = NLA_STRING, + .maxlen = IFNAMSIZ, + }, + [FRA_GOTO] = + { + .type = NLA_U32, + }, + [FRA_UNUSED2] = {}, + [FRA_PRIORITY] = + { + .type = NLA_U32, + }, + [FRA_UNUSED3] = {}, + [FRA_UNUSED4] = {}, + [FRA_UNUSED5] = {}, + [FRA_FWMARK] = + { + .type = NLA_U32, + }, + [FRA_FLOW] = + { + .type = NLA_U32, + }, + [FRA_TUN_ID] = + { + .type = NLA_U64, + }, + [FRA_SUPPRESS_IFGROUP] = + { + .type = NLA_U32, + }, + [FRA_SUPPRESS_PREFIXLEN] = + { + .type = NLA_U32, + }, + [FRA_TABLE] = + { + .type = NLA_U32, + }, + [FRA_FWMASK] = + { + .type = NLA_U32, + }, + [FRA_OIFNAME] = + { + .type = NLA_STRING, + .maxlen = IFNAMSIZ, + }, + [FRA_PAD] = + { + .type = NLA_U32, + }, + [FRA_L3MDEV] = + { + .type = NLA_U8, + }, + [FRA_UID_RANGE] = + { + .minlen = sizeof(NMFibRuleUidRange), + .maxlen = sizeof(NMFibRuleUidRange), + }, + [FRA_PROTOCOL] = + { + .type = NLA_U8, + }, + [FRA_IP_PROTO] = + { + .type = NLA_U8, + }, + [FRA_SPORT_RANGE] = + { + .minlen = sizeof(NMFibRulePortRange), + .maxlen = sizeof(NMFibRulePortRange), + }, + [FRA_DPORT_RANGE] = + { + .minlen = sizeof(NMFibRulePortRange), + .maxlen = sizeof(NMFibRulePortRange), + }, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct fib_rule_hdr *frh; + NMPlatformRoutingRule * props; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_family; + guint8 addr_size; + + if (nlmsg_parse_arr(nlh, sizeof(*frh), tb, policy) < 0) + return NULL; + + frh = nlmsg_data(nlh); + + addr_family = frh->family; + + if (!NM_IN_SET(addr_family, AF_INET, AF_INET6)) { + /* we don't care about other address families. */ + return NULL; + } + + addr_size = nm_utils_addr_family_to_size(addr_family); + + obj = nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, NULL); + props = &obj->routing_rule; + + props->addr_family = addr_family; + props->action = frh->action; + props->flags = frh->flags; + props->tos = frh->tos; + + props->table = tb[FRA_TABLE] ? nla_get_u32(tb[FRA_TABLE]) : frh->table; + + if (tb[FRA_SUPPRESS_PREFIXLEN]) + props->suppress_prefixlen_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]); + + if (tb[FRA_SUPPRESS_IFGROUP]) + props->suppress_ifgroup_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]); + + if (tb[FRA_IIFNAME]) + nla_strlcpy(props->iifname, tb[FRA_IIFNAME], sizeof(props->iifname)); + + if (tb[FRA_OIFNAME]) + nla_strlcpy(props->oifname, tb[FRA_OIFNAME], sizeof(props->oifname)); + + if (tb[FRA_PRIORITY]) + props->priority = nla_get_u32(tb[FRA_PRIORITY]); + + if (tb[FRA_FWMARK]) + props->fwmark = nla_get_u32(tb[FRA_FWMARK]); + + if (tb[FRA_FWMASK]) + props->fwmask = nla_get_u32(tb[FRA_FWMASK]); + + if (tb[FRA_GOTO]) + props->goto_target = nla_get_u32(tb[FRA_GOTO]); + + props->src_len = frh->src_len; + if (props->src_len > addr_size * 8) + return NULL; + if (!tb[FRA_SRC]) { + if (props->src_len > 0) + return NULL; + } else if (!nm_ip_addr_set_from_untrusted(addr_family, + &props->src, + nla_data(tb[FRA_SRC]), + nla_len(tb[FRA_SRC]), + NULL)) + return NULL; + + props->dst_len = frh->dst_len; + if (props->dst_len > addr_size * 8) + return NULL; + if (!tb[FRA_DST]) { + if (props->dst_len > 0) + return NULL; + } else if (!nm_ip_addr_set_from_untrusted(addr_family, + &props->dst, + nla_data(tb[FRA_DST]), + nla_len(tb[FRA_DST]), + NULL)) + return NULL; + + if (tb[FRA_FLOW]) + props->flow = nla_get_u32(tb[FRA_FLOW]); + + if (tb[FRA_TUN_ID]) + props->tun_id = nla_get_be64(tb[FRA_TUN_ID]); + + if (tb[FRA_L3MDEV]) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) { + /* support for FRA_L3MDEV was added in 96c63fa7393d0a346acfe5a91e0c7d4c7782641b, + * kernel 4.8, 3 October 2017. + * + * We can only detect support if the attribute is present. A missing attribute + * is not conclusive. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, 1); + } + + /* actually, kernel only allows this attribute to be missing or + * "1". Still, encode it as full uint8. + * + * Note that FRA_L3MDEV and FRA_TABLE are mutally exclusive. */ + props->l3mdev = nla_get_u8(tb[FRA_L3MDEV]); + } + + if (tb[FRA_PROTOCOL]) + props->protocol = nla_get_u8(tb[FRA_PROTOCOL]); + else + nm_assert(props->protocol == RTPROT_UNSPEC); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) { + /* FRA_PROTOCOL was added in kernel 4.17, dated 3 June, 2018. + * See commit 1b71af6053af1bd2f849e9fda4f71c1e3f145dcf. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL, + tb[FRA_PROTOCOL] ? 1 : -1); + } + + if (tb[FRA_IP_PROTO]) + props->ip_proto = nla_get_u8(tb[FRA_IP_PROTO]); + + G_STATIC_ASSERT_EXPR(sizeof(NMFibRulePortRange) == 4); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, start) == 0); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, end) == 2); + + nla_memcpy_checked_size(&props->sport_range, tb[FRA_SPORT_RANGE], sizeof(props->sport_range)); + nla_memcpy_checked_size(&props->dport_range, tb[FRA_DPORT_RANGE], sizeof(props->dport_range)); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + /* support for FRA_IP_PROTO, FRA_SPORT_RANGE, and FRA_DPORT_RANGE was added together + * by bfff4862653bb96001ab57c1edd6d03f48e5f035, kernel 4.17, 4 June 2018. + * + * Unfortunately, a missing attribute does not tell us anything about support. + * We can only tell for sure when we have support, but not when we don't have. */ + if (tb[FRA_IP_PROTO] || tb[FRA_SPORT_RANGE] || tb[FRA_DPORT_RANGE]) + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, 1); + } + + G_STATIC_ASSERT_EXPR(sizeof(NMFibRuleUidRange) == 8); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, start) == 0); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, end) == 4); + + if (tb[FRA_UID_RANGE]) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + /* support for FRA_UID_RANGE was added in 622ec2c9d52405973c9f1ca5116eb1c393adfc7d, + * kernel 4.10, 19 February 2017. + * + * We can only detect support if the attribute is present. A missing attribute + * is not conclusive. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, 1); + } + + nla_memcpy_checked_size(&props->uid_range, tb[FRA_UID_RANGE], sizeof(props->uid_range)); + props->uid_range_has = TRUE; + } + + return g_steal_pointer(&obj); +} + +static guint32 +psched_tick_to_time(NMPlatform *platform, guint32 tick) +{ + static gboolean initialized; + static double tick_in_usec = 1; + + if (!initialized) { + gs_free char *params = NULL; + double clock_factor = 1; + guint32 clock_res; + guint32 t2us; + guint32 us2t; + + initialized = TRUE; + params = nm_platform_sysctl_get(platform, NMP_SYSCTL_PATHID_ABSOLUTE("/proc/net/psched")); + if (!params || sscanf(params, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) { + _LOGW("packet scheduler parameters not available"); + } else { + /* See tc_core_init() in iproute2 */ + if (clock_res == 1000000000) + t2us = us2t; + + clock_factor = (double) clock_res / PSCHED_TIME_UNITS_PER_SEC; + tick_in_usec = (double) t2us / us2t * clock_factor; + } + } + + return tick / tick_in_usec; +} + +static NMPObject * +_new_from_nl_qdisc(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [TCA_KIND] = {.type = NLA_STRING}, + [TCA_OPTIONS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct tcmsg *tcm; + nm_auto_nmpobj NMPObject *obj = NULL; + + if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0) + return NULL; + + if (!tb[TCA_KIND]) + return NULL; + + tcm = nlmsg_data(nlh); + + obj = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL); + + obj->qdisc.kind = g_intern_string(nla_get_string(tb[TCA_KIND])); + obj->qdisc.ifindex = tcm->tcm_ifindex; + obj->qdisc.addr_family = tcm->tcm_family; + obj->qdisc.handle = tcm->tcm_handle; + obj->qdisc.parent = tcm->tcm_parent; + obj->qdisc.info = tcm->tcm_info; + + if (nm_streq0(obj->qdisc.kind, "fq_codel")) { + obj->qdisc.fq_codel.memory_limit = NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET; + obj->qdisc.fq_codel.ce_threshold = NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED; + } + + if (tb[TCA_OPTIONS]) { + struct nlattr *options_attr; + int remaining; + + if (nm_streq0(obj->qdisc.kind, "sfq")) { + struct tc_sfq_qopt_v1 opt; + + if (tb[TCA_OPTIONS]->nla_len >= nla_attr_size(sizeof(opt))) { + memcpy(&opt, nla_data(tb[TCA_OPTIONS]), sizeof(opt)); + obj->qdisc.sfq.quantum = opt.v0.quantum; + obj->qdisc.sfq.perturb_period = opt.v0.perturb_period; + obj->qdisc.sfq.limit = opt.v0.limit; + obj->qdisc.sfq.divisor = opt.v0.divisor; + obj->qdisc.sfq.flows = opt.v0.flows; + obj->qdisc.sfq.depth = opt.depth; + } + } else if (nm_streq0(obj->qdisc.kind, "tbf")) { + static const struct nla_policy tbf_policy[] = { + [TCA_TBF_PARMS] = {.minlen = sizeof(struct tc_tbf_qopt)}, + [TCA_TBF_RATE64] = {.type = NLA_U64}, + }; + struct nlattr * tbf_tb[G_N_ELEMENTS(tbf_policy)]; + struct tc_tbf_qopt opt; + + if (nla_parse_nested_arr(tbf_tb, tb[TCA_OPTIONS], tbf_policy) < 0) + return NULL; + if (!tbf_tb[TCA_TBF_PARMS]) + return NULL; + + nla_memcpy_checked_size(&opt, tbf_tb[TCA_TBF_PARMS], sizeof(opt)); + obj->qdisc.tbf.rate = opt.rate.rate; + if (tbf_tb[TCA_TBF_RATE64]) + obj->qdisc.tbf.rate = nla_get_u64(tbf_tb[TCA_TBF_RATE64]); + obj->qdisc.tbf.burst = + ((double) obj->qdisc.tbf.rate * psched_tick_to_time(platform, opt.buffer)) + / PSCHED_TIME_UNITS_PER_SEC; + obj->qdisc.tbf.limit = opt.limit; + } else { + nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) { + if (nla_len(options_attr) < sizeof(uint32_t)) + continue; + + if (nm_streq0(obj->qdisc.kind, "fq_codel")) { + switch (nla_type(options_attr)) { + case TCA_FQ_CODEL_LIMIT: + obj->qdisc.fq_codel.limit = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_FLOWS: + obj->qdisc.fq_codel.flows = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_TARGET: + obj->qdisc.fq_codel.target = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_INTERVAL: + obj->qdisc.fq_codel.interval = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_QUANTUM: + obj->qdisc.fq_codel.quantum = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_CE_THRESHOLD: + obj->qdisc.fq_codel.ce_threshold = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_MEMORY_LIMIT: + obj->qdisc.fq_codel.memory_limit = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_ECN: + obj->qdisc.fq_codel.ecn = !!nla_get_u32(options_attr); + break; + } + } + } + } + } + + return g_steal_pointer(&obj); +} + +static NMPObject * +_new_from_nl_tfilter(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [TCA_KIND] = {.type = NLA_STRING}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj = NULL; + const struct tcmsg *tcm; + + if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0) + return NULL; + + if (!tb[TCA_KIND]) + return NULL; + + tcm = nlmsg_data(nlh); + + obj = nmp_object_new(NMP_OBJECT_TYPE_TFILTER, NULL); + + obj->tfilter.kind = g_intern_string(nla_get_string(tb[TCA_KIND])); + obj->tfilter.ifindex = tcm->tcm_ifindex; + obj->tfilter.addr_family = tcm->tcm_family; + obj->tfilter.handle = tcm->tcm_handle; + obj->tfilter.parent = tcm->tcm_parent; + obj->tfilter.info = tcm->tcm_info; + + return obj; +} + +/** + * nmp_object_new_from_nl: + * @platform: (allow-none): for creating certain objects, the constructor wants to check + * sysfs. For this the platform instance is needed. If missing, the object might not + * be correctly detected. + * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information. + * If a cache is given, the object is completed with information from the cache. + * @nlh: the netlink message header + * @id_only: whether only to create an empty object with only the ID fields set. + * + * Returns: %NULL or a newly created NMPObject instance. + **/ +static NMPObject * +nmp_object_new_from_nl(NMPlatform * platform, + const NMPCache *cache, + struct nl_msg * msg, + gboolean id_only) +{ + struct nlmsghdr *msghdr; + + if (nlmsg_get_proto(msg) != NETLINK_ROUTE) + return NULL; + + msghdr = nlmsg_hdr(msg); + + switch (msghdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + case RTM_GETLINK: + case RTM_SETLINK: + return _new_from_nl_link(platform, cache, msghdr, id_only); + case RTM_NEWADDR: + case RTM_DELADDR: + case RTM_GETADDR: + return _new_from_nl_addr(msghdr, id_only); + case RTM_NEWROUTE: + case RTM_DELROUTE: + case RTM_GETROUTE: + return _new_from_nl_route(msghdr, id_only); + case RTM_NEWRULE: + case RTM_DELRULE: + case RTM_GETRULE: + return _new_from_nl_routing_rule(msghdr, id_only); + case RTM_NEWQDISC: + case RTM_DELQDISC: + case RTM_GETQDISC: + return _new_from_nl_qdisc(platform, msghdr, id_only); + case RTM_NEWTFILTER: + case RTM_DELTFILTER: + case RTM_GETTFILTER: + return _new_from_nl_tfilter(msghdr, id_only); + default: + return NULL; + } +} + +/*****************************************************************************/ + +static gboolean +_nl_msg_new_link_set_afspec(struct nl_msg *msg, int addr_gen_mode, NMUtilsIPv6IfaceId *iid) +{ + struct nlattr *af_spec; + struct nlattr *af_attr; + + nm_assert(msg); + + if (!(af_spec = nla_nest_start(msg, IFLA_AF_SPEC))) + goto nla_put_failure; + + if (addr_gen_mode >= 0 || iid) { + if (!(af_attr = nla_nest_start(msg, AF_INET6))) + goto nla_put_failure; + + if (addr_gen_mode >= 0) + NLA_PUT_U8(msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode); + + if (iid) { + struct in6_addr i6_token = {.s6_addr = { + 0, + }}; + + nm_utils_ipv6_addr_set_interface_identifier(&i6_token, *iid); + NLA_PUT(msg, IFLA_INET6_TOKEN, sizeof(struct in6_addr), &i6_token); + } + + nla_nest_end(msg, af_attr); + } + + nla_nest_end(msg, af_spec); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +_nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpointer extra_data) +{ + struct nlattr *info; + struct nlattr *data = NULL; + const char * kind; + + nm_assert(msg); + + kind = nm_link_type_to_rtnl_type_string(link_type); + if (!kind) + goto nla_put_failure; + + if (!(info = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, IFLA_INFO_KIND, kind); + + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + { + const NMPlatformLnkBridge *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_BR_FORWARD_DELAY, props->forward_delay); + NLA_PUT_U32(msg, IFLA_BR_HELLO_TIME, props->hello_time); + NLA_PUT_U32(msg, IFLA_BR_MAX_AGE, props->max_age); + NLA_PUT_U32(msg, IFLA_BR_AGEING_TIME, props->ageing_time); + NLA_PUT_U32(msg, IFLA_BR_STP_STATE, !!props->stp_state); + NLA_PUT_U16(msg, IFLA_BR_PRIORITY, props->priority); + NLA_PUT_U16(msg, IFLA_BR_VLAN_PROTOCOL, htons(props->vlan_protocol)); + if (props->vlan_stats_enabled) + NLA_PUT_U8(msg, IFLA_BR_VLAN_STATS_ENABLED, !!props->vlan_stats_enabled); + NLA_PUT_U16(msg, IFLA_BR_GROUP_FWD_MASK, props->group_fwd_mask); + NLA_PUT(msg, IFLA_BR_GROUP_ADDR, sizeof(props->group_addr), &props->group_addr); + NLA_PUT_U8(msg, IFLA_BR_MCAST_SNOOPING, !!props->mcast_snooping); + NLA_PUT_U8(msg, IFLA_BR_MCAST_ROUTER, props->mcast_router); + NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERY_USE_IFADDR, !!props->mcast_query_use_ifaddr); + NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERIER, !!props->mcast_querier); + NLA_PUT_U32(msg, IFLA_BR_MCAST_HASH_MAX, props->mcast_hash_max); + NLA_PUT_U32(msg, IFLA_BR_MCAST_LAST_MEMBER_CNT, props->mcast_last_member_count); + NLA_PUT_U32(msg, IFLA_BR_MCAST_STARTUP_QUERY_CNT, props->mcast_startup_query_count); + NLA_PUT_U64(msg, IFLA_BR_MCAST_LAST_MEMBER_INTVL, props->mcast_last_member_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_MEMBERSHIP_INTVL, props->mcast_membership_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERIER_INTVL, props->mcast_querier_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_INTVL, props->mcast_query_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, props->mcast_query_response_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, props->mcast_startup_query_interval); + break; + } + case NM_LINK_TYPE_VLAN: + { + const NMPlatformLnkVlan *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U16(msg, IFLA_VLAN_ID, props->id); + + { + struct ifla_vlan_flags flags = { + .flags = props->flags & NM_VLAN_FLAGS_ALL, + .mask = NM_VLAN_FLAGS_ALL, + }; + + NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags); + } + break; + } + case NM_LINK_TYPE_VRF: + { + const NMPlatformLnkVrf *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_VRF_TABLE, props->table); + break; + } + case NM_LINK_TYPE_VXLAN: + { + const NMPlatformLnkVxlan *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_VXLAN_ID, props->id); + + if (props->group) + NLA_PUT(msg, IFLA_VXLAN_GROUP, sizeof(props->group), &props->group); + else if (!IN6_IS_ADDR_UNSPECIFIED(&props->group6)) + NLA_PUT(msg, IFLA_VXLAN_GROUP6, sizeof(props->group6), &props->group6); + + if (props->local) + NLA_PUT(msg, IFLA_VXLAN_LOCAL, sizeof(props->local), &props->local); + else if (!IN6_IS_ADDR_UNSPECIFIED(&props->local6)) + NLA_PUT(msg, IFLA_VXLAN_LOCAL6, sizeof(props->local6), &props->local6); + + if (props->parent_ifindex >= 0) + NLA_PUT_U32(msg, IFLA_VXLAN_LINK, props->parent_ifindex); + + if (props->src_port_min || props->src_port_max) { + struct nm_ifla_vxlan_port_range port_range = { + .low = htons(props->src_port_min), + .high = htons(props->src_port_max), + }; + + NLA_PUT(msg, IFLA_VXLAN_PORT_RANGE, sizeof(port_range), &port_range); + } + + NLA_PUT_U16(msg, IFLA_VXLAN_PORT, htons(props->dst_port)); + NLA_PUT_U8(msg, IFLA_VXLAN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_VXLAN_TTL, props->ttl); + NLA_PUT_U32(msg, IFLA_VXLAN_AGEING, props->ageing); + NLA_PUT_U32(msg, IFLA_VXLAN_LIMIT, props->limit); + NLA_PUT_U8(msg, IFLA_VXLAN_LEARNING, !!props->learning); + NLA_PUT_U8(msg, IFLA_VXLAN_PROXY, !!props->proxy); + NLA_PUT_U8(msg, IFLA_VXLAN_RSC, !!props->rsc); + NLA_PUT_U8(msg, IFLA_VXLAN_L2MISS, !!props->l2miss); + NLA_PUT_U8(msg, IFLA_VXLAN_L3MISS, !!props->l3miss); + break; + } + case NM_LINK_TYPE_VETH: + { + const char * veth_peer = extra_data; + const struct ifinfomsg ifi = {}; + struct nlattr * info_peer; + + nm_assert(veth_peer); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + if (!(info_peer = nla_nest_start(msg, 1 /*VETH_INFO_PEER*/))) + goto nla_put_failure; + if (nlmsg_append_struct(msg, &ifi) < 0) + goto nla_put_failure; + NLA_PUT_STRING(msg, IFLA_IFNAME, veth_peer); + nla_nest_end(msg, info_peer); + break; + } + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + { + const NMPlatformLnkGre *props = extra_data; + + nm_assert(props); + nm_assert(props->is_tap == (link_type == NM_LINK_TYPE_GRETAP)); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_GRE_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_GRE_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_GRE_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_GRE_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_GRE_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery); + NLA_PUT_U32(msg, IFLA_GRE_IKEY, htonl(props->input_key)); + NLA_PUT_U32(msg, IFLA_GRE_OKEY, htonl(props->output_key)); + NLA_PUT_U16(msg, IFLA_GRE_IFLAGS, htons(props->input_flags)); + NLA_PUT_U16(msg, IFLA_GRE_OFLAGS, htons(props->output_flags)); + break; + } + case NM_LINK_TYPE_SIT: + { + const NMPlatformLnkSit *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); + break; + } + case NM_LINK_TYPE_IP6TNL: + { + const NMPlatformLnkIp6Tnl *props = extra_data; + guint32 flowinfo; + + nm_assert(props); + nm_assert(!props->is_gre); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_IPTUN_LOCAL, sizeof(props->local), &props->local); + if (!IN6_IS_ADDR_UNSPECIFIED(&props->remote)) + NLA_PUT(msg, IFLA_IPTUN_REMOTE, sizeof(props->remote), &props->remote); + + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit); + + flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK; + flowinfo |= (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT) & IP6_FLOWINFO_TCLASS_MASK; + NLA_PUT_U32(msg, IFLA_IPTUN_FLOWINFO, htonl(flowinfo)); + NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, props->proto); + NLA_PUT_U32(msg, IFLA_IPTUN_FLAGS, props->flags); + break; + } + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + { + const NMPlatformLnkIp6Tnl *props = extra_data; + guint32 flowinfo; + + nm_assert(props); + nm_assert(props->is_gre); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_GRE_LINK, props->parent_ifindex); + + NLA_PUT_U32(msg, IFLA_GRE_IKEY, htonl(props->input_key)); + NLA_PUT_U32(msg, IFLA_GRE_OKEY, htonl(props->output_key)); + NLA_PUT_U16(msg, IFLA_GRE_IFLAGS, htons(props->input_flags)); + NLA_PUT_U16(msg, IFLA_GRE_OFLAGS, htons(props->output_flags)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_GRE_LOCAL, sizeof(props->local), &props->local); + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_GRE_REMOTE, sizeof(props->remote), &props->remote); + + NLA_PUT_U8(msg, IFLA_GRE_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit); + + flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK; + flowinfo |= (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT) & IP6_FLOWINFO_TCLASS_MASK; + NLA_PUT_U32(msg, IFLA_GRE_FLOWINFO, htonl(flowinfo)); + NLA_PUT_U32(msg, IFLA_GRE_FLAGS, props->flags); + break; + } + case NM_LINK_TYPE_IPIP: + { + const NMPlatformLnkIpIp *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); + break; + } + case NM_LINK_TYPE_MACSEC: + { + const NMPlatformLnkMacsec *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->icv_length) + NLA_PUT_U8(msg, IFLA_MACSEC_ICV_LEN, 16); + if (props->cipher_suite) + NLA_PUT_U64(msg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite); + if (props->replay_protect) + NLA_PUT_U32(msg, IFLA_MACSEC_WINDOW, props->window); + + NLA_PUT_U64(msg, IFLA_MACSEC_SCI, htobe64(props->sci)); + NLA_PUT_U8(msg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa); + NLA_PUT_U8(msg, IFLA_MACSEC_ENCRYPT, props->encrypt); + NLA_PUT_U8(msg, IFLA_MACSEC_PROTECT, props->protect); + NLA_PUT_U8(msg, IFLA_MACSEC_INC_SCI, props->include_sci); + NLA_PUT_U8(msg, IFLA_MACSEC_ES, props->es); + NLA_PUT_U8(msg, IFLA_MACSEC_SCB, props->scb); + NLA_PUT_U8(msg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect); + NLA_PUT_U8(msg, IFLA_MACSEC_VALIDATION, props->validation); + break; + }; + case NM_LINK_TYPE_MACVTAP: + case NM_LINK_TYPE_MACVLAN: + { + const NMPlatformLnkMacvlan *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_MACVLAN_MODE, props->mode); + NLA_PUT_U16(msg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0); + break; + } + default: + nm_assert(!extra_data); + break; + } + + if (data) + nla_nest_end(msg, data); + + nla_nest_end(msg, info); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +_nl_msg_new_link_set_linkinfo_vlan(struct nl_msg * msg, + int vlan_id, + guint32 flags_mask, + guint32 flags_set, + const NMVlanQosMapping *ingress_qos, + int ingress_qos_len, + const NMVlanQosMapping *egress_qos, + int egress_qos_len) +{ + struct nlattr *info; + struct nlattr *data; + guint i; + gboolean has_any_vlan_properties = FALSE; + + G_STATIC_ASSERT(NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR); + G_STATIC_ASSERT(NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP); + G_STATIC_ASSERT(NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING); + G_STATIC_ASSERT(NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP); + +#define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0) + + nm_assert(msg); + + /* We must not create an empty IFLA_LINKINFO section. Otherwise, kernel + * rejects the request as invalid. */ + if (flags_mask != 0 || vlan_id >= 0) + has_any_vlan_properties = TRUE; + if (!has_any_vlan_properties && ingress_qos && ingress_qos_len > 0) { + for (i = 0; i < ingress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) { + has_any_vlan_properties = TRUE; + break; + } + } + } + if (!has_any_vlan_properties && egress_qos && egress_qos_len > 0) { + for (i = 0; i < egress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) { + has_any_vlan_properties = TRUE; + break; + } + } + } + if (!has_any_vlan_properties) + return TRUE; + + if (!(info = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, IFLA_INFO_KIND, "vlan"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (vlan_id >= 0) + NLA_PUT_U16(msg, IFLA_VLAN_ID, vlan_id); + + if (flags_mask != 0) { + struct ifla_vlan_flags flags = { + .flags = flags_mask & flags_set, + .mask = flags_mask, + }; + + NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags); + } + + if (ingress_qos && ingress_qos_len > 0) { + struct nlattr *qos = NULL; + + for (i = 0; i < ingress_qos_len; i++) { + /* Silently ignore invalid mappings. Kernel would truncate + * them and modify the wrong mapping. */ + if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) { + if (!qos) { + if (!(qos = nla_nest_start(msg, IFLA_VLAN_INGRESS_QOS))) + goto nla_put_failure; + } + NLA_PUT(msg, i, sizeof(ingress_qos[i]), &ingress_qos[i]); + } + } + + if (qos) + nla_nest_end(msg, qos); + } + + if (egress_qos && egress_qos_len > 0) { + struct nlattr *qos = NULL; + + for (i = 0; i < egress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) { + if (!qos) { + if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS))) + goto nla_put_failure; + } + NLA_PUT(msg, i, sizeof(egress_qos[i]), &egress_qos[i]); + } + } + + if (qos) + nla_nest_end(msg, qos); + } + + nla_nest_end(msg, data); + nla_nest_end(msg, info); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static struct nl_msg * +_nl_msg_new_link_full(int nlmsg_type, + int nlmsg_flags, + int ifindex, + const char *ifname, + guint8 family, + unsigned flags_mask, + unsigned flags_set) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const struct ifinfomsg ifi = { + .ifi_family = family, + .ifi_change = flags_mask, + .ifi_flags = flags_set, + .ifi_index = ifindex, + }; + + nm_assert(NM_IN_SET(nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK, RTM_SETLINK)); + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + if (nlmsg_append_struct(msg, &ifi) < 0) + goto nla_put_failure; + + if (ifname) + NLA_PUT_STRING(msg, IFLA_IFNAME, ifname); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_link(int nlmsg_type, int nlmsg_flags, int ifindex, const char *ifname) +{ + return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0); +} + +/* Copied and modified from libnl3's build_addr_msg(). */ +static struct nl_msg * +_nl_msg_new_address(int nlmsg_type, + int nlmsg_flags, + int family, + int ifindex, + gconstpointer address, + guint8 plen, + gconstpointer peer_address, + guint32 flags, + int scope, + guint32 lifetime, + guint32 preferred, + in_addr_t ip4_broadcast_address, + const char * label) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct ifaddrmsg am = { + .ifa_family = family, + .ifa_index = ifindex, + .ifa_prefixlen = plen, + .ifa_flags = flags, + }; + gsize addr_len; + + nm_assert(NM_IN_SET(family, AF_INET, AF_INET6)); + nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWADDR, RTM_DELADDR)); + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + if (scope == -1) { + /* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */ + if (family == AF_INET && address && *((char *) address) == 127) + scope = RT_SCOPE_HOST; + else + scope = RT_SCOPE_UNIVERSE; + } + am.ifa_scope = scope, + + addr_len = family == AF_INET ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (nlmsg_append_struct(msg, &am) < 0) + goto nla_put_failure; + + if (address) + NLA_PUT(msg, IFA_LOCAL, addr_len, address); + + if (peer_address) + NLA_PUT(msg, IFA_ADDRESS, addr_len, peer_address); + else if (address) + NLA_PUT(msg, IFA_ADDRESS, addr_len, address); + + if (label && label[0]) + NLA_PUT_STRING(msg, IFA_LABEL, label); + + if (ip4_broadcast_address != 0) + NLA_PUT(msg, IFA_BROADCAST, sizeof(in_addr_t), &ip4_broadcast_address); + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { + struct ifa_cacheinfo ca = { + .ifa_valid = lifetime, + .ifa_prefered = preferred, + }; + + NLA_PUT(msg, IFA_CACHEINFO, sizeof(ca), &ca); + } + + if (flags & ~((guint32) 0xFF)) { + /* only set the IFA_FLAGS attribute, if they actually contain additional + * flags that are not already set to am.ifa_flags. + * + * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL + * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which + * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59. */ + NLA_PUT_U32(msg, IFA_FLAGS, flags); + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static guint32 +ip_route_get_lock_flag(const NMPlatformIPRoute *route) +{ + return (((guint32) route->lock_window) << RTAX_WINDOW) + | (((guint32) route->lock_cwnd) << RTAX_CWND) + | (((guint32) route->lock_initcwnd) << RTAX_INITCWND) + | (((guint32) route->lock_initrwnd) << RTAX_INITRWND) + | (((guint32) route->lock_mtu) << RTAX_MTU); +} + +/* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */ +static struct nl_msg * +_nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const NMPClass * klass = NMP_OBJECT_GET_CLASS(obj); + gboolean is_v4 = klass->addr_family == AF_INET; + const guint32 lock = ip_route_get_lock_flag(NMP_OBJECT_CAST_IP_ROUTE(obj)); + const guint32 table = + nm_platform_route_table_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->table_coerced, TRUE); + const struct rtmsg rtmsg = { + .rtm_family = klass->addr_family, + .rtm_tos = is_v4 ? obj->ip4_route.tos : 0, + .rtm_table = table <= 0xFF ? table : RT_TABLE_UNSPEC, + .rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot(obj->ip_route.rt_source), + .rtm_scope = + is_v4 ? nm_platform_route_scope_inv(obj->ip4_route.scope_inv) : RT_SCOPE_NOWHERE, + .rtm_type = nm_platform_route_type_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced), + .rtm_flags = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)), + .rtm_dst_len = obj->ip_route.plen, + .rtm_src_len = is_v4 ? 0 : NMP_OBJECT_CAST_IP6_ROUTE(obj)->src_plen, + }; + + gsize addr_len; + + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE)); + + msg = nlmsg_alloc_simple(nlmsg_type, (int) nlmsgflags); + + if (nlmsg_append_struct(msg, &rtmsg) < 0) + goto nla_put_failure; + + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + NLA_PUT(msg, + RTA_DST, + addr_len, + is_v4 ? (gconstpointer) &obj->ip4_route.network + : (gconstpointer) &obj->ip6_route.network); + + if (!is_v4) { + if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->src)) + NLA_PUT(msg, RTA_SRC, addr_len, &obj->ip6_route.src); + } + + NLA_PUT_U32(msg, + RTA_PRIORITY, + is_v4 ? nm_platform_ip4_route_get_effective_metric(&obj->ip4_route) + : nm_platform_ip6_route_get_effective_metric(&obj->ip6_route)); + + if (table > 0xFF) + NLA_PUT_U32(msg, RTA_TABLE, table); + + if (is_v4) { + if (NMP_OBJECT_CAST_IP4_ROUTE(obj)->pref_src) + NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src); + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->pref_src)) + NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src); + } + + if (obj->ip_route.mss || obj->ip_route.window || obj->ip_route.cwnd || obj->ip_route.initcwnd + || obj->ip_route.initrwnd || obj->ip_route.mtu || lock) { + struct nlattr *metrics; + + metrics = nla_nest_start(msg, RTA_METRICS); + if (!metrics) + goto nla_put_failure; + + if (obj->ip_route.mss) + NLA_PUT_U32(msg, RTAX_ADVMSS, obj->ip_route.mss); + if (obj->ip_route.window) + NLA_PUT_U32(msg, RTAX_WINDOW, obj->ip_route.window); + if (obj->ip_route.cwnd) + NLA_PUT_U32(msg, RTAX_CWND, obj->ip_route.cwnd); + if (obj->ip_route.initcwnd) + NLA_PUT_U32(msg, RTAX_INITCWND, obj->ip_route.initcwnd); + if (obj->ip_route.initrwnd) + NLA_PUT_U32(msg, RTAX_INITRWND, obj->ip_route.initrwnd); + if (obj->ip_route.mtu) + NLA_PUT_U32(msg, RTAX_MTU, obj->ip_route.mtu); + if (lock) + NLA_PUT_U32(msg, RTAX_LOCK, lock); + + nla_nest_end(msg, metrics); + } + + /* We currently don't have need for multi-hop routes... */ + if (is_v4) { + NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) + NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway); + } + NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex); + + if (!is_v4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM) + NLA_PUT_U8(msg, RTA_PREF, obj->ip6_route.rt_pref); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_routing_rule(int nlmsg_type, int nlmsg_flags, const NMPlatformRoutingRule *routing_rule) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const guint8 addr_size = nm_utils_addr_family_to_size(routing_rule->addr_family); + guint32 table; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + table = routing_rule->table; + + if (NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6) + && routing_rule->action == FR_ACT_TO_TBL && routing_rule->l3mdev == 0 + && table == RT_TABLE_UNSPEC) { + /* for IPv6, this setting is invalid and rejected by kernel. That's fine. + * + * for IPv4, kernel will automatically assign an unused table. That's not + * fine, because we don't know what we will get. + * + * The caller must not allow that to happen. */ + nm_assert_not_reached(); + } + + { + const struct fib_rule_hdr frh = { + .family = routing_rule->addr_family, + .src_len = routing_rule->src_len, + .dst_len = routing_rule->dst_len, + .tos = routing_rule->tos, + .table = table < 0x100u ? (guint8) table : (guint8) RT_TABLE_UNSPEC, + .action = routing_rule->action, + + /* we only allow setting the "not" flag. */ + .flags = routing_rule->flags & ((guint32) FIB_RULE_INVERT), + }; + + if (nlmsg_append_struct(msg, &frh) < 0) + goto nla_put_failure; + } + + if (table > G_MAXINT8) + NLA_PUT_U32(msg, FRA_TABLE, table); + + if (routing_rule->suppress_prefixlen_inverse != 0) + NLA_PUT_U32(msg, FRA_SUPPRESS_PREFIXLEN, ~routing_rule->suppress_prefixlen_inverse); + + if (routing_rule->suppress_ifgroup_inverse != 0) + NLA_PUT_U32(msg, FRA_SUPPRESS_IFGROUP, ~routing_rule->suppress_ifgroup_inverse); + + if (routing_rule->iifname[0] != '\0') + NLA_PUT_STRING(msg, FRA_IIFNAME, routing_rule->iifname); + + if (routing_rule->oifname[0] != '\0') + NLA_PUT_STRING(msg, FRA_OIFNAME, routing_rule->oifname); + + /* we always set the priority and don't support letting kernel pick one. */ + NLA_PUT_U32(msg, FRA_PRIORITY, routing_rule->priority); + + if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) { + NLA_PUT_U32(msg, FRA_FWMARK, routing_rule->fwmark); + NLA_PUT_U32(msg, FRA_FWMASK, routing_rule->fwmask); + } + + if (routing_rule->src_len > 0) + NLA_PUT(msg, FRA_SRC, addr_size, &routing_rule->src); + + if (routing_rule->dst_len > 0) + NLA_PUT(msg, FRA_DST, addr_size, &routing_rule->dst); + + if (routing_rule->flow != 0) { + /* only relevant for IPv4. */ + NLA_PUT_U32(msg, FRA_FLOW, routing_rule->flow); + } + + if (routing_rule->tun_id != 0) + NLA_PUT_U64(msg, FRA_TUN_ID, htobe64(routing_rule->tun_id)); + + if (routing_rule->l3mdev) + NLA_PUT_U8(msg, FRA_L3MDEV, routing_rule->l3mdev); + + if (routing_rule->protocol != RTPROT_UNSPEC) + NLA_PUT_U8(msg, FRA_PROTOCOL, routing_rule->protocol); + + if (routing_rule->ip_proto != 0) + NLA_PUT_U8(msg, FRA_IP_PROTO, routing_rule->ip_proto); + + if (routing_rule->sport_range.start || routing_rule->sport_range.end) + NLA_PUT(msg, + FRA_SPORT_RANGE, + sizeof(routing_rule->sport_range), + &routing_rule->sport_range); + + if (routing_rule->dport_range.start || routing_rule->dport_range.end) + NLA_PUT(msg, + FRA_DPORT_RANGE, + sizeof(routing_rule->dport_range), + &routing_rule->dport_range); + + if (routing_rule->uid_range_has) + NLA_PUT(msg, FRA_UID_RANGE, sizeof(routing_rule->uid_range), &routing_rule->uid_range); + + switch (routing_rule->action) { + case FR_ACT_GOTO: + NLA_PUT_U32(msg, FRA_GOTO, routing_rule->goto_target); + break; + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_qdisc(int nlmsg_type, int nlmsg_flags, const NMPlatformQdisc *qdisc) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * tc_options; + const struct tcmsg tcm = { + .tcm_family = qdisc->addr_family, + .tcm_ifindex = qdisc->ifindex, + .tcm_handle = qdisc->handle, + .tcm_parent = qdisc->parent, + .tcm_info = qdisc->info, + }; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO); + + if (nlmsg_append_struct(msg, &tcm) < 0) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_KIND, qdisc->kind); + + if (nm_streq(qdisc->kind, "sfq")) { + struct tc_sfq_qopt_v1 opt = {}; + + opt.v0.quantum = qdisc->sfq.quantum; + opt.v0.limit = qdisc->sfq.limit; + opt.v0.perturb_period = qdisc->sfq.perturb_period; + opt.v0.flows = qdisc->sfq.flows; + opt.v0.divisor = qdisc->sfq.divisor; + opt.depth = qdisc->sfq.depth; + + NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt); + } else if (nm_streq(qdisc->kind, "tbf")) { + struct tc_tbf_qopt opt = {}; + + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + opt.rate.rate = (qdisc->tbf.rate >= (1ULL << 32)) ? ~0U : (guint32) qdisc->tbf.rate; + if (qdisc->tbf.limit) + opt.limit = qdisc->tbf.limit; + else if (qdisc->tbf.latency) { + opt.limit = qdisc->tbf.rate * (double) qdisc->tbf.latency / PSCHED_TIME_UNITS_PER_SEC + + qdisc->tbf.burst; + } + + NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opt), &opt); + if (qdisc->tbf.rate >= (1ULL << 32)) + NLA_PUT_U64(msg, TCA_TBF_RATE64, qdisc->tbf.rate); + NLA_PUT_U32(msg, TCA_TBF_BURST, qdisc->tbf.burst); + + nla_nest_end(msg, tc_options); + } else if (nm_streq(qdisc->kind, "prio")) { + struct tc_prio_qopt opt = {3, {1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}; + + NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt); + } else { + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + if (nm_streq(qdisc->kind, "fq_codel")) { + if (qdisc->fq_codel.limit) + NLA_PUT_U32(msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit); + if (qdisc->fq_codel.flows) + NLA_PUT_U32(msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows); + if (qdisc->fq_codel.target) + NLA_PUT_U32(msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target); + if (qdisc->fq_codel.interval) + NLA_PUT_U32(msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval); + if (qdisc->fq_codel.quantum) + NLA_PUT_U32(msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum); + if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) + NLA_PUT_U32(msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold); + if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET) + NLA_PUT_U32(msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit); + if (qdisc->fq_codel.ecn) + NLA_PUT_U32(msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn); + } + + nla_nest_end(msg, tc_options); + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tfilter) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * tc_options; + struct nlattr * act_tab; + const struct tcmsg tcm = { + .tcm_family = tfilter->addr_family, + .tcm_ifindex = tfilter->ifindex, + .tcm_handle = tfilter->handle, + .tcm_parent = tfilter->parent, + .tcm_info = tfilter->info, + }; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO); + + if (nlmsg_append_struct(msg, &tcm) < 0) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_KIND, tfilter->kind); + + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + if (!(act_tab = nla_nest_start(msg, TCA_OPTIONS))) // 3 TCA_ACT_KIND TCA_ACT_KIND + goto nla_put_failure; + + if (tfilter->action.kind) { + const NMPlatformAction *action = &tfilter->action; + struct nlattr * prio; + struct nlattr * act_options; + + if (!(prio = nla_nest_start(msg, 1 /* priority */))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_ACT_KIND, action->kind); + + if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + const NMPlatformActionSimple *simple = &action->simple; + struct tc_defact sel = { + 0, + }; + + if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS))) + goto nla_put_failure; + + NLA_PUT(msg, TCA_DEF_PARMS, sizeof(sel), &sel); + NLA_PUT(msg, TCA_DEF_DATA, sizeof(simple->sdata), simple->sdata); + + nla_nest_end(msg, act_options); + + } else if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + const NMPlatformActionMirred *mirred = &action->mirred; + struct tc_mirred sel = { + 0, + }; + + if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS))) + goto nla_put_failure; + + if (mirred->egress && mirred->redirect) + sel.eaction = TCA_EGRESS_REDIR; + else if (mirred->egress && mirred->mirror) + sel.eaction = TCA_EGRESS_MIRROR; + else if (mirred->ingress && mirred->redirect) + sel.eaction = TCA_INGRESS_REDIR; + else if (mirred->ingress && mirred->mirror) + sel.eaction = TCA_INGRESS_MIRROR; + sel.ifindex = mirred->ifindex; + + NLA_PUT(msg, TCA_MIRRED_PARMS, sizeof(sel), &sel); + + nla_nest_end(msg, act_options); + } + + nla_nest_end(msg, prio); + } + + nla_nest_end(msg, tc_options); + + nla_nest_end(msg, act_tab); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +/*****************************************************************************/ + +static struct nl_sock * +_genl_sock(NMLinuxPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return priv->genl; +} + +#define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \ + G_STMT_START \ + { \ + const char *const _pathid = (pathid); \ + const int _dirfd = (dirfd); \ + const char *const _path = (path); \ + \ + nm_assert(_path &&_path[0]); \ + g_assert(!strstr(_path, "/../")); \ + if (_dirfd < 0) { \ + nm_assert(!_pathid); \ + nm_assert(_path[0] == '/'); \ + nm_assert(g_str_has_prefix(_path, "/proc/sys/") || g_str_has_prefix(_path, "/sys/") \ + || g_str_has_prefix(_path, "/proc/net")); \ + } else { \ + nm_assert(_pathid &&_pathid[0] && _pathid[0] != '/'); \ + nm_assert(_path[0] != '/'); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +/* core sysctl-set functions can be called from a non-main thread. + * Hence, we require locking from nm-logging. Indicate that by + * setting NM_THREAD_SAFE_ON_MAIN_THREAD to zero. */ +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 0 + +static void +_log_dbg_sysctl_set_impl(NMPlatform *platform, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + GError * error = NULL; + gs_free char *contents = NULL; + gs_free char *value_escaped = g_strescape(value, NULL); + + if (!nm_utils_file_get_contents(dirfd, + path, + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + &error)) { + _LOGD("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", + pathid ?: path, + value_escaped, + error->message); + g_clear_error(&error); + return; + } + + g_strstrip(contents); + if (nm_streq(contents, value)) + _LOGD("sysctl: setting '%s' to '%s' (current value is identical)", + pathid ?: path, + value_escaped); + else { + gs_free char *contents_escaped = g_strescape(contents, NULL); + + _LOGD("sysctl: setting '%s' to '%s' (current value is '%s')", + pathid ?: path, + value_escaped, + contents_escaped); + } +} + +#define _log_dbg_sysctl_set(platform, pathid, dirfd, path, value) \ + G_STMT_START \ + { \ + if (_LOGD_ENABLED()) { \ + _log_dbg_sysctl_set_impl(platform, pathid, dirfd, path, value); \ + } \ + } \ + G_STMT_END + +static gboolean +sysctl_set_internal(NMPlatform *platform, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + int fd, tries; + gssize nwrote; + gssize len; + char * actual; + gs_free char *actual_free = NULL; + int errsv; + + if (dirfd < 0) { + pathid = path; + + fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC); + if (fd == -1) { + errsv = errno; + if (errsv == ENOENT) { + _LOGD("sysctl: failed to open '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } else { + _LOGE("sysctl: failed to open '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } + errno = errsv; + return FALSE; + } + } else { + fd = openat(dirfd, path, O_WRONLY | O_TRUNC | O_CLOEXEC); + if (fd == -1) { + errsv = errno; + if (errsv == ENOENT) { + _LOGD("sysctl: failed to openat '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } else { + _LOGE("sysctl: failed to openat '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } + errno = errsv; + return FALSE; + } + } + + _log_dbg_sysctl_set(platform, pathid, dirfd, path, value); + + /* Most sysfs and sysctl options don't care about a trailing LF, while some + * (like infiniband) do. So always add the LF. Also, neither sysfs nor + * sysctl support partial writes so the LF must be added to the string we're + * about to write. + */ + len = strlen(value) + 1; + nm_assert(len > 0); + if (len > 512) + actual = actual_free = g_malloc(len + 1); + else + actual = g_alloca(len + 1); + memcpy(actual, value, len - 1); + actual[len - 1] = '\n'; + actual[len] = '\0'; + + /* Try to write the entire value three times if a partial write occurs */ + errsv = 0; + for (tries = 0, nwrote = 0; tries < 3 && nwrote < len - 1; tries++) { + nwrote = write(fd, actual, len); + if (nwrote == -1) { + errsv = errno; + if (errsv == EINTR) { + _LOGD("sysctl: interrupted, will try again"); + continue; + } + break; + } + } + if (nwrote == -1) { + NMLogLevel level = LOGL_ERR; + + if (errsv == EEXIST) { + level = LOGL_DEBUG; + } else if (errsv == EINVAL + && nm_utils_sysctl_ip_conf_is_path(AF_INET6, path, NULL, "mtu")) { + /* setting the MTU can fail under regular conditions. Suppress + * logging a warning. */ + level = LOGL_DEBUG; + } + + _NMLOG(level, + "sysctl: failed to set '%s' to '%s': (%d) %s", + path, + value, + errsv, + nm_strerror_native(errsv)); + } else if (nwrote < len - 1) { + _LOGE("sysctl: failed to set '%s' to '%s' after three attempts", path, value); + } + + if (nwrote < len - 1) { + if (nm_close(fd) != 0) { + if (errsv != 0) + errno = errsv; + } else if (errsv != 0) + errno = errsv; + else + errno = EIO; + return FALSE; + } + if (nm_close(fd) != 0) { + /* errno is already properly set. */ + return FALSE; + } + + /* success. errno is undefined (no need to set). */ + return TRUE; +} + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + +/*****************************************************************************/ + +static gboolean +sysctl_set(NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(value, FALSE); + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd < 0 && !nm_platform_netns_push(platform, &netns)) { + errno = ENETDOWN; + return FALSE; + } + + return sysctl_set_internal(platform, pathid, dirfd, path, value); +} + +typedef struct { + NMPlatform * platform; + char * pathid; + int dirfd; + char * path; + char ** values; + GCancellable * cancellable; + NMPlatformAsyncCallback callback; + gpointer callback_data; +} SysctlAsyncInfo; + +static void +sysctl_async_info_free(SysctlAsyncInfo *info) +{ + g_object_unref(info->platform); + g_free(info->pathid); + if (info->dirfd >= 0) + nm_close(info->dirfd); + g_free(info->path); + g_strfreev(info->values); + g_object_unref(info->cancellable); + g_slice_free(SysctlAsyncInfo, info); +} + +static void +sysctl_async_cb(GObject *object, GAsyncResult *res, gpointer user_data) +{ + NMPlatform * platform; + GTask * task = G_TASK(res); + SysctlAsyncInfo *info; + gs_free_error GError *error = NULL; + gs_free char * values_str = NULL; + + info = g_task_get_task_data(task); + + if (g_task_propagate_boolean(task, &error)) { + platform = info->platform; + _LOGD("sysctl: successfully set-async '%s' to values '%s'", + info->pathid ?: info->path, + (values_str = g_strjoinv(", ", info->values))); + } + + if (info->callback) + info->callback(error, info->callback_data); +} + +static void +sysctl_async_thread_fn(GTask * task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + SysctlAsyncInfo * info = task_data; + GError * error = NULL; + char ** value; + + if (g_task_return_error_if_cancelled(task)) + return; + + if (info->dirfd < 0 && !nm_platform_netns_push(info->platform, &netns)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failed changing namespace"); + g_task_return_error(task, error); + return; + } + + for (value = info->values; *value; value++) { + if (!sysctl_set_internal(info->platform, info->pathid, info->dirfd, info->path, *value)) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failed setting '%s' to value '%s': %s", + info->pathid ?: info->path, + *value, + nm_strerror_native(errno)); + g_task_return_error(task, error); + return; + } + if (g_task_return_error_if_cancelled(task)) + return; + } + g_task_return_boolean(task, TRUE); +} + +static void +sysctl_set_async_return_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_free_error GError *cancelled_error = NULL; + gs_free_error GError * error = NULL; + NMPlatformAsyncCallback callback; + gpointer callback_data; + + nm_utils_user_data_unpack(user_data, &platform, &callback, &callback_data, &error); + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + callback(cancelled_error ?: error, callback_data); +} + +static void +sysctl_set_async(NMPlatform * platform, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + SysctlAsyncInfo *info; + GTask * task; + int dirfd_dup, errsv; + gpointer packed; + GError * error = NULL; + + g_return_if_fail(platform); + g_return_if_fail(path); + g_return_if_fail(values && values[0]); + g_return_if_fail(cancellable); + g_return_if_fail(!data || callback); + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd >= 0) { + dirfd_dup = fcntl(dirfd, F_DUPFD_CLOEXEC, 0); + if (dirfd_dup < 0) { + if (!callback) + return; + errsv = errno; + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failure duplicating directory fd: %s", + nm_strerror_native(errsv)); + packed = nm_utils_user_data_pack(g_object_ref(platform), callback, data, error); + nm_utils_invoke_on_idle(cancellable, sysctl_set_async_return_idle, packed); + return; + } + } else + dirfd_dup = -1; + + info = g_slice_new0(SysctlAsyncInfo); + info->platform = g_object_ref(platform); + info->pathid = g_strdup(pathid); + info->dirfd = dirfd_dup; + info->path = g_strdup(path); + info->values = g_strdupv((char **) values); + info->callback = callback; + info->callback_data = data; + info->cancellable = g_object_ref(cancellable); + + task = g_task_new(platform, cancellable, sysctl_async_cb, NULL); + g_task_set_task_data(task, info, (GDestroyNotify) sysctl_async_info_free); + g_task_set_return_on_cancel(task, FALSE); + g_task_run_in_thread(task, sysctl_async_thread_fn); + g_object_unref(task); +} + +static GSList *sysctl_clear_cache_list; + +void +_nm_logging_clear_platform_logging_cache(void) +{ + while (sysctl_clear_cache_list) { + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(sysctl_clear_cache_list->data); + + sysctl_clear_cache_list = + g_slist_delete_link(sysctl_clear_cache_list, sysctl_clear_cache_list); + + g_hash_table_destroy(priv->sysctl_get_prev_values); + priv->sysctl_get_prev_values = NULL; + } +} + +typedef struct { + const char *path; + CList lst; + char * value; + char path_data[]; +} SysctlCacheEntry; + +static void +sysctl_cache_entry_free(SysctlCacheEntry *entry) +{ + c_list_unlink_stale(&entry->lst); + g_free(entry->value); + g_free(entry); +} + +static void +_log_dbg_sysctl_get_impl(NMPlatform *platform, const char *pathid, const char *contents) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + SysctlCacheEntry * entry = NULL; + + if (!priv->sysctl_get_prev_values) { + sysctl_clear_cache_list = g_slist_prepend(sysctl_clear_cache_list, platform); + c_list_init(&priv->sysctl_list); + priv->sysctl_get_prev_values = + g_hash_table_new_full(nm_pstr_hash, + nm_pstr_equal, + (GDestroyNotify) sysctl_cache_entry_free, + NULL); + } else + entry = g_hash_table_lookup(priv->sysctl_get_prev_values, &pathid); + + if (entry) { + if (!nm_streq(entry->value, contents)) { + gs_free char *contents_escaped = g_strescape(contents, NULL); + gs_free char *prev_value_escaped = g_strescape(entry->value, NULL); + + _LOGD("sysctl: reading '%s': '%s' (changed from '%s' on last read)", + pathid, + contents_escaped, + prev_value_escaped); + g_free(entry->value); + entry->value = g_strdup(contents); + } + nm_c_list_move_front(&priv->sysctl_list, &entry->lst); + } else { + gs_free char * contents_escaped = g_strescape(contents, NULL); + SysctlCacheEntry *old; + size_t len; + + len = strlen(pathid); + entry = g_malloc(sizeof(SysctlCacheEntry) + len + 1); + entry->value = g_strdup(contents); + entry->path = entry->path_data; + memcpy(entry->path_data, pathid, len + 1); + + /* Remove oldest entry when the cache becomes too big */ + if (g_hash_table_size(priv->sysctl_get_prev_values) > 1000) { + old = c_list_last_entry(&priv->sysctl_list, SysctlCacheEntry, lst); + g_hash_table_remove(priv->sysctl_get_prev_values, old); + } + + _LOGD("sysctl: reading '%s': '%s'", pathid, contents_escaped); + + g_hash_table_add(priv->sysctl_get_prev_values, entry); + c_list_link_front(&priv->sysctl_list, &entry->lst); + } +} + +#define _log_dbg_sysctl_get(platform, pathid, contents) \ + G_STMT_START \ + { \ + if (_LOGD_ENABLED()) \ + _log_dbg_sysctl_get_impl(platform, pathid, contents); \ + } \ + G_STMT_END + +static char * +sysctl_get(NMPlatform *platform, const char *pathid, int dirfd, const char *path) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + GError * error = NULL; + gs_free char * contents = NULL; + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd < 0) { + if (!nm_platform_netns_push(platform, &netns)) { + errno = EBUSY; + return NULL; + } + pathid = path; + } + + if (!nm_utils_file_get_contents(dirfd, + path, + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + &error)) { + NMLogLevel log_level = LOGL_ERR; + int errsv = EBUSY; + + if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + errsv = ENOENT; + log_level = LOGL_DEBUG; + } else if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NODEV) + || g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) { + /* We assume FAILED means EOPNOTSUP and don't log a error message. */ + log_level = LOGL_DEBUG; + } + + _NMLOG(log_level, "error reading %s: %s", pathid, error->message); + g_clear_error(&error); + errno = errsv; + return NULL; + } + + g_strstrip(contents); + + _log_dbg_sysctl_get(platform, pathid, contents); + + /* errno is left undefined (as we don't return NULL). */ + return g_steal_pointer(&contents); +} + +/*****************************************************************************/ + +static void +process_events(NMPlatform *platform) +{ + delayed_action_handle_all(platform, TRUE); +} + +/*****************************************************************************/ + +static const RefreshAllInfo * +refresh_all_type_get_info(RefreshAllType refresh_all_type) +{ + static const RefreshAllInfo infos[] = { +#define R(_refresh_all_type, _obj_type, _addr_family) \ + [_refresh_all_type] = { \ + .obj_type = _obj_type, \ + .addr_family = _addr_family, \ + } + R(REFRESH_ALL_TYPE_LINKS, NMP_OBJECT_TYPE_LINK, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP4_ROUTES, NMP_OBJECT_TYPE_IP4_ROUTE, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP6_ROUTES, NMP_OBJECT_TYPE_IP6_ROUTE, AF_UNSPEC), + R(REFRESH_ALL_TYPE_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET), + R(REFRESH_ALL_TYPE_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6), + R(REFRESH_ALL_TYPE_QDISCS, NMP_OBJECT_TYPE_QDISC, AF_UNSPEC), + R(REFRESH_ALL_TYPE_TFILTERS, NMP_OBJECT_TYPE_TFILTER, AF_UNSPEC), +#undef R + }; + + nm_assert(_NM_INT_NOT_NEGATIVE(refresh_all_type)); + nm_assert(refresh_all_type < G_N_ELEMENTS(infos)); + nm_assert(nmp_class_from_type(infos[refresh_all_type].obj_type)); + + return &infos[refresh_all_type]; +} + +static NM_UTILS_LOOKUP_DEFINE( + delayed_action_type_to_refresh_all_type, + DelayedActionType, + RefreshAllType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, REFRESH_ALL_TYPE_LINKS), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, + REFRESH_ALL_TYPE_IP4_ADDRESSES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, + REFRESH_ALL_TYPE_IP6_ADDRESSES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, REFRESH_ALL_TYPE_IP4_ROUTES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, REFRESH_ALL_TYPE_IP6_ROUTES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, + REFRESH_ALL_TYPE_ROUTING_RULES_IP4), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + REFRESH_ALL_TYPE_ROUTING_RULES_IP6), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, REFRESH_ALL_TYPE_QDISCS), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, REFRESH_ALL_TYPE_TFILTERS), + NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); + +static DelayedActionType +delayed_action_type_from_refresh_all_type(RefreshAllType refresh_all_type) +{ + DelayedActionType t; + + nm_assert(refresh_all_type_get_info(refresh_all_type)); + + t = (((DelayedActionType) 1) << refresh_all_type); + + nm_assert(refresh_all_type == delayed_action_type_to_refresh_all_type(t)); + + return t; +} + +static RefreshAllType +refresh_all_type_from_needle_object(const NMPObject *obj_needle) +{ + switch (NMP_OBJECT_GET_TYPE(obj_needle)) { + case NMP_OBJECT_TYPE_LINK: + return REFRESH_ALL_TYPE_LINKS; + case NMP_OBJECT_TYPE_IP4_ADDRESS: + return REFRESH_ALL_TYPE_IP4_ADDRESSES; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return REFRESH_ALL_TYPE_IP6_ADDRESSES; + case NMP_OBJECT_TYPE_IP4_ROUTE: + return REFRESH_ALL_TYPE_IP4_ROUTES; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return REFRESH_ALL_TYPE_IP6_ROUTES; + case NMP_OBJECT_TYPE_QDISC: + return REFRESH_ALL_TYPE_QDISCS; + case NMP_OBJECT_TYPE_TFILTER: + return REFRESH_ALL_TYPE_TFILTERS; + case NMP_OBJECT_TYPE_ROUTING_RULE: + switch (NMP_OBJECT_CAST_ROUTING_RULE(obj_needle)->addr_family) { + case AF_INET: + return REFRESH_ALL_TYPE_ROUTING_RULES_IP4; + case AF_INET6: + return REFRESH_ALL_TYPE_ROUTING_RULES_IP6; + } + nm_assert_not_reached(); + return 0; + default: + nm_assert_not_reached(); + return 0; + } +} + +static const NMPLookup * +refresh_all_type_init_lookup(RefreshAllType refresh_all_type, NMPLookup *lookup) +{ + const RefreshAllInfo *refresh_all_info; + + nm_assert(lookup); + + refresh_all_info = refresh_all_type_get_info(refresh_all_type); + + nm_assert(refresh_all_info); + + if (NM_IN_SET(refresh_all_info->obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)) { + return nmp_lookup_init_object_by_addr_family(lookup, + refresh_all_info->obj_type, + refresh_all_info->addr_family); + } + + /* not yet implemented. */ + nm_assert(refresh_all_info->addr_family == AF_UNSPEC); + + return nmp_lookup_init_obj_type(lookup, refresh_all_info->obj_type); +} + +static DelayedActionType +delayed_action_refresh_from_needle_object(const NMPObject *obj_needle) +{ + return delayed_action_type_from_refresh_all_type( + refresh_all_type_from_needle_object(obj_needle)); +} + +static NM_UTILS_LOOKUP_STR_DEFINE( + delayed_action_to_string, + DelayedActionType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, "refresh-all-links"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, + "refresh-all-ip4-addresses"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, + "refresh-all-ip6-addresses"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, "refresh-all-ip4-routes"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, "refresh-all-ip6-routes"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, + "refresh-all-routing-rules-ip4"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + "refresh-all-routing-rules-ip6"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, "refresh-all-qdiscs"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, "refresh-all-tfilters"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_LINK, "refresh-link"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_MASTER_CONNECTED, "master-connected"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_NETLINK, "read-netlink"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, "wait-for-nl-response"), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_NONE), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL), + NM_UTILS_LOOKUP_ITEM_IGNORE(__DELAYED_ACTION_TYPE_MAX), ); + +static const char * +delayed_action_to_string_full(DelayedActionType action_type, + gpointer user_data, + char * buf, + gsize buf_size) +{ + char * buf0 = buf; + const DelayedActionWaitForNlResponseData *data; + + nm_utils_strbuf_append_str(&buf, &buf_size, delayed_action_to_string(action_type)); + switch (action_type) { + case DELAYED_ACTION_TYPE_MASTER_CONNECTED: + nm_utils_strbuf_append(&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT(user_data)); + break; + case DELAYED_ACTION_TYPE_REFRESH_LINK: + nm_utils_strbuf_append(&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT(user_data)); + break; + case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE: + data = user_data; + + if (data) { + gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_nsec(); + char b[255]; + + nm_utils_strbuf_append( + &buf, + &buf_size, + " (seq %u, timeout in %s%" G_GINT64_FORMAT ".%09" G_GINT64_FORMAT + ", response-type %d%s%s)", + data->seq_number, + timeout < 0 ? "-" : "", + (timeout < 0 ? -timeout : timeout) / NM_UTILS_NSEC_PER_SEC, + (timeout < 0 ? -timeout : timeout) % NM_UTILS_NSEC_PER_SEC, + (int) data->response_type, + data->seq_result ? ", " : "", + data->seq_result + ? wait_for_nl_response_to_string(data->seq_result, NULL, b, sizeof(b)) + : ""); + } else + nm_utils_strbuf_append_str(&buf, &buf_size, " (any)"); + break; + default: + nm_assert(!user_data); + break; + } + return buf0; +} + +#define _LOGt_delayed_action(action_type, user_data, operation) \ + G_STMT_START \ + { \ + char _buf[255]; \ + \ + _LOGt("delayed-action: %s %s", \ + "" operation, \ + delayed_action_to_string_full(action_type, user_data, _buf, sizeof(_buf))); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static gboolean +delayed_action_refresh_all_in_progress(NMPlatform *platform, DelayedActionType action_type) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + RefreshAllType refresh_all_type; + + nm_assert(nm_utils_is_power_of_two(action_type)); + nm_assert(NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_ALL)); + nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL)); + + if (NM_FLAGS_ANY(priv->delayed_action.flags, action_type)) + return TRUE; + + refresh_all_type = delayed_action_type_to_refresh_all_type(action_type); + return (priv->delayed_action.refresh_all_in_progress[refresh_all_type] > 0); +} + +static void +delayed_action_wait_for_nl_response_complete(NMPlatform * platform, + guint idx, + WaitForNlResponseResult seq_result) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + + nm_assert(NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + nm_assert(idx < priv->delayed_action.list_wait_for_nl_response->len); + nm_assert(seq_result); + + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + idx); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete"); + + if (priv->delayed_action.list_wait_for_nl_response->len <= 1) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE; + if (data->out_seq_result) + *data->out_seq_result = seq_result; + switch (data->response_type) { + case DELAYED_ACTION_RESPONSE_TYPE_VOID: + break; + case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS: + if (data->response.out_refresh_all_in_progress) { + nm_assert(*data->response.out_refresh_all_in_progress > 0); + *data->response.out_refresh_all_in_progress -= 1; + data->response.out_refresh_all_in_progress = NULL; + } + break; + case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET: + if (data->response.out_route_get) { + nm_assert(!*data->response.out_route_get); + data->response.out_route_get = NULL; + } + break; + } + + g_array_remove_index_fast(priv->delayed_action.list_wait_for_nl_response, idx); +} + +static void +delayed_action_wait_for_nl_response_complete_check(NMPlatform * platform, + WaitForNlResponseResult force_result, + guint32 * out_next_seq_number, + gint64 * out_next_timeout_abs_ns, + gint64 * p_now_ns) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + guint i; + guint32 next_seq_number = 0; + gint64 next_timeout_abs_ns = 0; + int now_ns = 0; + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len;) { + const DelayedActionWaitForNlResponseData *data = + &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->seq_result) + delayed_action_wait_for_nl_response_complete(platform, i, data->seq_result); + else if (p_now_ns + && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_nsec())) + >= data->timeout_abs_ns)) { + /* the caller can optionally check for timeout by providing a p_now_ns argument. */ + delayed_action_wait_for_nl_response_complete( + platform, + i, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT); + } else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) + delayed_action_wait_for_nl_response_complete(platform, i, force_result); + else { + if (next_seq_number == 0 || next_timeout_abs_ns > data->timeout_abs_ns) { + next_seq_number = data->seq_number; + next_timeout_abs_ns = data->timeout_abs_ns; + } + i++; + } + } + + if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) { + nm_assert( + !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + nm_assert(priv->delayed_action.list_wait_for_nl_response->len == 0); + } + + NM_SET_OUT(out_next_seq_number, next_seq_number); + NM_SET_OUT(out_next_timeout_abs_ns, next_timeout_abs_ns); + NM_SET_OUT(p_now_ns, now_ns); +} + +static void +delayed_action_wait_for_nl_response_complete_all(NMPlatform * platform, + WaitForNlResponseResult fallback_result) +{ + delayed_action_wait_for_nl_response_complete_check(platform, fallback_result, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +static void +delayed_action_handle_MASTER_CONNECTED(NMPlatform *platform, int master_ifindex) +{ + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCacheOpsType cache_op; + + cache_op = nmp_cache_update_link_master_connected(nm_platform_get_cache(platform), + master_ifindex, + &obj_old, + &obj_new); + if (cache_op == NMP_CACHE_OPS_UNCHANGED) + return; + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); +} + +static void +delayed_action_handle_REFRESH_LINK(NMPlatform *platform, int ifindex) +{ + do_request_link_no_delayed_actions(platform, ifindex, NULL); +} + +static void +delayed_action_handle_REFRESH_ALL(NMPlatform *platform, DelayedActionType flags) +{ + do_request_all_no_delayed_actions(platform, flags); +} + +static void +delayed_action_handle_READ_NETLINK(NMPlatform *platform) +{ + event_handler_read_netlink(platform, FALSE); +} + +static void +delayed_action_handle_WAIT_FOR_NL_RESPONSE(NMPlatform *platform) +{ + event_handler_read_netlink(platform, TRUE); +} + +static gboolean +delayed_action_handle_one(NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gpointer user_data; + + if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE) + return FALSE; + + /* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions. + * This type of action is entirely cache-internal and is here to resolve a + * cache inconsistency. It should be fixed right away. */ + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) { + nm_assert(priv->delayed_action.list_master_connected->len > 0); + + user_data = priv->delayed_action.list_master_connected->pdata[0]; + g_ptr_array_remove_index_fast(priv->delayed_action.list_master_connected, 0); + if (priv->delayed_action.list_master_connected->len == 0) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED; + nm_assert(_nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_master_connected->pdata, + priv->delayed_action.list_master_connected->len, + user_data) + < 0); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle"); + delayed_action_handle_MASTER_CONNECTED(platform, GPOINTER_TO_INT(user_data)); + return TRUE; + } + nm_assert(priv->delayed_action.list_master_connected->len == 0); + + /* Next we prefer read-netlink, because the buffer size is limited and we want to process events + * from netlink early. */ + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) { + _LOGt_delayed_action(DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle"); + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK; + delayed_action_handle_READ_NETLINK(platform); + return TRUE; + } + + if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) { + DelayedActionType flags, iflags; + + flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL; + + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL; + + if (_LOGt_ENABLED()) { + FOR_EACH_DELAYED_ACTION(iflags, flags) + _LOGt_delayed_action(iflags, NULL, "handle"); + } + + delayed_action_handle_REFRESH_ALL(platform, flags); + return TRUE; + } + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) { + nm_assert(priv->delayed_action.list_refresh_link->len > 0); + + user_data = priv->delayed_action.list_refresh_link->pdata[0]; + g_ptr_array_remove_index_fast(priv->delayed_action.list_refresh_link, 0); + if (priv->delayed_action.list_refresh_link->len == 0) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK; + nm_assert(_nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_refresh_link->pdata, + priv->delayed_action.list_refresh_link->len, + user_data) + < 0); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle"); + + delayed_action_handle_REFRESH_LINK(platform, GPOINTER_TO_INT(user_data)); + + return TRUE; + } + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle"); + delayed_action_handle_WAIT_FOR_NL_RESPONSE(platform); + return TRUE; + } + + return FALSE; +} + +static gboolean +delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gboolean any = FALSE; + + g_return_val_if_fail(priv->delayed_action.is_handling == 0, FALSE); + + priv->delayed_action.is_handling++; + if (read_netlink) + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL); + while (delayed_action_handle_one(platform)) + any = TRUE; + priv->delayed_action.is_handling--; + + cache_prune_all(platform); + + return any; +} + +static void +delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionType iflags; + + nm_assert(action_type != DELAYED_ACTION_TYPE_NONE); + + switch (action_type) { + case DELAYED_ACTION_TYPE_REFRESH_LINK: + if (_nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_refresh_link->pdata, + priv->delayed_action.list_refresh_link->len, + user_data) + < 0) + g_ptr_array_add(priv->delayed_action.list_refresh_link, user_data); + break; + case DELAYED_ACTION_TYPE_MASTER_CONNECTED: + if (_nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_master_connected->pdata, + priv->delayed_action.list_master_connected->len, + user_data) + < 0) + g_ptr_array_add(priv->delayed_action.list_master_connected, user_data); + break; + case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE: + g_array_append_vals(priv->delayed_action.list_wait_for_nl_response, user_data, 1); + break; + default: + nm_assert(!user_data); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + break; + } + + priv->delayed_action.flags |= action_type; + + if (_LOGt_ENABLED()) { + FOR_EACH_DELAYED_ACTION(iflags, action_type) + _LOGt_delayed_action(iflags, user_data, "schedule"); + } +} + +static void +delayed_action_schedule_WAIT_FOR_NL_RESPONSE(NMPlatform * platform, + guint32 seq_number, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + DelayedActionWaitForNlResponseData data = { + .seq_number = seq_number, + .timeout_abs_ns = + nm_utils_get_monotonic_timestamp_nsec() + (200 * (NM_UTILS_NSEC_PER_SEC / 1000)), + .out_seq_result = out_seq_result, + .out_errmsg = out_errmsg, + .response_type = response_type, + .response.out_data = response_out_data, + }; + + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, &data); +} + +/*****************************************************************************/ + +static void +cache_prune_one_type(NMPlatform *platform, const NMPLookup *lookup) +{ + NMDedupMultiIter iter; + const NMPObject *obj; + NMPCacheOpsType cache_op; + NMPCache * cache = nm_platform_get_cache(platform); + + nm_dedup_multi_iter_init(&iter, nmp_cache_lookup(cache, lookup)); + while (nm_dedup_multi_iter_next(&iter)) { + const NMDedupMultiEntry *main_entry; + + /* we only track the dirty flag for the OBJECT-TYPE index. That means, + * for other lookup types we need to check the dirty flag of the main-entry. */ + main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup); + if (!main_entry->dirty) + continue; + + obj = main_entry->obj; + + _LOGt("cache-prune: prune %s", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + + { + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + cache_op = nmp_cache_remove(cache, obj, TRUE, TRUE, &obj_old); + nm_assert(cache_op == NMP_CACHE_OPS_REMOVED); + cache_on_change(platform, cache_op, obj_old, NULL); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, NULL); + } + } +} + +static void +cache_prune_all(NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + RefreshAllType refresh_all_type; + + for (refresh_all_type = _REFRESH_ALL_TYPE_FIRST; refresh_all_type < _REFRESH_ALL_TYPE_NUM; + refresh_all_type++) { + NMPLookup lookup; + + if (priv->pruning[refresh_all_type] == 0) + continue; + priv->pruning[refresh_all_type] -= 1; + if (priv->pruning[refresh_all_type] > 0) + continue; + refresh_all_type_init_lookup(refresh_all_type, &lookup); + cache_prune_one_type(platform, &lookup); + } +} + +static void +cache_on_change(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ + const NMPClass *klass; + char str_buf[sizeof(_nm_utils_to_string_buffer)]; + char str_buf2[sizeof(_nm_utils_to_string_buffer)]; + NMPCache * cache = nm_platform_get_cache(platform); + + ASSERT_nmp_cache_ops(cache, cache_op, obj_old, obj_new); + nm_assert(cache_op != NMP_CACHE_OPS_UNCHANGED); + + klass = obj_old ? NMP_OBJECT_GET_CLASS(obj_old) : NMP_OBJECT_GET_CLASS(obj_new); + + _LOGt( + "update-cache-%s: %s: %s%s%s", + klass->obj_type_name, + (cache_op == NMP_CACHE_OPS_UPDATED ? "UPDATE" + : (cache_op == NMP_CACHE_OPS_REMOVED ? "REMOVE" + : (cache_op == NMP_CACHE_OPS_ADDED) ? "ADD" + : "???")), + (cache_op != NMP_CACHE_OPS_ADDED + ? nmp_object_to_string(obj_old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2)) + : nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2))), + (cache_op == NMP_CACHE_OPS_UPDATED) ? " -> " : "", + (cache_op == NMP_CACHE_OPS_UPDATED + ? nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof(str_buf)) + : "")); + + switch (klass->obj_type) { + case NMP_OBJECT_TYPE_LINK: + { + /* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */ + if (obj_old + && nmp_cache_link_connected_needs_toggle_by_ifindex(cache, + obj_old->link.master, + obj_new, + obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_old->link.master)); + if (obj_new && (!obj_old || obj_old->link.master != obj_new->link.master) + && nmp_cache_link_connected_needs_toggle_by_ifindex(cache, + obj_new->link.master, + obj_new, + obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_new->link.master)); + } + { + /* check whether we are about to change a master link that needs toggling connected state. */ + if (obj_new /* <-- nonsensical, make coverity happy */ + && nmp_cache_link_connected_needs_toggle(cache, obj_new, obj_new, obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_new->link.ifindex)); + } + { + int ifindex = 0; + + /* if we remove a link (from netlink), we must refresh the addresses, routes, qdiscs and tfilters */ + if (cache_op == NMP_CACHE_OPS_REMOVED + && obj_old /* <-- nonsensical, make coverity happy */) + ifindex = obj_old->link.ifindex; + else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && !obj_new->_link.netlink.is_in_netlink + && obj_new->_link.netlink.is_in_netlink + != obj_old->_link.netlink.is_in_netlink) + ifindex = obj_new->link.ifindex; + + if (ifindex > 0) { + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + } + } + { + int ifindex = -1; + + /* removal of a link could be caused by moving the link to another netns. + * In this case, we potentially have to update other links that have this link as parent. + * Currently, kernel misses to sent us a notification in this case + * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */ + + if (cache_op == NMP_CACHE_OPS_REMOVED + && obj_old /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink) + ifindex = obj_old->link.ifindex; + else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink + && !obj_new->_link.netlink.is_in_netlink) + ifindex = obj_new->link.ifindex; + + if (ifindex > 0) { + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink *l; + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + nmp_cache_iter_for_each_link (&iter, nmp_cache_lookup(cache, &lookup), &l) { + if (l->parent == ifindex) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(l->ifindex)); + } + } + } + { + /* if a link goes down, we must refresh routes */ + if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink && obj_new->_link.netlink.is_in_netlink + && ((NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_UP) + && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_UP)) + || (NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP) + && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP)))) { + /* FIXME: I suspect that IFF_LOWER_UP must not be considered, and I + * think kernel does send RTM_DELROUTE events for IPv6 routes, so + * we might not need to refresh IPv6 routes. */ + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, + NULL); + } + } + if (NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED) + && (obj_new && obj_new->_link.netlink.is_in_netlink) + && (!obj_old || !obj_old->_link.netlink.is_in_netlink)) { + gboolean re_request_link = FALSE; + const NMPlatformLnkTun *lnk_tun; + + if (!obj_new->_link.netlink.lnk + && NM_IN_SET(obj_new->link.type, + NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP, + NM_LINK_TYPE_INFINIBAND, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_TUN, + NM_LINK_TYPE_VLAN, + NM_LINK_TYPE_VXLAN)) { + /* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that + * kernel didn't send this notification, thus when we first learn about a link + * that lacks an lnk_data we re-request it again. + * + * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_TUN && obj_new->_link.netlink.lnk + && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun) && !lnk_tun->persist + && lnk_tun->pi && !lnk_tun->vnet_hdr && !lnk_tun->multi_queue + && !lnk_tun->owner_valid && !lnk_tun->group_valid) { + /* kernel has/had a know issue that the first notification for TUN device would + * be sent with invalid parameters. The message looks like that kind, so refetch + * it. */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_VETH && obj_new->link.parent == 0) { + /* the initial notification when adding a veth pair can lack the parent/IFLA_LINK + * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827). + * Request it again. */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_ETHERNET + && obj_new->link.l_address.len == 0) { + /* Due to a kernel bug, we sometimes receive spurious NEWLINK + * messages after a wifi interface has disappeared. Since the + * link is not present anymore we can't determine its type and + * thus it will show up as a Ethernet one, with no address + * specified. Request the link again to check if it really + * exists. https://bugzilla.redhat.com/show_bug.cgi?id=1302037 + */ + re_request_link = TRUE; + } + if (re_request_link) { + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(obj_new->link.ifindex)); + } + } + { + /* on enslave/release, we also refresh the master. */ + int ifindex1 = 0, ifindex2 = 0; + gboolean changed_master, changed_connected; + + changed_master = + (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0 + ? obj_new->link.master + : 0) + != (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0 + ? obj_old->link.master + : 0); + changed_connected = (obj_new && obj_new->_link.netlink.is_in_netlink + ? NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP) + : 2) + != (obj_old && obj_old->_link.netlink.is_in_netlink + ? NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP) + : 2); + + if (changed_master || changed_connected) { + ifindex1 = + (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0) + ? obj_old->link.master + : 0; + ifindex2 = + (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0) + ? obj_new->link.master + : 0; + + if (ifindex1 > 0) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(ifindex1)); + if (ifindex2 > 0 && ifindex1 != ifindex2) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(ifindex2)); + } + } + break; + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + { + /* Address deletion is sometimes accompanied by route deletion. We need to + * check all routes belonging to the same interface. */ + if (cache_op == NMP_CACHE_OPS_REMOVED) { + delayed_action_schedule(platform, + (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS) + ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, + NULL); + } + } break; + default: + break; + } +} + +/*****************************************************************************/ + +static guint32 +_nlh_seq_next_get(NMLinuxPlatformPrivate *priv) +{ + /* generate a new sequence number, but never return zero. + * Wrapping numbers are not a problem, because we don't rely + * on strictly increasing sequence numbers. */ + return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next); +} + +/** + * _nl_send_nlmsghdr: + * @platform: + * @nlhdr: + * @out_seq_result: + * @response_type: + * @response_out_data: + * + * Returns: 0 on success or a negative errno. + */ +static int +_nl_send_nlmsghdr(NMPlatform * platform, + struct nlmsghdr * nlhdr, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + guint32 seq; + int errsv; + + nm_assert(nlhdr); + + seq = _nlh_seq_next_get(priv); + nlhdr->nlmsg_seq = seq; + + { + struct sockaddr_nl nladdr = { + .nl_family = AF_NETLINK, + }; + struct iovec iov = {.iov_base = nlhdr, .iov_len = nlhdr->nlmsg_len}; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int try_count; + + if (!nlhdr->nlmsg_pid) + nlhdr->nlmsg_pid = nl_socket_get_local_port(priv->nlh); + nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK); + + try_count = 0; +again: + errsv = sendmsg(nl_socket_get_fd(priv->nlh), &msg, 0); + if (errsv < 0) { + errsv = errno; + if (errsv == EINTR && try_count++ < 100) + goto again; + _LOGD("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", + nm_strerror_native(errsv), + errsv); + return -nm_errno_from_native(errsv); + } + } + + delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform, + seq, + out_seq_result, + out_errmsg, + response_type, + response_out_data); + return 0; +} + +/** + * _nl_send_nlmsg: + * @platform: + * @nlmsg: + * @out_seq_result: + * @response_type: + * @response_out_data: + * + * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno). + */ +static int +_nl_send_nlmsg(NMPlatform * platform, + struct nl_msg * nlmsg, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + struct nlmsghdr * nlhdr; + guint32 seq; + int nle; + + nlhdr = nlmsg_hdr(nlmsg); + seq = _nlh_seq_next_get(priv); + nlhdr->nlmsg_seq = seq; + + nle = nl_send_auto(priv->nlh, nlmsg); + if (nle < 0) { + _LOGD("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle); + return nle; + } + + delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform, + seq, + out_seq_result, + out_errmsg, + response_type, + response_out_data); + return 0; +} + +static void +do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int nle; + + if (name && !name[0]) + name = NULL; + + g_return_if_fail(ifindex > 0 || name); + + _LOGD("do-request-link: %d %s", ifindex, name ?: ""); + + if (ifindex > 0) { + const NMDedupMultiEntry *entry; + + entry = nmp_cache_lookup_entry_link(nm_platform_get_cache(platform), ifindex); + if (entry) { + priv->pruning[REFRESH_ALL_TYPE_LINKS] += 1; + nm_dedup_multi_entry_set_dirty(entry, TRUE); + } + } + + event_handler_read_netlink(platform, FALSE); + + nlmsg = _nl_msg_new_link(RTM_GETLINK, 0, ifindex, name); + if (nlmsg) { + nle = _nl_send_nlmsg(platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + if (nle < 0) { + _LOGE("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)", + ifindex, + name ?: "", + nm_strerror(nle), + -nle); + return; + } + } +} + +static void +do_request_link(NMPlatform *platform, int ifindex, const char *name) +{ + do_request_link_no_delayed_actions(platform, ifindex, name); + delayed_action_handle_all(platform, FALSE); +} + +static struct nl_msg * +_nl_msg_new_dump(NMPObjectType obj_type, int preferred_addr_family) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const NMPClass * klass; + + klass = nmp_class_from_type(obj_type); + + nm_assert(klass); + nm_assert(klass->rtm_gettype > 0); + + nlmsg = nlmsg_alloc_simple(klass->rtm_gettype, NLM_F_DUMP); + + if (klass->addr_family != AF_UNSPEC) { + /* if the class specifies a particular address family, then it is preferred. */ + nm_assert(NM_IN_SET(preferred_addr_family, AF_UNSPEC, klass->addr_family)); + preferred_addr_family = klass->addr_family; + } + + switch (klass->obj_type) { + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + { + const struct tcmsg tcmsg = { + .tcm_family = preferred_addr_family, + }; + + if (nlmsg_append_struct(nlmsg, &tcmsg) < 0) + g_return_val_if_reached(NULL); + } break; + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_ROUTING_RULE: + { + const struct rtgenmsg gmsg = { + .rtgen_family = preferred_addr_family, + }; + + if (nlmsg_append_struct(nlmsg, &gmsg) < 0) + g_return_val_if_reached(NULL); + } break; + default: + g_return_val_if_reached(NULL); + } + + return g_steal_pointer(&nlmsg); +} + +static void +do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionType action_type_prune; + DelayedActionType iflags; + + nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL)); + action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL; + + action_type_prune = action_type; + + /* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra + * cache lookup for every entry. + * + * Avoid that, by special casing routing-rules here. */ + if (NM_FLAGS_ALL(action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL)) { + NMPLookup lookup; + + priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP4] += 1; + priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP6] += 1; + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup); + action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL; + } + + FOR_EACH_DELAYED_ACTION(iflags, action_type_prune) + { + RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags); + NMPLookup lookup; + + priv->pruning[refresh_all_type] += 1; + refresh_all_type_init_lookup(refresh_all_type, &lookup); + nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup); + } + + FOR_EACH_DELAYED_ACTION(iflags, action_type) + { + RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags); + const RefreshAllInfo *refresh_all_info = refresh_all_type_get_info(refresh_all_type); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int * out_refresh_all_in_progress; + + out_refresh_all_in_progress = + &priv->delayed_action.refresh_all_in_progress[refresh_all_type]; + nm_assert(*out_refresh_all_in_progress >= 0); + *out_refresh_all_in_progress += 1; + + /* clear any delayed action that request a refresh of this object type. */ + priv->delayed_action.flags &= ~iflags; + _LOGt_delayed_action(iflags, NULL, "handle (do-request-all)"); + + if (refresh_all_type == REFRESH_ALL_TYPE_LINKS) { + nm_assert( + (priv->delayed_action.list_refresh_link->len > 0) + == NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)); + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) { + _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK, + NULL, + "clear (do-request-all)"); + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK; + g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0); + } + } + + event_handler_read_netlink(platform, FALSE); + + nlmsg = _nl_msg_new_dump(refresh_all_info->obj_type, refresh_all_info->addr_family); + if (!nlmsg) + goto next_after_fail; + + if (_nl_send_nlmsg(platform, + nlmsg, + NULL, + NULL, + DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, + out_refresh_all_in_progress) + < 0) + goto next_after_fail; + + continue; + +next_after_fail: + nm_assert(*out_refresh_all_in_progress > 0); + *out_refresh_all_in_progress -= 1; + } +} + +static void +do_request_one_type_by_needle_object(NMPlatform *platform, const NMPObject *obj_needle) +{ + do_request_all_no_delayed_actions(platform, + delayed_action_refresh_from_needle_object(obj_needle)); + delayed_action_handle_all(platform, FALSE); +} + +static void +event_seq_check_refresh_all(NMPlatform *platform, guint32 seq_number) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + guint i; + + if (NM_IN_SET(seq_number, 0, priv->nlh_seq_last_seen)) + return; + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS + && data->response.out_refresh_all_in_progress + && data->seq_number == priv->nlh_seq_last_seen) { + *data->response.out_refresh_all_in_progress -= 1; + data->response.out_refresh_all_in_progress = NULL; + break; + } + } + } + + priv->nlh_seq_last_seen = seq_number; +} + +static void +event_seq_check(NMPlatform * platform, + guint32 seq_number, + WaitForNlResponseResult seq_result, + const char * msg) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + guint i; + + if (seq_number == 0) + return; + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->seq_number == seq_number) { + /* We potentially receive many parts partial responses for the same sequence number. + * Thus, we only remember the result, and collect it later. */ + if (data->seq_result < 0) { + /* we already saw an error for this sequence number. + * Preserve it. */ + } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN + || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) + data->seq_result = seq_result; + if (data->out_errmsg && !*data->out_errmsg) + *data->out_errmsg = g_strdup(msg); + return; + } + } + } + +#if NM_MORE_LOGGING + if (seq_number != priv->nlh_seq_last_handled) + _LOGt("netlink: recvmsg: unwaited sequence number %u", seq_number); + priv->nlh_seq_last_handled = seq_number; +#endif +} + +static void +event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events) +{ + NMLinuxPlatformPrivate *priv; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPCacheOpsType cache_op; + struct nlmsghdr * msghdr; + char buf_nlmsghdr[400]; + gboolean is_del = FALSE; + gboolean is_dump = FALSE; + NMPCache * cache = nm_platform_get_cache(platform); + + msghdr = nlmsg_hdr(msg); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) + && msghdr->nlmsg_type == RTM_NEWADDR) { + /* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6, + * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */ + if (nlmsg_valid_hdr(msghdr, sizeof(struct ifaddrmsg)) + && NM_IN_SET(((struct ifaddrmsg *) nlmsg_data(msghdr))->ifa_family, + AF_INET, + AF_INET6)) { + /* see if the nl_msg contains the IFA_FLAGS attribute. If it does, + * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR + * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14, + * dated 30 March, 2014. + * + * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy + * way to detect kernel support. */ + _nm_platform_kernel_support_init( + NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS, + !!nlmsg_find_attr(msghdr, sizeof(struct ifaddrmsg), IFA_FLAGS) ? 1 : -1); + } + } + + if (!handle_events) + return; + + if (NM_IN_SET(msghdr->nlmsg_type, + RTM_DELLINK, + RTM_DELADDR, + RTM_DELROUTE, + RTM_DELRULE, + RTM_DELQDISC, + RTM_DELTFILTER)) { + /* The event notifies about a deleted object. We don't need to initialize all + * fields of the object. */ + is_del = TRUE; + } + + obj = nmp_object_new_from_nl(platform, cache, msg, is_del); + if (!obj) { + _LOGT("event-notification: %s: ignore", + nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr))); + return; + } + + if (!is_del + && NM_IN_SET(msghdr->nlmsg_type, + RTM_NEWADDR, + RTM_NEWLINK, + RTM_NEWROUTE, + RTM_NEWRULE, + RTM_NEWQDISC, + RTM_NEWTFILTER)) { + is_dump = + delayed_action_refresh_all_in_progress(platform, + delayed_action_refresh_from_needle_object(obj)); + } + + _LOGT("event-notification: %s%s: %s", + nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)), + is_dump ? ", in-dump" : "", + nmp_object_to_string(obj, + is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, + NULL, + 0)); + + { + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + + switch (msghdr->nlmsg_type) { + case RTM_GETLINK: + case RTM_NEWADDR: + case RTM_NEWLINK: + case RTM_NEWQDISC: + case RTM_NEWRULE: + case RTM_NEWTFILTER: + cache_op = nmp_cache_update_netlink(cache, obj, is_dump, &obj_old, &obj_new); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + break; + + case RTM_NEWROUTE: + { + nm_auto_nmpobj const NMPObject *obj_replace = NULL; + gboolean resync_required = FALSE; + gboolean only_dirty = FALSE; + gboolean is_ipv6; + + /* IPv4 routes that are a response to RTM_GETROUTE must have + * the cloned flag while IPv6 routes don't have to. */ + is_ipv6 = NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP6_ROUTE; + if (is_ipv6 || NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED)) { + nm_assert(is_ipv6 || !nmp_object_is_alive(obj)); + priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + if (NM_FLAGS_HAS(priv->delayed_action.flags, + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + guint i; + + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + DelayedActionWaitForNlResponseData *data = + &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET + && data->response.out_route_get) { + nm_assert(!*data->response.out_route_get); + if (data->seq_number == nlmsg_hdr(msg)->nlmsg_seq) { + *data->response.out_route_get = nmp_object_clone(obj, FALSE); + data->response.out_route_get = NULL; + break; + } + } + } + } + } + + cache_op = nmp_cache_update_netlink_route(cache, + obj, + is_dump, + msghdr->nlmsg_flags, + &obj_old, + &obj_new, + &obj_replace, + &resync_required); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + if (obj_replace) { + const NMDedupMultiEntry *entry_replace; + + /* we found an object that is to be replaced by the RTM_NEWROUTE message. + * While we invoke the signal, the platform cache might change and invalidate + * the findings. Mitigate that (for the most part), by marking the entry as + * dirty and only delete @obj_replace if it is still dirty afterwards. + * + * Yes, there is a tiny tiny chance for still getting it wrong. But in practice, + * the signal handlers do not cause to call the platform again, so the cache + * is not really changing. -- if they would, it would anyway be dangerous to overflow + * the stack and it's not ensured that the processing of netlink messages is + * reentrant (maybe it is). + */ + entry_replace = nmp_cache_lookup_entry(cache, obj_replace); + nm_assert(entry_replace && entry_replace->obj == obj_replace); + nm_dedup_multi_entry_set_dirty(entry_replace, TRUE); + only_dirty = TRUE; + } + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + + if (obj_replace) { + /* the RTM_NEWROUTE message indicates that another route was replaced. + * Remove it now. */ + cache_op = nmp_cache_remove(cache, obj_replace, TRUE, only_dirty, NULL); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_assert(cache_op == NMP_CACHE_OPS_REMOVED); + cache_on_change(platform, cache_op, obj_replace, NULL); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_replace, NULL); + } + } + + if (resync_required) { + /* we'd like to avoid such resyncs as they are expensive and we should only rely on the + * netlink events. This needs investigation. */ + _LOGT("schedule resync of routes after RTM_NEWROUTE"); + delayed_action_schedule(platform, + delayed_action_refresh_from_needle_object(obj), + NULL); + } + break; + } + + case RTM_DELADDR: + case RTM_DELLINK: + case RTM_DELQDISC: + case RTM_DELROUTE: + case RTM_DELRULE: + case RTM_DELTFILTER: + cache_op = nmp_cache_remove_netlink(cache, obj, &obj_old, &obj_new); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + break; + default: + break; + } + } +} + +/*****************************************************************************/ + +static int +do_add_link_with_lookup(NMPlatform * platform, + NMLinkType link_type, + const char * name, + struct nl_msg * nlmsg, + const NMPlatformLink **out_link) +{ + const NMPObject * obj = NULL; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + NMPCache * cache = nm_platform_get_cache(platform); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)", + name, + nm_link_type_to_string(link_type), + nm_strerror(nle), + -nle); + NM_SET_OUT(out_link, NULL); + return nle; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-link[%s/%s]: %s", + name, + nm_link_type_to_string(link_type), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (out_link) { + obj = nmp_cache_lookup_link_full(cache, 0, name, FALSE, link_type, NULL, NULL); + *out_link = NMP_OBJECT_CAST_LINK(obj); + } + + return wait_for_nl_response_to_nmerr(seq_result); +} + +static int +do_add_addrroute(NMPlatform * platform, + const NMPObject *obj_id, + struct nl_msg * nlmsg, + gboolean suppress_netlink_failure) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + nm_strerror(nle), + -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK + || (suppress_netlink_failure && seq_result < 0)) + ? LOGL_DEBUG + : LOGL_WARN, + "do-add-%s[%s]: %s", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (NMP_OBJECT_GET_TYPE(obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) { + /* In rare cases, the object is not yet ready as we received the ACK from + * kernel. Need to refetch. + * + * We want to safe the expensive refetch, thus we look first into the cache + * whether the object exists. + * + * rh#1484434 */ + if (!nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id)) + do_request_one_type_by_needle_object(platform, obj_id); + } + + return wait_for_nl_response_to_nmerr(seq_result); +} + +static gboolean +do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + gboolean success; + const char * log_detail = ""; + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + nm_strerror(nle), + -nle); + return FALSE; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + success = TRUE; + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + /* ok */ + } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT)) + log_detail = ", meaning the object was already removed"; + else if (NM_IN_SET(-((int) seq_result), ENXIO) + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) { + /* On RHEL7 kernel, deleting a non existing address fails with ENXIO */ + log_detail = ", meaning the address was already removed"; + } else if (NM_IN_SET(-((int) seq_result), EADDRNOTAVAIL) + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS)) + log_detail = ", meaning the address was already removed"; + else + success = FALSE; + + _NMLOG(success ? LOGL_DEBUG : LOGL_WARN, + "do-delete-%s[%s]: %s%s", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)), + log_detail); + + if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER)) { + /* In rare cases, the object is still there after we receive the ACK from + * kernel. Need to refetch. + * + * We want to safe the expensive refetch, thus we look first into the cache + * whether the object exists. + * + * rh#1484434 */ + if (nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id)) + do_request_one_type_by_needle_object(platform, obj_id); + } + + return success; +} + +static int +do_change_link(NMPlatform * platform, + ChangeLinkType change_link_type, + int ifindex, + struct nl_msg * nlmsg, + const ChangeLinkData *data) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + int nle; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + char s_buf[256]; + int result = 0; + NMLogLevel log_level = LOGL_DEBUG; + const char * log_result = "failure"; + const char * log_detail = ""; + gs_free char * log_detail_free = NULL; + const NMPObject * obj_cache; + + if (!nm_platform_netns_push(platform, &netns)) { + log_level = LOGL_ERR; + log_detail = ", failure to change network namespace"; + goto out; + } + +retry: + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + log_level = LOGL_ERR; + log_detail_free = + g_strdup_printf(", failure sending netlink request: %s (%d)", nm_strerror(nle), -nle); + log_detail = log_detail_free; + goto out; + } + + /* always refetch the link after changing it. There seems to be issues + * and we sometimes lack events. Nuke it from the orbit... */ + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER(ifindex)); + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + if (NM_IN_SET(-((int) seq_result), EOPNOTSUPP) && nlmsg_hdr(nlmsg)->nlmsg_type == RTM_NEWLINK) { + nlmsg_hdr(nlmsg)->nlmsg_type = RTM_SETLINK; + goto retry; + } + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + log_result = "success"; + } else if (NM_IN_SET(-((int) seq_result), EEXIST, EADDRINUSE)) { + /* */ + } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT)) { + log_detail = ", firmware not found"; + result = -NME_PL_NO_FIRMWARE; + } else if (NM_IN_SET(-((int) seq_result), ERANGE) + && change_link_type == CHANGE_LINK_TYPE_SET_MTU) { + log_detail = ", setting MTU to requested size is not possible"; + result = -NME_PL_CANT_SET_MTU; + } else if (NM_IN_SET(-((int) seq_result), ENFILE) + && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS + && (obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex)) + && obj_cache->link.l_address.len == data->set_address.length + && memcmp(obj_cache->link.l_address.data, + data->set_address.address, + data->set_address.length) + == 0) { + /* workaround ENFILE which may be wrongly returned (bgo #770456). + * If the MAC address is as expected, assume success? */ + log_result = "success"; + log_detail = " (assume success changing address)"; + result = 0; + } else if (NM_IN_SET(-((int) seq_result), ENODEV)) { + log_level = LOGL_DEBUG; + result = -NME_PL_NOT_FOUND; + } else if (-((int) seq_result) == EAFNOSUPPORT) { + log_level = LOGL_DEBUG; + result = -NME_PL_OPNOTSUPP; + } else { + log_level = LOGL_WARN; + result = -NME_UNSPEC; + } + +out: + _NMLOG(log_level, + "do-change-link[%d]: %s changing link: %s%s", + ifindex, + log_result, + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)), + log_detail); + return result; +} + +static int +link_add(NMPlatform * platform, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + if (type == NM_LINK_TYPE_BOND) { + /* When the kernel loads the bond module, either via explicit modprobe + * or automatically in response to creating a bond master, it will also + * create a 'bond0' interface. Since the bond we're about to create may + * or may not be named 'bond0' prevent potential confusion about a bond + * that the user didn't want by telling the bonding module not to create + * bond0 automatically. + */ + if (!g_file_test("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS)) + (void) nm_utils_modprobe(NULL, TRUE, "bonding", "max_bonds=0", NULL); + } + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL, 0, name); + if (!nlmsg) + return -NME_UNSPEC; + + if (parent > 0) + NLA_PUT_U32(nlmsg, IFLA_LINK, parent); + + if (address && address_len) + NLA_PUT(nlmsg, IFLA_ADDRESS, address_len, address); + + if (mtu) + NLA_PUT_U32(nlmsg, IFLA_MTU, mtu); + + if (!_nl_msg_new_link_set_linkinfo(nlmsg, type, extra_data)) + return -NME_UNSPEC; + + return do_add_link_with_lookup(platform, type, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached(-NME_BUG); +} + +static gboolean +link_delete(NMPlatform *platform, int ifindex) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + const NMPObject * obj; + + obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj || !obj->_link.netlink.is_in_netlink) + return FALSE; + + nlmsg = _nl_msg_new_link(RTM_DELLINK, 0, ifindex, NULL); + + nmp_object_stackinit_id_link(&obj_id, ifindex); + return do_delete_object(platform, &obj_id, nlmsg); +} + +static gboolean +link_refresh(NMPlatform *platform, int ifindex) +{ + do_request_link(platform, ifindex, NULL); + return !!nm_platform_link_get_obj(platform, ifindex, TRUE); +} + +static gboolean +link_set_netns(NMPlatform *platform, int ifindex, int netns_fd) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT(nlmsg, IFLA_NET_NS_FD, 4, &netns_fd); + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static int +link_change_flags(NMPlatform *platform, int ifindex, unsigned flags_mask, unsigned flags_set) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + char s_flags[100]; + + _LOGD("link: change %d: flags: set 0x%x/0x%x ([%s] / [%s])", + ifindex, + flags_set, + flags_mask, + nm_platform_link_flags2str(flags_set, s_flags, sizeof(s_flags)), + nm_platform_link_flags2str(flags_mask, NULL, 0)); + + nlmsg = _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, flags_mask, flags_set); + if (!nlmsg) + return -NME_UNSPEC; + return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL); +} + +static gboolean +link_set_up(NMPlatform *platform, int ifindex, gboolean *out_no_firmware) +{ + int r; + + r = link_change_flags(platform, ifindex, IFF_UP, IFF_UP); + NM_SET_OUT(out_no_firmware, (r == -NME_PL_NO_FIRMWARE)); + return r >= 0; +} + +static gboolean +link_set_down(NMPlatform *platform, int ifindex) +{ + return (link_change_flags(platform, ifindex, IFF_UP, 0) >= 0); +} + +static gboolean +link_set_arp(NMPlatform *platform, int ifindex) +{ + return (link_change_flags(platform, ifindex, IFF_NOARP, 0) >= 0); +} + +static gboolean +link_set_noarp(NMPlatform *platform, int ifindex) +{ + return (link_change_flags(platform, ifindex, IFF_NOARP, IFF_NOARP) >= 0); +} + +static int +link_set_user_ipv6ll_enabled(NMPlatform *platform, int ifindex, gboolean enabled) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64; + + _LOGD("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s", + ifindex, + nm_platform_link_inet6_addrgenmode2str(mode, NULL, 0)); + + if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + _LOGD("link: change %d: user-ipv6ll: not supported", ifindex); + return -NME_PL_OPNOTSUPP; + } + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, mode, NULL)) + g_return_val_if_reached(-NME_BUG); + + return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL); +} + +static gboolean +link_set_token(NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD("link: change %d: token: set IPv6 address generation token to %s", + ifindex, + nm_utils_inet6_interface_identifier_to_token(iid, sbuf)); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, -1, &iid)) + g_return_val_if_reached(FALSE); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +} + +static gboolean +link_supports_carrier_detect(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + /* We use netlink for the actual carrier detection, but netlink can't tell + * us whether the device actually supports carrier detection in the first + * place. We assume any device that does implements one of these two APIs. + */ + return nmp_utils_ethtool_supports_carrier_detect(ifindex) + || nmp_utils_mii_supports_carrier_detect(ifindex); +} + +static gboolean +link_supports_vlans(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + const NMPObject * obj; + + obj = nm_platform_link_get_obj(platform, ifindex, TRUE); + + /* Only ARPHRD_ETHER links can possibly support VLANs. */ + if (!obj || obj->link.arptype != ARPHRD_ETHER) + return FALSE; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + return nmp_utils_ethtool_supports_vlans(ifindex); +} + +static gboolean +link_supports_sriov(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + nm_auto_close int dirfd = -1; + char ifname[IFNAMSIZ]; + int num = -1; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname); + if (dirfd < 0) + return FALSE; + + num = + nm_platform_sysctl_get_int32(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + -1); + + return num != -1; +} + +static int +link_set_address(NMPlatform *platform, int ifindex, gconstpointer address, size_t length) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const ChangeLinkData d = { + .set_address = + { + .address = address, + .length = length, + }, + }; + + if (!address || !length) + g_return_val_if_reached(-NME_BUG); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT(nlmsg, IFLA_ADDRESS, length, address); + + return do_change_link(platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d); +nla_put_failure: + g_return_val_if_reached(-NME_BUG); +} + +static int +link_set_name(NMPlatform *platform, int ifindex, const char *name) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT(nlmsg, IFLA_IFNAME, strlen(name) + 1, name); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_get_permanent_address(NMPlatform *platform, int ifindex, guint8 *buf, size_t *length) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + return nmp_utils_ethtool_get_permanent_address(ifindex, buf, length); +} + +static int +link_set_mtu(NMPlatform *platform, int ifindex, guint32 mtu) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT_U32(nlmsg, IFLA_MTU, mtu); + + return do_change_link(platform, CHANGE_LINK_TYPE_SET_MTU, ifindex, nlmsg, NULL); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static void +sriov_idle_cb(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_free_error GError *cancelled_error = NULL; + gs_free_error GError * error = NULL; + NMPlatformAsyncCallback callback; + gpointer callback_data; + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + nm_utils_user_data_unpack(user_data, &platform, &error, &callback, &callback_data); + callback(cancelled_error ?: error, callback_data); +} + +static void +link_set_sriov_params_async(NMPlatform * platform, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + gs_free_error GError *error = NULL; + nm_auto_close int dirfd = -1; + int current_autoprobe; + guint i, total; + gint64 current_num; + char ifname[IFNAMSIZ]; + gpointer packed; + const char * values[3]; + char buf[64]; + + g_return_if_fail(callback || !data); + g_return_if_fail(cancellable); + + if (!nm_platform_netns_push(platform, &netns)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't change namespace"); + goto out_idle; + } + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname); + if (!dirfd) { + g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "couldn't open netdir"); + goto out_idle; + } + + total = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_totalvfs"), + 10, + 0, + G_MAXUINT, + 0); + if (!errno && num_vfs > total) { + _LOGW("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs); + num_vfs = total; + } + + /* + * Take special care when setting new values: + * - don't touch anything if the right values are already set + * - to change the number of VFs or autoprobe we need to destroy existing VFs + * - the autoprobe setting is irrelevant when numvfs is zero + */ + current_num = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + 10, + 0, + G_MAXUINT, + -1); + current_autoprobe = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"), + 10, + 0, + 1, + -1); + + if (current_autoprobe == -1 && errno == ENOENT) { + /* older kernel versions don't have this sysctl. Assume the value is + * "1". */ + current_autoprobe = 1; + } + + if (current_num == num_vfs + && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe)) + goto out_idle; + + if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE) + && current_autoprobe != autoprobe + && !nm_platform_sysctl_set( + NM_PLATFORM_GET, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"), + nm_sprintf_buf(buf, "%d", (int) autoprobe))) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't set SR-IOV drivers-autoprobe to %d: %s", + (int) autoprobe, + nm_strerror_native(errno)); + goto out_idle; + } + + if (current_num == 0 && num_vfs == 0) + goto out_idle; + + i = 0; + if (current_num != 0) + values[i++] = "0"; + if (num_vfs != 0) + values[i++] = nm_sprintf_bufa(32, "%u", num_vfs); + values[i++] = NULL; + + sysctl_set_async(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + values, + callback, + data, + cancellable); + return; + +out_idle: + if (callback) { + packed = nm_utils_user_data_pack(g_object_ref(platform), + g_steal_pointer(&error), + callback, + data); + nm_utils_invoke_on_idle(cancellable, sriov_idle_cb, packed); + } +} + +static gboolean +link_set_sriov_vfs(NMPlatform *platform, int ifindex, const NMPlatformVF *const *vfs) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr * list, *info, *vlan_list; + guint i; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + if (!(list = nla_nest_start(nlmsg, IFLA_VFINFO_LIST))) + goto nla_put_failure; + + for (i = 0; vfs[i]; i++) { + const NMPlatformVF *vf = vfs[i]; + + if (!(info = nla_nest_start(nlmsg, IFLA_VF_INFO))) + goto nla_put_failure; + + if (vf->spoofchk >= 0) { + struct _ifla_vf_setting ivs = {0}; + + ivs.vf = vf->index; + ivs.setting = vf->spoofchk; + NLA_PUT(nlmsg, IFLA_VF_SPOOFCHK, sizeof(ivs), &ivs); + } + + if (vf->trust >= 0) { + struct _ifla_vf_setting ivs = {0}; + + ivs.vf = vf->index; + ivs.setting = vf->trust; + NLA_PUT(nlmsg, IFLA_VF_TRUST, sizeof(ivs), &ivs); + } + + if (vf->mac.len) { + struct ifla_vf_mac ivm = {0}; + + ivm.vf = vf->index; + memcpy(ivm.mac, vf->mac.data, vf->mac.len); + NLA_PUT(nlmsg, IFLA_VF_MAC, sizeof(ivm), &ivm); + } + + if (vf->min_tx_rate || vf->max_tx_rate) { + struct _ifla_vf_rate ivr = {0}; + + ivr.vf = vf->index; + ivr.min_tx_rate = vf->min_tx_rate; + ivr.max_tx_rate = vf->max_tx_rate; + NLA_PUT(nlmsg, IFLA_VF_RATE, sizeof(ivr), &ivr); + } + + /* Kernel only supports one VLAN per VF now. If this + * changes in the future, we need to figure out how to + * clear existing VLANs and set new ones in one message + * with the new API.*/ + if (vf->num_vlans > 1) { + _LOGW("multiple VLANs per VF are not supported at the moment"); + return FALSE; + } else { + struct _ifla_vf_vlan_info ivvi = {0}; + + if (!(vlan_list = nla_nest_start(nlmsg, IFLA_VF_VLAN_LIST))) + goto nla_put_failure; + + ivvi.vf = vf->index; + if (vf->num_vlans == 1) { + ivvi.vlan = vf->vlans[0].id; + ivvi.qos = vf->vlans[0].qos; + ivvi.vlan_proto = htons(vf->vlans[0].proto_ad ? ETH_P_8021AD : ETH_P_8021Q); + } else { + /* Clear existing VLAN */ + ivvi.vlan = 0; + ivvi.qos = 0; + ivvi.vlan_proto = htons(ETH_P_8021Q); + } + + NLA_PUT(nlmsg, IFLA_VF_VLAN_INFO, sizeof(ivvi), &ivvi); + nla_nest_end(nlmsg, vlan_list); + } + nla_nest_end(nlmsg, info); + } + nla_nest_end(nlmsg, list); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_set_bridge_vlans(NMPlatform * platform, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr * list; + struct bridge_vlan_info vinfo = {}; + guint i; + + nlmsg = + _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK, 0, ifindex, NULL, AF_BRIDGE, 0, 0); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + if (!(list = nla_nest_start(nlmsg, IFLA_AF_SPEC))) + goto nla_put_failure; + + NLA_PUT_U16(nlmsg, IFLA_BRIDGE_FLAGS, on_master ? BRIDGE_FLAGS_MASTER : BRIDGE_FLAGS_SELF); + + if (vlans) { + /* Add VLANs */ + for (i = 0; vlans[i]; i++) { + const NMPlatformBridgeVlan *vlan = vlans[i]; + gboolean is_range = vlan->vid_start != vlan->vid_end; + + vinfo.vid = vlan->vid_start; + vinfo.flags = is_range ? BRIDGE_VLAN_INFO_RANGE_BEGIN : 0; + + if (vlan->untagged) + vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED; + if (vlan->pvid) + vinfo.flags |= BRIDGE_VLAN_INFO_PVID; + + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + + if (is_range) { + vinfo.vid = vlan->vid_end; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + } + } + } else { + /* Flush existing VLANs */ + vinfo.vid = 1; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + + vinfo.vid = 4094; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + } + + nla_nest_end(nlmsg, list); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static char * +link_get_physical_port_id(NMPlatform *platform, int ifindex) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified); + if (dirfd < 0) + return NULL; + return sysctl_get(platform, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "phys_port_id")); +} + +static guint +link_get_dev_id(NMPlatform *platform, int ifindex) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified); + if (dirfd < 0) + return 0; + return nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "dev_id"), + 16, + 0, + G_MAXUINT16, + 0); +} + +static gboolean +link_tun_add(NMPlatform * platform, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd) +{ + const NMPObject * obj; + struct ifreq ifr = {}; + nm_auto_close int fd = -1; + + nm_assert(NM_IN_SET(props->type, IFF_TAP, IFF_TUN)); + nm_assert(props->persist || out_fd); + + fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC); + if (fd < 0) + return FALSE; + + nm_utils_ifname_cpy(ifr.ifr_name, name); + ifr.ifr_flags = ((short) props->type) | ((short) IFF_TUN_EXCL) + | (!props->pi ? (short) IFF_NO_PI : (short) 0) + | (props->vnet_hdr ? (short) IFF_VNET_HDR : (short) 0) + | (props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0); + if (ioctl(fd, TUNSETIFF, &ifr)) + return FALSE; + + if (props->owner_valid) { + if (ioctl(fd, TUNSETOWNER, (uid_t) props->owner)) + return FALSE; + } + + if (props->group_valid) { + if (ioctl(fd, TUNSETGROUP, (gid_t) props->group)) + return FALSE; + } + + if (props->persist) { + if (ioctl(fd, TUNSETPERSIST, 1)) + return FALSE; + } + + do_request_link(platform, 0, name); + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + name, + FALSE, + NM_LINK_TYPE_TUN, + NULL, + NULL); + + if (!obj) + return FALSE; + + NM_SET_OUT(out_link, &obj->link); + NM_SET_OUT(out_fd, nm_steal_fd(&fd)); + return TRUE; +} + +static void +_vlan_change_vlan_qos_mapping_create(gboolean is_ingress_map, + gboolean reset_all, + const NMVlanQosMapping *current_map, + guint current_n_map, + const NMVlanQosMapping *set_map, + guint set_n_map, + NMVlanQosMapping ** out_map, + guint * out_n_map) +{ + NMVlanQosMapping *map; + guint i, j, len; + const guint INGRESS_RANGE_LEN = 8; + + nm_assert(out_map && !*out_map); + nm_assert(out_n_map && !*out_n_map); + + if (!reset_all) + current_n_map = 0; + else if (is_ingress_map) + current_n_map = INGRESS_RANGE_LEN; + + len = current_n_map + set_n_map; + + if (len == 0) + return; + + map = g_new(NMVlanQosMapping, len); + + if (current_n_map) { + if (is_ingress_map) { + /* For the ingress-map, there are only 8 entries (0 to 7). + * When the user requests to reset all entries, we don't actually + * need the cached entries, we can just explicitly clear all possible + * ones. + * + * That makes only a real difference in case our cache is out-of-date. + * + * For the egress map we cannot do that, because there are far too + * many. There we can only clear the entries that we know about. */ + for (i = 0; i < INGRESS_RANGE_LEN; i++) { + map[i].from = i; + map[i].to = 0; + } + } else { + for (i = 0; i < current_n_map; i++) { + map[i].from = current_map[i].from; + map[i].to = 0; + } + } + } + if (set_n_map) + memcpy(&map[current_n_map], set_map, sizeof(*set_map) * set_n_map); + + g_qsort_with_data(map, len, sizeof(*map), _vlan_qos_mapping_cmp_from, NULL); + + for (i = 0, j = 0; i < len; i++) { + if ((is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].from)) + || (!is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].to))) + continue; + if (j > 0 && map[j - 1].from == map[i].from) + map[j - 1] = map[i]; + else + map[j++] = map[i]; + } + + *out_map = map; + *out_n_map = j; +} + +static gboolean +link_vlan_change(NMPlatform * platform, + int ifindex, + NMVlanFlags flags_mask, + NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map) +{ + const NMPObject * obj_cache; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const NMPObjectLnkVlan * lnk; + guint new_n_ingress_map = 0; + guint new_n_egress_map = 0; + gs_free NMVlanQosMapping *new_ingress_map = NULL; + gs_free NMVlanQosMapping *new_egress_map = NULL; + + obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) { + _LOGD("link: change %d: %s: link does not exist", ifindex, "vlan"); + return FALSE; + } + + lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL; + + flags_set &= flags_mask; + + _vlan_change_vlan_qos_mapping_create(TRUE, + ingress_reset_all, + lnk ? lnk->ingress_qos_map : NULL, + lnk ? lnk->n_ingress_qos_map : 0, + ingress_map, + n_ingress_map, + &new_ingress_map, + &new_n_ingress_map); + + _vlan_change_vlan_qos_mapping_create(FALSE, + egress_reset_all, + lnk ? lnk->egress_qos_map : NULL, + lnk ? lnk->n_egress_qos_map : 0, + egress_map, + n_egress_map, + &new_egress_map, + &new_n_egress_map); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg + || !_nl_msg_new_link_set_linkinfo_vlan(nlmsg, + -1, + flags_mask, + flags_set, + new_ingress_map, + new_n_ingress_map, + new_egress_map, + new_n_egress_map)) + g_return_val_if_reached(FALSE); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +} + +static gboolean +link_enslave(NMPlatform *platform, int master, int slave) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int ifindex = slave; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT_U32(nlmsg, IFLA_MASTER, master); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_release(NMPlatform *platform, int master, int slave) +{ + return link_enslave(platform, 0, slave); +} + +/*****************************************************************************/ + +static gboolean +_infiniband_partition_action(NMPlatform * platform, + InfinibandAction action, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + nm_auto_close int dirfd = -1; + char ifname_parent[IFNAMSIZ]; + const NMPObject * obj; + char id[20]; + char name[IFNAMSIZ]; + gboolean success; + + nm_assert(NM_IN_SET(action, INFINIBAND_ACTION_CREATE_CHILD, INFINIBAND_ACTION_DELETE_CHILD)); + nm_assert(p_key > 0 && p_key <= 0xffff && p_key != 0x8000); + + dirfd = nm_platform_sysctl_open_netdir(platform, parent, ifname_parent); + if (dirfd < 0) { + errno = ENOENT; + return FALSE; + } + + nm_sprintf_buf(id, "0x%04x", p_key); + if (action == INFINIBAND_ACTION_CREATE_CHILD) + success = + nm_platform_sysctl_set(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "create_child"), + id); + else + success = + nm_platform_sysctl_set(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "delete_child"), + id); + + if (!success) { + if (action == INFINIBAND_ACTION_DELETE_CHILD && errno == ENODEV) + return TRUE; + return FALSE; + } + + nm_utils_new_infiniband_name(name, ifname_parent, p_key); + do_request_link(platform, 0, name); + + if (action == INFINIBAND_ACTION_DELETE_CHILD) + return TRUE; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + name, + FALSE, + NM_LINK_TYPE_INFINIBAND, + NULL, + NULL); + if (out_link) + *out_link = obj ? &obj->link : NULL; + return !!obj; +} + +static gboolean +infiniband_partition_add(NMPlatform * platform, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + return _infiniband_partition_action(platform, + INFINIBAND_ACTION_CREATE_CHILD, + parent, + p_key, + out_link); +} + +static gboolean +infiniband_partition_delete(NMPlatform *platform, int parent, int p_key) +{ + return _infiniband_partition_action(platform, + INFINIBAND_ACTION_DELETE_CHILD, + parent, + p_key, + NULL); +} + +/*****************************************************************************/ + +static GObject * +get_ext_data(NMPlatform *platform, int ifindex) +{ + const NMPObject *obj; + + obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj) + return NULL; + + return obj->_link.ext_data; +} + +/*****************************************************************************/ + +#define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \ + nm_auto_pop_netns NMPNetns *netns = NULL; \ + NMWifiUtils * wifi_data; \ + if (!nm_platform_netns_push(platform, &netns)) \ + return retval; \ + wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex)); \ + if (!wifi_data) \ + return retval; + +static gboolean +wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + if (caps) + *caps = nm_wifi_utils_get_caps(wifi_data); + return TRUE; +} + +static guint32 +wifi_get_frequency(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_get_freq(wifi_data); +} + +static gboolean +wifi_get_station(NMPlatform * platform, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_get_station(wifi_data, out_bssid, out_quality, out_rate); +} + +static NM80211Mode +wifi_get_mode(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN); + return nm_wifi_utils_get_mode(wifi_data); +} + +static void +wifi_set_mode(NMPlatform *platform, int ifindex, NM80211Mode mode) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_set_mode(wifi_data, mode); +} + +static void +wifi_set_powersave(NMPlatform *platform, int ifindex, guint32 powersave) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_set_powersave(wifi_data, powersave); +} + +static guint32 +wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_find_freq(wifi_data, freqs); +} + +static void +wifi_indicate_addressing_running(NMPlatform *platform, int ifindex, gboolean running) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_indicate_addressing_running(wifi_data, running); +} + +static NMSettingWirelessWakeOnWLan +wifi_get_wake_on_wlan(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_get_wake_on_wlan(wifi_data); +} + +static gboolean +wifi_set_wake_on_wlan(NMPlatform *platform, int ifindex, NMSettingWirelessWakeOnWLan wowl) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_wake_on_wlan(wifi_data, wowl); +} + +/*****************************************************************************/ + +static gboolean +link_can_assume(NMPlatform *platform, int ifindex) +{ + NMPLookup lookup; + const NMPObject *link, *o; + NMDedupMultiIter iter; + NMPCache * cache = nm_platform_get_cache(platform); + + if (ifindex <= 0) + return FALSE; + + link = nm_platform_link_get_obj(platform, ifindex, TRUE); + if (!link) + return FALSE; + + if (!NM_FLAGS_HAS(link->link.n_ifi_flags, IFF_UP)) + return FALSE; + + if (link->link.master > 0) + return TRUE; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex); + if (nmp_cache_lookup(cache, &lookup)) + return TRUE; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); + nmp_cache_iter_for_each (&iter, nmp_cache_lookup(cache, &lookup), &o) { + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP6_ADDRESS); + if (!IN6_IS_ADDR_LINKLOCAL(&o->ip6_address.address)) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static guint32 +mesh_get_channel(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_get_mesh_channel(wifi_data); +} + +static gboolean +mesh_set_channel(NMPlatform *platform, int ifindex, guint32 channel) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_mesh_channel(wifi_data, channel); +} + +static gboolean +mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_mesh_ssid(wifi_data, ssid, len); +} + +/*****************************************************************************/ + +#define WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, retval) \ + NMWpanUtils *wpan_data = NM_WPAN_UTILS(get_ext_data(platform, ifindex)); \ + if (!wpan_data) \ + return retval; + +static guint16 +wpan_get_pan_id(NMPlatform *platform, int ifindex) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16); + return nm_wpan_utils_get_pan_id(wpan_data); +} + +static gboolean +wpan_set_pan_id(NMPlatform *platform, int ifindex, guint16 pan_id) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_pan_id(wpan_data, pan_id); +} + +static guint16 +wpan_get_short_addr(NMPlatform *platform, int ifindex) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16); + return nm_wpan_utils_get_short_addr(wpan_data); +} + +static gboolean +wpan_set_short_addr(NMPlatform *platform, int ifindex, guint16 short_addr) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_short_addr(wpan_data, short_addr); +} + +static gboolean +wpan_set_channel(NMPlatform *platform, int ifindex, guint8 page, guint8 channel) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_channel(wpan_data, page, channel); +} + +/*****************************************************************************/ + +static gboolean +link_get_wake_on_lan(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMLinkType type = nm_platform_link_get_type(platform, ifindex); + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + if (type == NM_LINK_TYPE_ETHERNET) + return nmp_utils_ethtool_get_wake_on_lan(ifindex); + else if (type == NM_LINK_TYPE_WIFI) { + NMWifiUtils *wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex)); + + if (!wifi_data) + return FALSE; + + return !NM_IN_SET(nm_wifi_utils_get_wake_on_wlan(wifi_data), + NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE, + NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE); + + } else + return FALSE; +} + +static gboolean +link_get_driver_info(NMPlatform *platform, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMPUtilsEthtoolDriverInfo driver_info; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + if (!nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) + return FALSE; + NM_SET_OUT(out_driver_name, g_strdup(driver_info.driver)); + NM_SET_OUT(out_driver_version, g_strdup(driver_info.version)); + NM_SET_OUT(out_fw_version, g_strdup(driver_info.fw_version)); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +ip4_address_add(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_addr, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_address(RTM_NEWADDR, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET, + ifindex, + &addr, + plen, + &peer_addr, + flags, + nm_utils_ip4_address_is_link_local(addr) ? RT_SCOPE_LINK + : RT_SCOPE_UNIVERSE, + lifetime, + preferred, + broadcast_address, + label); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_addr); + return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0); +} + +static gboolean +ip6_address_add(NMPlatform * platform, + int ifindex, + struct in6_addr addr, + guint8 plen, + struct in6_addr peer_addr, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_address(RTM_NEWADDR, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET6, + ifindex, + &addr, + plen, + IN6_IS_ADDR_UNSPECIFIED(&peer_addr) ? NULL : &peer_addr, + flags, + RT_SCOPE_UNIVERSE, + lifetime, + preferred, + 0, + NULL); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr); + return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0); +} + +static gboolean +ip4_address_delete(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_address) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + + nlmsg = _nl_msg_new_address(RTM_DELADDR, + 0, + AF_INET, + ifindex, + &addr, + plen, + &peer_address, + 0, + RT_SCOPE_NOWHERE, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0, + NULL); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_address); + return do_delete_object(platform, &obj_id, nlmsg); +} + +static gboolean +ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + + nlmsg = _nl_msg_new_address(RTM_DELADDR, + 0, + AF_INET6, + ifindex, + &addr, + plen, + NULL, + 0, + RT_SCOPE_NOWHERE, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0, + NULL); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr); + return do_delete_object(platform, &obj_id, nlmsg); +} + +/*****************************************************************************/ + +static int +ip_route_add(NMPlatform * platform, + NMPNlmFlags flags, + int addr_family, + const NMPlatformIPRoute *route) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj; + + nmp_object_stackinit(&obj, + NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), + (const NMPlatformObject *) route); + + nm_platform_ip_route_normalize(addr_family, NMP_OBJECT_CAST_IP_ROUTE(&obj)); + + nlmsg = _nl_msg_new_route(RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + return do_add_addrroute(platform, + &obj, + nlmsg, + NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE)); +} + +static gboolean +object_delete(NMPlatform *platform, const NMPObject *obj) +{ + nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL; + nm_auto_nlmsg struct nl_msg * nlmsg = NULL; + + if (!NMP_OBJECT_IS_STACKINIT(obj)) + obj_keep_alive = nmp_object_ref(obj); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + nlmsg = _nl_msg_new_route(RTM_DELROUTE, 0, obj); + break; + case NMP_OBJECT_TYPE_ROUTING_RULE: + nlmsg = _nl_msg_new_routing_rule(RTM_DELRULE, 0, NMP_OBJECT_CAST_ROUTING_RULE(obj)); + break; + case NMP_OBJECT_TYPE_QDISC: + nlmsg = _nl_msg_new_qdisc(RTM_DELQDISC, 0, NMP_OBJECT_CAST_QDISC(obj)); + break; + case NMP_OBJECT_TYPE_TFILTER: + nlmsg = _nl_msg_new_tfilter(RTM_DELTFILTER, 0, NMP_OBJECT_CAST_TFILTER(obj)); + break; + default: + break; + } + + if (!nlmsg) + g_return_val_if_reached(FALSE); + return do_delete_object(platform, obj, nlmsg); +} + +/*****************************************************************************/ + +static int +ip_route_get(NMPlatform * platform, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route) +{ + const gboolean is_v4 = (addr_family == AF_INET); + const int addr_len = is_v4 ? 4 : 16; + int try_count = 0; + WaitForNlResponseResult seq_result; + int nle; + nm_auto_nmpobj NMPObject *route = NULL; + + nm_assert(NM_IS_LINUX_PLATFORM(platform)); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + nm_assert(address); + + do { + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[64]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETROUTE, + .r.rtm_family = addr_family, + .r.rtm_tos = 0, + .r.rtm_dst_len = is_v4 ? 32 : 128, + .r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */, + }; + + nm_clear_pointer(&route, nmp_object_unref); + + if (!_nl_addattr_l(&req.n, sizeof(req), RTA_DST, address, addr_len)) + nm_assert_not_reached(); + + if (oif_ifindex > 0) { + gint32 ii = oif_ifindex; + + if (!_nl_addattr_l(&req.n, sizeof(req), RTA_OIF, &ii, sizeof(ii))) + nm_assert_not_reached(); + } + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + nle = _nl_send_nlmsghdr(platform, + &req.n, + &seq_result, + NULL, + DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, + &route); + if (nle < 0) { + _LOGE("get-route: failure sending netlink request \"%s\" (%d)", + nm_strerror_native(-nle), + -nle); + return -NME_UNSPEC; + } + + delayed_action_handle_all(platform, FALSE); + + /* Retry, if we failed due to a cache resync. That can happen when the netlink + * socket fills up and we lost the response. */ + } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC && ++try_count < 10); + + if (seq_result < 0) { + /* negative seq_result is an errno from kernel. Map it to negative + * int (which are also errno). */ + return (int) seq_result; + } + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + if (route) { + NM_SET_OUT(out_route, g_steal_pointer(&route)); + return 0; + } + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN; + } + + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char * errmsg = NULL; + char s_buf[256]; + int nle; + + msg = _nl_msg_new_routing_rule(RTM_NEWRULE, flags, routing_rule); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-rule: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return seq_result; + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind + * is undefined. */ + + msg = _nl_msg_new_qdisc(RTM_NEWQDISC, flags, qdisc); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-qdisc: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return seq_result; + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tfilter) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind + * and tfilter.action.kind is undefined. */ + + msg = _nl_msg_new_tfilter(RTM_NEWTFILTER, flags, tfilter); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-tfilter: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static gboolean +event_handler(int fd, GIOCondition io_condition, gpointer user_data) +{ + delayed_action_handle_all(NM_PLATFORM(user_data), TRUE); + return TRUE; +} + +/*****************************************************************************/ + +/* copied from libnl3's recvmsgs() */ +static int +event_handler_recvmsgs(NMPlatform *platform, gboolean handle_events) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + struct nl_sock * sk = priv->nlh; + int n; + int err = 0; + gboolean multipart = 0; + gboolean interrupted = FALSE; + struct nlmsghdr * hdr; + WaitForNlResponseResult seq_result; + struct sockaddr_nl nla = {0}; + struct ucred creds; + gboolean creds_has; + nm_auto_free unsigned char *buf = NULL; + +continue_reading: + nm_clear_pointer(&buf, free); + n = nl_recv(sk, &nla, &buf, &creds, &creds_has); + + if (n <= 0) { + if (n == -NME_NL_MSG_TRUNC) { + int buf_size; + + /* the message receive buffer was too small. We lost one message, which + * is unfortunate. Try to double the buffer size for the next time. */ + buf_size = nl_socket_get_msg_buf_size(sk); + if (buf_size < 512 * 1024) { + buf_size *= 2; + _LOGT("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", + buf_size); + if (nl_socket_set_msg_buf_size(sk, buf_size) < 0) + nm_assert_not_reached(); + if (!handle_events) + goto continue_reading; + } + } + + return n; + } + + hdr = (struct nlmsghdr *) buf; + while (nlmsg_ok(hdr, n)) { + nm_auto_nlmsg struct nl_msg *msg = NULL; + gboolean abort_parsing = FALSE; + gboolean process_valid_msg = FALSE; + guint32 seq_number; + char buf_nlmsghdr[400]; + const char * extack_msg = NULL; + + msg = nlmsg_alloc_convert(hdr); + + nlmsg_set_proto(msg, NETLINK_ROUTE); + nlmsg_set_src(msg, &nla); + + if (!creds_has || creds.pid) { + if (!creds_has) + _LOGT("netlink: recvmsg: received message without credentials"); + else + _LOGT("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid); + err = 0; + goto stop; + } + + _LOGt("netlink: recvmsg: new message %s", + nl_nlmsghdr_to_str(hdr, buf_nlmsghdr, sizeof(buf_nlmsghdr))); + + nlmsg_set_creds(msg, &creds); + + if (hdr->nlmsg_flags & NLM_F_MULTI) + multipart = TRUE; + + if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) { + /* + * We have to continue reading to clear + * all messages until a NLMSG_DONE is + * received and report the inconsistency. + */ + interrupted = TRUE; + } + + /* Other side wishes to see an ack for this message */ + if (hdr->nlmsg_flags & NLM_F_ACK) { + /* FIXME: implement */ + } + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN; + + if (hdr->nlmsg_type == NLMSG_DONE) { + /* messages terminates a multipart message, this is + * usually the end of a message and therefore we slip + * out of the loop by default. the user may overrule + * this action by skipping this packet. */ + multipart = FALSE; + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } else if (hdr->nlmsg_type == NLMSG_NOOP) { + /* Message to be ignored, the default action is to + * skip this message if no callback is specified. The + * user may overrule this action by returning + * NL_PROCEED. */ + } else if (hdr->nlmsg_type == NLMSG_OVERRUN) { + /* Data got lost, report back to user. The default action is to + * quit parsing. The user may overrule this action by returning + * NL_SKIP or NL_PROCEED (dangerous) */ + err = -NME_NL_MSG_OVERFLOW; + abort_parsing = TRUE; + } else if (hdr->nlmsg_type == NLMSG_ERROR) { + /* Message carries a nlmsgerr */ + struct nlmsgerr *e = nlmsg_data(hdr); + + if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) { + /* Truncated error message, the default action + * is to stop parsing. The user may overrule + * this action by returning NL_SKIP or + * NL_PROCEED (dangerous) */ + err = -NME_NL_MSG_TRUNC; + abort_parsing = TRUE; + } else if (e->error) { + int errsv = nm_errno_native(e->error); + + if (NM_FLAGS_HAS(hdr->nlmsg_flags, NLM_F_ACK_TLVS) + && hdr->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) { + static const struct nla_policy policy[] = { + [NLMSGERR_ATTR_MSG] = {.type = NLA_STRING}, + [NLMSGERR_ATTR_OFFS] = {.type = NLA_U32}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + struct nlattr *tlvs; + + tlvs = (struct nlattr *) ((char *) e + sizeof(*e) + e->msg.nlmsg_len + - NLMSG_HDRLEN); + if (nla_parse_arr(tb, + tlvs, + hdr->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len, + policy) + >= 0) { + if (tb[NLMSGERR_ATTR_MSG]) + extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]); + } + } + + /* Error message reported back from kernel. */ + _LOGD("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d", + nm_strerror_native(errsv), + errsv, + NM_PRINT_FMT_QUOTED(extack_msg, " \"", extack_msg, "\"", ""), + nlmsg_hdr(msg)->nlmsg_seq); + seq_result = -NM_ERRNO_NATIVE(errsv); + } else + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } else + process_valid_msg = TRUE; + + seq_number = nlmsg_hdr(msg)->nlmsg_seq; + + /* check whether the seq number is different from before, and + * whether the previous number (@nlh_seq_last_seen) is a pending + * refresh-all request. In that case, the pending request is thereby + * completed. + * + * We must do that before processing the message with event_valid_msg(), + * because we must track the completion of the pending request before that. */ + event_seq_check_refresh_all(platform, seq_number); + + if (process_valid_msg) { + /* Valid message (not checking for MULTIPART bit to + * get along with broken kernels. NL_SKIP has no + * effect on this. */ + + event_valid_msg(platform, msg, handle_events); + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } + + event_seq_check(platform, seq_number, seq_result, extack_msg); + + if (abort_parsing) + goto stop; + + err = 0; + hdr = nlmsg_next(hdr, &n); + } + + if (multipart) { + /* Multipart message not yet complete, continue reading */ + goto continue_reading; + } +stop: + if (!handle_events) { + /* when we don't handle events, we want to drain all messages from the socket + * without handling the messages (but still check for sequence numbers). + * Repeat reading. */ + goto continue_reading; + } + + if (interrupted) + return -NME_NL_DUMP_INTR; + return err; +} + +/*****************************************************************************/ + +static gboolean +event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int r; + struct pollfd pfd; + gboolean any = FALSE; + int timeout_msec; + struct { + guint32 seq_number; + gint64 timeout_abs_ns; + gint64 now_ns; + } next; + + if (!nm_platform_netns_push(platform, &netns)) { + delayed_action_wait_for_nl_response_complete_all(platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS); + return FALSE; + } + + for (;;) { + for (;;) { + int nle; + + nle = event_handler_recvmsgs(platform, TRUE); + + if (nle < 0) { + switch (nle) { + case -EAGAIN: + goto after_read; + case -NME_NL_DUMP_INTR: + _LOGD("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", + nm_strerror(nle), + nle); + break; + case -NME_NL_MSG_TRUNC: + case -ENOBUFS: + _LOGI("netlink: read: %s. Need to resynchronize platform cache", ({ + const char *_reason = "unknown"; + switch (nle) { + case -NME_NL_MSG_TRUNC: + _reason = "message truncated"; + break; + case -ENOBUFS: + _reason = "too many netlink events"; + break; + } + _reason; + })); + event_handler_recvmsgs(platform, FALSE); + delayed_action_wait_for_nl_response_complete_all( + platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC); + + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + break; + default: + _LOGE("netlink: read: failed to retrieve incoming events: %s (%d)", + nm_strerror(nle), + nle); + break; + } + } + any = TRUE; + } + +after_read: + + if (!NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) + return any; + + delayed_action_wait_for_nl_response_complete_check(platform, + WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN, + &next.seq_number, + &next.timeout_abs_ns, + &next.now_ns); + + if (!wait_for_acks + || !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) + return any; + + nm_assert(next.seq_number); + nm_assert(next.now_ns > 0); + nm_assert(next.timeout_abs_ns > next.now_ns); + + _LOGT("netlink: read: wait for ACK for sequence number %u...", next.seq_number); + + timeout_msec = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NSEC_PER_SEC / 1000); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = nl_socket_get_fd(priv->nlh); + pfd.events = POLLIN; + r = poll(&pfd, 1, MAX(1, timeout_msec)); + + if (r == 0) { + /* timeout and there is nothing to read. */ + goto after_read; + } + + if (r < 0) { + int errsv = errno; + + if (errsv != EINTR) { + _LOGE("netlink: read: poll failed with %s", nm_strerror_native(errsv)); + delayed_action_wait_for_nl_response_complete_all( + platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL); + return any; + } + /* Continue to read again, even if there might be nothing to read after EINTR. */ + } + } +} + +/*****************************************************************************/ + +static void +cache_update_link_udev(NMPlatform *platform, int ifindex, struct udev_device *udevice) +{ + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCacheOpsType cache_op; + + cache_op = nmp_cache_update_link_udev(nm_platform_get_cache(platform), + ifindex, + udevice, + &obj_old, + &obj_new); + + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + cache_on_change(platform, cache_op, obj_old, obj_new); + if (!nm_platform_netns_push(platform, &netns)) + return; + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } +} + +static void +udev_device_added(NMPlatform *platform, struct udev_device *udevice) +{ + const char *ifname; + const char *ifindex_s; + int ifindex; + + ifname = udev_device_get_sysname(udevice); + if (!ifname) { + _LOGD("udev-add: failed to get device's interface"); + return; + } + + ifindex_s = udev_device_get_property_value(udevice, "IFINDEX"); + if (!ifindex_s) { + _LOGW("udev-add[%s]failed to get device's ifindex", ifname); + return; + } + ifindex = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0); + if (ifindex <= 0) { + _LOGW("udev-add[%s]: retrieved invalid IFINDEX=%d", ifname, ifindex); + return; + } + + if (!udev_device_get_syspath(udevice)) { + _LOGD("udev-add[%s,%d]: couldn't determine device path; ignoring...", ifname, ifindex); + return; + } + + _LOGT("udev-add[%s,%d]: device added", ifname, ifindex); + cache_update_link_udev(platform, ifindex, udevice); +} + +static gboolean +_udev_device_removed_match_link(const NMPObject *obj, gpointer udevice) +{ + return obj->_link.udev.device == udevice; +} + +static void +udev_device_removed(NMPlatform *platform, struct udev_device *udevice) +{ + const char *ifindex_s; + int ifindex = 0; + + ifindex_s = udev_device_get_property_value(udevice, "IFINDEX"); + ifindex = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0); + if (ifindex <= 0) { + const NMPObject *obj; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + NULL, + FALSE, + NM_LINK_TYPE_NONE, + _udev_device_removed_match_link, + udevice); + if (obj) + ifindex = obj->link.ifindex; + } + + _LOGD("udev-remove: IFINDEX=%d", ifindex); + if (ifindex <= 0) + return; + + cache_update_link_udev(platform, ifindex, NULL); +} + +static void +handle_udev_event(NMUdevClient *udev_client, struct udev_device *udevice, gpointer user_data) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMPlatform * platform = NM_PLATFORM(user_data); + const char * subsys; + const char * ifindex; + guint64 seqnum; + const char * action; + + action = udev_device_get_action(udevice); + g_return_if_fail(action); + + subsys = udev_device_get_subsystem(udevice); + g_return_if_fail(nm_streq0(subsys, "net")); + + if (!nm_platform_netns_push(platform, &netns)) + return; + + ifindex = udev_device_get_property_value(udevice, "IFINDEX"); + seqnum = udev_device_get_seqnum(udevice); + _LOGD("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, + action, + subsys, + udev_device_get_sysname(udevice), + ifindex ?: "unknown", + seqnum); + + if (NM_IN_STRSET(action, "add", "move")) + udev_device_added(platform, udevice); + else if (NM_IN_STRSET(action, "remove")) + udev_device_removed(platform, udevice); +} + +/*****************************************************************************/ + +void +nm_linux_platform_setup(void) +{ + nm_platform_setup(nm_linux_platform_new(FALSE, FALSE)); +} + +/*****************************************************************************/ + +static void +nm_linux_platform_init(NMLinuxPlatform *self) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(self); + + priv->delayed_action.list_master_connected = g_ptr_array_new(); + priv->delayed_action.list_refresh_link = g_ptr_array_new(); + priv->delayed_action.list_wait_for_nl_response = + g_array_new(FALSE, TRUE, sizeof(DelayedActionWaitForNlResponseData)); +} + +static void +constructed(GObject *_object) +{ + NMPlatform * platform = NM_PLATFORM(_object); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int nle; + int fd; + + nm_assert(!platform->_netns || platform->_netns == nmp_netns_get_current()); + + if (nm_platform_get_use_udev(platform)) { + priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("net"), handle_udev_event, platform); + } + + _LOGD("create (%s netns, %s, %s udev)", + !platform->_netns ? "ignore" : "use", + !platform->_netns && nmp_netns_is_initial() + ? "initial netns" + : (!nmp_netns_get_current() + ? "no netns support" + : nm_sprintf_bufa(100, + "in netns[%p]%s", + nmp_netns_get_current(), + nmp_netns_get_current() == nmp_netns_get_initial() ? "/main" + : "")), + nm_platform_get_use_udev(platform) ? "use" : "no"); + + priv->genl = nl_socket_alloc(); + g_assert(priv->genl); + + nle = nl_connect(priv->genl, NETLINK_GENERIC); + if (nle) { + _LOGE("unable to connect the generic netlink socket \"%s\" (%d)", nm_strerror(nle), -nle); + nl_socket_free(priv->genl); + priv->genl = NULL; + } + + priv->nlh = nl_socket_alloc(); + g_assert(priv->nlh); + + nle = nl_connect(priv->nlh, NETLINK_ROUTE); + g_assert(!nle); + nle = nl_socket_set_passcred(priv->nlh, 1); + g_assert(!nle); + + /* No blocking for event socket, so that we can drain it safely. */ + nle = nl_socket_set_nonblocking(priv->nlh); + g_assert(!nle); + + /* use 8 MB for receive socket kernel queue. */ + nle = nl_socket_set_buffer_size(priv->nlh, 8 * 1024 * 1024, 0); + g_assert(!nle); + + nle = nl_socket_set_ext_ack(priv->nlh, TRUE); + if (nle) + _LOGD("could not enable extended acks on netlink socket"); + + /* explicitly set the msg buffer size and disable MSG_PEEK. + * If we later encounter NME_NL_MSG_TRUNC, we will adjust the buffer size. */ + nl_socket_disable_msg_peek(priv->nlh); + nle = nl_socket_set_msg_buf_size(priv->nlh, 32 * 1024); + g_assert(!nle); + + nle = nl_socket_add_memberships(priv->nlh, + RTNLGRP_IPV4_IFADDR, + RTNLGRP_IPV4_ROUTE, + RTNLGRP_IPV4_RULE, + RTNLGRP_IPV6_RULE, + RTNLGRP_IPV6_IFADDR, + RTNLGRP_IPV6_ROUTE, + RTNLGRP_LINK, + RTNLGRP_TC, + 0); + g_assert(!nle); + + fd = nl_socket_get_fd(priv->nlh); + + _LOGD("Netlink socket for events established: port=%u, fd=%d", + nl_socket_get_local_port(priv->nlh), + fd); + + priv->event_source = + nm_g_unix_fd_source_new(fd, + G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + event_handler, + platform, + NULL); + g_source_attach(priv->event_source, NULL); + + /* complete construction of the GObject instance before populating the cache. */ + G_OBJECT_CLASS(nm_linux_platform_parent_class)->constructed(_object); + + _LOGD("populate platform cache"); + delayed_action_schedule( + platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + + delayed_action_handle_all(platform, FALSE); + + /* Set up udev monitoring */ + if (priv->udev_client) { + struct udev_enumerate * enumerator; + struct udev_list_entry *devices, *l; + + /* And read initial device list */ + enumerator = nm_udev_client_enumerate_new(priv->udev_client); + + udev_enumerate_add_match_is_initialized(enumerator); + + udev_enumerate_scan_devices(enumerator); + + devices = udev_enumerate_get_list_entry(enumerator); + for (l = devices; l; l = udev_list_entry_get_next(l)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), + udev_list_entry_get_name(l)); + if (!udevice) + continue; + + udev_device_added(platform, udevice); + udev_device_unref(udevice); + } + + udev_enumerate_unref(enumerator); + } +} + +/* Similar to systemd's path_is_read_only_fs(), at + * https://github.com/systemd/systemd/blob/v246/src/basic/stat-util.c#L132 */ +static int +path_is_read_only_fs(const char *path) +{ + struct statvfs st; + + if (statvfs(path, &st) < 0) + return -errno; + + if (st.f_flag & ST_RDONLY) + return TRUE; + + /* On NFS, statvfs() might not reflect whether we can actually + * write to the remote share. Let's try again with + * access(W_OK) which is more reliable, at least sometimes. */ + if (access(path, W_OK) < 0 && errno == EROFS) + return TRUE; + + return FALSE; +} + +NMPlatform * +nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support) +{ + gboolean use_udev = FALSE; + + if (nmp_netns_is_initial() && path_is_read_only_fs("/sys") == FALSE) + use_udev = TRUE; + + return g_object_new(NM_TYPE_LINUX_PLATFORM, + NM_PLATFORM_LOG_WITH_PTR, + log_with_ptr, + NM_PLATFORM_USE_UDEV, + use_udev, + NM_PLATFORM_NETNS_SUPPORT, + netns_support, + NULL); +} + +static void +dispose(GObject *object) +{ + NMPlatform * platform = NM_PLATFORM(object); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + _LOGD("dispose"); + + delayed_action_wait_for_nl_response_complete_all(platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING); + + priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE; + g_ptr_array_set_size(priv->delayed_action.list_master_connected, 0); + g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0); + + G_OBJECT_CLASS(nm_linux_platform_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(object); + + g_ptr_array_unref(priv->delayed_action.list_master_connected); + g_ptr_array_unref(priv->delayed_action.list_refresh_link); + g_array_unref(priv->delayed_action.list_wait_for_nl_response); + + nl_socket_free(priv->genl); + + nm_clear_g_source_inst(&priv->event_source); + + nl_socket_free(priv->nlh); + + if (priv->sysctl_get_prev_values) { + sysctl_clear_cache_list = g_slist_remove(sysctl_clear_cache_list, object); + g_hash_table_destroy(priv->sysctl_get_prev_values); + } + + priv->udev_client = nm_udev_client_destroy(priv->udev_client); + + G_OBJECT_CLASS(nm_linux_platform_parent_class)->finalize(object); +} + +static void +nm_linux_platform_class_init(NMLinuxPlatformClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMPlatformClass *platform_class = NM_PLATFORM_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + platform_class->sysctl_set = sysctl_set; + platform_class->sysctl_set_async = sysctl_set_async; + platform_class->sysctl_get = sysctl_get; + + platform_class->link_add = link_add; + platform_class->link_delete = link_delete; + + platform_class->link_refresh = link_refresh; + + platform_class->link_set_netns = link_set_netns; + + platform_class->link_set_up = link_set_up; + platform_class->link_set_down = link_set_down; + platform_class->link_set_arp = link_set_arp; + platform_class->link_set_noarp = link_set_noarp; + + platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled; + platform_class->link_set_token = link_set_token; + + platform_class->link_set_address = link_set_address; + platform_class->link_get_permanent_address = link_get_permanent_address; + platform_class->link_set_mtu = link_set_mtu; + platform_class->link_set_name = link_set_name; + platform_class->link_set_sriov_params_async = link_set_sriov_params_async; + platform_class->link_set_sriov_vfs = link_set_sriov_vfs; + platform_class->link_set_bridge_vlans = link_set_bridge_vlans; + + platform_class->link_get_physical_port_id = link_get_physical_port_id; + platform_class->link_get_dev_id = link_get_dev_id; + platform_class->link_get_wake_on_lan = link_get_wake_on_lan; + platform_class->link_get_driver_info = link_get_driver_info; + + platform_class->link_supports_carrier_detect = link_supports_carrier_detect; + platform_class->link_supports_vlans = link_supports_vlans; + platform_class->link_supports_sriov = link_supports_sriov; + + platform_class->link_enslave = link_enslave; + platform_class->link_release = link_release; + + platform_class->link_can_assume = link_can_assume; + + platform_class->link_vlan_change = link_vlan_change; + platform_class->link_wireguard_change = link_wireguard_change; + + platform_class->infiniband_partition_add = infiniband_partition_add; + platform_class->infiniband_partition_delete = infiniband_partition_delete; + + platform_class->wifi_get_capabilities = wifi_get_capabilities; + platform_class->wifi_get_frequency = wifi_get_frequency; + platform_class->wifi_get_station = wifi_get_station; + platform_class->wifi_get_mode = wifi_get_mode; + platform_class->wifi_set_mode = wifi_set_mode; + platform_class->wifi_set_powersave = wifi_set_powersave; + platform_class->wifi_find_frequency = wifi_find_frequency; + platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running; + platform_class->wifi_get_wake_on_wlan = wifi_get_wake_on_wlan; + platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan; + + platform_class->mesh_get_channel = mesh_get_channel; + platform_class->mesh_set_channel = mesh_set_channel; + platform_class->mesh_set_ssid = mesh_set_ssid; + + platform_class->wpan_get_pan_id = wpan_get_pan_id; + platform_class->wpan_set_pan_id = wpan_set_pan_id; + platform_class->wpan_get_short_addr = wpan_get_short_addr; + platform_class->wpan_set_short_addr = wpan_set_short_addr; + platform_class->wpan_set_channel = wpan_set_channel; + + platform_class->link_tun_add = link_tun_add; + + platform_class->object_delete = object_delete; + platform_class->ip4_address_add = ip4_address_add; + platform_class->ip6_address_add = ip6_address_add; + platform_class->ip4_address_delete = ip4_address_delete; + platform_class->ip6_address_delete = ip6_address_delete; + + platform_class->ip_route_add = ip_route_add; + platform_class->ip_route_get = ip_route_get; + + platform_class->routing_rule_add = routing_rule_add; + + platform_class->qdisc_add = qdisc_add; + platform_class->tfilter_add = tfilter_add; + + platform_class->process_events = process_events; +} diff --git a/src/core/platform/nm-linux-platform.h b/src/core/platform/nm-linux-platform.h new file mode 100644 index 0000000..6837e64 --- /dev/null +++ b/src/core/platform/nm-linux-platform.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_LINUX_PLATFORM_H__ +#define __NETWORKMANAGER_LINUX_PLATFORM_H__ + +#include "nm-platform.h" + +#define NM_TYPE_LINUX_PLATFORM (nm_linux_platform_get_type()) +#define NM_LINUX_PLATFORM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatform)) +#define NM_LINUX_PLATFORM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) +#define NM_IS_LINUX_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LINUX_PLATFORM)) +#define NM_IS_LINUX_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LINUX_PLATFORM)) +#define NM_LINUX_PLATFORM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) + +typedef struct _NMLinuxPlatform NMLinuxPlatform; +typedef struct _NMLinuxPlatformClass NMLinuxPlatformClass; + +GType nm_linux_platform_get_type(void); + +NMPlatform *nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support); + +void nm_linux_platform_setup(void); + +#endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */ diff --git a/src/core/platform/nm-platform-private.h b/src/core/platform/nm-platform-private.h new file mode 100644 index 0000000..cf80568 --- /dev/null +++ b/src/core/platform/nm-platform-private.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_PLATFORM_PRIVATE_H__ +#define __NM_PLATFORM_PRIVATE_H__ + +#include "nm-platform.h" +#include "nmp-object.h" + +NMPCache *nm_platform_get_cache(NMPlatform *self); + +#define NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform) \ + G_STMT_START \ + { \ + NMPlatform *_platform = (platform); \ + \ + nm_assert(NM_IS_PLATFORM(_platform)); \ + nm_assert(NM_IN_SET(nm_platform_netns_get(_platform), NULL, nmp_netns_get_current())); \ + } \ + G_STMT_END + +void nm_platform_cache_update_emit_signal(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new); + +#endif /* __NM_PLATFORM_PRIVATE_H__ */ diff --git a/src/core/platform/nm-platform.c b/src/core/platform/nm-platform.c new file mode 100644 index 0000000..6411abb --- /dev/null +++ b/src/core/platform/nm-platform.c @@ -0,0 +1,8966 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-udev-aux/nm-udev-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" + +#include "nm-core-utils.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-platform-private.h" +#include "nmp-object.h" +#include "nm-platform/nmp-netns.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Route, network)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Route, network)); + +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP4Route)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP6Route)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIPXRoute)); + +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP4Address)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP6Address)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIPXAddress)); + +/*****************************************************************************/ + +G_STATIC_ASSERT(sizeof(((NMPLinkAddress *) NULL)->data) == NM_UTILS_HWADDR_LEN_MAX); +G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_address.data) == NM_UTILS_HWADDR_LEN_MAX); +G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_broadcast.data) == NM_UTILS_HWADDR_LEN_MAX); + +static const char * +_nmp_link_address_to_string(const NMPLinkAddress *addr, + char buf[static(NM_UTILS_HWADDR_LEN_MAX * 3)]) +{ + nm_assert(addr); + + if (addr->len > 0) { + if (!_nm_utils_hwaddr_ntoa(addr->data, addr->len, TRUE, buf, NM_UTILS_HWADDR_LEN_MAX * 3)) { + buf[0] = '\0'; + g_return_val_if_reached(buf); + } + } else + buf[0] = '\0'; + + return buf; +} + +gconstpointer +nmp_link_address_get(const NMPLinkAddress *addr, size_t *length) +{ + if (!addr || addr->len <= 0) { + NM_SET_OUT(length, 0); + return NULL; + } + + if (addr->len > NM_UTILS_HWADDR_LEN_MAX) { + NM_SET_OUT(length, 0); + g_return_val_if_reached(NULL); + } + + NM_SET_OUT(length, addr->len); + return addr->data; +} + +GBytes * +nmp_link_address_get_as_bytes(const NMPLinkAddress *addr) +{ + gconstpointer data; + size_t length; + + data = nmp_link_address_get(addr, &length); + + return length > 0 ? g_bytes_new(data, length) : NULL; +} + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "platform" + +#define NMLOG_COMMON(level, name, ...) \ + char __prefix[32]; \ + const char * __p_prefix = _NMLOG_PREFIX_NAME; \ + const NMPlatform *const __self = (self); \ + const char * __name = name; \ + \ + if (__self && NM_PLATFORM_GET_PRIVATE(__self)->log_with_ptr) { \ + g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + __name, \ + NULL, \ + "%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix, \ + NM_PRINT_FMT_QUOTED(__name, "(", __name, ") ", "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, NULL, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, name, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _NMLOG3(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, \ + ifindex > 0 ? nm_platform_link_get_name(self, ifindex) : NULL, \ + __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = {0}; + +enum { + PROP_0, + PROP_NETNS_SUPPORT, + PROP_USE_UDEV, + PROP_LOG_WITH_PTR, + LAST_PROP, +}; + +typedef struct _NMPlatformPrivate { + bool use_udev : 1; + bool log_with_ptr : 1; + + guint ip4_dev_route_blacklist_check_id; + guint ip4_dev_route_blacklist_gc_timeout_id; + GHashTable * ip4_dev_route_blacklist_hash; + NMDedupMultiIndex *multi_idx; + NMPCache * cache; +} NMPlatformPrivate; + +G_DEFINE_TYPE(NMPlatform, nm_platform, G_TYPE_OBJECT) + +#define NM_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMPlatform, NM_IS_PLATFORM) + +/*****************************************************************************/ + +static void _ip4_dev_route_blacklist_schedule(NMPlatform *self); + +/*****************************************************************************/ + +gboolean +nm_platform_get_use_udev(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->use_udev; +} + +gboolean +nm_platform_get_log_with_ptr(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->log_with_ptr; +} + +/*****************************************************************************/ + +guint +_nm_platform_signal_id_get(NMPlatformSignalIdType signal_type) +{ + nm_assert(signal_type > 0 && signal_type != NM_PLATFORM_SIGNAL_ID_NONE + && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST); + + return signals[signal_type]; +} + +/*****************************************************************************/ + +/* Singleton NMPlatform subclass instance and cached class object */ +NM_DEFINE_SINGLETON_INSTANCE(NMPlatform); + +NM_DEFINE_SINGLETON_REGISTER(NMPlatform); + +/* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS() + * is only a plain read on the self instance, which the compiler + * like can optimize out. + */ +#define _CHECK_SELF_VOID(self, klass) \ + NMPlatformClass *klass; \ + do { \ + g_return_if_fail(NM_IS_PLATFORM(self)); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + } while (0) + +#define _CHECK_SELF(self, klass, err_val) \ + NMPlatformClass *klass; \ + do { \ + g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + } while (0) + +#define _CHECK_SELF_NETNS(self, klass, netns, err_val) \ + nm_auto_pop_netns NMPNetns *netns = NULL; \ + NMPlatformClass * klass; \ + do { \ + g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + if (!nm_platform_netns_push(self, &netns)) \ + return (err_val); \ + } while (0) + +/** + * nm_platform_setup: + * @instance: the #NMPlatform instance + * + * Failing to set up #NMPlatform singleton results in a fatal error, + * as well as trying to initialize it multiple times without freeing + * it. + * + * NetworkManager will typically use only one platform object during + * its run. Test programs might want to switch platform implementations, + * though. + */ +void +nm_platform_setup(NMPlatform *instance) +{ + g_return_if_fail(NM_IS_PLATFORM(instance)); + g_return_if_fail(!singleton_instance); + + singleton_instance = instance; + + nm_singleton_instance_register(); + + nm_log_dbg(LOGD_CORE, + "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + "NMPlatform", + NM_HASH_OBFUSCATE_PTR(instance)); +} + +/** + * nm_platform_get: + * @self: platform instance + * + * Retrieve #NMPlatform singleton. Use this whenever you want to connect to + * #NMPlatform signals. It is an error to call it before nm_platform_setup(). + * + * Returns: (transfer none): The #NMPlatform singleton reference. + */ +NMPlatform * +nm_platform_get() +{ + g_assert(singleton_instance); + + return singleton_instance; +} + +/*****************************************************************************/ + +NMDedupMultiIndex * +nm_platform_get_multi_idx(NMPlatform *self) +{ + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + + return NM_PLATFORM_GET_PRIVATE(self)->multi_idx; +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _nmp_nlm_flag_to_string_lookup, + NMPNlmFlags, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_ADD, "add"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_CHANGE, "change"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_REPLACE, "replace"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_PREPEND, "prepend"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_APPEND, "append"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_TEST, "test"), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_APPEND), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_FMASK), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_ECHO), ); + +#define _nmp_nlm_flag_to_string(flags) \ + ({ \ + NMPNlmFlags _flags = (flags); \ + \ + _nmp_nlm_flag_to_string_lookup(flags) \ + ?: nm_sprintf_bufa(100, "new[0x%x]", (unsigned) _flags); \ + }) + +/*****************************************************************************/ + +volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = {}; + +static const struct { + bool compile_time_default; + const char *name; + const char *desc; +} _nm_platform_kernel_support_info[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = { + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS] = + { + .compile_time_default = TRUE, + .name = "EXTENDED_IFA_FLAGS", + .desc = "IPv6 temporary addresses support", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL] = + { + .compile_time_default = TRUE, + .name = "USER_IPV6LL", + .desc = "IFLA_INET6_ADDR_GEN_MODE support", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF] = + { + .compile_time_default = (RTA_MAX >= 20 /* RTA_PREF */), + .name = "RTA_PREF", + .desc = "ability to set router preference for IPv6 routes", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV] = + { + .compile_time_default = (FRA_MAX >= 19 /* FRA_L3MDEV */), + .name = "FRA_L3MDEV", + .desc = "FRA_L3MDEV attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE] = + { + .compile_time_default = (FRA_MAX >= 20 /* FRA_UID_RANGE */), + .name = "FRA_UID_RANGE", + .desc = "FRA_UID_RANGE attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL] = + { + .compile_time_default = (FRA_MAX >= 21 /* FRA_PROTOCOL */), + .name = "FRA_PROTOCOL", + .desc = "FRA_PROTOCOL attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO] = + { + .compile_time_default = (FRA_MAX >= 22 /* FRA_IP_PROTO */), + .name = "FRA_IP_PROTO", + .desc = "FRA_IP_PROTO, FRA_SPORT_RANGE, FRA_DPORT_RANGE attributes for policy routing " + "rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED] = + { + .compile_time_default = (IFLA_BR_MAX >= 41 /* IFLA_BR_VLAN_STATS_ENABLED */), + .name = "IFLA_BR_VLAN_STATS_ENABLE", + .desc = "IFLA_BR_VLAN_STATS_ENABLE bridge link attribute", + }, +}; + +int +_nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value) +{ + volatile int *p_state; + gboolean set_default = FALSE; + + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + p_state = &_nm_platform_kernel_support_state[type]; + + if (value == 0) { + set_default = TRUE; + value = _nm_platform_kernel_support_info[type].compile_time_default ? 1 : -1; + } + + nm_assert(NM_IN_SET(value, -1, 1)); + + if (!g_atomic_int_compare_and_exchange(p_state, 0, value)) { + value = g_atomic_int_get(p_state); + nm_assert(NM_IN_SET(value, -1, 1)); + return value; + } + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 0 + + if (set_default) { + nm_log_dbg(LOGD_PLATFORM, + "platform: kernel-support for %s (%s) not detected: assume %ssupported", + _nm_platform_kernel_support_info[type].name, + _nm_platform_kernel_support_info[type].desc, + value >= 0 ? "" : "not "); + } else { + nm_log_dbg(LOGD_PLATFORM, + "platform: kernel-support for %s (%s) detected: %ssupported", + _nm_platform_kernel_support_info[type].name, + _nm_platform_kernel_support_info[type].desc, + value >= 0 ? "" : "not "); + } + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + + return value; +} + +/*****************************************************************************/ + +/** + * nm_platform_process_events: + * @self: platform instance + * + * Process pending events or handle pending delayed-actions. + * Effectively, this reads the netlink socket and processes + * new netlink messages. Possibly it will raise change signals. + */ +void +nm_platform_process_events(NMPlatform *self) +{ + _CHECK_SELF_VOID(self, klass); + + if (klass->process_events) + klass->process_events(self); +} + +const NMPlatformLink * +nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname) +{ + const NMPObject *obj; + gboolean refreshed = FALSE; + + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + + if (ifindex <= 0 && !ifname) + return NULL; + + /* we look into the cache, whether a link for given ifindex/ifname + * exits. If not, we poll the netlink socket, maybe the event + * with the link is waiting. + * + * Then we try again to find the object. + * + * If the link is already cached the first time, we avoid polling + * the netlink socket. */ +again: + obj = nmp_cache_lookup_link_full( + nm_platform_get_cache(self), + ifindex, + ifname, + FALSE, /* also invisible. We don't care here whether udev is ready */ + NM_LINK_TYPE_NONE, + NULL, + NULL); + if (obj) + return NMP_OBJECT_CAST_LINK(obj); + if (!refreshed) { + refreshed = TRUE; + nm_platform_process_events(self); + goto again; + } + + return NULL; +} + +/*****************************************************************************/ + +/** + * nm_platform_sysctl_open_netdir: + * @self: platform instance + * @ifindex: the ifindex for which to open /sys/class/net/%s + * @out_ifname: optional output argument of the found ifname. + * + * Wraps nmp_utils_sysctl_open_netdir() by first changing into the right + * network-namespace. + * + * Returns: on success, the open file descriptor to the /sys/class/net/%s + * directory. + */ +int +nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname) +{ + const char *ifname_guess; + _CHECK_SELF_NETNS(self, klass, netns, -1); + + g_return_val_if_fail(ifindex > 0, -1); + + /* we don't have an @ifname_guess argument to make the API nicer. + * But still do a cache-lookup first. Chances are good that we have + * the right ifname cached and save if_indextoname() */ + ifname_guess = nm_platform_link_get_name(self, ifindex); + + return nmp_utils_sysctl_open_netdir(ifindex, ifname_guess, out_ifname); +} + +/** + * nm_platform_sysctl_set: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: optional file descriptor for parent directory for openat() + * @path: Absolute option path + * @value: Value to write + * + * This function is intended to be used for writing values to sysctl-style + * virtual runtime configuration files. This includes not only /proc/sys + * but also for example /sys/class. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_sysctl_set(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(value, FALSE); + + return klass->sysctl_set(self, pathid, dirfd, path, value); +} + +/** + * nm_platform_sysctl_set_async: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up + * @dirfd: optional file descriptor for parent directory for openat() + * @path: absolute option path + * @values: NULL-terminated array of strings to be written + * @callback: function called on termination + * @data: data passed to callback function + * @cancellable: to cancel the operation + * + * This function is intended to be used for writing values to sysctl-style + * virtual runtime configuration files. This includes not only /proc/sys + * but also for example /sys/class. The function does not block and returns + * immediately. The callback is always invoked, and asynchronously. The file + * is closed after writing each value and reopened to write the next one so + * that the function can be used safely on all /proc and /sys files, + * independently of how /proc/sys/kernel/sysctl_writes_strict is configured. + */ +void +nm_platform_sysctl_set_async(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + _CHECK_SELF_VOID(self, klass); + + klass->sysctl_set_async(self, pathid, dirfd, path, values, callback, data, cancellable); +} + +gboolean +nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value) +{ + const char *path; + gint64 cur; + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + _CHECK_SELF(self, klass, FALSE); + + /* the hop-limit provided via RA is uint8. */ + if (value > 0xFF) + return FALSE; + + /* don't allow unreasonable small values */ + if (value < 10) + return FALSE; + + path = nm_utils_sysctl_ip_conf_path(AF_INET6, buf, iface, "hop_limit"); + cur = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_ABSOLUTE(path), + 10, + 1, + G_MAXINT32, + -1); + + /* only allow increasing the hop-limit to avoid DOS by an attacker + * setting a low hop-limit (CVE-2015-2924, rh#1209902) */ + + if (value < cur) + return FALSE; + if (value != cur) { + char svalue[20]; + + sprintf(svalue, "%d", value); + nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), svalue); + } + + return TRUE; +} + +gboolean +nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self, + const char *iface, + guint value_ms) +{ + char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char str[128]; + guint clamped; + + _CHECK_SELF(self, klass, FALSE); + + if (!value_ms) + return TRUE; + + /* RFC 4861 says the value can't be greater than one hour. + * Also use a reasonable lower threshold. */ + clamped = NM_CLAMP(value_ms, 100, 3600000); + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", iface); + nm_sprintf_buf(str, "%u", clamped); + if (!nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str)) + return FALSE; + + /* Set stale time in the same way as kernel */ + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/gc_stale_time", iface); + nm_sprintf_buf(str, "%u", clamped * 3 / 1000); + + return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str); +} + +gboolean +nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self, + const char *iface, + guint value_ms) +{ + char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char str[128]; + + _CHECK_SELF(self, klass, FALSE); + + if (!value_ms) + return TRUE; + + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/retrans_time_ms", iface); + nm_sprintf_buf(str, "%u", NM_CLAMP(value_ms, 10, 3600000)); + + return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str); +} + +/** + * nm_platform_sysctl_get: + * @self: platform instance + * @dirfd: if non-negative, used to lookup the path via openat(). + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @path: Absolute path to sysctl + * + * Returns: (transfer full): Contents of the virtual sysctl file. + * + * If the path does not exist, %NULL is returned and %errno set to %ENOENT. + */ +char * +nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(path, NULL); + + return klass->sysctl_get(self, pathid, dirfd, path); +} + +/** + * nm_platform_sysctl_get_int32: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: if non-negative, used to lookup the path via openat(). + * @path: Absolute path to sysctl + * @fallback: default value, if the content of path could not be read + * as decimal integer. + * + * Returns: contents of the sysctl file parsed as s32 integer, or + * @fallback on error. On error, %errno will be set to a non-zero + * value, on success %errno will be set to zero. + */ +gint32 +nm_platform_sysctl_get_int32(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + gint32 fallback) +{ + return nm_platform_sysctl_get_int_checked(self, + pathid, + dirfd, + path, + 10, + G_MININT32, + G_MAXINT32, + fallback); +} + +/** + * nm_platform_sysctl_get_int_checked: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: if non-negative, used to lookup the path via openat(). + * @path: Absolute path to sysctl + * @base: base of numeric conversion + * @min: minimal value that is still valid + * @max: maximal value that is still valid + * @fallback: default value, if the content of path could not be read + * as valid integer. + * + * Returns: contents of the sysctl file parsed as s64 integer, or + * @fallback on error. On error, %errno will be set to a non-zero + * value. On success, %errno will be set to zero. The returned value + * will always be in the range between @min and @max + * (inclusive) or @fallback. + * If the file does not exist, the fallback is returned and %errno + * is set to ENOENT. + */ +gint64 +nm_platform_sysctl_get_int_checked(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + char * value = NULL; + gint32 ret; + int errsv; + + _CHECK_SELF(self, klass, fallback); + + g_return_val_if_fail(path, fallback); + + if (!path) { + errno = EINVAL; + return fallback; + } + + value = nm_platform_sysctl_get(self, pathid, dirfd, path); + if (!value) { + /* nm_platform_sysctl_get() set errno to ENOENT if the file does not exist. + * Propagate/preserve that. */ + if (errno != ENOENT) + errno = EINVAL; + return fallback; + } + + ret = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback); + errsv = errno; + g_free(value); + errno = errsv; + return ret; +} + +/*****************************************************************************/ + +char * +nm_platform_sysctl_ip_conf_get(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_get( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property))); +} + +gint64 +nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_get_int_checked( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + base, + min, + max, + fallback); +} + +gboolean +nm_platform_sysctl_ip_conf_set(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + const char *value) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_set( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + value); +} + +gboolean +nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + gint64 value) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char s[64]; + + return nm_platform_sysctl_set( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + nm_sprintf_buf(s, "%" G_GINT64_FORMAT, value)); +} + +int +nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *self, + const char *ifname, + gboolean consider_all, + gboolean * out_due_to_all) +{ + int val, val_all; + + NM_SET_OUT(out_due_to_all, FALSE); + + if (!ifname) + return -1; + + val = nm_platform_sysctl_ip_conf_get_int_checked(self, + AF_INET, + ifname, + "rp_filter", + 10, + 0, + 2, + -1); + if (val == -1) + return -1; + + /* the effectively used value is the rp_filter sysctl value of MAX(all,ifname). + * Note that this is the numerical MAX(), despite rp_filter "1" being more strict + * than "2". */ + if (val < 2 && consider_all && !nm_streq(ifname, "all")) { + val_all = nm_platform_sysctl_ip_conf_get_int_checked(self, + AF_INET, + "all", + "rp_filter", + 10, + 0, + 2, + val); + if (val_all > val) { + val = val_all; + NM_SET_OUT(out_due_to_all, TRUE); + } + } + + return val; +} + +/*****************************************************************************/ + +static int +_link_get_all_presort(gconstpointer p_a, gconstpointer p_b, gpointer sort_by_name) +{ + const NMPlatformLink *a = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_a)); + const NMPlatformLink *b = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_b)); + + /* Loopback always first */ + if (a->ifindex == 1) + return -1; + if (b->ifindex == 1) + return 1; + + if (GPOINTER_TO_INT(sort_by_name)) { + /* Initialized links first */ + if (a->initialized > b->initialized) + return -1; + if (a->initialized < b->initialized) + return 1; + + return strcmp(a->name, b->name); + } else + return a->ifindex - b->ifindex; +} + +/** + * nm_platform_link_get_all: + * @self: platform instance + * @sort_by_name: whether to sort by name or ifindex. + * + * Retrieve a snapshot of configuration for all links at once. The result is + * owned by the caller and should be freed with g_ptr_array_unref(). + */ +GPtrArray * +nm_platform_link_get_all(NMPlatform *self, gboolean sort_by_name) +{ + gs_unref_ptrarray GPtrArray *links = NULL; + GPtrArray * result; + guint i, nresult; + gs_unref_hashtable GHashTable *unseen = NULL; + const NMPlatformLink * item; + NMPLookup lookup; + + _CHECK_SELF(self, klass, NULL); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + links = nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, &lookup), NULL, NULL); + if (!links) + return NULL; + + for (i = 0; i < links->len;) { + if (!nmp_object_is_visible(links->pdata[i])) + g_ptr_array_remove_index_fast(links, i); + else + i++; + } + + if (links->len == 0) + return NULL; + + /* first sort the links by their ifindex or name. Below we will sort + * further by moving children/slaves to the end. */ + g_ptr_array_sort_with_data(links, _link_get_all_presort, GINT_TO_POINTER(sort_by_name)); + + unseen = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + nm_assert(item->ifindex > 0); + if (!g_hash_table_insert(unseen, GINT_TO_POINTER(item->ifindex), NULL)) + nm_assert_not_reached(); + } + +#if NM_MORE_ASSERTS + /* Ensure that link_get_all returns a consistent and valid result. */ + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + + if (!item->ifindex) + continue; + if (item->master != 0) { + g_warn_if_fail(item->master > 0); + g_warn_if_fail(item->master != item->ifindex); + g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->master))); + } + if (item->parent != 0) { + if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) { + g_warn_if_fail(item->parent > 0); + g_warn_if_fail(item->parent != item->ifindex); + g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent))); + } + } + } +#endif + + /* Re-order the links list such that children/slaves come after all ancestors */ + nm_assert(g_hash_table_size(unseen) == links->len); + nresult = links->len; + result = g_ptr_array_new_full(nresult, (GDestroyNotify) nmp_object_unref); + + while (TRUE) { + gboolean found_something = FALSE; + guint first_idx = G_MAXUINT; + + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + + if (!item) + continue; + + g_assert(g_hash_table_contains(unseen, GINT_TO_POINTER(item->ifindex))); + + if (item->master > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->master))) + goto skip; + if (item->parent > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent))) + goto skip; + + g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex)); + g_ptr_array_add(result, links->pdata[i]); + links->pdata[i] = NULL; + found_something = TRUE; + continue; +skip: + if (first_idx == G_MAXUINT) + first_idx = i; + } + + if (found_something) { + if (first_idx == G_MAXUINT) + break; + } else { + nm_assert(first_idx != G_MAXUINT); + /* There is a loop, pop the first (remaining) element from the list. + * This can happen for veth pairs where each peer is parent of the other end. */ + item = NMP_OBJECT_CAST_LINK(links->pdata[first_idx]); + nm_assert(item); + g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex)); + g_ptr_array_add(result, links->pdata[first_idx]); + links->pdata[first_idx] = NULL; + } + nm_assert(result->len < nresult); + } + nm_assert(result->len == nresult); + + return result; +} + +/*****************************************************************************/ + +const NMPObject * +nm_platform_link_get_obj(NMPlatform *self, int ifindex, gboolean visible_only) +{ + const NMPObject *obj_cache; + + _CHECK_SELF(self, klass, NULL); + + obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(self), ifindex); + if (!obj_cache || (visible_only && !nmp_object_is_visible(obj_cache))) + return NULL; + return obj_cache; +} + +/*****************************************************************************/ + +/** + * nm_platform_link_get: + * @self: platform instance + * @ifindex: ifindex of the link + * + * Lookup the internal NMPlatformLink object. + * + * Returns: %NULL, if such a link exists or the internal + * platform link object. Do not modify the returned value. + * Also, be aware that any subsequent platform call might + * invalidate/modify the returned instance. + **/ +const NMPlatformLink * +nm_platform_link_get(NMPlatform *self, int ifindex) +{ + return NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, TRUE)); +} + +/** + * nm_platform_link_get_by_ifname: + * @self: platform instance + * @ifname: the ifname + * + * Returns: the first #NMPlatformLink instance with the given name. + **/ +const NMPlatformLink * +nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname) +{ + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + if (!ifname || !*ifname) + return NULL; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self), + 0, + ifname, + TRUE, + NM_LINK_TYPE_NONE, + NULL, + NULL); + return NMP_OBJECT_CAST_LINK(obj); +} + +struct _nm_platform_link_get_by_address_data { + gconstpointer data; + guint8 len; +}; + +static gboolean +_nm_platform_link_get_by_address_match_link(const NMPObject * obj, + struct _nm_platform_link_get_by_address_data *d) +{ + return obj->link.l_address.len == d->len && !memcmp(obj->link.l_address.data, d->data, d->len); +} + +/** + * nm_platform_link_get_by_address: + * @self: platform instance + * @address: a pointer to the binary hardware address + * @length: the size of @address in bytes + * + * Returns: the first #NMPlatformLink object with a matching + * address. + **/ +const NMPlatformLink * +nm_platform_link_get_by_address(NMPlatform * self, + NMLinkType link_type, + gconstpointer address, + size_t length) +{ + const NMPObject * obj; + struct _nm_platform_link_get_by_address_data d = { + .data = address, + .len = length, + }; + + _CHECK_SELF(self, klass, NULL); + + if (length == 0) + return NULL; + + if (length > NM_UTILS_HWADDR_LEN_MAX) + g_return_val_if_reached(NULL); + if (!address) + g_return_val_if_reached(NULL); + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self), + 0, + NULL, + TRUE, + link_type, + (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, + &d); + return NMP_OBJECT_CAST_LINK(obj); +} + +static int +_link_add_check_existing(NMPlatform * self, + const char * name, + NMLinkType type, + const NMPlatformLink **out_link) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get_by_ifname(self, name); + if (pllink) { + gboolean wrong_type; + + wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type; + _LOG2D("link: skip adding link due to existing interface of type %s%s%s", + nm_link_type_to_string(pllink->type), + wrong_type ? ", expected " : "", + wrong_type ? nm_link_type_to_string(type) : ""); + if (out_link) + *out_link = pllink; + if (wrong_type) + return -NME_PL_WRONG_TYPE; + return -NME_PL_EXISTS; + } + if (out_link) + *out_link = NULL; + return 0; +} + +/** + * nm_platform_link_add: + * @self: platform instance + * @type: Interface type + * @name: Interface name + * @parent: the IFLA_LINK parameter or 0. + * @address: (allow-none): set the mac address of the link + * @address_len: the length of the @address + * @extra_data: depending on @type, additional data. + * @out_link: on success, the link object + * + * Add a software interface. If the interface already exists and is of type + * @type, return -NME_PL_EXISTS and returns the link + * in @out_link. If the interface already exists and is not of type @type, + * return -NME_PL_WRONG_TYPE. + * + * Any link-changed ADDED signal will be emitted directly, before this + * function finishes. + * + * Returns: the negative nm-error on failure. + */ +int +nm_platform_link_add(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link) +{ + int r; + char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3]; + char mtu_buf[16]; + char parent_buf[64]; + char buf[512]; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(name, -NME_BUG); + g_return_val_if_fail((address != NULL) ^ (address_len == 0), -NME_BUG); + g_return_val_if_fail(address_len <= NM_UTILS_HWADDR_LEN_MAX, -NME_BUG); + g_return_val_if_fail(parent >= 0, -NME_BUG); + + r = _link_add_check_existing(self, name, type, out_link); + if (r < 0) + return r; + + _LOG2D("link: adding link: " + "%s " /* type */ + "\"%s\"" /* name */ + "%s%s" /* parent */ + "%s%s" /* address */ + "%s%s" /* mtu */ + "%s" /* extra_data */ + "", + nm_link_type_to_string(type), + name, + parent > 0 ? ", parent " : "", + parent > 0 ? nm_sprintf_buf(parent_buf, "%d", parent) : "", + address ? ", address: " : "", + address ? _nm_utils_hwaddr_ntoa(address, address_len, FALSE, addr_buf, sizeof(addr_buf)) + : "", + mtu ? ", mtu: " : "", + mtu ? nm_sprintf_buf(mtu_buf, "%u", mtu) : "", + ({ + char *buf_p = buf; + gsize buf_len = sizeof(buf); + + buf[0] = '\0'; + + switch (type) { + case NM_LINK_TYPE_BRIDGE: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_bridge_to_string((const NMPlatformLnkBridge *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VLAN: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vlan_to_string((const NMPlatformLnkVlan *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VRF: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vrf_to_string((const NMPlatformLnkVrf *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VXLAN: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vxlan_to_string((const NMPlatformLnkVxlan *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VETH: + nm_sprintf_buf(buf, ", veth-peer \"%s\"", (const char *) extra_data); + break; + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_gre_to_string((const NMPlatformLnkGre *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_SIT: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_sit_to_string((const NMPlatformLnkSit *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_IP6TNL: + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_ip6tnl_to_string((const NMPlatformLnkIp6Tnl *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_IPIP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_ipip_to_string((const NMPlatformLnkIpIp *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_MACSEC: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_macsec_to_string((const NMPlatformLnkMacsec *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_MACVLAN: + case NM_LINK_TYPE_MACVTAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_macvlan_to_string((const NMPlatformLnkMacvlan *) extra_data, + buf_p, + buf_len); + break; + default: + nm_assert(!extra_data); + break; + } + + buf; + })); + + return klass + ->link_add(self, type, name, parent, address, address_len, mtu, extra_data, out_link); +} + +/** + * nm_platform_link_delete: + * @self: platform instance + * @ifindex: Interface index + */ +gboolean +nm_platform_link_delete(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: deleting"); + return klass->link_delete(self, ifindex); +} + +/** + * nm_platform_link_set_netns: + * @self: platform instance + * @ifindex: Interface index + * @netns_fd: the file descriptor for the new netns. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(netns_fd > 0, FALSE); + + _LOG3D("link: move link to network namespace with fd %d", netns_fd); + return klass->link_set_netns(self, ifindex, netns_fd); +} + +/** + * nm_platform_link_get_index: + * @self: platform instance + * @name: Interface name + * + * Returns: The interface index corresponding to the given interface name + * or 0. Interface name is owned by #NMPlatform, don't free it. + */ +int +nm_platform_link_get_ifindex(NMPlatform *self, const char *name) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get_by_ifname(self, name); + return pllink ? pllink->ifindex : 0; +} + +const char * +nm_platform_if_indextoname(NMPlatform *self, int ifindex, char out_ifname[static 16 /* IFNAMSIZ */]) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + return nmp_utils_if_indextoname(ifindex, out_ifname); +} + +int +nm_platform_if_nametoindex(NMPlatform *self, const char *ifname) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + return nmp_utils_if_nametoindex(ifname); +} + +/** + * nm_platform_link_get_name: + * @self: platform instance + * @name: Interface name + * + * Returns: The interface name corresponding to the given interface index + * or %NULL. + */ +const char * +nm_platform_link_get_name(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->name : NULL; +} + +/** + * nm_platform_link_get_type: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: Link type constant as defined in nm-platform.h. On error, + * NM_LINK_TYPE_NONE is returned. + */ +NMLinkType +nm_platform_link_get_type(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->type : NM_LINK_TYPE_NONE; +} + +/** + * nm_platform_link_get_type_name: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: A string describing the type of link. In some cases this + * may be more specific than nm_platform_link_get_type(), but in + * other cases it may not. On error, %NULL is returned. + */ +const char * +nm_platform_link_get_type_name(NMPlatform *self, int ifindex) +{ + const NMPObject *obj; + + obj = nm_platform_link_get_obj(self, ifindex, TRUE); + if (!obj) + return NULL; + + if (obj->link.type != NM_LINK_TYPE_UNKNOWN) { + /* We could detect the @link_type. In this case the function returns + * our internal module names, which differs from rtnl_link_get_type(): + * - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib") + * - NM_LINK_TYPE_TAP (gives "tap", instead of "tun"). + * Note that this functions is only used by NMDeviceGeneric to + * set type_description. */ + return nm_link_type_to_string(obj->link.type); + } + /* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */ + return obj->link.kind ?: "unknown"; +} + +static gboolean +link_get_udev_property(NMPlatform *self, int ifindex, const char *name, const char **out_value) +{ + struct udev_device *udevice = NULL; + const char * uproperty; + + udevice = nm_platform_link_get_udev_device(self, ifindex); + if (!udevice) + return FALSE; + + uproperty = udev_device_get_property_value(udevice, name); + if (!uproperty) + return FALSE; + + NM_SET_OUT(out_value, uproperty); + return TRUE; +} + +/** + * nm_platform_link_get_unmanaged: + * @self: platform instance + * @ifindex: interface index + * @unmanaged: management status (in case %TRUE is returned) + * + * Returns: %TRUE if platform overrides NM default-unmanaged status, + * %FALSE otherwise (with @unmanaged unmodified). + */ +gboolean +nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged) +{ + const char *value; + + if (link_get_udev_property(self, ifindex, "NM_UNMANAGED", &value)) { + NM_SET_OUT(unmanaged, nm_udev_utils_property_as_boolean(value)); + return TRUE; + } + + return FALSE; +} + +/** + * nm_platform_link_is_software: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: %TRUE if ifindex belongs to a software interface, not backed by + * a physical device. + */ +gboolean +nm_platform_link_is_software(NMPlatform *self, int ifindex) +{ + return nm_link_type_is_software(nm_platform_link_get_type(self, ifindex)); +} + +/** + * nm_platform_link_supports_slaves: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: %TRUE if ifindex belongs to an interface capable of enslaving + * other interfaces. + */ +gboolean +nm_platform_link_supports_slaves(NMPlatform *self, int ifindex) +{ + return nm_link_type_supports_slaves(nm_platform_link_get_type(self, ifindex)); +} + +/** + * nm_platform_link_refresh: + * @self: platform instance + * @ifindex: Interface index + * + * Reload the cache for ifindex synchronously. + */ +gboolean +nm_platform_link_refresh(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (klass->link_refresh) + return klass->link_refresh(self, ifindex); + + return TRUE; +} + +int +nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags) +{ + const NMPlatformLink *pllink; + + /* include invisible links (only in netlink, not udev). */ + pllink = NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, FALSE)); + if (!pllink) + return -ENODEV; + + /* Errors are signaled as negative values. That means, you cannot request + * the most significant bit (2^31) with this API. Assert against that. */ + nm_assert((int) requested_flags >= 0); + nm_assert(requested_flags < (guint) G_MAXINT); + + return (int) (pllink->n_ifi_flags & requested_flags); +} + +/** + * nm_platform_link_is_up: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is up. + */ +gboolean +nm_platform_link_is_up(NMPlatform *self, int ifindex) +{ + return nm_platform_link_get_ifi_flags(self, ifindex, IFF_UP) == IFF_UP; +} + +/** + * nm_platform_link_is_connected: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is connected. + */ +gboolean +nm_platform_link_is_connected(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->connected : FALSE; +} + +/** + * nm_platform_link_uses_arp: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is configured to use ARP. + */ +gboolean +nm_platform_link_uses_arp(NMPlatform *self, int ifindex) +{ + int f; + + f = nm_platform_link_get_ifi_flags(self, ifindex, IFF_NOARP); + + if (f < 0) + return FALSE; + if (f == IFF_NOARP) + return FALSE; + return TRUE; +} + +/** + * nm_platform_link_set_ipv6_token: + * @self: platform instance + * @ifindex: Interface index + * @iid: Tokenized interface identifier + * + * Sets then IPv6 tokenized interface identifier. + * + * Returns: %TRUE a tokenized identifier was available + */ +gboolean +nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + if (klass->link_set_token) + return klass->link_set_token(self, ifindex, iid); + return FALSE; +} + +const char * +nm_platform_link_get_udi(NMPlatform *self, int ifindex) +{ + struct udev_device *device; + + device = nm_platform_link_get_udev_device(self, ifindex); + return device ? udev_device_get_syspath(device) : NULL; +} + +const char * +nm_platform_link_get_path(NMPlatform *self, int ifindex) +{ + const char *value = NULL; + + link_get_udev_property(self, ifindex, "ID_PATH", &value); + + return value; +} + +struct udev_device * +nm_platform_link_get_udev_device(NMPlatform *self, int ifindex) +{ + const NMPObject *obj_cache; + + obj_cache = nm_platform_link_get_obj(self, ifindex, FALSE); + return obj_cache ? obj_cache->_link.udev.device : NULL; +} + +/** + * nm_platform_link_get_user_ip6vll_enabled: + * @self: platform instance + * @ifindex: Interface index + * + * Check whether NM handles IPv6LL address creation for the link. If the + * platform or OS doesn't support changing the IPv6LL address mode, this call + * will fail and return %FALSE. + * + * Returns: %TRUE if NM handles the IPv6LL address for @ifindex + */ +gboolean +nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + if (pllink && pllink->inet6_addr_gen_mode_inv) + return _nm_platform_uint8_inv(pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE; + return FALSE; +} + +/** + * nm_platform_link_set_user_ip6vll_enabled: + * @self: platform instance + * @ifindex: Interface index + * + * Set whether NM handles IPv6LL address creation for the link. If the + * platform or OS doesn't support changing the IPv6LL address mode, this call + * will fail and return %FALSE. + * + * Returns: the negative nm-error on failure. + */ +int +nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex > 0, -NME_BUG); + + return klass->link_set_user_ipv6ll_enabled(self, ifindex, enabled); +} + +/** + * nm_platform_link_set_address: + * @self: platform instance + * @ifindex: Interface index + * @address: The new MAC address + * + * Set interface MAC address. + */ +int +nm_platform_link_set_address(NMPlatform *self, int ifindex, gconstpointer address, size_t length) +{ + gs_free char *mac = NULL; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex > 0, -NME_BUG); + g_return_val_if_fail(address, -NME_BUG); + g_return_val_if_fail(length > 0, -NME_BUG); + + _LOG3D("link: setting hardware address to %s", (mac = nm_utils_hwaddr_ntoa(address, length))); + return klass->link_set_address(self, ifindex, address, length); +} + +/** + * nm_platform_link_get_address: + * @self: platform instance + * @ifindex: Interface index + * @length: Pointer to a variable to store address length + * + * Returns: the interface hardware address as an array of bytes of + * length @length. + */ +gconstpointer +nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return nmp_link_address_get(pllink ? &pllink->l_address : NULL, length); +} + +/** + * nm_platform_link_get_permanent_address: + * @self: platform instance + * @ifindex: Interface index + * @buf: buffer of at least %NM_UTILS_HWADDR_LEN_MAX bytes, on success + * the permanent hardware address + * @length: Pointer to a variable to store address length + * + * Returns: %TRUE on success, %FALSE on failure to read the permanent hardware + * address. + */ +gboolean +nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length) +{ + _CHECK_SELF(self, klass, FALSE); + + if (length) + *length = 0; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(buf, FALSE); + g_return_val_if_fail(length, FALSE); + + if (klass->link_get_permanent_address) + return klass->link_get_permanent_address(self, ifindex, buf, length); + return FALSE; +} + +gboolean +nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_carrier_detect(self, ifindex); +} + +gboolean +nm_platform_link_supports_vlans(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_vlans(self, ifindex); +} + +gboolean +nm_platform_link_supports_sriov(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_sriov(self, ifindex); +} + +/** + * nm_platform_link_set_sriov_params: + * @self: platform instance + * @ifindex: the index of the interface to change + * @num_vfs: the number of VFs to create + * @autoprobe: the new autoprobe-drivers value (pass + * %NM_OPTION_BOOL_DEFAULT to keep current value) + * @callback: called when the operation finishes + * @callback_data: data passed to @callback + * @cancellable: cancellable to abort the operation + * + * Sets SR-IOV parameters asynchronously without + * blocking the main thread. The callback function is + * always invoked, and asynchronously. + */ +void +nm_platform_link_set_sriov_params_async(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + _LOG3D("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe); + klass->link_set_sriov_params_async(self, + ifindex, + num_vfs, + autoprobe, + callback, + callback_data, + cancellable); +} + +gboolean +nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs) +{ + guint i; + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: setting VFs"); + for (i = 0; vfs[i]; i++) { + const NMPlatformVF *vf = vfs[i]; + + _LOG3D("link: VF %s", nm_platform_vf_to_string(vf, NULL, 0)); + } + + return klass->link_set_sriov_vfs(self, ifindex, vfs); +} + +gboolean +nm_platform_link_set_bridge_vlans(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans) +{ + guint i; + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: %s bridge VLANs on %s", + vlans ? "setting" : "clearing", + on_master ? "master" : "self"); + if (vlans) { + for (i = 0; vlans[i]; i++) { + const NMPlatformBridgeVlan *vlan = vlans[i]; + + _LOG3D("link: bridge VLAN %s", nm_platform_bridge_vlan_to_string(vlan, NULL, 0)); + } + } + + return klass->link_set_bridge_vlans(self, ifindex, on_master, vlans); +} + +/** + * nm_platform_link_set_up: + * @self: platform instance + * @ifindex: Interface index + * @out_no_firmware: (allow-none): if the failure reason is due to missing firmware. + * + * Bring the interface up. + */ +gboolean +nm_platform_link_set_up(NMPlatform *self, int ifindex, gboolean *out_no_firmware) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: setting up"); + return klass->link_set_up(self, ifindex, out_no_firmware); +} + +/** + * nm_platform_link_set_down: + * @self: platform instance + * @ifindex: Interface index + * + * Take the interface down. + */ +gboolean +nm_platform_link_set_down(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: setting down"); + return klass->link_set_down(self, ifindex); +} + +/** + * nm_platform_link_set_arp: + * @self: platform instance + * @ifindex: Interface index + * + * Enable ARP on the interface. + */ +gboolean +nm_platform_link_set_arp(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + _LOG3D("link: setting arp"); + return klass->link_set_arp(self, ifindex); +} + +/** + * nm_platform_link_set_noarp: + * @self: platform instance + * @ifindex: Interface index + * + * Disable ARP on the interface. + */ +gboolean +nm_platform_link_set_noarp(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + _LOG3D("link: setting noarp"); + return klass->link_set_noarp(self, ifindex); +} + +/** + * nm_platform_link_set_mtu: + * @self: platform instance + * @ifindex: Interface index + * @mtu: The new MTU value + * + * Set interface MTU. + */ +int +nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + g_return_val_if_fail(mtu > 0, FALSE); + + _LOG3D("link: setting mtu %" G_GUINT32_FORMAT, mtu); + return klass->link_set_mtu(self, ifindex, mtu); +} + +/** + * nm_platform_link_get_mtu: + * @self: platform instance + * @ifindex: Interface index + * + * Returns: MTU value for the interface or 0 on error. + */ +guint32 +nm_platform_link_get_mtu(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->mtu : 0; +} + +/** + * nm_platform_link_set_name: + * @self: platform instance + * @ifindex: Interface index + * @name: The new interface name + * + * Set interface name. + */ +gboolean +nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + g_return_val_if_fail(name, FALSE); + + _LOG3D("link: setting name %s", name); + + if (strlen(name) + 1 > IFNAMSIZ) + return FALSE; + + return klass->link_set_name(self, ifindex, name); +} + +/** + * nm_platform_link_get_physical_port_id: + * @self: platform instance + * @ifindex: Interface index + * + * The physical port ID, if present, indicates some unique identifier of + * the parent interface (eg, the physical port of which this link is a child). + * Two links that report the same physical port ID can be assumed to be + * children of the same physical port and may share resources that limit + * their abilities. + * + * Returns: physical port ID for the interface, or %NULL on error + * or if the interface has no physical port ID. + */ +char * +nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex >= 0, NULL); + + if (klass->link_get_physical_port_id) + return klass->link_get_physical_port_id(self, ifindex); + return NULL; +} + +/** + * nm_platform_link_get_dev_id: + * @self: platform instance + * @ifindex: Interface index + * + * In contrast to the physical device ID (which indicates which parent a + * child has) the device ID differentiates sibling devices that may share + * the same MAC address. + * + * Returns: device ID for the interface, or 0 on error or if the + * interface has no device ID. + */ +guint +nm_platform_link_get_dev_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex >= 0, 0); + + if (klass->link_get_dev_id) + return klass->link_get_dev_id(self, ifindex); + return 0; +} + +/** + * nm_platform_link_get_wake_onlan: + * @self: platform instance + * @ifindex: Interface index + * + * Returns: the "Wake-on-LAN" status for @ifindex. + */ +gboolean +nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + if (klass->link_get_wake_on_lan) + return klass->link_get_wake_on_lan(self, ifindex); + return FALSE; +} + +/** + * nm_platform_link_get_driver_info: + * @self: platform instance + * @ifindex: Interface index + * @out_driver_name: (transfer full): on success, the driver name if available + * @out_driver_version: (transfer full): on success, the driver version if available + * @out_fw_version: (transfer full): on success, the firmware version if available + * + * Returns: %TRUE on success (though @out_driver_name, @out_driver_version and + * @out_fw_version can be %NULL if no information was available), %FALSE on + * failure. + */ +gboolean +nm_platform_link_get_driver_info(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_get_driver_info(self, + ifindex, + out_driver_name, + out_driver_version, + out_fw_version); +} + +/** + * nm_platform_link_enslave: + * @self: platform instance + * @master: Interface index of the master + * @ifindex: Interface index of the slave + * + * Enslave @ifindex to @master. + */ +gboolean +nm_platform_link_enslave(NMPlatform *self, int master, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(master > 0, FALSE); + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: enslaving to master '%s'", nm_platform_link_get_name(self, master)); + return klass->link_enslave(self, master, ifindex); +} + +/** + * nm_platform_link_release: + * @self: platform instance + * @master: Interface index of the master + * @ifindex: Interface index of the slave + * + * Release @slave from @master. + */ +gboolean +nm_platform_link_release(NMPlatform *self, int master, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(master > 0, FALSE); + g_return_val_if_fail(ifindex > 0, FALSE); + + if (nm_platform_link_get_master(self, ifindex) != master) + return FALSE; + + _LOG3D("link: releasing from master '%s'", nm_platform_link_get_name(self, master)); + return klass->link_release(self, master, ifindex); +} + +/** + * nm_platform_link_get_master: + * @self: platform instance + * @slave: Interface index of the slave. + * + * Returns: Interface index of the slave's master. + */ +int +nm_platform_link_get_master(NMPlatform *self, int slave) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, slave); + return pllink ? pllink->master : 0; +} + +/*****************************************************************************/ + +gboolean +nm_platform_link_can_assume(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + if (klass->link_can_assume) + return klass->link_can_assume(self, ifindex); + g_return_val_if_reached(FALSE); +} + +/*****************************************************************************/ + +/** + * nm_platform_link_get_lnk: + * @self: the platform instance + * @ifindex: the link ifindex to lookup + * @link_type: filter by link-type. + * @out_link: (allow-none): returns the platform link instance + * + * If the function returns %NULL, that could mean that no such ifindex + * exists, of that the link has no lnk data. You can find that out + * by checking @out_link. @out_link will always be set if a link + * with @ifindex exists. + * + * If @link_type is %NM_LINK_TYPE_NONE, the function returns the lnk + * object if it is present. If you set link-type, you can be sure + * that only a link type of the matching type is returned (or %NULL). + * + * Returns: the internal link lnk object. The returned object + * is owned by the platform cache and must not be modified. Note + * however, that the object is guaranteed to be immutable, so + * you can safely take a reference and keep it for yourself + * (but don't modify it). + */ +const NMPObject * +nm_platform_link_get_lnk(NMPlatform * self, + int ifindex, + NMLinkType link_type, + const NMPlatformLink **out_link) +{ + const NMPObject *obj; + + obj = nm_platform_link_get_obj(self, ifindex, TRUE); + if (!obj) { + NM_SET_OUT(out_link, NULL); + return NULL; + } + + NM_SET_OUT(out_link, &obj->link); + + if (!obj->_link.netlink.lnk) + return NULL; + if (link_type != NM_LINK_TYPE_NONE + && (link_type != obj->link.type + || link_type != NMP_OBJECT_GET_CLASS(obj->_link.netlink.lnk)->lnk_link_type)) + return NULL; + + return obj->_link.netlink.lnk; +} + +static gconstpointer +_link_get_lnk(NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link) +{ + const NMPObject *lnk; + + lnk = nm_platform_link_get_lnk(self, ifindex, link_type, out_link); + return lnk ? &lnk->object : NULL; +} + +const NMPlatformLnkBridge * +nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_BRIDGE, out_link); +} + +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRE, out_link); +} + +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRETAP, out_link); +} + +const NMPlatformLnkInfiniband * +nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_INFINIBAND, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6TNL, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRE, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRETAP, out_link); +} + +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IPIP, out_link); +} + +const NMPlatformLnkMacsec * +nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACSEC, out_link); +} + +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACVLAN, out_link); +} + +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvtap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACVTAP, out_link); +} + +const NMPlatformLnkSit * +nm_platform_link_get_lnk_sit(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_SIT, out_link); +} + +const NMPlatformLnkTun * +nm_platform_link_get_lnk_tun(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_TUN, out_link); +} + +const NMPlatformLnkVlan * +nm_platform_link_get_lnk_vlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VLAN, out_link); +} + +const NMPlatformLnkVrf * +nm_platform_link_get_lnk_vrf(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VRF, out_link); +} + +const NMPlatformLnkVxlan * +nm_platform_link_get_lnk_vxlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VXLAN, out_link); +} + +const NMPlatformLnkWireGuard * +nm_platform_link_get_lnk_wireguard(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link); +} + +/*****************************************************************************/ + +static NM_UTILS_FLAGS2STR_DEFINE( + _wireguard_change_flags_to_string, + NMPlatformWireGuardChangeFlags, + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS, "replace-peers"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT, "has-listen-port"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK, "has-fwmark"), ); + +static NM_UTILS_FLAGS2STR_DEFINE( + _wireguard_change_peer_flags_to_string, + NMPlatformWireGuardChangePeerFlags, + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME, "remove"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY, "psk"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL, "ka"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT, "ep"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS, "aips"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS, "remove-aips"), ); + +int +nm_platform_link_wireguard_change(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + nm_assert(klass->link_wireguard_change); + + if (_LOGD_ENABLED()) { + char buf_lnk[256]; + char buf_peers[512]; + char buf_change_flags[100]; + + buf_peers[0] = '\0'; + if (peers_len > 0) { + char *b = buf_peers; + gsize len = sizeof(buf_peers); + guint i; + + nm_utils_strbuf_append_str(&b, &len, " { "); + for (i = 0; i < peers_len; i++) { + nm_utils_strbuf_append_str(&b, &len, " { "); + nm_platform_wireguard_peer_to_string(&peers[i], b, len); + nm_utils_strbuf_seek_end(&b, &len); + if (peer_flags) { + nm_utils_strbuf_append( + &b, + &len, + " (%s)", + _wireguard_change_peer_flags_to_string(peer_flags[i], + buf_change_flags, + sizeof(buf_change_flags))); + } + nm_utils_strbuf_append_str(&b, &len, " } "); + } + nm_utils_strbuf_append_str(&b, &len, "}"); + } + + _LOG3D("link: change wireguard ifindex %d, %s, (%s), %u peers%s", + ifindex, + nm_platform_lnk_wireguard_to_string(lnk_wireguard, buf_lnk, sizeof(buf_lnk)), + _wireguard_change_flags_to_string(change_flags, + buf_change_flags, + sizeof(buf_change_flags)), + peers_len, + buf_peers); + } + + return klass->link_wireguard_change(self, + ifindex, + lnk_wireguard, + peers, + peer_flags, + peers_len, + change_flags); +} + +/*****************************************************************************/ + +/** + * nm_platform_link_tun_add: + * @self: platform instance + * @name: new interface name + * @tap: whether the interface is a TAP + * @owner: interface owner or -1 + * @group: interface group or -1 + * @pi: whether to clear the IFF_NO_PI flag + * @vnet_hdr: whether to set the IFF_VNET_HDR flag + * @multi_queue: whether to set the IFF_MULTI_QUEUE flag + * @out_link: on success, the link object + * @out_fd: (allow-none): if give, return the file descriptor for the + * created device. Note that when creating a non-persistent device, + * this argument is mandatory, otherwise it makes no sense + * to create such an interface. + * The caller is responsible for closing this file descriptor. + * + * Create a TUN or TAP interface. + */ +int +nm_platform_link_tun_add(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd) +{ + char b[255]; + int r; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(name, -NME_BUG); + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(NM_IN_SET(props->type, IFF_TUN, IFF_TAP), -NME_BUG); + + /* creating a non-persistent device requires that the caller handles + * the file descriptor. */ + g_return_val_if_fail(props->persist || out_fd, -NME_BUG); + + NM_SET_OUT(out_fd, -1); + + r = _link_add_check_existing(self, name, NM_LINK_TYPE_TUN, out_link); + if (r < 0) + return r; + + _LOG2D("link: adding link %s", nm_platform_lnk_tun_to_string(props, b, sizeof(b))); + + if (!klass->link_tun_add(self, name, props, out_link, out_fd)) + return -NME_UNSPEC; + return 0; +} + +gboolean +nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent) +{ + const NMPlatformLink *plink; + + plink = nm_platform_link_get(self, ifindex); + if (!plink) + return FALSE; + + if (plink->type != NM_LINK_TYPE_6LOWPAN) + return FALSE; + + if (plink->parent != 0) { + NM_SET_OUT(out_parent, plink->parent); + return TRUE; + } + + /* As of 4.16 kernel does not expose the peer_ifindex as IFA_LINK. + * Find the WPAN device with the same MAC address. */ + if (out_parent) { + const NMPlatformLink *parent_plink; + + parent_plink = nm_platform_link_get_by_address(self, + NM_LINK_TYPE_WPAN, + plink->l_address.data, + plink->l_address.len); + NM_SET_OUT(out_parent, parent_plink ? parent_plink->ifindex : -1); + } + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +link_set_option(NMPlatform *self, + int ifindex, + const char *category, + const char *option, + const char *value) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const char * path; + + if (!category || !option) + return FALSE; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return FALSE; + + path = + nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option); + return nm_platform_sysctl_set(self, + NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path), + value); +} + +static char * +link_get_option(NMPlatform *self, int ifindex, const char *category, const char *option) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const char * path; + + if (!category || !option) + return NULL; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return NULL; + + path = + nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option); + return nm_platform_sysctl_get(self, + NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path)); +} + +static const char * +master_category(NMPlatform *self, int master) +{ + switch (nm_platform_link_get_type(self, master)) { + case NM_LINK_TYPE_BRIDGE: + return "bridge"; + case NM_LINK_TYPE_BOND: + return "bonding"; + default: + return NULL; + } +} + +static const char * +slave_category(NMPlatform *self, int slave) +{ + int master = nm_platform_link_get_master(self, slave); + + if (master <= 0) + return NULL; + + switch (nm_platform_link_get_type(self, master)) { + case NM_LINK_TYPE_BRIDGE: + return "brport"; + default: + return NULL; + } +} + +gboolean +nm_platform_sysctl_master_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + g_return_val_if_fail(value, FALSE); + + return link_set_option(self, ifindex, master_category(self, ifindex), option, value); +} + +char * +nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + + return link_get_option(self, ifindex, master_category(self, ifindex), option); +} + +gboolean +nm_platform_sysctl_slave_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + g_return_val_if_fail(value, FALSE); + + return link_set_option(self, ifindex, slave_category(self, ifindex), option, value); +} + +char * +nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + + return link_get_option(self, ifindex, slave_category(self, ifindex), option); +} + +/*****************************************************************************/ + +gboolean +nm_platform_link_vlan_change(NMPlatform * self, + int ifindex, + NMVlanFlags flags_mask, + NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map) +{ + _CHECK_SELF(self, klass, FALSE); + + nm_assert(klass->link_vlan_change); + + g_return_val_if_fail(!n_ingress_map || ingress_map, FALSE); + g_return_val_if_fail(!n_egress_map || egress_map, FALSE); + + flags_set &= flags_mask; + + if (_LOGD_ENABLED()) { + char buf[512]; + char *b = buf; + gsize len, i; + + b[0] = '\0'; + len = sizeof(buf); + + if (flags_mask) + nm_utils_strbuf_append(&b, + &len, + " flags 0x%x/0x%x", + (unsigned) flags_set, + (unsigned) flags_mask); + + if (ingress_reset_all || n_ingress_map) { + nm_utils_strbuf_append_str(&b, &len, " ingress-qos-map"); + nm_platform_vlan_qos_mapping_to_string("", ingress_map, n_ingress_map, b, len); + i = strlen(b); + b += i; + len -= i; + if (ingress_reset_all) + nm_utils_strbuf_append_str(&b, &len, " (reset-all)"); + } + + if (egress_reset_all || n_egress_map) { + nm_utils_strbuf_append_str(&b, &len, " egress-qos-map"); + nm_platform_vlan_qos_mapping_to_string("", egress_map, n_egress_map, b, len); + i = strlen(b); + b += i; + len -= i; + if (egress_reset_all) + nm_utils_strbuf_append_str(&b, &len, " (reset-all)"); + } + + _LOG3D("link: change vlan %s", buf); + } + return klass->link_vlan_change(self, + ifindex, + flags_mask, + flags_set, + ingress_reset_all, + ingress_map, + n_ingress_map, + egress_reset_all, + egress_map, + n_egress_map); +} + +gboolean +nm_platform_link_vlan_set_ingress_map(NMPlatform *self, int ifindex, int from, int to) +{ + NMVlanQosMapping map = { + .from = from, + .to = to, + }; + + return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, &map, 1, FALSE, NULL, 0); +} + +gboolean +nm_platform_link_vlan_set_egress_map(NMPlatform *self, int ifindex, int from, int to) +{ + NMVlanQosMapping map = { + .from = from, + .to = to, + }; + + return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1); +} + +static int +_infiniband_add_add_or_delete(NMPlatform * self, + int ifindex, + int p_key, + gboolean add, + const NMPlatformLink **out_link) +{ + char name[IFNAMSIZ]; + const NMPlatformLink *parent_link; + int r; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex >= 0, -NME_BUG); + g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, -NME_BUG); + + /* the special keys 0x0000 and 0x8000 are not allowed. */ + if (NM_IN_SET(p_key, 0, 0x8000)) + return -NME_UNSPEC; + + parent_link = nm_platform_link_get(self, ifindex); + if (!parent_link) + return -NME_PL_NOT_FOUND; + + if (parent_link->type != NM_LINK_TYPE_INFINIBAND) + return -NME_PL_WRONG_TYPE; + + nm_utils_new_infiniband_name(name, parent_link->name, p_key); + + if (add) { + r = _link_add_check_existing(self, name, NM_LINK_TYPE_INFINIBAND, out_link); + if (r < 0) + return r; + + _LOG3D("link: adding infiniband partition %s, key %d", name, p_key); + if (!klass->infiniband_partition_add(self, ifindex, p_key, out_link)) + return -NME_UNSPEC; + } else { + _LOG3D("link: deleting infiniband partition %s, key %d", name, p_key); + + if (!klass->infiniband_partition_delete(self, ifindex, p_key)) + return -NME_UNSPEC; + } + + return 0; +} + +int +nm_platform_link_infiniband_add(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + return _infiniband_add_add_or_delete(self, parent, p_key, TRUE, out_link); +} + +int +nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key) +{ + return _infiniband_add_add_or_delete(self, parent, p_key, FALSE, NULL); +} + +gboolean +nm_platform_link_infiniband_get_properties(NMPlatform * self, + int ifindex, + int * out_parent, + int * out_p_key, + const char **out_mode) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const NMPlatformLnkInfiniband *plnk; + const NMPlatformLink * plink; + char * contents; + const char * mode; + int p_key = 0; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + plnk = nm_platform_link_get_lnk_infiniband(self, ifindex, &plink); + + if (!plink || plink->type != NM_LINK_TYPE_INFINIBAND) + return FALSE; + + if (plnk) { + NM_SET_OUT(out_parent, plink->parent); + NM_SET_OUT(out_p_key, plnk->p_key); + NM_SET_OUT(out_mode, plnk->mode); + return TRUE; + } + + /* Could not get the link information via netlink. To support older kernels, + * fallback to reading sysfs. */ + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return FALSE; + + contents = + nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode")); + if (!contents) + return FALSE; + if (strstr(contents, "datagram")) + mode = "datagram"; + else if (strstr(contents, "connected")) + mode = "connected"; + else + mode = NULL; + g_free(contents); + + p_key = + nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "pkey"), + 16, + 0, + 0xFFFF, + -1); + if (p_key < 0) + return FALSE; + + NM_SET_OUT(out_parent, plink->parent); + NM_SET_OUT(out_p_key, p_key); + NM_SET_OUT(out_mode, mode); + return TRUE; +} + +gboolean +nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex) +{ + const NMPlatformLink *plink; + int peer_ifindex; + + plink = nm_platform_link_get(self, ifindex); + if (!plink) + return FALSE; + + if (plink->type != NM_LINK_TYPE_VETH) + return FALSE; + + if (plink->parent != 0) { + NM_SET_OUT(out_peer_ifindex, plink->parent); + return TRUE; + } + + /* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */ + if (out_peer_ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(self, &netns)) + return FALSE; + peer_ifindex = nmp_utils_ethtool_get_peer_ifindex(plink->ifindex); + if (peer_ifindex <= 0) + return FALSE; + + *out_peer_ifindex = peer_ifindex; + } + return TRUE; +} + +/** + * nm_platform_link_tun_get_properties: + * @self: the #NMPlatform instance + * @ifindex: the ifindex to look up + * @out_properties: (out) (allow-none): return the read properties + * + * Only recent versions of kernel export tun properties via netlink. + * So, if that's the case, then we have the NMPlatformLnkTun instance + * in the platform cache ready to return. Otherwise, this function + * falls back reading sysctl to obtain the tun properties. That + * is racy, because querying sysctl means that the object might + * be already removed from cache (while NM didn't yet process the + * netlink message). + * + * Hence, to lookup the tun properties, you always need to use this + * function, and use it with care knowing that it might obtain its + * data by reading sysctl. Note that we don't want to add this workaround + * to the platform cache itself, because the cache should (mainly) + * contain data from netlink. To access the sysctl side channel, the + * user needs to do explicitly. + * + * Returns: #TRUE, if the properties could be read. */ +gboolean +nm_platform_link_tun_get_properties(NMPlatform *self, int ifindex, NMPlatformLnkTun *out_properties) +{ + const NMPObject *plobj; + const NMPObject *pllnk; + char ifname[IFNAMSIZ]; + gint64 owner; + gint64 group; + gint64 flags; + + /* we consider also invisible links (those that are not yet in udev). */ + plobj = nm_platform_link_get_obj(self, ifindex, FALSE); + if (!plobj) + return FALSE; + + if (NMP_OBJECT_CAST_LINK(plobj)->type != NM_LINK_TYPE_TUN) + return FALSE; + + pllnk = plobj->_link.netlink.lnk; + if (pllnk) { + nm_assert(NMP_OBJECT_GET_TYPE(pllnk) == NMP_OBJECT_TYPE_LNK_TUN); + nm_assert(NMP_OBJECT_GET_CLASS(pllnk)->lnk_link_type == NM_LINK_TYPE_TUN); + + /* recent kernels expose tun properties via netlink and thus we have them + * in the platform cache. */ + NM_SET_OUT(out_properties, pllnk->lnk_tun); + return TRUE; + } + + /* fallback to reading sysctl. */ + { + nm_auto_close int dirfd = -1; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname); + if (dirfd < 0) + return FALSE; + + owner = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "owner"), + 10, + -1, + G_MAXUINT32, + -2); + if (owner == -2) + return FALSE; + + group = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "group"), + 10, + -1, + G_MAXUINT32, + -2); + if (group == -2) + return FALSE; + + flags = + nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "tun_flags"), + 16, + 0, + G_MAXINT64, + -1); + if (flags == -1) + return FALSE; + } + + if (out_properties) { + memset(out_properties, 0, sizeof(*out_properties)); + if (owner != -1) { + out_properties->owner_valid = TRUE; + out_properties->owner = owner; + } + if (group != -1) { + out_properties->group_valid = TRUE; + out_properties->group = group; + } + out_properties->type = (flags & TUN_TYPE_MASK); + out_properties->pi = !(flags & IFF_NO_PI); + out_properties->vnet_hdr = !!(flags & IFF_VNET_HDR); + out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE); + out_properties->persist = !!(flags & IFF_PERSIST); + } + return TRUE; +} + +gboolean +nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_capabilities(self, ifindex, caps); +} + +guint32 +nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + + return klass->wifi_get_frequency(self, ifindex); +} + +gboolean +nm_platform_wifi_get_station(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_station(self, ifindex, out_bssid, out_quality, out_rate); +} + +NM80211Mode +nm_platform_wifi_get_mode(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, NM_802_11_MODE_UNKNOWN); + + g_return_val_if_fail(ifindex > 0, NM_802_11_MODE_UNKNOWN); + + return klass->wifi_get_mode(self, ifindex); +} + +void +nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, NM80211Mode mode) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_set_mode(self, ifindex, mode); +} + +static void +wifi_set_powersave(NMPlatform *p, int ifindex, guint32 powersave) +{ + /* empty */ +} + +void +nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_set_powersave(self, ifindex, powersave); +} + +guint32 +nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + g_return_val_if_fail(freqs != NULL, 0); + + return klass->wifi_find_frequency(self, ifindex, freqs); +} + +void +nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_indicate_addressing_running(self, ifindex, running); +} + +NMSettingWirelessWakeOnWLan +nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_wake_on_wlan(self, ifindex); +} + +gboolean +nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_set_wake_on_wlan(self, ifindex, wowl); +} + +guint32 +nm_platform_mesh_get_channel(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + + return klass->mesh_get_channel(self, ifindex); +} + +gboolean +nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->mesh_set_channel(self, ifindex, channel); +} + +gboolean +nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ssid != NULL, FALSE); + + return klass->mesh_set_ssid(self, ifindex, ssid, len); +} + +guint16 +nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_get_pan_id(self, ifindex); +} + +gboolean +nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_pan_id(self, ifindex, pan_id); +} + +guint16 +nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_get_short_addr(self, ifindex); +} + +gboolean +nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_short_addr(self, ifindex, short_addr); +} + +gboolean +nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_channel(self, ifindex, page, channel); +} + +#define TO_STRING_DEV_BUF_SIZE (5 + 15 + 1) +static const char * +_to_string_dev(NMPlatform *self, int ifindex, char *buf, size_t size) +{ + g_assert(buf && size >= TO_STRING_DEV_BUF_SIZE); + + if (ifindex) { + const char *name = ifindex > 0 && self ? nm_platform_link_get_name(self, ifindex) : NULL; + char * buf2; + + strcpy(buf, " dev "); + buf2 = buf + 5; + size -= 5; + + if (name) + g_strlcpy(buf2, name, size); + else + g_snprintf(buf2, size, "%d", ifindex); + } else + buf[0] = 0; + + return buf; +} + +#define TO_STRING_IFA_FLAGS_BUF_SIZE 256 + +static const char * +_to_string_ifa_flags(guint32 ifa_flags, char *buf, gsize size) +{ +#define S_FLAGS_PREFIX " flags " + nm_assert(buf && size >= TO_STRING_IFA_FLAGS_BUF_SIZE && size > NM_STRLEN(S_FLAGS_PREFIX)); + + if (!ifa_flags) + buf[0] = '\0'; + else { + nm_platform_addr_flags2str(ifa_flags, + &buf[NM_STRLEN(S_FLAGS_PREFIX)], + size - NM_STRLEN(S_FLAGS_PREFIX)); + if (buf[NM_STRLEN(S_FLAGS_PREFIX)] == '\0') + buf[0] = '\0'; + else + memcpy(buf, S_FLAGS_PREFIX, NM_STRLEN(S_FLAGS_PREFIX)); + } + return buf; +} + +/*****************************************************************************/ + +gboolean +nm_platform_ethtool_set_wake_on_lan(NMPlatform * self, + int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_wake_on_lan(ifindex, wol, wol_password); +} + +gboolean +nm_platform_ethtool_set_link_settings(NMPlatform * self, + int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_link_settings(ifindex, autoneg, speed, duplex); +} + +gboolean +nm_platform_ethtool_get_link_settings(NMPlatform * self, + int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex); +} + +/*****************************************************************************/ + +NMEthtoolFeatureStates * +nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex) +{ + _CHECK_SELF_NETNS(self, klass, netns, NULL); + + g_return_val_if_fail(ifindex > 0, NULL); + + return nmp_utils_ethtool_get_features(ifindex); +} + +gboolean +nm_platform_ethtool_set_features( + NMPlatform * self, + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_features(ifindex, features, requested, do_set); +} + +gboolean +nm_platform_ethtool_get_link_coalesce(NMPlatform * self, + int ifindex, + NMEthtoolCoalesceState *coalesce) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + return nmp_utils_ethtool_get_coalesce(ifindex, coalesce); +} + +gboolean +nm_platform_ethtool_set_coalesce(NMPlatform * self, + int ifindex, + const NMEthtoolCoalesceState *coalesce) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_coalesce(ifindex, coalesce); +} + +gboolean +nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + return nmp_utils_ethtool_get_ring(ifindex, ring); +} + +gboolean +nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_ring(ifindex, ring); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_platform_lookup_all(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nmp_cache_lookup_all(nm_platform_get_cache(self), cache_id_type, obj); +} + +const NMDedupMultiEntry * +nm_platform_lookup_entry(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nmp_cache_lookup_entry_with_idx_type(nm_platform_get_cache(self), cache_id_type, obj); +} + +const NMDedupMultiHeadEntry * +nm_platform_lookup(NMPlatform *self, const NMPLookup *lookup) +{ + return nmp_cache_lookup(nm_platform_get_cache(self), lookup); +} + +gboolean +nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data) +{ + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + return nm_platform_route_table_is_main( + nm_platform_ip_route_get_effective_table(&obj->ip_route)); +} + +gboolean +nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj, + gpointer user_data) +{ + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + return nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&obj->ip_route)) + && obj->ip_route.rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL; +} + +/** + * nm_platform_lookup_clone: + * @self: + * @lookup: + * @predicate: if given, only objects for which @predicate returns %TRUE are included + * in the result. + * @user_data: user data for @predicate + * + * Returns the result of lookup in a GPtrArray. The result array contains + * references objects from the cache, its destroy function will unref them. + * + * The user must unref the GPtrArray, which will also unref the NMPObject + * elements. + * + * The elements in the array *must* not be modified. + * + * Returns: the result of the lookup. + */ +GPtrArray * +nm_platform_lookup_clone(NMPlatform * self, + const NMPLookup * lookup, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + return nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, lookup), + (NMDedupMultiFcnSelectPredicate) predicate, + user_data); +} + +void +nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen) +{ + nm_assert(plen <= 32); + + addr->address = address; + addr->peer_address = address; + addr->plen = plen; +} + +const struct in6_addr * +nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr) +{ + if (IN6_IS_ADDR_UNSPECIFIED(&addr->peer_address) + || IN6_ARE_ADDR_EQUAL(&addr->peer_address, &addr->address)) + return &addr->address; + return &addr->peer_address; +} + +gboolean +nm_platform_ip6_address_match(const NMPlatformIP6Address *addr, NMPlatformMatchFlags match_flag) +{ + nm_assert(!NM_FLAGS_ANY( + match_flag, + ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))); + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY)); + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)); + + if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL)) + return FALSE; + } else { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL)) + return FALSE; + } + + if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED)) + return FALSE; + } else if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) + return FALSE; + } else { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_platform_ip4_address_add(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 32, FALSE); + g_return_val_if_fail(lifetime > 0, FALSE); + g_return_val_if_fail(preferred <= lifetime, FALSE); + g_return_val_if_fail(!label || strlen(label) < sizeof(((NMPlatformIP4Address *) NULL)->label), + FALSE); + + if (_LOGD_ENABLED()) { + NMPlatformIP4Address addr; + + addr = (NMPlatformIP4Address){ + .ifindex = ifindex, + .address = address, + .peer_address = peer_address, + .plen = plen, + .timestamp = 0, /* set it at zero, which to_string will treat as *now* */ + .lifetime = lifetime, + .preferred = preferred, + .n_ifa_flags = flags, + .broadcast_address = broadcast_address, + .use_ip4_broadcast_address = TRUE, + }; + if (label) + g_strlcpy(addr.label, label, sizeof(addr.label)); + + _LOG3D("address: adding or updating IPv4 address: %s", + nm_platform_ip4_address_to_string(&addr, NULL, 0)); + } + return klass->ip4_address_add(self, + ifindex, + address, + plen, + peer_address, + broadcast_address, + lifetime, + preferred, + flags, + label); +} + +gboolean +nm_platform_ip6_address_add(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 128, FALSE); + g_return_val_if_fail(lifetime > 0, FALSE); + g_return_val_if_fail(preferred <= lifetime, FALSE); + + if (_LOGD_ENABLED()) { + NMPlatformIP6Address addr = {0}; + + addr.ifindex = ifindex; + addr.address = address; + addr.peer_address = peer_address; + addr.plen = plen; + addr.timestamp = 0; /* set it to zero, which to_string will treat as *now* */ + addr.lifetime = lifetime; + addr.preferred = preferred; + addr.n_ifa_flags = flags; + + _LOG3D("address: adding or updating IPv6 address: %s", + nm_platform_ip6_address_to_string(&addr, NULL, 0)); + } + return klass + ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags); +} + +gboolean +nm_platform_ip4_address_delete(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char str_peer[INET_ADDRSTRLEN + 50]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 32, FALSE); + + _LOG3D("address: deleting IPv4 address %s/%d, %s%s", + _nm_utils_inet4_ntop(address, b1), + plen, + peer_address != address + ? nm_sprintf_buf(str_peer, "peer %s, ", _nm_utils_inet4_ntop(peer_address, b2)) + : "", + _to_string_dev(self, ifindex, str_dev, sizeof(str_dev))); + return klass->ip4_address_delete(self, ifindex, address, plen, peer_address); +} + +gboolean +nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 128, FALSE); + + _LOG3D("address: deleting IPv6 address %s/%d, %s", + _nm_utils_inet6_ntop(&address, sbuf), + plen, + _to_string_dev(self, ifindex, str_dev, sizeof(str_dev))); + return klass->ip6_address_delete(self, ifindex, address, plen); +} + +const NMPlatformIP4Address * +nm_platform_ip4_address_get(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address) +{ + NMPObject obj_id; + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(plen <= 32, NULL); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, address, plen, peer_address); + obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id); + nm_assert(!obj || nmp_object_is_visible(obj)); + return NMP_OBJECT_CAST_IP4_ADDRESS(obj); +} + +const NMPlatformIP6Address * +nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address) +{ + NMPObject obj_id; + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + nm_assert(address); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, address); + obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id); + nm_assert(!obj || nmp_object_is_visible(obj)); + return NMP_OBJECT_CAST_IP6_ADDRESS(obj); +} + +static gboolean +_addr_array_clean_expired(int addr_family, + int ifindex, + GPtrArray * array, + guint32 now, + GHashTable **idx) +{ + guint i; + gboolean any_addrs = FALSE; + + nm_assert_addr_family(addr_family); + nm_assert(ifindex > 0); + nm_assert(now > 0); + + if (!array) + return FALSE; + + /* remove all addresses that are already expired. */ + for (i = 0; i < array->len; i++) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(array->pdata[i]); + +#if NM_MORE_ASSERTS > 10 + nm_assert(a); + nm_assert(a->ifindex == ifindex); + { + const NMPObject *o = NMP_OBJECT_UP_CAST(a); + guint j; + + nm_assert(NMP_OBJECT_GET_CLASS(o)->addr_family == addr_family); + for (j = i + 1; j < array->len; j++) { + const NMPObject *o2 = array->pdata[j]; + + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_GET_TYPE(o2)); + nm_assert(!nmp_object_id_equal(o, o2)); + } + } +#endif + + if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) { + /* temporary addresses are never added explicitly by NetworkManager but + * kernel adds them via mngtempaddr flag. + * + * We drop them from this list. */ + goto clear_and_next; + } + + if (!nm_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, now, NULL)) + goto clear_and_next; + + if (idx) { + if (G_UNLIKELY(!*idx)) { + *idx = g_hash_table_new((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + if (!g_hash_table_add(*idx, (gpointer) NMP_OBJECT_UP_CAST(a))) + nm_assert_not_reached(); + } + any_addrs = TRUE; + continue; + +clear_and_next: + nmp_object_unref(g_steal_pointer(&array->pdata[i])); + } + + return any_addrs; +} + +static gboolean +ip4_addr_subnets_is_plain_address(const GPtrArray *addresses, gconstpointer needle) +{ + return needle >= (gconstpointer) &addresses->pdata[0] + && needle < (gconstpointer) &addresses->pdata[addresses->len]; +} + +static const NMPObject ** +ip4_addr_subnets_addr_list_get(const GPtrArray *addr_list, guint idx) +{ + nm_assert(addr_list); + nm_assert(addr_list->len > 1); + nm_assert(idx < addr_list->len); + nm_assert(addr_list->pdata[idx]); + nm_assert(!(*((gpointer *) addr_list->pdata[idx])) + || NMP_OBJECT_CAST_IP4_ADDRESS(*((gpointer *) addr_list->pdata[idx]))); + nm_assert(idx == 0 || ip4_addr_subnets_addr_list_get(addr_list, idx - 1)); + return addr_list->pdata[idx]; +} + +static void +ip4_addr_subnets_destroy_index(GHashTable *subnets, const GPtrArray *addresses) +{ + GHashTableIter iter; + gpointer p; + + if (!subnets) + return; + + g_hash_table_iter_init(&iter, subnets); + while (g_hash_table_iter_next(&iter, NULL, &p)) { + if (!ip4_addr_subnets_is_plain_address(addresses, p)) + g_ptr_array_free((GPtrArray *) p, TRUE); + } + + g_hash_table_unref(subnets); +} + +static GHashTable * +ip4_addr_subnets_build_index(const GPtrArray *addresses, + gboolean consider_flags, + gboolean full_index) +{ + GHashTable *subnets; + guint i; + + nm_assert(addresses && addresses->len); + + subnets = g_hash_table_new(nm_direct_hash, NULL); + + /* Build a hash table of all addresses per subnet */ + for (i = 0; i < addresses->len; i++) { + const NMPlatformIP4Address *address; + gpointer p_address; + GPtrArray * addr_list; + guint32 net; + int position; + gpointer p; + + if (!addresses->pdata[i]) + continue; + + p_address = &addresses->pdata[i]; + address = NMP_OBJECT_CAST_IP4_ADDRESS(addresses->pdata[i]); + + net = address->address & _nm_utils_ip4_prefix_to_netmask(address->plen); + if (!g_hash_table_lookup_extended(subnets, GUINT_TO_POINTER(net), NULL, &p)) { + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address); + continue; + } + nm_assert(p); + + if (full_index) { + if (ip4_addr_subnets_is_plain_address(addresses, p)) { + addr_list = g_ptr_array_new(); + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), addr_list); + g_ptr_array_add(addr_list, p); + } else + addr_list = p; + + if (!consider_flags || NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY)) + position = -1; /* append */ + else + position = 0; /* prepend */ + g_ptr_array_insert(addr_list, position, p_address); + } else { + /* we only care about the primary. No need to track the secondaries + * as a GPtrArray. */ + nm_assert(ip4_addr_subnets_is_plain_address(addresses, p)); + if (consider_flags && !NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY)) { + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address); + } + } + } + + return subnets; +} + +/** + * ip4_addr_subnets_is_secondary: + * @address: an address + * @subnets: the hash table mapping subnets to addresses + * @addresses: array of addresses in the hash table + * @out_addr_list: array of addresses belonging to the same subnet + * + * Checks whether @address is secondary and returns in @out_addr_list the list of addresses + * belonging to the same subnet, if it contains other elements. + * + * Returns: %TRUE if the address is secondary, %FALSE otherwise + */ +static gboolean +ip4_addr_subnets_is_secondary(const NMPObject * address, + GHashTable * subnets, + const GPtrArray * addresses, + const GPtrArray **out_addr_list) +{ + const NMPlatformIP4Address *a; + const GPtrArray * addr_list; + gconstpointer p; + guint32 net; + const NMPObject ** o; + + a = NMP_OBJECT_CAST_IP4_ADDRESS(address); + + net = a->address & _nm_utils_ip4_prefix_to_netmask(a->plen); + p = g_hash_table_lookup(subnets, GUINT_TO_POINTER(net)); + nm_assert(p); + if (!ip4_addr_subnets_is_plain_address(addresses, p)) { + addr_list = p; + nm_assert(addr_list->len > 1); + NM_SET_OUT(out_addr_list, addr_list); + o = ip4_addr_subnets_addr_list_get(addr_list, 0); + nm_assert(o && *o); + if (*o != address) + return TRUE; + } else { + NM_SET_OUT(out_addr_list, NULL); + return address != *((gconstpointer *) p); + } + return FALSE; +} + +typedef enum { + IP6_ADDR_SCOPE_LOOPBACK, + IP6_ADDR_SCOPE_LINKLOCAL, + IP6_ADDR_SCOPE_SITELOCAL, + IP6_ADDR_SCOPE_OTHER, +} IP6AddrScope; + +static IP6AddrScope +ip6_address_scope(const NMPlatformIP6Address *a) +{ + if (IN6_IS_ADDR_LOOPBACK(&a->address)) + return IP6_ADDR_SCOPE_LOOPBACK; + if (IN6_IS_ADDR_LINKLOCAL(&a->address)) + return IP6_ADDR_SCOPE_LINKLOCAL; + if (IN6_IS_ADDR_SITELOCAL(&a->address)) + return IP6_ADDR_SCOPE_SITELOCAL; + return IP6_ADDR_SCOPE_OTHER; +} + +static int +ip6_address_scope_cmp(gconstpointer p_a, gconstpointer p_b, gpointer increasing) +{ + const NMPlatformIP6Address *a; + const NMPlatformIP6Address *b; + + if (!increasing) + NM_SWAP(&p_a, &p_b); + + a = NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_a); + b = NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_b); + + NM_CMP_DIRECT(ip6_address_scope(a), ip6_address_scope(b)); + return 0; +} + +/** + * nm_platform_ip_address_sync: + * @self: platform instance + * @addr_family: the address family AF_INET or AF_INET6. + * @ifindex: Interface index + * @known_addresses: List of addresses. The list will be modified and only + * addresses that were successfully added will be kept in the list. + * That means, expired addresses and addresses that could not be added + * will be dropped. + * Hence, the input argument @known_addresses is also an output argument + * telling which addresses were successfully added. + * Addresses are removed by unrefing the instance via nmp_object_unref() + * and leaving a NULL tombstone. + * @addresses_prune: (allow-none): the list of addresses to delete. + * If platform has such an address configured, it will be deleted + * at the beginning of the sync. Note that the array will be modified + * by the function. + * Note that the addresses must be properly sorted, by their priority. + * Create this list with nm_platform_ip_address_get_prune_list() which + * gets the sorting right. + * + * A convenience function to synchronize addresses for a specific interface + * with the least possible disturbance. It simply removes addresses that are + * not listed and adds addresses that are. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + GPtrArray * addresses_prune) +{ + const gint32 now = nm_utils_get_monotonic_timestamp_sec(); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_hashtable GHashTable *known_addresses_idx = NULL; + GPtrArray * plat_addresses; + GHashTable * known_subnets = NULL; + guint32 ifa_flags; + guint i_plat; + guint i_know; + guint i; + guint j; + + _CHECK_SELF(self, klass, FALSE); + + /* The order we want to enforce is only among addresses with the same + * scope, as the kernel keeps addresses sorted by scope. Therefore, + * apply the same sorting to known addresses, so that we don't try to + * unnecessary change the order of addresses with different scopes. */ + if (!IS_IPv4) { + if (known_addresses) + g_ptr_array_sort_with_data(known_addresses, + ip6_address_scope_cmp, + GINT_TO_POINTER(TRUE)); + } + + if (!_addr_array_clean_expired(addr_family, + ifindex, + known_addresses, + now, + &known_addresses_idx)) + known_addresses = NULL; + + /* @plat_addresses must be sorted in decreasing priority order (highest priority addresses first), contrary to + * @known_addresses which is in increasing priority order (lowest priority addresses first). */ + plat_addresses = addresses_prune; + + if (nm_g_ptr_array_len(plat_addresses) > 0) { + /* Delete unknown addresses */ + if (IS_IPv4) { + GHashTable *plat_subnets; + + plat_subnets = ip4_addr_subnets_build_index(plat_addresses, TRUE, TRUE); + + for (i = 0; i < plat_addresses->len; i++) { + const NMPObject * plat_obj; + const NMPlatformIP4Address *plat_address; + const GPtrArray * addr_list; + + plat_obj = plat_addresses->pdata[i]; + if (!plat_obj) { + /* Already deleted */ + continue; + } + + plat_address = NMP_OBJECT_CAST_IP4_ADDRESS(plat_obj); + + if (known_addresses) { + const NMPObject *o; + + o = g_hash_table_lookup(known_addresses_idx, plat_obj); + if (o) { + gboolean secondary; + + if (!known_subnets) + known_subnets = + ip4_addr_subnets_build_index(known_addresses, FALSE, FALSE); + + secondary = + ip4_addr_subnets_is_secondary(o, known_subnets, known_addresses, NULL); + if (secondary == NM_FLAGS_HAS(plat_address->n_ifa_flags, IFA_F_SECONDARY)) { + /* if we have an existing known-address, with matching secondary role, + * do not delete the platform-address. */ + continue; + } + } + } + + nm_platform_ip4_address_delete(self, + ifindex, + plat_address->address, + plat_address->plen, + plat_address->peer_address); + + if (!ip4_addr_subnets_is_secondary(plat_obj, + plat_subnets, + plat_addresses, + &addr_list) + && addr_list) { + /* If we just deleted a primary addresses and there were + * secondary ones the kernel can do two things, depending on + * version and sysctl setting: delete also secondary addresses + * or promote a secondary to primary. Ensure that secondary + * addresses are deleted, so that we can start with a clean + * slate and add addresses in the right order. */ + for (j = 1; j < addr_list->len; j++) { + const NMPObject **o; + + o = ip4_addr_subnets_addr_list_get(addr_list, j); + nm_assert(o); + + if (*o) { + const NMPlatformIP4Address *a; + + a = NMP_OBJECT_CAST_IP4_ADDRESS(*o); + nm_platform_ip4_address_delete(self, + ifindex, + a->address, + a->plen, + a->peer_address); + nmp_object_unref(*o); + *o = NULL; + } + } + } + } + ip4_addr_subnets_destroy_index(plat_subnets, plat_addresses); + } else { + guint known_addresses_len; + IP6AddrScope cur_scope; + gboolean delete_remaining_addrs; + + g_ptr_array_sort_with_data(plat_addresses, + ip6_address_scope_cmp, + GINT_TO_POINTER(FALSE)); + + known_addresses_len = known_addresses ? known_addresses->len : 0; + + /* First, compare every address whether it is still a "known address", that is, whether + * to keep it or to delete it. + * + * If we don't find a matching valid address in @known_addresses, we will delete + * plat_addr. + * + * Certain addresses, like temporary addresses, are ignored by this function + * if not run with full_sync. These addresses are usually not managed by NetworkManager + * directly, or at least, they are not managed via nm_platform_ip6_address_sync(). + * Only in full_sync mode, we really want to get rid of them (usually, when we take + * the interface down). + * + * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */ + for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) { + const NMPObject * plat_obj = plat_addresses->pdata[i_plat]; + const NMPObject * know_obj; + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj); + + if (known_addresses_idx) { + know_obj = g_hash_table_lookup(known_addresses_idx, plat_obj); + if (know_obj + && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS(know_obj)->plen) { + /* technically, plen is not part of the ID for IPv6 addresses and thus + * @plat_addr is essentially the same address as @know_addr (regrading + * its identity, not its other attributes). + * However, we cannot modify an existing addresses' plen without + * removing and readding it. Thus, only keep plat_addr, if the plen + * matches. + * + * keep this one, and continue */ + continue; + } + } + + nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen); + nmp_object_unref(g_steal_pointer(&plat_addresses->pdata[i_plat])); + } + + /* Next, we must preserve the priority of the routes. That is, source address + * selection will choose addresses in the order as they are reported by kernel. + * Note that the order in @plat_addresses of the remaining matches is highest + * priority first. + * We need to compare this to the order of addresses with same scope in + * @known_addresses (which has lowest priority first). + * + * If we find a first discrepancy, we need to delete all remaining addresses + * with same scope from that point on, because below we must re-add all the + * addresses in the right order to get their priority right. */ + cur_scope = IP6_ADDR_SCOPE_LOOPBACK; + delete_remaining_addrs = FALSE; + i_plat = plat_addresses->len; + i_know = 0; + while (i_plat > 0) { + const NMPlatformIP6Address *plat_addr = + NMP_OBJECT_CAST_IP6_ADDRESS(plat_addresses->pdata[--i_plat]); + IP6AddrScope plat_scope; + + if (!plat_addr) + continue; + + plat_scope = ip6_address_scope(plat_addr); + if (cur_scope != plat_scope) { + nm_assert(cur_scope < plat_scope); + delete_remaining_addrs = FALSE; + cur_scope = plat_scope; + } + + if (!delete_remaining_addrs) { + delete_remaining_addrs = TRUE; + for (; i_know < known_addresses_len; i_know++) { + const NMPlatformIP6Address *know_addr = + NMP_OBJECT_CAST_IP6_ADDRESS(known_addresses->pdata[i_know]); + IP6AddrScope know_scope; + + if (!know_addr) + continue; + + know_scope = ip6_address_scope(know_addr); + if (know_scope < plat_scope) + continue; + + if (IN6_ARE_ADDR_EQUAL(&plat_addr->address, &know_addr->address)) { + /* we have a match. Mark address as handled. */ + i_know++; + delete_remaining_addrs = FALSE; + goto next_plat; + } + + /* plat_address has no match. Now delete_remaining_addrs is TRUE and we will + * delete all the remaining addresses with cur_scope. */ + break; + } + } + + nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen); +next_plat:; + } + } + } + + if (!known_addresses) + return TRUE; + + if (IS_IPv4) + ip4_addr_subnets_destroy_index(known_subnets, known_addresses); + + ifa_flags = nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) + ? IFA_F_NOPREFIXROUTE + : 0; + + /* Add missing addresses. New addresses are added by kernel with top + * priority. + */ + for (i_know = 0; i_know < known_addresses->len; i_know++) { + const NMPlatformIPXAddress *known_address; + const NMPObject * o; + guint32 lifetime; + guint32 preferred; + + o = known_addresses->pdata[i_know]; + if (!o) + continue; + + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)); + + known_address = NMP_OBJECT_CAST_IPX_ADDRESS(o); + + lifetime = nm_utils_lifetime_get(known_address->ax.timestamp, + known_address->ax.lifetime, + known_address->ax.preferred, + now, + &preferred); + nm_assert(lifetime > 0); + + if (IS_IPv4) { + if (!nm_platform_ip4_address_add( + self, + ifindex, + known_address->a4.address, + known_address->a4.plen, + known_address->a4.peer_address, + nm_platform_ip4_broadcast_address_from_addr(&known_address->a4), + lifetime, + preferred, + ifa_flags, + known_address->a4.label)) { + /* ignore error, for unclear reasons. */ + } + } else { + if (!nm_platform_ip6_address_add(self, + ifindex, + known_address->a6.address, + known_address->a6.plen, + known_address->a6.peer_address, + lifetime, + preferred, + ifa_flags | known_address->a6.n_ifa_flags)) + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex) +{ + gboolean success = TRUE; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) + success &= nm_platform_ip4_address_sync(self, ifindex, NULL); + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) + success &= nm_platform_ip6_address_sync(self, ifindex, NULL, TRUE); + return success; +} + +/*****************************************************************************/ + +static gboolean +_err_inval_due_to_ipv6_tentative_pref_src(NMPlatform *self, const NMPObject *obj) +{ + const NMPlatformIP6Route * r; + const NMPlatformIP6Address *a; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + /* trying to add an IPv6 route with pref-src fails, if the address is + * still tentative (rh#1452684). We need to hack around that. + * + * Detect it, by guessing whether that's the case. */ + + if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_ROUTE) + return FALSE; + + r = NMP_OBJECT_CAST_IP6_ROUTE(obj); + + /* we only allow this workaround for routes added manually by the user. */ + if (r->rt_source != NM_IP_CONFIG_SOURCE_USER) + return FALSE; + + if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src)) + return FALSE; + + a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src); + if (!a) + return FALSE; + if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE) + || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED)) + return FALSE; + + return TRUE; +} + +GPtrArray * +nm_platform_ip_address_get_prune_list(NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + GPtrArray * result; + CList * iter; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family)), ifindex); + + head_entry = nm_platform_lookup(self, &lookup); + + if (!head_entry) + return NULL; + + result = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nmp_object_unref); + + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj; + + if (!IS_IPv4) { + if (exclude_ipv6_temporary_addrs + && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_TEMPORARY)) + continue; + } + + g_ptr_array_add(result, (gpointer) nmp_object_ref(obj)); + } + + if (result->len == 0) { + g_ptr_array_unref(result); + return NULL; + } + return result; +} + +GPtrArray * +nm_platform_ip_route_get_prune_list(NMPlatform * self, + int addr_family, + int ifindex, + NMIPRouteTableSyncMode route_table_sync) +{ + NMPLookup lookup; + GPtrArray * routes_prune; + const NMDedupMultiHeadEntry *head_entry; + CList * iter; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + nm_assert(NM_IN_SET(route_table_sync, + NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, + NM_IP_ROUTE_TABLE_SYNC_MODE_FULL, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL)); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), ifindex); + head_entry = nm_platform_lookup(self, &lookup); + if (!head_entry) + return NULL; + + routes_prune = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nm_dedup_multi_obj_unref); + + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj; + + if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_FULL) { + if (nm_platform_ip_route_get_effective_table(NMP_OBJECT_CAST_IP_ROUTE(obj)) + == RT_TABLE_LOCAL) + continue; + } else if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN) { + if (!nm_platform_route_table_is_main( + nm_platform_ip_route_get_effective_table(NMP_OBJECT_CAST_IP_ROUTE(obj)))) + continue; + } else + nm_assert(route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_ALL); + + g_ptr_array_add(routes_prune, (gpointer) nmp_object_ref(obj)); + } + + if (routes_prune->len == 0) { + g_ptr_array_unref(routes_prune); + return NULL; + } + return routes_prune; +} + +/** + * nm_platform_ip_route_sync: + * @self: the #NMPlatform instance. + * @addr_family: AF_INET or AF_INET6. + * @ifindex: the @ifindex for which the routes are to be added. + * @routes: (allow-none): a list of routes to configure. Must contain + * NMPObject instances of routes, according to @addr_family. + * @routes_prune: (allow-none): the list of routes to delete. + * If platform has such a route configured, it will be deleted + * at the end of the operation. Note that if @routes contains + * the same route, then it will not be deleted. @routes overrules + * @routes_prune list. + * @out_temporary_not_available: (allow-none) (out): routes that could + * currently not be synced. The caller shall keep them and try later again. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_ip_route_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * routes, + GPtrArray * routes_prune, + GPtrArray **out_temporary_not_available) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPlatformVTableRoute *vt; + gs_unref_hashtable GHashTable *routes_idx = NULL; + const NMPObject * conf_o; + const NMDedupMultiEntry * plat_entry; + guint i; + int i_type; + gboolean success = TRUE; + char sbuf1[sizeof(_nm_utils_to_string_buffer)]; + char sbuf2[sizeof(_nm_utils_to_string_buffer)]; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + vt = &nm_platform_vtable_route.vx[IS_IPv4]; + + for (i_type = 0; routes && i_type < 2; i_type++) { + for (i = 0; i < routes->len; i++) { + int r, r2; + gboolean gateway_route_added = FALSE; + + conf_o = routes->pdata[i]; + +#define VTABLE_IS_DEVICE_ROUTE(vt, o) \ + (vt->is_ip4 ? (NMP_OBJECT_CAST_IP4_ROUTE(o)->gateway == 0) \ + : IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(o)->gateway)) + + if ((i_type == 0 && !VTABLE_IS_DEVICE_ROUTE(vt, conf_o)) + || (i_type == 1 && VTABLE_IS_DEVICE_ROUTE(vt, conf_o))) { + /* we add routes in two runs over @i_type. + * + * First device routes, then gateway routes. */ + continue; + } + + if (!routes_idx) { + routes_idx = g_hash_table_new((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + if (!g_hash_table_insert(routes_idx, (gpointer) conf_o, (gpointer) conf_o)) { + _LOG3D("route-sync: skip adding duplicate route %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + continue; + } + + if (!IS_IPv4 + && nm_platform_ip6_route_get_effective_metric(NMP_OBJECT_CAST_IP6_ROUTE(conf_o)) + == 0) { + /* User space cannot add routes with metric 0. However, kernel can, and we might track such + * routes in @route as they are present external. Skip them silently. */ + continue; + } + + plat_entry = nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); + if (plat_entry) { + const NMPObject *plat_o; + + plat_o = plat_entry->obj; + + if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), + NMP_OBJECT_CAST_IPX_ROUTE(plat_o), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + == 0) + continue; + + /* we need to replace the existing route with a (slightly) different + * one. Delete it first. */ + if (!nm_platform_object_delete(self, plat_o)) { + /* ignore error. */ + } + } + +sync_route_add: + r = nm_platform_ip_route_add(self, + NMP_NLM_FLAG_APPEND + | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, + conf_o); + if (r < 0) { + if (r == -EEXIST) { + /* Don't fail for EEXIST. It's not clear that the existing route + * is identical to the one that we were about to add. However, + * above we should have deleted conflicting (non-identical) routes. */ + if (_LOGD_ENABLED()) { + plat_entry = + nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); + if (!plat_entry) { + _LOG3D("route-sync: adding route %s failed with EEXIST, however we " + "cannot find such a route", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), + NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + != 0) { + _LOG3D("route-sync: adding route %s failed due to existing " + "(different!) route %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nmp_object_to_string(plat_entry->obj, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf2, + sizeof(sbuf2))); + } + } + } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { + _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + } else if (r == -EINVAL && out_temporary_not_available + && _err_inval_due_to_ipv6_tentative_pref_src(self, conf_o)) { + _LOG3D("route-sync: ignore failure to add IPv6 route with tentative IPv6 " + "pref-src: %s: %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + if (!*out_temporary_not_available) + *out_temporary_not_available = + g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref); + g_ptr_array_add(*out_temporary_not_available, + (gpointer) nmp_object_ref(conf_o)); + } else if (!gateway_route_added + && ((r == -ENETUNREACH && vt->is_ip4 + && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway) + || (r == -EHOSTUNREACH && !vt->is_ip4 + && !IN6_IS_ADDR_UNSPECIFIED( + &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) { + NMPObject oo; + + if (vt->is_ip4) { + const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o); + + nmp_object_stackinit( + &oo, + NMP_OBJECT_TYPE_IP4_ROUTE, + &((NMPlatformIP4Route){ + .ifindex = rt->ifindex, + .network = rt->gateway, + .plen = 32, + .metric = nm_platform_ip4_route_get_effective_metric(rt), + .rt_source = rt->rt_source, + .table_coerced = nm_platform_ip_route_get_effective_table( + NM_PLATFORM_IP_ROUTE_CAST(rt)), + })); + } else { + const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o); + + nmp_object_stackinit( + &oo, + NMP_OBJECT_TYPE_IP6_ROUTE, + &((NMPlatformIP6Route){ + .ifindex = rt->ifindex, + .network = rt->gateway, + .plen = 128, + .metric = nm_platform_ip6_route_get_effective_metric(rt), + .rt_source = rt->rt_source, + .table_coerced = nm_platform_ip_route_get_effective_table( + NM_PLATFORM_IP_ROUTE_CAST(rt)), + })); + } + + _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct " + "route to gateway %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r), + nmp_object_to_string(&oo, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf2, + sizeof(sbuf2))); + + r2 = nm_platform_ip_route_add(self, + NMP_NLM_FLAG_APPEND + | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, + &oo); + + if (r2 < 0) { + _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r2)); + } + + gateway_route_added = TRUE; + goto sync_route_add; + } else { + _LOG3W("route-sync: failure to add IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + success = FALSE; + } + } + } + } + + if (routes_prune) { + for (i = 0; i < routes_prune->len; i++) { + const NMPObject *prune_o; + + prune_o = routes_prune->pdata[i]; + + nm_assert((NM_IS_IPv4(addr_family) + && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP4_ROUTE) + || (!NM_IS_IPv4(addr_family) + && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP6_ROUTE)); + + if (routes_idx && g_hash_table_lookup(routes_idx, prune_o)) + continue; + + if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, prune_o)) + continue; + + if (!nm_platform_object_delete(self, prune_o)) { + /* ignore error... */ + } + } + } + + return success; +} + +gboolean +nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex) +{ + gboolean success = TRUE; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + + routes_prune = nm_platform_ip_route_get_prune_list(self, + AF_INET, + ifindex, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL); + success &= nm_platform_ip_route_sync(self, AF_INET, ifindex, NULL, routes_prune, NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + + routes_prune = nm_platform_ip_route_get_prune_list(self, + AF_INET6, + ifindex, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL); + success &= nm_platform_ip_route_sync(self, AF_INET6, ifindex, NULL, routes_prune, NULL); + } + return success; +} + +/*****************************************************************************/ + +static guint8 +_ip_route_scope_inv_get_normalized(const NMPlatformIP4Route *route) +{ + /* in kernel, you cannot set scope to RT_SCOPE_NOWHERE (255). + * That means, in NM, we treat RT_SCOPE_NOWHERE as unset, and detect + * it based on the presence of the gateway. In other words, when adding + * a route with scope RT_SCOPE_NOWHERE (in NetworkManager) to kernel, + * the resulting scope will be either "link" or "universe" (depending + * on the gateway). + * + * Note that internally, we track @scope_inv is the inverse of scope, + * so that the default equals zero (~(RT_SCOPE_NOWHERE)). + **/ + if (route->scope_inv == 0) { + if (route->type_coerced == nm_platform_route_type_coerce(RTN_LOCAL)) + return nm_platform_route_scope_inv(RT_SCOPE_HOST); + else { + return nm_platform_route_scope_inv(!route->gateway ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE); + } + } + return route->scope_inv; +} + +static guint8 +_route_pref_normalize(guint8 pref) +{ + /* for kernel (and ICMPv6) pref can only have one of 3 values. Normalize. */ + return NM_IN_SET(pref, NM_ICMPV6_ROUTER_PREF_LOW, NM_ICMPV6_ROUTER_PREF_HIGH) + ? pref + : NM_ICMPV6_ROUTER_PREF_MEDIUM; +} + +/** + * nm_platform_ip_route_normalize: + * @addr_family: AF_INET or AF_INET6 + * @route: an NMPlatformIP4Route or NMPlatformIP6Route instance, depending on @addr_family. + * + * Adding a route to kernel via nm_platform_ip_route_add() will normalize/coerce some + * properties of the route. This function modifies (normalizes) the route like it + * would be done by adding the route in kernel. + * + * Note that this function is related to NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY + * in that if two routes compare semantically equal, after normalizing they also shall + * compare equal with NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL. + */ +void +nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route) +{ + NMPlatformIP4Route *r4; + NMPlatformIP6Route *r6; + + route->table_coerced = + nm_platform_route_table_coerce(nm_platform_ip_route_get_effective_table(route)); + route->table_any = FALSE; + + route->rt_source = nmp_utils_ip_config_source_round_trip_rtprot(route->rt_source); + + switch (addr_family) { + case AF_INET: + r4 = (NMPlatformIP4Route *) route; + route->metric = nm_platform_ip4_route_get_effective_metric(r4); + route->metric_any = FALSE; + r4->network = nm_utils_ip4_address_clear_host_address(r4->network, r4->plen); + r4->scope_inv = _ip_route_scope_inv_get_normalized(r4); + break; + case AF_INET6: + r6 = (NMPlatformIP6Route *) route; + route->metric = nm_platform_ip6_route_get_effective_metric(r6); + route->metric_any = FALSE; + nm_utils_ip6_address_clear_host_address(&r6->network, &r6->network, r6->plen); + nm_utils_ip6_address_clear_host_address(&r6->src, &r6->src, r6->src_plen); + break; + default: + nm_assert_not_reached(); + break; + } +} + +static int +_ip_route_add(NMPlatform *self, NMPNlmFlags flags, int addr_family, gconstpointer route) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + int ifindex; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(route); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + ifindex = ((const NMPlatformIPRoute *) route)->ifindex; + _LOG3D("route: %-10s IPv%c route: %s", + _nmp_nlm_flag_to_string(flags & NMP_NLM_FLAG_FMASK), + nm_utils_addr_family_to_char(addr_family), + NM_IS_IPv4(addr_family) ? nm_platform_ip4_route_to_string(route, sbuf, sizeof(sbuf)) + : nm_platform_ip6_route_to_string(route, sbuf, sizeof(sbuf))); + + return klass->ip_route_add(self, flags, addr_family, route); +} + +int +nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route) +{ + int addr_family; + + switch (NMP_OBJECT_GET_TYPE(route)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + addr_family = AF_INET; + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + addr_family = AF_INET6; + break; + default: + g_return_val_if_reached(FALSE); + } + + return _ip_route_add(self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE(route)); +} + +int +nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route) +{ + return _ip_route_add(self, flags, AF_INET, route); +} + +int +nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route) +{ + return _ip_route_add(self, flags, AF_INET6, route); +} + +gboolean +nm_platform_object_delete(NMPlatform *self, const NMPObject *obj) +{ + int ifindex; + + _CHECK_SELF(self, klass, FALSE); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_ROUTING_RULE: + _LOGD("%s: delete %s", + NMP_OBJECT_GET_CLASS(obj)->obj_type_name, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj)->ifindex; + _LOG3D("%s: delete %s", + NMP_OBJECT_GET_CLASS(obj)->obj_type_name, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + break; + default: + g_return_val_if_reached(FALSE); + } + + return klass->object_delete(self, obj); +} + +/*****************************************************************************/ + +int +nm_platform_ip_route_get(NMPlatform * self, + int addr_family, + gconstpointer address /* in_addr_t or struct in6_addr */, + int oif_ifindex, + NMPObject ** out_route) +{ + nm_auto_nmpobj NMPObject *route = NULL; + int result; + char buf[NM_UTILS_INET_ADDRSTRLEN]; + char buf_oif[64]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(address, -NME_BUG); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), -NME_BUG); + + _LOGT("route: get IPv%c route for: %s%s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + oif_ifindex > 0 ? nm_sprintf_buf(buf_oif, " oif %d", oif_ifindex) : ""); + + if (!klass->ip_route_get) + result = -NME_PL_OPNOTSUPP; + else { + result = klass->ip_route_get(self, addr_family, address, oif_ifindex, &route); + } + + if (result < 0) { + nm_assert(!route); + _LOGW("route: get IPv%c route for: %s failed with %s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + nm_strerror(result)); + } else { + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(route), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(route)); + nm_assert(route->parent._ref_count == 1); + _LOGD("route: get IPv%c route for: %s succeeded: %s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + nmp_object_to_string(route, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + NM_SET_OUT(out_route, g_steal_pointer(&route)); + } + return result; +} + +/*****************************************************************************/ + +#define IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS ((int) 1500) +#define IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S \ + ((int) (((IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS + 999) * 3) / 1000)) + +static gint64 +_ip4_dev_route_blacklist_timeout_ms_get(gint64 timeout_msec) +{ + return timeout_msec >> 1; +} + +static gint64 +_ip4_dev_route_blacklist_timeout_ms_marked(gint64 timeout_msec) +{ + return !!(timeout_msec & ((gint64) 1)); +} + +static gboolean +_ip4_dev_route_blacklist_check_cb(gpointer user_data) +{ + NMPlatform * self = user_data; + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + GHashTableIter iter; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + priv->ip4_dev_route_blacklist_check_id = 0; + +again: + if (!priv->ip4_dev_route_blacklist_hash) + goto out; + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (!_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms)) + continue; + + /* unmark because we checked it. */ + *p_timeout_ms = *p_timeout_ms & ~((gint64) 1); + + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) + continue; + + if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, p_obj)) + continue; + + _LOGT("ip4-dev-route: delete %s", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + nm_platform_object_delete(self, p_obj); + goto again; + } + +out: + return G_SOURCE_REMOVE; +} + +static void +_ip4_dev_route_blacklist_check_schedule(NMPlatform *self) +{ + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + if (!priv->ip4_dev_route_blacklist_check_id) { + priv->ip4_dev_route_blacklist_check_id = + g_idle_add_full(G_PRIORITY_HIGH, _ip4_dev_route_blacklist_check_cb, self, NULL); + } +} + +static void +_ip4_dev_route_blacklist_notify_route(NMPlatform *self, const NMPObject *obj) +{ + NMPlatformPrivate *priv; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE); + + priv = NM_PLATFORM_GET_PRIVATE(self); + + nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id); + + if (!g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash, + obj, + (gpointer *) &p_obj, + (gpointer *) &p_timeout_ms)) + return; + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) { + /* already expired. Wait for gc. */ + return; + } + + if (_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms)) { + nm_assert(priv->ip4_dev_route_blacklist_check_id); + return; + } + + /* We cannot delete it right away because we are in the process of receiving netlink messages. + * It may be possible to do so, but complicated and error prone. + * + * Instead, we mark the entry and schedule an idle action (with high priority). */ + *p_timeout_ms = (*p_timeout_ms) | ((gint64) 1); + _ip4_dev_route_blacklist_check_schedule(self); +} + +static gboolean +_ip4_dev_route_blacklist_gc_timeout_handle(gpointer user_data) +{ + NMPlatform * self = user_data; + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + GHashTableIter iter; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id); + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) { + _LOGT("ip4-dev-route: cleanup %s", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + g_hash_table_iter_remove(&iter); + } + } + + _ip4_dev_route_blacklist_schedule(self); + return G_SOURCE_CONTINUE; +} + +static void +_ip4_dev_route_blacklist_schedule(NMPlatform *self) +{ + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + if (!priv->ip4_dev_route_blacklist_hash + || g_hash_table_size(priv->ip4_dev_route_blacklist_hash) == 0) { + nm_clear_pointer(&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref); + nm_clear_g_source(&priv->ip4_dev_route_blacklist_gc_timeout_id); + } else { + if (!priv->ip4_dev_route_blacklist_gc_timeout_id) { + /* this timeout is only to garbage collect the expired entries from priv->ip4_dev_route_blacklist_hash. + * It can run infrequently, and it doesn't hurt if expired entries linger around a bit + * longer then necessary. */ + priv->ip4_dev_route_blacklist_gc_timeout_id = + g_timeout_add_seconds(IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S, + _ip4_dev_route_blacklist_gc_timeout_handle, + self); + } + } +} + +/** + * nm_platform_ip4_dev_route_blacklist_set: + * @self: + * @ifindex: + * @ip4_dev_route_blacklist: + * + * When adding an IP address, kernel automatically adds a device route. + * This can be suppressed via the IFA_F_NOPREFIXROUTE address flag. For proper + * IPv6 support, we require kernel support for IFA_F_NOPREFIXROUTE and always + * add the device route manually. + * + * For IPv4, this flag is rather new and we don't rely on it yet. We want to use + * it (but currently still don't). So, for IPv4, kernel possibly adds a device + * route, however it has a wrong metric of zero. We add our own device route (with + * proper metric), but need to delete the route that kernel adds. + * + * The problem is, that kernel does not immediately add the route, when adding + * the address. It only shows up some time later. So, we register here a list + * of blacklisted routes, and when they show up within a time out, we assume it's + * the kernel generated one, and we delete it. + * + * Eventually, we want to get rid of this and use IFA_F_NOPREFIXROUTE for IPv4 + * routes as well. + */ +void +nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self, + int ifindex, + GPtrArray * ip4_dev_route_blacklist) +{ + NMPlatformPrivate *priv; + GHashTableIter iter; + const NMPObject * p_obj; + guint i; + gint64 timeout_msec; + gint64 timeout_msec_val; + gint64 * p_timeout_ms; + gboolean needs_check = FALSE; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + /* TODO: the blacklist should be maintained by NML3Cfg. */ + + priv = NM_PLATFORM_GET_PRIVATE(self); + + /* first, expire all for current ifindex... */ + if (priv->ip4_dev_route_blacklist_hash) { + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (NMP_OBJECT_CAST_IP4_ROUTE(p_obj)->ifindex == ifindex) { + /* we could g_hash_table_iter_remove(&iter) the current entry. + * Instead, just expire it and let _ip4_dev_route_blacklist_gc_timeout_handle() + * handle it. + * + * The assumption is, that ip4_dev_route_blacklist contains the very same entry + * again, with a new timeout. So, we can un-expire it below. */ + *p_timeout_ms = 0; + } + } + } + + if (ip4_dev_route_blacklist && ip4_dev_route_blacklist->len > 0) { + if (!priv->ip4_dev_route_blacklist_hash) { + priv->ip4_dev_route_blacklist_hash = + g_hash_table_new_full((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + nm_g_slice_free_fcn_gint64); + } + + timeout_msec = nm_utils_get_monotonic_timestamp_msec() + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS; + timeout_msec_val = (timeout_msec << 1) | ((gint64) 1); + for (i = 0; i < ip4_dev_route_blacklist->len; i++) { + const NMPObject *o; + + needs_check = TRUE; + o = ip4_dev_route_blacklist->pdata[i]; + if (g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash, + o, + (gpointer *) &p_obj, + (gpointer *) &p_timeout_ms)) { + if (nmp_object_equal(p_obj, o)) { + /* un-expire and reuse the entry. */ + _LOGT("ip4-dev-route: register %s (update)", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + *p_timeout_ms = timeout_msec_val; + continue; + } + } + + _LOGT("ip4-dev-route: register %s", + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + p_timeout_ms = g_slice_new(gint64); + *p_timeout_ms = timeout_msec_val; + g_hash_table_replace(priv->ip4_dev_route_blacklist_hash, + (gpointer) nmp_object_ref(o), + p_timeout_ms); + } + } + + _ip4_dev_route_blacklist_schedule(self); + + if (needs_check) + _ip4_dev_route_blacklist_check_schedule(self); +} + +/*****************************************************************************/ + +int +nm_platform_routing_rule_add(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(routing_rule, -NME_BUG); + + _LOGD("routing-rule: adding or updating: %s", + nm_platform_routing_rule_to_string(routing_rule, NULL, 0)); + return klass->routing_rule_add(self, flags, routing_rule); +} + +/*****************************************************************************/ + +int +nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc) +{ + int ifindex = qdisc->ifindex; + _CHECK_SELF(self, klass, -NME_BUG); + + /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind + * is undefined. */ + + _LOG3D("adding or updating a qdisc: %s", nm_platform_qdisc_to_string(qdisc, NULL, 0)); + return klass->qdisc_add(self, flags, qdisc); +} + +/** + * nm_platform_qdisc_sync: + * @self: the #NMPlatform instance + * @ifindex: the ifindex where to configure the qdiscs. + * @known_qdiscs: the list of qdiscs (#NMPObject). + * + * The function promises not to take any reference to the qdisc + * instances from @known_qdiscs, nor to keep them around after + * the function returns. This is important, because it allows the + * caller to pass NMPlatformQdisc instances which "kind" string + * have a limited lifetime. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs) +{ + gs_unref_ptrarray GPtrArray *plat_qdiscs = NULL; + NMPLookup lookup; + guint i; + gboolean success = TRUE; + gs_unref_hashtable GHashTable *known_qdiscs_idx = NULL; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + known_qdiscs_idx = + g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal); + if (known_qdiscs) { + for (i = 0; i < known_qdiscs->len; i++) { + const NMPObject *q = g_ptr_array_index(known_qdiscs, i); + + if (!g_hash_table_insert(known_qdiscs_idx, (gpointer) q, (gpointer) q)) { + _LOGW("duplicate qdisc %s", nm_platform_qdisc_to_string(&q->qdisc, NULL, 0)); + return FALSE; + } + } + } + + plat_qdiscs = + nm_platform_lookup_clone(self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex), + NULL, + NULL); + if (plat_qdiscs) { + for (i = 0; i < plat_qdiscs->len; i++) { + const NMPObject *p = g_ptr_array_index(plat_qdiscs, i); + const NMPObject *k; + + /* look up known qdisc with same parent */ + k = g_hash_table_lookup(known_qdiscs_idx, p); + + if (k) { + const NMPlatformQdisc *qdisc_k = NMP_OBJECT_CAST_QDISC(k); + const NMPlatformQdisc *qdisc_p = NMP_OBJECT_CAST_QDISC(p); + + /* check other fields */ + if (nm_platform_qdisc_cmp_full(qdisc_k, qdisc_p, FALSE) != 0 + || (qdisc_k->handle != qdisc_p->handle && qdisc_k != 0)) { + k = NULL; + } + } + + if (k) { + g_hash_table_remove(known_qdiscs_idx, k); + } else { + /* can't delete qdisc with zero handle */ + if (TC_H_MAJ(p->qdisc.handle) != 0) { + success &= nm_platform_object_delete(self, p); + } + } + } + } + + if (known_qdiscs) { + for (i = 0; i < known_qdiscs->len; i++) { + const NMPObject *q = g_ptr_array_index(known_qdiscs, i); + + if (g_hash_table_contains(known_qdiscs_idx, q)) { + success &= + (nm_platform_qdisc_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_QDISC(q)) >= 0); + } + } + } + + return success; +} + +/*****************************************************************************/ + +int +nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter) +{ + int ifindex = tfilter->ifindex; + _CHECK_SELF(self, klass, -NME_BUG); + + /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind + * and tfilter.action.kind is undefined. */ + + _LOG3D("adding or updating a tfilter: %s", nm_platform_tfilter_to_string(tfilter, NULL, 0)); + return klass->tfilter_add(self, flags, tfilter); +} + +/** + * nm_platform_qdisc_sync: + * @self: the #NMPlatform instance + * @ifindex: the ifindex where to configure the qdiscs. + * @known_tfilters: the list of tfilters (#NMPObject). + * + * The function promises not to take any reference to the tfilter + * instances from @known_tfilters, nor to keep them around after + * the function returns. This is important, because it allows the + * caller to pass NMPlatformTfilter instances which "kind" string + * have a limited lifetime. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters) +{ + gs_unref_ptrarray GPtrArray *plat_tfilters = NULL; + NMPLookup lookup; + guint i; + gboolean success = TRUE; + gs_unref_hashtable GHashTable *known_tfilters_idx = NULL; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + known_tfilters_idx = + g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal); + + if (known_tfilters) { + for (i = 0; i < known_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(known_tfilters, i); + + g_hash_table_insert(known_tfilters_idx, (gpointer) q, (gpointer) q); + } + } + + plat_tfilters = + nm_platform_lookup_clone(self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_TFILTER, ifindex), + NULL, + NULL); + + if (plat_tfilters) { + for (i = 0; i < plat_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(plat_tfilters, i); + + if (!g_hash_table_lookup(known_tfilters_idx, q)) + success &= nm_platform_object_delete(self, q); + } + } + + if (known_tfilters) { + for (i = 0; i < known_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(known_tfilters, i); + + success &= + (nm_platform_tfilter_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_TFILTER(q)) >= 0); + } + } + + return success; +} + +/*****************************************************************************/ + +const char * +nm_platform_vlan_qos_mapping_to_string(const char * name, + const NMVlanQosMapping *map, + gsize n_map, + char * buf, + gsize len) +{ + gsize i; + char *b; + + nm_utils_to_string_buffer_init(&buf, &len); + + if (!n_map) { + nm_utils_strbuf_append_str(&buf, &len, ""); + return buf; + } + + if (!map) + g_return_val_if_reached(""); + + b = buf; + + if (name) { + nm_utils_strbuf_append_str(&b, &len, name); + nm_utils_strbuf_append_str(&b, &len, " {"); + } else + nm_utils_strbuf_append_c(&b, &len, '{'); + + for (i = 0; i < n_map; i++) + nm_utils_strbuf_append(&b, &len, " %u:%u", map[i].from, map[i].to); + nm_utils_strbuf_append_str(&b, &len, " }"); + return buf; +} + +static const char * +_lifetime_to_string(guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size) +{ + if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT) + return "forever"; + + g_snprintf(buf, + buf_size, + "%usec", + nm_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now)); + return buf; +} + +static const char * +_lifetime_summary_to_string(gint32 now, + guint32 timestamp, + guint32 preferred, + guint32 lifetime, + char * buf, + size_t buf_size) +{ + g_snprintf(buf, + buf_size, + " lifetime %d-%u[%u,%u]", + (signed) now, + (unsigned) timestamp, + (unsigned) preferred, + (unsigned) lifetime); + return buf; +} + +/** + * nm_platform_link_to_string: + * @route: pointer to NMPlatformLink address structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting an link struct into a string representation. + * + * Returns: a string representation of the link. + */ +const char * +nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len) +{ + char master[20]; + char parent[20]; + char str_flags[1 + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 1]; + char str_highlighted_flags[50]; + char * s; + gsize l; + char str_addrmode[30]; + char str_address[NM_UTILS_HWADDR_LEN_MAX * 3]; + char str_broadcast[NM_UTILS_HWADDR_LEN_MAX * 3]; + char str_inet6_token[NM_UTILS_INET_ADDRSTRLEN]; + const char *str_link_type; + + if (!nm_utils_to_string_buffer_init_null(link, &buf, &len)) + return buf; + + s = str_highlighted_flags; + l = sizeof(str_highlighted_flags); + if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_NOARP)) + nm_utils_strbuf_append_str(&s, &l, "NOARP,"); + if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_UP)) + nm_utils_strbuf_append_str(&s, &l, "UP"); + else + nm_utils_strbuf_append_str(&s, &l, "DOWN"); + if (link->connected) + nm_utils_strbuf_append_str(&s, &l, ",LOWER_UP"); + nm_assert(s > str_highlighted_flags && l > 0); + + if (link->n_ifi_flags) { + str_flags[0] = ';'; + nm_platform_link_flags2str(link->n_ifi_flags, &str_flags[1], sizeof(str_flags) - 1); + } else + str_flags[0] = '\0'; + + if (link->master) + g_snprintf(master, sizeof(master), " master %d", link->master); + else + master[0] = 0; + + if (link->parent > 0) + g_snprintf(parent, sizeof(parent), "@%d", link->parent); + else if (link->parent == NM_PLATFORM_LINK_OTHER_NETNS) + g_strlcpy(parent, "@other-netns", sizeof(parent)); + else + parent[0] = 0; + + _nmp_link_address_to_string(&link->l_address, str_address); + _nmp_link_address_to_string(&link->l_broadcast, str_broadcast); + + str_link_type = nm_link_type_to_string(link->type); + + g_snprintf( + buf, + len, + "%d: " /* ifindex */ + "%s" /* name */ + "%s" /* parent */ + " <%s%s>" /* flags */ + " mtu %d" + "%s" /* master */ + " arp %u" /* arptype */ + " %s" /* link->type */ + "%s%s" /* kind */ + "%s" /* is-in-udev */ + "%s%s" /* addr-gen-mode */ + "%s%s" /* l_address */ + "%s%s" /* l_broadcast */ + "%s%s" /* inet6_token */ + "%s%s" /* driver */ + " rx:%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT " tx:%" G_GUINT64_FORMAT + ",%" G_GUINT64_FORMAT, + link->ifindex, + link->name, + parent, + str_highlighted_flags, + str_flags, + link->mtu, + master, + link->arptype, + str_link_type ?: "???", + link->kind ? (g_strcmp0(str_link_type, link->kind) ? "/" : "*") : "?", + link->kind && g_strcmp0(str_link_type, link->kind) ? link->kind : "", + link->initialized ? " init" : " not-init", + link->inet6_addr_gen_mode_inv ? " addrgenmode " : "", + link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str( + _nm_platform_uint8_inv(link->inet6_addr_gen_mode_inv), + str_addrmode, + sizeof(str_addrmode)) + : "", + str_address[0] ? " addr " : "", + str_address[0] ? str_address : "", + str_broadcast[0] ? " brd " : "", + str_broadcast[0] ? str_broadcast : "", + link->inet6_token.id ? " inet6token " : "", + link->inet6_token.id + ? nm_utils_inet6_interface_identifier_to_token(link->inet6_token, str_inet6_token) + : "", + link->driver ? " driver " : "", + link->driver ?: "", + link->rx_packets, + link->rx_bytes, + link->tx_packets, + link->tx_bytes); + return buf; +} + +const NMPlatformLnkBridge nm_platform_lnk_bridge_default = { + .forward_delay = NM_BRIDGE_FORWARD_DELAY_DEF_SYS, + .hello_time = NM_BRIDGE_HELLO_TIME_DEF_SYS, + .max_age = NM_BRIDGE_MAX_AGE_DEF_SYS, + .ageing_time = NM_BRIDGE_AGEING_TIME_DEF_SYS, + .stp_state = FALSE, + .priority = NM_BRIDGE_PRIORITY_DEF, + .vlan_protocol = 0x8100, + .vlan_stats_enabled = NM_BRIDGE_VLAN_STATS_ENABLED_DEF, + .group_fwd_mask = 0, + .group_addr = NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN), + .mcast_snooping = NM_BRIDGE_MULTICAST_SNOOPING_DEF, + .mcast_router = 1, + .mcast_query_use_ifaddr = NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF, + .mcast_querier = NM_BRIDGE_MULTICAST_QUERIER_DEF, + .mcast_hash_max = NM_BRIDGE_MULTICAST_HASH_MAX_DEF, + .mcast_last_member_count = NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF, + .mcast_startup_query_count = NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF, + .mcast_last_member_interval = NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF, + .mcast_membership_interval = NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF, + .mcast_querier_interval = NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF, + .mcast_query_interval = NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF, + .mcast_query_response_interval = NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF, + .mcast_startup_query_interval = NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF, +}; + +const char * +nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "forward_delay %u" + " hello_time %u" + " max_age %u" + " ageing_time %u" + " stp_state %d" + " priority %u" + " vlan_protocol %u" + " vlan_stats_enabled %d" + " group_fwd_mask %#x" + " group_address " NM_ETHER_ADDR_FORMAT_STR " mcast_snooping %d" + " mcast_router %u" + " mcast_query_use_ifaddr %d" + " mcast_querier %d" + " mcast_hash_max %u" + " mcast_last_member_count %u" + " mcast_startup_query_count %u" + " mcast_last_member_interval %" G_GUINT64_FORMAT + " mcast_membership_interval %" G_GUINT64_FORMAT + " mcast_querier_interval %" G_GUINT64_FORMAT + " mcast_query_interval %" G_GUINT64_FORMAT + " mcast_query_response_interval %" G_GUINT64_FORMAT + " mcast_startup_query_interval %" G_GUINT64_FORMAT "", + lnk->forward_delay, + lnk->hello_time, + lnk->max_age, + lnk->ageing_time, + (int) lnk->stp_state, + lnk->priority, + lnk->vlan_protocol, + (int) lnk->vlan_stats_enabled, + lnk->group_fwd_mask, + NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr), + (int) lnk->mcast_snooping, + lnk->mcast_router, + (int) lnk->mcast_query_use_ifaddr, + (int) lnk->mcast_querier, + lnk->mcast_hash_max, + lnk->mcast_last_member_count, + lnk->mcast_startup_query_count, + lnk->mcast_last_member_interval, + lnk->mcast_membership_interval, + lnk->mcast_querier_interval, + lnk->mcast_query_interval, + lnk->mcast_query_response_interval, + lnk->mcast_startup_query_interval); + return buf; +} + +const char * +nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_parent_ifindex[30]; + char str_input_flags[30]; + char str_output_flags[30]; + char str_input_key[30]; + char str_input_key1[NM_UTILS_INET_ADDRSTRLEN]; + char str_output_key[30]; + char str_output_key1[NM_UTILS_INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "gre%s" /* is_tap */ + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "%s" /* iflags */ + "%s" /* oflags */ + "%s" /* ikey */ + "%s" /* okey */ + "", + lnk->is_tap ? "tap" : "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc", + lnk->input_flags ? nm_sprintf_buf(str_input_flags, " iflags 0x%x", lnk->input_flags) : "", + lnk->output_flags ? nm_sprintf_buf(str_output_flags, " oflags 0x%x", lnk->output_flags) + : "", + NM_FLAGS_HAS(lnk->input_flags, GRE_KEY) || lnk->input_key + ? nm_sprintf_buf(str_input_key, + " ikey %s", + _nm_utils_inet4_ntop(lnk->input_key, str_input_key1)) + : "", + NM_FLAGS_HAS(lnk->output_flags, GRE_KEY) || lnk->output_key + ? nm_sprintf_buf(str_output_key, + " okey %s", + _nm_utils_inet4_ntop(lnk->output_key, str_output_key1)) + : ""); + return buf; +} + +const char * +nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len) +{ + char str_p_key[64]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "infiniband" + "%s" /* p_key */ + "%s%s" /* mode */ + "", + lnk->p_key ? nm_sprintf_buf(str_p_key, " pkey %d", lnk->p_key) : "", + lnk->mode ? " mode " : "", + lnk->mode ?: ""); + return buf; +} + +const char * +nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tclass[30]; + char str_flow[30]; + char str_encap[30]; + char str_proto[30]; + char str_parent_ifindex[30]; + char *str_type; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->is_gre) + str_type = lnk->is_tap ? "ip6gretap" : "ip6gre"; + else + str_type = "ip6tnl"; + + g_snprintf( + buf, + len, + "%s" /* type */ + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tclass */ + "%s" /* encap limit */ + "%s" /* flow label */ + "%s" /* proto */ + " flags 0x%x" + "", + str_type, + nm_sprintf_buf(str_remote, " remote %s", _nm_utils_inet6_ntop(&lnk->remote, str_remote1)), + nm_sprintf_buf(str_local, " local %s", _nm_utils_inet6_ntop(&lnk->local, str_local1)), + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tclass == 1 ? " tclass inherit" + : nm_sprintf_buf(str_tclass, " tclass 0x%x", lnk->tclass), + nm_sprintf_buf(str_encap, " encap-limit %u", lnk->encap_limit), + nm_sprintf_buf(str_flow, " flow-label 0x05%x", lnk->flow_label), + nm_sprintf_buf(str_proto, " proto %u", lnk->proto), + (guint) lnk->flags); + return buf; +} + +const char * +nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_parent_ifindex[30]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "ipip" + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc"); + return buf; +} + +const char * +nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "macsec " + "sci %016llx " + "protect %s " + "cipher %016llx " + "icvlen %u " + "encodingsa %u " + "validate %u " + "encrypt %s " + "send_sci %s " + "end_station %s " + "scb %s " + "replay %s", + (unsigned long long) lnk->sci, + lnk->protect ? "on" : "off", + (unsigned long long) lnk->cipher_suite, + lnk->icv_length, + lnk->encoding_sa, + lnk->validation, + lnk->encrypt ? "on" : "off", + lnk->include_sci ? "on" : "off", + lnk->es ? "on" : "off", + lnk->scb ? "on" : "off", + lnk->replay_protect ? "on" : "off"); + return buf; +} + +const char * +nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "%s mode %u %s", + lnk->tap ? "macvtap" : "macvlan", + lnk->mode, + lnk->no_promisc ? "not-promisc" : "promisc"); + return buf; +} + +const char * +nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_flags[30]; + char str_proto[30]; + char str_parent_ifindex[30]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "sit" + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "%s" /* flags */ + "%s" /* proto */ + "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc", + lnk->flags ? nm_sprintf_buf(str_flags, " flags 0x%x", lnk->flags) : "", + lnk->proto ? nm_sprintf_buf(str_proto, " proto 0x%x", lnk->proto) : ""); + return buf; +} + +const char * +nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len) +{ + char str_owner[50]; + char str_group[50]; + char str_type[50]; + const char *type; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->type == IFF_TUN) + type = "tun"; + else if (lnk->type == IFF_TAP) + type = "tap"; + else + type = nm_sprintf_buf(str_type, "tun type %u", (guint) lnk->type); + + g_snprintf(buf, + len, + "%s" /* type */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + "%s" /* persist */ + "%s" /* owner */ + "%s" /* group */ + "", + type, + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + lnk->persist ? " persist" : "", + lnk->owner_valid ? nm_sprintf_buf(str_owner, " owner %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_buf(str_group, " group %u", (guint) lnk->group) : ""); + return buf; +} + +const char * +nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *lnk, char *buf, gsize len) +{ + char *b; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + b = buf; + + nm_utils_strbuf_append(&b, &len, "vlan %u", lnk->id); + if (lnk->flags) + nm_utils_strbuf_append(&b, &len, " flags 0x%x", lnk->flags); + return buf; +} + +const char * +nm_platform_lnk_vrf_to_string(const NMPlatformLnkVrf *lnk, char *buf, gsize len) +{ + char *b; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + b = buf; + + nm_utils_strbuf_append(&b, &len, "table %u", lnk->table); + return buf; +} + +const char * +nm_platform_lnk_vxlan_to_string(const NMPlatformLnkVxlan *lnk, char *buf, gsize len) +{ + char str_group[100]; + char str_group6[100]; + char str_local[100]; + char str_local6[100]; + char str_dev[25]; + char str_limit[25]; + char str_src_port[35]; + char str_dst_port[25]; + char str_tos[25]; + char str_ttl[25]; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->group == 0) + str_group[0] = '\0'; + else { + g_snprintf(str_group, + sizeof(str_group), + " %s %s", + IN_MULTICAST(ntohl(lnk->group)) ? "group" : "remote", + _nm_utils_inet4_ntop(lnk->group, sbuf)); + } + if (IN6_IS_ADDR_UNSPECIFIED(&lnk->group6)) + str_group6[0] = '\0'; + else { + g_snprintf(str_group6, + sizeof(str_group6), + " %s%s %s", + IN6_IS_ADDR_MULTICAST(&lnk->group6) ? "group" : "remote", + str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */ + _nm_utils_inet6_ntop(&lnk->group6, sbuf)); + } + + if (lnk->local == 0) + str_local[0] = '\0'; + else { + g_snprintf(str_local, + sizeof(str_local), + " local %s", + _nm_utils_inet4_ntop(lnk->local, sbuf)); + } + if (IN6_IS_ADDR_UNSPECIFIED(&lnk->local6)) + str_local6[0] = '\0'; + else { + g_snprintf(str_local6, + sizeof(str_local6), + " local%s %s", + str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */ + _nm_utils_inet6_ntop(&lnk->local6, sbuf)); + } + + g_snprintf( + buf, + len, + "vxlan" + " id %u" /* id */ + "%s%s" /* group/group6 */ + "%s%s" /* local/local6 */ + "%s" /* dev */ + "%s" /* src_port_min/src_port_max */ + "%s" /* dst_port */ + "%s" /* learning */ + "%s" /* proxy */ + "%s" /* rsc */ + "%s" /* l2miss */ + "%s" /* l3miss */ + "%s" /* tos */ + "%s" /* ttl */ + " ageing %u" /* ageing */ + "%s" /* limit */ + "", + (guint) lnk->id, + str_group, + str_group6, + str_local, + str_local6, + lnk->parent_ifindex ? nm_sprintf_buf(str_dev, " dev %d", lnk->parent_ifindex) : "", + lnk->src_port_min || lnk->src_port_max + ? nm_sprintf_buf(str_src_port, " srcport %u %u", lnk->src_port_min, lnk->src_port_max) + : "", + lnk->dst_port ? nm_sprintf_buf(str_dst_port, " dstport %u", lnk->dst_port) : "", + !lnk->learning ? " nolearning" : "", + lnk->proxy ? " proxy" : "", + lnk->rsc ? " rsc" : "", + lnk->l2miss ? " l2miss" : "", + lnk->l3miss ? " l3miss" : "", + lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos %#x", lnk->tos), + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : "", + lnk->ageing, + lnk->limit ? nm_sprintf_buf(str_limit, " maxaddr %u", lnk->limit) : ""); + return buf; +} + +const char * +nm_platform_wireguard_peer_to_string(const NMPWireGuardPeer *peer, char *buf, gsize len) +{ + char * buf0 = buf; + gs_free char *public_key_b64 = NULL; + char s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100]; + char s_endpoint[20 + sizeof(s_sockaddr)]; + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + char s_keepalive[100]; + guint i; + + nm_utils_to_string_buffer_init(&buf, &len); + + public_key_b64 = g_base64_encode(peer->public_key, sizeof(peer->public_key)); + + if (peer->endpoint.sa.sa_family != AF_UNSPEC) { + nm_sprintf_buf( + s_endpoint, + " endpoint %s", + nm_sock_addr_union_to_string(&peer->endpoint, s_sockaddr, sizeof(s_sockaddr))); + } else + s_endpoint[0] = '\0'; + + nm_utils_strbuf_append( + &buf, + &len, + "public-key %s" + "%s" /* preshared-key */ + "%s" /* endpoint */ + " rx %" G_GUINT64_FORMAT " tx %" G_GUINT64_FORMAT "%s" /* persistent-keepalive */ + "%s", /* allowed-ips */ + public_key_b64, + nm_utils_memeqzero_secret(peer->preshared_key, sizeof(peer->preshared_key)) + ? "" + : " preshared-key (hidden)", + s_endpoint, + peer->rx_bytes, + peer->tx_bytes, + peer->persistent_keepalive_interval > 0 + ? nm_sprintf_buf(s_keepalive, + " keepalive %u", + (guint) peer->persistent_keepalive_interval) + : "", + peer->allowed_ips_len > 0 ? " allowed-ips" : ""); + + for (i = 0; i < peer->allowed_ips_len; i++) { + const NMPWireGuardAllowedIP *allowed_ip = &peer->allowed_ips[i]; + + nm_utils_strbuf_append(&buf, + &len, + " %s/%u", + nm_utils_inet_ntop(allowed_ip->family, &allowed_ip->addr, s_addr), + allowed_ip->mask); + } + + return buf0; +} + +const char * +nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len) +{ + gs_free char *public_b64 = NULL; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (!nm_utils_memeqzero(lnk->public_key, sizeof(lnk->public_key))) + public_b64 = g_base64_encode(lnk->public_key, sizeof(lnk->public_key)); + + g_snprintf(buf, + len, + "wireguard" + "%s%s" /* public-key */ + "%s" /* private-key */ + " listen-port %u" + " fwmark 0x%x", + public_b64 ? " public-key " : "", + public_b64 ?: "", + nm_utils_memeqzero_secret(lnk->private_key, sizeof(lnk->private_key)) + ? "" + : " private-key (hidden)", + lnk->listen_port, + lnk->fwmark); + + return buf; +} + +/** + * nm_platform_ip4_address_to_string: + * @route: pointer to NMPlatformIP4Address address structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting an address struct into a string representation. + * + * Example output: "" + * + * Returns: a string representation of the address. + */ +const char * +nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len) +{ + char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE]; + char s_address[INET_ADDRSTRLEN]; + char s_peer[INET_ADDRSTRLEN]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char str_label[32]; + char str_lft[30], str_pref[30], str_time[50], s_source[50]; + char * str_peer = NULL; + const char *str_lft_p, *str_pref_p, *str_time_p; + gint32 now = nm_utils_get_monotonic_timestamp_sec(); + in_addr_t broadcast_address; + char str_broadcast[INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(address, &buf, &len)) + return buf; + + inet_ntop(AF_INET, &address->address, s_address, sizeof(s_address)); + + if (address->peer_address != address->address) { + inet_ntop(AF_INET, &address->peer_address, s_peer, sizeof(s_peer)); + str_peer = g_strconcat(" ptp ", s_peer, NULL); + } + + _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev)); + + if (*address->label) + g_snprintf(str_label, sizeof(str_label), " label %s", address->label); + else + str_label[0] = 0; + + str_lft_p = _lifetime_to_string(address->timestamp, + address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_lft, + sizeof(str_lft)), + str_pref_p = + (address->lifetime == address->preferred) + ? str_lft_p + : (_lifetime_to_string(address->timestamp, + address->lifetime ? MIN(address->preferred, address->lifetime) + : NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_pref, + sizeof(str_pref))); + str_time_p = _lifetime_summary_to_string(now, + address->timestamp, + address->preferred, + address->lifetime, + str_time, + sizeof(str_time)); + + broadcast_address = nm_platform_ip4_broadcast_address_from_addr(address); + + g_snprintf( + buf, + len, + "%s/%d" + "%s%s" /* broadcast */ + " lft %s" + " pref %s" + "%s" /* time */ + "%s" /* peer */ + "%s" /* dev */ + "%s" /* flags */ + "%s" /* label */ + " src %s" + "%s" /* external */ + "%s" /* ip4acd_not_ready */ + "", + s_address, + address->plen, + broadcast_address != 0u || address->use_ip4_broadcast_address + ? (address->use_ip4_broadcast_address ? " brd " : " brd* ") + : "", + broadcast_address != 0u || address->use_ip4_broadcast_address + ? _nm_utils_inet4_ntop(broadcast_address, str_broadcast) + : "", + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + str_dev, + _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)), + str_label, + nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), + address->external ? " ext" : "", + address->ip4acd_not_ready ? " ip4acd-not-ready" : ""); + g_free(str_peer); + return buf; +} + +NM_UTILS_FLAGS2STR_DEFINE(nm_platform_link_flags2str, + unsigned, + NM_UTILS_FLAGS2STR(IFF_LOOPBACK, "loopback"), + NM_UTILS_FLAGS2STR(IFF_BROADCAST, "broadcast"), + NM_UTILS_FLAGS2STR(IFF_POINTOPOINT, "pointopoint"), + NM_UTILS_FLAGS2STR(IFF_MULTICAST, "multicast"), + NM_UTILS_FLAGS2STR(IFF_NOARP, "noarp"), + NM_UTILS_FLAGS2STR(IFF_ALLMULTI, "allmulti"), + NM_UTILS_FLAGS2STR(IFF_PROMISC, "promisc"), + NM_UTILS_FLAGS2STR(IFF_MASTER, "master"), + NM_UTILS_FLAGS2STR(IFF_SLAVE, "slave"), + NM_UTILS_FLAGS2STR(IFF_DEBUG, "debug"), + NM_UTILS_FLAGS2STR(IFF_DYNAMIC, "dynamic"), + NM_UTILS_FLAGS2STR(IFF_AUTOMEDIA, "automedia"), + NM_UTILS_FLAGS2STR(IFF_PORTSEL, "portsel"), + NM_UTILS_FLAGS2STR(IFF_NOTRAILERS, "notrailers"), + NM_UTILS_FLAGS2STR(IFF_UP, "up"), + NM_UTILS_FLAGS2STR(IFF_RUNNING, "running"), + NM_UTILS_FLAGS2STR(IFF_LOWER_UP, "lowerup"), + NM_UTILS_FLAGS2STR(IFF_DORMANT, "dormant"), + NM_UTILS_FLAGS2STR(IFF_ECHO, "echo"), ); + +NM_UTILS_ENUM2STR_DEFINE(nm_platform_link_inet6_addrgenmode2str, + guint8, + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_NONE, "none"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_EUI64, "eui64"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_RANDOM, "random"), ); + +NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str, + unsigned, + NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"), + NM_UTILS_FLAGS2STR(IFA_F_NODAD, "nodad"), + NM_UTILS_FLAGS2STR(IFA_F_OPTIMISTIC, "optimistic"), + NM_UTILS_FLAGS2STR(IFA_F_HOMEADDRESS, "homeaddress"), + NM_UTILS_FLAGS2STR(IFA_F_DEPRECATED, "deprecated"), + NM_UTILS_FLAGS2STR(IFA_F_PERMANENT, "permanent"), + NM_UTILS_FLAGS2STR(IFA_F_MANAGETEMPADDR, "mngtmpaddr"), + NM_UTILS_FLAGS2STR(IFA_F_NOPREFIXROUTE, "noprefixroute"), + NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), ); + +NM_UTILS_ENUM2STR_DEFINE(nm_platform_route_scope2str, + int, + NM_UTILS_ENUM2STR(RT_SCOPE_NOWHERE, "nowhere"), + NM_UTILS_ENUM2STR(RT_SCOPE_HOST, "host"), + NM_UTILS_ENUM2STR(RT_SCOPE_LINK, "link"), + NM_UTILS_ENUM2STR(RT_SCOPE_SITE, "site"), + NM_UTILS_ENUM2STR(RT_SCOPE_UNIVERSE, "global"), ); + +/** + * nm_platform_ip6_address_to_string: + * @route: pointer to NMPlatformIP6Address address structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting an address struct into a string representation. + * + * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1" + * + * Returns: a string representation of the address. + */ +const char * +nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len) +{ + char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE]; + char s_address[INET6_ADDRSTRLEN]; + char s_peer[INET6_ADDRSTRLEN]; + char str_lft[30], str_pref[30], str_time[50], s_source[50]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char * str_peer = NULL; + const char *str_lft_p, *str_pref_p, *str_time_p; + gint32 now = nm_utils_get_monotonic_timestamp_sec(); + + if (!nm_utils_to_string_buffer_init_null(address, &buf, &len)) + return buf; + + inet_ntop(AF_INET6, &address->address, s_address, sizeof(s_address)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&address->peer_address)) { + inet_ntop(AF_INET6, &address->peer_address, s_peer, sizeof(s_peer)); + str_peer = g_strconcat(" ptp ", s_peer, NULL); + } + + _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev)); + + str_lft_p = _lifetime_to_string(address->timestamp, + address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_lft, + sizeof(str_lft)), + str_pref_p = + (address->lifetime == address->preferred) + ? str_lft_p + : (_lifetime_to_string(address->timestamp, + address->lifetime ? MIN(address->preferred, address->lifetime) + : NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_pref, + sizeof(str_pref))); + str_time_p = _lifetime_summary_to_string(now, + address->timestamp, + address->preferred, + address->lifetime, + str_time, + sizeof(str_time)); + + g_snprintf( + buf, + len, + "%s/%d lft %s pref %s%s%s%s%s src %s%s", + s_address, + address->plen, + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + str_dev, + _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)), + nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), + address->external ? " ext" : ""); + g_free(str_peer); + return buf; +} + +static NM_UTILS_FLAGS2STR_DEFINE(_rtm_flags_to_string, + unsigned, + NM_UTILS_FLAGS2STR(RTNH_F_DEAD, "dead"), + NM_UTILS_FLAGS2STR(RTNH_F_PERVASIVE, "pervasive"), + NM_UTILS_FLAGS2STR(RTNH_F_ONLINK, "onlink"), + NM_UTILS_FLAGS2STR(8 /*RTNH_F_OFFLOAD*/, "offload"), + NM_UTILS_FLAGS2STR(16 /*RTNH_F_LINKDOWN*/, "linkdown"), + NM_UTILS_FLAGS2STR(32 /*RTNH_F_UNRESOLVED*/, "unresolved"), + + NM_UTILS_FLAGS2STR(RTM_F_NOTIFY, "notify"), + NM_UTILS_FLAGS2STR(RTM_F_CLONED, "cloned"), + NM_UTILS_FLAGS2STR(RTM_F_EQUALIZE, "equalize"), + NM_UTILS_FLAGS2STR(RTM_F_PREFIX, "prefix"), + NM_UTILS_FLAGS2STR(0x1000 /*RTM_F_LOOKUP_TABLE*/, "lookup-table"), + NM_UTILS_FLAGS2STR(0x2000 /*RTM_F_FIB_MATCH*/, "fib-match"), ); + +#define _RTM_FLAGS_TO_STRING_MAXLEN 200 + +static const char * +_rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags) +{ + const char *buf0 = buf; + + nm_assert(buf_size >= _RTM_FLAGS_TO_STRING_MAXLEN); + + if (!rtm_flags) + return ""; + + nm_utils_strbuf_append_str(&buf, &buf_size, " rtm_flags "); + _rtm_flags_to_string(rtm_flags, buf, buf_size); + nm_assert(strlen(buf) < buf_size); + return buf0; +} + +/** + * nm_platform_ip4_route_to_string: + * @route: pointer to NMPlatformIP4Route route structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting a route struct into a string representation. + * + * Example output: "192.168.1.0/24 via 0.0.0.0 dev em1 metric 0 mss 0" + * + * Returns: a string representation of the route. + */ +const char * +nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len) +{ + char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN]; + char s_pref_src[INET_ADDRSTRLEN]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char str_table[30]; + char str_scope[30], s_source[50]; + char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32]; + char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN]; + char str_type[30]; + char str_metric[30]; + + if (!nm_utils_to_string_buffer_init_null(route, &buf, &len)) + return buf; + + inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network)); + inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway)); + + _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev)); + + g_snprintf( + buf, + len, + "type %s " /* type */ + "%s" /* table */ + "%s/%d" + " via %s" + "%s" + " metric %s" + " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ + "%s" /* rtm_flags */ + "%s%s" /* scope */ + "%s%s" /* pref-src */ + "%s" /* tos */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "", + nm_utils_route_type2str(nm_platform_route_type_uncoerce(route->type_coerced), + str_type, + sizeof(str_type)), + route->table_any + ? "table ?? " + : (route->table_coerced + ? nm_sprintf_buf(str_table, + "table %u ", + nm_platform_route_table_uncoerce(route->table_coerced, FALSE)) + : ""), + s_network, + route->plen, + s_gateway, + str_dev, + route->metric_any + ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??") + : nm_sprintf_buf(str_metric, "%u", route->metric), + route->mss, + nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)), + _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags), + route->scope_inv ? " scope " : "", + route->scope_inv + ? (nm_platform_route_scope2str(nm_platform_route_scope_inv(route->scope_inv), + str_scope, + sizeof(str_scope))) + : "", + route->pref_src ? " pref-src " : "", + route->pref_src ? inet_ntop(AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "", + route->tos ? nm_sprintf_buf(str_tos, " tos 0x%x", (unsigned) route->tos) : "", + route->window || route->lock_window ? nm_sprintf_buf(str_window, + " window %s%" G_GUINT32_FORMAT, + route->lock_window ? "lock " : "", + route->window) + : "", + route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd, + " cwnd %s%" G_GUINT32_FORMAT, + route->lock_cwnd ? "lock " : "", + route->cwnd) + : "", + route->initcwnd || route->lock_initcwnd + ? nm_sprintf_buf(str_initcwnd, + " initcwnd %s%" G_GUINT32_FORMAT, + route->lock_initcwnd ? "lock " : "", + route->initcwnd) + : "", + route->initrwnd || route->lock_initrwnd + ? nm_sprintf_buf(str_initrwnd, + " initrwnd %s%" G_GUINT32_FORMAT, + route->lock_initrwnd ? "lock " : "", + route->initrwnd) + : "", + route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu, + " mtu %s%" G_GUINT32_FORMAT, + route->lock_mtu ? "lock " : "", + route->mtu) + : ""); + return buf; +} + +/** + * nm_platform_ip6_route_to_string: + * @route: pointer to NMPlatformIP6Route route structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting a route struct into a string representation. + * + * Example output: "ff02::fb/128 via :: dev em1 metric 0" + * + * Returns: a string representation of the route. + */ +const char * +nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len) +{ + char s_network[INET6_ADDRSTRLEN]; + char s_gateway[INET6_ADDRSTRLEN]; + char s_pref_src[INET6_ADDRSTRLEN]; + char s_src_all[INET6_ADDRSTRLEN + 40]; + char s_src[INET6_ADDRSTRLEN]; + char str_type[30]; + char str_table[30]; + char str_pref[40]; + char str_pref2[30]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char s_source[50]; + char str_window[32]; + char str_cwnd[32]; + char str_initcwnd[32]; + char str_initrwnd[32]; + char str_mtu[32]; + char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN]; + char str_metric[30]; + + if (!nm_utils_to_string_buffer_init_null(route, &buf, &len)) + return buf; + + inet_ntop(AF_INET6, &route->network, s_network, sizeof(s_network)); + inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway)); + + if (IN6_IS_ADDR_UNSPECIFIED(&route->pref_src)) + s_pref_src[0] = 0; + else + inet_ntop(AF_INET6, &route->pref_src, s_pref_src, sizeof(s_pref_src)); + + _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev)); + + g_snprintf( + buf, + len, + "type %s " /* type */ + "%s" /* table */ + "%s/%d" + " via %s" + "%s" + " metric %s" + " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ + "%s" /* source */ + "%s" /* rtm_flags */ + "%s%s" /* pref-src */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "%s" /* pref */ + "", + nm_utils_route_type2str(nm_platform_route_type_uncoerce(route->type_coerced), + str_type, + sizeof(str_type)), + route->table_any + ? "table ?? " + : (route->table_coerced + ? nm_sprintf_buf(str_table, + "table %u ", + nm_platform_route_table_uncoerce(route->table_coerced, FALSE)) + : ""), + s_network, + route->plen, + s_gateway, + str_dev, + route->metric_any + ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??") + : nm_sprintf_buf(str_metric, "%u", route->metric), + route->mss, + nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)), + route->src_plen || !IN6_IS_ADDR_UNSPECIFIED(&route->src) + ? nm_sprintf_buf(s_src_all, + " src %s/%u", + _nm_utils_inet6_ntop(&route->src, s_src), + (unsigned) route->src_plen) + : "", + _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags), + s_pref_src[0] ? " pref-src " : "", + s_pref_src[0] ? s_pref_src : "", + route->window || route->lock_window ? nm_sprintf_buf(str_window, + " window %s%" G_GUINT32_FORMAT, + route->lock_window ? "lock " : "", + route->window) + : "", + route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd, + " cwnd %s%" G_GUINT32_FORMAT, + route->lock_cwnd ? "lock " : "", + route->cwnd) + : "", + route->initcwnd || route->lock_initcwnd + ? nm_sprintf_buf(str_initcwnd, + " initcwnd %s%" G_GUINT32_FORMAT, + route->lock_initcwnd ? "lock " : "", + route->initcwnd) + : "", + route->initrwnd || route->lock_initrwnd + ? nm_sprintf_buf(str_initrwnd, + " initrwnd %s%" G_GUINT32_FORMAT, + route->lock_initrwnd ? "lock " : "", + route->initrwnd) + : "", + route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu, + " mtu %s%" G_GUINT32_FORMAT, + route->lock_mtu ? "lock " : "", + route->mtu) + : "", + route->rt_pref ? nm_sprintf_buf( + str_pref, + " pref %s", + nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2))) + : ""); + + return buf; +} + +static void +_routing_rule_addr_to_string(char ** buf, + gsize * len, + int addr_family, + const NMIPAddr *addr, + guint8 plen, + gboolean is_src) +{ + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + gboolean is_zero; + gsize addr_size; + + nm_assert_addr_family(addr_family); + nm_assert(addr); + + addr_size = nm_utils_addr_family_to_size(addr_family); + + is_zero = nm_utils_memeqzero(addr, addr_size); + + if (plen == 0 && is_zero) { + if (is_src) + nm_utils_strbuf_append_str(buf, len, " from all"); + else + nm_utils_strbuf_append_str(buf, len, ""); + return; + } + + nm_utils_strbuf_append_str(buf, len, is_src ? " from " : " to "); + + nm_utils_strbuf_append_str(buf, len, nm_utils_inet_ntop(addr_family, addr, s_addr)); + + if (plen != (addr_size * 8)) + nm_utils_strbuf_append(buf, len, "/%u", plen); +} + +static void +_routing_rule_port_range_to_string(char ** buf, + gsize * len, + const NMFibRulePortRange *port_range, + const char * name) +{ + if (port_range->start == 0 && port_range->end == 0) + nm_utils_strbuf_append_str(buf, len, ""); + else { + nm_utils_strbuf_append(buf, len, " %s %u", name, port_range->start); + if (port_range->start != port_range->end) + nm_utils_strbuf_append(buf, len, "-%u", port_range->end); + } +} + +const char * +nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len) +{ + const char *buf0; + guint32 rr_flags; + + if (!nm_utils_to_string_buffer_init_null(routing_rule, &buf, &len)) + return buf; + + if (!NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6)) { + /* invalid addr-family. The other fields are undefined. */ + if (routing_rule->addr_family == AF_UNSPEC) + g_snprintf(buf, len, "[routing-rule]"); + else + g_snprintf(buf, len, "[routing-rule family:%u]", routing_rule->addr_family); + return buf; + } + + buf0 = buf; + + rr_flags = routing_rule->flags; + + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_INVERT); + nm_utils_strbuf_append(&buf, + &len, + "[%c] " /* addr-family */ + "%u:" /* priority */ + "%s", /* not/FIB_RULE_INVERT */ + nm_utils_addr_family_to_char(routing_rule->addr_family), + routing_rule->priority, + (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_INVERT) ? " not" : "")); + + _routing_rule_addr_to_string(&buf, + &len, + routing_rule->addr_family, + &routing_rule->src, + routing_rule->src_len, + TRUE); + + _routing_rule_addr_to_string(&buf, + &len, + routing_rule->addr_family, + &routing_rule->dst, + routing_rule->dst_len, + FALSE); + + if (routing_rule->tos) + nm_utils_strbuf_append(&buf, &len, " tos 0x%02x", routing_rule->tos); + + if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) { + nm_utils_strbuf_append(&buf, &len, " fwmark %#x", (unsigned) routing_rule->fwmark); + if (routing_rule->fwmark != 0xFFFFFFFFu) + nm_utils_strbuf_append(&buf, &len, "/%#x", (unsigned) routing_rule->fwmask); + } + + if (routing_rule->iifname[0]) { + nm_utils_strbuf_append(&buf, &len, " iif %s", routing_rule->iifname); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_IIF_DETACHED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_IIF_DETACHED)) + nm_utils_strbuf_append_str(&buf, &len, " [detached]"); + } + + if (routing_rule->oifname[0]) { + nm_utils_strbuf_append(&buf, &len, " oif %s", routing_rule->oifname); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_OIF_DETACHED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_OIF_DETACHED)) + nm_utils_strbuf_append_str(&buf, &len, " [detached]"); + } + + if (routing_rule->l3mdev != 0) { + if (routing_rule->l3mdev == 1) + nm_utils_strbuf_append_str(&buf, &len, " lookup [l3mdev-table]"); + else { + nm_utils_strbuf_append(&buf, + &len, + " lookup [l3mdev-table/%u]", + (unsigned) routing_rule->l3mdev); + } + } + + if (routing_rule->uid_range_has || routing_rule->uid_range.start + || routing_rule->uid_range.end) { + nm_utils_strbuf_append(&buf, + &len, + " uidrange %u-%u%s", + routing_rule->uid_range.start, + routing_rule->uid_range.end, + routing_rule->uid_range_has ? "" : "(?)"); + } + + if (routing_rule->ip_proto != 0) { + /* we don't call getprotobynumber(), just print the numeric value. + * This differs from what ip-rule prints. */ + nm_utils_strbuf_append(&buf, &len, " ipproto %u", routing_rule->ip_proto); + } + + _routing_rule_port_range_to_string(&buf, &len, &routing_rule->sport_range, "sport"); + + _routing_rule_port_range_to_string(&buf, &len, &routing_rule->dport_range, "dport"); + + if (routing_rule->tun_id != 0) { + nm_utils_strbuf_append(&buf, &len, " tun_id %" G_GUINT64_FORMAT, routing_rule->tun_id); + } + + if (routing_rule->table != 0) { + nm_utils_strbuf_append(&buf, &len, " lookup %u", routing_rule->table); + } + + if (routing_rule->suppress_prefixlen_inverse != 0) { + nm_utils_strbuf_append(&buf, + &len, + " suppress_prefixlen %d", + (int) (~routing_rule->suppress_prefixlen_inverse)); + } + + if (routing_rule->suppress_ifgroup_inverse != 0) { + nm_utils_strbuf_append(&buf, + &len, + " suppress_ifgroup %d", + (int) (~routing_rule->suppress_ifgroup_inverse)); + } + + if (routing_rule->flow) { + /* FRA_FLOW is only for IPv4, but we want to print the value for all address-families, + * to see when it is set. In practice, this should not be set except for IPv4. + * + * We don't follow the style how ip-rule prints flow/realms. It's confusing. Just + * print the value hex. */ + nm_utils_strbuf_append(&buf, &len, " realms 0x%08x", routing_rule->flow); + } + + if (routing_rule->action == RTN_NAT) { + G_STATIC_ASSERT_EXPR(RTN_NAT == 10); + + /* NAT is deprecated for many years. We don't support RTA_GATEWAY/FRA_UNUSED2 + * for the gateway, and so do recent kernels ignore that parameter. */ + nm_utils_strbuf_append_str(&buf, &len, " masquerade"); + } else if (routing_rule->action == FR_ACT_GOTO) { + if (routing_rule->goto_target != 0) + nm_utils_strbuf_append(&buf, &len, " goto %u", routing_rule->goto_target); + else + nm_utils_strbuf_append_str(&buf, &len, " goto none"); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_UNRESOLVED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_UNRESOLVED)) + nm_utils_strbuf_append_str(&buf, &len, " unresolved"); + } else if (routing_rule->action != FR_ACT_TO_TBL) { + const char *ss; + char ss_buf[60]; + +#define _V(v1, v2) ((sizeof(char[(((int) (v1)) == ((int) (v2))) ? 1 : -1]) * 0) + (v1)) + switch (routing_rule->action) { + case _V(FR_ACT_UNSPEC, RTN_UNSPEC): + ss = "none"; + break; + case _V(FR_ACT_TO_TBL, RTN_UNICAST): + ss = "unicast"; + break; + case _V(FR_ACT_GOTO, RTN_LOCAL): + ss = "local"; + break; + case _V(FR_ACT_NOP, RTN_BROADCAST): + ss = "nop"; + break; + case _V(FR_ACT_RES3, RTN_ANYCAST): + ss = "anycast"; + break; + case _V(FR_ACT_RES4, RTN_MULTICAST): + ss = "multicast"; + break; + case _V(FR_ACT_BLACKHOLE, RTN_BLACKHOLE): + ss = "blackhole"; + break; + case _V(FR_ACT_UNREACHABLE, RTN_UNREACHABLE): + ss = "unreachable"; + break; + case _V(FR_ACT_PROHIBIT, RTN_PROHIBIT): + ss = "prohibit"; + break; + case RTN_THROW: + ss = "throw"; + break; + case RTN_NAT: + ss = "nat"; + break; + case RTN_XRESOLVE: + ss = "xresolve"; + break; + default: + ss = nm_sprintf_buf(ss_buf, "action-%u", routing_rule->action); + break; + } +#undef _V + nm_utils_strbuf_append(&buf, &len, " %s", ss); + } + + if (routing_rule->protocol != RTPROT_UNSPEC) + nm_utils_strbuf_append(&buf, &len, " protocol %u", routing_rule->protocol); + + if (routing_rule->goto_target != 0 && routing_rule->action != FR_ACT_GOTO) { + /* a trailing target is set for an unexpected action. Print it. */ + nm_utils_strbuf_append(&buf, &len, " goto-target %u", routing_rule->goto_target); + } + + if (rr_flags != 0) { + /* we have some flags we didn't print about yet. */ + nm_utils_strbuf_append(&buf, &len, " remaining-flags %x", rr_flags); + } + + return buf0; +} + +const char * +nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + const char *buf0; + + if (!nm_utils_to_string_buffer_init_null(qdisc, &buf, &len)) + return buf; + + buf0 = buf; + + nm_utils_strbuf_append(&buf, + &len, + "%s%s family %u handle %x parent %x info %x", + qdisc->kind, + _to_string_dev(NULL, qdisc->ifindex, str_dev, sizeof(str_dev)), + qdisc->addr_family, + qdisc->handle, + qdisc->parent, + qdisc->info); + + if (nm_streq0(qdisc->kind, "fq_codel")) { + if (qdisc->fq_codel.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->fq_codel.limit); + if (qdisc->fq_codel.flows) + nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->fq_codel.flows); + if (qdisc->fq_codel.target) + nm_utils_strbuf_append(&buf, &len, " target %u", qdisc->fq_codel.target); + if (qdisc->fq_codel.interval) + nm_utils_strbuf_append(&buf, &len, " interval %u", qdisc->fq_codel.interval); + if (qdisc->fq_codel.quantum) + nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->fq_codel.quantum); + if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) + nm_utils_strbuf_append(&buf, &len, " ce_threshold %u", qdisc->fq_codel.ce_threshold); + if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET) + nm_utils_strbuf_append(&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit); + if (qdisc->fq_codel.ecn) + nm_utils_strbuf_append(&buf, &len, " ecn"); + } else if (nm_streq0(qdisc->kind, "sfq")) { + if (qdisc->sfq.quantum) + nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->sfq.quantum); + if (qdisc->sfq.perturb_period) + nm_utils_strbuf_append(&buf, &len, " perturb %d", qdisc->sfq.perturb_period); + if (qdisc->sfq.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", (guint) qdisc->sfq.limit); + if (qdisc->sfq.divisor) + nm_utils_strbuf_append(&buf, &len, " divisor %u", qdisc->sfq.divisor); + if (qdisc->sfq.flows) + nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->sfq.flows); + if (qdisc->sfq.depth) + nm_utils_strbuf_append(&buf, &len, " depth %u", qdisc->sfq.depth); + } else if (nm_streq0(qdisc->kind, "tbf")) { + nm_utils_strbuf_append(&buf, &len, " rate %" G_GUINT64_FORMAT, qdisc->tbf.rate); + nm_utils_strbuf_append(&buf, &len, " burst %u", qdisc->tbf.burst); + if (qdisc->tbf.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->tbf.limit); + if (qdisc->tbf.latency) + nm_utils_strbuf_append(&buf, &len, " latency %uns", qdisc->tbf.latency); + } + + return buf0; +} + +void +nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h) +{ + nm_hash_update_str0(h, obj->kind); + nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info); + if (nm_streq0(obj->kind, "fq_codel")) { + nm_hash_update_vals(h, + obj->fq_codel.limit, + obj->fq_codel.flows, + obj->fq_codel.target, + obj->fq_codel.interval, + obj->fq_codel.quantum, + obj->fq_codel.ce_threshold, + obj->fq_codel.memory_limit, + NM_HASH_COMBINE_BOOLS(guint8, obj->fq_codel.ecn)); + } else if (nm_streq0(obj->kind, "sfq")) { + nm_hash_update_vals(h, + obj->sfq.quantum, + obj->sfq.perturb_period, + obj->sfq.limit, + obj->sfq.divisor, + obj->sfq.flows, + obj->sfq.depth); + } else if (nm_streq0(obj->kind, "tbf")) { + nm_hash_update_vals(h, obj->tbf.rate, obj->tbf.burst, obj->tbf.limit, obj->tbf.latency); + } +} + +int +nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a, + const NMPlatformQdisc *b, + gboolean compare_handle) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD(a, b, addr_family); + if (compare_handle) + NM_CMP_FIELD(a, b, handle); + NM_CMP_FIELD(a, b, info); + + if (nm_streq0(a->kind, "fq_codel")) { + NM_CMP_FIELD(a, b, fq_codel.limit); + NM_CMP_FIELD(a, b, fq_codel.flows); + NM_CMP_FIELD(a, b, fq_codel.target); + NM_CMP_FIELD(a, b, fq_codel.interval); + NM_CMP_FIELD(a, b, fq_codel.quantum); + NM_CMP_FIELD(a, b, fq_codel.ce_threshold); + NM_CMP_FIELD(a, b, fq_codel.memory_limit); + NM_CMP_FIELD_UNSAFE(a, b, fq_codel.ecn); + } else if (nm_streq0(a->kind, "sfq")) { + NM_CMP_FIELD(a, b, sfq.quantum); + NM_CMP_FIELD(a, b, sfq.perturb_period); + NM_CMP_FIELD(a, b, sfq.limit); + NM_CMP_FIELD(a, b, sfq.flows); + NM_CMP_FIELD(a, b, sfq.divisor); + NM_CMP_FIELD(a, b, sfq.depth); + } else if (nm_streq0(a->kind, "tbf")) { + NM_CMP_FIELD(a, b, tbf.rate); + NM_CMP_FIELD(a, b, tbf.burst); + NM_CMP_FIELD(a, b, tbf.limit); + NM_CMP_FIELD(a, b, tbf.latency); + } + + return 0; +} + +int +nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b) +{ + return nm_platform_qdisc_cmp_full(a, b, TRUE); +} + +const char * +nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char act_buf[300]; + char *p; + gsize l; + + if (!nm_utils_to_string_buffer_init_null(tfilter, &buf, &len)) + return buf; + + if (tfilter->action.kind) { + p = act_buf; + l = sizeof(act_buf); + + nm_utils_strbuf_append(&p, &l, " \"%s\"", tfilter->action.kind); + if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + gs_free char *t = NULL; + + nm_utils_strbuf_append( + &p, + &l, + " (\"%s\")", + nm_utils_str_utf8safe_escape(tfilter->action.kind, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL + | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII, + &t)); + } else if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + nm_utils_strbuf_append(&p, + &l, + "%s%s%s%s dev %d", + tfilter->action.mirred.ingress ? " ingress" : "", + tfilter->action.mirred.egress ? " egress" : "", + tfilter->action.mirred.mirror ? " mirror" : "", + tfilter->action.mirred.redirect ? " redirect" : "", + tfilter->action.mirred.ifindex); + } + } else + act_buf[0] = '\0'; + + g_snprintf(buf, + len, + "%s%s family %u handle %x parent %x info %x%s", + tfilter->kind, + _to_string_dev(NULL, tfilter->ifindex, str_dev, sizeof(str_dev)), + tfilter->addr_family, + tfilter->handle, + tfilter->parent, + tfilter->info, + act_buf); + + return buf; +} + +void +nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h) +{ + nm_hash_update_str0(h, obj->kind); + nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info); + if (obj->action.kind) { + nm_hash_update_str(h, obj->action.kind); + if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + nm_hash_update_strarr(h, obj->action.simple.sdata); + } else if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + nm_hash_update_vals(h, + obj->action.mirred.ifindex, + NM_HASH_COMBINE_BOOLS(guint8, + obj->action.mirred.ingress, + obj->action.mirred.egress, + obj->action.mirred.mirror, + obj->action.mirred.redirect)); + } + } +} + +int +nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD(a, b, addr_family); + NM_CMP_FIELD(a, b, handle); + NM_CMP_FIELD(a, b, info); + + NM_CMP_FIELD_STR_INTERNED(a, b, action.kind); + if (a->action.kind) { + if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + NM_CMP_FIELD_STR(a, b, action.simple.sdata); + } else if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + NM_CMP_FIELD(a, b, action.mirred.ifindex); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.ingress); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.egress); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.mirror); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.redirect); + } + } + + return 0; +} + +const char * +nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len) +{ + char str_mac[128], mac[128]; + char str_spoof_check[64]; + char str_trust[64]; + char str_min_tx_rate[64]; + char str_max_tx_rate[64]; + nm_auto_free_gstring GString *gstr_vlans = NULL; + guint i; + + if (!nm_utils_to_string_buffer_init_null(vf, &buf, &len)) + return buf; + + if (vf->mac.len) { + _nm_utils_hwaddr_ntoa(vf->mac.data, vf->mac.len, TRUE, mac, sizeof(mac)); + nm_sprintf_buf(str_mac, " mac %s", mac); + } else + str_mac[0] = '\0'; + + if (vf->num_vlans) { + gstr_vlans = g_string_new(""); + for (i = 0; i < vf->num_vlans; i++) { + g_string_append_printf(gstr_vlans, " vlan %u", (unsigned) vf->vlans[i].id); + if (vf->vlans[i].qos) + g_string_append_printf(gstr_vlans, " qos %u", (unsigned) vf->vlans[i].qos); + if (vf->vlans[i].proto_ad) + g_string_append(gstr_vlans, " proto 802.1ad"); + } + } + + g_snprintf(buf, + len, + "%u" /* index */ + "%s" /* MAC */ + "%s" /* spoof check */ + "%s" /* trust */ + "%s" /* min tx rate */ + "%s" /* max tx rate */ + "%s", /* VLANs */ + vf->index, + str_mac, + vf->spoofchk >= 0 ? nm_sprintf_buf(str_spoof_check, " spoofchk %d", vf->spoofchk) + : "", + vf->trust >= 0 ? nm_sprintf_buf(str_trust, " trust %d", vf->trust) : "", + vf->min_tx_rate + ? nm_sprintf_buf(str_min_tx_rate, " min_tx_rate %u", (unsigned) vf->min_tx_rate) + : "", + vf->max_tx_rate + ? nm_sprintf_buf(str_max_tx_rate, " max_tx_rate %u", (unsigned) vf->max_tx_rate) + : "", + gstr_vlans ? gstr_vlans->str : ""); + + return buf; +} + +const char * +nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len) +{ + char str_vid_end[64]; + + if (!nm_utils_to_string_buffer_init_null(vlan, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "%u" + "%s" + "%s" + "%s", + vlan->vid_start, + vlan->vid_start != vlan->vid_end ? nm_sprintf_buf(str_vid_end, "-%u", vlan->vid_end) + : "", + vlan->pvid ? " PVID" : "", + vlan->untagged ? " untagged" : ""); + + return buf; +} + +void +nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->master, + obj->parent, + obj->n_ifi_flags, + obj->mtu, + obj->type, + obj->arptype, + obj->inet6_addr_gen_mode_inv, + obj->inet6_token, + obj->rx_packets, + obj->rx_bytes, + obj->tx_packets, + obj->tx_bytes, + NM_HASH_COMBINE_BOOLS(guint8, obj->connected, obj->initialized)); + nm_hash_update_strarr(h, obj->name); + nm_hash_update_str0(h, obj->kind); + nm_hash_update_str0(h, obj->driver); + /* nm_hash_update_mem() also hashes the length obj->addr.len */ + nm_hash_update_mem(h, + obj->l_address.data, + NM_MIN(obj->l_address.len, sizeof(obj->l_address.data))); + nm_hash_update_mem(h, + obj->l_broadcast.data, + NM_MIN(obj->l_broadcast.len, sizeof(obj->l_broadcast.data))); +} + +int +nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type); + NM_CMP_FIELD_STR(a, b, name); + NM_CMP_FIELD(a, b, master); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD(a, b, n_ifi_flags); + NM_CMP_FIELD_UNSAFE(a, b, connected); + NM_CMP_FIELD(a, b, mtu); + NM_CMP_FIELD_BOOL(a, b, initialized); + NM_CMP_FIELD(a, b, arptype); + NM_CMP_FIELD(a, b, l_address.len); + NM_CMP_FIELD(a, b, l_broadcast.len); + NM_CMP_FIELD(a, b, inet6_addr_gen_mode_inv); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD_STR_INTERNED(a, b, driver); + if (a->l_address.len) + NM_CMP_FIELD_MEMCMP_LEN(a, b, l_address.data, a->l_address.len); + if (a->l_broadcast.len) + NM_CMP_FIELD_MEMCMP_LEN(a, b, l_broadcast.data, a->l_broadcast.len); + NM_CMP_FIELD_MEMCMP(a, b, inet6_token); + NM_CMP_FIELD(a, b, rx_packets); + NM_CMP_FIELD(a, b, rx_bytes); + NM_CMP_FIELD(a, b, tx_packets); + NM_CMP_FIELD(a, b, tx_bytes); + return 0; +} + +void +nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->forward_delay, + obj->hello_time, + obj->max_age, + obj->ageing_time, + obj->priority, + obj->vlan_protocol, + obj->group_fwd_mask, + obj->group_addr, + obj->mcast_hash_max, + obj->mcast_last_member_count, + obj->mcast_startup_query_count, + obj->mcast_last_member_interval, + obj->mcast_membership_interval, + obj->mcast_querier_interval, + obj->mcast_query_interval, + obj->mcast_router, + obj->mcast_query_response_interval, + obj->mcast_startup_query_interval, + NM_HASH_COMBINE_BOOLS(guint8, + obj->stp_state, + obj->mcast_querier, + obj->mcast_query_use_ifaddr, + obj->mcast_snooping, + obj->vlan_stats_enabled)); +} + +int +nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, forward_delay); + NM_CMP_FIELD(a, b, hello_time); + NM_CMP_FIELD(a, b, max_age); + NM_CMP_FIELD(a, b, ageing_time); + NM_CMP_FIELD_BOOL(a, b, stp_state); + NM_CMP_FIELD(a, b, priority); + NM_CMP_FIELD(a, b, vlan_protocol); + NM_CMP_FIELD_BOOL(a, b, vlan_stats_enabled); + NM_CMP_FIELD(a, b, group_fwd_mask); + NM_CMP_FIELD_MEMCMP(a, b, group_addr); + NM_CMP_FIELD_BOOL(a, b, mcast_snooping); + NM_CMP_FIELD(a, b, mcast_router); + NM_CMP_FIELD_BOOL(a, b, mcast_query_use_ifaddr); + NM_CMP_FIELD_BOOL(a, b, mcast_querier); + NM_CMP_FIELD(a, b, mcast_hash_max); + NM_CMP_FIELD(a, b, mcast_last_member_count); + NM_CMP_FIELD(a, b, mcast_startup_query_count); + NM_CMP_FIELD(a, b, mcast_last_member_interval); + NM_CMP_FIELD(a, b, mcast_membership_interval); + NM_CMP_FIELD(a, b, mcast_querier_interval); + NM_CMP_FIELD(a, b, mcast_query_interval); + NM_CMP_FIELD(a, b, mcast_query_response_interval); + NM_CMP_FIELD(a, b, mcast_startup_query_interval); + + return 0; +} + +void +nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->input_flags, + obj->output_flags, + obj->input_key, + obj->output_key, + obj->ttl, + obj->tos, + (bool) obj->path_mtu_discovery, + (bool) obj->is_tap); +} + +int +nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, input_flags); + NM_CMP_FIELD(a, b, output_flags); + NM_CMP_FIELD(a, b, input_key); + NM_CMP_FIELD(a, b, output_key); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + NM_CMP_FIELD_BOOL(a, b, is_tap); + return 0; +} + +void +nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h) +{ + nm_hash_update_val(h, obj->p_key); + nm_hash_update_str0(h, obj->mode); +} + +int +nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, p_key); + NM_CMP_FIELD_STR_INTERNED(a, b, mode); + return 0; +} + +void +nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->ttl, + obj->tclass, + obj->encap_limit, + obj->proto, + obj->flow_label, + obj->flags, + obj->input_flags, + obj->output_flags, + obj->input_key, + obj->output_key, + (bool) obj->is_gre, + (bool) obj->is_tap); +} + +int +nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD_MEMCMP(a, b, local); + NM_CMP_FIELD_MEMCMP(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tclass); + NM_CMP_FIELD(a, b, encap_limit); + NM_CMP_FIELD(a, b, flow_label); + NM_CMP_FIELD(a, b, proto); + NM_CMP_FIELD(a, b, flags); + NM_CMP_FIELD(a, b, input_flags); + NM_CMP_FIELD(a, b, output_flags); + NM_CMP_FIELD(a, b, input_key); + NM_CMP_FIELD(a, b, output_key); + NM_CMP_FIELD_BOOL(a, b, is_gre); + NM_CMP_FIELD_BOOL(a, b, is_tap); + return 0; +} + +void +nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->ttl, + obj->tos, + (bool) obj->path_mtu_discovery); +} + +int +nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + return 0; +} + +void +nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->parent_ifindex, + obj->sci, + obj->cipher_suite, + obj->window, + obj->icv_length, + obj->encoding_sa, + obj->validation, + NM_HASH_COMBINE_BOOLS(guint8, + obj->encrypt, + obj->protect, + obj->include_sci, + obj->es, + obj->scb, + obj->replay_protect)); +} + +int +nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, sci); + NM_CMP_FIELD(a, b, icv_length); + NM_CMP_FIELD(a, b, cipher_suite); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, encoding_sa); + NM_CMP_FIELD(a, b, validation); + NM_CMP_FIELD_UNSAFE(a, b, encrypt); + NM_CMP_FIELD_UNSAFE(a, b, protect); + NM_CMP_FIELD_UNSAFE(a, b, include_sci); + NM_CMP_FIELD_UNSAFE(a, b, es); + NM_CMP_FIELD_UNSAFE(a, b, scb); + NM_CMP_FIELD_UNSAFE(a, b, replay_protect); + return 0; +} + +void +nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->mode, NM_HASH_COMBINE_BOOLS(guint8, obj->no_promisc, obj->tap)); +} + +int +nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, mode); + NM_CMP_FIELD_UNSAFE(a, b, no_promisc); + NM_CMP_FIELD_UNSAFE(a, b, tap); + return 0; +} + +void +nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->flags, + obj->ttl, + obj->tos, + obj->proto, + (bool) obj->path_mtu_discovery); +} + +int +nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + NM_CMP_FIELD(a, b, flags); + NM_CMP_FIELD(a, b, proto); + return 0; +} + +void +nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->type, + obj->owner, + obj->group, + NM_HASH_COMBINE_BOOLS(guint8, + obj->owner_valid, + obj->group_valid, + obj->pi, + obj->vnet_hdr, + obj->multi_queue, + obj->persist)); +} + +int +nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, type); + NM_CMP_FIELD(a, b, owner); + NM_CMP_FIELD(a, b, group); + NM_CMP_FIELD_BOOL(a, b, owner_valid); + NM_CMP_FIELD_BOOL(a, b, group_valid); + NM_CMP_FIELD_BOOL(a, b, pi); + NM_CMP_FIELD_BOOL(a, b, vnet_hdr); + NM_CMP_FIELD_BOOL(a, b, multi_queue); + NM_CMP_FIELD_BOOL(a, b, persist); + return 0; +} + +void +nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->id, obj->flags); +} + +int +nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, id); + NM_CMP_FIELD(a, b, flags); + return 0; +} + +void +nm_platform_lnk_vrf_hash_update(const NMPlatformLnkVrf *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->table); +} + +int +nm_platform_lnk_vrf_cmp(const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, table); + return 0; +} + +void +nm_platform_lnk_vxlan_hash_update(const NMPlatformLnkVxlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->group6, + obj->local6, + obj->group, + obj->local, + obj->parent_ifindex, + obj->id, + obj->ageing, + obj->limit, + obj->dst_port, + obj->src_port_min, + obj->src_port_max, + obj->tos, + obj->ttl, + NM_HASH_COMBINE_BOOLS(guint8, + obj->learning, + obj->proxy, + obj->rsc, + obj->l2miss, + obj->l3miss)); +} + +int +nm_platform_lnk_vxlan_cmp(const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, id); + NM_CMP_FIELD(a, b, group); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD_MEMCMP(a, b, group6); + NM_CMP_FIELD_MEMCMP(a, b, local6); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD_BOOL(a, b, learning); + NM_CMP_FIELD(a, b, ageing); + NM_CMP_FIELD(a, b, limit); + NM_CMP_FIELD(a, b, dst_port); + NM_CMP_FIELD(a, b, src_port_min); + NM_CMP_FIELD(a, b, src_port_max); + NM_CMP_FIELD_BOOL(a, b, proxy); + NM_CMP_FIELD_BOOL(a, b, rsc); + NM_CMP_FIELD_BOOL(a, b, l2miss); + NM_CMP_FIELD_BOOL(a, b, l3miss); + return 0; +} + +void +nm_platform_lnk_wireguard_hash_update(const NMPlatformLnkWireGuard *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->listen_port, obj->fwmark); + nm_hash_update(h, obj->private_key, sizeof(obj->private_key)); + nm_hash_update(h, obj->public_key, sizeof(obj->public_key)); +} + +int +nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, listen_port); + NM_CMP_FIELD(a, b, fwmark); + NM_CMP_FIELD_MEMCMP(a, b, private_key); + NM_CMP_FIELD_MEMCMP(a, b, public_key); + return 0; +} + +static int +_address_pretty_sort_get_prio_4(in_addr_t addr) +{ + if (nm_utils_ip4_address_is_link_local(addr)) + return 0; + return 1; +} + +int +nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1, + const NMPlatformIP4Address *a2) +{ + in_addr_t n1; + in_addr_t n2; + + nm_assert(a1); + nm_assert(a2); + + /* Sort by address type. For example link local will + * be sorted *after* a global address. */ + NM_CMP_DIRECT(_address_pretty_sort_get_prio_4(a2->address), + _address_pretty_sort_get_prio_4(a1->address)); + + /* Sort the addresses based on their source. */ + NM_CMP_DIRECT(a2->addr_source, a1->addr_source); + + NM_CMP_DIRECT((a2->label[0] == '\0'), (a1->label[0] == '\0')); + + /* Finally, sort addresses lexically. We compare only the + * network part so that the order of addresses in the same + * subnet (and thus also the primary/secondary role) is + * preserved. + */ + n1 = a1->address & _nm_utils_ip4_prefix_to_netmask(a1->plen); + n2 = a2->address & _nm_utils_ip4_prefix_to_netmask(a2->plen); + NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32)); + return 0; +} + +static int +_address_pretty_sort_get_prio_6(const struct in6_addr *addr) +{ + if (IN6_IS_ADDR_V4MAPPED(addr)) + return 0; + if (IN6_IS_ADDR_V4COMPAT(addr)) + return 1; + if (IN6_IS_ADDR_UNSPECIFIED(addr)) + return 2; + if (IN6_IS_ADDR_LOOPBACK(addr)) + return 3; + if (IN6_IS_ADDR_LINKLOCAL(addr)) + return 4; + if (IN6_IS_ADDR_SITELOCAL(addr)) + return 5; + return 6; +} + +int +nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1, + const NMPlatformIP6Address *a2, + gboolean prefer_temp) +{ + gboolean ipv6_privacy1; + gboolean ipv6_privacy2; + + nm_assert(a1); + nm_assert(a2); + + /* tentative addresses are always sorted back... */ + /* sort tentative addresses after non-tentative. */ + NM_CMP_DIRECT(NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE), + NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_TENTATIVE)); + + /* Sort by address type. For example link local will + * be sorted *after* site local or global. */ + NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address), + _address_pretty_sort_get_prio_6(&a1->address)); + + ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY); + ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY); + if (ipv6_privacy1 || ipv6_privacy2) { + gboolean public1 = TRUE; + gboolean public2 = TRUE; + + if (ipv6_privacy1) { + if (a1->n_ifa_flags & IFA_F_TEMPORARY) + public1 = prefer_temp; + else + public1 = !prefer_temp; + } + if (ipv6_privacy2) { + if (a2->n_ifa_flags & IFA_F_TEMPORARY) + public2 = prefer_temp; + else + public2 = !prefer_temp; + } + + NM_CMP_DIRECT(public2, public1); + } + + /* Sort the addresses based on their source. */ + NM_CMP_DIRECT(a2->addr_source, a1->addr_source); + + /* sort permanent addresses before non-permanent. */ + NM_CMP_DIRECT(NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_PERMANENT), + NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_PERMANENT)); + + /* finally sort addresses lexically */ + NM_CMP_DIRECT_IN6ADDR(&a1->address, &a2->address); + NM_CMP_DIRECT_MEMCMP(a1, a2, sizeof(*a1)); + return 0; +} + +void +nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->addr_source, + obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u), + obj->timestamp, + obj->lifetime, + obj->preferred, + obj->n_ifa_flags, + obj->plen, + obj->address, + obj->peer_address, + NM_HASH_COMBINE_BOOLS(guint8, + obj->external, + obj->use_ip4_broadcast_address, + obj->ip4acd_not_ready)); + nm_hash_update_strarr(h, obj->label); +} + +int +nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, address); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD(a, b, peer_address); + NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address); + if (a->use_ip4_broadcast_address) + NM_CMP_FIELD(a, b, broadcast_address); + NM_CMP_FIELD(a, b, addr_source); + NM_CMP_FIELD(a, b, timestamp); + NM_CMP_FIELD(a, b, lifetime); + NM_CMP_FIELD(a, b, preferred); + NM_CMP_FIELD(a, b, n_ifa_flags); + NM_CMP_FIELD_STR(a, b, label); + NM_CMP_FIELD_UNSAFE(a, b, external); + NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready); + return 0; +} + +void +nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->addr_source, + obj->timestamp, + obj->lifetime, + obj->preferred, + obj->n_ifa_flags, + obj->plen, + obj->address, + obj->peer_address, + NM_HASH_COMBINE_BOOLS(guint8, obj->external)); +} + +int +nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b) +{ + const struct in6_addr *p_a, *p_b; + + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD_MEMCMP(a, b, address); + NM_CMP_FIELD(a, b, plen); + p_a = nm_platform_ip6_address_get_peer(a); + p_b = nm_platform_ip6_address_get_peer(b); + NM_CMP_DIRECT_MEMCMP(p_a, p_b, sizeof(*p_a)); + NM_CMP_FIELD(a, b, addr_source); + NM_CMP_FIELD(a, b, timestamp); + NM_CMP_FIELD(a, b, lifetime); + NM_CMP_FIELD(a, b, preferred); + NM_CMP_FIELD(a, b, n_ifa_flags); + NM_CMP_FIELD_UNSAFE(a, b, external); + return 0; +} + +void +nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h) +{ + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + nm_hash_update_vals( + h, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->tos, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->tos, + /* on top of WEAK_ID: */ + obj->ifindex, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + _ip_route_scope_inv_get_normalized(obj), + obj->gateway, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags & RTNH_F_ONLINK, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + obj->ifindex, + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->gateway, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + _ip_route_scope_inv_get_normalized(obj), + obj->tos, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK), + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + nm_hash_update_vals(h, + obj->type_coerced, + obj->table_coerced, + obj->ifindex, + obj->network, + obj->plen, + obj->metric, + obj->gateway, + obj->rt_source, + obj->scope_inv, + obj->tos, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu)); + break; + } +} + +int +nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, + const NMPlatformIP4Route *b, + NMPlatformIPRouteCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + NM_CMP_FIELD_UNSAFE(a, b, table_any); + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen)); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD(a, b, tos); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a), + _ip_route_scope_inv_get_normalized(b)); + NM_CMP_FIELD(a, b, gateway); + NM_CMP_FIELD(a, b, mss); + NM_CMP_FIELD(a, b, pref_src); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + NM_CMP_DIRECT(a->r_rtm_flags & RTNH_F_ONLINK, b->r_rtm_flags & RTNH_F_ONLINK); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + } + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_UNSAFE(a, b, table_any); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + } else + NM_CMP_FIELD(a, b, table_coerced); + NM_CMP_FIELD(a, b, ifindex); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen)); + else + NM_CMP_FIELD(a, b, network); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD(a, b, gateway); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a), + _ip_route_scope_inv_get_normalized(b)); + } else { + NM_CMP_FIELD(a, b, rt_source); + NM_CMP_FIELD(a, b, scope_inv); + } + NM_CMP_FIELD(a, b, mss); + NM_CMP_FIELD(a, b, pref_src); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(a->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK), + b->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK)); + } else + NM_CMP_FIELD(a, b, r_rtm_flags); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + break; + } + return 0; +} + +void +nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h) +{ + struct in6_addr a1, a2; + + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + nm_hash_update_vals( + h, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any), + /* on top of WEAK_ID: */ + obj->ifindex, + obj->gateway); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + obj->ifindex, + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + obj->gateway, + obj->pref_src, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + obj->mss, + obj->r_rtm_flags & RTM_F_CLONED, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu), + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + _route_pref_normalize(obj->rt_pref)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + nm_hash_update_vals(h, + obj->type_coerced, + obj->table_coerced, + obj->ifindex, + obj->network, + obj->metric, + obj->gateway, + obj->pref_src, + obj->src, + obj->src_plen, + obj->rt_source, + obj->mss, + obj->r_rtm_flags, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu), + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->rt_pref); + break; + } +} + +int +nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, + const NMPlatformIP6Route *b, + NMPlatformIPRouteCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + NM_CMP_FIELD_UNSAFE(a, b, table_any); + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen)); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen)); + NM_CMP_FIELD(a, b, src_plen); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + } + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_UNSAFE(a, b, table_any); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + } else + NM_CMP_FIELD(a, b, table_coerced); + NM_CMP_FIELD(a, b, ifindex); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen)); + else + NM_CMP_FIELD_IN6ADDR(a, b, network); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + NM_CMP_FIELD_IN6ADDR(a, b, pref_src); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen)); + NM_CMP_FIELD(a, b, src_plen); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + } else { + NM_CMP_FIELD_IN6ADDR(a, b, src); + NM_CMP_FIELD(a, b, src_plen); + NM_CMP_FIELD(a, b, rt_source); + } + NM_CMP_FIELD(a, b, mss); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(a->r_rtm_flags & RTM_F_CLONED, b->r_rtm_flags & RTM_F_CLONED); + } else + NM_CMP_FIELD(a, b, r_rtm_flags); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref)); + else + NM_CMP_FIELD(a, b, rt_pref); + break; + } + return 0; +} + +#define _ROUTING_RULE_FLAGS_IGNORE \ + (FIB_RULE_UNRESOLVED | FIB_RULE_IIF_DETACHED | FIB_RULE_OIF_DETACHED) + +#define _routing_rule_compare(cmp_type, kernel_support_type) \ + ((cmp_type) == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL \ + || nm_platform_kernel_support_get(kernel_support_type)) + +void +nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj, + NMPlatformRoutingRuleCmpType cmp_type, + NMHashState * h) +{ + gboolean cmp_full = TRUE; + gsize addr_size; + guint32 flags_mask = G_MAXUINT32; + + if (G_UNLIKELY(!NM_IN_SET(obj->addr_family, AF_INET, AF_INET6))) { + /* the address family is not one of the supported ones. That means, the + * instance will only compare equal to itself (pointer-equality). */ + nm_hash_update_val(h, (gconstpointer) obj); + return; + } + + switch (cmp_type) { + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID: + + flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY: + + cmp_full = FALSE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL: + + nm_hash_update_vals( + h, + obj->addr_family, + obj->tun_id, + obj->table, + obj->flags & flags_mask, + obj->priority, + obj->fwmark, + obj->fwmask, + ((cmp_full + || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY + && obj->action == FR_ACT_GOTO)) + ? obj->goto_target + : (guint32) 0u), + ((cmp_full || obj->addr_family == AF_INET) ? obj->flow : (guint32) 0u), + NM_HASH_COMBINE_BOOLS( + guint8, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE) + ? obj->uid_range_has + : FALSE)), + obj->suppress_prefixlen_inverse, + obj->suppress_ifgroup_inverse, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV) + ? (cmp_full ? (guint16) obj->l3mdev : (guint16) !!obj->l3mdev) + : G_MAXUINT16), + obj->action, + obj->tos, + obj->src_len, + obj->dst_len, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL) + ? (guint16) obj->protocol + : G_MAXUINT16)); + addr_size = nm_utils_addr_family_to_size(obj->addr_family); + if (cmp_full || obj->src_len > 0) + nm_hash_update(h, &obj->src, addr_size); + if (cmp_full || obj->dst_len > 0) + nm_hash_update(h, &obj->dst, addr_size); + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + if (cmp_full || obj->uid_range_has) + nm_hash_update_valp(h, &obj->uid_range); + } + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + nm_hash_update_val(h, obj->ip_proto); + nm_hash_update_valp(h, &obj->sport_range); + nm_hash_update_valp(h, &obj->dport_range); + } + nm_hash_update_str(h, obj->iifname); + nm_hash_update_str(h, obj->oifname); + return; + } + + nm_assert_not_reached(); +} + +int +nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a, + const NMPlatformRoutingRule *b, + NMPlatformRoutingRuleCmpType cmp_type) +{ + gboolean cmp_full = TRUE; + gsize addr_size; + bool valid; + guint32 flags_mask = G_MAXUINT32; + + NM_CMP_SELF(a, b); + + valid = NM_IN_SET(a->addr_family, AF_INET, AF_INET6); + NM_CMP_DIRECT(valid, (bool) NM_IN_SET(b->addr_family, AF_INET, AF_INET6)); + + if (G_UNLIKELY(!valid)) { + /* the address family is not one of the supported ones. That means, the + * instance will only compare equal to itself. */ + NM_CMP_DIRECT((uintptr_t) a, (uintptr_t) b); + nm_assert_not_reached(); + return 0; + } + + switch (cmp_type) { + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID: + + flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY: + + cmp_full = FALSE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, addr_family); + NM_CMP_FIELD(a, b, action); + NM_CMP_FIELD(a, b, priority); + NM_CMP_FIELD(a, b, tun_id); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) { + if (cmp_full) + NM_CMP_FIELD(a, b, l3mdev); + else + NM_CMP_FIELD_BOOL(a, b, l3mdev); + } + + NM_CMP_FIELD(a, b, table); + + NM_CMP_DIRECT(a->flags & flags_mask, b->flags & flags_mask); + + NM_CMP_FIELD(a, b, fwmark); + NM_CMP_FIELD(a, b, fwmask); + + if (cmp_full + || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY + && a->action == FR_ACT_GOTO)) + NM_CMP_FIELD(a, b, goto_target); + + NM_CMP_FIELD(a, b, suppress_prefixlen_inverse); + NM_CMP_FIELD(a, b, suppress_ifgroup_inverse); + NM_CMP_FIELD(a, b, tos); + + if (cmp_full || a->addr_family == AF_INET) + NM_CMP_FIELD(a, b, flow); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) + NM_CMP_FIELD(a, b, protocol); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + NM_CMP_FIELD(a, b, ip_proto); + NM_CMP_FIELD(a, b, sport_range.start); + NM_CMP_FIELD(a, b, sport_range.end); + NM_CMP_FIELD(a, b, dport_range.start); + NM_CMP_FIELD(a, b, dport_range.end); + } + + addr_size = nm_utils_addr_family_to_size(a->addr_family); + + NM_CMP_FIELD(a, b, src_len); + if (cmp_full || a->src_len > 0) + NM_CMP_FIELD_MEMCMP_LEN(a, b, src, addr_size); + + NM_CMP_FIELD(a, b, dst_len); + if (cmp_full || a->dst_len > 0) + NM_CMP_FIELD_MEMCMP_LEN(a, b, dst, addr_size); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + NM_CMP_FIELD_UNSAFE(a, b, uid_range_has); + if (cmp_full || a->uid_range_has) { + NM_CMP_FIELD(a, b, uid_range.start); + NM_CMP_FIELD(a, b, uid_range.end); + } + } + + NM_CMP_FIELD_STR(a, b, iifname); + NM_CMP_FIELD_STR(a, b, oifname); + return 0; + } + + nm_assert_not_reached(); + return 0; +} + +/** + * nm_platform_ip_address_cmp_expiry: + * @a: a NMPlatformIPAddress to compare + * @b: the other NMPlatformIPAddress to compare + * + * Compares two addresses and returns which one has a longer remaining lifetime. + * If both addresses have the same lifetime, look at the remaining preferred time. + * + * For comparison, only the timestamp, lifetime and preferred fields are considered. + * If they compare equal (== 0), their other fields were not considered. + * + * Returns: -1, 0, or 1 according to the comparison + **/ +int +nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b) +{ + gint64 ta = 0, tb = 0; + + NM_CMP_SELF(a, b); + + if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0) + ta = G_MAXINT64; + else if (a->timestamp) + ta = ((gint64) a->timestamp) + a->lifetime; + + if (b->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0) + tb = G_MAXINT64; + else if (b->timestamp) + tb = ((gint64) b->timestamp) + b->lifetime; + + if (ta == tb) { + /* if the lifetime is equal, compare the preferred time. */ + ta = tb = 0; + + if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT + || a->lifetime == 0 /* lifetime==0 means permanent! */) + ta = G_MAXINT64; + else if (a->timestamp) + ta = ((gint64) a->timestamp) + a->preferred; + + if (b->preferred == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0) + tb = G_MAXINT64; + else if (b->timestamp) + tb = ((gint64) b->timestamp) + b->preferred; + + if (ta == tb) + return 0; + } + + return ta < tb ? -1 : 1; +} + +/*****************************************************************************/ + +GHashTable * +nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + GHashTable * hash; + + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex); + + head_entry = nmp_cache_lookup(NM_PLATFORM_GET_PRIVATE(self)->cache, &lookup); + + if (!head_entry) + return NULL; + + hash = g_hash_table_new(nm_direct_hash, NULL); + + nmp_cache_iter_for_each (&iter, head_entry, &obj) { + const NMPlatformIP4Address *a = NMP_OBJECT_CAST_IP4_ADDRESS(obj); + + g_hash_table_add(hash, GUINT_TO_POINTER(a->address)); + } + + return hash; +} + +/*****************************************************************************/ + +const char * +nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type) +{ + switch (change_type) { + case NM_PLATFORM_SIGNAL_ADDED: + return "added"; + case NM_PLATFORM_SIGNAL_CHANGED: + return "changed"; + case NM_PLATFORM_SIGNAL_REMOVED: + return "removed"; + default: + g_return_val_if_reached("UNKNOWN"); + } +} + +static void +log_link(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformLink * device, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: link %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_link_to_string(device, NULL, 0)); +} + +static void +log_ip4_address(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Address * address, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: address 4 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip4_address_to_string(address, NULL, 0)); +} + +static void +log_ip6_address(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Address * address, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: address 6 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip6_address_to_string(address, NULL, 0)); +} + +static void +log_ip4_route(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Route * route, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: route 4 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip4_route_to_string(route, NULL, 0)); +} + +static void +log_ip6_route(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Route * route, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: route 6 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip6_route_to_string(route, NULL, 0)); +} + +static void +log_routing_rule(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformRoutingRule * routing_rule, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + /* routing rules don't have an ifindex. We probably should refactor the signals that are emitted for platform changes. */ + _LOG3D("signal: rt-rule %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_routing_rule_to_string(routing_rule, NULL, 0)); +} + +static void +log_qdisc(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformQdisc * qdisc, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: qdisc %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_qdisc_to_string(qdisc, NULL, 0)); +} + +static void +log_tfilter(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformTfilter * tfilter, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: tfilter %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_tfilter_to_string(tfilter, NULL, 0)); +} + +/*****************************************************************************/ + +void +nm_platform_cache_update_emit_signal(NMPlatform * self, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ + gboolean visible_new; + gboolean visible_old; + const NMPObject *o; + const NMPClass * klass; + int ifindex; + + nm_assert(NM_IN_SET((NMPlatformSignalChangeType) cache_op, + NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED)); + + ASSERT_nmp_cache_ops(nm_platform_get_cache(self), cache_op, obj_old, obj_new); + + NMTST_ASSERT_PLATFORM_NETNS_CURRENT(self); + + switch (cache_op) { + case NMP_CACHE_OPS_ADDED: + if (!nmp_object_is_visible(obj_new)) + return; + o = obj_new; + break; + case NMP_CACHE_OPS_UPDATED: + visible_old = nmp_object_is_visible(obj_old); + visible_new = nmp_object_is_visible(obj_new); + if (!visible_old && visible_new) { + o = obj_new; + cache_op = NMP_CACHE_OPS_ADDED; + } else if (visible_old && !visible_new) { + o = obj_old; + cache_op = NMP_CACHE_OPS_REMOVED; + } else if (!visible_new) { + /* it was invisible and stayed invisible. Nothing to do. */ + return; + } else + o = obj_new; + break; + case NMP_CACHE_OPS_REMOVED: + if (!nmp_object_is_visible(obj_old)) + return; + o = obj_old; + break; + default: + nm_assert(cache_op == NMP_CACHE_OPS_UNCHANGED); + return; + } + + klass = NMP_OBJECT_GET_CLASS(o); + + if (klass->obj_type == NMP_OBJECT_TYPE_ROUTING_RULE) + ifindex = 0; + else + ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(o)->ifindex; + + if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE + && NM_PLATFORM_GET_PRIVATE(self)->ip4_dev_route_blacklist_gc_timeout_id + && NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)) + _ip4_dev_route_blacklist_notify_route(self, o); + + _LOG3t("emit signal %s %s: %s", + klass->signal_type, + nm_platform_signal_change_type_to_string((NMPlatformSignalChangeType) cache_op), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + + nmp_object_ref(o); + g_signal_emit(self, + _nm_platform_signal_id_get(klass->signal_type_id), + 0, + (int) klass->obj_type, + ifindex, + &o->object, + (int) cache_op); + nmp_object_unref(o); +} + +/*****************************************************************************/ + +NMPCache * +nm_platform_get_cache(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->cache; +} + +NMPNetns * +nm_platform_netns_get(NMPlatform *self) +{ + _CHECK_SELF(self, klass, NULL); + + return self->_netns; +} + +gboolean +nm_platform_netns_push(NMPlatform *self, NMPNetns **netns) +{ + g_return_val_if_fail(NM_IS_PLATFORM(self), FALSE); + + if (self->_netns && !nmp_netns_push(self->_netns)) { + NM_SET_OUT(netns, NULL); + return FALSE; + } + + NM_SET_OUT(netns, self->_netns); + return TRUE; +} + +/*****************************************************************************/ + +const _NMPlatformVTableRouteUnion nm_platform_vtable_route = { + .v4 = + { + .is_ip4 = TRUE, + .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE, + .addr_family = AF_INET, + .sizeof_route = sizeof(NMPlatformIP4Route), + .route_cmp = (int (*)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp, + .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route, + char * buf, + gsize len)) nm_platform_ip4_route_to_string, + }, + .v6 = + { + .is_ip4 = FALSE, + .obj_type = NMP_OBJECT_TYPE_IP6_ROUTE, + .addr_family = AF_INET6, + .sizeof_route = sizeof(NMPlatformIP6Route), + .route_cmp = (int (*)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp, + .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route, + char * buf, + gsize len)) nm_platform_ip6_route_to_string, + }, +}; + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMPlatform * self = NM_PLATFORM(object); + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_NETNS_SUPPORT: + /* construct-only */ + if (g_value_get_boolean(value)) { + NMPNetns *netns; + + netns = nmp_netns_get_current(); + if (netns) + self->_netns = g_object_ref(netns); + } + break; + case PROP_USE_UDEV: + /* construct-only */ + priv->use_udev = g_value_get_boolean(value); + break; + case PROP_LOG_WITH_PTR: + /* construct-only */ + priv->log_with_ptr = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_platform_init(NMPlatform *self) +{ + self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_PLATFORM, NMPlatformPrivate); +} + +static GObject * +constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params) +{ + GObject * object; + NMPlatform * self; + NMPlatformPrivate *priv; + + object = G_OBJECT_CLASS(nm_platform_parent_class) + ->constructor(type, n_construct_params, construct_params); + self = NM_PLATFORM(object); + priv = NM_PLATFORM_GET_PRIVATE(self); + + priv->multi_idx = nm_dedup_multi_index_new(); + + priv->cache = nmp_cache_new(priv->multi_idx, priv->use_udev); + + return object; +} + +static void +finalize(GObject *object) +{ + NMPlatform * self = NM_PLATFORM(object); + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + nm_clear_g_source(&priv->ip4_dev_route_blacklist_check_id); + nm_clear_g_source(&priv->ip4_dev_route_blacklist_gc_timeout_id); + nm_clear_pointer(&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref); + g_clear_object(&self->_netns); + nm_dedup_multi_index_unref(priv->multi_idx); + nmp_cache_free(priv->cache); +} + +static void +nm_platform_class_init(NMPlatformClass *platform_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(platform_class); + + g_type_class_add_private(object_class, sizeof(NMPlatformPrivate)); + + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->finalize = finalize; + + platform_class->wifi_set_powersave = wifi_set_powersave; + + g_object_class_install_property( + object_class, + PROP_NETNS_SUPPORT, + g_param_spec_boolean(NM_PLATFORM_NETNS_SUPPORT, + "", + "", + NM_PLATFORM_NETNS_SUPPORT_DEFAULT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property( + object_class, + PROP_USE_UDEV, + g_param_spec_boolean(NM_PLATFORM_USE_UDEV, + "", + "", + FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property( + object_class, + PROP_LOG_WITH_PTR, + g_param_spec_boolean(NM_PLATFORM_LOG_WITH_PTR, + "", + "", + TRUE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + +#define SIGNAL(signal, signal_id, method) \ + G_STMT_START \ + { \ + signals[signal] = \ + g_signal_new_class_handler("" signal_id "", \ + G_OBJECT_CLASS_TYPE(object_class), \ + G_SIGNAL_RUN_FIRST, \ + G_CALLBACK(method), \ + NULL, \ + NULL, \ + NULL, \ + G_TYPE_NONE, \ + 4, \ + G_TYPE_INT, /* (int) NMPObjectType */ \ + G_TYPE_INT, /* ifindex */ \ + G_TYPE_POINTER /* const NMPObject * */, \ + G_TYPE_INT /* (int) NMPlatformSignalChangeType */ \ + ); \ + } \ + G_STMT_END + + /* Signals */ + SIGNAL(NM_PLATFORM_SIGNAL_ID_LINK, NM_PLATFORM_SIGNAL_LINK_CHANGED, log_link); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + log_ip4_address); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + log_ip6_address); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, log_ip4_route); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, log_ip6_route); + SIGNAL(NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, + log_routing_rule); + SIGNAL(NM_PLATFORM_SIGNAL_ID_QDISC, NM_PLATFORM_SIGNAL_QDISC_CHANGED, log_qdisc); + SIGNAL(NM_PLATFORM_SIGNAL_ID_TFILTER, NM_PLATFORM_SIGNAL_TFILTER_CHANGED, log_tfilter); +} diff --git a/src/core/platform/nm-platform.h b/src/core/platform/nm-platform.h new file mode 100644 index 0000000..d400c56 --- /dev/null +++ b/src/core/platform/nm-platform.h @@ -0,0 +1,2372 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2018 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_PLATFORM_H__ +#define __NETWORKMANAGER_PLATFORM_H__ + +#include "nm-dbus-interface.h" +#include "nm-core-types-internal.h" + +#include "nm-platform/nmp-base.h" +#include "nm-base/nm-base.h" + +#include "nm-core-utils.h" +#include "nm-setting-vlan.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-ip-tunnel.h" + +#define NM_TYPE_PLATFORM (nm_platform_get_type()) +#define NM_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PLATFORM, NMPlatform)) +#define NM_PLATFORM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PLATFORM, NMPlatformClass)) +#define NM_IS_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PLATFORM)) +#define NM_IS_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PLATFORM)) +#define NM_PLATFORM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PLATFORM, NMPlatformClass)) + +#define NM_PLATFORM_NETNS_SUPPORT_DEFAULT FALSE + +/*****************************************************************************/ + +#define NM_PLATFORM_NETNS_SUPPORT "netns-support" +#define NM_PLATFORM_USE_UDEV "use-udev" +#define NM_PLATFORM_LOG_WITH_PTR "log-with-ptr" + +/*****************************************************************************/ + +/* IFNAMSIZ is both defined in and . In the past, these + * headers conflicted, so we cannot simply include either of them in a header-file.*/ +#define NMP_IFNAMSIZ 16 + +/*****************************************************************************/ + +struct _NMPWireGuardPeer; + +struct udev_device; + +typedef gboolean (*NMPObjectPredicateFunc)(const NMPObject *obj, gpointer user_data); + +/* workaround for older libnl version, that does not define these flags. */ +#ifndef IFA_F_MANAGETEMPADDR + #define IFA_F_MANAGETEMPADDR 0x100 +#endif +#ifndef IFA_F_NOPREFIXROUTE + #define IFA_F_NOPREFIXROUTE 0x200 +#endif + +#define NM_RT_SCOPE_LINK 253 /* RT_SCOPE_LINK */ + +/* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers + * that don't define it. */ +#define NM_IN6_ADDR_GEN_MODE_UNKNOWN 255 /* no corresponding value. */ +#define NM_IN6_ADDR_GEN_MODE_EUI64 0 /* IN6_ADDR_GEN_MODE_EUI64 */ +#define NM_IN6_ADDR_GEN_MODE_NONE 1 /* IN6_ADDR_GEN_MODE_NONE */ +#define NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY 2 /* IN6_ADDR_GEN_MODE_STABLE_PRIVACY */ +#define NM_IN6_ADDR_GEN_MODE_RANDOM 3 /* IN6_ADDR_GEN_MODE_RANDOM */ + +#define NM_IFF_MULTI_QUEUE 0x0100 /* IFF_MULTI_QUEUE */ + +/* Redefine this in host's endianness */ +#define NM_GRE_KEY 0x2000 + +typedef enum { + NMP_NLM_FLAG_F_ECHO = 0x08, /* NLM_F_ECHO, Echo this request */ + + /* use our own platform enum for the nlmsg-flags. Otherwise, we'd have + * to include */ + NMP_NLM_FLAG_F_REPLACE = 0x100, /* NLM_F_REPLACE, Override existing */ + NMP_NLM_FLAG_F_EXCL = 0x200, /* NLM_F_EXCL, Do not touch, if it exists */ + NMP_NLM_FLAG_F_CREATE = 0x400, /* NLM_F_CREATE, Create, if it does not exist */ + NMP_NLM_FLAG_F_APPEND = 0x800, /* NLM_F_APPEND, Add to end of list */ + + NMP_NLM_FLAG_FMASK = 0xFFFF, /* a mask for all NMP_NLM_FLAG_F_* flags */ + + /* instructs NM to suppress logging an error message for any failures + * received from kernel. + * + * It will still log with debug-level, and it will still log + * other failures aside the kernel response. */ + NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE = 0x10000, + + /* the following aliases correspond to iproute2's `ip route CMD` for + * RTM_NEWROUTE, with CMD being one of add, change, replace, prepend, + * append and test. */ + NMP_NLM_FLAG_ADD = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_EXCL, + NMP_NLM_FLAG_CHANGE = NMP_NLM_FLAG_F_REPLACE, + NMP_NLM_FLAG_REPLACE = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_REPLACE, + NMP_NLM_FLAG_PREPEND = NMP_NLM_FLAG_F_CREATE, + NMP_NLM_FLAG_APPEND = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_APPEND, + NMP_NLM_FLAG_TEST = NMP_NLM_FLAG_F_EXCL, +} NMPNlmFlags; + +typedef enum { + /* compare fields which kernel considers as similar routes. + * It is a looser comparisong then NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID + * and means that `ip route add` would fail to add two routes + * that have the same NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID. + * On the other hand, `ip route append` would allow that, as + * long as NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID differs. */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + + /* compare two routes as kernel would allow to add them with + * `ip route append`. In other words, kernel does not allow you to + * add two routes (at the same time) which compare equal according + * to NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID. + * + * For the ID we can only recognize route fields that we actually implement. + * However, kernel supports more routing options, some of them also part of + * the ID. NetworkManager is oblivious to these options and will wrongly think + * that two routes are identical, while they are not. That can lead to an + * inconsistent platform cache. Not much what we can do about that, except + * implementing all options that kernel supports *sigh*. See rh#1337860. + */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, + + /* compare all fields as they make sense for kernel. For example, + * a route destination 192.168.1.5/24 is not accepted by kernel and + * we treat it identical to 192.168.1.0/24. Semantically these + * routes are identical, but NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL will + * report them as different. + * + * The result shall be identical to call first nm_platform_ip_route_normalize() + * on both routes and then doing a full comparison. */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY, + + /* compare all fields. This should have the same effect as memcmp(), + * except allowing for undefined data in holes between field alignment. + */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + +} NMPlatformIPRouteCmpType; + +typedef enum { + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY, + + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, +} NMPlatformRoutingRuleCmpType; + +typedef struct { + union { + guint8 data[20 /* NM_UTILS_HWADDR_LEN_MAX */]; + NMEtherAddr ether_addr; + }; + guint8 len; +} NMPLinkAddress; + +/* assert that NMEtherAddr does not affect the alignment of NMPLinkAddress struct. */ +G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1); +G_STATIC_ASSERT(_nm_alignof(NMPLinkAddress) == 1); + +gconstpointer nmp_link_address_get(const NMPLinkAddress *addr, size_t *length); +GBytes * nmp_link_address_get_as_bytes(const NMPLinkAddress *addr); + +typedef enum { + + /* match-flags are strictly inclusive. That means, + * by default nothing is matched, but if you enable a particular + * flag, a candidate that matches passes the check. + * + * In other words: adding more flags can only extend the result + * set of matching objects. + * + * Also, the flags form partitions. Like, an address can be either of + * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for + * the ADDRSTATE match types. + */ + NM_PLATFORM_MATCH_WITH_NONE = 0, + + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL = (1LL << 0), + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL = (1LL << 1), + NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY = + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL, + + NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL = (1LL << 2), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE = (1LL << 3), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED = (1LL << 4), + NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY = NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED, +} NMPlatformMatchFlags; + +#define NM_PLATFORM_LINK_OTHER_NETNS (-1) + +struct _NMPlatformObject { + /* the object type has no fields of its own, it is only used to having + * a special pointer type that can be used to indicate "any" type. */ + char _dummy_don_t_use_me; +}; + +#define __NMPlatformObjWithIfindex_COMMON \ + int ifindex; \ + ; + +struct _NMPlatformObjWithIfindex { + __NMPlatformObjWithIfindex_COMMON; +}; + +struct _NMPlatformLink { + __NMPlatformObjWithIfindex_COMMON; + char name[NMP_IFNAMSIZ]; + NMLinkType type; + + /* rtnl_link_get_type(), IFLA_INFO_KIND. */ + /* NMPlatform initializes this field with a static string. */ + const char *kind; + + /* NMPlatform initializes this field with a static string. */ + const char *driver; + + int master; + + /* rtnl_link_get_link(), IFLA_LINK. + * If IFLA_LINK_NETNSID indicates that the parent is in another namespace, + * this field be set to (negative) NM_PLATFORM_LINK_OTHER_NETNS. */ + int parent; + + /* IFF_* flags. Note that the flags in 'struct ifinfomsg' are declared as 'unsigned'. */ + guint n_ifi_flags; + + guint mtu; + + /* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */ + guint32 arptype; + + /* IFLA_ADDRESS */ + NMPLinkAddress l_address; + + /* IFLA_BROADCAST */ + NMPLinkAddress l_broadcast; + + /* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */ + NMUtilsIPv6IfaceId inet6_token; + + /* The bitwise inverse of rtnl_link_inet6_get_addr_gen_mode(). It is inverse + * to have a default of 0 -- meaning: unspecified. That way, a struct + * initialized with memset(0) has and unset value.*/ + guint8 inet6_addr_gen_mode_inv; + + /* Statistics */ + guint64 rx_packets; + guint64 rx_bytes; + guint64 tx_packets; + guint64 tx_bytes; + + /* @connected is mostly identical to (@n_ifi_flags & IFF_UP). Except for bridge/bond masters, + * where we coerce the link as disconnect if it has no slaves. */ + bool connected : 1; + + bool initialized : 1; +}; + +typedef enum { /*< skip >*/ + NM_PLATFORM_SIGNAL_ID_NONE, + NM_PLATFORM_SIGNAL_ID_LINK, + NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, + NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, + NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + NM_PLATFORM_SIGNAL_ID_QDISC, + NM_PLATFORM_SIGNAL_ID_TFILTER, + _NM_PLATFORM_SIGNAL_ID_LAST, +} NMPlatformSignalIdType; + +guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type); + +typedef enum { + NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, +} NMPlatformSignalChangeType; + +#define NM_PLATFORM_IP_ADDRESS_CAST(address) \ + NM_CONSTCAST(NMPlatformIPAddress, \ + (address), \ + NMPlatformIPXAddress, \ + NMPlatformIP4Address, \ + NMPlatformIP6Address) + +#define __NMPlatformIPAddress_COMMON \ + __NMPlatformObjWithIfindex_COMMON; \ + NMIPConfigSource addr_source; \ + \ + /* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*(). + * + * The rules are: + * 1 @lifetime==0: @timestamp and @preferred is irrelevant (but mostly set to 0 too). Such addresses + * are permanent. This rule is so that unset addresses (calloc) are permanent by default. + * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly + * set to 0). Such addresses are permanent. + * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp + * and never returned by nm_utils_get_monotonic_timestamp_sec(). In this case @valid/@preferred + * is anchored at @timestamp. + * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time + * moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add(). + * + * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the + * lease was received. Addresses from kernel might have the @timestamp based on the last modification + * time of the addresses. But don't rely on this behaviour, the @timestamp is only defined for anchoring + * @lifetime and @preferred. + */ \ + guint32 timestamp; \ + guint32 lifetime; /* seconds since timestamp */ \ + guint32 preferred; /* seconds since timestamp */ \ + \ + /* ifa_flags in 'struct ifaddrmsg' from , extended to 32 bit by + * IFA_FLAGS attribute. */ \ + guint32 n_ifa_flags; \ + \ + guint8 plen; \ + \ + /* FIXME(l3cfg): the external marker won't be necessary anymore, because we only + * merge addresses we care about, and ignore (don't remove) external addresses. */ \ + bool external : 1; \ + \ + bool use_ip4_broadcast_address : 1; \ + \ + /* Whether the address is ready to be configured. By default, an address is, but this + * flag may indicate that the address is just for tracking purpose only, but the ACD + * state is not yet ready for the address to be configured. */ \ + bool ip4acd_not_ready : 1; \ + ; + +/** + * NMPlatformIPAddress: + * + * Common parts of NMPlatformIP4Address and NMPlatformIP6Address. + **/ +typedef struct { + __NMPlatformIPAddress_COMMON; + union { + guint8 address_ptr[1]; + guint32 __dummy_for_32bit_alignment; + }; +} NMPlatformIPAddress; + +/** + * NMPlatformIP4Address: + * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec() + **/ +struct _NMPlatformIP4Address { + __NMPlatformIPAddress_COMMON; + + /* The local address IFA_LOCAL. */ + in_addr_t address; + + /* The IFA_ADDRESS PTP peer address. This field is rather important, because + * it constitutes the identifier for the IPv4 address (e.g. you can add two + * addresses that only differ by their peer's network-part. + * + * Beware that for most cases, NetworkManager doesn't want to set an explicit + * peer-address. However, that corresponds to setting the peer address to @address + * itself. Leaving peer-address unset/zero, means explicitly setting the peer + * address to 0.0.0.0, which you probably don't want. + * */ + in_addr_t peer_address; /* PTP peer address */ + + /* IFA_BROADCAST. + * + * This parameter is ignored unless use_ip4_broadcast_address is TRUE. + * See nm_platform_ip4_broadcast_address_from_addr(). */ + in_addr_t broadcast_address; + + char label[NMP_IFNAMSIZ]; +}; + +/** + * NMPlatformIP6Address: + * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec() + **/ +struct _NMPlatformIP6Address { + __NMPlatformIPAddress_COMMON; + struct in6_addr address; + struct in6_addr peer_address; +}; + +typedef union { + NMPlatformIPAddress ax; + NMPlatformIP4Address a4; + NMPlatformIP6Address a6; +} NMPlatformIPXAddress; + +#undef __NMPlatformIPAddress_COMMON + +#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address){__VA_ARGS__})) + +#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address){__VA_ARGS__})) + +/* Default value for adding an IPv4 route. This is also what iproute2 does. + * Note that contrary to IPv6, you can add routes with metric 0 and it is even + * the default. + */ +#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 ((guint32) 0u) + +/* Default value for adding an IPv6 route. This is also what iproute2 does. + * Adding an IPv6 route with metric 0, kernel translates to IP6_RT_PRIO_USER (1024). + * + * Note that kernel doesn't allow adding IPv6 routes with metric zero via netlink. + * It however can itself add routes with metric zero. */ +#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 ((guint32) 1024u) + +/* For IPv4, kernel adds a device route (subnet routes) with metric 0 when user + * configures addresses. */ +#define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE ((guint32) 0u) + +#define __NMPlatformIPRoute_COMMON \ + __NMPlatformObjWithIfindex_COMMON; \ + \ + /* The NMIPConfigSource. For routes that we receive from cache this corresponds + * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values). + * When adding a route, the source will be coerced to the protocol using + * nmp_utils_ip_config_source_coerce_to_rtprot(). + * + * rtm_protocol is part of the primary key of an IPv4 route (meaning, you can add + * two IPv4 routes that only differ in their rtm_protocol. For IPv6, that is not + * the case. + * + * When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even + * if it is not part of the primary key for IPv6) -- unless rtm_protocol is set + * to zero, in which case the first matching route (with proto ignored) is deleted. */ \ + NMIPConfigSource rt_source; \ + \ + guint8 plen; \ + \ + /* RTA_METRICS: + * + * For IPv4 routes, these properties are part of their + * ID (meaning: you can add otherwise identical IPv4 routes that + * only differ by the metric property). + * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ + * by an RTA_METRICS property. + * + * When deleting a route, kernel seems to ignore the RTA_METRICS properties. + * That is a problem/bug for IPv4 because you cannot explicitly select which + * route to delete. Kernel just picks the first. See rh#1475642. */ \ + \ + /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */ \ + bool lock_window : 1; \ + bool lock_cwnd : 1; \ + bool lock_initcwnd : 1; \ + bool lock_initrwnd : 1; \ + bool lock_mtu : 1; \ + \ + /* if TRUE, the "metric" field is interpreted as an offset that is added to a default + * metric. For example, form a DHCP lease we don't know the actually used metric, because + * that is determined by upper layers (the configuration). However, we have a default + * metric that should be used. So we set "metric_any" to %TRUE, which means to use + * the default metric. However, we still treat the "metric" field as an offset that + * will be added to the default metric. In most case, you want that "metric" is zero + * when setting "metric_any". */ \ + bool metric_any : 1; \ + \ + /* like "metric_any", the table is determined by other layers of the code. + * This field overrides "table_coerced" field. If "table_any" is true, then + * the "table_coerced" field is ignored (unlike for the metric). */ \ + bool table_any : 1; \ + \ + /* rtnh_flags + * + * Routes with rtm_flags RTM_F_CLONED are hidden by platform and + * do not exist from the point-of-view of platform users. + * Such a route is not alive, according to nmp_object_is_alive(). + * + * NOTE: currently we ignore all flags except RTM_F_CLONED + * and RTNH_F_ONLINK. + * We also may not properly consider the flags as part of the ID + * in route-cmp. */ \ + unsigned r_rtm_flags; \ + \ + /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \ + guint32 mss; \ + \ + /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */ \ + guint32 window; \ + \ + /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */ \ + guint32 cwnd; \ + \ + /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */ \ + guint32 initcwnd; \ + \ + /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */ \ + guint32 initrwnd; \ + \ + /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */ \ + guint32 mtu; \ + \ + /* RTA_PRIORITY (iproute2: metric) + * If "metric_any" is %TRUE, then this is interpreted as an offset that will be + * added to a default base metric. In such cases, the offset is usually zero. */ \ + guint32 metric; \ + \ + /* rtm_table, RTA_TABLE. + * + * This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and + * zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main + * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \ + guint32 table_coerced; \ + \ + /* rtm_type. + * + * This is not the original type, if type_coerced is 0 then + * it means RTN_UNSPEC otherwise the type value is preserved. + * */ \ + guint8 type_coerced; \ + \ + /*end*/ + +typedef struct { + __NMPlatformIPRoute_COMMON; + union { + guint8 network_ptr[1]; + guint32 __dummy_for_32bit_alignment; + }; +} NMPlatformIPRoute; + +#define NM_PLATFORM_IP_ROUTE_CAST(route) \ + NM_CONSTCAST(NMPlatformIPRoute, \ + (route), \ + NMPlatformIPXRoute, \ + NMPlatformIP4Route, \ + NMPlatformIP6Route) + +#define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) (NM_PLATFORM_IP_ROUTE_CAST(route)->plen <= 0) + +struct _NMPlatformIP4Route { + __NMPlatformIPRoute_COMMON; + in_addr_t network; + + /* RTA_GATEWAY. The gateway is part of the primary key for a route */ + in_addr_t gateway; + + /* RTA_PREFSRC (called "src" by iproute2). + * + * pref_src is part of the ID of an IPv4 route. When deleting a route, + * pref_src must match, unless set to 0.0.0.0 to match any. */ + in_addr_t pref_src; + + /* rtm_tos (iproute2: tos) + * + * For IPv4, tos is part of the weak-id (like metric). + * + * For IPv6, tos is ignored by kernel. */ + guint8 tos; + + /* The bitwise inverse of the route scope rtm_scope. It is inverted so that the + * default value (RT_SCOPE_NOWHERE) is zero. Use nm_platform_route_scope_inv() + * to convert back and forth between the inverse representation and the + * real value. + * + * rtm_scope is part of the primary key for IPv4 routes. When deleting a route, + * the scope must match, unless it is left at RT_SCOPE_NOWHERE, in which case the first + * matching route is deleted. + * + * For IPv6 routes, the scope is ignored and kernel always assumes global scope. + * Hence, this field is only in NMPlatformIP4Route. */ + guint8 scope_inv; +}; + +struct _NMPlatformIP6Route { + __NMPlatformIPRoute_COMMON; + struct in6_addr network; + + /* RTA_GATEWAY. The gateway is part of the primary key for a route */ + struct in6_addr gateway; + + /* RTA_PREFSRC (called "src" by iproute2). + * + * pref_src is not part of the ID for an IPv6 route. You cannot add two + * routes that only differ by pref_src. + * + * When deleting a route, pref_src is ignored by kernel. */ + struct in6_addr pref_src; + + /* RTA_SRC and rtm_src_len (called "from" by iproute2). + * + * Kernel clears the host part of src/src_plen. + * + * src/src_plen is part of the ID of a route just like network/plen. That is, + * Not only `ip route append`, but also `ip route add` allows to add routes that only + * differ in their src/src_plen. + */ + struct in6_addr src; + guint8 src_plen; + + /* RTA_PREF router preference. + * + * The type is guint8 to keep the struct size small. But the values are compatible with + * the NMIcmpv6RouterPref enum. */ + guint8 rt_pref; +}; + +typedef union { + NMPlatformIPRoute rx; + NMPlatformIP4Route r4; + NMPlatformIP6Route r6; +} NMPlatformIPXRoute; + +#undef __NMPlatformIPRoute_COMMON + +typedef struct { + /* struct fib_rule_uid_range */ + guint32 start; + guint32 end; +} NMFibRuleUidRange; + +typedef struct { + /* struct fib_rule_port_range */ + guint16 start; + guint16 end; +} NMFibRulePortRange; + +typedef struct { + NMIPAddr src; /* FRA_SRC */ + NMIPAddr dst; /* FRA_DST */ + guint64 tun_id; /* betoh64(FRA_TUN_ID) */ + guint32 table; /* (struct fib_rule_hdr).table, FRA_TABLE */ + guint32 flags; /* (struct fib_rule_hdr).flags */ + guint32 priority; /* RA_PRIORITY */ + guint32 fwmark; /* FRA_FWMARK */ + guint32 fwmask; /* FRA_FWMASK */ + guint32 goto_target; /* FRA_GOTO */ + guint32 flow; /* FRA_FLOW */ + guint32 suppress_prefixlen_inverse; /* ~(FRA_SUPPRESS_PREFIXLEN) */ + guint32 suppress_ifgroup_inverse; /* ~(FRA_SUPPRESS_IFGROUP) */ + NMFibRuleUidRange uid_range; /* FRA_UID_RANGE */ + NMFibRulePortRange sport_range; /* FRA_SPORT_RANGE */ + NMFibRulePortRange dport_range; /* FRA_DPORT_RANGE */ + char iifname[NMP_IFNAMSIZ]; /* FRA_IIFNAME */ + char oifname[NMP_IFNAMSIZ]; /* FRA_OIFNAME */ + guint8 addr_family; /* (struct fib_rule_hdr).family */ + guint8 action; /* (struct fib_rule_hdr).action */ + guint8 tos; /* (struct fib_rule_hdr).tos */ + guint8 src_len; /* (struct fib_rule_hdr).src_len */ + guint8 dst_len; /* (struct fib_rule_hdr).dst_len */ + guint8 l3mdev; /* FRA_L3MDEV */ + guint8 protocol; /* FRA_PROTOCOL */ + guint8 ip_proto; /* FRA_IP_PROTO */ + + bool uid_range_has : 1; /* has(FRA_UID_RANGE) */ +} NMPlatformRoutingRule; + +#define NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET (~((guint32) 0)) + +#define NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED ((guint32) 0x83126E97u) + +G_STATIC_ASSERT(((((guint64) NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) * 1000u) >> 10) + == (guint64) INT_MAX); + +typedef struct { + guint32 limit; + guint32 flows; + guint32 target; + guint32 interval; + guint32 quantum; + + /* TCA_FQ_CODEL_CE_THRESHOLD: kernel internally stores this value as + * ((val64 * NSEC_PER_USEC) >> CODEL_SHIFT). The default value (in + * the domain with this coercion) is CODEL_DISABLED_THRESHOLD (INT_MAX). + * That means, "disabled" is expressed on RTM_NEWQDISC netlink API by absence of the + * netlink attribute but also as the special value 0x83126E97u + * (NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED). + * Beware: zero is not the default you must always explicitly set this value. */ + guint32 ce_threshold; + + /* TCA_FQ_CODEL_MEMORY_LIMIT: note that only values <= 2^31 are accepted by kernel + * and kernel defaults to 32MB. + * Note that we use the special value NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET + * to indicate that no explicit limit is set (when we send a RTM_NEWQDISC request). + * This will cause kernel to choose the default (32MB). + * Beware: zero is not the default you must always explicitly set this value. */ + guint32 memory_limit; + + bool ecn : 1; +} NMPlatformQdiscFqCodel; + +typedef struct { + unsigned quantum; + int perturb_period; + guint32 limit; + unsigned divisor; + unsigned flows; + unsigned depth; +} NMPlatformQdiscSfq; + +typedef struct { + guint64 rate; + guint32 burst; + guint32 limit; + guint32 latency; +} NMPlatformQdiscTbf; + +typedef struct { + __NMPlatformObjWithIfindex_COMMON; + + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + int addr_family; + guint32 handle; + guint32 parent; + guint32 info; + union { + NMPlatformQdiscFqCodel fq_codel; + NMPlatformQdiscSfq sfq; + NMPlatformQdiscTbf tbf; + }; +} NMPlatformQdisc; + +typedef struct { + char sdata[32]; +} NMPlatformActionSimple; + +typedef struct { + int ifindex; + bool egress : 1; + bool ingress : 1; + bool mirror : 1; + bool redirect : 1; +} NMPlatformActionMirred; + +typedef struct { + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + union { + NMPlatformActionSimple simple; + NMPlatformActionMirred mirred; + }; +} NMPlatformAction; + +#define NM_PLATFORM_ACTION_KIND_SIMPLE "simple" +#define NM_PLATFORM_ACTION_KIND_MIRRED "mirred" + +typedef struct { + __NMPlatformObjWithIfindex_COMMON; + + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + int addr_family; + guint32 handle; + guint32 parent; + guint32 info; + NMPlatformAction action; +} NMPlatformTfilter; + +#undef __NMPlatformObjWithIfindex_COMMON + +typedef struct { + gboolean is_ip4; + NMPObjectType obj_type; + int addr_family; + gsize sizeof_route; + int (*route_cmp)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type); + const char *(*route_to_string)(const NMPlatformIPXRoute *route, char *buf, gsize len); +} NMPlatformVTableRoute; + +typedef union { + struct { + NMPlatformVTableRoute v6; + NMPlatformVTableRoute v4; + }; + NMPlatformVTableRoute vx[2]; +} _NMPlatformVTableRouteUnion; + +extern const _NMPlatformVTableRouteUnion nm_platform_vtable_route; + +typedef struct { + guint16 id; + guint32 qos; + bool proto_ad : 1; +} NMPlatformVFVlan; + +typedef struct { + guint32 index; + guint32 min_tx_rate; + guint32 max_tx_rate; + guint num_vlans; + NMPlatformVFVlan *vlans; + struct { + guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */ + guint8 len; + } mac; + gint8 spoofchk; + gint8 trust; +} NMPlatformVF; + +typedef struct { + guint16 vid_start; + guint16 vid_end; + bool untagged : 1; + bool pvid : 1; +} NMPlatformBridgeVlan; + +typedef struct { + NMEtherAddr group_addr; + bool mcast_querier : 1; + bool mcast_query_use_ifaddr : 1; + bool mcast_snooping : 1; + bool stp_state : 1; + bool vlan_stats_enabled : 1; + guint16 group_fwd_mask; + guint16 priority; + guint16 vlan_protocol; + guint32 ageing_time; + guint32 forward_delay; + guint32 hello_time; + guint32 max_age; + guint32 mcast_last_member_count; + guint32 mcast_startup_query_count; + guint32 mcast_hash_max; + guint64 mcast_last_member_interval; + guint64 mcast_membership_interval; + guint64 mcast_querier_interval; + guint64 mcast_query_interval; + guint64 mcast_query_response_interval; + guint64 mcast_startup_query_interval; + guint8 mcast_router; +} NMPlatformLnkBridge; + +extern const NMPlatformLnkBridge nm_platform_lnk_bridge_default; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint16 input_flags; + guint16 output_flags; + guint32 input_key; + guint32 output_key; + guint8 ttl; + guint8 tos; + bool path_mtu_discovery : 1; + bool is_tap : 1; +} NMPlatformLnkGre; + +typedef struct { + int p_key; + const char *mode; +} NMPlatformLnkInfiniband; + +typedef struct { + struct in6_addr local; + struct in6_addr remote; + int parent_ifindex; + guint8 ttl; + guint8 tclass; + guint8 encap_limit; + guint8 proto; + guint flow_label; + guint32 flags; + + /* IP6GRE only */ + guint32 input_key; + guint32 output_key; + guint16 input_flags; + guint16 output_flags; + bool is_tap : 1; + bool is_gre : 1; +} NMPlatformLnkIp6Tnl; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint8 ttl; + guint8 tos; + bool path_mtu_discovery : 1; +} NMPlatformLnkIpIp; + +typedef struct { + int parent_ifindex; + guint64 sci; /* host byte order */ + guint64 cipher_suite; + guint32 window; + guint8 icv_length; + guint8 encoding_sa; + guint8 validation; + bool encrypt : 1; + bool protect : 1; + bool include_sci : 1; + bool es : 1; + bool scb : 1; + bool replay_protect : 1; +} NMPlatformLnkMacsec; + +typedef struct { + guint mode; + bool no_promisc : 1; + bool tap : 1; +} NMPlatformLnkMacvlan; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint16 flags; + guint8 ttl; + guint8 tos; + guint8 proto; + bool path_mtu_discovery : 1; +} NMPlatformLnkSit; + +typedef struct { + guint32 owner; + guint32 group; + + guint8 type; + + bool owner_valid : 1; + bool group_valid : 1; + + bool pi : 1; + bool vnet_hdr : 1; + bool multi_queue : 1; + bool persist : 1; +} NMPlatformLnkTun; + +typedef struct { + /* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */ + guint16 id; + NMVlanFlags flags; +} NMPlatformLnkVlan; + +typedef struct { + guint32 table; +} NMPlatformLnkVrf; + +typedef struct { + struct in6_addr group6; + struct in6_addr local6; + in_addr_t group; + in_addr_t local; + int parent_ifindex; + guint32 id; + guint32 ageing; + guint32 limit; + guint16 dst_port; + guint16 src_port_min; + guint16 src_port_max; + guint8 tos; + guint8 ttl; + bool learning : 1; + bool proxy : 1; + bool rsc : 1; + bool l2miss : 1; + bool l3miss : 1; +} NMPlatformLnkVxlan; + +#define NMP_WIREGUARD_PUBLIC_KEY_LEN 32 +#define NMP_WIREGUARD_SYMMETRIC_KEY_LEN 32 + +typedef struct { + guint32 fwmark; + guint16 listen_port; + guint8 private_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; + guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; +} NMPlatformLnkWireGuard; + +typedef enum { + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE = 0, + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS = (1LL << 0), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY = (1LL << 1), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT = (1LL << 2), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK = (1LL << 3), +} NMPlatformWireGuardChangeFlags; + +typedef enum { + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE = 0, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME = (1LL << 0), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY = (1LL << 1), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL = (1LL << 2), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT = (1LL << 3), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS = (1LL << 4), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS = (1LL << 5), + + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT = + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS, + +} NMPlatformWireGuardChangePeerFlags; + +typedef void (*NMPlatformAsyncCallback)(GError *error, gpointer user_data); + +/*****************************************************************************/ + +typedef enum { + NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED, + + /* this also includes FRA_SPORT_RANGE and FRA_DPORT_RANGE which + * were added at the same time. */ + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, + + _NM_PLATFORM_KERNEL_SUPPORT_NUM, +} NMPlatformKernelSupportType; + +extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM]; + +int _nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value); + +static inline gboolean +_nm_platform_kernel_support_detected(NMPlatformKernelSupportType type) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + return G_LIKELY(g_atomic_int_get(&_nm_platform_kernel_support_state[type]) != 0); +} + +static inline NMOptionBool +nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean init_if_not_set) +{ + int v; + + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + v = g_atomic_int_get(&_nm_platform_kernel_support_state[type]); + if (G_UNLIKELY(v == 0)) { + if (!init_if_not_set) + return NM_OPTION_BOOL_DEFAULT; + v = _nm_platform_kernel_support_init(type, 0); + } + return (v >= 0); +} + +static inline gboolean +nm_platform_kernel_support_get(NMPlatformKernelSupportType type) +{ + return nm_platform_kernel_support_get_full(type, TRUE) != NM_OPTION_BOOL_FALSE; +} + +/*****************************************************************************/ + +struct _NMPlatformPrivate; + +struct _NMPlatform { + GObject parent; + NMPNetns * _netns; + struct _NMPlatformPrivate *_priv; +}; + +typedef struct { + GObjectClass parent; + + gboolean (*sysctl_set)(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value); + void (*sysctl_set_async)(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable); + char *(*sysctl_get)(NMPlatform *self, const char *pathid, int dirfd, const char *path); + + void (*refresh_all)(NMPlatform *self, NMPObjectType obj_type); + void (*process_events)(NMPlatform *self); + + int (*link_add)(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link); + gboolean (*link_delete)(NMPlatform *self, int ifindex); + gboolean (*link_refresh)(NMPlatform *self, int ifindex); + gboolean (*link_set_netns)(NMPlatform *self, int ifindex, int netns_fd); + gboolean (*link_set_up)(NMPlatform *self, int ifindex, gboolean *out_no_firmware); + gboolean (*link_set_down)(NMPlatform *self, int ifindex); + gboolean (*link_set_arp)(NMPlatform *self, int ifindex); + gboolean (*link_set_noarp)(NMPlatform *self, int ifindex); + + int (*link_set_user_ipv6ll_enabled)(NMPlatform *self, int ifindex, gboolean enabled); + gboolean (*link_set_token)(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid); + + gboolean (*link_get_permanent_address)(NMPlatform *self, + int ifindex, + guint8 * buf, + size_t * length); + int (*link_set_address)(NMPlatform *self, int ifindex, gconstpointer address, size_t length); + int (*link_set_mtu)(NMPlatform *self, int ifindex, guint32 mtu); + gboolean (*link_set_name)(NMPlatform *self, int ifindex, const char *name); + void (*link_set_sriov_params_async)(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable); + gboolean (*link_set_sriov_vfs)(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); + gboolean (*link_set_bridge_vlans)(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans); + + char *(*link_get_physical_port_id)(NMPlatform *self, int ifindex); + guint (*link_get_dev_id)(NMPlatform *self, int ifindex); + gboolean (*link_get_wake_on_lan)(NMPlatform *self, int ifindex); + gboolean (*link_get_driver_info)(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version); + + gboolean (*link_supports_carrier_detect)(NMPlatform *self, int ifindex); + gboolean (*link_supports_vlans)(NMPlatform *self, int ifindex); + gboolean (*link_supports_sriov)(NMPlatform *self, int ifindex); + + gboolean (*link_enslave)(NMPlatform *self, int master, int slave); + gboolean (*link_release)(NMPlatform *self, int master, int slave); + + gboolean (*link_can_assume)(NMPlatform *self, int ifindex); + + int (*link_wireguard_change)(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const struct _NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags); + + gboolean (*link_vlan_change)(NMPlatform * self, + int ifindex, + NMVlanFlags flags_mask, + NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map); + gboolean (*link_tun_add)(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd); + + gboolean (*infiniband_partition_add)(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link); + gboolean (*infiniband_partition_delete)(NMPlatform *self, int parent, int p_key); + + gboolean (*wifi_get_capabilities)(NMPlatform * self, + int ifindex, + NMDeviceWifiCapabilities *caps); + gboolean (*wifi_get_station)(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + gboolean (*wifi_get_bssid)(NMPlatform *self, int ifindex, guint8 *bssid); + guint32 (*wifi_get_frequency)(NMPlatform *self, int ifindex); + int (*wifi_get_quality)(NMPlatform *self, int ifindex); + guint32 (*wifi_get_rate)(NMPlatform *self, int ifindex); + NM80211Mode (*wifi_get_mode)(NMPlatform *self, int ifindex); + void (*wifi_set_mode)(NMPlatform *self, int ifindex, NM80211Mode mode); + void (*wifi_set_powersave)(NMPlatform *self, int ifindex, guint32 powersave); + guint32 (*wifi_find_frequency)(NMPlatform *self, int ifindex, const guint32 *freqs); + void (*wifi_indicate_addressing_running)(NMPlatform *self, int ifindex, gboolean running); + NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan)(NMPlatform *self, int ifindex); + gboolean (*wifi_set_wake_on_wlan)(NMPlatform * self, + int ifindex, + NMSettingWirelessWakeOnWLan wowl); + + guint32 (*mesh_get_channel)(NMPlatform *self, int ifindex); + gboolean (*mesh_set_channel)(NMPlatform *self, int ifindex, guint32 channel); + gboolean (*mesh_set_ssid)(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len); + + guint16 (*wpan_get_pan_id)(NMPlatform *self, int ifindex); + gboolean (*wpan_set_pan_id)(NMPlatform *self, int ifindex, guint16 pan_id); + guint16 (*wpan_get_short_addr)(NMPlatform *self, int ifindex); + gboolean (*wpan_set_short_addr)(NMPlatform *self, int ifindex, guint16 short_addr); + gboolean (*wpan_set_channel)(NMPlatform *self, int ifindex, guint8 page, guint8 channel); + + gboolean (*object_delete)(NMPlatform *self, const NMPObject *obj); + + gboolean (*ip4_address_add)(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags, + const char *label); + gboolean (*ip6_address_add)(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags); + gboolean (*ip4_address_delete)(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); + gboolean (*ip6_address_delete)(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen); + + int (*ip_route_add)(NMPlatform * self, + NMPNlmFlags flags, + int addr_family, + const NMPlatformIPRoute *route); + int (*ip_route_get)(NMPlatform * self, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route); + + int (*routing_rule_add)(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule); + + int (*qdisc_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc); + + int (*tfilter_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter); +} NMPlatformClass; + +/* NMPlatform signals + * + * Each signal handler is called with a type-specific object that provides + * key attributes that constitute identity of the object. They may also + * provide additional attributes for convenience. + * + * The object only intended to be used by the signal handler to determine + * the current values. It is no longer valid after the signal handler exits + * but you are free to copy the provided information and use it for later + * reference. + */ +#define NM_PLATFORM_SIGNAL_LINK_CHANGED "link-changed" +#define NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED "ip4-address-changed" +#define NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED "ip6-address-changed" +#define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED "ip4-route-changed" +#define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED "ip6-route-changed" +#define NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED "routing-rule-changed" +#define NM_PLATFORM_SIGNAL_QDISC_CHANGED "qdisc-changed" +#define NM_PLATFORM_SIGNAL_TFILTER_CHANGED "tfilter-changed" + +const char *nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type); + +/*****************************************************************************/ + +GType nm_platform_get_type(void); + +void nm_platform_setup(NMPlatform *instance); +NMPlatform *nm_platform_get(void); + +#define NM_PLATFORM_GET (nm_platform_get()) + +/*****************************************************************************/ + +static inline in_addr_t +nm_platform_ip4_broadcast_address_create(in_addr_t address, guint8 plen) +{ + return address | ~_nm_utils_ip4_prefix_to_netmask(plen); +} + +static inline in_addr_t +nm_platform_ip4_broadcast_address_from_addr(const NMPlatformIP4Address *addr) +{ + nm_assert(addr); + + if (addr->use_ip4_broadcast_address) + return addr->broadcast_address; + + /* the set broadcast-address gets ignored, and we determine a default brd base + * on the peer IFA_ADDRESS. */ + if (addr->peer_address != 0u && addr->plen < 31 /* RFC3021 */) + return nm_platform_ip4_broadcast_address_create(addr->peer_address, addr->plen); + return 0u; +} + +/*****************************************************************************/ + +/** + * nm_platform_route_table_coerce: + * @table: the route table, in its original value as received + * from rtm_table/RTA_TABLE. + * + * Returns: returns the coerced table id, that can be stored in + * NMPlatformIPRoute.table_coerced. + */ +static inline guint32 +nm_platform_route_table_coerce(guint32 table) +{ + /* For kernel, the default table is RT_TABLE_MAIN (254). + * We want that in NMPlatformIPRoute.table_coerced a numeric + * zero is the default. Hence, @table_coerced swaps the + * value 0 and 254. Use nm_platform_route_table_coerce() + * and nm_platform_route_table_uncoerce() to convert between + * the two domains. */ + switch (table) { + case 0 /* RT_TABLE_UNSPEC */: + return 254; + case 254 /* RT_TABLE_MAIN */: + return 0; + default: + return table; + } +} + +/** + * nm_platform_route_table_uncoerce: + * @table_coerced: the route table, in its coerced value + * @normalize: whether to normalize RT_TABLE_UNSPEC to + * RT_TABLE_MAIN. For kernel, routes with a table id + * RT_TABLE_UNSPEC do not exist and are treated like + * RT_TABLE_MAIN. + * + * Returns: reverts the coerced table ID in NMPlatformIPRoute.table_coerced + * to the original value as kernel understands it. + */ +static inline guint32 +nm_platform_route_table_uncoerce(guint32 table_coerced, gboolean normalize) +{ + /* this undoes nm_platform_route_table_coerce(). */ + switch (table_coerced) { + case 0 /* RT_TABLE_UNSPEC */: + return 254; + case 254 /* RT_TABLE_MAIN */: + return normalize ? 254 : 0; + default: + return table_coerced; + } +} + +static inline gboolean +nm_platform_route_table_is_main(guint32 table) +{ + /* same as + * nm_platform_route_table_uncoerce (table, TRUE) == RT_TABLE_MAIN + * and + * nm_platform_route_table_uncoerce (nm_platform_route_table_coerce (table), TRUE) == RT_TABLE_MAIN + * + * That is, the function operates the same on @table and its coerced + * form. + */ + return table == 0 || table == 254; +} + +/** + * nm_platform_route_scope_inv: + * @scope: the route scope, either its original value, or its inverse. + * + * This function is useful, because the constants such as RT_SCOPE_NOWHERE + * are 'int', so ~scope also gives an 'int'. This function gets the type + * casts to guint8 right. + * + * Returns: the bitwise inverse of the route scope. + * */ +#define nm_platform_route_scope_inv _nm_platform_uint8_inv +static inline guint8 +_nm_platform_uint8_inv(guint8 scope) +{ + return (guint8) ~scope; +} + +/** + * nm_platform_route_type_coerce: + * @table: the route type, in its original value. + * + * Returns: returns the coerced type, that can be stored in + * NMPlatformIPRoute.type_coerced. + */ +static inline guint8 +nm_platform_route_type_coerce(guint8 type) +{ + switch (type) { + case 0 /* RTN_UNSPEC */: + return 1; + case 1 /* RTN_UNICAST */: + return 0; + default: + return type; + } +} + +/** + * nm_platform_route_type_uncoerce: + * @table: the type table, in its coerced value + * + * Returns: reverts the coerced type in NMPlatformIPRoute.type_coerced + * to the original value as kernel understands it. + */ +static inline guint8 +nm_platform_route_type_uncoerce(guint8 type_coerced) +{ + return nm_platform_route_type_coerce(type_coerced); +} + +gboolean nm_platform_get_use_udev(NMPlatform *self); +gboolean nm_platform_get_log_with_ptr(NMPlatform *self); + +NMPNetns *nm_platform_netns_get(NMPlatform *self); +gboolean nm_platform_netns_push(NMPlatform *self, NMPNetns **netns); + +const char *nm_link_type_to_string(NMLinkType link_type); + +#define NMP_SYSCTL_PATHID_ABSOLUTE(path) ((const char *) NULL), -1, (path) + +#define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path) \ + nm_sprintf_buf_unsafe_a(NM_STRLEN("net:/sys/class/net//\0") + NMP_IFNAMSIZ + ({ \ + const gsize _l = strlen(path); \ + \ + nm_assert(_l < 200); \ + _l; \ + }), \ + "net:/sys/class/net/%s/%s", \ + (ifname), \ + (path)), \ + (dirfd), (path) + +#define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path) \ + nm_sprintf_bufa(NM_STRLEN("net:/sys/class/net//" path "/\0") + NMP_IFNAMSIZ, \ + "net:/sys/class/net/%s/%s", \ + (ifname), \ + path), \ + (dirfd), ("" path "") + +int nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname); +gboolean nm_platform_sysctl_set(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value); +void nm_platform_sysctl_set_async(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable); +char * nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path); +gint32 nm_platform_sysctl_get_int32(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + gint32 fallback); +gint64 nm_platform_sysctl_get_int_checked(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +char *nm_platform_sysctl_ip_conf_get(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property); + +gint64 nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +gboolean nm_platform_sysctl_ip_conf_set(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + const char *value); + +gboolean nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + gint64 value); + +gboolean +nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value); +gboolean nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self, + const char *iface, + guint value_ms); +gboolean nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self, + const char *iface, + guint value_ms); +int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *platform, + const char *iface, + gboolean consider_all, + gboolean * out_due_to_all); + +const char *nm_platform_if_indextoname(NMPlatform *self, + int ifindex, + char out_ifname[static 16 /* IFNAMSIZ */]); +int nm_platform_if_nametoindex(NMPlatform *self, const char *ifname); + +const NMPObject *nm_platform_link_get_obj(NMPlatform *self, int ifindex, gboolean visible_only); +const NMPlatformLink *nm_platform_link_get(NMPlatform *self, int ifindex); +const NMPlatformLink *nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname); +const NMPlatformLink *nm_platform_link_get_by_address(NMPlatform * self, + NMLinkType link_type, + gconstpointer address, + size_t length); + +GPtrArray *nm_platform_link_get_all(NMPlatform *self, gboolean sort_by_name); + +int nm_platform_link_add(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link); + +static inline int +nm_platform_link_veth_add(NMPlatform * self, + const char * name, + const char * peer, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, 0, peer, out_link); +} + +static inline int +nm_platform_link_dummy_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_bridge_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + guint32 mtu, + const NMPlatformLnkBridge *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, + NM_LINK_TYPE_BRIDGE, + name, + 0, + address, + address_len, + mtu, + props, + out_link); +} + +static inline int +nm_platform_link_bond_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_team_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_gre_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + const NMPlatformLnkGre *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + + return nm_platform_link_add(self, + props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, + name, + 0, + address, + address_len, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_sit_add(NMPlatform * self, + const char * name, + const NMPlatformLnkSit *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_vlan_add(NMPlatform * self, + const char * name, + int parent, + int vlanid, + guint32 vlanflags, + const NMPlatformLink **out_link) +{ + g_return_val_if_fail(parent >= 0, -NME_BUG); + g_return_val_if_fail(vlanid >= 0, -NME_BUG); + + return nm_platform_link_add(self, + NM_LINK_TYPE_VLAN, + name, + parent, + NULL, + 0, + 0, + &((NMPlatformLnkVlan){ + .id = vlanid, + .flags = vlanflags, + }), + out_link); +} + +static inline int +nm_platform_link_vrf_add(NMPlatform * self, + const char * name, + const NMPlatformLnkVrf *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_vxlan_add(NMPlatform * self, + const char * name, + const NMPlatformLnkVxlan *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_6lowpan_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, + NM_LINK_TYPE_6LOWPAN, + name, + parent, + NULL, + 0, + 0, + NULL, + out_link); +} + +static inline int +nm_platform_link_ip6tnl_add(NMPlatform * self, + const char * name, + const NMPlatformLnkIp6Tnl *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(!props->is_gre, -NME_BUG); + + return nm_platform_link_add(self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_ip6gre_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + const NMPlatformLnkIp6Tnl *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(props->is_gre, -NME_BUG); + + return nm_platform_link_add(self, + props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE, + name, + 0, + address, + address_len, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_ipip_add(NMPlatform * self, + const char * name, + const NMPlatformLnkIpIp *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + + return nm_platform_link_add(self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_macsec_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLnkMacsec *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(parent > 0, -NME_BUG); + + return nm_platform_link_add(self, + NM_LINK_TYPE_MACSEC, + name, + parent, + NULL, + 0, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_macvlan_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLnkMacvlan *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(parent > 0, -NME_BUG); + + return nm_platform_link_add(self, + props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN, + name, + parent, + NULL, + 0, + 0, + props, + out_link); +} + +gboolean nm_platform_link_delete(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd); + +struct _NMDedupMultiHeadEntry; +struct _NMPLookup; +const struct _NMDedupMultiHeadEntry *nm_platform_lookup(NMPlatform * self, + const struct _NMPLookup *lookup); + +#define nm_platform_iter_obj_for_each(iter, self, lookup, obj) \ + for (nm_dedup_multi_iter_init((iter), nm_platform_lookup((self), (lookup))); \ + nm_platform_dedup_multi_iter_next_obj((iter), (obj), NMP_OBJECT_TYPE_UNKNOWN);) + +gboolean nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data); +gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj, + gpointer user_data); + +GPtrArray *nm_platform_lookup_clone(NMPlatform * self, + const struct _NMPLookup *lookup, + NMPObjectPredicateFunc predicate, + gpointer user_data); + +/* convenience methods to lookup the link and access fields of NMPlatformLink. */ +int nm_platform_link_get_ifindex(NMPlatform *self, const char *name); +const char *nm_platform_link_get_name(NMPlatform *self, int ifindex); +NMLinkType nm_platform_link_get_type(NMPlatform *self, int ifindex); +gboolean nm_platform_link_is_software(NMPlatform *self, int ifindex); +int nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags); +gboolean nm_platform_link_is_up(NMPlatform *self, int ifindex); +gboolean nm_platform_link_is_connected(NMPlatform *self, int ifindex); +gboolean nm_platform_link_uses_arp(NMPlatform *self, int ifindex); +guint32 nm_platform_link_get_mtu(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex); + +gconstpointer nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length); + +int nm_platform_link_get_master(NMPlatform *self, int slave); + +gboolean nm_platform_link_can_assume(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged); +gboolean nm_platform_link_supports_slaves(NMPlatform *self, int ifindex); +const char *nm_platform_link_get_type_name(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_refresh(NMPlatform *self, int ifindex); +void nm_platform_process_events(NMPlatform *self); + +const NMPlatformLink * +nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname); + +gboolean nm_platform_link_set_up(NMPlatform *self, int ifindex, gboolean *out_no_firmware); +gboolean nm_platform_link_set_down(NMPlatform *self, int ifindex); +gboolean nm_platform_link_set_arp(NMPlatform *self, int ifindex); +gboolean nm_platform_link_set_noarp(NMPlatform *self, int ifindex); + +const char *nm_platform_link_get_udi(NMPlatform *self, int ifindex); +const char *nm_platform_link_get_path(NMPlatform *self, int ifindex); + +struct udev_device *nm_platform_link_get_udev_device(NMPlatform *self, int ifindex); + +int nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled); +gboolean nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid); + +gboolean +nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length); +int nm_platform_link_set_address(NMPlatform *self, int ifindex, const void *address, size_t length); +int nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu); +gboolean nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name); + +void nm_platform_link_set_sriov_params_async(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable); + +gboolean +nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); +gboolean nm_platform_link_set_bridge_vlans(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans); + +char * nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex); +guint nm_platform_link_get_dev_id(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_driver_info(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version); + +gboolean nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex); +gboolean nm_platform_link_supports_vlans(NMPlatform *self, int ifindex); +gboolean nm_platform_link_supports_sriov(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_enslave(NMPlatform *self, int master, int slave); +gboolean nm_platform_link_release(NMPlatform *self, int master, int slave); + +gboolean nm_platform_sysctl_master_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value); +char * nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option); +gboolean nm_platform_sysctl_slave_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value); +char * nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option); + +const NMPObject *nm_platform_link_get_lnk(NMPlatform * self, + int ifindex, + NMLinkType link_type, + const NMPlatformLink **out_link); +const NMPlatformLnkBridge * +nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkInfiniband * +nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacsec * +nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvtap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkSit * +nm_platform_link_get_lnk_sit(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkTun * +nm_platform_link_get_lnk_tun(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVlan * +nm_platform_link_get_lnk_vlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVrf * +nm_platform_link_get_lnk_vrf(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVxlan * +nm_platform_link_get_lnk_vxlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkWireGuard * +nm_platform_link_get_lnk_wireguard(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); + +gboolean nm_platform_link_vlan_set_ingress_map(NMPlatform *self, int ifindex, int from, int to); +gboolean nm_platform_link_vlan_set_egress_map(NMPlatform *self, int ifindex, int from, int to); +gboolean nm_platform_link_vlan_change(NMPlatform * self, + int ifindex, + NMVlanFlags flags_mask, + NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map); + +int nm_platform_link_infiniband_add(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link); +int nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key); +gboolean nm_platform_link_infiniband_get_properties(NMPlatform * self, + int ifindex, + int * parent, + int * p_key, + const char **mode); + +gboolean nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex); +gboolean nm_platform_link_tun_get_properties(NMPlatform * self, + int ifindex, + NMPlatformLnkTun *out_properties); + +gboolean +nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps); +guint32 nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex); +gboolean nm_platform_wifi_get_station(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); +NM80211Mode nm_platform_wifi_get_mode(NMPlatform *self, int ifindex); +void nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, NM80211Mode mode); +void nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave); +guint32 nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs); +void nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running); +NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex); +gboolean +nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl); + +guint32 nm_platform_mesh_get_channel(NMPlatform *self, int ifindex); +gboolean nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel); +gboolean nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len); + +guint16 nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex); +gboolean nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id); +guint16 nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex); +gboolean nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr); +gboolean nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel); + +void nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen); +const struct in6_addr *nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr); + +const NMPlatformIP4Address *nm_platform_ip4_address_get(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); + +int nm_platform_link_sit_add(NMPlatform * self, + const char * name, + const NMPlatformLnkSit *props, + const NMPlatformLink ** out_link); +int nm_platform_link_tun_add(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd); +gboolean nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent); + +int +nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link); + +int nm_platform_link_wireguard_change(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const struct _NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags); + +const NMPlatformIP6Address * +nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address); + +gboolean nm_platform_object_delete(NMPlatform *self, const NMPObject *route); + +gboolean nm_platform_ip4_address_add(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags, + const char *label); +gboolean nm_platform_ip6_address_add(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags); +gboolean nm_platform_ip4_address_delete(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); +gboolean +nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen); + +gboolean nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + GPtrArray * addresses_prune); + +GPtrArray *nm_platform_ip_address_get_prune_list(NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs); + +static inline gboolean +_nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + gboolean full_sync) +{ + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + + addresses_prune = nm_platform_ip_address_get_prune_list(self, addr_family, ifindex, !full_sync); + return nm_platform_ip_address_sync(self, + addr_family, + ifindex, + known_addresses, + addresses_prune); +} + +static inline gboolean +nm_platform_ip4_address_sync(NMPlatform *self, int ifindex, GPtrArray *known_addresses) +{ + return _nm_platform_ip_address_sync(self, AF_INET, ifindex, known_addresses, TRUE); +} + +static inline gboolean +nm_platform_ip6_address_sync(NMPlatform *self, + int ifindex, + GPtrArray * known_addresses, + gboolean full_sync) +{ + return _nm_platform_ip_address_sync(self, AF_INET6, ifindex, known_addresses, full_sync); +} + +gboolean nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex); + +static inline gconstpointer +nm_platform_ip_address_get_peer_address(int addr_family, const NMPlatformIPAddress *addr) +{ + nm_assert_addr_family(addr_family); + nm_assert(addr); + + if (NM_IS_IPv4(addr_family)) + return &((NMPlatformIP4Address *) addr)->peer_address; + return &((NMPlatformIP6Address *) addr)->peer_address; +} + +void nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route); + +static inline guint32 +nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r) +{ + nm_assert(r); + + return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, r->metric) + : r->metric; +} + +static inline guint32 +nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r) +{ + nm_assert(r); + + return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, r->metric) + : r->metric; +} + +static inline guint32 +nm_platform_ip_route_get_effective_table(const NMPlatformIPRoute *r) +{ + nm_assert(r); + nm_assert(!r->table_any || r->table_coerced == 0); + + return r->table_any ? 254u /* RT_TABLE_MAIN */ + : nm_platform_route_table_uncoerce(r->table_coerced, TRUE); +} + +static inline gconstpointer +nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route) +{ + nm_assert_addr_family(addr_family); + nm_assert(route); + + if (NM_IS_IPv4(addr_family)) + return &((NMPlatformIP4Route *) route)->gateway; + return &((NMPlatformIP6Route *) route)->gateway; +} + +int nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route); +int nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route); +int nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route); + +GPtrArray *nm_platform_ip_route_get_prune_list(NMPlatform * self, + int addr_family, + int ifindex, + NMIPRouteTableSyncMode route_table_sync); + +gboolean nm_platform_ip_route_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * routes, + GPtrArray * routes_prune, + GPtrArray **out_temporary_not_available); + +gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex); + +int nm_platform_ip_route_get(NMPlatform * self, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route); + +int nm_platform_routing_rule_add(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule); + +int nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc); +gboolean nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs); + +int nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter); +gboolean nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters); + +const char *nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len); +const char *nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len); +const char *nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len); +const char *nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len); +const char *nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len); +const char *nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len); +const char *nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len); +const char *nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vrf_to_string(const NMPlatformLnkVrf *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vxlan_to_string(const NMPlatformLnkVxlan *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len); +const char * +nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len); +const char * +nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len); +const char *nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len); +const char *nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len); +const char * +nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len); +const char *nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len); +const char *nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len); +const char *nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len); +const char * +nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len); + +const char *nm_platform_vlan_qos_mapping_to_string(const char * name, + const NMVlanQosMapping *map, + gsize n_map, + char * buf, + gsize len); + +const char * +nm_platform_wireguard_peer_to_string(const struct _NMPWireGuardPeer *peer, char *buf, gsize len); + +int nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b); +int nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b); +int nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b); +int nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, + const NMPlatformLnkInfiniband *b); +int nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b); +int nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b); +int nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b); +int nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b); +int nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b); +int nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b); +int nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b); +int nm_platform_lnk_vrf_cmp(const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b); +int nm_platform_lnk_vxlan_cmp(const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b); +int nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b); +int nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b); +int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b); + +int nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1, + const NMPlatformIP4Address *a2); + +int nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1, + const NMPlatformIP6Address *a2, + gboolean prefer_temp); + +GHashTable *nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex); + +int nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, + const NMPlatformIP4Route *b, + NMPlatformIPRouteCmpType cmp_type); +int nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, + const NMPlatformIP6Route *b, + NMPlatformIPRouteCmpType cmp_type); + +static inline int +nm_platform_ip4_route_cmp_full(const NMPlatformIP4Route *a, const NMPlatformIP4Route *b) +{ + return nm_platform_ip4_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL); +} + +static inline int +nm_platform_ip6_route_cmp_full(const NMPlatformIP6Route *a, const NMPlatformIP6Route *b) +{ + return nm_platform_ip6_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL); +} + +int nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a, + const NMPlatformRoutingRule *b, + NMPlatformRoutingRuleCmpType cmp_type); + +static inline int +nm_platform_routing_rule_cmp_full(const NMPlatformRoutingRule *a, const NMPlatformRoutingRule *b) +{ + return nm_platform_routing_rule_cmp(a, b, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL); +} + +int nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b); +int nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a, + const NMPlatformQdisc *b, + gboolean compare_handle); +int nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b); + +void nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h); +void nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h); +void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h); +void nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h); +void nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h); +void nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj, + NMPlatformRoutingRuleCmpType cmp_type, + NMHashState * h); +void nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h); +void nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h); +void nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h); +void nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h); +void nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h); +void nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h); +void nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h); +void nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h); +void nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h); +void nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h); +void nm_platform_lnk_vrf_hash_update(const NMPlatformLnkVrf *obj, NMHashState *h); +void nm_platform_lnk_vxlan_hash_update(const NMPlatformLnkVxlan *obj, NMHashState *h); +void nm_platform_lnk_wireguard_hash_update(const NMPlatformLnkWireGuard *obj, NMHashState *h); + +void nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h); +void nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h); + +#define NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN ((gsize) 162) + +const char *nm_platform_link_flags2str(unsigned flags, char *buf, gsize len); +const char *nm_platform_link_inet6_addrgenmode2str(guint8 mode, char *buf, gsize len); +const char *nm_platform_addr_flags2str(unsigned flags, char *buf, gsize len); +const char *nm_platform_route_scope2str(int scope, char *buf, gsize len); + +int nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b); + +gboolean nm_platform_ethtool_set_wake_on_lan(NMPlatform * self, + int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password); +gboolean nm_platform_ethtool_set_link_settings(NMPlatform * self, + int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex); +gboolean nm_platform_ethtool_get_link_settings(NMPlatform * self, + int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex); + +NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex); +gboolean nm_platform_ethtool_set_features( + NMPlatform * self, + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */); + +gboolean nm_platform_ethtool_get_link_coalesce(NMPlatform * self, + int ifindex, + NMEthtoolCoalesceState *coalesce); + +gboolean nm_platform_ethtool_set_coalesce(NMPlatform * self, + int ifindex, + const NMEthtoolCoalesceState *coalesce); + +gboolean nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring); + +gboolean +nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring); + +void nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self, + int ifindex, + GPtrArray * ip4_dev_route_blacklist); + +struct _NMDedupMultiIndex *nm_platform_get_multi_idx(NMPlatform *self); + +/*****************************************************************************/ + +gboolean nm_platform_ip6_address_match(const NMPlatformIP6Address *addr, + NMPlatformMatchFlags match_flag); + +#endif /* __NETWORKMANAGER_PLATFORM_H__ */ diff --git a/src/core/platform/nmp-object.c b/src/core/platform/nmp-object.c new file mode 100644 index 0000000..9520013 --- /dev/null +++ b/src/core/platform/nmp-object.c @@ -0,0 +1,3457 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nmp-object.h" + +#include +#include +#include +#include + +#include "nm-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" + +#include "nm-core-utils.h" +#include "nm-platform/nm-platform-utils.h" + +#include "wifi/nm-wifi-utils.h" +#include "wpan/nm-wpan-utils.h" + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, obj, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + const NMPObject *const __obj = (obj); \ + \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __obj, \ + (__obj ? NMP_OBJECT_GET_CLASS(__obj)->obj_type_name \ + : "???") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +typedef struct { + NMDedupMultiIdxType parent; + NMPCacheIdType cache_id_type; +} DedupMultiIdxType; + +struct _NMPCache { + /* the cache contains only one hash table for all object types, and similarly + * it contains only one NMMultiIndex. + * This works, because different object types don't ever compare equal and + * because their index ids also don't overlap. + * + * For routes and addresses, the cache contains an address if (and only if) the + * object was reported via netlink. + * For links, the cache contain a link if it was reported by either netlink + * or udev. That means, a link object can be alive, even if it was already + * removed via netlink. + * + * This effectively merges the udev-device cache into the NMPCache. + */ + + NMDedupMultiIndex *multi_idx; + + /* an idx_type entry for each NMP_CACHE_ID_TYPE. Note that NONE (zero) + * is skipped, so the index is shifted by one: idx_type[cache_id_type - 1]. + * + * Don't bother, use _idx_type_get() instead! */ + DedupMultiIdxType idx_types[NMP_CACHE_ID_TYPE_MAX]; + + gboolean use_udev; +}; + +/*****************************************************************************/ + +int +nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b) +{ + nm_assert(!a || NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + nm_assert(!b || NM_IN_SET(b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, sa.sa_family); + switch (a->sa.sa_family) { + case AF_INET: + NM_CMP_DIRECT(ntohl(a->in.sin_addr.s_addr), ntohl(b->in.sin_addr.s_addr)); + NM_CMP_DIRECT(htons(a->in.sin_port), htons(b->in.sin_port)); + break; + case AF_INET6: + NM_CMP_DIRECT_IN6ADDR(&a->in6.sin6_addr, &b->in6.sin6_addr); + NM_CMP_DIRECT(htons(a->in6.sin6_port), htons(b->in6.sin6_port)); + NM_CMP_FIELD(a, b, in6.sin6_scope_id); + NM_CMP_FIELD(a, b, in6.sin6_flowinfo); + break; + } + return 0; +} + +void +nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h) +{ + if (!a) { + nm_hash_update_val(h, 1241364739u); + return; + } + + nm_assert(NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + + switch (a->sa.sa_family) { + case AF_INET: + nm_hash_update_vals(h, a->in.sin_family, a->in.sin_addr.s_addr, a->in.sin_port); + return; + case AF_INET6: + nm_hash_update_vals(h, + a->in6.sin6_family, + a->in6.sin6_addr, + a->in6.sin6_port, + a->in6.sin6_scope_id, + a->in6.sin6_flowinfo); + return; + default: + nm_hash_update_val(h, a->sa.sa_family); + return; + } +} + +/** + * nm_sock_addr_union_cpy: + * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, + * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of + * error). + * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in + * memory. If not %NULL, the buffer must be at least large enough to contain + * sa.sa_family, and then, depending on sa.sa_family, it must be large enough + * to hold struct sockaddr_in or struct sockaddr_in6. + * + * @dst will always be fully initialized (including setting all un-used bytes to zero). + */ +void +nm_sock_addr_union_cpy(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */) +{ + struct sockaddr sa; + gsize src_len; + + nm_assert(dst); + + *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + + if (!src) + return; + + memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family)); + + if (sa.sa_family == AF_INET) + src_len = sizeof(struct sockaddr_in); + else if (sa.sa_family == AF_INET6) + src_len = sizeof(struct sockaddr_in6); + else + return; + + memcpy(dst, src, src_len); + nm_assert(dst->sa.sa_family == sa.sa_family); +} + +/** + * nm_sock_addr_union_cpy_untrusted: + * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, + * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of + * error). + * @src: the source buffer with an sockaddr to copy. It may be unaligned in + * memory. + * @src_len: the length of @src in bytes. + * + * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6). + * If that's the case, then @src will be interpreted as such structure (unaligned), and + * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the + * struct will be copied. + * + * On any failure, @dst will be set to sa.sa_family AF_UNSPEC. + * @dst will always be fully initialized (including setting all un-used bytes to zero). + */ +void +nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */, + gsize src_len) +{ + int f_expected; + struct sockaddr sa; + + nm_assert(dst); + + *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + + if (src_len == sizeof(struct sockaddr_in)) + f_expected = AF_INET; + else if (src_len == sizeof(struct sockaddr_in6)) + f_expected = AF_INET6; + else + return; + + memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family)); + + if (sa.sa_family != f_expected) + return; + + memcpy(dst, src, src_len); + nm_assert(dst->sa.sa_family == sa.sa_family); +} + +const char * +nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len) +{ + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + char s_scope_id[40]; + + if (!nm_utils_to_string_buffer_init_null(sa, &buf, &len)) + return buf; + + /* maybe we should use getnameinfo(), but here implement it ourself. + * + * We want to see the actual bytes for debugging (as we understand them), + * and now what getnameinfo() makes of it. Also, it's simpler this way. */ + + switch (sa->sa.sa_family) { + case AF_INET: + g_snprintf(buf, + len, + "%s:%u", + _nm_utils_inet4_ntop(sa->in.sin_addr.s_addr, s_addr), + (guint) htons(sa->in.sin_port)); + break; + case AF_INET6: + g_snprintf(buf, + len, + "[%s%s]:%u", + _nm_utils_inet6_ntop(&sa->in6.sin6_addr, s_addr), + (sa->in6.sin6_scope_id != 0 + ? nm_sprintf_buf(s_scope_id, "%u", sa->in6.sin6_scope_id) + : ""), + (guint) htons(sa->in6.sin6_port)); + break; + case AF_UNSPEC: + g_snprintf(buf, len, "unspec"); + break; + default: + g_snprintf(buf, len, "{addr-family:%u}", (unsigned) sa->sa.sa_family); + break; + } + + return buf; +} + +/*****************************************************************************/ + +static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class; + +static void +_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + const NMPObject *o = (NMPObject *) obj; + + nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class); + nm_assert(NMP_OBJECT_GET_TYPE(o) != NMP_OBJECT_TYPE_UNKNOWN); + + nmp_object_id_hash_update(o, h); +} + +static gboolean +_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + const NMPObject *o_a = (NMPObject *) obj_a; + const NMPObject *o_b = (NMPObject *) obj_b; + + nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class); + nm_assert(NMP_OBJECT_GET_TYPE(o_a) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(NMP_OBJECT_GET_TYPE(o_b) != NMP_OBJECT_TYPE_UNKNOWN); + + return nmp_object_id_equal(o_a, o_b); +} + +static guint +_idx_obj_part(const DedupMultiIdxType *idx_type, + const NMPObject * obj_a, + const NMPObject * obj_b, + NMHashState * h) +{ + NMPObjectType obj_type; + + /* the hash/equals functions are strongly related. So, keep them + * side-by-side and do it all in _idx_obj_part(). */ + + nm_assert(idx_type); + nm_assert(idx_type->parent.klass == &_dedup_multi_idx_type_class); + nm_assert(obj_a); + nm_assert(NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_b || (NMP_OBJECT_GET_TYPE(obj_b) != NMP_OBJECT_TYPE_UNKNOWN)); + nm_assert(!h || !obj_b); + + switch (idx_type->cache_id_type) { + case NMP_CACHE_ID_TYPE_OBJECT_TYPE: + if (obj_b) + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b); + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a)); + } + return 1; + + case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME: + if (NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_LINK) { + /* first check, whether obj_a is suitable for this idx_type. + * If not, return 0 (which is correct for partitionable(), hash() and equal() + * functions. */ + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + /* we are in equal() mode. Compare obj_b with obj_a. */ + return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_LINK + && nm_streq(obj_a->link.name, obj_b->link.name); + } + if (h) { + nm_hash_update_val(h, idx_type->cache_id_type); + nm_hash_update_strarr(h, obj_a->link.name); + } + /* just return 1, to indicate that obj_a is partitionable by this idx_type. */ + return 1; + + case NMP_CACHE_ID_TYPE_DEFAULT_ROUTES: + if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE) + || !NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_a->ip_route) + || !nmp_object_is_visible(obj_a)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b) + && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_b->ip_route) + && nmp_object_is_visible(obj_b); + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a)); + } + return 1; + + case NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX: + if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER) + || !nmp_object_is_visible(obj_a)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + nm_assert(NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex > 0); + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b) + && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex + == NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_b)->ifindex + && nmp_object_is_visible(obj_b); + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->obj_with_ifindex.ifindex); + } + return 1; + + case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID: + obj_type = NMP_OBJECT_GET_TYPE(obj_a); + if (!NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) + || NMP_OBJECT_CAST_IP_ROUTE(obj_a)->ifindex <= 0) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return obj_type == NMP_OBJECT_GET_TYPE(obj_b) + && NMP_OBJECT_CAST_IP_ROUTE(obj_b)->ifindex > 0 + && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE + ? (nm_platform_ip4_route_cmp(&obj_a->ip4_route, + &obj_b->ip4_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) + == 0) + : (nm_platform_ip6_route_cmp(&obj_a->ip6_route, + &obj_b->ip6_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) + == 0)); + } + if (h) { + nm_hash_update_val(h, idx_type->cache_id_type); + if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) + nm_platform_ip4_route_hash_update(&obj_a->ip4_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + h); + else + nm_platform_ip6_route_hash_update(&obj_a->ip6_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + h); + } + return 1; + + case NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY: + obj_type = NMP_OBJECT_GET_TYPE(obj_a); + /* currently, only routing rules are supported for this cache-id-type. */ + if (obj_type != NMP_OBJECT_TYPE_ROUTING_RULE + || !NM_IN_SET(obj_a->routing_rule.addr_family, AF_INET, AF_INET6)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_ROUTING_RULE + && obj_a->routing_rule.addr_family == obj_b->routing_rule.addr_family; + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->routing_rule.addr_family); + } + return 1; + + case NMP_CACHE_ID_TYPE_NONE: + case __NMP_CACHE_ID_TYPE_MAX: + break; + } + nm_assert_not_reached(); + return 0; +} + +static gboolean +_idx_obj_partitionable(const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj) +{ + return _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, NULL) != 0; +} + +static void +_idx_obj_partition_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, h); +} + +static gboolean +_idx_obj_partition_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + return _idx_obj_part((DedupMultiIdxType *) idx_type, + (NMPObject *) obj_a, + (NMPObject *) obj_b, + NULL); +} + +static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class = { + .idx_obj_id_hash_update = _idx_obj_id_hash_update, + .idx_obj_id_equal = _idx_obj_id_equal, + .idx_obj_partitionable = _idx_obj_partitionable, + .idx_obj_partition_hash_update = _idx_obj_partition_hash_update, + .idx_obj_partition_equal = _idx_obj_partition_equal, +}; + +static void +_dedup_multi_idx_type_init(DedupMultiIdxType *idx_type, NMPCacheIdType cache_id_type) +{ + nm_dedup_multi_idx_type_init((NMDedupMultiIdxType *) idx_type, &_dedup_multi_idx_type_class); + idx_type->cache_id_type = cache_id_type; +} + +/*****************************************************************************/ + +static void +_vlan_xgress_qos_mappings_hash_update(guint n_map, const NMVlanQosMapping *map, NMHashState *h) +{ + /* ensure no padding. */ + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == 2 * sizeof(guint32)); + + nm_hash_update_val(h, n_map); + if (n_map) + nm_hash_update(h, map, n_map * sizeof(*map)); +} + +static int +_vlan_xgress_qos_mappings_cmp(guint n_map, + const NMVlanQosMapping *map1, + const NMVlanQosMapping *map2) +{ + guint i; + + for (i = 0; i < n_map; i++) { + if (map1[i].from != map2[i].from) + return map1[i].from < map2[i].from ? -1 : 1; + if (map1[i].to != map2[i].to) + return map1[i].to < map2[i].to ? -1 : 1; + } + return 0; +} + +static void +_vlan_xgress_qos_mappings_cpy(guint * dst_n_map, + NMVlanQosMapping ** dst_map, + guint src_n_map, + const NMVlanQosMapping *src_map) +{ + if (src_n_map == 0) { + nm_clear_g_free(dst_map); + *dst_n_map = 0; + } else if (src_n_map != *dst_n_map + || _vlan_xgress_qos_mappings_cmp(src_n_map, *dst_map, src_map) != 0) { + nm_clear_g_free(dst_map); + *dst_n_map = src_n_map; + if (src_n_map > 0) + *dst_map = nm_memdup(src_map, sizeof(*src_map) * src_n_map); + } +} + +/*****************************************************************************/ + +static void +_wireguard_allowed_ip_hash_update(const NMPWireGuardAllowedIP *ip, NMHashState *h) +{ + nm_hash_update_vals(h, ip->family, ip->mask); + + if (ip->family == AF_INET) + nm_hash_update_val(h, ip->addr.addr4); + else if (ip->family == AF_INET6) + nm_hash_update_val(h, ip->addr.addr6); +} + +static int +_wireguard_allowed_ip_cmp(const NMPWireGuardAllowedIP *a, const NMPWireGuardAllowedIP *b) +{ + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, family); + NM_CMP_FIELD(a, b, mask); + + if (a->family == AF_INET) + NM_CMP_FIELD(a, b, addr.addr4); + else if (a->family == AF_INET6) + NM_CMP_FIELD_IN6ADDR(a, b, addr.addr6); + + return 0; +} + +static void +_wireguard_peer_hash_update(const NMPWireGuardPeer *peer, NMHashState *h) +{ + guint i; + + nm_hash_update(h, peer->public_key, sizeof(peer->public_key)); + nm_hash_update(h, peer->preshared_key, sizeof(peer->preshared_key)); + nm_hash_update_vals(h, + peer->persistent_keepalive_interval, + peer->allowed_ips_len, + peer->rx_bytes, + peer->tx_bytes, + peer->last_handshake_time.tv_sec, + peer->last_handshake_time.tv_nsec); + + nm_sock_addr_union_hash_update(&peer->endpoint, h); + + for (i = 0; i < peer->allowed_ips_len; i++) + _wireguard_allowed_ip_hash_update(&peer->allowed_ips[i], h); +} + +static int +_wireguard_peer_cmp(const NMPWireGuardPeer *a, const NMPWireGuardPeer *b) +{ + guint i; + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, last_handshake_time.tv_sec); + NM_CMP_FIELD(a, b, last_handshake_time.tv_nsec); + NM_CMP_FIELD(a, b, rx_bytes); + NM_CMP_FIELD(a, b, tx_bytes); + NM_CMP_FIELD(a, b, allowed_ips_len); + NM_CMP_FIELD(a, b, persistent_keepalive_interval); + NM_CMP_FIELD(a, b, endpoint.sa.sa_family); + NM_CMP_FIELD_MEMCMP(a, b, public_key); + NM_CMP_FIELD_MEMCMP(a, b, preshared_key); + + NM_CMP_RETURN(nm_sock_addr_union_cmp(&a->endpoint, &b->endpoint)); + + for (i = 0; i < a->allowed_ips_len; i++) { + NM_CMP_RETURN(_wireguard_allowed_ip_cmp(&a->allowed_ips[i], &b->allowed_ips[i])); + } + + return 0; +} + +/*****************************************************************************/ + +static const char * +_link_get_driver(struct udev_device *udevice, const char *kind, int ifindex) +{ + const char *driver = NULL; + + nm_assert(kind == g_intern_string(kind)); + + if (udevice) { + driver = nmp_utils_udev_get_driver(udevice); + if (driver) + return driver; + } + + if (kind) + return kind; + + if (ifindex > 0) { + NMPUtilsEthtoolDriverInfo driver_info; + + if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (driver_info.driver[0]) + return g_intern_string(driver_info.driver); + } + } + + return "unknown"; +} + +void +_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev) +{ + const char *driver = NULL; + gboolean initialized = FALSE; + NMPObject * obj; + + nm_assert(obj_orig || *obj_new); + nm_assert(obj_new); + nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK); + nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK); + + obj = *obj_new ?: obj_orig; + + /* The link contains internal fields that are combined by + * properties from netlink and udev. Update those properties */ + + /* When a link is not in netlink, its udev fields don't matter. */ + if (obj->_link.netlink.is_in_netlink) { + driver = _link_get_driver(obj->_link.udev.device, obj->link.kind, obj->link.ifindex); + if (obj->_link.udev.device) + initialized = TRUE; + else if (!use_udev) { + /* If we don't use udev, we immediately mark the link as initialized. + * + * For that, we consult @use_udev argument, that is cached via + * nmp_cache_use_udev_get(). It is on purpose not to test + * for a writable /sys on every call. A minor reason for that is + * performance, but the real reason is reproducibility. + * */ + initialized = TRUE; + } + } + + if (nm_streq0(obj->link.driver, driver) && obj->link.initialized == initialized) + return; + + if (!*obj_new) + obj = *obj_new = nmp_object_clone(obj, FALSE); + + obj->link.driver = driver; + obj->link.initialized = initialized; +} + +static void +_nmp_object_fixup_link_master_connected(NMPObject ** obj_new, + NMPObject * obj_orig, + const NMPCache *cache) +{ + NMPObject *obj; + + nm_assert(obj_orig || *obj_new); + nm_assert(obj_new); + nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK); + nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK); + + obj = *obj_new ?: obj_orig; + + if (nmp_cache_link_connected_needs_toggle(cache, obj, NULL, NULL)) { + if (!*obj_new) + obj = *obj_new = nmp_object_clone(obj, FALSE); + obj->link.connected = !obj->link.connected; + } +} + +/*****************************************************************************/ + +static void +_vt_cmd_obj_dispose_link(NMPObject *obj) +{ + if (obj->_link.udev.device) { + udev_device_unref(obj->_link.udev.device); + obj->_link.udev.device = NULL; + } + g_clear_object(&obj->_link.ext_data); + nmp_object_unref(obj->_link.netlink.lnk); +} + +static void +_vt_cmd_obj_dispose_lnk_vlan(NMPObject *obj) +{ + g_free((gpointer) obj->_lnk_vlan.ingress_qos_map); + g_free((gpointer) obj->_lnk_vlan.egress_qos_map); +} + +static void +_wireguard_clear(NMPObjectLnkWireGuard *lnk) +{ + guint i; + + nm_explicit_bzero(lnk->_public.private_key, sizeof(lnk->_public.private_key)); + for (i = 0; i < lnk->peers_len; i++) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i]; + + nm_explicit_bzero(peer->preshared_key, sizeof(peer->preshared_key)); + } + g_free((gpointer) lnk->peers); + g_free((gpointer) lnk->_allowed_ips_buf); +} + +static void +_vt_cmd_obj_dispose_lnk_wireguard(NMPObject *obj) +{ + _wireguard_clear(&obj->_lnk_wireguard); +} + +static NMPObject * +_nmp_object_new_from_class(const NMPClass *klass) +{ + NMPObject *obj; + + nm_assert(klass); + nm_assert(klass->sizeof_data > 0); + nm_assert(klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data); + + obj = g_slice_alloc0(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object)); + obj->_class = klass; + obj->parent._ref_count = 1; + return obj; +} + +NMPObject * +nmp_object_new(NMPObjectType obj_type, gconstpointer plobj) +{ + const NMPClass *klass = nmp_class_from_type(obj_type); + NMPObject * obj; + + obj = _nmp_object_new_from_class(klass); + if (plobj) + memcpy(&obj->object, plobj, klass->sizeof_public); + return obj; +} + +NMPObject * +nmp_object_new_link(int ifindex) +{ + NMPObject *obj; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL); + obj->link.ifindex = ifindex; + return obj; +} + +/*****************************************************************************/ + +static void +_nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass) +{ + nm_assert(obj); + nm_assert(klass); + + *obj = (NMPObject){ + .parent = + { + .klass = (const NMDedupMultiObjClass *) klass, + ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, + }, + }; +} + +static NMPObject * +_nmp_object_stackinit_from_type(NMPObject *obj, NMPObjectType obj_type) +{ + const NMPClass *klass; + + nm_assert(obj); + klass = nmp_class_from_type(obj_type); + nm_assert(klass); + + *obj = (NMPObject){ + .parent = + { + .klass = (const NMDedupMultiObjClass *) klass, + ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, + }, + }; + return obj; +} + +const NMPObject * +nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj) +{ + const NMPClass *klass = nmp_class_from_type(obj_type); + + _nmp_object_stackinit_from_class(obj, klass); + if (plobj) + memcpy(&obj->object, plobj, klass->sizeof_public); + return obj; +} + +const NMPObject * +nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src) +{ + const NMPClass *klass; + + nm_assert(NMP_OBJECT_IS_VALID(src)); + nm_assert(obj); + + klass = NMP_OBJECT_GET_CLASS(src); + _nmp_object_stackinit_from_class(obj, klass); + if (klass->cmd_plobj_id_copy) + klass->cmd_plobj_id_copy(&obj->object, &src->object); + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_link(NMPObject *obj, int ifindex) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_LINK); + obj->link.ifindex = ifindex; + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_ip4_address(NMPObject *obj, + int ifindex, + guint32 address, + guint8 plen, + guint32 peer_address) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP4_ADDRESS); + obj->ip4_address.ifindex = ifindex; + obj->ip4_address.address = address; + obj->ip4_address.plen = plen; + obj->ip4_address.peer_address = peer_address; + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP6_ADDRESS); + obj->ip4_address.ifindex = ifindex; + if (address) + obj->ip6_address.address = *address; + return obj; +} + +/*****************************************************************************/ + +const char * +nmp_object_to_string(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + + if (!nm_utils_to_string_buffer_init_null(obj, &buf, &buf_size)) + return buf; + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + + klass = NMP_OBJECT_GET_CLASS(obj); + + if (klass->cmd_obj_to_string) + return klass->cmd_obj_to_string(obj, to_string_mode, buf, buf_size); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + if (!klass->cmd_plobj_to_string_id) { + g_snprintf(buf, buf_size, "%p", obj); + return buf; + } + return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size); + case NMP_OBJECT_TO_STRING_ALL: + g_snprintf( + buf, + buf_size, + "[%s,%p,%u,%calive,%cvisible; %s]", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf2, sizeof(buf2))); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_link(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj); + char * b = buf; + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size); + case NMP_OBJECT_TO_STRING_ALL: + nm_utils_strbuf_append(&b, + &buf_size, + "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + obj->_link.netlink.is_in_netlink ? '+' : '-', + obj->_link.udev.device); + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + if (obj->_link.netlink.lnk) { + nm_utils_strbuf_append_str(&b, &buf_size, "; "); + nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + } + nm_utils_strbuf_append_c(&b, &buf_size, ']'); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size); + if (obj->_link.netlink.lnk) { + nm_utils_strbuf_seek_end(&b, &buf_size); + nm_utils_strbuf_append_str(&b, &buf_size, "; "); + nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, buf_size); + } + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_lnk_vlan(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + char * b; + gsize l; + + klass = NMP_OBJECT_GET_CLASS(obj); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + g_snprintf(buf, buf_size, "%p", obj); + return buf; + case NMP_OBJECT_TO_STRING_ALL: + + g_snprintf(buf, + buf_size, + "[%s,%p,%u,%calive,%cvisible; %s]", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2))); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + + b = buf; + l = strlen(b); + b += l; + buf_size -= l; + + if (obj->_lnk_vlan.n_ingress_qos_map) { + nm_platform_vlan_qos_mapping_to_string(" ingress-qos-map", + obj->_lnk_vlan.ingress_qos_map, + obj->_lnk_vlan.n_ingress_qos_map, + b, + buf_size); + l = strlen(b); + b += l; + buf_size -= l; + } + if (obj->_lnk_vlan.n_egress_qos_map) { + nm_platform_vlan_qos_mapping_to_string(" egress-qos-map", + obj->_lnk_vlan.egress_qos_map, + obj->_lnk_vlan.n_egress_qos_map, + b, + buf_size); + l = strlen(b); + b += l; + buf_size -= l; + } + + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_lnk_wireguard(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + char * b; + guint i; + + klass = NMP_OBJECT_GET_CLASS(obj); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + g_snprintf(buf, buf_size, "%p", obj); + return buf; + case NMP_OBJECT_TO_STRING_ALL: + b = buf; + + nm_utils_strbuf_append( + &b, + &buf_size, + "[%s,%p,%u,%calive,%cvisible; %s" + "%s", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)), + obj->_lnk_wireguard.peers_len > 0 ? " peers {" : ""); + + for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) { + const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i]; + + nm_utils_strbuf_append_str(&b, &buf_size, " { "); + nm_platform_wireguard_peer_to_string(peer, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + nm_utils_strbuf_append_str(&b, &buf_size, " }"); + } + if (obj->_lnk_wireguard.peers_len) + nm_utils_strbuf_append_str(&b, &buf_size, " }"); + + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +#define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \ + static const char *_vt_cmd_plobj_to_string_id_##type(const NMPlatformObject *_obj, \ + char * buf, \ + gsize buf_len) \ + { \ + plat_type *const obj = (plat_type *) _obj; \ + _nm_unused char buf1[NM_UTILS_INET_ADDRSTRLEN]; \ + _nm_unused char buf2[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + g_snprintf(buf, buf_len, __VA_ARGS__); \ + return buf; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_to_string_id(link, NMPlatformLink, "%d", obj->ifindex); + +_vt_cmd_plobj_to_string_id(ip4_address, + NMPlatformIP4Address, + "%d: %s/%d%s%s", + obj->ifindex, + _nm_utils_inet4_ntop(obj->address, buf1), + obj->plen, + obj->peer_address != obj->address ? "," : "", + obj->peer_address != obj->address ? _nm_utils_inet4_ntop( + nm_utils_ip4_address_clear_host_address(obj->peer_address, + obj->plen), + buf2) + : ""); + +_vt_cmd_plobj_to_string_id(ip6_address, + NMPlatformIP6Address, + "%d: %s", + obj->ifindex, + _nm_utils_inet6_ntop(&obj->address, buf1)); + +_vt_cmd_plobj_to_string_id(qdisc, NMPlatformQdisc, "%d: %d", obj->ifindex, obj->parent); + +_vt_cmd_plobj_to_string_id(tfilter, NMPlatformTfilter, "%d: %d", obj->ifindex, obj->parent); + +void +nmp_object_hash_update(const NMPObject *obj, NMHashState *h) +{ + const NMPClass *klass; + + g_return_if_fail(NMP_OBJECT_IS_VALID(obj)); + + klass = NMP_OBJECT_GET_CLASS(obj); + + nm_hash_update_val(h, klass->obj_type); + if (klass->cmd_obj_hash_update) + klass->cmd_obj_hash_update(obj, h); + else if (klass->cmd_plobj_hash_update) + klass->cmd_plobj_hash_update(&obj->object, h); + else + nm_hash_update_val(h, obj); +} + +static void +_vt_cmd_obj_hash_update_link(const NMPObject *obj, NMHashState *h) +{ + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK); + + nm_platform_link_hash_update(&obj->link, h); + nm_hash_update_vals(h, + obj->_link.netlink.is_in_netlink, + obj->_link.wireguard_family_id, + obj->_link.udev.device); + if (obj->_link.netlink.lnk) + nmp_object_hash_update(obj->_link.netlink.lnk, h); +} + +static void +_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, NMHashState *h) +{ + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_VLAN); + + nm_platform_lnk_vlan_hash_update(&obj->lnk_vlan, h); + _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_ingress_qos_map, + obj->_lnk_vlan.ingress_qos_map, + h); + _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_egress_qos_map, + obj->_lnk_vlan.egress_qos_map, + h); +} + +static void +_vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, NMHashState *h) +{ + guint i; + + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD); + + nm_platform_lnk_wireguard_hash_update(&obj->lnk_wireguard, h); + + nm_hash_update_val(h, obj->_lnk_wireguard.peers_len); + for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) + _wireguard_peer_hash_update(&obj->_lnk_wireguard.peers[i], h); +} + +int +nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass1, *klass2; + + NM_CMP_SELF(obj1, obj2); + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), -1); + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), 1); + + klass1 = NMP_OBJECT_GET_CLASS(obj1); + klass2 = NMP_OBJECT_GET_CLASS(obj2); + + if (klass1 != klass2) { + nm_assert(klass1->obj_type != klass2->obj_type); + return klass1->obj_type < klass2->obj_type ? -1 : 1; + } + + if (klass1->cmd_obj_cmp) + return klass1->cmd_obj_cmp(obj1, obj2); + return klass1->cmd_plobj_cmp(&obj1->object, &obj2->object); +} + +static int +_vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2) +{ + NM_CMP_RETURN(nm_platform_link_cmp(&obj1->link, &obj2->link)); + NM_CMP_DIRECT(obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink); + NM_CMP_RETURN(nmp_object_cmp(obj1->_link.netlink.lnk, obj2->_link.netlink.lnk)); + NM_CMP_DIRECT(obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id); + + if (obj1->_link.udev.device != obj2->_link.udev.device) { + if (!obj1->_link.udev.device) + return -1; + if (!obj2->_link.udev.device) + return 1; + + /* Only compare based on pointer values. That is ugly because it's not a + * stable sort order. + * + * Have this check as very last. */ + return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1; + } + + return 0; +} + +static int +_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2) +{ + int c; + + c = nm_platform_lnk_vlan_cmp(&obj1->lnk_vlan, &obj2->lnk_vlan); + if (c) + return c; + + if (obj1->_lnk_vlan.n_ingress_qos_map != obj2->_lnk_vlan.n_ingress_qos_map) + return obj1->_lnk_vlan.n_ingress_qos_map < obj2->_lnk_vlan.n_ingress_qos_map ? -1 : 1; + if (obj1->_lnk_vlan.n_egress_qos_map != obj2->_lnk_vlan.n_egress_qos_map) + return obj1->_lnk_vlan.n_egress_qos_map < obj2->_lnk_vlan.n_egress_qos_map ? -1 : 1; + + c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_ingress_qos_map, + obj1->_lnk_vlan.ingress_qos_map, + obj2->_lnk_vlan.ingress_qos_map); + if (c) + return c; + c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_egress_qos_map, + obj1->_lnk_vlan.egress_qos_map, + obj2->_lnk_vlan.egress_qos_map); + + return c; +} + +static int +_vt_cmd_obj_cmp_lnk_wireguard(const NMPObject *obj1, const NMPObject *obj2) +{ + guint i; + + NM_CMP_RETURN(nm_platform_lnk_wireguard_cmp(&obj1->lnk_wireguard, &obj2->lnk_wireguard)); + + NM_CMP_FIELD(obj1, obj2, _lnk_wireguard.peers_len); + + for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++) + NM_CMP_RETURN( + _wireguard_peer_cmp(&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i])); + + return 0; +} + +/* @src is a const object, which is not entirely correct for link types, where + * we increase the ref count for src->_link.udev.device. + * Hence, nmp_object_copy() can violate the const promise of @src. + * */ +void +nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only) +{ + g_return_if_fail(NMP_OBJECT_IS_VALID(dst)); + g_return_if_fail(NMP_OBJECT_IS_VALID(src)); + g_return_if_fail(!NMP_OBJECT_IS_STACKINIT(dst)); + + if (src != dst) { + const NMPClass *klass = NMP_OBJECT_GET_CLASS(dst); + + g_return_if_fail(klass == NMP_OBJECT_GET_CLASS(src)); + + if (id_only) { + if (klass->cmd_plobj_id_copy) + klass->cmd_plobj_id_copy(&dst->object, &src->object); + } else if (klass->cmd_obj_copy) + klass->cmd_obj_copy(dst, src); + else + memcpy(&dst->object, &src->object, klass->sizeof_data); + } +} + +static void +_vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src) +{ + if (dst->_link.udev.device != src->_link.udev.device) { + if (src->_link.udev.device) + udev_device_ref(src->_link.udev.device); + if (dst->_link.udev.device) + udev_device_unref(dst->_link.udev.device); + dst->_link.udev.device = src->_link.udev.device; + } + if (dst->_link.netlink.lnk != src->_link.netlink.lnk) { + if (src->_link.netlink.lnk) + nmp_object_ref(src->_link.netlink.lnk); + if (dst->_link.netlink.lnk) + nmp_object_unref(dst->_link.netlink.lnk); + dst->_link.netlink.lnk = src->_link.netlink.lnk; + } + if (dst->_link.ext_data != src->_link.ext_data) { + if (dst->_link.ext_data) + g_clear_object(&dst->_link.ext_data); + if (src->_link.ext_data) + dst->_link.ext_data = g_object_ref(src->_link.ext_data); + } + dst->_link = src->_link; +} + +static void +_vt_cmd_obj_copy_lnk_vlan(NMPObject *dst, const NMPObject *src) +{ + dst->lnk_vlan = src->lnk_vlan; + _vlan_xgress_qos_mappings_cpy( + &dst->_lnk_vlan.n_ingress_qos_map, + NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map), + src->_lnk_vlan.n_ingress_qos_map, + src->_lnk_vlan.ingress_qos_map); + _vlan_xgress_qos_mappings_cpy(&dst->_lnk_vlan.n_egress_qos_map, + NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map), + src->_lnk_vlan.n_egress_qos_map, + src->_lnk_vlan.egress_qos_map); +} + +static void +_vt_cmd_obj_copy_lnk_wireguard(NMPObject *dst, const NMPObject *src) +{ + guint i; + + nm_assert(dst != src); + + _wireguard_clear(&dst->_lnk_wireguard); + + dst->_lnk_wireguard = src->_lnk_wireguard; + + dst->_lnk_wireguard.peers = nm_memdup(dst->_lnk_wireguard.peers, + sizeof(NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len); + dst->_lnk_wireguard._allowed_ips_buf = + nm_memdup(dst->_lnk_wireguard._allowed_ips_buf, + sizeof(NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len); + + /* all the peers' pointers point into the buffer. They need to be readjusted. */ + for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i]; + + if (peer->allowed_ips_len == 0) { + nm_assert(!peer->allowed_ips); + continue; + } + nm_assert(dst->_lnk_wireguard._allowed_ips_buf_len > 0); + nm_assert(src->_lnk_wireguard._allowed_ips_buf); + nm_assert(peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf); + nm_assert( + &peer->allowed_ips[peer->allowed_ips_len] + <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]); + + peer->allowed_ips = + &dst->_lnk_wireguard + ._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf]; + } + + nm_assert(nmp_object_equal(src, dst)); +} + +#define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \ + static void _vt_cmd_plobj_id_copy_##type(NMPlatformObject *_dst, const NMPlatformObject *_src) \ + { \ + plat_type *const dst = (plat_type *) _dst; \ + const plat_type *const src = (const plat_type *) _src; \ + { \ + cmd \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_copy(link, NMPlatformLink, { dst->ifindex = src->ifindex; }); + +_vt_cmd_plobj_id_copy(ip4_address, NMPlatformIP4Address, { + dst->ifindex = src->ifindex; + dst->plen = src->plen; + dst->address = src->address; + dst->peer_address = src->peer_address; +}); + +_vt_cmd_plobj_id_copy(ip6_address, NMPlatformIP6Address, { + dst->ifindex = src->ifindex; + dst->address = src->address; +}); + +_vt_cmd_plobj_id_copy(ip4_route, NMPlatformIP4Route, { + *dst = *src; + nm_assert(nm_platform_ip4_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); +}); + +_vt_cmd_plobj_id_copy(ip6_route, NMPlatformIP6Route, { + *dst = *src; + nm_assert(nm_platform_ip6_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); +}); + +_vt_cmd_plobj_id_copy(routing_rule, NMPlatformRoutingRule, { + *dst = *src; + nm_assert(nm_platform_routing_rule_cmp(dst, src, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0); +}); + +/* Uses internally nmp_object_copy(), hence it also violates the const + * promise for @obj. + * */ +NMPObject * +nmp_object_clone(const NMPObject *obj, gboolean id_only) +{ + NMPObject *dst; + + if (!obj) + return NULL; + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + + dst = _nmp_object_new_from_class(NMP_OBJECT_GET_CLASS(obj)); + nmp_object_copy(dst, obj, id_only); + return dst; +} + +int +nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass, *klass2; + + NM_CMP_SELF(obj1, obj2); + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), FALSE); + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), FALSE); + + klass = NMP_OBJECT_GET_CLASS(obj1); + nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp); + + klass2 = NMP_OBJECT_GET_CLASS(obj2); + nm_assert(klass); + if (klass != klass2) { + nm_assert(klass2); + NM_CMP_DIRECT(klass->obj_type, klass2->obj_type); + /* resort to pointer comparison */ + NM_CMP_DIRECT_PTR(klass, klass2); + return 0; + } + + if (!klass->cmd_plobj_id_cmp) { + /* the klass doesn't implement ID cmp(). That means, different objects + * never compare equal, but the cmp() according to their pointer value. */ + NM_CMP_DIRECT_PTR(obj1, obj2); + return 0; + } + + return klass->cmd_plobj_id_cmp(&obj1->object, &obj2->object); +} + +#define _vt_cmd_plobj_id_cmp(type, plat_type, cmd) \ + static int _vt_cmd_plobj_id_cmp_##type(const NMPlatformObject *_obj1, \ + const NMPlatformObject *_obj2) \ + { \ + const plat_type *const obj1 = (const plat_type *) _obj1; \ + const plat_type *const obj2 = (const plat_type *) _obj2; \ + \ + NM_CMP_SELF(obj1, obj2); \ + { \ + cmd; \ + } \ + return 0; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_cmp(link, NMPlatformLink, { NM_CMP_FIELD(obj1, obj2, ifindex); }); + +_vt_cmd_plobj_id_cmp(ip4_address, NMPlatformIP4Address, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, plen); + NM_CMP_FIELD(obj1, obj2, address); + /* for IPv4 addresses, you can add the same local address with differing peer-address + * (IFA_ADDRESS), provided that their net-part differs. */ + NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(obj1->peer_address, obj2->peer_address, obj1->plen); +}); + +_vt_cmd_plobj_id_cmp(ip6_address, NMPlatformIP6Address, { + NM_CMP_FIELD(obj1, obj2, ifindex); + /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ + NM_CMP_FIELD_IN6ADDR(obj1, obj2, address); +}); + +_vt_cmd_plobj_id_cmp(qdisc, NMPlatformQdisc, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, parent); +}); + +_vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, handle); +}); + +static int +_vt_cmd_plobj_id_cmp_ip4_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip4_route_cmp((NMPlatformIP4Route *) obj1, + (NMPlatformIP4Route *) obj2, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID); +} + +static int +_vt_cmd_plobj_id_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip6_route_cmp((NMPlatformIP6Route *) obj1, + (NMPlatformIP6Route *) obj2, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID); +} + +static int +_vt_cmd_plobj_id_cmp_routing_rule(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_routing_rule_cmp((NMPlatformRoutingRule *) obj1, + (NMPlatformRoutingRule *) obj2, + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID); +} + +void +nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h) +{ + const NMPClass *klass; + + g_return_if_fail(NMP_OBJECT_IS_VALID(obj)); + + klass = NMP_OBJECT_GET_CLASS(obj); + + nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp); + + if (!klass->cmd_plobj_id_hash_update) { + /* The klass doesn't implement ID compare. It means, to use pointer + * equality. */ + nm_hash_update_val(h, obj); + return; + } + + nm_hash_update_val(h, klass->obj_type); + klass->cmd_plobj_id_hash_update(&obj->object, h); +} + +guint +nmp_object_id_hash(const NMPObject *obj) +{ + NMHashState h; + + if (!obj) + return nm_hash_static(914932607u); + + nm_hash_init(&h, 914932607u); + nmp_object_id_hash_update(obj, &h); + return nm_hash_complete(&h); +} + +#define _vt_cmd_plobj_id_hash_update(type, plat_type, cmd) \ + static void _vt_cmd_plobj_id_hash_update_##type(const NMPlatformObject *_obj, NMHashState *h) \ + { \ + const plat_type *const obj = (const plat_type *) _obj; \ + { \ + cmd; \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_hash_update(link, NMPlatformLink, { nm_hash_update_val(h, obj->ifindex); }); + +_vt_cmd_plobj_id_hash_update(ip4_address, NMPlatformIP4Address, { + nm_hash_update_vals( + h, + obj->ifindex, + obj->plen, + obj->address, + /* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */ + nm_utils_ip4_address_clear_host_address(obj->peer_address, obj->plen)); +}); + +_vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, { + nm_hash_update_vals( + h, + obj->ifindex, + /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ + obj->address); +}); + +_vt_cmd_plobj_id_hash_update(ip4_route, NMPlatformIP4Route, { + nm_platform_ip4_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, { + nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(routing_rule, NMPlatformRoutingRule, { + nm_platform_routing_rule_hash_update(obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(qdisc, NMPlatformQdisc, { + nm_hash_update_vals(h, obj->ifindex, obj->parent); +}); + +_vt_cmd_plobj_id_hash_update(tfilter, NMPlatformTfilter, { + nm_hash_update_vals(h, obj->ifindex, obj->handle); +}); + +static void +_vt_cmd_plobj_hash_update_ip4_route(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip4_route_hash_update((const NMPlatformIP4Route *) obj, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + h); +} + +static void +_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + h); +} + +static void +_vt_cmd_plobj_hash_update_routing_rule(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_routing_rule_hash_update((const NMPlatformRoutingRule *) obj, + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, + h); +} + +guint +nmp_object_indirect_id_hash(gconstpointer a) +{ + const NMPObject *const *p_obj = a; + + return nmp_object_id_hash(*p_obj); +} + +gboolean +nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b) +{ + const NMPObject *const *p_obj_a = a; + const NMPObject *const *p_obj_b = b; + + return nmp_object_id_equal(*p_obj_a, *p_obj_b); +} + +/*****************************************************************************/ + +gboolean +nmp_object_is_alive(const NMPObject *obj) +{ + const NMPClass *klass; + + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + klass = NMP_OBJECT_GET_CLASS(obj); + return !klass->cmd_obj_is_alive || klass->cmd_obj_is_alive(obj); +} + +static gboolean +_vt_cmd_obj_is_alive_link(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_LINK(obj)->ifindex > 0 + && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device); +} + +static gboolean +_vt_cmd_obj_is_alive_ipx_address(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_IP_ADDRESS(obj)->ifindex > 0; +} + +static gboolean +_vt_cmd_obj_is_alive_ipx_route(const NMPObject *obj) +{ + /* We want to ignore routes that are RTM_F_CLONED but we still + * let nmp_object_from_nl() create such route objects, instead of + * returning NULL right away. + * + * The idea is, that if we have the same route (according to its id) + * in the cache with !RTM_F_CLONED, an update that changes the route + * to be RTM_F_CLONED must remove the instance. + * + * If nmp_object_from_nl() would just return NULL, we couldn't look + * into the cache to see if it contains a route that now disappears + * (because it changed to be cloned). + * + * Instead we create a dead object, and nmp_cache_update_netlink() + * will remove the old version of the update. + **/ + return NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex > 0 + && !NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED); +} + +static gboolean +_vt_cmd_obj_is_alive_routing_rule(const NMPObject *obj) +{ + return NM_IN_SET(obj->routing_rule.addr_family, AF_INET, AF_INET6); +} + +static gboolean +_vt_cmd_obj_is_alive_qdisc(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_QDISC(obj)->ifindex > 0; +} + +static gboolean +_vt_cmd_obj_is_alive_tfilter(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_TFILTER(obj)->ifindex > 0; +} + +gboolean +nmp_object_is_visible(const NMPObject *obj) +{ + const NMPClass *klass; + + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + klass = NMP_OBJECT_GET_CLASS(obj); + + /* a dead object is never visible. */ + if (klass->cmd_obj_is_alive && !klass->cmd_obj_is_alive(obj)) + return FALSE; + + return !klass->cmd_obj_is_visible || klass->cmd_obj_is_visible(obj); +} + +static gboolean +_vt_cmd_obj_is_visible_link(const NMPObject *obj) +{ + return obj->_link.netlink.is_in_netlink && obj->link.name[0]; +} + +/*****************************************************************************/ + +static const guint8 _supported_cache_ids_object[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + 0, +}; + +static const guint8 _supported_cache_ids_link[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, + 0, +}; + +static const guint8 _supported_cache_ids_ipx_address[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + 0, +}; + +static const guint8 _supported_cache_ids_ipx_route[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + NMP_CACHE_ID_TYPE_DEFAULT_ROUTES, + NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, + 0, +}; + +static const guint8 _supported_cache_ids_routing_rules[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY, + 0, +}; + +/*****************************************************************************/ + +static void +_vt_dedup_obj_destroy(NMDedupMultiObj *obj) +{ + NMPObject * o = (NMPObject *) obj; + const NMPClass *klass; + + nm_assert(o->parent._ref_count == 0); + nm_assert(!o->parent._multi_idx); + + klass = o->_class; + if (klass->cmd_obj_dispose) + klass->cmd_obj_dispose(o); + g_slice_free1(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object), o); +} + +static const NMDedupMultiObj * +_vt_dedup_obj_clone(const NMDedupMultiObj *obj) +{ + return (const NMDedupMultiObj *) nmp_object_clone((const NMPObject *) obj, FALSE); +} + +#define DEDUP_MULTI_OBJ_CLASS_INIT() \ + { \ + .obj_clone = _vt_dedup_obj_clone, .obj_destroy = _vt_dedup_obj_destroy, \ + .obj_full_hash_update = \ + (void (*)(const NMDedupMultiObj *obj, NMHashState *h)) nmp_object_hash_update, \ + .obj_full_equal = (gboolean(*)(const NMDedupMultiObj *obj_a, \ + const NMDedupMultiObj *obj_b)) nmp_object_equal, \ + } + +/*****************************************************************************/ + +static NMDedupMultiIdxType * +_idx_type_get(const NMPCache *cache, NMPCacheIdType cache_id_type) +{ + nm_assert(cache); + nm_assert(cache_id_type > NMP_CACHE_ID_TYPE_NONE); + nm_assert(cache_id_type <= NMP_CACHE_ID_TYPE_MAX); + nm_assert((int) cache_id_type - 1 >= 0); + nm_assert((int) cache_id_type - 1 < G_N_ELEMENTS(cache->idx_types)); + + return (NMDedupMultiIdxType *) &cache->idx_types[cache_id_type - 1]; +} + +gboolean +nmp_cache_use_udev_get(const NMPCache *cache) +{ + g_return_val_if_fail(cache, TRUE); + + return cache->use_udev; +} + +/*****************************************************************************/ + +gboolean +nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave) +{ + nm_assert(NMP_OBJECT_GET_TYPE(slave) == NMP_OBJECT_TYPE_LINK); + + return ifindex_master > 0 && slave->link.master == ifindex_master && slave->link.connected + && nmp_object_is_visible(slave); +} + +/** + * nmp_cache_link_connected_needs_toggle: + * @cache: the platform cache + * @master: the link object, that is checked whether its connected property + * needs to be toggled. + * @potential_slave: (allow-none): an additional link object that is treated + * as if it was inside @cache. If given, it shaddows a link in the cache + * with the same ifindex. + * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave + * is not in the cache. + * + * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported + * from netlink) and master->link.connected. For bond and bridge master, kernel reports + * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead + * a combined @connected flag that shows masters without slaves as down. + * + * Check if the connected flag of @master should be toggled according to the content + * of @cache (including @potential_slave). + * + * Returns: %TRUE, if @master->link.connected should be flipped/toggled. + **/ +gboolean +nmp_cache_link_connected_needs_toggle(const NMPCache * cache, + const NMPObject *master, + const NMPObject *potential_slave, + const NMPObject *ignore_slave) +{ + gboolean is_lower_up = FALSE; + + if (!master || NMP_OBJECT_GET_TYPE(master) != NMP_OBJECT_TYPE_LINK || master->link.ifindex <= 0 + || !nmp_object_is_visible(master) + || !NM_IN_SET(master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND)) + return FALSE; + + /* if native IFF_LOWER_UP is down, link.connected must also be down + * regardless of the slaves. */ + if (!NM_FLAGS_HAS(master->link.n_ifi_flags, IFF_LOWER_UP)) + return !!master->link.connected; + + if (potential_slave && NMP_OBJECT_GET_TYPE(potential_slave) != NMP_OBJECT_TYPE_LINK) + potential_slave = NULL; + + if (potential_slave + && nmp_cache_link_connected_for_slave(master->link.ifindex, potential_slave)) + is_lower_up = TRUE; + else { + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink *link = NULL; + + nmp_cache_iter_for_each_link ( + &iter, + nmp_cache_lookup(cache, nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK)), + &link) { + const NMPObject *obj = NMP_OBJECT_UP_CAST((NMPlatformObject *) link); + + if ((!potential_slave || potential_slave->link.ifindex != link->ifindex) + && ignore_slave != obj + && nmp_cache_link_connected_for_slave(master->link.ifindex, obj)) { + is_lower_up = TRUE; + break; + } + } + } + return !!master->link.connected != is_lower_up; +} + +/** + * nmp_cache_link_connected_needs_toggle_by_ifindex: + * @cache: + * @master_ifindex: the ifindex of a potential master that should be checked + * whether it needs toggling. + * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle(). + * It considers @potential_slave as being inside the cache, replacing an existing + * link with the same ifindex. + * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle(). + * + * The flag obj->link.connected depends on the state of other links in the + * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex + * of a master, check if the cache contains such a master link that needs + * toggling of the connected flag. + * + * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled. + * Otherwise, return the link object from inside the cache with the given ifindex. + * The connected flag of that master should be toggled. + */ +const NMPObject * +nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache, + int master_ifindex, + const NMPObject *potential_slave, + const NMPObject *ignore_slave) +{ + const NMPObject *master; + + if (master_ifindex > 0) { + master = nmp_cache_lookup_link(cache, master_ifindex); + if (nmp_cache_link_connected_needs_toggle(cache, master, potential_slave, ignore_slave)) + return master; + } + return NULL; +} + +/*****************************************************************************/ + +static const NMDedupMultiEntry * +_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj) +{ + const NMDedupMultiEntry *entry; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + entry = + nm_dedup_multi_index_lookup_obj(cache->multi_idx, _idx_type_get(cache, cache_id_type), obj); + nm_assert(!entry + || (NMP_OBJECT_IS_VALID(entry->obj) + && NMP_OBJECT_GET_CLASS(entry->obj) == NMP_OBJECT_GET_CLASS(obj))); + return entry; +} + +static const NMDedupMultiEntry * +_lookup_entry(const NMPCache *cache, const NMPObject *obj) +{ + return _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj) +{ + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(obj, NULL); + g_return_val_if_fail(cache_id_type > NMP_CACHE_ID_TYPE_NONE + && cache_id_type <= NMP_CACHE_ID_TYPE_MAX, + NULL); + + return _lookup_entry_with_idx_type(cache, cache_id_type, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj) +{ + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(obj, NULL); + + return _lookup_entry(cache, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex) +{ + NMPObject obj_needle; + + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + nmp_object_stackinit_id_link(&obj_needle, ifindex); + return _lookup_entry(cache, &obj_needle); +} + +const NMPObject * +nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj) +{ + return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry(cache, obj)); +} + +const NMPObject * +nmp_cache_lookup_link(const NMPCache *cache, int ifindex) +{ + return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry_link(cache, ifindex)); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nmp_cache_lookup_all(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *select_obj) +{ + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(select_obj)); + + return nm_dedup_multi_index_lookup_head(cache->multi_idx, + _idx_type_get(cache, cache_id_type), + select_obj); +} + +static const NMPLookup * +_L(const NMPLookup *lookup) +{ +#if NM_MORE_ASSERTS + DedupMultiIdxType idx_type; + + nm_assert(lookup); + _dedup_multi_idx_type_init(&idx_type, lookup->cache_id_type); + nm_assert( + idx_type.parent.klass->idx_obj_partitionable((NMDedupMultiIdxType *) &idx_type, + (NMDedupMultiObj *) &lookup->selector_obj)); +#endif + return lookup; +} + +const NMPLookup * +nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type) +{ + nm_assert(lookup); + + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_ROUTING_RULE: + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE; + return _L(lookup); + default: + nm_assert_not_reached(); + return NULL; + } +} + +const NMPLookup * +nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(ifname); + nm_assert(strlen(ifname) < IFNAMSIZ); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_LINK); + if (g_strlcpy(o->link.name, ifname, sizeof(o->link.name)) >= sizeof(o->link.name)) + g_return_val_if_reached(NULL); + lookup->cache_id_type = NMP_CACHE_ID_TYPE_LINK_BY_IFNAME; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER)); + + if (ifindex <= 0) { + return nmp_lookup_init_obj_type(lookup, obj_type); + } + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + o->obj_with_ifindex.ifindex = ifindex; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + o->ip_route.ifindex = 1; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj) +{ + const NMPlatformIP4Route *r4; + const NMPlatformIP6Route *r6; + + nm_assert(lookup); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + r4 = NMP_OBJECT_CAST_IP4_ROUTE(obj); + return nmp_lookup_init_ip4_route_by_weak_id(lookup, + r4->network, + r4->plen, + r4->metric, + r4->tos); + case NMP_OBJECT_TYPE_IP6_ROUTE: + r6 = NMP_OBJECT_CAST_IP6_ROUTE(obj); + return nmp_lookup_init_ip6_route_by_weak_id(lookup, + &r6->network, + r6->plen, + r6->metric, + &r6->src, + r6->src_plen); + default: + nm_assert_not_reached(); + return NULL; + } +} + +const NMPLookup * +nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos) +{ + NMPObject *o; + + nm_assert(lookup); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE); + o->ip4_route.ifindex = 1; + o->ip4_route.plen = plen; + o->ip4_route.metric = metric; + if (network) + o->ip4_route.network = network; + o->ip4_route.tos = tos; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_ip6_route_by_weak_id(NMPLookup * lookup, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + NMPObject *o; + + nm_assert(lookup); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE); + o->ip6_route.ifindex = 1; + o->ip6_route.plen = plen; + o->ip6_route.metric = metric; + if (network) + o->ip6_route.network = *network; + if (src) + o->ip6_route.src = *src; + o->ip6_route.src_plen = src_plen; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)); + + if (addr_family == AF_UNSPEC) + return nmp_lookup_init_obj_type(lookup, obj_type); + + nm_assert_addr_family(addr_family); + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + NMP_OBJECT_CAST_ROUTING_RULE(o)->addr_family = addr_family; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY; + return _L(lookup); +} + +/*****************************************************************************/ + +GArray * +nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry, + NMPObjectType obj_type, + gboolean visible_only) +{ + const NMPClass * klass = nmp_class_from_type(obj_type); + NMDedupMultiIter iter; + const NMPObject *o; + GArray * array; + + g_return_val_if_fail(klass, NULL); + + array = g_array_sized_new(FALSE, FALSE, klass->sizeof_public, head_entry ? head_entry->len : 0); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + nm_assert(NMP_OBJECT_GET_CLASS(o) == klass); + if (visible_only && !nmp_object_is_visible(o)) + continue; + g_array_append_vals(array, &o->object, 1); + } + return array; +} + +/*****************************************************************************/ + +const NMPObject * +nmp_cache_lookup_link_full(const NMPCache * cache, + int ifindex, + const char * ifname, + gboolean visible_only, + NMLinkType link_type, + NMPObjectMatchFn match_fn, + gpointer user_data) +{ + NMPObject obj_needle; + const NMPObject * obj; + NMDedupMultiIter iter; + const NMDedupMultiHeadEntry *head_entry; + const NMPlatformLink * link = NULL; + NMPLookup lookup; + + if (ifindex > 0) { + obj = nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&obj_needle, ifindex)); + + if (!obj || (visible_only && !nmp_object_is_visible(obj)) + || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) + || (ifname && strcmp(obj->link.name, ifname)) + || (match_fn && !match_fn(obj, user_data))) + return NULL; + return obj; + } else if (!ifname && !match_fn) + return NULL; + else { + const NMPObject *obj_best = NULL; + + if (ifname) { + if (strlen(ifname) >= IFNAMSIZ) + return NULL; + nmp_lookup_init_link_by_ifname(&lookup, ifname); + } else + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + + head_entry = nmp_cache_lookup(cache, &lookup); + nmp_cache_iter_for_each_link (&iter, head_entry, &link) { + obj = NMP_OBJECT_UP_CAST(link); + + if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) + continue; + if (visible_only && !nmp_object_is_visible(obj)) + continue; + if (match_fn && !match_fn(obj, user_data)) + continue; + + /* if there are multiple candidates, prefer the visible ones. */ + if (visible_only || nmp_object_is_visible(obj)) + return obj; + if (!obj_best) + obj_best = obj; + } + return obj_best; + } +} + +/*****************************************************************************/ + +static NMDedupMultiIdxMode +_obj_get_add_mode(const NMPObject *obj) +{ + /* new objects are usually appended to the list. Except for + * addresses, which are prepended during `ip address add`. + * + * Actually, for routes it is more complicated, because depending on + * `ip route append`, `ip route replace`, `ip route prepend`, the object + * will be added at the tail, at the front, or even replace an element + * in the list. However, that is handled separately by nmp_cache_update_netlink_route() + * and of no concern here. */ + if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS)) + return NM_DEDUP_MULTI_IDX_MODE_PREPEND; + return NM_DEDUP_MULTI_IDX_MODE_APPEND; +} + +static void +_idxcache_update_order_for_dump(NMPCache *cache, const NMDedupMultiEntry *entry) +{ + const NMPClass * klass; + const guint8 * i_idx_type; + const NMDedupMultiEntry *entry2; + + nm_dedup_multi_entry_reorder(entry, NULL, TRUE); + + klass = NMP_OBJECT_GET_CLASS(entry->obj); + for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { + NMPCacheIdType id_type = *i_idx_type; + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) + continue; + + entry2 = nm_dedup_multi_index_lookup_obj(cache->multi_idx, + _idx_type_get(cache, id_type), + entry->obj); + if (!entry2) + continue; + + nm_assert(entry2 != entry); + nm_assert(entry2->obj == entry->obj); + + nm_dedup_multi_entry_reorder(entry2, NULL, TRUE); + } +} + +static void +_idxcache_update_other_cache_ids(NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj_old, + const NMPObject *obj_new, + gboolean is_dump) +{ + const NMDedupMultiEntry *entry_new; + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_order; + NMDedupMultiIdxType * idx_type; + + nm_assert(obj_new || obj_old); + nm_assert(!obj_new || NMP_OBJECT_GET_TYPE(obj_new) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_old || NMP_OBJECT_GET_TYPE(obj_old) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_old || !obj_new + || NMP_OBJECT_GET_CLASS(obj_new) == NMP_OBJECT_GET_CLASS(obj_old)); + nm_assert(!obj_old || !obj_new || !nmp_object_equal(obj_new, obj_old)); + nm_assert(!obj_new || obj_new == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_new)); + nm_assert(!obj_old || obj_old == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_old)); + + idx_type = _idx_type_get(cache, cache_id_type); + + if (obj_old) { + entry_old = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_old); + if (!obj_new) { + if (entry_old) + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); + return; + } + } else + entry_old = NULL; + + if (obj_new) { + if (obj_old && nm_dedup_multi_idx_type_id_equal(idx_type, obj_old, obj_new) + && nm_dedup_multi_idx_type_partition_equal(idx_type, obj_old, obj_new)) { + /* optimize. We just looked up the @obj_old entry and @obj_new compares equal + * according to idx_obj_id_equal(). entry_new is the same as entry_old. */ + entry_new = entry_old; + } else { + entry_new = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_new); + } + + if (entry_new) + entry_order = entry_new; + else if (entry_old + && nm_dedup_multi_idx_type_partition_equal(idx_type, entry_old->obj, obj_new)) + entry_order = entry_old; + else + entry_order = NULL; + nm_dedup_multi_index_add_full( + cache->multi_idx, + idx_type, + obj_new, + is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : _obj_get_add_mode(obj_new), + is_dump ? NULL : entry_order, + entry_new ?: NM_DEDUP_MULTI_ENTRY_MISSING, + entry_new ? entry_new->head : (entry_order ? entry_order->head : NULL), + &entry_new, + NULL); + +#if NM_MORE_ASSERTS + if (entry_new) { + nm_assert(idx_type->klass->idx_obj_partitionable); + nm_assert(idx_type->klass->idx_obj_partition_equal); + nm_assert(idx_type->klass->idx_obj_partitionable(idx_type, entry_new->obj)); + nm_assert(idx_type->klass->idx_obj_partition_equal(idx_type, + (gpointer) obj_new, + entry_new->obj)); + } +#endif + } else + entry_new = NULL; + + if (entry_old && entry_old != entry_new) + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); +} + +static void +_idxcache_update(NMPCache * cache, + const NMDedupMultiEntry * entry_old, + NMPObject * obj_new, + gboolean is_dump, + const NMDedupMultiEntry **out_entry_new) +{ + const NMPClass * klass; + const guint8 * i_idx_type; + NMDedupMultiIdxType * idx_type_o = _idx_type_get(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE); + const NMDedupMultiEntry *entry_new = NULL; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + /* we update an object in the cache. + * + * Note that @entry_old MUST be what is currently tracked in multi_idx, and it must + * have the same ID as @obj_new. */ + + nm_assert(cache); + nm_assert(entry_old || obj_new); + nm_assert(!obj_new || nmp_object_is_alive(obj_new)); + nm_assert( + !entry_old + || entry_old + == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, entry_old->obj)); + nm_assert(!obj_new + || entry_old + == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, obj_new)); + nm_assert(!entry_old || entry_old->head->idx_type == idx_type_o); + nm_assert(!entry_old || !obj_new + || nm_dedup_multi_idx_type_partition_equal(idx_type_o, entry_old->obj, obj_new)); + nm_assert(!entry_old || !obj_new + || nm_dedup_multi_idx_type_id_equal(idx_type_o, entry_old->obj, obj_new)); + nm_assert(!entry_old || !obj_new + || (obj_new->parent.klass == ((const NMPObject *) entry_old->obj)->parent.klass + && !obj_new->parent.klass->obj_full_equal((NMDedupMultiObj *) obj_new, + entry_old->obj))); + + /* keep a reference to the pre-existing entry */ + if (entry_old) + obj_old = nmp_object_ref(entry_old->obj); + + /* first update the main index NMP_CACHE_ID_TYPE_OBJECT_TYPE. + * We already know the pre-existing @entry old, so all that + * nm_dedup_multi_index_add_full() effectively does, is update the + * obj reference. + * + * We also get the new boxed object, which we need below. */ + if (obj_new) { + nm_auto_nmpobj NMPObject *obj_old2 = NULL; + + nm_dedup_multi_index_add_full(cache->multi_idx, + idx_type_o, + obj_new, + is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE + : _obj_get_add_mode(obj_new), + NULL, + entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, + NULL, + &entry_new, + (const NMDedupMultiObj **) &obj_old2); + nm_assert(entry_new); + nm_assert(obj_old == obj_old2); + nm_assert(!entry_old || entry_old == entry_new); + } else + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); + + /* now update all other indexes. We know the previously boxed entry, and the + * newly boxed one. */ + klass = NMP_OBJECT_GET_CLASS(entry_new ? entry_new->obj : obj_old); + for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { + NMPCacheIdType id_type = *i_idx_type; + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) + continue; + _idxcache_update_other_cache_ids(cache, + id_type, + obj_old, + entry_new ? entry_new->obj : NULL, + is_dump); + } + + NM_SET_OUT(out_entry_new, entry_new); +} + +NMPCacheOpsType +nmp_cache_remove(NMPCache * cache, + const NMPObject * obj_needle, + gboolean equals_by_ptr, + gboolean only_dirty, + const NMPObject **out_obj_old) +{ + const NMDedupMultiEntry *entry_old; + const NMPObject * obj_old; + + entry_old = _lookup_entry(cache, obj_needle); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (equals_by_ptr && obj_old != obj_needle) { + /* We found an identical object, but we only delete it if it's the same pointer as + * @obj_needle. */ + return NMP_CACHE_OPS_UNCHANGED; + } + if (only_dirty && !entry_old->dirty) { + /* the entry is not dirty. Skip. */ + return NMP_CACHE_OPS_UNCHANGED; + } + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + return NMP_CACHE_OPS_REMOVED; +} + +NMPCacheOpsType +nmp_cache_remove_netlink(NMPCache * cache, + const NMPObject * obj_needle, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new = NULL; + const NMPObject * obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + + entry_old = _lookup_entry(cache, obj_needle); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + if (NMP_OBJECT_GET_TYPE(obj_needle) == NMP_OBJECT_TYPE_LINK) { + /* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be + * removed from netlink. Link objects are alive without being in netlink when they + * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink + * flag. */ + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (!obj_old->_link.netlink.is_in_netlink) { + nm_assert(obj_old->_link.udev.device); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (!obj_old->_link.udev.device) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + obj_new->_link.netlink.is_in_netlink = FALSE; + + _nmp_object_fixup_link_master_connected(&obj_new, NULL, cache); + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; + } + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, NULL); + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + return NMP_CACHE_OPS_REMOVED; +} + +/** + * nmp_cache_update_netlink: + * @cache: the platform cache + * @obj_hand_over: a #NMPObject instance as received from netlink and created via + * nmp_object_from_nl(). Especially for link, it must not have the udev + * replated fields set. + * This instance will be modified and might be put into the cache. When + * calling nmp_cache_update_netlink() you hand @obj over to the cache. + * Except, that the cache will increment the ref count as appropriate. You + * must still unref the obj to release your part of the ownership. + * @is_dump: whether this update comes during a dump of object of the same kind. + * kernel dumps objects in a certain order, which matters especially for routes. + * Before a dump we mark all objects as dirty, and remove all untouched objects + * afterwards. Hence, during a dump, every update should move the object to the + * end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, + * instead of NM_DEDUP_MULTI_IDX_MODE_APPEND. + * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over, + * that was in the cache before update. If an object is returned, the caller must + * unref it afterwards. + * @out_obj_new: (allow-none) (out): return the object from the cache after update. + * The caller must unref this object. + * + * Returns: how the cache changed. + * + * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old + * and @out_obj_new will be set accordingly. + **/ +NMPCacheOpsType +nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new; + const NMPObject * obj_old; + gboolean is_alive; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over)); + /* A link object from netlink must have the udev related fields unset. + * We could implement to handle that, but there is no need to support such + * a use-case */ + nm_assert(NMP_OBJECT_GET_TYPE(obj_hand_over) != NMP_OBJECT_TYPE_LINK + || (!obj_hand_over->_link.udev.device && !obj_hand_over->link.driver)); + nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over); + + entry_old = _lookup_entry(cache, obj_hand_over); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + + if (!nmp_object_is_alive(obj_hand_over)) { + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) { + _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache); + _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev); + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_ADDED; + } + + obj_old = entry_old->obj; + + if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) { + if (!obj_hand_over->_link.netlink.is_in_netlink) { + if (!obj_old->_link.netlink.is_in_netlink) { + nm_assert(obj_old->_link.udev.device); + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + if (obj_old->_link.udev.device) { + /* @obj_hand_over is not in netlink. + * + * This is similar to nmp_cache_remove_netlink(), but there we preserve the + * preexisting netlink properties. The use case of that is when kernel_get_object() + * cannot load an object (based on the id of a needle). + * + * Here we keep the data provided from @obj_hand_over. The usecase is when receiving + * a valid @obj_hand_over instance from netlink with RTM_DELROUTE. + */ + is_alive = TRUE; + } else + is_alive = FALSE; + } else + is_alive = TRUE; + + if (is_alive) { + _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache); + + /* Merge the netlink parts with what we have from udev. */ + udev_device_unref(obj_hand_over->_link.udev.device); + obj_hand_over->_link.udev.device = + obj_old->_link.udev.device ? udev_device_ref(obj_old->_link.udev.device) : NULL; + _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev); + + if (obj_hand_over->_link.netlink.lnk) { + nm_auto_nmpobj const NMPObject *lnk_old = obj_hand_over->_link.netlink.lnk; + + /* let's dedup/intern the lnk object. */ + obj_hand_over->_link.netlink.lnk = + nm_dedup_multi_index_obj_intern(cache->multi_idx, lnk_old); + } + } + } else + is_alive = nmp_object_is_alive(obj_hand_over); + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (!is_alive) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + if (nmp_object_equal(obj_old, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump(cache, entry_old); + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; +} + +NMPCacheOpsType +nmp_cache_update_netlink_route(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + guint16 nlmsgflags, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + const NMPObject **out_obj_replace, + gboolean * out_resync_required) +{ + NMDedupMultiIter iter; + const NMDedupMultiEntry * entry_old; + const NMDedupMultiEntry * entry_new; + const NMDedupMultiEntry * entry_cur; + const NMDedupMultiEntry * entry_replace; + const NMDedupMultiHeadEntry *head_entry; + gboolean is_alive; + NMPCacheOpsType ops_type = NMP_CACHE_OPS_UNCHANGED; + gboolean resync_required; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over)); + /* A link object from netlink must have the udev related fields unset. + * We could implement to handle that, but there is no need to support such + * a use-case */ + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_hand_over), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over); + + entry_old = _lookup_entry(cache, obj_hand_over); + entry_new = NULL; + + NM_SET_OUT(out_obj_old, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_old))); + + if (!entry_old) { + if (!nmp_object_is_alive(obj_hand_over)) + goto update_done; + + _idxcache_update(cache, NULL, obj_hand_over, is_dump, &entry_new); + ops_type = NMP_CACHE_OPS_ADDED; + goto update_done; + } + + is_alive = nmp_object_is_alive(obj_hand_over); + + if (!is_alive) { + /* the update would make @entry_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + ops_type = NMP_CACHE_OPS_REMOVED; + goto update_done; + } + + if (nmp_object_equal(entry_old->obj, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump(cache, entry_old); + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto update_done; + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + ops_type = NMP_CACHE_OPS_UPDATED; + +update_done: + NM_SET_OUT(out_obj_new, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_new))); + + /* a RTM_GETROUTE event may signal that another object was replaced. + * Find out whether that is the case and return it as @obj_replaced. + * + * Also, fixup the order of @entry_new within NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID + * index. For most parts, we don't care about the order of objects (including routes). + * But NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID we must keep in the correct order, to + * properly find @obj_replaced. */ + resync_required = FALSE; + entry_replace = NULL; + if (is_dump) + goto out; + + if (!entry_new) { + if (NM_FLAGS_HAS(nlmsgflags, NLM_F_REPLACE) + && nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj_hand_over)) { + /* hm. @obj_hand_over was not added, meaning it was not alive. + * However, we track some other objects with the same weak-id. + * It's unclear what that means. To be sure, resync. */ + resync_required = TRUE; + } + goto out; + } + + /* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID + * index. For all other indexes their order becomes messed up. */ + entry_cur = + _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj); + if (!entry_cur) { + nm_assert_not_reached(); + goto out; + } + nm_assert(entry_cur->obj == entry_new->obj); + + head_entry = entry_cur->head; + nm_assert(head_entry + == nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_cur->obj)); + + if (head_entry->len == 1) { + /* there is only one object, and we expect it to be @obj_new. */ + nm_assert(nm_dedup_multi_head_entry_get_idx(head_entry, 0) == entry_cur); + goto out; + } + + switch (nlmsgflags & (NLM_F_REPLACE | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND)) { + case NLM_F_REPLACE: + /* ip route change */ + + /* get the first element (but skip @obj_new). */ + nm_dedup_multi_iter_init(&iter, head_entry); + if (!nm_dedup_multi_iter_next(&iter)) + nm_assert_not_reached(); + if (iter.current == entry_cur) { + if (!nm_dedup_multi_iter_next(&iter)) + nm_assert_not_reached(); + } + entry_replace = iter.current; + + nm_assert(entry_replace && entry_cur != entry_replace); + + nm_dedup_multi_entry_reorder(entry_cur, entry_replace, FALSE); + break; + case NLM_F_CREATE | NLM_F_APPEND: + /* ip route append */ + nm_dedup_multi_entry_reorder(entry_cur, NULL, TRUE); + break; + case NLM_F_CREATE: + /* ip route prepend */ + nm_dedup_multi_entry_reorder(entry_cur, NULL, FALSE); + break; + default: + /* this is an unexpected case, probably a bug that we need to handle better. */ + resync_required = TRUE; + break; + } + +out: + NM_SET_OUT(out_obj_replace, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_replace))); + NM_SET_OUT(out_resync_required, resync_required); + return ops_type; +} + +NMPCacheOpsType +nmp_cache_update_link_udev(NMPCache * cache, + int ifindex, + struct udev_device *udevice, + const NMPObject ** out_obj_old, + const NMPObject ** out_obj_new) +{ + const NMPObject *obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + const NMDedupMultiEntry * entry_old; + const NMDedupMultiEntry * entry_new; + + entry_old = nmp_cache_lookup_entry_link(cache, ifindex); + + if (!entry_old) { + if (!udevice) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_new = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL); + obj_new->link.ifindex = ifindex; + obj_new->_link.udev.device = udev_device_ref(udevice); + + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, NULL, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_ADDED; + } else { + obj_old = entry_old->obj; + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (obj_old->_link.udev.device == udevice) { + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (!udevice && !obj_old->_link.netlink.is_in_netlink) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + + udev_device_unref(obj_new->_link.udev.device); + obj_new->_link.udev.device = udevice ? udev_device_ref(udevice) : NULL; + + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; + } +} + +NMPCacheOpsType +nmp_cache_update_link_master_connected(NMPCache * cache, + int ifindex, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new = NULL; + const NMPObject * obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + + entry_old = nmp_cache_lookup_entry_link(cache, ifindex); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + if (!nmp_cache_link_connected_needs_toggle(cache, obj_old, NULL, NULL)) { + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + obj_new->link.connected = !obj_old->link.connected; + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; +} + +/*****************************************************************************/ + +void +nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + + nm_assert(cache); + nm_assert(lookup); + + head_entry = nmp_cache_lookup(cache, lookup); + + nm_dedup_multi_iter_init(&iter, head_entry); + while (nm_dedup_multi_iter_next(&iter)) { + const NMDedupMultiEntry *main_entry; + + main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup); + + nm_dedup_multi_entry_set_dirty(main_entry, TRUE); + } +} + +/*****************************************************************************/ + +NMPCache * +nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev) +{ + NMPCache *cache = g_slice_new0(NMPCache); + guint i; + + for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) + _dedup_multi_idx_type_init((DedupMultiIdxType *) _idx_type_get(cache, i), i); + + cache->multi_idx = nm_dedup_multi_index_ref(multi_idx); + + cache->use_udev = !!use_udev; + return cache; +} + +void +nmp_cache_free(NMPCache *cache) +{ + guint i; + + for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) + nm_dedup_multi_index_remove_idx(cache->multi_idx, _idx_type_get(cache, i)); + + nm_dedup_multi_index_unref(cache->multi_idx); + + g_slice_free(NMPCache, cache); +} + +/*****************************************************************************/ + +void +nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache) +{} + +/*****************************************************************************/ + +/* below, ensure that addr_family get's automatically initialize to AF_UNSPEC. */ +G_STATIC_ASSERT(AF_UNSPEC == 0); + +typedef const char *(*CmdPlobjToStringFunc)(const NMPlatformObject *obj, char *buf, gsize len); +typedef const char *(*CmdPlobjToStringIdFunc)(const NMPlatformObject *obj, char *buf, gsize len); +typedef void (*CmdPlobjHashUpdateFunc)(const NMPlatformObject *obj, NMHashState *h); +typedef int (*CmdPlobjCmpFunc)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); + +const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { + [NMP_OBJECT_TYPE_LINK - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LINK, + .sizeof_data = sizeof(NMPObjectLink), + .sizeof_public = sizeof(NMPlatformLink), + .obj_type_name = "link", + .rtm_gettype = RTM_GETLINK, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_LINK, + .signal_type = NM_PLATFORM_SIGNAL_LINK_CHANGED, + .supported_cache_ids = _supported_cache_ids_link, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_link, + .cmd_obj_cmp = _vt_cmd_obj_cmp_link, + .cmd_obj_copy = _vt_cmd_obj_copy_link, + .cmd_obj_dispose = _vt_cmd_obj_dispose_link, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_link, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_link, + .cmd_obj_to_string = _vt_cmd_obj_to_string_link, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_link, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_link, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_link, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_link, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_link_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_link_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_link_cmp, + }, + [NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP4_ADDRESS, + .sizeof_data = sizeof(NMPObjectIP4Address), + .sizeof_public = sizeof(NMPlatformIP4Address), + .obj_type_name = "ip4-address", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETADDR, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_address, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_address, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_address, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_address_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_ip4_address_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip4_address_cmp, + }, + [NMP_OBJECT_TYPE_IP6_ADDRESS + - 1] = {.parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP6_ADDRESS, + .sizeof_data = sizeof(NMPObjectIP6Address), + .sizeof_public = sizeof(NMPlatformIP6Address), + .obj_type_name = "ip6-address", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETADDR, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_address, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_address, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_address, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_address_to_string, + .cmd_plobj_hash_update = + (CmdPlobjHashUpdateFunc) nm_platform_ip6_address_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip6_address_cmp}, + [NMP_OBJECT_TYPE_IP4_ROUTE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE, + .sizeof_data = sizeof(NMPObjectIP4Route), + .sizeof_public = sizeof(NMPlatformIP4Route), + .obj_type_name = "ip4-route", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETROUTE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_route, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_route, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_route, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip4_route_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_route_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip4_route, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip4_route_cmp_full, + }, + [NMP_OBJECT_TYPE_IP6_ROUTE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP6_ROUTE, + .sizeof_data = sizeof(NMPObjectIP6Route), + .sizeof_public = sizeof(NMPlatformIP6Route), + .obj_type_name = "ip6-route", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETROUTE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_route, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_route, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_route, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip6_route_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_route_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip6_route, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip6_route_cmp_full, + }, + [NMP_OBJECT_TYPE_ROUTING_RULE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_ROUTING_RULE, + .sizeof_data = sizeof(NMPObjectRoutingRule), + .sizeof_public = sizeof(NMPlatformRoutingRule), + .obj_type_name = "routing-rule", + .rtm_gettype = RTM_GETRULE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + .signal_type = NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, + .supported_cache_ids = _supported_cache_ids_routing_rules, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_routing_rule, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_routing_rule, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_routing_rule, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_routing_rule, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_routing_rule_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_routing_rule_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_routing_rule, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_routing_rule_cmp_full, + }, + [NMP_OBJECT_TYPE_QDISC - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_QDISC, + .sizeof_data = sizeof(NMPObjectQdisc), + .sizeof_public = sizeof(NMPlatformQdisc), + .obj_type_name = "qdisc", + .rtm_gettype = RTM_GETQDISC, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_QDISC, + .signal_type = NM_PLATFORM_SIGNAL_QDISC_CHANGED, + .supported_cache_ids = _supported_cache_ids_object, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_qdisc, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_qdisc, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_qdisc, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_qdisc, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_qdisc_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_qdisc_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_qdisc_cmp, + }, + [NMP_OBJECT_TYPE_TFILTER - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_TFILTER, + .sizeof_data = sizeof(NMPObjectTfilter), + .sizeof_public = sizeof(NMPlatformTfilter), + .obj_type_name = "tfilter", + .rtm_gettype = RTM_GETTFILTER, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_TFILTER, + .signal_type = NM_PLATFORM_SIGNAL_TFILTER_CHANGED, + .supported_cache_ids = _supported_cache_ids_object, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_tfilter, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_tfilter, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_tfilter, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_tfilter, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_tfilter_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_tfilter_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_tfilter_cmp, + }, + [NMP_OBJECT_TYPE_LNK_BRIDGE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_BRIDGE, + .sizeof_data = sizeof(NMPObjectLnkBridge), + .sizeof_public = sizeof(NMPlatformLnkBridge), + .obj_type_name = "bridge", + .lnk_link_type = NM_LINK_TYPE_BRIDGE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_bridge_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_bridge_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_bridge_cmp, + }, + [NMP_OBJECT_TYPE_LNK_GRE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_GRE, + .sizeof_data = sizeof(NMPObjectLnkGre), + .sizeof_public = sizeof(NMPlatformLnkGre), + .obj_type_name = "gre", + .lnk_link_type = NM_LINK_TYPE_GRE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp, + }, + [NMP_OBJECT_TYPE_LNK_GRETAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_GRETAP, + .sizeof_data = sizeof(NMPObjectLnkGre), + .sizeof_public = sizeof(NMPlatformLnkGre), + .obj_type_name = "gretap", + .lnk_link_type = NM_LINK_TYPE_GRETAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp, + }, + [NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_INFINIBAND, + .sizeof_data = sizeof(NMPObjectLnkInfiniband), + .sizeof_public = sizeof(NMPlatformLnkInfiniband), + .obj_type_name = "infiniband", + .lnk_link_type = NM_LINK_TYPE_INFINIBAND, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_infiniband_to_string, + .cmd_plobj_hash_update = + (CmdPlobjHashUpdateFunc) nm_platform_lnk_infiniband_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_infiniband_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6TNL, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6tnl", + .lnk_link_type = NM_LINK_TYPE_IP6TNL, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6GRE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRE, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6gre", + .lnk_link_type = NM_LINK_TYPE_IP6GRE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRETAP, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6gretap", + .lnk_link_type = NM_LINK_TYPE_IP6GRETAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IPIP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IPIP, + .sizeof_data = sizeof(NMPObjectLnkIpIp), + .sizeof_public = sizeof(NMPlatformLnkIpIp), + .obj_type_name = "ipip", + .lnk_link_type = NM_LINK_TYPE_IPIP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ipip_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ipip_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ipip_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACSEC - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACSEC, + .sizeof_data = sizeof(NMPObjectLnkMacsec), + .sizeof_public = sizeof(NMPlatformLnkMacsec), + .obj_type_name = "macsec", + .lnk_link_type = NM_LINK_TYPE_MACSEC, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macsec_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macsec_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macsec_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACVLAN, + .sizeof_data = sizeof(NMPObjectLnkMacvlan), + .sizeof_public = sizeof(NMPlatformLnkMacvlan), + .obj_type_name = "macvlan", + .lnk_link_type = NM_LINK_TYPE_MACVLAN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACVTAP, + .sizeof_data = sizeof(NMPObjectLnkMacvtap), + .sizeof_public = sizeof(NMPlatformLnkMacvlan), + .obj_type_name = "macvtap", + .lnk_link_type = NM_LINK_TYPE_MACVTAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_SIT - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_SIT, + .sizeof_data = sizeof(NMPObjectLnkSit), + .sizeof_public = sizeof(NMPlatformLnkSit), + .obj_type_name = "sit", + .lnk_link_type = NM_LINK_TYPE_SIT, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_sit_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_sit_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_sit_cmp, + }, + [NMP_OBJECT_TYPE_LNK_TUN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_TUN, + .sizeof_data = sizeof(NMPObjectLnkTun), + .sizeof_public = sizeof(NMPlatformLnkTun), + .obj_type_name = "tun", + .lnk_link_type = NM_LINK_TYPE_TUN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_tun_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_tun_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_tun_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VLAN, + .sizeof_data = sizeof(NMPObjectLnkVlan), + .sizeof_public = sizeof(NMPlatformLnkVlan), + .obj_type_name = "vlan", + .lnk_link_type = NM_LINK_TYPE_VLAN, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_lnk_vlan, + .cmd_obj_cmp = _vt_cmd_obj_cmp_lnk_vlan, + .cmd_obj_copy = _vt_cmd_obj_copy_lnk_vlan, + .cmd_obj_dispose = _vt_cmd_obj_dispose_lnk_vlan, + .cmd_obj_to_string = _vt_cmd_obj_to_string_lnk_vlan, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VRF - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VRF, + .sizeof_data = sizeof(NMPObjectLnkVrf), + .sizeof_public = sizeof(NMPlatformLnkVrf), + .obj_type_name = "vrf", + .lnk_link_type = NM_LINK_TYPE_VRF, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vrf_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vrf_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vrf_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VXLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VXLAN, + .sizeof_data = sizeof(NMPObjectLnkVxlan), + .sizeof_public = sizeof(NMPlatformLnkVxlan), + .obj_type_name = "vxlan", + .lnk_link_type = NM_LINK_TYPE_VXLAN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vxlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vxlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vxlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_WIREGUARD, + .sizeof_data = sizeof(NMPObjectLnkWireGuard), + .sizeof_public = sizeof(NMPlatformLnkWireGuard), + .obj_type_name = "wireguard", + .lnk_link_type = NM_LINK_TYPE_WIREGUARD, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_lnk_wireguard, + .cmd_obj_cmp = _vt_cmd_obj_cmp_lnk_wireguard, + .cmd_obj_copy = _vt_cmd_obj_copy_lnk_wireguard, + .cmd_obj_dispose = _vt_cmd_obj_dispose_lnk_wireguard, + .cmd_obj_to_string = _vt_cmd_obj_to_string_lnk_wireguard, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_wireguard_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_wireguard_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_wireguard_cmp, + }, +}; diff --git a/src/core/platform/nmp-object.h b/src/core/platform/nmp-object.h new file mode 100644 index 0000000..dc2cc86 --- /dev/null +++ b/src/core/platform/nmp-object.h @@ -0,0 +1,1140 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#ifndef __NMP_OBJECT_H__ +#define __NMP_OBJECT_H__ + +#include + +#include "nm-glib-aux/nm-obj.h" +#include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-platform.h" + +struct udev_device; + +/*****************************************************************************/ + +/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice, + * these are the same types. */ +G_STATIC_ASSERT(sizeof(long long) == sizeof(gint64)); + +typedef struct { + /* like "struct __kernel_timespec". */ + gint64 tv_sec; + gint64 tv_nsec; +} NMPTimespec64; + +/*****************************************************************************/ + +typedef union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +} NMSockAddrUnion; + +#define NM_SOCK_ADDR_UNION_INIT_UNSPEC \ + { \ + .sa = { \ + .sa_family = AF_UNSPEC, \ + }, \ + } + +int nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b); + +void nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h); + +void nm_sock_addr_union_cpy(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */); + +void nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */, + gsize src_len); + +const char *nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len); + +/*****************************************************************************/ + +typedef struct { + NMIPAddr addr; + guint8 family; + guint8 mask; +} NMPWireGuardAllowedIP; + +typedef struct _NMPWireGuardPeer { + NMSockAddrUnion endpoint; + + NMPTimespec64 last_handshake_time; + + guint64 rx_bytes; + guint64 tx_bytes; + + union { + const NMPWireGuardAllowedIP *allowed_ips; + guint _construct_idx_start; + }; + union { + guint allowed_ips_len; + guint _construct_idx_end; + }; + + guint16 persistent_keepalive_interval; + + guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; + guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN]; +} NMPWireGuardPeer; + +/*****************************************************************************/ + +typedef enum { /*< skip >*/ + NMP_OBJECT_TO_STRING_ID, + NMP_OBJECT_TO_STRING_PUBLIC, + NMP_OBJECT_TO_STRING_ALL, +} NMPObjectToStringMode; + +typedef enum { /*< skip >*/ + NMP_CACHE_OPS_UNCHANGED = NM_PLATFORM_SIGNAL_NONE, + NMP_CACHE_OPS_ADDED = NM_PLATFORM_SIGNAL_ADDED, + NMP_CACHE_OPS_UPDATED = NM_PLATFORM_SIGNAL_CHANGED, + NMP_CACHE_OPS_REMOVED = NM_PLATFORM_SIGNAL_REMOVED, +} NMPCacheOpsType; + +/* The NMPCacheIdType are the different index types. + * + * An object of a certain object-type, can be candidate to being + * indexed by a certain NMPCacheIdType or not. For example, all + * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE, + * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT. + * + * Of one index type, there can be multiple indexes or not. + * For example, of the index type NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX there + * are multiple instances (for different route/addresses, v4/v6, per-ifindex). + * + * But one object, can only be indexed by one particular index of a + * type. For example, a certain address instance is only indexed by + * the index NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX with + * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible. + * */ +typedef enum { /*< skip >*/ + NMP_CACHE_ID_TYPE_NONE, + + /* all the objects of a certain type. + * + * This index is special. It is the only one that contains *all* object. + * Other indexes may consider some object as non "partitionable", hence + * they don't track all objects. + * + * Hence, this index type is used when looking at all objects (still + * partitioned by type). + * + * Also, note that links may be considered invisible. This index type + * expose all links, even invisible ones. For addresses/routes, this + * distinction doesn't exist, as all addresses/routes that are alive + * are visible as well. */ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + + /* index for the link objects by ifname. */ + NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, + + /* indices for the visible default-routes, ignoring ifindex. + * This index only contains two partitions: all visible default-routes, + * separate for IPv4 and IPv6. */ + NMP_CACHE_ID_TYPE_DEFAULT_ROUTES, + + /* all the objects that have an ifindex (by object-type) for an ifindex. */ + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + + /* Consider all the destination fields of a route, that is, the ID without the ifindex + * and gateway (meaning: network/plen,metric). + * The reason for this is that `ip route change` can replace an existing route + * and modify its ifindex/gateway. Effectively, that means it deletes an existing + * route and adds a different one (as the ID of the route changes). However, it only + * sends one RTM_NEWADDR notification without notifying about the deletion. We detect + * that by having this index to contain overlapping routes which require special + * cache-resync. */ + NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, + + /* a filter for objects that track an explicit address family. + * + * Note that currently on NMPObjectRoutingRule is indexed by this filter. */ + NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY, + + __NMP_CACHE_ID_TYPE_MAX, + NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1, +} NMPCacheIdType; + +typedef struct { + NMDedupMultiObjClass parent; + const char * obj_type_name; + const char * signal_type; + const guint8 * supported_cache_ids; + int sizeof_data; + int sizeof_public; + int addr_family; + int rtm_gettype; + NMPObjectType obj_type; + NMPlatformSignalIdType signal_type_id; + + /* Only for NMPObjectLnk* types. */ + NMLinkType lnk_link_type; + + void (*cmd_obj_hash_update)(const NMPObject *obj, NMHashState *h); + int (*cmd_obj_cmp)(const NMPObject *obj1, const NMPObject *obj2); + void (*cmd_obj_copy)(NMPObject *dst, const NMPObject *src); + void (*cmd_obj_dispose)(NMPObject *obj); + gboolean (*cmd_obj_is_alive)(const NMPObject *obj); + gboolean (*cmd_obj_is_visible)(const NMPObject *obj); + const char *(*cmd_obj_to_string)(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size); + + /* functions that operate on NMPlatformObject */ + void (*cmd_plobj_id_copy)(NMPlatformObject *dst, const NMPlatformObject *src); + int (*cmd_plobj_id_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); + void (*cmd_plobj_id_hash_update)(const NMPlatformObject *obj, NMHashState *h); + const char *(*cmd_plobj_to_string_id)(const NMPlatformObject *obj, char *buf, gsize buf_size); + const char *(*cmd_plobj_to_string)(const NMPlatformObject *obj, char *buf, gsize len); + void (*cmd_plobj_hash_update)(const NMPlatformObject *obj, NMHashState *h); + int (*cmd_plobj_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); +} NMPClass; + +extern const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX]; + +typedef struct { + NMPlatformLink _public; + + struct { + bool is_in_netlink; + + /* Additional data that depends on the link-type (IFLA_INFO_DATA) */ + const NMPObject *lnk; + } netlink; + + struct { + /* note that "struct udev_device" references the library context + * "struct udev", but doesn't own it. + * + * Hence, the udev.device shall not be used after the library + * context is destroyed. + * + * In case of NMPObjectLink instances that you obtained from the + * platform cache, that means that you shall no keep references + * to those instances that outlife the NMPlatform instance. + * + * In practice, the requirement is less strict and you'll be even + * fine if the platform instance (and the "struct udev" instance) + * are already destroyed while you still hold onto a reference to + * the NMPObjectLink instance. Just don't make use of udev functions + * that cause access to the udev library context. + */ + struct udev_device *device; + } udev; + + /* Auxiliary data object for Wi-Fi and WPAN */ + GObject *ext_data; + + /* FIXME: not every NMPObjectLink should pay the price for tracking + * the wireguard family id. This should be tracked via ext_data, which + * would be exactly the right place. */ + int wireguard_family_id; +} NMPObjectLink; + +typedef struct { + NMPlatformLnkBridge _public; +} NMPObjectLnkBridge; + +typedef struct { + NMPlatformLnkGre _public; +} NMPObjectLnkGre; + +typedef struct { + NMPlatformLnkInfiniband _public; +} NMPObjectLnkInfiniband; + +typedef struct { + NMPlatformLnkIp6Tnl _public; +} NMPObjectLnkIp6Tnl; + +typedef struct { + NMPlatformLnkIpIp _public; +} NMPObjectLnkIpIp; + +typedef struct { + NMPlatformLnkMacsec _public; +} NMPObjectLnkMacsec; + +typedef struct { + NMPlatformLnkMacvlan _public; +} NMPObjectLnkMacvlan; + +typedef NMPObjectLnkMacvlan NMPObjectLnkMacvtap; + +typedef struct { + NMPlatformLnkSit _public; +} NMPObjectLnkSit; + +typedef struct { + NMPlatformLnkTun _public; +} NMPObjectLnkTun; + +typedef struct { + NMPlatformLnkVlan _public; + + guint n_ingress_qos_map; + guint n_egress_qos_map; + const NMVlanQosMapping *ingress_qos_map; + const NMVlanQosMapping *egress_qos_map; +} NMPObjectLnkVlan; + +typedef struct { + NMPlatformLnkVrf _public; +} NMPObjectLnkVrf; + +typedef struct { + NMPlatformLnkVxlan _public; +} NMPObjectLnkVxlan; + +typedef struct { + NMPlatformLnkWireGuard _public; + const NMPWireGuardPeer * peers; + const NMPWireGuardAllowedIP *_allowed_ips_buf; + guint peers_len; + guint _allowed_ips_buf_len; +} NMPObjectLnkWireGuard; + +typedef struct { + NMPlatformIP4Address _public; +} NMPObjectIP4Address; + +typedef struct { + NMPlatformIP4Route _public; +} NMPObjectIP4Route; + +typedef struct { + NMPlatformIP6Address _public; +} NMPObjectIP6Address; + +typedef struct { + NMPlatformIP6Route _public; +} NMPObjectIP6Route; + +typedef struct { + NMPlatformRoutingRule _public; +} NMPObjectRoutingRule; + +typedef struct { + NMPlatformQdisc _public; +} NMPObjectQdisc; + +typedef struct { + NMPlatformTfilter _public; +} NMPObjectTfilter; + +struct _NMPObject { + union { + NMDedupMultiObj parent; + const NMPClass *_class; + }; + union { + NMPlatformObject object; + + NMPlatformObjWithIfindex obj_with_ifindex; + + NMPlatformLink link; + NMPObjectLink _link; + + NMPlatformLnkBridge lnk_bridge; + NMPObjectLnkBridge _lnk_bridge; + + NMPlatformLnkGre lnk_gre; + NMPObjectLnkGre _lnk_gre; + + NMPlatformLnkInfiniband lnk_infiniband; + NMPObjectLnkInfiniband _lnk_infiniband; + + NMPlatformLnkIpIp lnk_ipip; + NMPObjectLnkIpIp _lnk_ipip; + + NMPlatformLnkIp6Tnl lnk_ip6tnl; + NMPObjectLnkIp6Tnl _lnk_ip6tnl; + + NMPlatformLnkMacsec lnk_macsec; + NMPObjectLnkMacsec _lnk_macsec; + + NMPlatformLnkMacvlan lnk_macvlan; + NMPObjectLnkMacvlan _lnk_macvlan; + + NMPlatformLnkSit lnk_sit; + NMPObjectLnkSit _lnk_sit; + + NMPlatformLnkTun lnk_tun; + NMPObjectLnkTun _lnk_tun; + + NMPlatformLnkVlan lnk_vlan; + NMPObjectLnkVlan _lnk_vlan; + + NMPlatformLnkVrf lnk_vrf; + NMPObjectLnkVrf _lnk_vrf; + + NMPlatformLnkVxlan lnk_vxlan; + NMPObjectLnkVxlan _lnk_vxlan; + + NMPlatformLnkWireGuard lnk_wireguard; + NMPObjectLnkWireGuard _lnk_wireguard; + + NMPlatformIPAddress ip_address; + NMPlatformIPXAddress ipx_address; + NMPlatformIP4Address ip4_address; + NMPlatformIP6Address ip6_address; + NMPObjectIP4Address _ip4_address; + NMPObjectIP6Address _ip6_address; + + NMPlatformIPRoute ip_route; + NMPlatformIPXRoute ipx_route; + NMPlatformIP4Route ip4_route; + NMPlatformIP6Route ip6_route; + NMPObjectIP4Route _ip4_route; + NMPObjectIP6Route _ip6_route; + + NMPlatformRoutingRule routing_rule; + NMPObjectRoutingRule _routing_rule; + + NMPlatformQdisc qdisc; + NMPObjectQdisc _qdisc; + NMPlatformTfilter tfilter; + NMPObjectTfilter _tfilter; + }; +}; + +/*****************************************************************************/ + +static inline gboolean +NMP_CLASS_IS_VALID(const NMPClass *klass) +{ + return klass >= &_nmp_classes[0] && klass <= &_nmp_classes[G_N_ELEMENTS(_nmp_classes)] + && ((((char *) klass) - ((char *) _nmp_classes)) % (sizeof(_nmp_classes[0]))) == 0; +} + +static inline const NMPClass * +nmp_class_from_type(NMPObjectType obj_type) +{ + nm_assert(obj_type > 0); + nm_assert(obj_type <= G_N_ELEMENTS(_nmp_classes)); + nm_assert(_nmp_classes[obj_type - 1].obj_type == obj_type); + nm_assert(NMP_CLASS_IS_VALID(&_nmp_classes[obj_type - 1])); + + return &_nmp_classes[obj_type - 1]; +} + +static inline NMPObject * +NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj) +{ + NMPObject *obj; + + obj = plobj ? (NMPObject *) (&(((char *) plobj)[-((int) G_STRUCT_OFFSET(NMPObject, object))])) + : NULL; + nm_assert(!obj || (obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class))); + return obj; +} +#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST((const NMPlatformObject *) (plobj))) + +static inline gboolean +NMP_OBJECT_IS_VALID(const NMPObject *obj) +{ + nm_assert(!obj || (obj && obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class))); + + /* There isn't really much to check. Either @obj is NULL, or we must + * assume that it points to valid memory. */ + return obj != NULL; +} + +static inline gboolean +NMP_OBJECT_IS_STACKINIT(const NMPObject *obj) +{ + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + return obj && obj->parent._ref_count == NM_OBJ_REF_COUNT_STACKINIT; +} + +static inline const NMPClass * +NMP_OBJECT_GET_CLASS(const NMPObject *obj) +{ + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + return obj->_class; +} + +static inline NMPObjectType +NMP_OBJECT_GET_TYPE(const NMPObject *obj) +{ + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN; +} + +static inline gboolean +_NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type) +{ + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + + case NMP_OBJECT_TYPE_QDISC: + + case NMP_OBJECT_TYPE_TFILTER: + + case NMP_OBJECT_TYPE_LNK_BRIDGE: + case NMP_OBJECT_TYPE_LNK_GRE: + case NMP_OBJECT_TYPE_LNK_GRETAP: + case NMP_OBJECT_TYPE_LNK_INFINIBAND: + case NMP_OBJECT_TYPE_LNK_IP6TNL: + case NMP_OBJECT_TYPE_LNK_IP6GRE: + case NMP_OBJECT_TYPE_LNK_IP6GRETAP: + case NMP_OBJECT_TYPE_LNK_IPIP: + case NMP_OBJECT_TYPE_LNK_MACSEC: + case NMP_OBJECT_TYPE_LNK_MACVLAN: + case NMP_OBJECT_TYPE_LNK_MACVTAP: + case NMP_OBJECT_TYPE_LNK_SIT: + case NMP_OBJECT_TYPE_LNK_TUN: + case NMP_OBJECT_TYPE_LNK_VLAN: + case NMP_OBJECT_TYPE_LNK_VRF: + case NMP_OBJECT_TYPE_LNK_VXLAN: + case NMP_OBJECT_TYPE_LNK_WIREGUARD: + return TRUE; + + case NMP_OBJECT_TYPE_ROUTING_RULE: + return FALSE; + + case NMP_OBJECT_TYPE_UNKNOWN: + case __NMP_OBJECT_TYPE_LAST: + break; + } + nm_assert_not_reached(); + return FALSE; +} + +#define NMP_OBJECT_CAST_OBJECT(obj) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert ( !_obj \ + || nmp_class_from_type (NMP_OBJECT_GET_TYPE (_obj)))); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->object : NULL; \ + }) + +#define NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert(!_obj || _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMP_OBJECT_GET_TYPE(_obj))); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->obj_with_ifindex : NULL; \ + }) + +#define _NMP_OBJECT_CAST(obj, field, ...) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert(!_obj || NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj), __VA_ARGS__)); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->field : NULL; \ + }) + +#define NMP_OBJECT_CAST_LINK(obj) _NMP_OBJECT_CAST(obj, link, NMP_OBJECT_TYPE_LINK) +#define NMP_OBJECT_CAST_IP_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ipx_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip4_address, NMP_OBJECT_TYPE_IP4_ADDRESS) +#define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip6_address, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IP_ROUTE(obj) \ + _NMP_OBJECT_CAST(obj, ip_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_IPX_ROUTE(obj) \ + _NMP_OBJECT_CAST(obj, ipx_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_IP4_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip4_route, NMP_OBJECT_TYPE_IP4_ROUTE) +#define NMP_OBJECT_CAST_IP6_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip6_route, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \ + _NMP_OBJECT_CAST(obj, routing_rule, NMP_OBJECT_TYPE_ROUTING_RULE) +#define NMP_OBJECT_CAST_QDISC(obj) _NMP_OBJECT_CAST(obj, qdisc, NMP_OBJECT_TYPE_QDISC) +#define NMP_OBJECT_CAST_TFILTER(obj) _NMP_OBJECT_CAST(obj, tfilter, NMP_OBJECT_TYPE_TFILTER) +#define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) \ + _NMP_OBJECT_CAST(obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD) +#define NMP_OBJECT_CAST_LNK_BRIDGE(obj) \ + _NMP_OBJECT_CAST(obj, lnk_bridge, NMP_OBJECT_TYPE_LNK_BRIDGE) + +static inline int +NMP_OBJECT_TYPE_TO_ADDR_FAMILY(NMPObjectType obj_type) +{ + return nmp_class_from_type(obj_type)->addr_family; +} + +static inline int +NMP_OBJECT_GET_ADDR_FAMILY(const NMPObject *obj) +{ + return NMP_OBJECT_GET_CLASS(obj)->addr_family; +} + +static inline const NMPObject * +nmp_object_ref(const NMPObject *obj) +{ + if (!obj) { + /* for convenience, allow NULL. */ + return NULL; + } + + /* ref and unref accept const pointers. NMPObject is supposed to be shared + * and kept immutable. Disallowing to take/return a reference to a const + * NMPObject is cumbersome, because callers are precisely expected to + * keep a ref on the otherwise immutable object. */ + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + g_return_val_if_fail(obj->parent._ref_count != NM_OBJ_REF_COUNT_STACKINIT, NULL); + + return (const NMPObject *) nm_dedup_multi_obj_ref((const NMDedupMultiObj *) obj); +} + +static inline void +nmp_object_unref(const NMPObject *obj) +{ + if (obj) { + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + nm_dedup_multi_obj_unref((const NMDedupMultiObj *) obj); + } +} + +#define nm_clear_nmp_object(ptr) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + typeof(*_ptr) _pptr; \ + gboolean _changed = FALSE; \ + \ + if (_ptr && (_pptr = *_ptr)) { \ + *_ptr = NULL; \ + nmp_object_unref(_pptr); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +static inline gboolean +nmp_object_ref_set(const NMPObject **pp, const NMPObject *obj) +{ + gboolean _changed = FALSE; + const NMPObject *p; + + nm_assert(!pp || !*pp || NMP_OBJECT_IS_VALID(*pp)); + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + if (pp && ((p = *pp) != obj)) { + nmp_object_ref(obj); + *pp = obj; + nmp_object_unref(p); + _changed = TRUE; + } + return _changed; +} + +NMPObject *nmp_object_new(NMPObjectType obj_type, gconstpointer plobj); +NMPObject *nmp_object_new_link(int ifindex); + +const NMPObject *nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj); + +static inline NMPObject * +nmp_object_stackinit_obj(NMPObject *obj, const NMPObject *src) +{ + return obj == src + ? obj + : (NMPObject *) nmp_object_stackinit(obj, NMP_OBJECT_GET_TYPE(src), &src->object); +} + +const NMPObject *nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src); +const NMPObject *nmp_object_stackinit_id_link(NMPObject *obj, int ifindex); +const NMPObject *nmp_object_stackinit_id_ip4_address(NMPObject *obj, + int ifindex, + guint32 address, + guint8 plen, + guint32 peer_address); +const NMPObject * +nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address); + +const char *nmp_object_to_string(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size); +void nmp_object_hash_update(const NMPObject *obj, NMHashState *h); +int nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2); + +static inline gboolean +nmp_object_equal(const NMPObject *obj1, const NMPObject *obj2) +{ + return nmp_object_cmp(obj1, obj2) == 0; +} + +void nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only); +NMPObject *nmp_object_clone(const NMPObject *obj, gboolean id_only); + +int nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2); +void nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h); +guint nmp_object_id_hash(const NMPObject *obj); + +static inline gboolean +nmp_object_id_equal(const NMPObject *obj1, const NMPObject *obj2) +{ + return nmp_object_id_cmp(obj1, obj2) == 0; +} + +guint nmp_object_indirect_id_hash(gconstpointer a); +gboolean nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b); + +gboolean nmp_object_is_alive(const NMPObject *obj); +gboolean nmp_object_is_visible(const NMPObject *obj); + +void +_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev); + +static inline void +_nm_auto_nmpobj_cleanup(gpointer p) +{ + nmp_object_unref(*((const NMPObject **) p)); +} +#define nm_auto_nmpobj nm_auto(_nm_auto_nmpobj_cleanup) + +typedef struct _NMPCache NMPCache; + +typedef void (*NMPCachePreHook)(NMPCache * cache, + const NMPObject *old, + const NMPObject *new, + NMPCacheOpsType ops_type, + gpointer user_data); +typedef gboolean (*NMPObjectMatchFn)(const NMPObject *obj, gpointer user_data); + +const NMDedupMultiEntry *nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj); +const NMDedupMultiEntry *nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj); +const NMDedupMultiEntry *nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex); +const NMPObject * nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj); +const NMPObject * nmp_cache_lookup_link(const NMPCache *cache, int ifindex); + +typedef struct _NMPLookup NMPLookup; + +struct _NMPLookup { + NMPCacheIdType cache_id_type; + NMPObject selector_obj; +}; + +const NMDedupMultiHeadEntry *nmp_cache_lookup_all(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *select_obj); + +static inline const NMDedupMultiHeadEntry * +nmp_cache_lookup(const NMPCache *cache, const NMPLookup *lookup) +{ + return nmp_cache_lookup_all(cache, lookup->cache_id_type, &lookup->selector_obj); +} + +const NMPLookup *nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type); +const NMPLookup *nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname); +const NMPLookup *nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex); +const NMPLookup *nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type); +const NMPLookup *nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj); +const NMPLookup *nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos); +const NMPLookup *nmp_lookup_init_ip6_route_by_weak_id(NMPLookup * lookup, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); +const NMPLookup * +nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family); + +GArray *nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry, + NMPObjectType obj_type, + gboolean visible_only); + +static inline gboolean +nmp_cache_iter_next(NMDedupMultiIter *iter, const NMPObject **out_obj) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(iter); + nm_assert(!has_next || NMP_OBJECT_IS_VALID(iter->current->obj)); + if (out_obj) + *out_obj = has_next ? iter->current->obj : NULL; + return has_next; +} + +static inline gboolean +nmp_cache_iter_next_link(NMDedupMultiIter *iter, const NMPlatformLink **out_obj) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(iter); + nm_assert(!has_next || NMP_OBJECT_GET_TYPE(iter->current->obj) == NMP_OBJECT_TYPE_LINK); + if (out_obj) + *out_obj = has_next ? &(((const NMPObject *) iter->current->obj)->link) : NULL; + return has_next; +} + +#define nmp_cache_iter_for_each(iter, head, obj) \ + for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next((iter), (obj));) + +#define nmp_cache_iter_for_each_link(iter, head, obj) \ + for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next_link((iter), (obj));) + +const NMPObject *nmp_cache_lookup_link_full(const NMPCache * cache, + int ifindex, + const char * ifname, + gboolean visible_only, + NMLinkType link_type, + NMPObjectMatchFn match_fn, + gpointer user_data); + +gboolean nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave); +gboolean nmp_cache_link_connected_needs_toggle(const NMPCache * cache, + const NMPObject *master, + const NMPObject *potential_slave, + const NMPObject *ignore_slave); +const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache, + int master_ifindex, + const NMPObject *potential_slave, + const NMPObject *ignore_slave); + +gboolean nmp_cache_use_udev_get(const NMPCache *cache); + +void nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache); + +NMPCacheOpsType nmp_cache_remove(NMPCache * cache, + const NMPObject * obj_needle, + gboolean equals_by_ptr, + gboolean only_dirty, + const NMPObject **out_obj_old); +NMPCacheOpsType nmp_cache_remove_netlink(NMPCache * cache, + const NMPObject * obj_needle, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); +NMPCacheOpsType nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); +NMPCacheOpsType nmp_cache_update_netlink_route(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + guint16 nlmsgflags, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + const NMPObject **out_obj_replace, + gboolean * out_resync_required); +NMPCacheOpsType nmp_cache_update_link_udev(NMPCache * cache, + int ifindex, + struct udev_device *udevice, + const NMPObject ** out_obj_old, + const NMPObject ** out_obj_new); +NMPCacheOpsType nmp_cache_update_link_master_connected(NMPCache * cache, + int ifindex, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); + +static inline const NMDedupMultiEntry * +nmp_cache_reresolve_main_entry(NMPCache * cache, + const NMDedupMultiEntry *entry, + const NMPLookup * lookup) +{ + const NMDedupMultiEntry *main_entry; + + nm_assert(cache); + nm_assert(entry); + nm_assert(lookup); + + if (lookup->cache_id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) { + nm_assert(entry == nmp_cache_lookup_entry(cache, entry->obj)); + return entry; + } + + /* we only track the dirty flag for the OBJECT-TYPE index. That means, + * for other lookup types we need to check the dirty flag of the main-entry. */ + main_entry = nmp_cache_lookup_entry(cache, entry->obj); + + nm_assert(main_entry); + nm_assert(main_entry->obj == entry->obj); + + return main_entry; +} + +void nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup); + +NMPCache *nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev); +void nmp_cache_free(NMPCache *cache); + +static inline void +ASSERT_nmp_cache_ops(const NMPCache * cache, + NMPCacheOpsType ops_type, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ +#if NM_MORE_ASSERTS + nm_assert(cache); + nm_assert(obj_old || obj_new); + nm_assert(!obj_old + || (NMP_OBJECT_IS_VALID(obj_old) && !NMP_OBJECT_IS_STACKINIT(obj_old) + && nmp_object_is_alive(obj_old))); + nm_assert(!obj_new + || (NMP_OBJECT_IS_VALID(obj_new) && !NMP_OBJECT_IS_STACKINIT(obj_new) + && nmp_object_is_alive(obj_new))); + + switch (ops_type) { + case NMP_CACHE_OPS_UNCHANGED: + nm_assert(obj_old == obj_new); + break; + case NMP_CACHE_OPS_ADDED: + nm_assert(!obj_old && obj_new); + break; + case NMP_CACHE_OPS_UPDATED: + nm_assert(obj_old && obj_new && obj_old != obj_new); + break; + case NMP_CACHE_OPS_REMOVED: + nm_assert(obj_old && !obj_new); + break; + default: + nm_assert_not_reached(); + } + + nm_assert(obj_new == NULL || obj_old == NULL || nmp_object_id_equal(obj_new, obj_old)); + nm_assert(!obj_old || !obj_new + || NMP_OBJECT_GET_CLASS(obj_old) == NMP_OBJECT_GET_CLASS(obj_new)); + + nm_assert(obj_new == nmp_cache_lookup_obj(cache, obj_new ?: obj_old)); +#endif +} + +const NMDedupMultiHeadEntry * +nm_platform_lookup_all(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj); + +const NMDedupMultiEntry * +nm_platform_lookup_entry(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj); + +static inline const NMPObject * +nm_platform_lookup_obj(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nm_dedup_multi_entry_get_obj(nm_platform_lookup_entry(platform, cache_id_type, obj)); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_obj_type(NMPlatform *platform, NMPObjectType obj_type) +{ + NMPLookup lookup; + + nmp_lookup_init_obj_type(&lookup, obj_type); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_link_by_ifname(NMPlatform *platform, const char *ifname) +{ + NMPLookup lookup; + + nmp_lookup_init_link_by_ifname(&lookup, ifname); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_object(NMPlatform *platform, NMPObjectType obj_type, int ifindex) +{ + NMPLookup lookup; + + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nm_platform_lookup(platform, &lookup); +} + +static inline GPtrArray * +nm_platform_lookup_object_clone(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + NMPLookup lookup; + + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nm_platform_lookup_clone(platform, &lookup, predicate, user_data); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_route_default(NMPlatform *platform, NMPObjectType obj_type) +{ + NMPLookup lookup; + + nmp_lookup_init_route_default(&lookup, obj_type); + return nm_platform_lookup(platform, &lookup); +} + +static inline GPtrArray * +nm_platform_lookup_route_default_clone(NMPlatform * platform, + NMPObjectType obj_type, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + NMPLookup lookup; + + nmp_lookup_init_route_default(&lookup, obj_type); + return nm_platform_lookup_clone(platform, &lookup, predicate, user_data); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_ip4_route_by_weak_id(NMPlatform *platform, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos) +{ + NMPLookup lookup; + + nmp_lookup_init_ip4_route_by_weak_id(&lookup, network, plen, metric, tos); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_ip6_route_by_weak_id(NMPlatform * platform, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + NMPLookup lookup; + + nmp_lookup_init_ip6_route_by_weak_id(&lookup, network, plen, metric, src, src_plen); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_object_by_addr_family(NMPlatform * platform, + NMPObjectType obj_type, + int addr_family) +{ + NMPLookup lookup; + + nmp_lookup_init_object_by_addr_family(&lookup, obj_type, addr_family); + return nm_platform_lookup(platform, &lookup); +} + +/*****************************************************************************/ + +static inline const char * +nmp_object_link_get_ifname(const NMPObject *obj) +{ + if (!obj) + return NULL; + return NMP_OBJECT_CAST_LINK(obj)->name; +} + +static inline gboolean +nmp_object_ip_route_is_best_defaut_route(const NMPObject *obj) +{ + const NMPlatformIPRoute *r = NMP_OBJECT_CAST_IP_ROUTE(obj); + + /* return whether @obj is considered a default-route. + * + * NMIP4Config/NMIP6Config tracks the (best) default-route explicitly, because + * at various places we act differently depending on whether there is a default-route + * configured. + * + * Note that this only considers the main routing table. */ + return r && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) + && nm_platform_route_table_is_main(r->table_coerced) + && r->type_coerced == nm_platform_route_type_coerce(1 /* RTN_UNICAST */); +} + +static inline gboolean +nmp_object_ip6_address_is_not_link_local(const NMPObject *obj) +{ + return !IN6_IS_ADDR_LINKLOCAL(&NMP_OBJECT_CAST_IP6_ADDRESS(obj)->address); +} + +/*****************************************************************************/ + +static inline gboolean +nm_platform_dedup_multi_iter_next_obj(NMDedupMultiIter *ipconf_iter, + const NMPObject **out_obj, + NMPObjectType assert_obj_type) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(ipconf_iter); + nm_assert(assert_obj_type == NMP_OBJECT_TYPE_UNKNOWN || !has_next + || NMP_OBJECT_GET_TYPE(ipconf_iter->current->obj) == assert_obj_type); + NM_SET_OUT(out_obj, has_next ? ipconf_iter->current->obj : NULL); + return has_next; +} + +#define _nm_platform_dedup_multi_iter_next(ipconf_iter, out_obj, field, ...) \ + ({ \ + NMDedupMultiIter *const _ipconf_iter = (ipconf_iter); \ + const typeof(((NMPObject *) NULL)->field) **const _out_obj = (out_obj); \ + gboolean _has_next; \ + \ + if (G_LIKELY(nm_dedup_multi_iter_next(_ipconf_iter))) { \ + if (_out_obj) { \ + *_out_obj = _NMP_OBJECT_CAST(_ipconf_iter->current->obj, field, __VA_ARGS__); \ + } else { \ + nm_assert( \ + NM_IN_SET(NMP_OBJECT_GET_TYPE(_ipconf_iter->current->obj), __VA_ARGS__)); \ + } \ + _has_next = TRUE; \ + } else { \ + if (_out_obj) \ + *_out_obj = NULL; \ + _has_next = FALSE; \ + } \ + _has_next; \ + }) + +#define nm_platform_dedup_multi_iter_next_ip_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip_address, \ + NMP_OBJECT_TYPE_IP4_ADDRESS, \ + NMP_OBJECT_TYPE_IP6_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip4_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip4_address, \ + NMP_OBJECT_TYPE_IP4_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip6_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip6_address, \ + NMP_OBJECT_TYPE_IP6_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip_route, \ + NMP_OBJECT_TYPE_IP4_ROUTE, \ + NMP_OBJECT_TYPE_IP6_ROUTE) + +#define nm_platform_dedup_multi_iter_next_ip4_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip4_route, \ + NMP_OBJECT_TYPE_IP4_ROUTE) + +#define nm_platform_dedup_multi_iter_next_ip6_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip6_route, \ + NMP_OBJECT_TYPE_IP6_ROUTE) + +#endif /* __NMP_OBJECT_H__ */ diff --git a/src/core/platform/nmp-rules-manager.c b/src/core/platform/nmp-rules-manager.c new file mode 100644 index 0000000..ab329c9 --- /dev/null +++ b/src/core/platform/nmp-rules-manager.c @@ -0,0 +1,808 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include "nmp-rules-manager.h" + +#include +#include + +#include "nm-std-aux/c-list-util.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +struct _NMPRulesManager { + NMPlatform *platform; + GHashTable *by_obj; + GHashTable *by_user_tag; + GHashTable *by_data; + guint ref_count; +}; + +/*****************************************************************************/ + +static void _rules_init(NMPRulesManager *self); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "rules-manager" + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static gboolean +NMP_IS_RULES_MANAGER(gpointer self) +{ + return self && ((NMPRulesManager *) self)->ref_count > 0 + && NM_IS_PLATFORM(((NMPRulesManager *) self)->platform); +} + +#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr(1240261787u, (user_tag)) + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gconstpointer user_tag; + CList obj_lst; + CList user_tag_lst; + + /* track_priority_val zero is special: those are weakly tracked rules. + * That means: NetworkManager will restore them only if it removed them earlier. + * But it will not remove or add them otherwise. + * + * Otherwise, the track_priority_val goes together with track_priority_present. + * In case of one rule being tracked multiple times (with different priorities), + * the one with higher priority wins. See _rules_obj_get_best_data(). + * Then, the winning present state either enforces that the rule is present + * or absent. + * + * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming + * that it was added externally by the user. But unlike weakly tracked rules, + * NM will *not* restore such rules if NetworkManager themself removed them. */ + guint32 track_priority_val; + bool track_priority_present : 1; + + bool dirty : 1; +} RulesData; + +typedef enum { + CONFIG_STATE_NONE = 0, + CONFIG_STATE_ADDED_BY_US = 1, + CONFIG_STATE_REMOVED_BY_US = 2, + + /* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE). + * + * Maybe we would only need to track whether we touched the rule at all. But we + * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US) + * or did we remove it (CONFIG_STATE_REMOVED_BY_US)? + * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively + * add/remove it, but whenever we are about to undo the add/remove, we need to do it. + * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally + * force the state next time when necessary. */ + CONFIG_STATE_OWNED_BY_US = 3, +} ConfigState; + +typedef struct { + const NMPObject *obj; + CList obj_lst_head; + + /* indicates whether we configured/removed the rule (during sync()). We need that, so + * if the rule gets untracked, that we know to remove/restore it. + * + * This makes NMPRulesManager stateful (beyond the configuration that indicates + * which rules are tracked). + * After a restart, NetworkManager would no longer remember which rules were added + * by us. + * + * That is partially fixed by NetworkManager taking over the rules that it + * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */ + ConfigState config_state; +} RulesObjData; + +typedef struct { + gconstpointer user_tag; + CList user_tag_lst_head; +} RulesUserTagData; + +/*****************************************************************************/ + +static void _rules_data_untrack(NMPRulesManager *self, + RulesData * rules_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us); + +/*****************************************************************************/ + +static void +_rules_data_assert(const RulesData *rules_data, gboolean linked) +{ + nm_assert(rules_data); + nm_assert(NMP_OBJECT_GET_TYPE(rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + nm_assert(nmp_object_is_visible(rules_data->obj)); + nm_assert(rules_data->user_tag); + nm_assert(!linked || !c_list_is_empty(&rules_data->obj_lst)); + nm_assert(!linked || !c_list_is_empty(&rules_data->user_tag_lst)); +} + +static guint +_rules_data_hash(gconstpointer data) +{ + const RulesData *rules_data = data; + NMHashState h; + + _rules_data_assert(rules_data, FALSE); + + nm_hash_init(&h, 269297543u); + nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(rules_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + nm_hash_update_val(&h, rules_data->user_tag); + return nm_hash_complete(&h); +} + +static gboolean +_rules_data_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesData *rules_data_a = data_a; + const RulesData *rules_data_b = data_b; + + _rules_data_assert(rules_data_a, FALSE); + _rules_data_assert(rules_data_b, FALSE); + + return rules_data_a->user_tag == rules_data_b->user_tag + && (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(rules_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE(rules_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); +} + +static void +_rules_data_destroy(gpointer data) +{ + RulesData *rules_data = data; + + _rules_data_assert(rules_data, FALSE); + + c_list_unlink_stale(&rules_data->obj_lst); + c_list_unlink_stale(&rules_data->user_tag_lst); + nmp_object_unref(rules_data->obj); + g_slice_free(RulesData, rules_data); +} + +static const RulesData * +_rules_obj_get_best_data(RulesObjData *obj_data) +{ + RulesData * rules_data; + const RulesData *rd_best = NULL; + + c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) { + _rules_data_assert(rules_data, TRUE); + + if (rd_best) { + if (rd_best->track_priority_val > rules_data->track_priority_val) + continue; + if (rd_best->track_priority_val == rules_data->track_priority_val) { + if (rd_best->track_priority_present || !rules_data->track_priority_present) { + /* if the priorities are identical, then "present" wins over + * "!present" (absent). */ + continue; + } + } + } + + rd_best = rules_data; + } + + return rd_best; +} + +static guint +_rules_obj_hash(gconstpointer data) +{ + const RulesObjData *obj_data = data; + NMHashState h; + + nm_hash_init(&h, 432817559u); + nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + return nm_hash_complete(&h); +} + +static gboolean +_rules_obj_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesObjData *obj_data_a = data_a; + const RulesObjData *obj_data_b = data_b; + + return (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE(obj_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); +} + +static void +_rules_obj_destroy(gpointer data) +{ + RulesObjData *obj_data = data; + + c_list_unlink_stale(&obj_data->obj_lst_head); + nmp_object_unref(obj_data->obj); + g_slice_free(RulesObjData, obj_data); +} + +static guint +_rules_user_tag_hash(gconstpointer data) +{ + const RulesUserTagData *user_tag_data = data; + + return nm_hash_val(644693447u, user_tag_data->user_tag); +} + +static gboolean +_rules_user_tag_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesUserTagData *user_tag_data_a = data_a; + const RulesUserTagData *user_tag_data_b = data_b; + + return user_tag_data_a->user_tag == user_tag_data_b->user_tag; +} + +static void +_rules_user_tag_destroy(gpointer data) +{ + RulesUserTagData *user_tag_data = data; + + c_list_unlink_stale(&user_tag_data->user_tag_lst_head); + g_slice_free(RulesUserTagData, user_tag_data); +} + +static RulesData * +_rules_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag) +{ + RulesData rules_data_needle = { + .obj = obj, + .user_tag = user_tag, + }; + + return g_hash_table_lookup(by_data, &rules_data_needle); +} + +/** + * nmp_rules_manager_track: + * @self: the #NMPRulesManager instance + * @routing_rule: the #NMPlatformRoutingRule to track or untrack + * @track_priority: the priority for tracking the rule. Note that + * negative values indicate a forced absence of the rule. Priorities + * are compared with their absolute values (with higher absolute + * value being more important). For example, if you track the same + * rule twice, once with priority -5 and +10, then the rule is + * present (because the positive number is more important). + * The special value 0 indicates weakly-tracked rules. + * @user_tag: the tag associated with tracking this rule. The same tag + * must be used to untrack the rule later. + * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag + * for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(), + * because it enforces ownership of the now tracked rule. On the other hand, + * a plain nmp_rules_manager_untrack() merely forgets about the tracking. + * The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. + */ +void +nmp_rules_manager_track(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack) +{ + NMPObject obj_stack; + const NMPObject * p_obj_stack; + RulesData * rules_data; + RulesObjData * obj_data; + RulesUserTagData *user_tag_data; + gboolean changed = FALSE; + guint32 track_priority_val; + gboolean track_priority_present; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(routing_rule); + g_return_if_fail(user_tag); + nm_assert(track_priority != G_MININT32); + + _rules_init(self); + + p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + if (track_priority >= 0) { + track_priority_val = track_priority; + track_priority_present = TRUE; + } else { + track_priority_val = -track_priority; + track_priority_present = FALSE; + } + + rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); + + if (!rules_data) { + rules_data = g_slice_new(RulesData); + *rules_data = (RulesData){ + .obj = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform), + p_obj_stack), + .user_tag = user_tag, + .track_priority_val = track_priority_val, + .track_priority_present = track_priority_present, + .dirty = FALSE, + }; + g_hash_table_add(self->by_data, rules_data); + + obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); + if (!obj_data) { + obj_data = g_slice_new(RulesObjData); + *obj_data = (RulesObjData){ + .obj = nmp_object_ref(rules_data->obj), + .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head), + .config_state = CONFIG_STATE_NONE, + }; + g_hash_table_add(self->by_obj, obj_data); + } + c_list_link_tail(&obj_data->obj_lst_head, &rules_data->obj_lst); + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); + if (!user_tag_data) { + user_tag_data = g_slice_new(RulesUserTagData); + *user_tag_data = (RulesUserTagData){ + .user_tag = user_tag, + .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head), + }; + g_hash_table_add(self->by_user_tag, user_tag_data); + } + c_list_link_tail(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst); + changed = TRUE; + } else { + rules_data->dirty = FALSE; + if (rules_data->track_priority_val != track_priority_val + || rules_data->track_priority_present != track_priority_present) { + rules_data->track_priority_val = track_priority_val; + rules_data->track_priority_present = track_priority_present; + changed = TRUE; + } + } + + if (user_tag_untrack) { + if (user_tag != user_tag_untrack) { + RulesData *rules_data_untrack; + + rules_data_untrack = _rules_data_lookup(self->by_data, p_obj_stack, user_tag_untrack); + if (rules_data_untrack) + _rules_data_untrack(self, rules_data_untrack, FALSE, TRUE); + } else + nm_assert_not_reached(); + } + + _rules_data_assert(rules_data, TRUE); + + if (changed) { + _LOGD("routing-rule: track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] \"%s\")", + _USER_TAG_LOG(rules_data->user_tag), + (rules_data->track_priority_val == 0 + ? "" + : (rules_data->track_priority_present ? "+" : "-")), + (guint) rules_data->track_priority_val, + nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + } +} + +static void +_rules_data_untrack(NMPRulesManager *self, + RulesData * rules_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us) +{ + RulesObjData *obj_data; + + nm_assert(NMP_IS_RULES_MANAGER(self)); + _rules_data_assert(rules_data, TRUE); + nm_assert(self->by_data); + nm_assert(g_hash_table_lookup(self->by_data, rules_data) == rules_data); + + _LOGD("routing-rule: untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] \"%s\"", + _USER_TAG_LOG(rules_data->user_tag), + nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + +#if NM_MORE_ASSERTS + { + RulesUserTagData *user_tag_data; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); + nm_assert(user_tag_data); + nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst)); + } +#endif + + nm_assert(!c_list_is_empty(&rules_data->user_tag_lst)); + + obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); + nm_assert(obj_data); + nm_assert(c_list_contains(&obj_data->obj_lst_head, &rules_data->obj_lst)); + nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &rules_data->obj)); + + if (make_owned_by_us) { + if (obj_data->config_state == CONFIG_STATE_NONE) { + /* we need to mark this entry that it requires a touch on the next + * sync. */ + obj_data->config_state = CONFIG_STATE_OWNED_BY_US; + } + } else if (remove_user_tag_data && c_list_length_is(&rules_data->user_tag_lst, 1)) + g_hash_table_remove(self->by_user_tag, &rules_data->user_tag); + + /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry + * around for the next sync -- so that we can undo what we did earlier. */ + if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&rules_data->obj_lst, 1)) + g_hash_table_remove(self->by_obj, &rules_data->obj); + + g_hash_table_remove(self->by_data, rules_data); +} + +void +nmp_rules_manager_untrack(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + RulesData * rules_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(routing_rule); + g_return_if_fail(user_tag); + + _rules_init(self); + + p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); + if (rules_data) + _rules_data_untrack(self, rules_data, TRUE, FALSE); +} + +void +nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag) +{ + RulesData * rules_data; + RulesUserTagData *user_tag_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst) + rules_data->dirty = TRUE; +} + +void +nmp_rules_manager_untrack_all(NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */) +{ + RulesData * rules_data; + RulesData * rules_data_safe; + RulesUserTagData *user_tag_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry_safe (rules_data, + rules_data_safe, + &user_tag_data->user_tag_lst_head, + user_tag_lst) { + if (all || rules_data->dirty) + _rules_data_untrack(self, rules_data, FALSE, FALSE); + } + if (c_list_is_empty(&user_tag_data->user_tag_lst_head)) + g_hash_table_remove(self->by_user_tag, user_tag_data); +} + +void +nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules) +{ + const NMDedupMultiHeadEntry *pl_head_entry; + NMDedupMultiIter pl_iter; + const NMPObject * plobj; + gs_unref_ptrarray GPtrArray *rules_to_delete = NULL; + RulesObjData * obj_data; + GHashTableIter h_iter; + guint i; + const RulesData * rd_best; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (!self->by_data) + return; + + _LOGD("sync%s", keep_deleted_rules ? " (don't remove any rules)" : ""); + + pl_head_entry = nm_platform_lookup_obj_type(self->platform, NMP_OBJECT_TYPE_ROUTING_RULE); + if (pl_head_entry) { + nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) { + obj_data = g_hash_table_lookup(self->by_obj, &plobj); + + if (!obj_data) { + /* this rule is not tracked. It was externally added, hence we + * ignore it. */ + continue; + } + + rd_best = _rules_obj_get_best_data(obj_data); + if (rd_best) { + if (rd_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + continue; + } + if (rd_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_ADDED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + } + + if (keep_deleted_rules) { + _LOGD("forget/leak rule added by us: %s", + nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + continue; + } + + if (!rules_to_delete) + rules_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(rules_to_delete, (gpointer) nmp_object_ref(plobj)); + + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + } + } + + if (rules_to_delete) { + for (i = 0; i < rules_to_delete->len; i++) + nm_platform_object_delete(self->platform, rules_to_delete->pdata[i]); + } + + g_hash_table_iter_init(&h_iter, self->by_obj); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_data, NULL)) { + rd_best = _rules_obj_get_best_data(obj_data); + + if (!rd_best) { + g_hash_table_iter_remove(&h_iter); + continue; + } + + if (!rd_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + continue; + } + if (rd_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_REMOVED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + + plobj = + nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj); + if (plobj) + continue; + + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + nm_platform_routing_rule_add(self->platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj)); + } +} + +void +nmp_rules_manager_track_from_platform(NMPRulesManager *self, + NMPlatform * platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag) +{ + NMPLookup lookup; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (!platform) + platform = self->platform; + else + g_return_if_fail(NM_IS_PLATFORM(platform)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o); + + if (addr_family != AF_UNSPEC && rr->addr_family != addr_family) + continue; + + nmp_rules_manager_track(self, rr, tracking_priority, user_tag, NULL); + } +} + +/*****************************************************************************/ + +void +nmp_rules_manager_track_default(NMPRulesManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + /* track the default rules. See also `man ip-rule`. */ + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32767, + .table = RT_TABLE_DEFAULT, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } +} + +static void +_rules_init(NMPRulesManager *self) +{ + if (self->by_data) + return; + + self->by_data = + g_hash_table_new_full(_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy); + self->by_obj = + g_hash_table_new_full(_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy); + self->by_user_tag = g_hash_table_new_full(_rules_user_tag_hash, + _rules_user_tag_equal, + NULL, + _rules_user_tag_destroy); +} + +/*****************************************************************************/ + +NMPRulesManager * +nmp_rules_manager_new(NMPlatform *platform) +{ + NMPRulesManager *self; + + g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL); + + self = g_slice_new(NMPRulesManager); + *self = (NMPRulesManager){ + .ref_count = 1, + .platform = g_object_ref(platform), + }; + return self; +} + +void +nmp_rules_manager_ref(NMPRulesManager *self) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + self->ref_count++; +} + +void +nmp_rules_manager_unref(NMPRulesManager *self) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (--self->ref_count > 0) + return; + + if (self->by_data) { + g_hash_table_destroy(self->by_user_tag); + g_hash_table_destroy(self->by_obj); + g_hash_table_destroy(self->by_data); + } + g_object_unref(self->platform); + g_slice_free(NMPRulesManager, self); +} diff --git a/src/core/platform/nmp-rules-manager.h b/src/core/platform/nmp-rules-manager.h new file mode 100644 index 0000000..69cf907 --- /dev/null +++ b/src/core/platform/nmp-rules-manager.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NMP_RULES_MANAGER_H__ +#define __NMP_RULES_MANAGER_H__ + +#include "nm-platform.h" + +/*****************************************************************************/ + +#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new) + +typedef struct _NMPRulesManager NMPRulesManager; + +NMPRulesManager *nmp_rules_manager_new(NMPlatform *platform); + +void nmp_rules_manager_ref(NMPRulesManager *self); +void nmp_rules_manager_unref(NMPRulesManager *self); + +#define nm_auto_unref_rules_manager nm_auto(_nmp_rules_manager_unref) +NM_AUTO_DEFINE_FCN0(NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref); + +void nmp_rules_manager_track(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack); + +void nmp_rules_manager_track_default(NMPRulesManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag); + +void nmp_rules_manager_track_from_platform(NMPRulesManager *self, + NMPlatform * platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag); + +void nmp_rules_manager_untrack(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag); + +void nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag); + +void nmp_rules_manager_untrack_all(NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */); + +void nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules); + +/*****************************************************************************/ + +#endif /* __NMP_RULES_MANAGER_H__ */ diff --git a/src/core/platform/tests/meson.build b/src/core/platform/tests/meson.build new file mode 100644 index 0000000..5d55707 --- /dev/null +++ b/src/core/platform/tests/meson.build @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_fake_c_flags = test_c_flags + ['-DSETUP=nm_fake_platform_setup'] +test_linux_c_flags = test_c_flags + ['-DSETUP=nm_linux_platform_setup'] + +test_units = [ + ['test-address-fake', 'test-address.c', test_fake_c_flags, default_test_timeout], + ['test-address-linux', 'test-address.c', test_linux_c_flags, default_test_timeout], + ['test-cleanup-fake', 'test-cleanup.c', test_fake_c_flags, default_test_timeout], + ['test-cleanup-linux', 'test-cleanup.c', test_linux_c_flags, default_test_timeout], + ['test-link-fake', 'test-link.c', test_fake_c_flags, default_test_timeout], + ['test-link-linux', 'test-link.c', test_linux_c_flags, 900], + ['test-nmp-object', 'test-nmp-object.c', test_c_flags, default_test_timeout], + ['test-platform-general', 'test-platform-general.c', test_c_flags, default_test_timeout], + ['test-route-fake', 'test-route.c', test_fake_c_flags, default_test_timeout], + ['test-route-linux', 'test-route.c', test_linux_c_flags, default_test_timeout], + ['test-tc-fake', 'test-tc.c', test_fake_c_flags, default_test_timeout], + ['test-tc-linux', 'test-tc.c', test_linux_c_flags, default_test_timeout], +] + +foreach test_unit: test_units + exe = executable( + test_unit[0], + test_unit[1], + dependencies: libNetworkManagerTest_dep, + c_args: test_unit[2], + ) + test( + 'platform/' + test_unit[0], + test_script, + timeout: test_unit[3], + args: test_args + [exe.full_path()], + ) +endforeach + +name = 'monitor' + +executable( + name, + name + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) diff --git a/src/core/platform/tests/monitor.c b/src/core/platform/tests/monitor.c new file mode 100644 index 0000000..b67d50b --- /dev/null +++ b/src/core/platform/tests/monitor.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +NMTST_DEFINE(); + +static struct { + gboolean persist; +} global_opt = { + .persist = TRUE, +}; + +static gboolean +read_argv(int *argc, char ***argv) +{ + GOptionContext *context; + GOptionEntry options[] = { + {"no-persist", + 'P', + G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, + &global_opt.persist, + "Exit after processing netlink messages", + NULL}, + {0}, + }; + gs_free_error GError *error = NULL; + + context = g_option_context_new(NULL); + g_option_context_set_summary(context, "Monitor netlink events in NMPlatform."); + g_option_context_add_main_entries(context, options, NULL); + + if (!g_option_context_parse(context, argc, argv, &error)) { + g_warning("Error parsing command line arguments: %s", error->message); + g_option_context_free(context); + return FALSE; + } + + g_option_context_free(context); + return TRUE; +} + +int +main(int argc, char **argv) +{ + GMainLoop *loop; + + if (!g_getenv("G_MESSAGES_DEBUG")) + g_setenv("G_MESSAGES_DEBUG", "all", TRUE); + + nmtst_init_with_logging(&argc, &argv, "DEBUG", "ALL"); + + if (!read_argv(&argc, &argv)) + return 2; + + nm_log_info(LOGD_PLATFORM, "platform monitor start"); + + loop = g_main_loop_new(NULL, FALSE); + + nm_linux_platform_setup(); + + if (global_opt.persist) + g_main_loop_run(loop); + + g_main_loop_unref(loop); + + return EXIT_SUCCESS; +} diff --git a/src/core/platform/tests/test-address.c b/src/core/platform/tests/test-address.c new file mode 100644 index 0000000..b9eabbd --- /dev/null +++ b/src/core/platform/tests/test-address.c @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "test-common.h" + +#define IP4_ADDRESS "192.0.2.1" +#define IP4_ADDRESS_PEER "192.0.2.2" +#define IP4_ADDRESS_PEER2 "192.0.3.1" +#define IP4_PLEN 24 +#define IP6_ADDRESS "2001:db8:a:b:1:2:3:4" +#define IP6_PLEN 64 + +#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX +#define EX NMTSTP_ENV1_EX + +/*****************************************************************************/ + +static void +ip4_address_callback(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Address * received, + NMPlatformSignalChangeType change_type, + SignalData * data) +{ + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +ip6_address_callback(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Address * received, + NMPlatformSignalChangeType change_type, + SignalData * data) +{ + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +/*****************************************************************************/ + +static void +test_ip4_address_general(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback, + ifindex); + SignalData *address_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_address_callback, + ifindex); + SignalData *address_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback, + ifindex); + GArray * addresses; + NMPlatformIP4Address *address; + in_addr_t addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + + /* Add address */ + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_added); + + /* Add address again (aka update) */ + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr, + lifetime + 100, + preferred + 50, + 0, + NULL); + accept_signals(address_changed, 0, 1); + + /* Test address listing */ + addresses = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addresses); + g_assert_cmpint(addresses->len, ==, 1); + address = &g_array_index(addresses, NMPlatformIP4Address, 0); + g_assert_cmpint(address->ifindex, ==, ifindex); + g_assert_cmphex(address->address, ==, addr); + g_assert_cmphex(address->peer_address, ==, addr); + g_assert_cmpint(address->plen, ==, IP4_PLEN); + g_array_unref(addresses); + + /* Remove address */ + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_removed); + + /* Remove address again */ + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + + free_signal(address_added); + free_signal(address_changed); + free_signal(address_removed); +} + +static void +test_ip6_address_general(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_address_callback, + ifindex); + SignalData *address_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip6_address_callback, + ifindex); + SignalData *address_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_address_callback, + ifindex); + GArray * addresses; + NMPlatformIP6Address *address; + struct in6_addr addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + guint flags = 0; + + inet_pton(AF_INET6, IP6_ADDRESS, &addr); + + /* Add address */ + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + accept_signal(address_added); + + /* Add address again (aka update) */ + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + accept_signals(address_changed, 0, 2); + + /* Test address listing */ + addresses = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addresses); + g_assert_cmpint(addresses->len, ==, 1); + address = &g_array_index(addresses, NMPlatformIP6Address, 0); + g_assert_cmpint(address->ifindex, ==, ifindex); + g_assert(!memcmp(&address->address, &addr, sizeof(addr))); + g_assert_cmpint(address->plen, ==, IP6_PLEN); + g_array_unref(addresses); + + /* Remove address */ + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + accept_signal(address_removed); + + /* Remove address again */ + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + + /* ensure not pending signal. */ + accept_signals(address_changed, 0, 1); + + free_signal(address_added); + free_signal(address_changed); + free_signal(address_removed); +} + +static void +test_ip4_address_general_2(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback); + SignalData *address_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback); + in_addr_t addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + g_assert(ifindex > 0); + + /* Looks like addresses are not announced by kernel when the interface + * is down. Link-local IPv6 address is automatically added. + */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, DEVICE_IFINDEX, NULL)); + + /* Add/delete notification */ + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + accept_signal(address_added); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + accept_signal(address_removed); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + + /* Add/delete conflict */ + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_added); + + free_signal(address_added); + free_signal(address_removed); +} + +static void +test_ip6_address_general_2(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData * address_added = add_signal(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_address_callback); + SignalData * address_removed = add_signal(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_address_callback); + struct in6_addr addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + guint flags = 0; + + inet_pton(AF_INET6, IP6_ADDRESS, &addr); + + /* Add/delete notification */ + nmtstp_ip6_address_add(NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0); + accept_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + accept_signal(address_removed); + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + /* Add/delete conflict */ + nmtstp_ip6_address_add(NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0); + accept_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + ensure_no_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + free_signal(address_added); + free_signal(address_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_address_peer(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData * address_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback); + SignalData * address_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback); + in_addr_t addr, addr_peer, addr_peer2; + guint32 lifetime = 2000; + guint32 preferred = 1000; + const NMPlatformIP4Address *a; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + inet_pton(AF_INET, IP4_ADDRESS_PEER, &addr_peer); + inet_pton(AF_INET, IP4_ADDRESS_PEER2, &addr_peer2); + g_assert(ifindex > 0); + + g_assert(addr != addr_peer); + + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + accept_signals(address_removed, 0, G_MAXINT); + accept_signals(address_added, 0, G_MAXINT); + + /* Add/delete notification */ + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr_peer, + lifetime, + preferred, + 0, + NULL); + accept_signal(address_added); + a = nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer); + g_assert(a); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)); + + nmtstp_ip_address_assert_lifetime((NMPlatformIPAddress *) a, -1, lifetime, preferred); + + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr_peer2, + lifetime, + preferred, + 0, + NULL); + accept_signal(address_added); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)); + a = nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2); + g_assert(a); + + nmtstp_ip_address_assert_lifetime((NMPlatformIPAddress *) a, -1, lifetime, preferred); + + g_assert(addr != addr_peer); + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr_peer); + accept_signal(address_removed); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)); + + free_signal(address_added); + free_signal(address_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_address_peer_zero(void) +{ + const int ifindex = DEVICE_IFINDEX; + in_addr_t addr, addr_peer; + guint32 lifetime = 2000; + guint32 preferred = 1000; + const gint8 plen = 24; + const char *label = NULL; + in_addr_t peers[3], r_peers[3]; + int i; + GArray * addrs; + + g_assert(ifindex > 0); + + inet_pton(AF_INET, "192.168.5.2", &addr); + inet_pton(AF_INET, "192.168.6.2", &addr_peer); + peers[0] = addr; + peers[1] = addr_peer; + peers[2] = 0; + + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + + nmtst_rand_perm(NULL, r_peers, peers, sizeof(peers[0]), G_N_ELEMENTS(peers)); + for (i = 0; i < G_N_ELEMENTS(peers); i++) { + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i])); + + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + plen, + r_peers[i], + lifetime, + preferred, + 0, + label); + + addrs = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addrs); + g_assert_cmpint(addrs->len, ==, i + 1); + g_array_unref(addrs); + } + + if (nmtst_is_debug() && nmtstp_is_root_test()) + nmtstp_run_command_check("ip address show dev %s", DEVICE_NAME); + + nmtst_rand_perm(NULL, r_peers, peers, sizeof(peers[0]), G_N_ELEMENTS(peers)); + for (i = 0; i < G_N_ELEMENTS(peers); i++) { + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i])); + + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, plen, r_peers[i]); + + addrs = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addrs); + g_assert_cmpint(addrs->len, ==, G_N_ELEMENTS(peers) - i - 1); + g_array_unref(addrs); + } +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +/***************************************************************************** + * SETUP TESTS + *****************************************************************************/ + +void +_nmtstp_setup_tests(void) +{ +#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, FALSE) + add_test_func("/address/ipv4/general", test_ip4_address_general); + add_test_func("/address/ipv6/general", test_ip6_address_general); + + add_test_func("/address/ipv4/general-2", test_ip4_address_general_2); + add_test_func("/address/ipv6/general-2", test_ip6_address_general_2); + + add_test_func("/address/ipv4/peer", test_ip4_address_peer); + add_test_func("/address/ipv4/peer/zero", test_ip4_address_peer_zero); +} diff --git a/src/core/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c new file mode 100644 index 0000000..2fd6a4e --- /dev/null +++ b/src/core/platform/tests/test-cleanup.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "test-common.h" + +static void +test_cleanup_internal(void) +{ + SignalData * link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + int ifindex; + GArray * addresses4; + GArray * addresses6; + GPtrArray * routes4; + GPtrArray * routes6; + in_addr_t addr4; + in_addr_t network4; + int plen4 = 24; + in_addr_t gateway4; + struct in6_addr addr6; + struct in6_addr network6; + int plen6 = 64; + struct in6_addr gateway6; + int lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + int preferred = NM_PLATFORM_LIFETIME_PERMANENT; + int metric = 20; + int mss = 1000; + guint32 flags = 0; + + inet_pton(AF_INET, "192.0.2.1", &addr4); + inet_pton(AF_INET, "192.0.3.0", &network4); + inet_pton(AF_INET, "198.51.100.1", &gateway4); + inet_pton(AF_INET6, "2001:db8:a:b:1:2:3:4", &addr6); + inet_pton(AF_INET6, "2001:db8:c:d:0:0:0:0", &network6); + inet_pton(AF_INET6, "2001:db8:e:f:1:2:3:4", &gateway6); + + /* Create and set up device */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + accept_signal(link_added); + free_signal(link_added); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, + nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME), + NULL)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + + /* wait for kernel to add the IPv6 link local address... it takes a bit. */ + NMTST_WAIT_ASSERT(300, { + gs_unref_array GArray * addrs = NULL; + const NMPlatformIP6Address *a; + + if (nmtst_wait_iteration > 0) { + nmtstp_wait_for_signal(NM_PLATFORM_GET, nmtst_wait_remaining_us / 1000); + nm_platform_process_events(NM_PLATFORM_GET); + } + addrs = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + if (addrs->len == 1 && (a = &g_array_index(addrs, NMPlatformIP6Address, 0)) + && IN6_IS_ADDR_LINKLOCAL(&a->address)) + break; + }); + + g_assert(nm_platform_ip4_address_add(NM_PLATFORM_GET, + ifindex, + addr4, + plen4, + addr4, + nm_platform_ip4_broadcast_address_create(addr4, plen4), + lifetime, + preferred, + 0, + NULL)); + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + ifindex, + addr6, + plen6, + in6addr_any, + lifetime, + preferred, + flags)); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway4, + 32, + INADDR_ANY, + 0, + metric, + mss); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network4, + plen4, + gateway4, + 0, + metric, + mss); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway4, + 0, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway6, + 128, + in6addr_any, + in6addr_any, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network6, + plen6, + gateway6, + in6addr_any, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway6, + in6addr_any, + metric, + mss); + + addresses4 = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + addresses6 = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + routes4 = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + routes6 = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + + g_assert_cmpint(addresses4->len, ==, 1); + g_assert_cmpint(addresses6->len, ==, 2); /* also has a IPv6 LL address. */ + g_assert_cmpint(routes4->len, ==, 3); + g_assert_cmpint(routes6->len, ==, 3); + + g_array_unref(addresses4); + g_array_unref(addresses6); + g_ptr_array_unref(routes4); + g_ptr_array_unref(routes6); + + /* Delete interface with all addresses and routes */ + g_assert(nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + addresses4 = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + addresses6 = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + routes4 = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + routes6 = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + + g_assert_cmpint(addresses4->len, ==, 0); + g_assert_cmpint(addresses6->len, ==, 0); + g_assert(!routes4); + g_assert(!routes6); + + g_array_unref(addresses4); + g_array_unref(addresses6); +} + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + g_test_add_func("/internal", test_cleanup_internal); + /* FIXME: add external cleanup check */ +} diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c new file mode 100644 index 0000000..529100b --- /dev/null +++ b/src/core/platform/tests/test-common.c @@ -0,0 +1,2731 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "test-common.h" + +#include +#include +#include +#include +#include + +#include "n-acd/src/n-acd.h" + +#define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)" +#define SIGNAL_DATA_ARG(data) \ + (data)->name, nm_platform_signal_change_type_to_string((data)->change_type), (data)->ifindex, \ + (data)->ifname ? " ifname '" : "", (data)->ifname ?: "", (data)->ifname ? "'" : "", \ + (data)->received_count + +int NMTSTP_ENV1_IFINDEX = -1; +int NMTSTP_ENV1_EX = -1; + +/*****************************************************************************/ + +void +nmtstp_setup_platform(void) +{ + g_assert(_nmtstp_setup_platform_func); + _nmtstp_setup_platform_func(); +} + +gboolean +nmtstp_is_root_test(void) +{ + g_assert(_nmtstp_setup_platform_func); + return _nmtstp_setup_platform_func == nm_linux_platform_setup; +} + +gboolean +nmtstp_is_sysfs_writable(void) +{ + return !nmtstp_is_root_test() || (access("/sys/devices", W_OK) == 0); +} + +static void +_init_platform(NMPlatform **platform, gboolean external_command) +{ + g_assert(platform); + if (!*platform) + *platform = NM_PLATFORM_GET; + g_assert(NM_IS_PLATFORM(*platform)); + + if (external_command) + g_assert(NM_IS_LINUX_PLATFORM(*platform)); +} + +/*****************************************************************************/ + +static GArray * +_ipx_address_get_all(NMPlatform *self, int ifindex, NMPObjectType obj_type) +{ + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(self)); + g_assert(ifindex > 0); + g_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nmp_cache_lookup_to_array(nm_platform_lookup(self, &lookup), + obj_type, + FALSE /*addresses are always visible. */); +} + +GArray * +nmtstp_platform_ip4_address_get_all(NMPlatform *self, int ifindex) +{ + return _ipx_address_get_all(self, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS); +} + +GArray * +nmtstp_platform_ip6_address_get_all(NMPlatform *self, int ifindex) +{ + return _ipx_address_get_all(self, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS); +} + +const NMPlatformIPAddress * +nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family, gconstpointer addr) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPlatformIPAddress *found = NULL; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex >= 0); + nm_assert_addr_family(addr_family); + nm_assert(addr); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex); + nm_platform_iter_obj_for_each (&iter, self, &lookup, &obj) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(obj); + + g_assert(NMP_OBJECT_GET_ADDR_FAMILY(obj) == addr_family); + g_assert(ifindex <= 0 || a->ifindex == ifindex); + + if (memcmp(addr, a->address_ptr, nm_utils_addr_family_to_size(addr_family)) != 0) + continue; + + g_assert(!found); + found = a; + } + + if (!IS_IPv4 && ifindex > 0) + g_assert(found + == (const NMPlatformIPAddress *) nm_platform_ip6_address_get(self, ifindex, addr)); + + return found; +} + +/*****************************************************************************/ + +typedef struct { + NMIPAddr addr; + int addr_family; + bool found : 1; +} IPAddressesAssertData; + +void +_nmtstp_platform_ip_addresses_assert(const char * filename, + int lineno, + NMPlatform * self, + int ifindex, + gboolean force_exact_4, + gboolean force_exact_6, + gboolean ignore_ll6, + guint addrs_len, + const char *const *addrs) +{ + gs_free IPAddressesAssertData *addrs_bin = NULL; + int IS_IPv4; + guint i; + + g_assert(filename); + g_assert(lineno >= 0); + g_assert(NM_IS_PLATFORM(self)); + g_assert(ifindex >= 0); + + addrs_bin = g_new(IPAddressesAssertData, addrs_len); + + for (i = 0; i < addrs_len; i++) { + const char *addrstr = addrs[i]; + int addr_family; + NMIPAddr a; + + if (!addrstr) { + addr_family = AF_UNSPEC; + a = nm_ip_addr_zero; + } else if (inet_pton(AF_INET, addrstr, &a) == 1) + addr_family = AF_INET; + else if (inet_pton(AF_INET6, addrstr, &a) == 1) + addr_family = AF_INET6; + else + g_error("%s:%d: invalid IP address in argument: %s", filename, lineno, addrstr); + + addrs_bin[i] = (IPAddressesAssertData){ + .addr_family = addr_family, + .addr = a, + .found = FALSE, + }; + } + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + gs_unref_ptrarray GPtrArray *plat_addrs = NULL; + NMPLookup lookup; + guint j; + + plat_addrs = nm_platform_lookup_clone( + self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex), + NULL, + NULL); + + for (i = 0; i < addrs_len; i++) { + IPAddressesAssertData *addr_bin = &addrs_bin[i]; + + if (addr_bin->addr_family != addr_family) + continue; + + g_assert(!addr_bin->found); + for (j = 0; j < nm_g_ptr_array_len(plat_addrs);) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(plat_addrs->pdata[j]); + + if (memcmp(&addr_bin->addr, + a->address_ptr, + nm_utils_addr_family_to_size(addr_family)) + != 0) { + j++; + continue; + } + + g_assert(!addr_bin->found); + addr_bin->found = TRUE; + g_ptr_array_remove_index_fast(plat_addrs, j); + } + + if (!addr_bin->found) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("%s:%d: IPv%c address %s was not found on ifindex %d", + filename, + lineno, + nm_utils_addr_family_to_char(addr_bin->addr_family), + nm_utils_inet_ntop(addr_bin->addr_family, &addr_bin->addr, sbuf), + ifindex); + } + } + if (!IS_IPv4 && ignore_ll6 && nm_g_ptr_array_len(plat_addrs) > 0) { + /* we prune all remaining, non-matching IPv6 link local addresses. */ + for (j = 0; j < nm_g_ptr_array_len(plat_addrs);) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(plat_addrs->pdata[j]); + + if (!IN6_IS_ADDR_LINKLOCAL(a->address_ptr)) { + j++; + continue; + } + + g_ptr_array_remove_index_fast(plat_addrs, j); + } + } + if ((IS_IPv4 ? force_exact_4 : force_exact_6) && nm_g_ptr_array_len(plat_addrs) > 0) { + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + + g_error("%s:%d: %u IPv%c addresses found on ifindex %d that should not be there (one " + "is %s)", + filename, + lineno, + plat_addrs->len, + nm_utils_addr_family_to_char(addr_family), + ifindex, + nmp_object_to_string(plat_addrs->pdata[0], + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf, + sizeof(sbuf))); + } + } +} + +/*****************************************************************************/ + +gboolean +nmtstp_platform_ip4_route_delete(NMPlatform *platform, + int ifindex, + in_addr_t network, + guint8 plen, + guint32 metric) +{ + NMDedupMultiIter iter; + + nm_platform_process_events(platform); + + nm_dedup_multi_iter_for_each ( + &iter, + nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(iter.current->obj); + + if (r->ifindex != ifindex || r->network != network || r->plen != plen + || r->metric != metric) { + continue; + } + + return nm_platform_object_delete(platform, NMP_OBJECT_UP_CAST(r)); + } + + return TRUE; +} + +gboolean +nmtstp_platform_ip6_route_delete(NMPlatform * platform, + int ifindex, + struct in6_addr network, + guint8 plen, + guint32 metric) +{ + NMDedupMultiIter iter; + + nm_platform_process_events(platform); + + nm_dedup_multi_iter_for_each ( + &iter, + nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex)) { + const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE(iter.current->obj); + + if (r->ifindex != ifindex || !IN6_ARE_ADDR_EQUAL(&r->network, &network) || r->plen != plen + || r->metric != metric) { + continue; + } + + return nm_platform_object_delete(platform, NMP_OBJECT_UP_CAST(r)); + } + + return TRUE; +} + +/*****************************************************************************/ + +SignalData * +add_signal_full(const char * name, + NMPlatformSignalChangeType change_type, + GCallback callback, + int ifindex, + const char * ifname) +{ + SignalData *data = g_new0(SignalData, 1); + + data->name = name; + data->change_type = change_type; + data->received_count = 0; + data->handler_id = g_signal_connect(NM_PLATFORM_GET, name, callback, data); + data->ifindex = ifindex; + data->ifname = ifname; + + g_assert(data->handler_id > 0); + + return data; +} + +void +_accept_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count != 1) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal one " + "time: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + data->received_count = 0; +} + +void +_accept_signals(const char *file, int line, const char *func, SignalData *data, int min, int max) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: " SIGNAL_DATA_FMT, + file, + line, + func, + min, + max, + SIGNAL_DATA_ARG(data)); + if (data->received_count < min || data->received_count > max) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal [%d,%d] " + "times: " SIGNAL_DATA_FMT, + file, + line, + func, + min, + max, + SIGNAL_DATA_ARG(data)); + data->received_count = 0; +} + +void +_ensure_no_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count > 0) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal 0 " + "times: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); +} + +void +_accept_or_wait_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): accept-or-wait signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count == 0) { + data->loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data->loop); + nm_clear_pointer(&data->loop, g_main_loop_unref); + } + + _accept_signal(file, line, func, data); +} + +void +_wait_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): wait signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to wait for signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + + data->loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data->loop); + nm_clear_pointer(&data->loop, g_main_loop_unref); + + _accept_signal(file, line, func, data); +} + +void +_free_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): free signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count != 0) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to free non-accepted " + "signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + + g_signal_handler_disconnect(NM_PLATFORM_GET, data->handler_id); + g_free(data); +} + +void +link_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink * cached; + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_LINK); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_LINK_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->ifname + && g_strcmp0(data->ifname, nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)) != 0) + return; + if (change_type != data->change_type) + return; + + if (data->loop) { + _LOGD("Quitting main loop."); + g_main_loop_quit(data->loop); + } + + data->received_count++; + _LOGD("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", + data->name, + nm_platform_signal_change_type_to_string(data->change_type), + ifindex, + received->name, + data->received_count); + + if (change_type == NM_PLATFORM_SIGNAL_REMOVED) + g_assert(!nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)); + else + g_assert(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)); + + /* Check the data */ + g_assert(received->ifindex > 0); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + nmp_cache_iter_for_each_link (&iter, nm_platform_lookup(platform, &lookup), &cached) { + if (!nmp_object_is_visible(NMP_OBJECT_UP_CAST(cached))) + continue; + if (cached->ifindex == received->ifindex) { + g_assert_cmpint(nm_platform_link_cmp(cached, received), ==, 0); + g_assert(!memcmp(cached, received, sizeof(*cached))); + if (data->change_type == NM_PLATFORM_SIGNAL_REMOVED) + g_error("Deleted link still found in the local cache."); + return; + } + } + + if (data->change_type != NM_PLATFORM_SIGNAL_REMOVED) + g_error("Added/changed link not found in the local cache."); +} + +/*****************************************************************************/ + +static const NMPlatformIP4Route * +_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos, + guint * out_c_exists) +{ + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject * o = NULL; + guint c; + const NMPlatformIP4Route *r = NULL; + + _init_platform(&platform, FALSE); + + nmp_lookup_init_ip4_route_by_weak_id(&lookup, network, plen, metric, tos); + + c = 0; + nmp_cache_iter_for_each (&iter, nm_platform_lookup(platform, &lookup), &o) { + if (NMP_OBJECT_CAST_IP4_ROUTE(o)->ifindex != ifindex && ifindex > 0) + continue; + if (!r) + r = NMP_OBJECT_CAST_IP4_ROUTE(o); + c++; + } + + NM_SET_OUT(out_c_exists, c); + return r; +} + +const NMPlatformIP4Route * +_nmtstp_assert_ip4_route_exists(const char *file, + guint line, + const char *func, + NMPlatform *platform, + int c_exists, + const char *ifname, + guint32 network, + int plen, + guint32 metric, + guint8 tos) +{ + int ifindex; + guint c; + const NMPlatformIP4Route *r = NULL; + + _init_platform(&platform, FALSE); + + ifindex = -1; + if (ifname) { + ifindex = nm_platform_link_get_ifindex(platform, ifname); + g_assert(ifindex > 0); + } + + r = _ip4_route_get(platform, ifindex, network, plen, metric, tos, &c); + + if (c != c_exists && c_exists != -1) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but " + "platform has it %u times", + file, + line, + func, + _nm_utils_inet4_ntop(network, sbuf), + plen, + metric, + tos, + c_exists, + c); + } + + return r; +} + +const NMPlatformIP4Route * +nmtstp_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos) +{ + return _ip4_route_get(platform, ifindex, network, plen, metric, tos, NULL); +} + +/*****************************************************************************/ + +static const NMPlatformIP6Route * +_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen, + guint * out_c_exists) +{ + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject * o = NULL; + guint c; + const NMPlatformIP6Route *r = NULL; + + _init_platform(&platform, FALSE); + + nmp_lookup_init_ip6_route_by_weak_id(&lookup, network, plen, metric, src, src_plen); + + c = 0; + nmp_cache_iter_for_each (&iter, nm_platform_lookup(platform, &lookup), &o) { + if (NMP_OBJECT_CAST_IP6_ROUTE(o)->ifindex != ifindex && ifindex > 0) + continue; + if (!r) + r = NMP_OBJECT_CAST_IP6_ROUTE(o); + c++; + } + + NM_SET_OUT(out_c_exists, c); + return r; +} + +const NMPlatformIP6Route * +_nmtstp_assert_ip6_route_exists(const char * file, + guint line, + const char * func, + NMPlatform * platform, + int c_exists, + const char * ifname, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + int ifindex; + guint c; + const NMPlatformIP6Route *r = NULL; + + _init_platform(&platform, FALSE); + + ifindex = -1; + if (ifname) { + ifindex = nm_platform_link_get_ifindex(platform, ifname); + g_assert(ifindex > 0); + } + + r = _ip6_route_get(platform, ifindex, network, plen, metric, src, src_plen, &c); + + if (c != c_exists && c_exists != -1) { + char s_src[NM_UTILS_INET_ADDRSTRLEN]; + char s_network[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but " + "platform has it %u times", + file, + line, + func, + _nm_utils_inet6_ntop(network, s_network), + plen, + metric, + _nm_utils_inet6_ntop(src, s_src), + src_plen, + c_exists, + c); + } + + return r; +} + +const NMPlatformIP6Route * +nmtstp_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + return _ip6_route_get(platform, ifindex, network, plen, metric, src, src_plen, NULL); +} + +/*****************************************************************************/ + +int +nmtstp_run_command(const char *format, ...) +{ + int result; + gs_free char *command = NULL; + va_list ap; + + va_start(ap, format); + command = g_strdup_vprintf(format, ap); + va_end(ap); + + _LOGD("Running command: %s", command); + result = system(command); + _LOGD("Command finished: result=%d", result); + + return result; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *loop; + guint signal_counts; + guint id; +} WaitForSignalData; + +static void +_wait_for_signal_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *plink, + int change_type_i, + gpointer user_data) +{ + WaitForSignalData *data = user_data; + + data->signal_counts++; + nm_clear_g_source(&data->id); + g_main_loop_quit(data->loop); +} + +static gboolean +_wait_for_signal_timeout(gpointer user_data) +{ + WaitForSignalData *data = user_data; + + g_assert(data->id); + data->id = 0; + g_main_loop_quit(data->loop); + return G_SOURCE_REMOVE; +} + +guint +nmtstp_wait_for_signal(NMPlatform *platform, gint64 timeout_msec) +{ + WaitForSignalData data = {0}; + gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route; + gulong id_qdisc, id_tfilter; + + _init_platform(&platform, FALSE); + + data.loop = g_main_loop_new(NULL, FALSE); + + id_link = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip4_address = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip6_address = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip4_route = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip6_route = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_qdisc = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_QDISC_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_tfilter = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_TFILTER_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + + /* if timeout_msec is negative, it means the wait-time already expired. + * Maybe, we should do nothing and return right away, without even + * processing events from platform. However, that inconsistency (of not + * processing events from mainloop) is inconvenient. + * + * It's better that on the return of nmtstp_wait_for_signal(), we always + * have no events pending. So, a negative timeout is treated the same as + * a zero timeout: we check whether there are any events pending in platform, + * and quite the mainloop immediately afterwards. But we always check. */ + + data.id = g_timeout_add(CLAMP(timeout_msec, 0, G_MAXUINT32), _wait_for_signal_timeout, &data); + + g_main_loop_run(data.loop); + + g_assert(!data.id); + g_assert(nm_clear_g_signal_handler(platform, &id_link)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip4_address)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip6_address)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip4_route)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip6_route)); + g_assert(nm_clear_g_signal_handler(platform, &id_tfilter)); + g_assert(nm_clear_g_signal_handler(platform, &id_qdisc)); + + nm_clear_pointer(&data.loop, g_main_loop_unref); + + /* return the number of signals, or 0 if timeout was reached .*/ + return data.signal_counts; +} + +guint +nmtstp_wait_for_signal_until(NMPlatform *platform, gint64 until_ms) +{ + gint64 now; + guint signal_counts; + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_msec(); + + if (until_ms < now) + return 0; + + signal_counts = nmtstp_wait_for_signal(platform, until_ms - now); + if (signal_counts) + return signal_counts; + } +} + +const NMPlatformLink * +nmtstp_wait_for_link(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 timeout_msec) +{ + return nmtstp_wait_for_link_until( + platform, + ifname, + expected_link_type, + timeout_msec ? nm_utils_get_monotonic_timestamp_msec() + timeout_msec : 0); +} + +const NMPlatformLink * +nmtstp_wait_for_link_until(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 until_ms) +{ + const NMPlatformLink *plink; + gint64 now; + gboolean waited_once = FALSE; + + _init_platform(&platform, FALSE); + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_msec(); + + plink = nm_platform_link_get_by_ifname(platform, ifname); + if (plink && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type)) + return plink; + + if (until_ms == 0) { + /* don't wait, don't even poll the socket. */ + return NULL; + } + + if (waited_once && until_ms < now) { + /* timeout reached (+ we already waited for a signal at least once). */ + return NULL; + } + + waited_once = TRUE; + /* regardless of whether timeout is already reached, we poll the netlink + * socket a bit. */ + nmtstp_wait_for_signal(platform, until_ms - now); + } +} + +/*****************************************************************************/ + +int +nmtstp_run_command_check_external_global(void) +{ + if (!nmtstp_is_root_test()) + return FALSE; + switch (nmtst_get_rand_uint32() % 3) { + case 0: + return -1; + case 1: + return FALSE; + default: + return TRUE; + } +} + +gboolean +nmtstp_run_command_check_external(int external_command) +{ + if (external_command != -1) { + g_assert(NM_IN_SET(external_command, FALSE, TRUE)); + g_assert(!external_command || nmtstp_is_root_test()); + return !!external_command; + } + if (!nmtstp_is_root_test()) + return FALSE; + return (nmtst_get_rand_uint32() % 2) == 0; +} + +/*****************************************************************************/ + +#define CHECK_LIFETIME_MAX_DIFF 2 + +gboolean +nmtstp_ip_address_check_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred) +{ + gint64 offset; + int i; + + g_assert(addr); + + if (now == -1) + now = nm_utils_get_monotonic_timestamp_sec(); + g_assert(now > 0); + + g_assert(expected_preferred <= expected_lifetime); + + if (expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_preferred == NM_PLATFORM_LIFETIME_PERMANENT) { + return addr->timestamp == 0 && addr->lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && addr->preferred == NM_PLATFORM_LIFETIME_PERMANENT; + } + + if (addr->timestamp == 0) + return FALSE; + + offset = (gint64) now - addr->timestamp; + + for (i = 0; i < 2; i++) { + guint32 lft = i ? expected_lifetime : expected_preferred; + guint32 adr = i ? addr->lifetime : addr->preferred; + + if (lft == NM_PLATFORM_LIFETIME_PERMANENT) { + if (adr != NM_PLATFORM_LIFETIME_PERMANENT) + return FALSE; + } else { + if (((gint64) adr) - offset <= ((gint64) lft) - CHECK_LIFETIME_MAX_DIFF + || ((gint64) adr) - offset >= ((gint64) lft) + CHECK_LIFETIME_MAX_DIFF) + return FALSE; + } + } + return TRUE; +} + +void +nmtstp_ip_address_assert_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred) +{ + gint64 n = now; + gint64 offset; + int i; + + g_assert(addr); + + if (now == -1) + now = nm_utils_get_monotonic_timestamp_sec(); + g_assert(now > 0); + + g_assert(expected_preferred <= expected_lifetime); + + if (expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_preferred == NM_PLATFORM_LIFETIME_PERMANENT) { + g_assert_cmpint(addr->timestamp, ==, 0); + g_assert_cmpint(addr->lifetime, ==, NM_PLATFORM_LIFETIME_PERMANENT); + g_assert_cmpint(addr->preferred, ==, NM_PLATFORM_LIFETIME_PERMANENT); + return; + } + + g_assert_cmpint(addr->timestamp, >, 0); + g_assert_cmpint(addr->timestamp, <=, now); + + offset = (gint64) now - addr->timestamp; + g_assert_cmpint(offset, >=, 0); + + for (i = 0; i < 2; i++) { + guint32 lft = i ? expected_lifetime : expected_preferred; + guint32 adr = i ? addr->lifetime : addr->preferred; + + if (lft == NM_PLATFORM_LIFETIME_PERMANENT) + g_assert_cmpint(adr, ==, NM_PLATFORM_LIFETIME_PERMANENT); + else { + g_assert_cmpint(adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF); + g_assert_cmpint(adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF); + } + } + + g_assert(nmtstp_ip_address_check_lifetime(addr, n, expected_lifetime, expected_preferred)); +} + +/*****************************************************************************/ + +static void +_ip_address_add(NMPlatform * platform, + gboolean external_command, + gboolean is_v4, + int ifindex, + const NMIPAddr *address, + int plen, + const NMIPAddr *peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char * label) +{ + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char * ifname; + gs_free char *s_valid = NULL; + gs_free char *s_preferred = NULL; + gs_free char *s_label = NULL; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT) + s_valid = g_strdup_printf(" valid_lft %d", lifetime); + if (preferred != NM_PLATFORM_LIFETIME_PERMANENT) + s_preferred = g_strdup_printf(" preferred_lft %d", preferred); + if (label) + s_label = g_strdup_printf("%s:%s", ifname, label); + + if (is_v4) { + char s_peer[NM_UTILS_INET_ADDRSTRLEN + 50]; + + g_assert(flags == 0); + + if (peer_address->addr4 != address->addr4 || nmtst_get_rand_uint32() % 2) { + /* If the peer is the same as the local address, we can omit it. The result should be identical */ + nm_sprintf_buf(s_peer, " peer %s", _nm_utils_inet4_ntop(peer_address->addr4, b2)); + } else + s_peer[0] = '\0'; + + nmtstp_run_command_check("ip address change %s%s/%d dev %s%s%s%s", + _nm_utils_inet4_ntop(address->addr4, b1), + s_peer, + plen, + ifname, + s_valid ?: "", + s_preferred ?: "", + s_label ?: ""); + } else { + g_assert(label == NULL); + + /* flags not implemented (yet) */ + g_assert(flags == 0); + nmtstp_run_command_check("ip address change %s%s%s/%d dev %s%s%s%s", + _nm_utils_inet6_ntop(&address->addr6, b1), + !IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) ? " peer " : "", + !IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) + ? _nm_utils_inet6_ntop(&peer_address->addr6, b2) + : "", + plen, + ifname, + s_valid ?: "", + s_preferred ?: "", + s_label ?: ""); + } + } else { + gboolean success; + + if (is_v4) { + success = nm_platform_ip4_address_add(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4, + 0u, + lifetime, + preferred, + flags, + label); + } else { + g_assert(label == NULL); + success = nm_platform_ip6_address_add(platform, + ifindex, + address->addr6, + plen, + peer_address->addr6, + lifetime, + preferred, + flags); + } + g_assert(success); + } + + /* Let's wait until we see the address. */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 500; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + if (is_v4) { + const NMPlatformIP4Address *a; + + g_assert(flags == 0); + a = nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + if (a && a->peer_address == peer_address->addr4 + && nmtstp_ip_address_check_lifetime((NMPlatformIPAddress *) a, + -1, + lifetime, + preferred) + && strcmp(a->label, label ?: "") == 0) + break; + } else { + const NMPlatformIP6Address *a; + + g_assert(label == NULL); + g_assert(flags == 0); + + a = nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + if (a + && !memcmp(nm_platform_ip6_address_get_peer(a), + (IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) + || IN6_ARE_ADDR_EQUAL(&address->addr6, &peer_address->addr6)) + ? &address->addr6 + : &peer_address->addr6, + sizeof(struct in6_addr)) + && nmtstp_ip_address_check_lifetime((NMPlatformIPAddress *) a, + -1, + lifetime, + preferred)) + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +void +nmtstp_ip4_address_add(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + _ip_address_add(platform, + external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + flags, + label); +} + +void +nmtstp_ip6_address_add(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + _ip_address_add(platform, + external_command, + FALSE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + flags, + NULL); +} + +void +nmtstp_ip4_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + in_addr_t network, + guint8 plen, + in_addr_t gateway, + in_addr_t pref_src, + guint32 metric, + guint32 mss) +{ + NMPlatformIP4Route route = {}; + + route.ifindex = ifindex; + route.rt_source = source; + route.network = network; + route.plen = plen; + route.gateway = gateway; + route.pref_src = pref_src; + route.metric = metric; + route.mss = mss; + + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip4_route_add(platform, NMP_NLM_FLAG_REPLACE, &route))); +} + +void +nmtstp_ip6_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + struct in6_addr network, + guint8 plen, + struct in6_addr gateway, + struct in6_addr pref_src, + guint32 metric, + guint32 mss) +{ + NMPlatformIP6Route route = {}; + + route.ifindex = ifindex; + route.rt_source = source; + route.network = network; + route.plen = plen; + route.gateway = gateway; + route.pref_src = pref_src; + route.metric = metric; + route.mss = mss; + + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip6_route_add(platform, NMP_NLM_FLAG_REPLACE, &route))); +} + +/*****************************************************************************/ + +static void +_ip_address_del(NMPlatform * platform, + gboolean external_command, + gboolean is_v4, + int ifindex, + const NMIPAddr *address, + int plen, + const NMIPAddr *peer_address) +{ + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *ifname; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + int success; + gboolean had_address; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + /* let's wait until we see the address as we added it. */ + if (is_v4) + had_address = !!nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + else + had_address = !!nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + + if (is_v4) { + success = nmtstp_run_command("ip address delete %s%s%s/%d dev %s", + _nm_utils_inet4_ntop(address->addr4, b1), + peer_address->addr4 != address->addr4 ? " peer " : "", + peer_address->addr4 != address->addr4 + ? _nm_utils_inet4_ntop(peer_address->addr4, b2) + : "", + plen, + ifname); + } else { + g_assert(!peer_address); + success = nmtstp_run_command("ip address delete %s/%d dev %s", + _nm_utils_inet6_ntop(&address->addr6, b1), + plen, + ifname); + } + g_assert(success == 0 || !had_address); + } else { + gboolean success; + + if (is_v4) { + success = nm_platform_ip4_address_delete(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + } else { + g_assert(!peer_address); + success = nm_platform_ip6_address_delete(platform, ifindex, address->addr6, plen); + } + g_assert(success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + if (is_v4) { + const NMPlatformIP4Address *a; + + a = nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + if (!a) + break; + } else { + const NMPlatformIP6Address *a; + + a = nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + if (!a) + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +void +nmtstp_ip4_address_del(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address) +{ + _ip_address_del(platform, + external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address); +} + +void +nmtstp_ip6_address_del(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen) +{ + _ip_address_del(platform, external_command, FALSE, ifindex, (NMIPAddr *) &address, plen, NULL); +} + +/*****************************************************************************/ + +#define _assert_pllink(platform, success, pllink, name, type) \ + G_STMT_START \ + { \ + const NMPlatformLink *_pllink = (pllink); \ + \ + if ((success)) { \ + g_assert(_pllink); \ + g_assert(_pllink \ + == nmtstp_link_get_typed(platform, _pllink->ifindex, (name), (type))); \ + } else { \ + g_assert(!_pllink); \ + g_assert(!nmtstp_link_get(platform, 0, (name))); \ + } \ + } \ + G_STMT_END + +const NMPlatformLink * +nmtstp_link_bridge_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkBridge *lnk) +{ + const NMPlatformLink * pllink = NULL; + const NMPlatformLnkBridge *ll = NULL; + int r = 0; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + char sbuf_gfw[100]; + char sbuf_mhm[100]; + char sbuf_mlmc[100]; + char sbuf_mlmi[100]; + char sbuf_mmi[100]; + char sbuf_mqi[100]; + char sbuf_mqii[100]; + char sbuf_msqc[100]; + char sbuf_msqi[100]; + char sbuf_mqri[100]; + + r = nmtstp_run_command( + "ip link add %s type bridge " + "forward_delay %u " + "hello_time %u " + "max_age %u " + "ageing_time %u " + "stp_state %d " + "priority %u " + "vlan_protocol %u " + "vlan_stats_enabled %d " + "%s" /* group_fwd_mask */ + "group_address " NM_ETHER_ADDR_FORMAT_STR " " + "mcast_snooping %d " + "mcast_router %u " + "mcast_query_use_ifaddr %d " + "mcast_querier %d " + "%s" /* mcast_hash_max */ + "%s" /* mcast_last_member_count */ + "%s" /* mcast_startup_query_count */ + "%s" /* mcast_last_member_interval */ + "%s" /* mcast_membership_interval */ + "%s" /* mcast_querier_interval */ + "%s" /* mcast_query_interval */ + "%s" /* mcast_query_response_interval */ + "%s" /* mcast_startup_query_interval */ + "", + name, + lnk->forward_delay, + lnk->hello_time, + lnk->max_age, + lnk->ageing_time, + (int) lnk->stp_state, + lnk->priority, + lnk->vlan_protocol, + (int) lnk->vlan_stats_enabled, + lnk->group_fwd_mask != 0 + ? nm_sprintf_buf(sbuf_gfw, "group_fwd_mask %#x ", lnk->group_fwd_mask) + : "", + NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr), + (int) lnk->mcast_snooping, + lnk->mcast_router, + (int) lnk->mcast_query_use_ifaddr, + (int) lnk->mcast_querier, + lnk->mcast_hash_max != NM_BRIDGE_MULTICAST_HASH_MAX_DEF + ? nm_sprintf_buf(sbuf_mhm, "mcast_hash_max %u ", lnk->mcast_hash_max) + : "", + lnk->mcast_last_member_count != NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF + ? nm_sprintf_buf(sbuf_mlmc, + "mcast_last_member_count %u ", + lnk->mcast_last_member_count) + : "", + lnk->mcast_startup_query_count != NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF + ? nm_sprintf_buf(sbuf_msqc, + "mcast_startup_query_count %u ", + lnk->mcast_startup_query_count) + : "", + lnk->mcast_last_member_interval != NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mlmi, + "mcast_last_member_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_last_member_interval) + : "", + lnk->mcast_membership_interval != NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mmi, + "mcast_membership_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_membership_interval) + : "", + lnk->mcast_querier_interval != NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqi, + "mcast_querier_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_querier_interval) + : "", + lnk->mcast_query_interval != NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqii, + "mcast_query_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_query_interval) + : "", + lnk->mcast_query_response_interval != NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqri, + "mcast_query_response_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_query_response_interval) + : "", + lnk->mcast_startup_query_interval != NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_msqi, + "mcast_startup_query_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_startup_query_interval) + : ""); + if (r == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_BRIDGE, 100); + else + _LOGI( + "Adding bridge device via iproute2 failed. Assume iproute2 is not up to the task."); + } + + if (!pllink) { + r = nm_platform_link_bridge_add(platform, name, NULL, 0, 0, lnk, &pllink); + } + + _assert_pllink(platform, r == 0, pllink, name, NM_LINK_TYPE_BRIDGE); + + ll = NMP_OBJECT_CAST_LNK_BRIDGE(NMP_OBJECT_UP_CAST(pllink)->_link.netlink.lnk); + + g_assert_cmpint(lnk->forward_delay, ==, ll->forward_delay); + g_assert_cmpint(lnk->hello_time, ==, ll->hello_time); + g_assert_cmpint(lnk->max_age, ==, ll->max_age); + g_assert_cmpint(lnk->ageing_time, ==, ll->ageing_time); + g_assert_cmpint(lnk->stp_state, ==, ll->stp_state); + g_assert_cmpint(lnk->priority, ==, ll->priority); + g_assert_cmpint(lnk->vlan_stats_enabled, ==, ll->vlan_stats_enabled); + g_assert_cmpint(lnk->group_fwd_mask, ==, ll->group_fwd_mask); + g_assert_cmpint(lnk->mcast_snooping, ==, ll->mcast_snooping); + g_assert_cmpint(lnk->mcast_router, ==, ll->mcast_router); + g_assert_cmpint(lnk->mcast_query_use_ifaddr, ==, ll->mcast_query_use_ifaddr); + g_assert_cmpint(lnk->mcast_querier, ==, ll->mcast_querier); + g_assert_cmpint(lnk->mcast_hash_max, ==, ll->mcast_hash_max); + g_assert_cmpint(lnk->mcast_last_member_count, ==, ll->mcast_last_member_count); + g_assert_cmpint(lnk->mcast_startup_query_count, ==, ll->mcast_startup_query_count); + g_assert_cmpint(lnk->mcast_last_member_interval, ==, ll->mcast_last_member_interval); + g_assert_cmpint(lnk->mcast_membership_interval, ==, ll->mcast_membership_interval); + g_assert_cmpint(lnk->mcast_querier_interval, ==, ll->mcast_querier_interval); + g_assert_cmpint(lnk->mcast_query_interval, ==, ll->mcast_query_interval); + g_assert_cmpint(lnk->mcast_query_response_interval, ==, ll->mcast_query_response_interval); + g_assert_cmpint(lnk->mcast_startup_query_interval, ==, ll->mcast_startup_query_interval); + + return pllink; +} +const NMPlatformLink * +nmtstp_link_veth_add(NMPlatform *platform, + gboolean external_command, + const char *name, + const char *peer) +{ + const NMPlatformLink *pllink = NULL; + gboolean success = FALSE; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + success = !nmtstp_run_command("ip link add dev %s type veth peer name %s", name, peer); + if (success) { + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VETH, 100); + nmtstp_assert_wait_for_link(platform, peer, NM_LINK_TYPE_VETH, 10); + } else { + /* iproute2 might fail in copr. See below. + * We accept that and try our platform implementation instead. */ + _LOGI("iproute2 failed to add veth device. Retry with platform code."); + external_command = FALSE; + } + } + + if (!external_command) { + int try_count = 0; + int r; + +again: + r = nm_platform_link_veth_add(platform, name, peer, &pllink); + if (r == -EPERM && try_count++ < 5) { + /* in copr (mock with Fedora 33 builders), this randomly fails with EPERM. + * Very odd. Try to work around by retrying. */ + _LOGI("netlink failuer EPERM to add veth device. Retry."); + goto again; + } + success = NMTST_NM_ERR_SUCCESS(r); + } + + g_assert(success); + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_VETH); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_dummy_add(NMPlatform *platform, gboolean external_command, const char *name) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + success = !nmtstp_run_command("ip link add %s type dummy", name); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_DUMMY, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(platform, name, &pllink)); + + g_assert(success); + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_DUMMY); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkGre *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + NMLinkType link_type; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + link_type = lnk->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE; + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + char * obj, *type; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + obj = lnk->is_tap ? "link" : "tunnel"; + type = lnk->is_tap ? "type gretap" : "mode gre"; + + success = !nmtstp_run_command("ip %s add %s %s %s local %s remote %s ttl %u tos %02x %s", + obj, + name, + type, + dev ?: "", + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, link_type, 100); + } else + success = + NMTST_NM_ERR_SUCCESS(nm_platform_link_gre_add(platform, name, NULL, 0, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ip6tnl_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char encap[20]; + char tclass[20]; + gboolean encap_ignore; + gboolean tclass_inherit; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(!lnk->is_gre); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + const char * mode; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + switch (lnk->proto) { + case IPPROTO_IPIP: + mode = "ipip6"; + break; + case IPPROTO_IPV6: + mode = "ip6ip6"; + break; + default: + g_assert_not_reached(); + } + + encap_ignore = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT); + tclass_inherit = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS); + + success = !nmtstp_run_command( + "ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %s encaplimit %s " + "flowlabel %x", + name, + mode, + dev, + _nm_utils_inet6_ntop(&lnk->local, b1), + _nm_utils_inet6_ntop(&lnk->remote, b2), + lnk->ttl, + tclass_inherit ? "inherit" : nm_sprintf_buf(tclass, "%02x", lnk->tclass), + encap_ignore ? "none" : nm_sprintf_buf(encap, "%u", lnk->encap_limit), + lnk->flow_label); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_IP6TNL, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_ip6tnl_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_IP6TNL); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ip6gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char tclass[20]; + gboolean tclass_inherit; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(lnk->is_gre); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + tclass_inherit = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS); + + success = !nmtstp_run_command( + "ip link add %s type %s %s local %s remote %s ttl %u tclass %s flowlabel %x", + name, + lnk->is_tap ? "ip6gretap" : "ip6gre", + dev, + _nm_utils_inet6_ntop(&lnk->local, b1), + _nm_utils_inet6_ntop(&lnk->remote, b2), + lnk->ttl, + tclass_inherit ? "inherit" : nm_sprintf_buf(tclass, "%02x", lnk->tclass), + lnk->flow_label); + if (success) { + pllink = nmtstp_assert_wait_for_link(platform, + name, + lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP + : NM_LINK_TYPE_IP6GRE, + 100); + } + } else + success = NMTST_NM_ERR_SUCCESS( + nm_platform_link_ip6gre_add(platform, name, NULL, 0, lnk, &pllink)); + + _assert_pllink(platform, + success, + pllink, + name, + lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ipip_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIpIp *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + success = !nmtstp_run_command( + "ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s", + name, + dev, + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_IPIP, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_ipip_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_IPIP); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_macvlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + int parent, + const NMPlatformLnkMacvlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + NMLinkType link_type; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + link_type = lnk->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN; + + if (external_command) { + const char *dev; + char * modes[] = { + [MACVLAN_MODE_BRIDGE] = "bridge", + [MACVLAN_MODE_VEPA] = "vepa", + [MACVLAN_MODE_PRIVATE] = "private", + [MACVLAN_MODE_PASSTHRU] = "passthru", + }; + + dev = nm_platform_link_get_name(platform, parent); + g_assert(dev); + g_assert_cmpint(lnk->mode, <, G_N_ELEMENTS(modes)); + + success = !nmtstp_run_command("ip link add name %s link %s type %s mode %s %s", + name, + dev, + lnk->tap ? "macvtap" : "macvlan", + modes[lnk->mode], + lnk->no_promisc ? "nopromisc" : ""); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, link_type, 100); + } else + success = NMTST_NM_ERR_SUCCESS( + nm_platform_link_macvlan_add(platform, name, parent, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_sit_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkSit *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *dev = ""; + + if (lnk->parent_ifindex) { + const char *parent_name; + + parent_name = nm_platform_link_get_name(platform, lnk->parent_ifindex); + g_assert(parent_name); + dev = nm_sprintf_bufa(100, " dev %s", parent_name); + } + + success = + !nmtstp_run_command("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s", + name, + dev, + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_SIT, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_sit_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_SIT); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_tun_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkTun *lnk, + int * out_fd) +{ + const NMPlatformLink *pllink = NULL; + int err; + int r; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(lnk); + g_assert(NM_IN_SET(lnk->type, IFF_TUN, IFF_TAP)); + g_assert(!out_fd || *out_fd == -1); + + if (!lnk->persist) { + /* ip tuntap does not support non-persistent devices. + * + * Add this device only via NMPlatform. */ + if (external_command == -1) + external_command = FALSE; + } + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + g_assert(lnk->persist); + + err = nmtstp_run_command( + "ip tuntap add" + " mode %s" + "%s" /* user */ + "%s" /* group */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + " name %s", + lnk->type == IFF_TUN ? "tun" : "tap", + lnk->owner_valid ? nm_sprintf_bufa(100, " user %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_bufa(100, " group %u", (guint) lnk->group) : "", + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + name); + /* Older versions of iproute2 don't support adding devices. + * On failure, fallback to using platform code. */ + if (err == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_TUN, 100); + else + g_error("failure to add tun/tap device via ip-route"); + } else { + g_assert(lnk->persist || out_fd); + r = nm_platform_link_tun_add(platform, name, lnk, &pllink, out_fd); + g_assert_cmpint(r, ==, 0); + } + + g_assert(pllink); + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_TUN); + g_assert_cmpstr(pllink->name, ==, name); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_vrf_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVrf *lnk, + gboolean * out_not_supported) +{ + const NMPlatformLink *pllink = NULL; + int r = 0; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + NM_SET_OUT(out_not_supported, FALSE); + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + r = nmtstp_run_command("ip link add %s type vrf table %u", name, lnk->table); + + if (r == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VRF, 100); + else + _LOGI("Adding vrf device via iproute2 failed. Assume iproute2 is not up to the task."); + } + + if (!pllink) { + r = nm_platform_link_vrf_add(platform, name, lnk, &pllink); + if (r == -EOPNOTSUPP) + NM_SET_OUT(out_not_supported, TRUE); + } + + _assert_pllink(platform, r == 0, pllink, name, NM_LINK_TYPE_VRF); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_vxlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVxlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + int err; + int r; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + char local[NM_UTILS_INET_ADDRSTRLEN]; + char group[NM_UTILS_INET_ADDRSTRLEN]; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + if (lnk->local) + _nm_utils_inet4_ntop(lnk->local, local); + else if (memcmp(&lnk->local6, &in6addr_any, sizeof(in6addr_any))) + _nm_utils_inet6_ntop(&lnk->local6, local); + else + local[0] = '\0'; + + if (lnk->group) + _nm_utils_inet4_ntop(lnk->group, group); + else if (memcmp(&lnk->group6, &in6addr_any, sizeof(in6addr_any))) + _nm_utils_inet6_ntop(&lnk->group6, group); + else + group[0] = '\0'; + + err = nmtstp_run_command("ip link add %s type vxlan id %u %s local %s group %s ttl %u tos " + "%02x dstport %u srcport %u %u ageing %u", + name, + lnk->id, + dev ?: "", + local, + group, + lnk->ttl, + lnk->tos, + lnk->dst_port, + lnk->src_port_min, + lnk->src_port_max, + lnk->ageing); + /* Older versions of iproute2 don't support adding vxlan devices. + * On failure, fallback to using platform code. */ + if (err == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VXLAN, 100); + else + _LOGI( + "Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task."); + } + if (!pllink) { + r = nm_platform_link_vxlan_add(platform, name, lnk, &pllink); + g_assert(NMTST_NM_ERR_SUCCESS(r)); + g_assert(pllink); + } + + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_VXLAN); + g_assert_cmpstr(pllink->name, ==, name); + return pllink; +} + +/*****************************************************************************/ + +const NMPlatformLink * +nmtstp_link_get_typed(NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type) +{ + const NMPlatformLink *pllink = NULL; + + _init_platform(&platform, FALSE); + + if (ifindex > 0) { + pllink = nm_platform_link_get(platform, ifindex); + + if (pllink) { + g_assert_cmpint(pllink->ifindex, ==, ifindex); + if (name) + g_assert_cmpstr(name, ==, pllink->name); + } else { + if (name) + g_assert(!nm_platform_link_get_by_ifname(platform, name)); + } + } else { + g_assert(name); + + pllink = nm_platform_link_get_by_ifname(platform, name); + + if (pllink) + g_assert_cmpstr(name, ==, pllink->name); + } + + g_assert(!name || nm_utils_ifname_valid_kernel(name, NULL)); + + if (pllink && link_type != NM_LINK_TYPE_NONE) + g_assert_cmpint(pllink->type, ==, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_get(NMPlatform *platform, int ifindex, const char *name) +{ + return nmtstp_link_get_typed(platform, ifindex, name, NM_LINK_TYPE_NONE); +} + +/*****************************************************************************/ + +void +nmtstp_link_delete(NMPlatform *platform, + gboolean external_command, + int ifindex, + const char *name, + gboolean require_exist) +{ + gint64 end_time; + const NMPlatformLink *pllink; + gboolean success; + gs_free char * name_copy = NULL; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + pllink = nmtstp_link_get(platform, ifindex, name); + + if (!pllink) { + g_assert(!require_exist); + return; + } + + name = name_copy = g_strdup(pllink->name); + ifindex = pllink->ifindex; + + if (external_command) { + nmtstp_run_command_check("ip link delete %s", name); + } else { + success = nm_platform_link_delete(platform, ifindex); + g_assert(success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + if (!nm_platform_link_get(platform, ifindex)) { + g_assert(!nm_platform_link_get_by_ifname(platform, name)); + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +/*****************************************************************************/ + +void +nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifindex, gboolean up) +{ + const NMPlatformLink *plink; + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *ifname; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + nmtstp_run_command_check("ip link set %s %s", ifname, up ? "up" : "down"); + } else { + if (up) + g_assert(nm_platform_link_set_up(platform, ifindex, NULL)); + else + g_assert(nm_platform_link_set_down(platform, ifindex)); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + plink = nm_platform_link_get(platform, ifindex); + g_assert(plink); + + if (NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP) == !!up) + break; + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +/*****************************************************************************/ + +gboolean +nmtstp_kernel_support_get(NMPlatformKernelSupportType type) +{ + const NMPlatformLink *pllink; + NMOptionBool v; + + v = nm_platform_kernel_support_get_full(type, FALSE); + if (v != NM_OPTION_BOOL_DEFAULT) + return v != NM_OPTION_BOOL_FALSE; + + switch (type) { + case NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED: + pllink = nmtstp_link_bridge_add(NULL, -1, "br-test-11", &nm_platform_lnk_bridge_default); + nmtstp_link_delete(NULL, -1, pllink->ifindex, NULL, TRUE); + v = nm_platform_kernel_support_get_full(type, FALSE); + g_assert(v != NM_OPTION_BOOL_DEFAULT); + return v; + default: + g_assert_not_reached(); + } +} + +/*****************************************************************************/ + +struct _NMTstpNamespaceHandle { + pid_t pid; + int pipe_fd; +}; + +NMTstpNamespaceHandle * +nmtstp_namespace_create(int unshare_flags, GError **error) +{ + NMTstpNamespaceHandle *ns_handle; + int e; + int errsv; + pid_t pid, pid2; + int pipefd_c2p[2]; + int pipefd_p2c[2]; + ssize_t r; + + e = pipe2(pipefd_c2p, O_CLOEXEC); + if (e != 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + return FALSE; + } + + e = pipe2(pipefd_p2c, O_CLOEXEC); + if (e != 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + nm_close(pipefd_c2p[0]); + nm_close(pipefd_c2p[1]); + return FALSE; + } + + pid = fork(); + if (pid < 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "fork() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + nm_close(pipefd_c2p[0]); + nm_close(pipefd_c2p[1]); + nm_close(pipefd_p2c[0]); + nm_close(pipefd_p2c[1]); + return FALSE; + } + + if (pid == 0) { + char read_buf[1]; + + nm_close(pipefd_c2p[0]); /* close read-end */ + nm_close(pipefd_p2c[1]); /* close write-end */ + + if (unshare(unshare_flags) != 0) { + errsv = errno; + if (errsv == 0) + errsv = -1; + } else + errsv = 0; + + /* sync with parent process and send result. */ + do { + r = write(pipefd_c2p[1], &errsv, sizeof(errsv)); + } while (r < 0 && errno == EINTR); + if (r != sizeof(errsv)) { + errsv = errno; + if (errsv == 0) + errsv = -2; + } + nm_close(pipefd_c2p[1]); + + /* wait until parent process terminates (or kills us). */ + if (errsv == 0) { + do { + r = read(pipefd_p2c[0], read_buf, sizeof(read_buf)); + } while (r < 0 && errno == EINTR); + } + nm_close(pipefd_p2c[0]); + _exit(0); + } + + nm_close(pipefd_c2p[1]); /* close write-end */ + nm_close(pipefd_p2c[0]); /* close read-end */ + + /* sync with child process. */ + do { + r = read(pipefd_c2p[0], &errsv, sizeof(errsv)); + } while (r < 0 && errno == EINTR); + + nm_close(pipefd_c2p[0]); + + if (r != sizeof(errsv) || errsv != 0) { + int status; + + if (r != sizeof(errsv)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "child process failed for unknown reason"); + } else { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "child process signaled failure %d (%s)", + errsv, + nm_strerror_native(errsv)); + } + nm_close(pipefd_p2c[1]); + kill(pid, SIGKILL); + do { + pid2 = waitpid(pid, &status, 0); + } while (pid2 == -1 && errno == EINTR); + return FALSE; + } + + ns_handle = g_new0(NMTstpNamespaceHandle, 1); + ns_handle->pid = pid; + ns_handle->pipe_fd = pipefd_p2c[1]; + return ns_handle; +} + +pid_t +nmtstp_namespace_handle_get_pid(NMTstpNamespaceHandle *ns_handle) +{ + g_return_val_if_fail(ns_handle, 0); + g_return_val_if_fail(ns_handle->pid > 0, 0); + + return ns_handle->pid; +} + +void +nmtstp_namespace_handle_release(NMTstpNamespaceHandle *ns_handle) +{ + pid_t pid; + int status; + + if (!ns_handle) + return; + + g_return_if_fail(ns_handle->pid > 0); + + nm_close(ns_handle->pipe_fd); + ns_handle->pipe_fd = 0; + + kill(ns_handle->pid, SIGKILL); + + do { + pid = waitpid(ns_handle->pid, &status, 0); + } while (pid == -1 && errno == EINTR); + ns_handle->pid = 0; + + g_free(ns_handle); +} + +int +nmtstp_namespace_get_fd_for_process(pid_t pid, const char *ns_name) +{ + char p[1000]; + + g_return_val_if_fail(pid > 0, 0); + g_return_val_if_fail(ns_name && ns_name[0] && strlen(ns_name) < 50, 0); + + nm_sprintf_buf(p, "/proc/%lu/ns/%s", (unsigned long) pid, ns_name); + + return open(p, O_RDONLY | O_CLOEXEC); +} + +/*****************************************************************************/ + +void +nmtstp_netns_select_random(NMPlatform **platforms, gsize n_platforms, NMPNetns **netns) +{ + int i; + + g_assert(platforms); + g_assert(n_platforms && n_platforms <= G_MAXINT32); + g_assert(netns && !*netns); + for (i = 0; i < n_platforms; i++) + g_assert(NM_IS_PLATFORM(platforms[i])); + + i = nmtst_get_rand_uint32() % (n_platforms + 1); + if (i == 0) + return; + g_assert(nm_platform_netns_push(platforms[i - 1], netns)); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +static gboolean +unshare_user(void) +{ + FILE *f; + uid_t uid = geteuid(); + gid_t gid = getegid(); + + /* Already a root? */ + if (gid == 0 && uid == 0) + return TRUE; + + /* Become a root in new user NS. */ + if (unshare(CLONE_NEWUSER) != 0) + return FALSE; + + /* Since Linux 3.19 we have to disable setgroups() in order to map users. + * Just proceed if the file is not there. */ + f = fopen("/proc/self/setgroups", "we"); + if (f) { + fprintf(f, "deny"); + fclose(f); + } + + /* Map current UID to root in NS to be created. */ + f = fopen("/proc/self/uid_map", "we"); + if (!f) + return FALSE; + fprintf(f, "0 %d 1", uid); + fclose(f); + + /* Map current GID to root in NS to be created. */ + f = fopen("/proc/self/gid_map", "we"); + if (!f) + return FALSE; + fprintf(f, "0 %d 1", gid); + fclose(f); + + return TRUE; +} + +int +main(int argc, char **argv) +{ + int result; + const char *program = *argv; + + _nmtstp_init_tests(&argc, &argv); + + if (nmtstp_is_root_test() && (geteuid() != 0 || getegid() != 0)) { + if (g_getenv("NMTST_FORCE_REAL_ROOT") || !unshare_user()) { + /* Try to exec as sudo, this function does not return, if a sudo-cmd is set. */ + nmtst_reexec_sudo(); + +#ifdef REQUIRE_ROOT_TESTS + g_print("Fail test: requires root privileges (%s)\n", program); + return EXIT_FAILURE; +#else + g_print("Skipping test: requires root privileges (%s)\n", program); + return g_test_run(); +#endif + } + } + + if (nmtstp_is_root_test() && !g_getenv("NMTST_NO_UNSHARE")) { + int errsv; + + if (unshare(CLONE_NEWNET | CLONE_NEWNS) != 0) { + errsv = errno; + g_error("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", + nm_strerror_native(errsv), + errsv); + } + + /* We need a read-only /sys so that the platform knows there's no udev. */ + mount(NULL, "/sys", "sysfs", MS_SLAVE, NULL); + if (mount("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) { + errsv = errno; + g_error("mount(\"/sys\") failed with %s (%d)", nm_strerror_native(errsv), errsv); + } + } + + nmtstp_setup_platform(); + + _nmtstp_setup_tests(); + + result = g_test_run(); + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + g_object_unref(NM_PLATFORM_GET); + return result; +} + +/*****************************************************************************/ + +struct _NMTstpAcdDefender { + int ifindex; + in_addr_t ip_addr; + NAcd * nacd; + NAcdProbe *probe; + GSource * source; + gint8 announce_started; +}; + +static gboolean +_l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) +{ + NMTstpAcdDefender *defender = user_data; + int r; + + r = n_acd_dispatch(defender->nacd); + if (r == N_ACD_E_PREEMPTED) + r = 0; + g_assert_cmpint(r, ==, 0); + + while (TRUE) { + NAcdEvent *event; + + r = n_acd_pop_event(defender->nacd, &event); + g_assert_cmpint(r, ==, 0); + if (!event) + return G_SOURCE_CONTINUE; + + switch (event->event) { + case N_ACD_EVENT_READY: + g_assert_cmpint(defender->announce_started, ==, 0); + g_assert(defender->probe == event->ready.probe); + defender->announce_started++; + _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: start announcing", + NM_HASH_OBFUSCATE_PTR(defender)); + r = n_acd_probe_announce(defender->probe, N_ACD_DEFEND_ALWAYS); + g_assert_cmpint(r, ==, 0); + break; + case N_ACD_EVENT_DEFENDED: + g_assert(defender->probe == event->defended.probe); + g_assert_cmpint(event->defended.n_sender, ==, ETH_ALEN); + _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT + "]: defended from " NM_ETHER_ADDR_FORMAT_STR, + NM_HASH_OBFUSCATE_PTR(defender), + NM_ETHER_ADDR_FORMAT_VAL((const NMEtherAddr *) event->defended.sender)); + break; + case N_ACD_EVENT_USED: + case N_ACD_EVENT_CONFLICT: + case N_ACD_EVENT_DOWN: + default: + g_assert_not_reached(); + break; + } + } +} + +NMTstpAcdDefender * +nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr) +{ + NMTstpAcdDefender * defender; + nm_auto(n_acd_config_freep) NAcdConfig * config = NULL; + nm_auto(n_acd_unrefp) NAcd * nacd = NULL; + nm_auto(n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL; + NAcdProbe * probe = NULL; + int fd; + int r; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + g_assert_cmpint(ifindex, >, 0); + g_assert(mac_addr); + + r = n_acd_config_new(&config); + g_assert_cmpint(r, ==, 0); + g_assert(config); + + n_acd_config_set_ifindex(config, ifindex); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, (const guint8 *) mac_addr, sizeof(*mac_addr)); + + r = n_acd_new(&nacd, config); + g_assert_cmpint(r, ==, 0); + g_assert(nacd); + + r = n_acd_probe_config_new(&probe_config); + g_assert_cmpint(r, ==, 0); + g_assert(probe_config); + + n_acd_probe_config_set_ip(probe_config, (struct in_addr){ip_addr}); + n_acd_probe_config_set_timeout(probe_config, 0); + + r = n_acd_probe(nacd, &probe, probe_config); + g_assert_cmpint(r, ==, 0); + g_assert(probe); + + defender = g_slice_new(NMTstpAcdDefender); + *defender = (NMTstpAcdDefender){ + .ifindex = ifindex, + .ip_addr = ip_addr, + .nacd = g_steal_pointer(&nacd), + .probe = g_steal_pointer(&probe), + }; + + _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT + "]: new for ifindex=%d, hwaddr=" NM_ETHER_ADDR_FORMAT_STR ", ipaddr=%s", + NM_HASH_OBFUSCATE_PTR(defender), + ifindex, + NM_ETHER_ADDR_FORMAT_VAL(mac_addr), + _nm_utils_inet4_ntop(ip_addr, sbuf_addr)); + + n_acd_probe_set_userdata(defender->probe, defender); + + n_acd_get_fd(defender->nacd, &fd); + g_assert_cmpint(fd, >=, 0); + + defender->source = nm_g_source_attach(nm_g_unix_fd_source_new(fd, + G_IO_IN, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_event, + defender, + NULL), + NULL); + + return defender; +} + +void +nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender) +{ + if (!defender) + return; + + _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: destroy", NM_HASH_OBFUSCATE_PTR(defender)); + + nm_clear_g_source_inst(&defender->source); + nm_clear_pointer(&defender->nacd, n_acd_unref); + nm_clear_pointer(&defender->probe, n_acd_probe_free); + + nm_g_slice_free(defender); +} diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h new file mode 100644 index 0000000..618645f --- /dev/null +++ b/src/core/platform/tests/test-common.h @@ -0,0 +1,619 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "platform/nm-fake-platform.h" +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +#define DEVICE_NAME "nm-test-device" + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "platform-test" +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, __VA_ARGS__) + +#define _LOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + gint64 _ts = nm_utils_get_monotonic_timestamp_nsec(); \ + \ + _nm_log(__level, \ + __domain, \ + 0, \ + NULL, \ + NULL, \ + "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (long) (_ts / NM_UTILS_NSEC_PER_SEC), \ + (long) (_ts % NM_UTILS_NSEC_PER_SEC) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +gboolean nmtstp_is_root_test(void); +gboolean nmtstp_is_sysfs_writable(void); + +/*****************************************************************************/ + +typedef struct _NMTstpNamespaceHandle NMTstpNamespaceHandle; + +NMTstpNamespaceHandle *nmtstp_namespace_create(int flags, GError **error); + +void nmtstp_namespace_handle_release(NMTstpNamespaceHandle *handle); +pid_t nmtstp_namespace_handle_get_pid(NMTstpNamespaceHandle *handle); + +int nmtstp_namespace_get_fd_for_process(pid_t pid, const char *ns_name); + +/*****************************************************************************/ + +void nmtstp_netns_select_random(NMPlatform **platforms, gsize n_platforms, NMPNetns **netns); + +/*****************************************************************************/ + +typedef struct { + gulong handler_id; + const char * name; + NMPlatformSignalChangeType change_type; + int received_count; + GMainLoop * loop; + int ifindex; + const char * ifname; +} SignalData; + +SignalData *add_signal_full(const char * name, + NMPlatformSignalChangeType change_type, + GCallback callback, + int ifindex, + const char * ifname); +#define add_signal(name, change_type, callback) \ + add_signal_full(name, change_type, (GCallback) callback, 0, NULL) +#define add_signal_ifindex(name, change_type, callback, ifindex) \ + add_signal_full(name, change_type, (GCallback) callback, ifindex, NULL) +#define add_signal_ifname(name, change_type, callback, ifname) \ + add_signal_full(name, change_type, (GCallback) callback, 0, ifname) +void _accept_signal(const char *file, int line, const char *func, SignalData *data); +void +_accept_signals(const char *file, int line, const char *func, SignalData *data, int min, int max); +void _wait_signal(const char *file, int line, const char *func, SignalData *data); +void _accept_or_wait_signal(const char *file, int line, const char *func, SignalData *data); +void _ensure_no_signal(const char *file, int line, const char *func, SignalData *data); +void _free_signal(const char *file, int line, const char *func, SignalData *data); +#define accept_signal(data) _accept_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define accept_signals(data, min, max) \ + _accept_signals(__FILE__, __LINE__, G_STRFUNC, data, min, max) +#define wait_signal(data) _wait_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define accept_or_wait_signal(data) _accept_or_wait_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define ensure_no_signal(data) _ensure_no_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define free_signal(data) _free_signal(__FILE__, __LINE__, G_STRFUNC, data) + +void link_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *received, + int change_type_i, + SignalData * data); + +/*****************************************************************************/ + +int nmtstp_run_command(const char *format, ...) _nm_printf(1, 2); +#define nmtstp_run_command_check(...) \ + do { \ + g_assert_cmpint(nmtstp_run_command(__VA_ARGS__), ==, 0); \ + } while (0) + +/*****************************************************************************/ + +guint nmtstp_wait_for_signal(NMPlatform *platform, gint64 timeout_msec); +guint nmtstp_wait_for_signal_until(NMPlatform *platform, gint64 until_ms); +const NMPlatformLink *nmtstp_wait_for_link(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 timeout_msec); +const NMPlatformLink *nmtstp_wait_for_link_until(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 until_ms); + +#define nmtstp_assert_wait_for_signal(platform, timeout_msec) \ + G_STMT_START \ + { \ + if (nmtstp_wait_for_signal(platform, timeout_msec) == 0) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + +#define nmtstp_assert_wait_for_signal_until(platform, until_ms) \ + G_STMT_START \ + { \ + if (nmtstp_wait_for_signal_until(platform, until_ms) == 0) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + +#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_msec) \ + nmtst_assert_nonnull(nmtstp_wait_for_link(platform, ifname, expected_link_type, timeout_msec)) + +#define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \ + nmtst_assert_nonnull(nmtstp_wait_for_link_until(platform, ifname, expected_link_type, until_ms)) + +/*****************************************************************************/ + +int nmtstp_run_command_check_external_global(void); +gboolean nmtstp_run_command_check_external(int external_command); + +/*****************************************************************************/ + +const NMPlatformIP4Route *_nmtstp_assert_ip4_route_exists(const char *file, + guint line, + const char *func, + NMPlatform *platform, + int c_exists, + const char *ifname, + guint32 network, + int plen, + guint32 metric, + guint8 tos); +#define nmtstp_assert_ip4_route_exists(platform, c_exists, ifname, network, plen, metric, tos) \ + _nmtstp_assert_ip4_route_exists(__FILE__, \ + __LINE__, \ + G_STRFUNC, \ + platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + tos) + +const NMPlatformIP4Route *nmtstp_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos); + +const NMPlatformIP6Route *_nmtstp_assert_ip6_route_exists(const char * file, + guint line, + const char * func, + NMPlatform * platform, + int c_exists, + const char * ifname, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); +#define nmtstp_assert_ip6_route_exists(platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + src, \ + src_plen) \ + _nmtstp_assert_ip6_route_exists(__FILE__, \ + __LINE__, \ + G_STRFUNC, \ + platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + src, \ + src_plen) + +const NMPlatformIP6Route *nmtstp_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); + +/*****************************************************************************/ + +gboolean nmtstp_ip_address_check_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred); +void nmtstp_ip_address_assert_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred); + +void nmtstp_ip4_address_add(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label); +void nmtstp_ip6_address_add(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags); +void nmtstp_ip4_address_del(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address); +void nmtstp_ip6_address_del(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen); + +void nmtstp_ip4_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + in_addr_t network, + guint8 plen, + in_addr_t gateway, + in_addr_t pref_src, + guint32 metric, + guint32 mss); + +void nmtstp_ip6_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + struct in6_addr network, + guint8 plen, + struct in6_addr gateway, + struct in6_addr pref_src, + guint32 metric, + guint32 mss); + +static inline GPtrArray * +nmtstp_ip4_route_get_all(NMPlatform *platform, int ifindex) +{ + return nm_platform_lookup_object_clone( + platform, + NMP_OBJECT_TYPE_IP4_ROUTE, + ifindex, + nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel, + NULL); +} + +static inline GPtrArray * +nmtstp_ip6_route_get_all(NMPlatform *platform, int ifindex) +{ + return nm_platform_lookup_object_clone( + platform, + NMP_OBJECT_TYPE_IP6_ROUTE, + ifindex, + nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel, + NULL); +} + +/*****************************************************************************/ + +GArray *nmtstp_platform_ip4_address_get_all(NMPlatform *self, int ifindex); +GArray *nmtstp_platform_ip6_address_get_all(NMPlatform *self, int ifindex); + +/*****************************************************************************/ + +const NMPlatformIPAddress * +nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family, gconstpointer addr); + +static inline const NMPlatformIP4Address * +nmtstp_platform_ip4_address_find(NMPlatform *self, int ifindex, in_addr_t addr) +{ + return (const NMPlatformIP4Address *) + nmtstp_platform_ip_address_find(self, ifindex, AF_INET, &addr); +} + +static inline const NMPlatformIP6Address * +nmtstp_platform_ip6_address_find(NMPlatform *self, int ifindex, const struct in6_addr *addr) +{ + return (const NMPlatformIP6Address *) + nmtstp_platform_ip_address_find(self, ifindex, AF_INET6, addr); +} + +void _nmtstp_platform_ip_addresses_assert(const char * filename, + int lineno, + NMPlatform * self, + int ifindex, + gboolean force_exact_4, + gboolean force_exact_6, + gboolean ignore_ll6, + guint addrs_len, + const char *const *addrs); + +#define nmtstp_platform_ip_addresses_assert(self, \ + ifindex, \ + force_exact_4, \ + force_exact_6, \ + ignore_ll6, \ + ...) \ + _nmtstp_platform_ip_addresses_assert(__FILE__, \ + __LINE__, \ + (self), \ + (ifindex), \ + (force_exact_4), \ + (force_exact_6), \ + (ignore_ll6), \ + NM_NARG(__VA_ARGS__), \ + ((const char *const[]){"dummy", ##__VA_ARGS__, NULL}) \ + + 1) + +/*****************************************************************************/ + +static inline gboolean +_nmtstp_platform_routing_rules_get_all_predicate(const NMPObject *obj, gpointer user_data) +{ + int addr_family = GPOINTER_TO_INT(user_data); + + g_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + + return addr_family == AF_UNSPEC + || NMP_OBJECT_CAST_ROUTING_RULE(obj)->addr_family == addr_family; +} + +static inline GPtrArray * +nmtstp_platform_routing_rules_get_all(NMPlatform *platform, int addr_family) +{ + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + return nm_platform_lookup_clone(platform, + &lookup, + _nmtstp_platform_routing_rules_get_all_predicate, + GINT_TO_POINTER(addr_family)); +} + +static inline guint +nmtstp_platform_routing_rules_get_count(NMPlatform *platform, int addr_family) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + guint n; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + + n = 0; + nmp_cache_iter_for_each (&iter, head_entry, &obj) { + if (_nmtstp_platform_routing_rules_get_all_predicate(obj, GINT_TO_POINTER(addr_family))) + n++; + } + return n; +} + +gboolean nmtstp_platform_ip4_route_delete(NMPlatform *platform, + int ifindex, + in_addr_t network, + guint8 plen, + guint32 metric); +gboolean nmtstp_platform_ip6_route_delete(NMPlatform * platform, + int ifindex, + struct in6_addr network, + guint8 plen, + guint32 metric); + +const NMPlatformLink * +nmtstp_link_get_typed(NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type); +const NMPlatformLink *nmtstp_link_get(NMPlatform *platform, int ifindex, const char *name); + +gboolean nmtstp_kernel_support_get(NMPlatformKernelSupportType type); + +void +nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifindex, gboolean up); + +const NMPlatformLink *nmtstp_link_bridge_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkBridge *lnk); +const NMPlatformLink *nmtstp_link_veth_add(NMPlatform *platform, + gboolean external_command, + const char *name, + const char *peer); +const NMPlatformLink * +nmtstp_link_dummy_add(NMPlatform *platform, gboolean external_command, const char *name); +const NMPlatformLink *nmtstp_link_gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkGre *lnk); +const NMPlatformLink *nmtstp_link_ip6tnl_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk); +const NMPlatformLink *nmtstp_link_ip6gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk); +const NMPlatformLink *nmtstp_link_ipip_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIpIp *lnk); +const NMPlatformLink *nmtstp_link_macvlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + int parent, + const NMPlatformLnkMacvlan *lnk); +const NMPlatformLink *nmtstp_link_sit_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkSit *lnk); +const NMPlatformLink *nmtstp_link_tun_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkTun *lnk, + int * out_fd); +const NMPlatformLink *nmtstp_link_vrf_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVrf *lnk, + gboolean * out_not_supported); +const NMPlatformLink *nmtstp_link_vxlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVxlan *lnk); + +void nmtstp_link_delete(NMPlatform *platform, + gboolean external_command, + int ifindex, + const char *name, + gboolean require_exist); + +/*****************************************************************************/ + +extern int NMTSTP_ENV1_IFINDEX; +extern int NMTSTP_ENV1_EX; + +static inline void +_nmtstp_env1_wrapper_setup(const NmtstTestData *test_data) +{ + int * p_ifindex; + gpointer p_ifup; + + nmtst_test_data_unpack(test_data, &p_ifindex, NULL, NULL, NULL, &p_ifup); + + g_assert(p_ifindex && *p_ifindex == -1); + + _LOGT("TEST[%s]: setup", test_data->testpath); + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + + *p_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(*p_ifindex, >, 0); + g_assert_cmpint(NMTSTP_ENV1_IFINDEX, ==, -1); + + if (GPOINTER_TO_INT(p_ifup)) + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, *p_ifindex, NULL)); + + nm_platform_process_events(NM_PLATFORM_GET); + + NMTSTP_ENV1_IFINDEX = *p_ifindex; + NMTSTP_ENV1_EX = nmtstp_run_command_check_external_global(); +} + +static inline void +_nmtstp_env1_wrapper_run(gconstpointer user_data) +{ + const NmtstTestData *test_data = user_data; + GTestDataFunc test_func_data; + GTestFunc test_func; + gconstpointer d; + + nmtst_test_data_unpack(test_data, NULL, &test_func, &test_func_data, &d, NULL); + + _LOGT("TEST[%s]: run", test_data->testpath); + if (test_func) + test_func(); + else + test_func_data(d); +} + +static inline void +_nmtstp_env1_wrapper_teardown(const NmtstTestData *test_data) +{ + int *p_ifindex; + + nmtst_test_data_unpack(test_data, &p_ifindex, NULL, NULL, NULL, NULL); + + g_assert_cmpint(NMTSTP_ENV1_IFINDEX, ==, *p_ifindex); + NMTSTP_ENV1_IFINDEX = -1; + + _LOGT("TEST[%s]: teardown", test_data->testpath); + + g_assert_cmpint(*p_ifindex, ==, nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(nm_platform_link_delete(NM_PLATFORM_GET, *p_ifindex)); + + nm_platform_process_events(NM_PLATFORM_GET); + + _LOGT("TEST[%s]: finished", test_data->testpath); + + *p_ifindex = -1; +} + +/* add test function, that set's up a particular environment, consisting + * of a dummy device with ifindex NMTSTP_ENV1_IFINDEX. */ +#define _nmtstp_env1_add_test_func_full(testpath, test_func, test_data_func, arg, ifup) \ + nmtst_add_test_func_full(testpath, \ + _nmtstp_env1_wrapper_run, \ + _nmtstp_env1_wrapper_setup, \ + _nmtstp_env1_wrapper_teardown, \ + ({ \ + static int _ifindex = -1; \ + &_ifindex; \ + }), \ + ({ \ + GTestFunc _test_func = (test_func); \ + _test_func; \ + }), \ + ({ \ + GTestDataFunc _test_func = (test_data_func); \ + _test_func; \ + }), \ + (arg), \ + ({ \ + gboolean _ifup = (ifup); \ + GINT_TO_POINTER(_ifup); \ + })) + +#define nmtstp_env1_add_test_func_data(testpath, test_func, arg, ifup) \ + _nmtstp_env1_add_test_func_full(testpath, NULL, test_func, arg, ifup) + +#define nmtstp_env1_add_test_func(testpath, test_func, ifup) \ + _nmtstp_env1_add_test_func_full(testpath, test_func, NULL, NULL, ifup) + +/*****************************************************************************/ + +typedef void (*NMTstpSetupFunc)(void); +extern NMTstpSetupFunc const _nmtstp_setup_platform_func; + +void nmtstp_setup_platform(void); + +/*****************************************************************************/ + +typedef struct _NMTstpAcdDefender NMTstpAcdDefender; + +NMTstpAcdDefender * +nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr); + +void nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender); + +/*****************************************************************************/ + +void _nmtstp_init_tests(int *argc, char ***argv); +void _nmtstp_setup_tests(void); diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c new file mode 100644 index 0000000..895b89a --- /dev/null +++ b/src/core/platform/tests/test-link.c @@ -0,0 +1,3823 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-base/nm-ethtool-base.h" +#include "platform/nmp-object.h" +#include "nm-platform/nmp-netns.h" +#include "nm-platform/nm-platform-utils.h" + +#include "test-common.h" +#include "nm-test-utils-core.h" + +#define LO_INDEX 1 +#define LO_NAME "lo" +#define LO_TYPEDESC "loopback" + +#define DUMMY_TYPEDESC "dummy" +#define BOGUS_NAME "nm-bogus-device" +#define BOGUS_IFINDEX INT_MAX +#define SLAVE_NAME "nm-test-slave" +#define PARENT_NAME "nm-test-parent" +#define VLAN_ID 4077 +#define VLAN_FLAGS 0 +#define MTU 1357 + +#define _ADD_DUMMY(platform, name) \ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add((platform), (name), NULL))) + +static void +test_bogus(void) +{ + size_t addrlen; + + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, BOGUS_NAME)); + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_ifindex(NM_PLATFORM_GET, BOGUS_NAME)); + g_assert(!nm_platform_link_get_name(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_type(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_type_name(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_up(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + + g_assert(!nm_platform_link_set_down(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_arp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_noarp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_get_address(NM_PLATFORM_GET, BOGUS_IFINDEX, &addrlen)); + g_assert(!addrlen); + g_assert(!nm_platform_link_get_address(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + + g_assert(!NMTST_NM_ERR_SUCCESS(nm_platform_link_set_mtu(NM_PLATFORM_GET, BOGUS_IFINDEX, MTU))); + + g_assert(!nm_platform_link_get_mtu(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_supports_vlans(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + g_assert(!nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0)); + g_assert(!nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0)); +} + +static void +test_loopback(void) +{ + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, LO_NAME)); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, LO_INDEX), + ==, + NM_LINK_TYPE_LOOPBACK); + g_assert_cmpint(nm_platform_link_get_ifindex(NM_PLATFORM_GET, LO_NAME), ==, LO_INDEX); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, LO_INDEX), ==, LO_NAME); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, LO_INDEX), ==, LO_TYPEDESC); + + g_assert(nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, LO_INDEX)); + g_assert(!nm_platform_link_supports_vlans(NM_PLATFORM_GET, LO_INDEX)); +} + +static gboolean +software_add(NMLinkType link_type, const char *name) +{ + switch (link_type) { + case NM_LINK_TYPE_DUMMY: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, name, NULL)); + case NM_LINK_TYPE_BRIDGE: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + name, + NULL, + 0, + 0, + &nm_platform_lnk_bridge_default, + NULL)); + case NM_LINK_TYPE_BOND: + { + gboolean bond0_exists = !!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0"); + int r; + + r = nm_platform_link_bond_add(NM_PLATFORM_GET, name, NULL); + + /* Check that bond0 is *not* automatically created. */ + if (!bond0_exists) + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0")); + return r >= 0; + } + case NM_LINK_TYPE_TEAM: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_team_add(NM_PLATFORM_GET, name, NULL)); + case NM_LINK_TYPE_VLAN: + { + SignalData *parent_added; + SignalData *parent_changed; + + /* Don't call link_callback for the bridge interface */ + parent_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + PARENT_NAME); + if (NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + PARENT_NAME, + NULL, + 0, + 0, + &nm_platform_lnk_bridge_default, + NULL))) + accept_signal(parent_added); + free_signal(parent_added); + + { + int parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + gboolean was_up = nm_platform_link_is_up(NM_PLATFORM_GET, parent_ifindex); + + parent_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + parent_ifindex); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, parent_ifindex, NULL)); + if (was_up) { + /* when NM is running in the background, it will mess with addrgenmode which might cause additional signals. */ + accept_signals(parent_changed, 0, 1); + } else + accept_signals(parent_changed, 1, 2); + free_signal(parent_changed); + + return NMTST_NM_ERR_SUCCESS( + nm_platform_link_vlan_add(NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL)); + } + } + default: + g_error("Link type %d unhandled.", link_type); + } + g_assert_not_reached(); +} + +static void +test_link_changed_signal_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP4Route *route, + int change_type_i, + gboolean * p_test_link_changed_signal_arg) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + + /* test invocation of platform signals with multiple listeners + * connected to the signal. Platform signals have enum-typed + * arguments and there seem to be an issue with invoking such + * signals on s390x and ppc64 archs. + * https://bugzilla.redhat.com/show_bug.cgi?id=1260577 + * + * As the test shows, the failure is not reproducible for + * platform signals. + */ + g_assert(NM_IS_PLATFORM(platform)); + g_assert(platform == NM_PLATFORM_GET); + + g_assert(ifindex > 0); + g_assert(route); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_LINK); + + g_assert_cmpint((gint64) change_type, !=, (gint64) 0); + g_assert_cmpint(change_type, !=, NM_PLATFORM_SIGNAL_NONE); + + *p_test_link_changed_signal_arg = TRUE; +} + +static void +test_slave(int master, int type, SignalData *master_changed) +{ + int ifindex; + SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + SLAVE_NAME); + SignalData *link_changed, *link_removed; + char * value; + NMLinkType link_type = nm_platform_link_get_type(NM_PLATFORM_GET, master); + gboolean test_link_changed_signal_arg1; + gboolean test_link_changed_signal_arg2; + + g_assert(NM_IN_SET(link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE)); + + g_assert(software_add(type, SLAVE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME); + g_assert(ifindex > 0); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + accept_signal(link_added); + + /* Set the slave up to see whether master's IFF_LOWER_UP is set correctly. + * + * See https://bugzilla.redhat.com/show_bug.cgi?id=910348 + */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + ensure_no_signal(link_changed); + + /* Enslave */ + link_changed->ifindex = ifindex; + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, master); + + accept_signals(link_changed, 1, 3); + accept_signals(master_changed, 0, 2); + + /* enslaveing brings put the slave */ + if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + else + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + + test_link_changed_signal_arg1 = FALSE; + test_link_changed_signal_arg2 = FALSE; + g_signal_connect(NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg1); + g_signal_connect(NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg2); + + /* Set master up */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, master, NULL)); + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, master)); + accept_signals(master_changed, 1, 3); + + g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg1); + g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg2); + g_assert(test_link_changed_signal_arg1); + g_assert(test_link_changed_signal_arg2); + + /* Master with a disconnected slave is disconnected + * + * For some reason, bonding and teaming slaves are automatically set up. We + * need to set them back down for this test. + */ + switch (nm_platform_link_get_type(NM_PLATFORM_GET, master)) { + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + accept_signals(master_changed, 0, 3); + break; + default: + break; + } + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, master)) { + if (nm_platform_link_get_type(NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) { + /* Older team versions (e.g. Fedora 17) have a bug that team master stays + * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if + * `ip link` also claims master to be up, accept it. */ + char *stdout_str = NULL; + + nmtst_spawn_sync(NULL, + &stdout_str, + NULL, + 0, + "/sbin/ip", + "link", + "show", + "dev", + nm_platform_link_get_name(NM_PLATFORM_GET, master)); + + g_assert(strstr(stdout_str, "LOWER_UP")); + g_free(stdout_str); + } else + g_assert_not_reached(); + } + + /* Set slave up and see if master gets up too */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, master)); + accept_signals(link_changed, 1, 3); + /* NM running, can cause additional change of addrgenmode */ + accept_signals(master_changed, 0, 3); + + /* Enslave again + * + * Gracefully succeed if already enslaved. + */ + ensure_no_signal(link_changed); + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); + accept_signals(link_changed, 0, 2); + accept_signals(master_changed, 0, 2); + + /* Set slave option */ + switch (type) { + case NM_LINK_TYPE_BRIDGE: + if (nmtstp_is_sysfs_writable()) { + g_assert( + nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET, ifindex, "priority", "614")); + value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex, "priority"); + g_assert_cmpstr(value, ==, "614"); + g_free(value); + } + break; + default: + break; + } + + /* Release */ + ensure_no_signal(link_added); + ensure_no_signal(link_changed); + ensure_no_signal(link_removed); + g_assert(nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, 0); + if (link_changed->received_count > 0) { + accept_signals(link_added, 0, 1); + accept_signals(link_changed, 1, 5); + accept_signals(link_removed, 0, 1); + } else { + /* Due to https://bugzilla.redhat.com/show_bug.cgi?id=1285719 , kernel might send a + * wrong RTM_DELLINK message so that we instead see an removed+added signal. */ + accept_signal(link_added); + ensure_no_signal(link_changed); + accept_signal(link_removed); + } + accept_signals(master_changed, 0, 3); + + ensure_no_signal(master_changed); + + /* Release again */ + ensure_no_signal(link_changed); + g_assert(!nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); + + ensure_no_signal(master_changed); + + /* Remove */ + ensure_no_signal(link_added); + ensure_no_signal(link_changed); + ensure_no_signal(link_removed); + nmtstp_link_delete(NULL, -1, ifindex, NULL, TRUE); + accept_signals(master_changed, 0, 1); + accept_signals(link_changed, 0, 1); + accept_signal(link_removed); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +static void +test_software(NMLinkType link_type, const char *link_typename) +{ + int ifindex; + char *value; + int vlan_parent = -1, vlan_id; + + SignalData *link_added, *link_changed, *link_removed; + + /* Add */ + link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + g_assert(software_add(link_type, DEVICE_NAME)); + accept_signal(link_added); + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex >= 0); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, link_type); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, link_typename); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + if (link_type == NM_LINK_TYPE_VLAN) { + const NMPlatformLink * plink; + const NMPlatformLnkVlan *plnk; + + plnk = nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, &plink); + g_assert(plnk); + g_assert(plink); + + vlan_parent = plink->parent; + vlan_id = plnk->id; + g_assert_cmpint(vlan_parent, + ==, + nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME)); + g_assert_cmpint(vlan_id, ==, VLAN_ID); + } + + /* Add again */ + g_assert(!software_add(link_type, DEVICE_NAME)); + + /* Set ARP/NOARP */ + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signals(link_changed, 1, 2); + g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* Set master option */ + if (nmtstp_is_root_test()) { + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + if (nmtstp_is_sysfs_writable()) { + g_assert(nm_platform_sysctl_master_set_option(NM_PLATFORM_GET, + ifindex, + "forward_delay", + "628")); + value = + nm_platform_sysctl_master_get_option(NM_PLATFORM_GET, ifindex, "forward_delay"); + g_assert_cmpstr(value, ==, "628"); + g_free(value); + } + break; + case NM_LINK_TYPE_BOND: + if (nmtstp_is_sysfs_writable()) { + g_assert(nm_platform_sysctl_master_set_option(NM_PLATFORM_GET, + ifindex, + "mode", + "active-backup")); + value = nm_platform_sysctl_master_get_option(NM_PLATFORM_GET, ifindex, "mode"); + /* When reading back, the output looks slightly different. */ + g_assert(g_str_has_prefix(value, "active-backup")); + g_free(value); + } + break; + default: + break; + } + } + + /* Enslave and release */ + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + link_changed->ifindex = ifindex; + test_slave(ifindex, NM_LINK_TYPE_DUMMY, link_changed); + link_changed->ifindex = 0; + break; + default: + break; + } + free_signal(link_changed); + + /* Delete */ + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + accept_signal(link_removed); + + /* Delete again */ + g_assert(nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME) <= 0); + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + /* VLAN: Delete parent */ + if (link_type == NM_LINK_TYPE_VLAN) { + SignalData *link_removed_parent = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + vlan_parent); + + nmtstp_link_delete(NULL, -1, vlan_parent, NULL, TRUE); + accept_signal(link_removed_parent); + free_signal(link_removed_parent); + } + + /* No pending signal */ + free_signal(link_added); + free_signal(link_removed); +} + +static void +test_bridge(void) +{ + test_software(NM_LINK_TYPE_BRIDGE, "bridge"); +} + +static int +_system(const char *cmd) +{ + /* some gcc version really want to warn on -Werror=unused-result. Add a bogus wrapper + * function. */ + return system(cmd); +} + +static void +test_bond(void) +{ + if (nmtstp_is_root_test() && !g_file_test("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) + && _system("modprobe --show bonding") != 0) { + g_test_skip("Skipping test for bonding: bonding module not available"); + return; + } + + test_software(NM_LINK_TYPE_BOND, "bond"); +} + +static void +test_team(void) +{ + int r; + + if (nmtstp_is_root_test()) { + r = nm_platform_link_team_add(NM_PLATFORM_GET, "nm-team-check", NULL); + + if (r < 0) { + g_assert_cmpint(r, ==, -EOPNOTSUPP); + g_test_skip("Skipping test for teaming: team module not functioning"); + return; + } + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, "nm-team-check", FALSE); + } + + test_software(NM_LINK_TYPE_TEAM, "team"); +} + +static void +test_vlan(void) +{ + test_software(NM_LINK_TYPE_VLAN, "vlan"); +} + +/*****************************************************************************/ + +static void +test_bridge_addr(void) +{ + char addr[ETH_ALEN]; + NMPlatformLink link; + const NMPlatformLink *plink = NULL; + + nm_utils_hwaddr_aton("de:ad:be:ef:00:11", addr, sizeof(addr)); + + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + DEVICE_NAME, + addr, + sizeof(addr), + 0, + &nm_platform_lnk_bridge_default, + &plink))); + g_assert(plink); + link = *plink; + g_assert_cmpstr(link.name, ==, DEVICE_NAME); + + g_assert_cmpint(link.l_address.len, ==, sizeof(addr)); + g_assert(!memcmp(link.l_address.data, addr, sizeof(addr))); + + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_EUI64); + + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, TRUE))); + g_assert(nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_NONE); + + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, FALSE))); + g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_EUI64); + } + + g_assert_cmpint(plink->l_address.len, ==, sizeof(addr)); + g_assert(!memcmp(plink->l_address.data, addr, sizeof(addr))); + + nmtstp_link_delete(NULL, -1, link.ifindex, link.name, TRUE); +} + +/*****************************************************************************/ + +static void +test_internal(void) +{ + SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + SignalData *link_changed, *link_removed; + const char mac[6] = {0x00, 0xff, 0x11, 0xee, 0x22, 0xdd}; + const char *address; + size_t addrlen; + int ifindex; + + /* Check the functions for non-existent devices */ + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(!nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME)); + + /* Add device */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + accept_signal(link_added); + + /* Try to add again */ + g_assert(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL) == -NME_PL_EXISTS); + + /* Check device index, name and type */ + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + + /* Up/connected */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + accept_signals(link_changed, 1, 2); + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* arp/noarp */ + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* Features */ + g_assert(!nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_supports_vlans(NM_PLATFORM_GET, ifindex)); + + /* Set MAC address */ + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_address(NM_PLATFORM_GET, ifindex, mac, sizeof(mac)))); + address = nm_platform_link_get_address(NM_PLATFORM_GET, ifindex, &addrlen); + g_assert(addrlen == sizeof(mac)); + g_assert(!memcmp(address, mac, addrlen)); + address = nm_platform_link_get_address(NM_PLATFORM_GET, ifindex, NULL); + g_assert(!memcmp(address, mac, addrlen)); + accept_signal(link_changed); + + /* Set MTU */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_set_mtu(NM_PLATFORM_GET, ifindex, MTU))); + g_assert_cmpint(nm_platform_link_get_mtu(NM_PLATFORM_GET, ifindex), ==, MTU); + accept_signal(link_changed); + + /* Delete device */ + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + accept_signal(link_removed); + + /* Try to delete again */ + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +/*****************************************************************************/ + +static void +test_external(void) +{ + const NMPlatformLink *pllink; + SignalData * link_added, *link_changed, *link_removed; + int ifindex; + + link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + + nmtstp_run_command_check("ip link add %s type %s", DEVICE_NAME, "dummy"); + wait_signal(link_added); + + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex); + g_assert(pllink); + if (!pllink->initialized) { + /* we still lack the notification via UDEV. Expect another link changed signal. */ + wait_signal(link_changed); + } + + /* Up/connected/arp */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link set %s up", DEVICE_NAME); + wait_signal(link_changed); + + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + nmtstp_run_command_check("ip link set %s down", DEVICE_NAME); + wait_signal(link_changed); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link set %s arp on", DEVICE_NAME); + wait_signal(link_changed); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + nmtstp_run_command_check("ip link set %s arp off", DEVICE_NAME); + wait_signal(link_changed); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link del %s", DEVICE_NAME); + wait_signal(link_removed); + accept_signals(link_changed, 0, 1); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +/*****************************************************************************/ + +static guint8 * +_copy_base64(guint8 *dst, gsize dst_len, const char *base64_src) +{ + g_assert(dst); + g_assert(dst_len > 0); + + if (!base64_src) + memset(dst, 0, dst_len); + else { + gs_free guint8 *b = NULL; + gsize l; + + b = g_base64_decode(base64_src, &l); + g_assert(b); + g_assert(l == dst_len); + + memcpy(dst, b, dst_len); + } + return dst; +} + +typedef struct { + const char *pri; + const char *pub; + const char *pre; +} KeyPair; + +static void +_test_wireguard_change(NMPlatform *platform, int ifindex, int test_mode) +{ + const KeyPair self_key = {"yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=", + "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk="}; + const KeyPair keys[100] = { + {"+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=", + "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=", + "2IvZnKTzbF1UlChznWSsEYtGbPjhYSTT41GXO6zLxvk="}, + {"qGZyV2BO1nyY/FGYd6elBPirwJC9QyZwqbm2OJAgLkY=", + "v8L1FEitO0xo+wW/CVVUnALlw0zGveApSFdlITi/5lI=", + "/R2c0JmBNGJzT594NQ0mBJ2XJjxt2QUSo+ZiqeY0EQA="}, + {"YDgsIb0oe+9NcxIx2r0HEEPQpRMxmRN0ALoLm9Sh40Q=", + "nFPs1HaU7uFBvE9xZCMF8oOAjzLpZ49AzDHOluY1O2E=", + "zsYED2Ef7zIHJoRBPcen+w4ktrRsLPEfYwZhWIuXfds="}, + {"kHkosM503LWu43tdYXbNwVOpRrtPgd9XFqcN7k4t6G4=", + "b8e092WT+eNmnxCr5WE2QC/MXAjDagfG1g03cs2mBC0=", + "VXz0ShGWT7H0CBCg2awfatmOJF15ZtaSMPhMsp+hc3A="}, + {"4C2w5CEnxH59Y2aa6CJXgLdDtWoNMS2UJounRCM1Jkk=", + "gC/R9umlnEQL+Qsz/Y64AlsdMge4ECe5/u/JHZCMWSs=", + "2bmL5ISr+V5a7l7xFJ695BLIJyBgx8xnxzybkxiRHOg="}, + {"KHJSzFGkXcZf/wbH2rr99SYtGKWbL680wA2AcDE94lo=", + "BsN23h4aOi458Q3EgMQsodsWQxd9h/RxqskAUpsXfgg=", + "nK4Zv34YKEhjuexhq1SgK4oTd4MZJT5gcpYvEuPjc7Q="}, + {"QGMulXJ9e3AVxtpi8+UUVqPOr/YBWvCNFVsWS9IUnUA=", + "kjVclP5Ifi6og2BBCEHKS/aa/WktArB4+ig06lYaVlg=", + "0+mmceDPcSRK3vFnYqHd9iAfY+Nyjzf/1KgDeYGlRkQ="}, + {"AOJiDD4y6GA7P7gugjjQG9Cctvc5Y27fajHz6aU3gU4=", + "gEnHn6euHtcMEwZBlX6HANPeN9Of+voBDtltS38xDUw=", + "wIH1OxgX6GLxx/bnR+t3fbmjGZDTU3WMxp7t1XGezqM="}, + {"COsls2BlCltaIrrq1+FU51cWddlmoPPppSeIDunOxGA=", + "+n6WuV8Tb1/iZArTrHsyNqkRHABbavBQt9Me72K2KEc=", + "t4baiprSO9ZbKD2/RutOY9cr+yCajQWZGCTnQdrFQj0="}, + {"uHawQq2BRyJlsTPoCa+MfBVnv4MwtRoS+S9FEpFOEVg=", + "8lcmr27afeb6iI3BQCaDtvalF2Cl7gxRZgs+nyJ/fEg=", + "Eh9o/6W60iujBLIHuRfNrPhOWAn7PambT2JRln9iwA0="}, + {"yL7hmoE/JfRGAotRzx9xOpjfrDA3BFlPEemFiQw40Wk=", + "BHK0PHi5kp7rOfQ46oc9xlVpB+pZeZYXTtH7EXr5TwU=", + "BS2h2ZZyW0BlYMmLR29xyHEcZ4jtO7rgj1jkz/EEaxU="}, + {"ON8YrTHQgoC5e0mAR9FakZ8hZ/9I7ysuE21sG546J1Y=", + "Bm3l5I6iH1tDrv6EgdZU9PzHqp0H26Z6ggmmIypwxy8=", + "qKVfbCnK1EI3eC28SsL+jyBnEnIF/nRJQJzDevBFdNQ="}, + {"KGLO0RI0VlQFCG+ETUqk25HdaKUnUPXKOKkTh/w8BXU=", + "sBDBwQFC7k8UMdMzuavKBkBjYYKigzvYmGF5rm6muQc=", + "BNfZF9d7540pUGGOQCh3iDxAZcPBqX4qqM00GSLPBek="}, + {"KGdWyEodIn7KKI2e4EFK9tT6Bt3bNMFBRFVTKjjJm2E=", + "lrbjU/Xn9wDCZQiU8E5fY6igTSozghvo47QeVIIWaUk=", + "LczqW48MW8qDQIZYRELsz/VCzVDc5niROvk7OqrTxR0="}, + {"wO3xinUGABgEmX+RJZbBbOtAmBuPPFG6oVdimvoo90w=", + "dCIvTzR6EerOOsRKnWly1a9WGzbJ4qc+6t3SSzLgWFk=", + "wFj0zpr5PadBoBy0couLuZ1qudZbXLbV/j3UT+AyKeo="}, + {"+JNOBlO4tp9vvQk6UO4r3sILyEgjl+BBoWketZufyn0=", + "Q6LSv9y7YQkJEzQ/1mpjJEgOrO8GYPUTgcizjh7Cm34=", + "kg7AG9MuN04xPJ5Z0IcNZ4a8d1b/n4GsGIeyA4FaaSE="}, + {"+EJcThLRwjZ+h1CNNFu15HWzznf4u/lPVw8hifTm2Ec=", + "Kkn2jFqwBzyIFQfD0OpmePFSmBYmhKagv4pGgkqsWgE=", + "jYpxojj8WKYe/XIXMP+uv1Fv0+TKrs83tqfzP0AGdcI="}, + {"+DKFqSMNFmxriEFj3qatuzYeTJ9+xWYspZ4ydL3eC0Q=", + "3o37bsg6HhRg/M9+fTlLcFYc2w/Bz9rLQySvvYCKbRE=", + "Jb9qoDIBat1EexlgfpRbXa7OflptME8/zt93bldkiVE="}, + {"EH3MjFOMqRoDFQz+hSlJpntWBeH3lTk6WPjTIQjr42o=", + "PbPewED/nxSBLdM7AXMj7uS3bCgAAg8M6F4iPLd0b1U=", + "pj4+UgOGkpJwlRvX5BRXZzmzAUnDtUtJsS7LzbcJWzw="}, + {"kL6M2KvO+vPBLEc/a0DpEHTibQ1bwaMRT9b9SkzeP0Y=", + "pS3G2bHHlkOE6UHP0qVitDuxXgjEaZTviTjNc55RbVs=", + "ZVZpWOtYqhX3CpF1kATg/38J6pvUJo8AS1sVYjs3rUE="}, + {"sFlcRLDn36fnew2Ld92IHJwnKifdS3aF1MWRPs6K3Wg=", + "OpjUOTiWExaDYULTINB4yQFqc3mnU3RjQzGRV+KtdFY=", + "of0V/uoFRNljv/XTt/tXgoquLRH93Ty0KNiaPUpEi2w="}, + {"UJ8hjDsg3jfsnnfPH8Gw9FnCb6taTuviurAfZu+kEFg=", + "3byjHksUOv8CNjGGKvHvvrJDURBhCIL5UtfZbgyVWCE=", + "9f7dbWif51gGrE7R9LeuewQSrvGFGTOB3ceJC67jSkI="}, + {"iFFPKGIfqeUKY/w72KAZSjd/PGTqCakHYBV10xMDfnI=", + "ehneHATNSXtsJTOiPjVSc0QARkihgcfcvoXKFWKfQnI=", + "yKdqDBcRwA7RCg4GiY/b5IsWcExPleOBde/hjxc36a4="}, + {"GDGocdPJTPUAllxQo7SpXZKqMPn7lpxQELQUX9ETHmE=", + "n0ScNEou4ekfrXRRXvcADLu2Afj8g5D3TuDP/I4KrnY=", + "8QhswqAhi/ehhcmCwQF5aSh80TvIGC/gRL5jBn5wOH8="}, + {"UCcrlN8fX2ZdNdhaEBNwktwL+H0ZO7fhaj7rgdUutmw=", + "LF8J728ilXs4TphnrgR6r0p7W3912DYnsXkGMEPQnRE=", + "cYfMjxl2REYir7frGeB0u+NHAaFYF02ysgpBhOL5ygc="}, + {"SH0657XuIiHidVmViXZF30RUWtkuWXcWWHmKZHTiOG8=", + "7k6j49W1u5qgLE5MQUc1osPVW1oPPhzjrGvJ7o9YamY=", + "dV2+7rNk/3LR2IcwYg/c+Wvzep1yjY7/u1I+nnlTQ00="}, + {"qFQWs6jzrscV42pGISQyA7JDvFFAvEQCWJi584VHD2g=", + "AT63nHKLC17yUvkR4lOVPxCr4DD3QhXmcmecOTn+Amc=", + "2Qe2fwJbFcu1CmKpktElOFkSQMqlyvlV3ZUIAd/Dcts="}, + {"6J+yLxgPwWtUbk9I3zbeD8RuK6XkQjJ0wTJ1zSVhflQ=", + "NJzMBYyPZjk3eLmgdKaOeWyNER5YZF1mR8Umeiu9f28=", + "pp+4+XHw5ZmHGJ7WbZ1xLYRsnTI17QIbb0bzHzYZrBs="}, + {"KJGoWYNVDUWcEMg+E4tljv1LiWAbdRw2QVapYqdFa1Y=", + "M2SGk9WVnzYNGnT777G/JE8uUsY2f7mszTwlue73UDE=", + "Jg8N7GbhbYB400foFP+OH0v+hCaL1jW61bajSA6EZqc="}, + {"uAgrgppPyIvk8S1CHUmaCaORsgFFfBreB8pxXmbSdXw=", + "dJ22bER4fD3qF2/yIGWQ7SgmZ990sy/SbANjmUMkzws=", + "mKkd0OoAR9sClmD+k3fL9weBsoCy5GQGz9BP0kAQIjc="}, + {"EI3Q8gePNPrtFyoMcv7hOihQgroF/dfjzPn8yvpGPWQ=", + "Y9QhJFeiyuuIZNPU56B6U//ZK+XTTe4EP7h/p3Q+dE8=", + "qBVUYw9rTWaxn55nUd55NpCWtxOUSWLt4WJGusiDS8Q="}, + {"oEQvdRm/yHkx+JvlhHGT5RUFrFEUleKb9DCT55EqZ30=", + "8hsh/UHwWADTHntOJq4dy0o7ahcNAAlo2rDpjzzrVXk=", + "/GF2inW2mPtA26IgFdgOEBbBEerT740wWuP/8NyANdc="}, + {"6OWrGKZKNsgfRezSUw29EnHymcgKyEKvX5/pZQlLmWs=", + "oaJeO6YSS2dodNEf97DvgWrYnFFelG5daEdN84jVVGw=", + "T5wvTdyVxK0LY96kouLjs06oUfhGChfty8OUL1Mddro="}, + {"mPmzQbh2R+r1DC5hSquzKM1SDrxUdfnBPRPJrpqrgkE=", + "BtUHAnYWjDjBI42qBf9dJqezTUikYsF96o6PKEWPrVo=", + "MxU8EMmq+vVHpuK/AkFZrZDF2b+VbSqukZLPbNsCcgo="}, + {"8GVxuoo1Veyr+nqxr5Q4vmsMf5qfiXwSlQ4q3+BU60g=", + "uSOLe/E9/OjIgUOk0NMBHB45E0+q4Rd+IUO2UOxmKlM=", + "+30F+56OE0Sr3wY2clKw4kgE+2XiucMg7xjK6EemXuk="}, + {"qORPKb+qFuU/9TpFbRUupHsqm9iyk9pa6cpik+EVDkg=", + "bMZxxd+Z9I0XA1h9U8JEY+/mRxWGnvbXDZ5Dxz7YzS0=", + "SmkkqOz4OhHuSL6cxuRm9+Mlt50Sfd0sMDFTC78gqOE="}, + {"2Ko3IYhXKcdOMIJGNpASk9saNSZsI64lyJPOoxpQ2kI=", + "xVOc9PxY1VFaZfemmKi+Ei2liHhmeTu+JMa+rS00gnA=", + "+398DlW8kWeI2aRaC4QfcrEjwqPKCohyDeWdaI1wvv0="}, + {"CPioGCVpxnym62nH/QoCt1RiiaaxUcuFjvh5kRhjqHA=", + "W0XxlBLrZgFKhggMvvv6oFf/RJbfs92qv8JK9e+5i2o=", + "bsK2U6CRAUv1uVgYQ7NpqjWWswFIDiDPDEtU1XQygSA="}, + {"AF17siKaeiO85hikYN0IWCMGWqPm1UOoCkXMltJGUVk=", + "B+PFos9aN2S5bLxzGZHljRZj41j3rIx8RWu0vDUzq1w=", + "Qb8d6iDYv3m1h7PE8j0Exl2cSwpHkim/fJ1S4P7MYvY="}, + {"wMFDBTJzx5tDCBhMkrptYJ8w9EeURjc4xeDQpevxAWo=", + "4ec439EXE5WQzvtV9reSX+aMmdq5k7o9Ayt8oQp+RhQ=", + "jwQlvdNH5WtSSU10H+fh/JisOlaBaohDPEp/BYnTt1Q="}, + {"4GaJpIFigNDwd31O84pLIMM/o2qhp0ydlI/ydD/2a2Q=", + "r/LdkCoK5/BPGdq2+XJO8sCRhI+8ULFmg0887V43PAI=", + "Da+3ZZvEJdx4TYFMIDUlbkmytILnSTNxTKX+sQdjMd4="}, + {"gMnojGqCLmMfGp2m31xlKZ/rIV2b8ockw9DPahRyu0c=", + "H6tKCTosnM4BXKqflXrkTdJyNlCIZhQ3ZRxfrvSdrDk=", + "4Z6K3LKIMV89plcjMb9CzSzJl03SWRe/++geBMZcOtY="}, + {"wKCg22aNNoHnDJ0oAKE46FcSSsREW4AaGn5WxCSeXUs=", + "9NDTFC0iPt4HbbWepLHhN5poNTN2fdxJKNadsNT7qzY=", + "GSVTOCnfLpJ1VCOLHaKSjCMv7/OlcnQiP5+5woqkud8="}, + {"oCoykq7pcJcg2X2V5TBRzGwn8hzzHC05WUreuotdznY=", + "DxfwnbMqr5Wn5SAyFolfEmNQT6l84Oq69ngpg6H6Iio=", + "p1RHBuqhuDa1MAQ2lbqmUQFu0CTwYlf73fWZSj9tQhA="}, + {"UO7YVyRUVkcKr7c63VWWV2zj36XD3HyDfLZCqvrZmFc=", + "360lzYtIyHq5lv/QXSCe4bL4G2J1jBXFJ8yS+Ycr7Bc=", + "RRPQ1XWF1HN8Us4dtfn2eemdjgtWm7U8r7mM3y00NOk="}, + {"eCGFYV/NuGP4H552E8Of1xU+3IvZxGyX+p5UFGW8iHI=", + "LqhZ4AS9dQ/MhsQnE5Oy7Q8INXY+P+mrfGY5dtg9SlE=", + "SICfqs8T5wP6IzATDCT4ovamBKPdkZ7JP4Cfsb3izec="}, + {"oI3HBZknoIMMZw1BuYMkTBylt25reX7AbCqtWQv8cno=", + "B7dUgLgvQhi0RGmvaMrmf26WdjEVrhaiBclVkCKd4AA=", + "5O9K9pLXwxFAt5lfMWh4qGbwX1BM7sz0QGYxAnR77dk="}, + {"sBfYn14EFVIS2M/E1aahP7mOmRNbNtyDChDMS1s6aGs=", + "FLYv0ZvzxMkc9A7OzhC4P1ZRu1aKIQd7u6gqfdekC3c=", + "kaYLcNCXnCLgiB8fleMQuboUJsj5u3YAmXL9x3ywV0M="}, + {"qFwZESU/XYZXUtxwrGsFU9qPAFTzjm7EhTS1Q6ajGlM=", + "hraQQaqJCkS2yQXv+ccMOVh9V9a/qgSZJgdMAhrt8ms=", + "72oDfnWOn39gdk/ncw8Lv267I0I+m73SwxrpYojpWYk="}, + {"YHhp6Zf/miuc2QXeI2lTezy0lL0pTv2b+nWNkmjYQWI=", + "UhNO5arLzF0WlZSgNOx7+IjWN+GSxDdQxZRp8uIwsyI=", + "1Q39Nzv2NGI9zWKWMpYLURAMZUg+FP+OboHHzFU8Anc="}, + {"mLBBXKaCJ+7qeBZpS3wxGi/SQ4kLzun+K+QwwdwfJVQ=", + "gIq/nh7NwCJ36MvRnyrWHaRWu8lTmwfN2NvsjVl6SXQ=", + "AahcNR91GDyJBIP+vC8ZuIV8ukqjSGtd8s+cmjVC2Ao="}, + {"OJG4LZlNNngFtAEQdbVVWVm6QAjOOauGcMZGbQrb40M=", + "pFHAC6HaWAOtvTRRVfSHvzG05mp4SJZXKsN/tkSF0kM=", + "IoXT3wIqWNxQhYuHWl12ODq/P7RM9LwaqglhmjKg+0g="}, + {"OOwBFOQNhepiqDf04DehQLh1gpBNOluDF1ia752Yfng=", + "u715uJ/XhdjXjThCTJ9w6zzXnIhp3VCxhtso1wk+oBQ=", + "5x1Ip3Ym0KzDjGhiYjmpeWWr+dgrZlYwfr02GngPOTM="}, + {"eNPFnwkQy1qw+IjFAlrDA6+sIsxbWDlzbNSsBW8R1UA=", + "OaOXaAfPb1MRpWadawFje8YZ0oxJgdCIDIP8c5X+r1U=", + "NtfaRRD0GqujnaQQoNoBbtovgO4dfVwEmEQx/YgnDpw="}, + {"OLdaZItbtxH3mGqItkibIJp7KV27FrQavjhd5zq6s3Q=", + "SLSmAYxkMCGj0DO35cMLkC3NVAqK2VmVFndbOZEdA24=", + "SnBO68XQTDjxYbmYaAeEHgLwD2u4D+BPT86raRuUQZM="}, + {"UBQOEz08izwr4eEK/SnQUpkt+TxCjo6Sya/XOGMLOE0=", + "wQwrwezI9LzKevGsJJCBHDG8noR0yIEtOK5Rig97SSo=", + "DpyS+0d7lrlFWkztsniG2v/j44vcuvWz3sPeghRyb5A="}, + {"mN98iuqUKh67ggUdq9ZIQNZCZM90fgycTVqYKEo+DkY=", + "GYdXVW1jpS0dN1q9zMehubP7LfYqs34kszN0bXQqxxA=", + "AJPIHffB4uvvJJki4xCG0VORVBbF6bc2mZQqUx+idPc="}, + {"OEd/1it8C3o+NOWxDI3DfLMXVBHJQg15N3E8F8d99l8=", + "QL1NcuUkoXxDy7M9VjGslCejcUlnUDHRghFVnr+8fmA=", + "nven9Dicl8U6QXuDO8rRNtjd4NYaa90SU+Gmv435XKY="}, + {"AFMCGDu2oAP68miucsi4fmYX2KeRZnsEGv8tQm8JEng=", + "1sNxvk8uZhFsBUgxOXmuCMjDAgBbjVeWe9oaFk5Osy0=", + "t5iI5XXd56S5q0Y9HC91gzgF9uGjL9FIy6NUaKqkydo="}, + {"CIAwfJghQHHr4YlztN9at6/iWkrEVCGFAxNVuQCuT3I=", + "zpUOF1h17g7RpBzrVlN7oTRz6e+dxcDL8OsAtHwgLC8=", + "kOSwC1p6Uoti9E9Eg7ViPZwCytuvp5Fr5Buw677aogU="}, + {"sC8vrAVBU0zvhWDRfzfySjvopXm2/cTMkTLmioyO3Es=", + "p6H7GWm8NfgyO5OCX/COjvVT4MAnTs9ZUj4uZMK8XHA=", + "9Tzqo57V/h7+6nSNAHSBKdmU7ultlvZbAnNKSRlrLi4="}, + {"eI63gjxCZGnzqZxPEi/ifYphXhxIRI2ZxK8jzqo3mmU=", + "XyNzEuU9x37fxFCnrZH89Krs5/UqGVx5wNkGfQCAYz0=", + "vZ2fTlRPnJQ+q33YdS5p1aweqPGj/kTMc4Uq80FtFjI="}, + {"aNJlGtm79/RS4SQ/PC4YM6LFo9zAqDr2/RjLqk/z/1A=", + "lgZ9akPrABmfHQMlfNFnnpAJzGtcsaU9mUjEYKfzZHc=", + "d0Xt1Bcgphd1HMI0RneA4VdBbMZL1qNGJAvFhb080eA="}, + {"oONSnHirNh3cuH93Ty0C9AXKebGY+cdF3R0DtPzIQlo=", + "TuREKfA8EVQiYWsPx8veUzjN2cz/b72limSLWlrCWxw=", + "vEqqKbpZf0EM6EApMUaUH65r3Zr81Y/DSODhE4H7U3Y="}, + {"+D6RyLEaHJ9YF9WDyOlwh87KaNJcc6lqX8Arp6yqHF0=", + "EpecjfIo1/EEbmsgUtzEDqLu2ut+SMmzqaBL9Z/MlCA=", + "oYfO6/7XQgEYT9zmr4sqFrk0muK/fEv3FfD8MzZzjkE="}, + {"OCmW5KQql2PRMJnsMYQjXlr6TSYUbxJBknqZtXJPSHM=", + "ZZR2ghHlCwAJu/XlsEZuNS6XiGPwuXzyMPPywYFapVM=", + "fqSCXq+pKJJ6yNvlOi+tyQ9E4Y6kc4kblGrVqN2WuXA="}, + {"APWXDAe8d2ia1CUbf/IzSPXOUjR8TVuJgmISiWw0/EY=", + "jrT5P5YCkG+U7cfNTvCKy0GSEgsjwmJtHg+8HBP6ZCA=", + "t75aUjZXMPir8Ao0yhVClh9/BdWxSL+11CjK3iELNWk="}, + {"8Nw4sRis7M/6Om+3w5YHXthyMzLGuP48teqdzbHNPlA=", + "lj3q3ZYij3ZJ/QunK8n9I00cv/Z+O1TU1kFFl8x3DTE=", + "adqB79P7gbXEYYnSd1/UPCwFffTAPXa9kHWynRBYcGo="}, + {"yOupps5XbjV0fIZKnGhrpcxB7yDQzbBILC0UMJyVS1c=", + "+MDV/t9UCIdgm3IkH3BZlxaRPJ3lejRmrm4UPApq4mk=", + "AOJPmQxsU6hjOd+9mHnF0VL7Afih3P1Fr625xFT4FtY="}, + {"cD2DEl4MBwONuTV0db5XreoVjQAUZFNXqIeFEU3KFkE=", + "2CeHrjN7tBX48k4Sgv5fIHG06e57q/ucCL+8DIRmfXw=", + "3EUo6MRzs6rSoY/7AFs8wiBiTXPcHzerLh6Xp3aMGKo="}, + {"EA+S3a9ZeOLiRbhTxaT2wkpyDheAmai+UJa6SFGzSm8=", + "GGByUKZx/FPa2OkJoqVTHXx+6jrIpIw5rf0rp43MHko=", + "BXoDA3yn0JcMV7hHVzEqhlwAORvhToFO1qG00nas92A="}, + {"eBeJi/imBiV52WEqrwAprUQggqdQmvTTmWtLq8pDDkM=", + "zCX26ZTOZHLpq5x5aIUL1XhIVoXJLp/zcXwnmFA3jBo=", + "Dm/DCxXWYXEsmQgxAD3KREK2PF0bUSnV5WRAaya8s1I="}, + {"8C7p+EQO+CnWUSjHVu3PpeWpUIbLy48zpftZu021plM=", + "DxpnF/IbKAh6kmWC5Jpj8iw387EDkrvjsjOb9fbTSng=", + "bGrk0OshJB+0oQOK0QGKU8+lotnIDz3oeUnMZGienyM="}, + {"COIez7YcBJiOJCLxxWV5UGLW5/o009YI0aszlD/PiUc=", + "eD9USWV37LFIOxlDSHyOmfFqNJFpORRlzEI+HoF/czI=", + "n+/ra86gUSF2pNZS51nt2JgrzXnQJl+dWswOq/Ahs94="}, + {"iBBSTG9VLC+T9+ahNaQ4umZoig8o7w1DaeOw+cD2BGc=", + "XnAxqDlvGnQ6aakv8ABGHVj07qVQfk4NChZbstTMBxg=", + "7KKSwu/4yWr1UzFmNMGtiaSwdYMhP/HKbrQLlABL4UE="}, + {"eNmwattflehr9+KsVqTuwt1YaAc5ONkaIaTQt9Gkhn8=", + "1NNVvm++YTTGMKyAXfGOCZ4aDDdFFH5Um3vAg4XimDo=", + "bXNrnDTP0pBay9ytZe7xpiKoSi12F7WUXqoIeI++Xvo="}, + {"QJotpmZINx9eptKpkh9j3JlEDcHdWnjEbicdBS7gPXM=", + "3gLYKeoruVZ/AYjym0gciDvRHj45UIWyHhNjWj2Wj28=", + "NwuUkkE5yOWT7wed7bltgAk71miz3cSooiIDAdv6kKw="}, + {"OAYGuxH70OPQvhVIX4BhSCWUyzAI5H2IkYxKgC/AO0M=", + "Rj9iNF/FagkXfdLPqc9LHfaoGR8GlvY3gun2FilE508=", + "hkNXLVMlBRsMEaQKkSzevcEK2sMu0AShGKQJMNqdzWM="}, + {"uBrgZ7wLHrOV/0dNiEqo7FjY9VnJqL5eUDHJWAc9QXg=", + "3Hnln8ZHfSaK4OzESJe5U6NcLaW56wzfZICzvzefnSo=", + "DhXsehe5FAmbUidXT5ZpZIAuu1eF9rkU6cF3FBoKwOE="}, + {"sJknn3CHvx/812EWU3ddLdLLZFBKsc+wx35GXyiRsHY=", + "qqa2dNSt0jWozGyqpokP392H5/DOAUUZmUpyZDaUEEE=", + "1Dyz8CvmF17oKT/wG2fu3vRzPzgQv8/OY9GJYew4FG8="}, + {"yOumS9HN68ZwIm+5hZol3jFQ0DB4SKuW/ld3y8wioGk=", + "6PowsbKj/fKSzXZMAfaSkP3fE+4AThL9xm6ysQzMDxg=", + "vF8cKu9X9FxgCjyVZ5RG7nuue8RelF5Qsb8Efme4M4A="}, + {"mISm5vQfPdK72SsaHh6O1/ARvaWCtm+KZNcpTsyt500=", + "YCORDQDpb1U8vADdstBgXkg0N7QaAc5VoXJ4QFuA/UY=", + "JlrBmfaCgbfEVD9YQq3c03WwwsHWc1nBwp1JkFORC3A="}, + {"cPyu3Qry6qbsiOJKFGRziZ9LJWJ47k3ZSXiGkQXuQm0=", + "/cmBZTbqEp8sababPAxGb3OvDAEE7MlwPOwEFHE+7yM=", + "lp0Hhc/rVtpT5FtLLccChqDl3El48XtP6Wm6JwjI7jo="}, + {"YKsMYU0SINbPwWw4RDCJV6GnzDlSp1ZNwUw2euGWi0A=", + "/KmBReATQbFnLg8YKV0jwhqKeishRoWvlVtMX3550Vk=", + "7fXpPSMo1Fw2sOXtjTtvFU+DbZvS/FWB9wAsywWx6R4="}, + {"WD0YI3y71eIp/GXw9i+7scEiQKSBkGihZWE+s6fGmWo=", + "RdthAL/qPnAZFb3xBgRMiAtGHNjgokzoKX9iO2K5qhc=", + "4dk4HGkT9dBmomGNorDE/hLr/HEFhljtl4zz3M4sG58="}, + {"oD8KWhJYZVhutJRb0kZlZnB22QUXzi2FfPRD0ll65UM=", + "MYTBGHh4Ukj97pKj6qcfWmxGNQzmU3/aBOX2f1tfhG0=", + "VT1gC+a9nRJzYMi/TPvRVnn3IQlaop/jKmmxZePEME0="}, + {"0Ns/1SOiqR2CpHRG03QNzJJd5gxTm1XJmSkFlugjQ3k=", + "KvQAI+ekNOa2xfEvfyc9JGcS+CTUrnnhsKrlyJGJixg=", + "J8LmSX6zElX3S9q4PNvh2NKUtAiQ3oHiYjSJ7yErPlY="}, + {"mD6TeF4ezSPXN/csN1OhoAREFSXllI+zl4DUOInVq2Q=", + "WmLJ9ep2EqFcSftnYFJsmWyUxqL0zzuSzVEv94PcISM=", + "U2+ILy2NDmmfgSW78C8dl8GyHESUc1lXPHPpg5F+gr8="}, + {"SMJoXOYgHz8HSzY+ByeWLcSP5qFwv7YjRe0bcKesRnM=", + "DEsNSOY3TEs9J2YgqroQ4xKq8T8xNJQjvvE4UrTItQw=", + "Bws1Hk2+lO+JQ7ME16EbwAdsBkWsGvti0Gb6LY2Lrms="}, + {"iGhXF0Hg0tqZmpwAMiolxvbvTPClQ7LlBAspSSyFEHE=", + "7Xxzpwl7yRWehHNWTYVtFkdChJdXhtY4Mtw1fA9QcCg=", + "Swjfv0PjuaE8Oq3a17BVno5I+q49dZlPwKK1bPUoKNI="}, + {"MIazjx3qTi6Qz3WzhtCPw3i4Q2uZBHcuMoh++ZGFYUk=", + "oni8pbFqk9Ya+Fx+911Nl1SN0FD/hR1jwb2RH3t/pRk=", + "ZYAFcj67LkbNURYbSnCCWGxAG8QLDGWwbl968mA6ZA4="}, + {"MAadYdiFM2cPuJF19q20Yoo5KJabuR9TUQ9jG5nvA1w=", + "5OcE8XV+UPoBVbgqBQdVF62GZCW9DOQEdxrQsktPPBA=", + "FCZsEFXouy+xtxv8X7VroXtvPG1Z1HFHL724tz1jcUI="}, + {"+GwxMmD2dee5+QmvXNI0NdP+rNWoSXTN42otbp0aZ24=", + "Y0N44baz9ihclCUnv6rRbDqCYu4BxQlBfNnTz3NNe2A=", + "/LqSgkVQNkQ/oBiZSgpM9Rw7BJv0RvRpEQpvlizvHy0="}, + {"8FREpCtncOcT7+W2nW4aYSjmSbADtVSH9rIliQZZUH4=", + "fTNSd0JeREhXmPfjrmrAu6Lu/yHkB9GyxR3SyO4kZ28=", + "262KN/iG/iJEaZeerFm1yVtvhFVGgQFwSvtxTcjZzeg="}, + {"EDhaRQGtscjoSE9wJOnSXoQVtVruIqyzknty+x/vDWo=", + "eMmMgws6ZxDIxZ6QSwGjZO1Mx/r5T+fJjSTKGMBk/BU=", + "0CyaJV6AG9bZ0C4yeZ/RDsOs9BdNqZpUxAsD30WmJO4="}, + {"CJV0UB2YdvVDG1cs4oiJgHAS+f1FocGr/vGCfiovsWQ=", + "9/O9GWZEOXVm7On8lftL27PffRORju8OKl6gZd/74CA=", + "A+kXRVNOwIrA5DUa+3v7dpRC+Gbxm23LTiYmOUAXUyY="}, + {"gCjDsJUwZGA7BjYVoCQsvdIgN9Q4lBHlSyKwUrl751A=", + "HRwS8T9y2qPYk7JVU/8Y+6cS+Bk8XCLCXxwN/ttbQiI=", + "iFotjA6rhUfkDv4S/wspJgEWunEmrlGSGsXcJ0+8laQ="}, + {"6N5pL4gsuK+shHpDxirTnAGdyKXIlYHyfIhtB0njJGA=", + "CVZvW7NaN2XMEEKHodghBA9hLCwee/jrmttiWh/CmEg=", + "OpPEd3Sp8r6KdjNDTN4bVHETlGJ92BCK74FCdEaDe9g="}, + {"UIPPTUdvhlg8qEDv6JRxM4/8F5ORjJz4ud82QZrgeEY=", + "7Nd13z5EpB3ChytvQC1CxvDY7n0H8r2Y7lzLEY8hdEk=", + "b22PvgU0M2QfNC7ZGN+RXNe5fjOzMsY32IcHTwLNIqw="}, + {"oBn53Q5fmxKX02PgI6F47Rb+XoLeFQO07ok2tYhk0lE=", + "e0gtPDKXCZSoNW1uHqBPQXLfiYgyeqPMU2zZJgPXACI=", + "wmjW2wDT2EzFkyaGui7YWNLTRu8Q4eD/GVKM2utZkEs="}, + }; + gs_unref_ptrarray GPtrArray *allowed_ips_keep_alive = NULL; + gs_unref_array GArray *peers = NULL; + NMPlatformLnkWireGuard lnk_wireguard; + int r; + guint i; + + allowed_ips_keep_alive = g_ptr_array_new_with_free_func(g_free); + + peers = g_array_new(FALSE, TRUE, sizeof(NMPWireGuardPeer)); + + lnk_wireguard = (NMPlatformLnkWireGuard){ + .listen_port = 50754, + .fwmark = 0x1102, + }; + _copy_base64(lnk_wireguard.private_key, sizeof(lnk_wireguard.private_key), self_key.pri); + _copy_base64(lnk_wireguard.public_key, sizeof(lnk_wireguard.public_key), self_key.pub); + + if (test_mode == 0) { + /* no peers. */ + } else if (NM_IN_SET(test_mode, 1, 2)) { + guint num_peers = (test_mode == 1) ? 1 : G_N_ELEMENTS(keys); + + for (i = 0; i < num_peers; i++) { + NMPWireGuardPeer peer; + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + NMSockAddrUnion endpoint; + guint i_allowed_ips, n_allowed_ips; + NMPWireGuardAllowedIP *allowed_ips; + + if ((i % 2) == 1) { + endpoint = (NMSockAddrUnion){ + .in = + { + .sin_family = AF_INET, + .sin_addr.s_addr = + nmtst_inet4_from_string(nm_sprintf_buf(s_addr, "192.168.7.%d", i)), + .sin_port = htons(14000 + i), + }, + }; + } else { + endpoint = (NMSockAddrUnion){ + .in6 = + { + .sin6_family = AF_INET6, + .sin6_addr = *nmtst_inet6_from_string( + nm_sprintf_buf(s_addr, "a:b:c:e::1:%d", i)), + .sin6_port = htons(16000 + i), + }, + }; + } + + if (test_mode == 1) + n_allowed_ips = 1; + else + n_allowed_ips = i % 10; + allowed_ips = g_new0(NMPWireGuardAllowedIP, n_allowed_ips); + g_ptr_array_add(allowed_ips_keep_alive, allowed_ips); + for (i_allowed_ips = 0; i_allowed_ips < n_allowed_ips; i_allowed_ips++) { + NMPWireGuardAllowedIP *aip = &allowed_ips[i_allowed_ips]; + + aip->family = (i_allowed_ips % 2) ? AF_INET : AF_INET6; + if (aip->family == AF_INET) { + aip->addr.addr4 = nmtst_inet4_from_string( + nm_sprintf_buf(s_addr, "10.%u.%u.0", i, i_allowed_ips)); + aip->mask = 32 - (i_allowed_ips % 8); + } else { + aip->addr.addr6 = *nmtst_inet6_from_string( + nm_sprintf_buf(s_addr, "a:d:f:%02x:%02x::", i, i_allowed_ips)); + aip->mask = 128 - (i_allowed_ips % 10); + } + } + + peer = (NMPWireGuardPeer){ + .persistent_keepalive_interval = 60 + i, + .endpoint = endpoint, + .allowed_ips = n_allowed_ips > 0 ? allowed_ips : NULL, + .allowed_ips_len = n_allowed_ips, + }; + _copy_base64(peer.public_key, sizeof(peer.public_key), keys[i].pub); + _copy_base64(peer.preshared_key, + sizeof(peer.preshared_key), + (i % 3) ? NULL : keys[i].pre); + + g_array_append_val(peers, peer); + } + } else + g_assert_not_reached(); + + r = nm_platform_link_wireguard_change(platform, + ifindex, + &lnk_wireguard, + (const NMPWireGuardPeer *) peers->data, + NULL, + peers->len, + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS); + g_assert(NMTST_NM_ERR_SUCCESS(r)); +} + +/*****************************************************************************/ + +typedef struct { + NMLinkType link_type; + int test_mode; + gboolean external_command; +} TestAddSoftwareDetectData; + +static void +test_software_detect(gconstpointer user_data) +{ + const TestAddSoftwareDetectData *test_data = user_data; + int ifindex, ifindex_parent; + const NMPlatformLink * plink; + const NMPObject * lnk; + int r; + guint i_step; + const gboolean ext = test_data->external_command; + NMPlatformLnkBridge lnk_bridge = {}; + NMPlatformLnkTun lnk_tun; + NMPlatformLnkGre lnk_gre = {}; + nm_auto_close int tun_fd = -1; + + nmtstp_run_command_check("ip link add %s type dummy", PARENT_NAME); + ifindex_parent = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex; + + switch (test_data->link_type) { + case NM_LINK_TYPE_BRIDGE: + + lnk_bridge.forward_delay = 1560; + lnk_bridge.hello_time = 150; + lnk_bridge.max_age = 2100; + lnk_bridge.ageing_time = 2200; + lnk_bridge.stp_state = TRUE; + lnk_bridge.priority = 22; + lnk_bridge.vlan_protocol = 0x8100; + lnk_bridge.vlan_stats_enabled = + nmtstp_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED) + ? TRUE + : FALSE; + lnk_bridge.group_fwd_mask = 8; + lnk_bridge.group_addr = (NMEtherAddr){{0x01, 0x80, 0xC2, 0x00, 0x00, 0x08}}; + lnk_bridge.mcast_snooping = TRUE; + lnk_bridge.mcast_router = 1; + lnk_bridge.mcast_query_use_ifaddr = TRUE; + lnk_bridge.mcast_querier = TRUE; + lnk_bridge.mcast_hash_max = 1024; + lnk_bridge.mcast_last_member_count = 2; + lnk_bridge.mcast_startup_query_count = 3; + lnk_bridge.mcast_last_member_interval = 5000; + lnk_bridge.mcast_membership_interval = 25000; + lnk_bridge.mcast_querier_interval = 26000; + lnk_bridge.mcast_query_interval = 12000; + lnk_bridge.mcast_query_response_interval = 5200; + lnk_bridge.mcast_startup_query_interval = 3000; + + if (!nmtstp_link_bridge_add(NULL, ext, DEVICE_NAME, &lnk_bridge)) + g_error("Failed adding Bridge interface"); + break; + + case NM_LINK_TYPE_GRE: + { + gboolean gracefully_skip = FALSE; + + lnk_gre.local = nmtst_inet4_from_string("192.168.233.204"); + lnk_gre.remote = nmtst_inet4_from_string("172.168.10.25"); + lnk_gre.parent_ifindex = ifindex_parent; + lnk_gre.ttl = 174; + lnk_gre.tos = 37; + lnk_gre.path_mtu_discovery = TRUE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gre0")) { + /* Seems that the ip_gre module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0; + } + + if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create gre tunnel because of missing ip_gre module (modprobe ip_gre)"); + goto out_delete_parent; + } + g_error("Failed adding GRE tunnel"); + } + break; + } + case NM_LINK_TYPE_GRETAP: + { + gboolean gracefully_skip = FALSE; + + lnk_gre.local = nmtst_inet4_from_string("192.168.1.133"); + lnk_gre.remote = nmtst_inet4_from_string("172.168.101.2"); + lnk_gre.parent_ifindex = ifindex_parent; + lnk_gre.ttl = 39; + lnk_gre.tos = 12; + lnk_gre.path_mtu_discovery = FALSE; + lnk_gre.is_tap = TRUE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gretap0")) { + /* Seems that the ip_gre module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0; + } + + if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) { + if (gracefully_skip) { + g_test_skip("Cannot create gretap tunnel because of missing ip_gre module " + "(modprobe ip_gre)"); + goto out_delete_parent; + } + g_error("Failed adding GRETAP tunnel"); + } + break; + } + case NM_LINK_TYPE_IPIP: + { + NMPlatformLnkIpIp lnk_ipip = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "tunl0")) { + /* Seems that the ipip module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ipip", NULL) != 0; + } + + lnk_ipip.local = nmtst_inet4_from_string("1.2.3.4"); + lnk_ipip.remote = nmtst_inet4_from_string("5.6.7.8"); + lnk_ipip.parent_ifindex = ifindex_parent; + lnk_ipip.tos = 32; + lnk_ipip.path_mtu_discovery = FALSE; + + if (!nmtstp_link_ipip_add(NULL, ext, DEVICE_NAME, &lnk_ipip)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create ipip tunnel because of missing ipip module (modprobe ipip)"); + goto out_delete_parent; + } + g_error("Failed adding IPIP tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6TNL: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6tnl0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_tunnel", NULL) != 0; + } + + switch (test_data->test_mode) { + case 0: + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::15"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::16"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 20; + lnk_ip6tnl.encap_limit = 6; + lnk_ip6tnl.flow_label = 1337; + lnk_ip6tnl.proto = IPPROTO_IPV6; + break; + case 1: + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::17"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::18"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 0; + lnk_ip6tnl.encap_limit = 0; + lnk_ip6tnl.flow_label = 1338; + lnk_ip6tnl.proto = IPPROTO_IPV6; + lnk_ip6tnl.flags = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS; + break; + } + + if (!nmtstp_link_ip6tnl_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6tnl tunnel because of missing ip6_tunnel module " + "(modprobe ip6_tunnel)"); + goto out_delete_parent; + } + g_error("Failed adding IP6TNL tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6GRE: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0; + } + + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::42"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::aaaa"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 21; + lnk_ip6tnl.flow_label = 1338; + lnk_ip6tnl.is_gre = TRUE; + + if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6gre tunnel because of missing ip6_gre module " + "(modprobe ip6_gre)"); + goto out_delete_parent; + } + g_error("Failed adding IP6GRE tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6GRETAP: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0; + } + + lnk_ip6tnl.local = *nmtst_inet6_from_string("fe80::abcd"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fc01::bbbb"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.ttl = 10; + lnk_ip6tnl.tclass = 22; + lnk_ip6tnl.flow_label = 1339; + lnk_ip6tnl.is_gre = TRUE; + lnk_ip6tnl.is_tap = TRUE; + + if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6gretap tunnel because of missing ip6_gre module " + "(modprobe ip6_gre)"); + goto out_delete_parent; + } + g_error("Failed adding IP6GRETAP tunnel"); + } + break; + } + case NM_LINK_TYPE_MACVLAN: + { + NMPlatformLnkMacvlan lnk_macvlan = {}; + const NMPlatformLink *dummy; + char buf[256]; + int i; + + lnk_macvlan.mode = MACVLAN_MODE_BRIDGE; + lnk_macvlan.no_promisc = FALSE; + lnk_macvlan.tap = FALSE; + + /* Since in old kernel versions sysfs files for macvtaps are not + * namespaced, the creation can fail if a macvtap in another namespace + * has the same index. Try to detect this situation and skip already + * used indexes. + * The fix (17af2bce) is included kernel 4.7, dated 24 July, 2016. + */ + for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) { + snprintf(buf, sizeof(buf), "/sys/class/macvtap/tap%d", i); + if (!g_file_test(buf, G_FILE_TEST_IS_SYMLINK)) + break; + + _LOGD("skipping ifindex %d as already used by a macvtap", i); + + dummy = nmtstp_link_dummy_add(NM_PLATFORM_GET, FALSE, "dummy-tmp"); + g_assert_cmpint(dummy->ifindex, ==, i); + nmtstp_link_delete(NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL, TRUE); + } + + if (!nmtstp_link_macvlan_add(NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan)) + g_error("Failed adding MACVLAN interface"); + break; + } + case NM_LINK_TYPE_MACVTAP: + { + NMPlatformLnkMacvlan lnk_macvtap = {}; + + lnk_macvtap.mode = MACVLAN_MODE_PRIVATE; + lnk_macvtap.no_promisc = FALSE; + lnk_macvtap.tap = TRUE; + + if (!nmtstp_link_macvlan_add(NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvtap)) + g_error("Failed adding MACVTAP interface"); + break; + } + case NM_LINK_TYPE_SIT: + { + NMPlatformLnkSit lnk_sit = {}; + gboolean gracefully_skip = FALSE; + + lnk_sit.local = nmtst_inet4_from_string("192.168.200.1"); + lnk_sit.remote = nmtst_inet4_from_string("172.25.100.14"); + lnk_sit.parent_ifindex = ifindex_parent; + lnk_sit.ttl = 0; + lnk_sit.tos = 31; + lnk_sit.path_mtu_discovery = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "sit0")) { + /* Seems that the sit module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "sit", NULL) != 0; + } + + if (!nmtstp_link_sit_add(NULL, ext, DEVICE_NAME, &lnk_sit)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create sit tunnel because of missing sit module (modprobe sit)"); + goto out_delete_parent; + } + g_error("Failed adding SIT tunnel"); + } + break; + } + case NM_LINK_TYPE_VLAN: + nmtstp_run_command_check("ip link add name %s link %s type vlan id 1242", + DEVICE_NAME, + PARENT_NAME); + break; + case NM_LINK_TYPE_VRF: + { + NMPlatformLnkVrf lnk_vrf = {}; + gboolean not_supported; + + lnk_vrf.table = 9876; + + if (!nmtstp_link_vrf_add(NULL, ext, DEVICE_NAME, &lnk_vrf, ¬_supported)) { + if (not_supported) { + g_test_skip("Cannot create VRF interface because of missing kernel support"); + goto out_delete_parent; + } + g_error("Failed adding VRF interface"); + } + break; + } + case NM_LINK_TYPE_VXLAN: + { + NMPlatformLnkVxlan lnk_vxlan = {}; + + switch (test_data->test_mode) { + case 0: + lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + lnk_vxlan.id = 42; + lnk_vxlan.local = nmtst_inet4_from_string("23.1.2.164"); + lnk_vxlan.group = nmtst_inet4_from_string("239.1.2.134"); + lnk_vxlan.dst_port = 4789; + lnk_vxlan.learning = TRUE; + lnk_vxlan.ageing = 1245; + break; + case 1: + lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + lnk_vxlan.id = 11214423; + lnk_vxlan.local6 = *nmtst_inet6_from_string("1:2:3:4:334:23::23"); + lnk_vxlan.group6 = *nmtst_inet6_from_string("ff0e::115"); + lnk_vxlan.ttl = 32; + lnk_vxlan.dst_port = 57412; + lnk_vxlan.src_port_min = 1000; + lnk_vxlan.src_port_max = 1003; + lnk_vxlan.learning = TRUE; + lnk_vxlan.ageing = 3245; + break; + } + + g_assert(nmtstp_link_vxlan_add(NULL, ext, DEVICE_NAME, &lnk_vxlan)); + break; + } + case NM_LINK_TYPE_TUN: + { + gboolean owner_valid = nmtst_get_rand_bool(); + gboolean group_valid = nmtst_get_rand_bool(); + + switch (test_data->test_mode) { + case 0: + lnk_tun = (NMPlatformLnkTun){ + .type = nmtst_get_rand_bool() ? IFF_TUN : IFF_TAP, + .owner = owner_valid ? getuid() : 0, + .owner_valid = owner_valid, + .group = group_valid ? getgid() : 0, + .group_valid = group_valid, + .pi = nmtst_get_rand_bool(), + .vnet_hdr = nmtst_get_rand_bool(), + .multi_queue = nmtst_get_rand_bool(), + + /* if we add the device via iproute2 (external), we can only + * create persistent devices. */ + .persist = (ext == 1) ? TRUE : nmtst_get_rand_bool(), + }; + break; + default: + g_assert_not_reached(); + break; + } + + g_assert(nmtstp_link_tun_add(NULL, + ext, + DEVICE_NAME, + &lnk_tun, + (!lnk_tun.persist || nmtst_get_rand_bool()) ? &tun_fd : NULL)); + break; + } + case NM_LINK_TYPE_WIREGUARD: + { + const NMPlatformLink *link; + + r = nm_platform_link_wireguard_add(NM_PLATFORM_GET, DEVICE_NAME, &link); + if (r == -EOPNOTSUPP) { + g_test_skip("wireguard not supported (modprobe wireguard?)"); + goto out_delete_parent; + } + + g_assert(NMTST_NM_ERR_SUCCESS(r)); + g_assert(NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(link)) == NMP_OBJECT_TYPE_LINK); + break; + } + default: + g_assert_not_reached(); + } + + ifindex = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, DEVICE_NAME, test_data->link_type, 100) + ->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_parent, TRUE); + + for (i_step = 0; i_step < 5; i_step++) { + _LOGD("test-software-detect: step %u", i_step); + if (nmtst_is_debug()) + nmtstp_run_command_check("ip -d link show %s", DEVICE_NAME); + + if (i_step > 0) { + gboolean set_up = (i_step % 2) == 1; + + if (test_data->link_type == NM_LINK_TYPE_VXLAN && set_up) { + /* On RHEL-7, we need to add a tiny sleep here, otherwise, + * upping the vxlan device fails with EADDRINUSE. + * https://bugzilla.redhat.com/show_bug.cgi?id=1277131 */ + g_usleep(1); + } + nmtstp_link_set_updown(NULL, -1, ifindex, set_up); + } + + lnk = nm_platform_link_get_lnk(NM_PLATFORM_GET, ifindex, test_data->link_type, &plink); + g_assert(plink); + g_assert_cmpint(plink->ifindex, ==, ifindex); + + if (!lnk && test_data->link_type == NM_LINK_TYPE_TUN) { + /* this is ok. Kernel apparently does not support tun properties via netlink. We + * fetch them from sysfs below. */ + } else + g_assert(lnk); + + switch (test_data->link_type) { + case NM_LINK_TYPE_BRIDGE: + { + const NMPlatformLnkBridge *plnk = &lnk->lnk_bridge; + + g_assert(plnk == nm_platform_link_get_lnk_bridge(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0); + g_assert_cmpint(plnk->forward_delay, ==, 1560); + g_assert_cmpint(plnk->hello_time, ==, 150); + g_assert_cmpint(plnk->max_age, ==, 2100); + g_assert_cmpint(plnk->ageing_time, ==, 2200); + g_assert_cmpint(plnk->stp_state, ==, TRUE); + g_assert_cmpint(plnk->priority, ==, 22); + g_assert_cmpint(plnk->vlan_protocol, ==, 0x8100); + g_assert_cmpint(plnk->vlan_stats_enabled, ==, lnk_bridge.vlan_stats_enabled); + g_assert_cmpint(plnk->group_fwd_mask, ==, 8); + g_assert_cmpint(plnk->mcast_snooping, ==, TRUE); + g_assert_cmpint(plnk->mcast_router, ==, 1); + g_assert_cmpint(plnk->mcast_query_use_ifaddr, ==, TRUE); + g_assert_cmpint(plnk->mcast_querier, ==, TRUE); + g_assert_cmpint(plnk->mcast_hash_max, ==, 1024); + g_assert_cmpint(plnk->mcast_last_member_count, ==, 2); + g_assert_cmpint(plnk->mcast_startup_query_count, ==, 3); + g_assert_cmpint(plnk->mcast_last_member_interval, ==, 5000); + g_assert_cmpint(plnk->mcast_membership_interval, ==, 25000); + g_assert_cmpint(plnk->mcast_querier_interval, ==, 26000); + g_assert_cmpint(plnk->mcast_query_interval, ==, 12000); + g_assert_cmpint(plnk->mcast_query_response_interval, ==, 5200); + g_assert_cmpint(plnk->mcast_startup_query_interval, ==, 3000); + g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0); + break; + } + case NM_LINK_TYPE_GRE: + { + const NMPlatformLnkGre *plnk = &lnk->lnk_gre; + + g_assert(plnk == nm_platform_link_get_lnk_gre(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_lnk_gre_cmp(plnk, &lnk_gre) == 0); + + break; + } + case NM_LINK_TYPE_GRETAP: + { + const NMPlatformLnkGre *plnk = &lnk->lnk_gre; + + g_assert(plnk == nm_platform_link_get_lnk_gretap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_lnk_gre_cmp(plnk, &lnk_gre) == 0); + break; + } + case NM_LINK_TYPE_IP6TNL: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + switch (test_data->test_mode) { + case 0: + g_assert(plnk == nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::15"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::16"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tclass, ==, 20); + g_assert_cmpint(plnk->encap_limit, ==, 6); + g_assert_cmpint(plnk->flow_label, ==, 1337); + g_assert_cmpint(plnk->proto, ==, IPPROTO_IPV6); + break; + case 1: + g_assert(plnk == nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::17"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::18"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->flow_label, ==, 1338); + g_assert_cmpint(plnk->proto, ==, IPPROTO_IPV6); + g_assert_cmpint(plnk->flags & 0xFFFF, /* ignore kernel internal flags */ + ==, + IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS); + break; + } + break; + } + case NM_LINK_TYPE_IP6GRE: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + g_assert(plnk == nm_platform_link_get_lnk_ip6gre(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::42"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::aaaa"); + g_assert_cmpint(plnk->tclass, ==, 21); + g_assert_cmpint(plnk->flow_label, ==, 1338); + g_assert_cmpint(plnk->is_gre, ==, TRUE); + g_assert_cmpint(plnk->is_tap, ==, FALSE); + break; + } + case NM_LINK_TYPE_IP6GRETAP: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + g_assert(plnk == nm_platform_link_get_lnk_ip6gretap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fe80::abcd"); + nmtst_assert_ip6_address(&plnk->remote, "fc01::bbbb"); + g_assert_cmpint(plnk->ttl, ==, 10); + g_assert_cmpint(plnk->tclass, ==, 22); + g_assert_cmpint(plnk->flow_label, ==, 1339); + g_assert_cmpint(plnk->is_gre, ==, TRUE); + g_assert_cmpint(plnk->is_tap, ==, TRUE); + break; + } + case NM_LINK_TYPE_IPIP: + { + const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip; + + g_assert(plnk == nm_platform_link_get_lnk_ipip(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip4_address(plnk->local, "1.2.3.4"); + nmtst_assert_ip4_address(plnk->remote, "5.6.7.8"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tos, ==, 32); + g_assert_cmpint(plnk->path_mtu_discovery, ==, FALSE); + break; + } + case NM_LINK_TYPE_MACVLAN: + { + const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan; + + g_assert(plnk == nm_platform_link_get_lnk_macvlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->no_promisc, ==, FALSE); + g_assert_cmpint(plnk->mode, ==, MACVLAN_MODE_BRIDGE); + break; + } + case NM_LINK_TYPE_MACVTAP: + { + const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan; + + g_assert(plnk == nm_platform_link_get_lnk_macvtap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->no_promisc, ==, FALSE); + g_assert_cmpint(plnk->mode, ==, MACVLAN_MODE_PRIVATE); + break; + } + case NM_LINK_TYPE_SIT: + { + const NMPlatformLnkSit *plnk = &lnk->lnk_sit; + + g_assert(plnk == nm_platform_link_get_lnk_sit(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip4_address(plnk->local, "192.168.200.1"); + nmtst_assert_ip4_address(plnk->remote, "172.25.100.14"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tos, ==, 31); + g_assert_cmpint(plnk->path_mtu_discovery, ==, FALSE); + break; + } + case NM_LINK_TYPE_TUN: + { + const NMPlatformLnkTun *plnk; + NMPlatformLnkTun lnk_tun2; + + g_assert((lnk ? &lnk->lnk_tun : NULL) + == nm_platform_link_get_lnk_tun(NM_PLATFORM_GET, ifindex, NULL)); + + /* kernel might not expose tun options via netlink. Either way, try + * to read them (either from platform cache, or fallback to sysfs). + * See also: rh#1547213. */ + if (!nm_platform_link_tun_get_properties(NM_PLATFORM_GET, ifindex, &lnk_tun2)) + g_assert_not_reached(); + + plnk = lnk ? &lnk->lnk_tun : &lnk_tun2; + if (lnk) + g_assert(memcmp(plnk, &lnk_tun2, sizeof(NMPlatformLnkTun)) == 0); + + if (i_step == 0) { + /* Before we upped the device for the first time the kernel didn't notify + * us of the owner set after the link creation: + * https://bugzilla.redhat.com/show_bug.cgi?id=1566062 + */ + break; + } + + g_assert(nm_platform_lnk_tun_cmp(plnk, &lnk_tun) == 0); + break; + } + case NM_LINK_TYPE_VLAN: + { + const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan; + + g_assert(plnk == nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->id, ==, 1242); + break; + } + case NM_LINK_TYPE_VRF: + { + const NMPlatformLnkVrf *plnk = &lnk->lnk_vrf; + + g_assert(plnk == nm_platform_link_get_lnk_vrf(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->table, ==, 9876); + break; + } + case NM_LINK_TYPE_VXLAN: + { + const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan; + + g_assert(plnk == nm_platform_link_get_lnk_vxlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, !=, 0); + g_assert_cmpint(plnk->tos, ==, 0); + g_assert_cmpint(plnk->learning, ==, TRUE); + g_assert_cmpint(plnk->limit, ==, 0); + g_assert_cmpint(plnk->proxy, ==, FALSE); + g_assert_cmpint(plnk->rsc, ==, FALSE); + g_assert_cmpint(plnk->l2miss, ==, FALSE); + g_assert_cmpint(plnk->l3miss, ==, FALSE); + + switch (test_data->test_mode) { + case 0: + g_assert_cmpint(plnk->id, ==, 42); + nmtst_assert_ip4_address(plnk->local, "23.1.2.164"); + nmtst_assert_ip4_address(plnk->group, "239.1.2.134"); + nmtst_assert_ip6_address(&plnk->group6, "::"); + nmtst_assert_ip6_address(&plnk->local6, "::"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->ageing, ==, 1245); + g_assert_cmpint(plnk->dst_port, ==, 4789); + if (plnk->src_port_min != 0 || plnk->src_port_max != 0) { + /* on some kernels, omitting the port range results in setting + * following default port range. */ + g_assert_cmpint(plnk->src_port_min, ==, 32768); + g_assert_cmpint(plnk->src_port_max, ==, 61000); + } + break; + case 1: + g_assert_cmpint(plnk->id, ==, 11214423); + nmtst_assert_ip4_address(plnk->local, "0.0.0.0"); + nmtst_assert_ip4_address(plnk->group, "0.0.0.0"); + nmtst_assert_ip6_address(&plnk->group6, "ff0e::115"); + nmtst_assert_ip6_address(&plnk->local6, "1:2:3:4:334:23::23"); + g_assert_cmpint(plnk->ageing, ==, 3245); + g_assert_cmpint(plnk->dst_port, ==, 57412); + g_assert_cmpint(plnk->ttl, ==, 32); + g_assert_cmpint(plnk->src_port_min, ==, 1000); + g_assert_cmpint(plnk->src_port_max, ==, 1003); + break; + } + break; + } + case NM_LINK_TYPE_WIREGUARD: + { + const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard; + + g_assert(plnk == nm_platform_link_get_lnk_wireguard(NM_PLATFORM_GET, ifindex, NULL)); + + if (plink->n_ifi_flags & IFF_UP) { + _test_wireguard_change(NM_PLATFORM_GET, plink->ifindex, test_data->test_mode); + if (_LOGD_ENABLED()) + _system("WG_HIDE_KEYS=never wg show all"); + } + break; + } + default: + g_assert_not_reached(); + } + } + + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); +out_delete_parent: + nmtstp_link_delete(NULL, -1, ifindex_parent, PARENT_NAME, TRUE); +} + +static void +test_software_detect_add(const char *testpath, NMLinkType link_type, int test_mode) +{ + TestAddSoftwareDetectData *test_data; + char * path; + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = TRUE; + + path = g_strdup_printf("%s/external", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = FALSE; + + path = g_strdup_printf("%s/platform", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = -1; + + path = g_strdup_printf("%s/random", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); +} + +/*****************************************************************************/ + +static void +_assert_xgress_qos_mappings_impl(int ifindex, gboolean is_ingress_map, int n_entries, int n, ...) +{ + const NMPlatformLink * plink; + const NMPObject * lnk; + guint n_map; + const NMVlanQosMapping *map; + va_list ap; + guint i; + + lnk = nm_platform_link_get_lnk(NM_PLATFORM_GET, ifindex, NM_LINK_TYPE_VLAN, &plink); + + g_assert(plink); + g_assert_cmpint(plink->ifindex, ==, ifindex); + g_assert(lnk); + g_assert(&lnk->lnk_vlan == nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL)); + + if (nmtst_is_debug()) + nmtstp_run_command_check("ip -d link show %s", plink->name); + + if (is_ingress_map) { + map = lnk->_lnk_vlan.ingress_qos_map; + n_map = lnk->_lnk_vlan.n_ingress_qos_map; + } else { + map = lnk->_lnk_vlan.egress_qos_map; + n_map = lnk->_lnk_vlan.n_egress_qos_map; + } + + if (n_entries != -1) + g_assert_cmpint(n_map, ==, n_entries); + + for (i = 0; i < n_map; i++) { + if (is_ingress_map) { + g_assert_cmpint(map[i].from, >=, 0); + g_assert_cmpint(map[i].from, <=, 7); + } + if (i > 0) + g_assert_cmpint(map[i - 1].from, <, map[i].from); + } + + va_start(ap, n); + for (; n > 0; n--) { + gboolean found = FALSE; + guint from = va_arg(ap, guint); + guint to = va_arg(ap, guint); + + for (i = 0; i < n_map; i++) { + if (map[i].from == from) { + g_assert(!found); + found = TRUE; + + g_assert(map[i].to == to); + } + } + g_assert(found); + } + va_end(ap); +} +#define _assert_xgress_qos_mappings(ifindex, is_ingress_map, n_entries, ...) \ + _assert_xgress_qos_mappings_impl( \ + (ifindex), \ + (is_ingress_map), \ + (n_entries), \ + (G_STATIC_ASSERT_EXPR((NM_NARG(__VA_ARGS__) % 2) == 0), NM_NARG(__VA_ARGS__) / 2), \ + __VA_ARGS__) +#define _assert_ingress_qos_mappings(ifindex, n_entries, ...) \ + _assert_xgress_qos_mappings(ifindex, TRUE, n_entries, __VA_ARGS__) +#define _assert_egress_qos_mappings(ifindex, n_entries, ...) \ + _assert_xgress_qos_mappings(ifindex, FALSE, n_entries, __VA_ARGS__) + +static void +_assert_vlan_flags(int ifindex, NMVlanFlags flags) +{ + const NMPlatformLnkVlan *plnk; + + plnk = nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL); + g_assert(plnk); + g_assert_cmpint(plnk->flags, ==, flags); +} + +static void +test_vlan_set_xgress(void) +{ + int ifindex, ifindex_parent; + + nmtstp_run_command_check("ip link add %s type dummy", PARENT_NAME); + ifindex_parent = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex; + + nmtstp_run_command_check("ip link add name %s link %s type vlan id 1245", + DEVICE_NAME, + PARENT_NAME); + ifindex = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex; + + /* ingress-qos-map */ + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 4, 5)); + _assert_ingress_qos_mappings(ifindex, 1, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 3, 7)); + _assert_ingress_qos_mappings(ifindex, 2, 3, 7, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 3, 8)); + _assert_ingress_qos_mappings(ifindex, 2, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 4)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 4, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32)); + _assert_ingress_qos_mappings(ifindex, 3, 0, G_MAXUINT32, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32 - 1)); + _assert_ingress_qos_mappings(ifindex, 3, 0, G_MAXUINT32 - 1, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 5)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 5)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + /* Set invalid values: */ + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 8, 3)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 9, 4)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + /* egress-qos-map */ + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 7, 3)); + _assert_egress_qos_mappings(ifindex, 1, 7, 3); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 4)); + _assert_egress_qos_mappings(ifindex, 2, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 0, 4)); + _assert_egress_qos_mappings(ifindex, 3, 0, 4, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 4)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 4, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 5)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 5, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 9, 5)); + _assert_egress_qos_mappings(ifindex, 5, 0, 4, 1, 5, 7, 3, 8, 4, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 5)); + _assert_egress_qos_mappings(ifindex, 5, 0, 4, 1, 5, 7, 3, 8, 5, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 0)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 0, 0)); + _assert_egress_qos_mappings(ifindex, 3, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 100, 4)); + _assert_egress_qos_mappings(ifindex, 4, 1, 5, 7, 3, 9, 5, 100, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 4)); + _assert_egress_qos_mappings(ifindex, 5, 1, 5, 7, 3, 9, 5, 100, 4, G_MAXUINT32, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 8)); + _assert_egress_qos_mappings(ifindex, 5, 1, 5, 7, 3, 9, 5, 100, 4, G_MAXUINT32, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 0)); + _assert_egress_qos_mappings(ifindex, 4, 1, 5, 7, 3, 9, 5, 100, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 100, 0)); + _assert_egress_qos_mappings(ifindex, 3, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 0)); + _assert_egress_qos_mappings(ifindex, 2, 7, 3, 9, 5); + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 5}, + {.from = 7, .to = 1655}, + {.from = 7, .to = 17655}, + {.from = 5, .to = 754}, + {.from = 4, .to = 12}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 4, 3, 5, 4, 12, 7, 17655, 5, 754); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 18}, + {.from = 6, .to = 121}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 5, 3, 18, 4, 12, 6, 121, 7, 17655, 5, 754); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 0}, + {.from = 6, .to = 7}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 6, 7); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 5, .to = 1}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 5, 1); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 5, .to = 3}, + {.from = 1655, .to = 5}, + {.from = 1655, .to = 7}, + {.from = G_MAXUINT32, .to = 6}, + {.from = G_MAXUINT32, .to = 8}, + {.from = 754, .to = 4}, + {.from = 3, .to = 2}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 5, 3, 2, 5, 3, 754, 4, 1655, 7, G_MAXUINT32, 6); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 754, .to = 3}, + {.from = 755, .to = 8}, + {.from = 1655, .to = 0}, + {.from = 6, .to = 1}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + FALSE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 5, 3, 2, 5, 3, 6, 1, 754, 3, G_MAXUINT32, 6); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 6, .to = 0}, + {.from = 3, .to = 4}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 3, 4); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 6, .to = 145}, + {.from = 4, .to = 1}, + {.from = 6, .to = 12}, + }; + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 5}, + {.from = 3232, .to = 7}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP, + NM_VLAN_FLAG_REORDER_HEADERS, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12); + _assert_egress_qos_mappings(ifindex, 2, 1, 5, 3232, 7); + _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 6, .to = 145}, + {.from = 4, .to = 1}, + {.from = 6, .to = 12}, + }; + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 7}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 3232, .to = 0}, + {.from = 64, .to = 4}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + NM_VLAN_FLAG_GVRP, + NM_VLAN_FLAG_GVRP, + FALSE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12); + _assert_egress_qos_mappings(ifindex, 2, 1, 7, 64, 4); + _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP); + } + + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + nmtstp_link_delete(NULL, -1, ifindex_parent, PARENT_NAME, TRUE); +} + +/*****************************************************************************/ + +static void +test_create_many_links_do(guint n_devices) +{ + gint64 time, start_time = nm_utils_get_monotonic_timestamp_nsec(); + guint i; + char name[64]; + const NMPlatformLink *pllink; + gs_unref_array GArray *ifindexes = g_array_sized_new(FALSE, FALSE, sizeof(int), n_devices); + const int EX = ((int) (nmtst_get_rand_uint32() % 4)) - 1; + + g_assert(EX >= -1 && EX <= 2); + + _LOGI(">>> create devices (EX=%d)...", EX); + + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + if (EX == 2) { + /* This mode is different from letting nmtstp_link_dummy_add() + * because in this case we don't process any platform events + * while adding all the links. */ + nmtstp_run_command_check("ip link add %s type dummy", name); + } else + nmtstp_link_dummy_add(NULL, EX, name); + } + + _LOGI(">>> process events after creating devices..."); + + nm_platform_process_events(NM_PLATFORM_GET); + + _LOGI(">>> check devices..."); + + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + + pllink = nm_platform_link_get_by_ifname(NM_PLATFORM_GET, name); + g_assert(pllink); + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(pllink->name, ==, name); + + g_array_append_val(ifindexes, pllink->ifindex); + } + + _LOGI(">>> delete devices..."); + + g_assert_cmpint(ifindexes->len, ==, n_devices); + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + + if (EX == 2) + nmtstp_run_command_check("ip link delete %s", name); + else + nmtstp_link_delete(NULL, EX, g_array_index(ifindexes, int, i), name, TRUE); + } + + _LOGI(">>> process events after deleting devices..."); + nm_platform_process_events(NM_PLATFORM_GET); + + time = nm_utils_get_monotonic_timestamp_nsec() - start_time; + _LOGI(">>> finished in %ld.%09ld seconds", + (long) (time / NM_UTILS_NSEC_PER_SEC), + (long) (time % NM_UTILS_NSEC_PER_SEC)); +} + +static void +test_create_many_links(gconstpointer user_data) +{ + guint n_devices = GPOINTER_TO_UINT(user_data); + + if (n_devices > 100 && nmtst_test_quick()) { + g_print("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-link-linux"); + g_test_skip("Skip long running test"); + return; + } + + test_create_many_links_do(n_devices); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_veth(void) +{ + const char * IFACE_VETH0 = "nm-test-veth0"; + const char * IFACE_VETH1 = "nm-test-veth1"; + int ifindex_veth0, ifindex_veth1; + int i; + const NMPlatformLink *pllink_veth0, *pllink_veth1; + gs_free_error GError * error = NULL; + NMTstpNamespaceHandle *ns_handle = NULL; + + /* create veth pair. */ + ifindex_veth0 = nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex; + ifindex_veth1 = + nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex; + + /* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */ + g_assert(nm_platform_link_veth_get_properties(NM_PLATFORM_GET, ifindex_veth0, &i)); + g_assert_cmpint(i, ==, ifindex_veth1); + + g_assert(nm_platform_link_veth_get_properties(NM_PLATFORM_GET, ifindex_veth1, &i)); + g_assert_cmpint(i, ==, ifindex_veth0); + + /* assert that NMPlatformLink.parent is the peer-ifindex. */ + pllink_veth0 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth0); + g_assert(pllink_veth0); + if (pllink_veth0->parent == 0) { + /* Kernels prior to 4.1 dated 21 June, 2015 don't support exposing the veth peer + * as IFA_LINK. skip the remainder of the test. */ + goto out; + } + g_assert_cmpint(pllink_veth0->parent, ==, ifindex_veth1); + + /* The following tests whether we have a workaround for kernel bug + * https://bugzilla.redhat.com/show_bug.cgi?id=1285827 in place. */ + pllink_veth1 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth1); + g_assert(pllink_veth1); + g_assert_cmpint(pllink_veth1->parent, ==, ifindex_veth0); + + /* move one veth peer to another namespace and check that the + * parent/IFLA_LINK of the remaining peer properly updates + * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */ + ns_handle = nmtstp_namespace_create(CLONE_NEWNET, &error); + g_assert_no_error(error); + g_assert(ns_handle); + + nmtstp_run_command_check("ip link set %s netns %ld", + IFACE_VETH1, + (long) nmtstp_namespace_handle_get_pid(ns_handle)); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + nm_platform_process_events(NM_PLATFORM_GET); + + pllink_veth1 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth1); + pllink_veth0 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth0); + if (!pllink_veth1 && pllink_veth0 && pllink_veth0->parent == NM_PLATFORM_LINK_OTHER_NETNS) { + break; + } + }); + +out: + nmtstp_link_delete(NULL, -1, ifindex_veth0, IFACE_VETH0, TRUE); + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_veth0, IFACE_VETH0)); + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_veth1, IFACE_VETH1)); + nmtstp_namespace_handle_release(ns_handle); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_spuroius_newlink(void) +{ + const char * IFACE_BOND0 = "nm-test-bond0"; + const char * IFACE_DUMMY0 = "nm-test-dummy0"; + int ifindex_bond0, ifindex_dummy0; + const NMPlatformLink *pllink; + gboolean wait_for_settle; + + /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */ + + nmtstp_run_command_check("ip link add %s type dummy", IFACE_DUMMY0); + ifindex_dummy0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100) + ->ifindex; + + nmtstp_run_command_check("ip link add %s type bond", IFACE_BOND0); + ifindex_bond0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_bond0, TRUE); + + nmtstp_run_command_check("ip link set %s master %s", IFACE_DUMMY0, IFACE_BOND0); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + if (pllink->master == ifindex_bond0) + break; + }); + + nmtstp_run_command_check("ip link del %s", IFACE_BOND0); + + wait_for_settle = TRUE; + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +again: + nm_platform_process_events(NM_PLATFORM_GET); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bond0); + g_assert(!pllink); + + if (wait_for_settle) { + wait_for_settle = FALSE; + NMTST_WAIT(300, { nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); }); + goto again; + } + + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_bond0, IFACE_BOND0)); + nmtstp_link_delete(NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_spuroius_dellink(void) +{ + const char * IFACE_BRIDGE0 = "nm-test-bridge0"; + const char * IFACE_DUMMY0 = "nm-test-dummy0"; + int ifindex_bridge0, ifindex_dummy0; + const NMPlatformLink *pllink; + gboolean wait_for_settle; + + /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */ + + nmtstp_run_command_check("ip link add %s type dummy", IFACE_DUMMY0); + ifindex_dummy0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100) + ->ifindex; + + nmtstp_run_command_check("ip link add %s type bridge", IFACE_BRIDGE0); + ifindex_bridge0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100) + ->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_bridge0, TRUE); + + nmtstp_run_command_check("ip link set %s master %s", IFACE_DUMMY0, IFACE_BRIDGE0); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + if (pllink->master == ifindex_bridge0) + break; + }); + + nm_platform_process_events(NM_PLATFORM_GET); + + nmtstp_run_command_check("ip link set %s nomaster", IFACE_DUMMY0); + + wait_for_settle = TRUE; + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +again: + nm_platform_process_events(NM_PLATFORM_GET); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bridge0); + g_assert(pllink); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + g_assert_cmpint(pllink->parent, ==, 0); + + if (wait_for_settle) { + wait_for_settle = FALSE; + NMTST_WAIT(300, { nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); }); + goto again; + } + + nmtstp_link_delete(NULL, -1, ifindex_bridge0, IFACE_BRIDGE0, TRUE); + nmtstp_link_delete(NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE); +} + +/*****************************************************************************/ + +static void +_test_netns_setup(gpointer fixture, gconstpointer test_data) +{ + /* the singleton platform instance has netns support disabled. + * Destroy the instance before the test and re-create it afterwards. */ + g_object_unref(NM_PLATFORM_GET); +} + +static void +_test_netns_teardown(gpointer fixture, gconstpointer test_data) +{ + nmtstp_setup_platform(); +} + +static NMPlatform * +_test_netns_create_platform(void) +{ + NMPNetns * netns; + NMPlatform *platform; + + netns = nmp_netns_new(); + g_assert(NMP_IS_NETNS(netns)); + + platform = nm_linux_platform_new(TRUE, TRUE); + g_assert(NM_IS_LINUX_PLATFORM(platform)); + + nmp_netns_pop(netns); + g_object_unref(netns); + + return platform; +} + +static gboolean +_test_netns_check_skip(void) +{ + static int support = -1; + static int support_errsv = 0; + NMPNetns * netns; + gs_unref_object NMPNetns *netns2 = NULL; + + netns = nmp_netns_get_current(); + if (!netns) { + g_test_skip("No netns support"); + return TRUE; + } + + g_assert(nmp_netns_get_fd_net(netns) > 0); + + if (support == -1) { + support = (setns(nmp_netns_get_fd_net(netns), CLONE_NEWNET) == 0); + if (!support) + support_errsv = errno; + } + if (!support) { + _LOGD("setns() failed with \"%s\". This indicates missing support (valgrind?)", + nm_strerror_native(support_errsv)); + g_test_skip("No netns support (setns failed)"); + return TRUE; + } + + netns2 = nmp_netns_new(); + if (!netns2) { + /* skip tests for https://bugzilla.gnome.org/show_bug.cgi?id=790214 */ + g_assert_cmpint(errno, ==, EINVAL); + g_test_skip("No netns support to create another netns"); + return TRUE; + } + nmp_netns_pop(netns2); + + return FALSE; +} + +static gboolean +_check_sysctl_skip(void) +{ + if (access("/proc/sys/net/ipv4/ip_forward", W_OK) == -1) { + g_test_skip("Can not write sysctls"); + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +#define _sysctl_assert_eq(plat, path, value) \ + G_STMT_START \ + { \ + gs_free char *_val = NULL; \ + \ + _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \ + g_assert_cmpstr(_val, ==, value); \ + } \ + G_STMT_END + +static void +test_netns_general(gpointer fixture, gconstpointer test_data) +{ + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + NMPNetns * netns_tmp; + char sbuf[100]; + int i, j, k; + gboolean ethtool_support; + NMPUtilsEthtoolDriverInfo driver_info; + + if (_test_netns_check_skip()) + return; + + if (_check_sysctl_skip()) + return; + + platform_1 = nm_linux_platform_new(TRUE, TRUE); + platform_2 = _test_netns_create_platform(); + + /* add some dummy devices. The "other-*" devices are there to bump the ifindex */ + for (k = 0; k < 2; k++) { + NMPlatform *p = (k == 0 ? platform_1 : platform_2); + const char *id = (k == 0 ? "a" : "b"); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-a-%s-%02d", id, i)); + + _ADD_DUMMY(p, "dummy1_"); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-b-%s-%02d", id, i)); + + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "dummy2%s", id)); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-c-%s-%02d", id, i)); + } + + _sysctl_assert_eq( + platform_1, + "/sys/devices/virtual/net/dummy1_/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq( + platform_1, + "/sys/devices/virtual/net/dummy2a/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq(platform_1, "/sys/devices/virtual/net/dummy2b/ifindex", NULL); + + _sysctl_assert_eq( + platform_2, + "/sys/devices/virtual/net/dummy1_/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq(platform_2, "/sys/devices/virtual/net/dummy2a/ifindex", NULL); + _sysctl_assert_eq( + platform_2, + "/sys/devices/virtual/net/dummy2b/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex)); + + for (i = 0; i < 10; i++) { + NMPlatform *pl; + const char *path; + + j = nmtst_get_rand_uint32() % 2; + + if (nmtst_get_rand_uint32() % 2) { + pl = platform_1; + if (nmtst_get_rand_uint32() % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"; + } else { + pl = platform_2; + if (nmtst_get_rand_uint32() % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"; + } + g_assert(nm_platform_sysctl_set(pl, + NMP_SYSCTL_PATHID_ABSOLUTE(path), + nm_sprintf_buf(sbuf, "%d", j))); + _sysctl_assert_eq(pl, path, nm_sprintf_buf(sbuf, "%d", j)); + } + + _sysctl_assert_eq(platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6", NULL); + _sysctl_assert_eq(platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6", NULL); + + /* Kernels prior to 3.19 dated 8 February, 2015 don't support ethtool -i for dummy devices. + * Work around that and skip asserts that are known to fail. */ + ethtool_support = nmtstp_run_command("ethtool -i dummy1_ > /dev/null") == 0; + if (ethtool_support) { + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2a > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2b 2> /dev/null"), !=, 0); + } + + g_assert(nm_platform_netns_push(platform_2, &netns_tmp)); + + if (ethtool_support) { + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2a 2> /dev/null"), !=, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2b > /dev/null"), ==, 0); + } + + nmp_netns_pop(netns_tmp); +} + +/*****************************************************************************/ + +static void +test_netns_set_netns(gpointer fixture, gconstpointer test_data) +{ + NMPlatform * platforms[3]; + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop); + +#define LINK_MOVE_NAME "link-move" + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + _ADD_DUMMY(platform_1, LINK_MOVE_NAME); + g_assert(nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + g_assert(nm_platform_link_set_netns( + platform_1, + nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)->ifindex, + nmp_netns_get_fd_net(nm_platform_netns_get(platform_2)))); + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + nmtstp_assert_wait_for_link(platform_2, LINK_MOVE_NAME, NM_LINK_TYPE_DUMMY, 100); + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); +} + +/*****************************************************************************/ + +static char * +_get_current_namespace_id(int ns_type) +{ + const char *p; + GError * error = NULL; + char * id; + + switch (ns_type) { + case CLONE_NEWNET: + p = "/proc/self/ns/net"; + break; + case CLONE_NEWNS: + p = "/proc/self/ns/mnt"; + break; + default: + g_assert_not_reached(); + } + + id = g_file_read_link(p, &error); + g_assert_no_error(error); + g_assert(id); + return id; +} + +static char * +_get_sysctl_value(const char *path) +{ + char * data = NULL; + gs_free_error GError *error = NULL; + + if (!g_file_get_contents(path, &data, NULL, &error)) { + nmtst_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT, NULL); + g_assert(!data); + } else { + g_assert_no_error(error); + g_assert(data); + g_strstrip(data); + } + return data; +} + +static void +test_netns_push(gpointer fixture, gconstpointer test_data) +{ + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + gs_unref_ptrarray GPtrArray *device_names = g_ptr_array_new_with_free_func(g_free); + int i, j; + const int ns_types_list[] = {CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWNET | CLONE_NEWNS}; + const int ns_types_test[] = {CLONE_NEWNET, CLONE_NEWNS}; + typedef struct { + NMPlatform *platform; + const char *device_name; + const char *sysctl_path; + const char *sysctl_value; + const char *ns_net; + const char *ns_mnt; + } PlatformData; + PlatformData pl[3] = {}; + PlatformData *pl_base; + struct { + PlatformData *pl; + int ns_types; + } stack[6] = {}; + int nstack; + + if (_test_netns_check_skip()) + return; + + if (_check_sysctl_skip()) + return; + + pl[0].platform = platform_0 = nm_linux_platform_new(TRUE, TRUE); + pl[1].platform = platform_1 = _test_netns_create_platform(); + pl[2].platform = platform_2 = _test_netns_create_platform(); + + pl_base = &pl[0]; + i = nmtst_get_rand_uint32() % (G_N_ELEMENTS(pl) + 1); + if (i < G_N_ELEMENTS(pl)) { + pl_base = &pl[i]; + g_assert(nm_platform_netns_push(pl[i].platform, &netns_pop)); + } + + for (i = 0; i < G_N_ELEMENTS(pl); i++) { + nm_auto_pop_netns NMPNetns *netns_free = NULL; + char * tmp; + + g_assert(nm_platform_netns_push(pl[i].platform, &netns_free)); + + tmp = g_strdup_printf("nmtst-dev-%d", i); + g_ptr_array_add(device_names, tmp); + pl[i].device_name = tmp; + + tmp = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", pl[i].device_name); + g_ptr_array_add(device_names, tmp); + pl[i].sysctl_path = tmp; + + pl[i].sysctl_value = nmtst_get_rand_uint32() % 2 ? "1" : "0"; + + _ADD_DUMMY(pl[i].platform, pl[i].device_name); + + g_assert(nm_platform_sysctl_set(pl[i].platform, + NMP_SYSCTL_PATHID_ABSOLUTE(pl[i].sysctl_path), + pl[i].sysctl_value)); + + tmp = _get_current_namespace_id(CLONE_NEWNET); + g_ptr_array_add(device_names, tmp); + pl[i].ns_net = tmp; + + tmp = _get_current_namespace_id(CLONE_NEWNS); + g_ptr_array_add(device_names, tmp); + pl[i].ns_mnt = tmp; + } + + nstack = nmtst_get_rand_uint32() % (G_N_ELEMENTS(stack) + 1); + for (i = 0; i < nstack; i++) { + stack[i].pl = &pl[nmtst_get_rand_uint32() % G_N_ELEMENTS(pl)]; + stack[i].ns_types = ns_types_list[nmtst_get_rand_uint32() % G_N_ELEMENTS(ns_types_list)]; + + nmp_netns_push_type(nm_platform_netns_get(stack[i].pl->platform), stack[i].ns_types); + } + + /* pop some again. */ + for (i = nmtst_get_rand_uint32() % (nstack + 1); i > 0; i--) { + g_assert(nstack > 0); + nstack--; + nmp_netns_pop(nm_platform_netns_get(stack[nstack].pl->platform)); + } + + for (i = 0; i < G_N_ELEMENTS(ns_types_test); i++) { + int ns_type = ns_types_test[i]; + PlatformData *p; + gs_free char *current_namespace_id = NULL; + + p = pl_base; + for (j = nstack; j >= 1;) { + j--; + if (NM_FLAGS_ANY(stack[j].ns_types, ns_type)) { + p = stack[j].pl; + break; + } + } + + current_namespace_id = _get_current_namespace_id(ns_type); + + if (ns_type == CLONE_NEWNET) { + g_assert_cmpstr(current_namespace_id, ==, p->ns_net); + for (j = 0; j < G_N_ELEMENTS(pl); j++) { + gs_free char *data = NULL; + + if (p == &pl[j]) + g_assert_cmpint( + nmtstp_run_command("ip link show %s 1>/dev/null", pl[j].device_name), + ==, + 0); + else + g_assert_cmpint( + nmtstp_run_command("ip link show %s 2>/dev/null", pl[j].device_name), + !=, + 0); + + data = _get_sysctl_value(pl[j].sysctl_path); + if (p == &pl[j]) + g_assert_cmpstr(data, ==, pl[j].sysctl_value); + else + g_assert(!data); + } + } else if (ns_type == CLONE_NEWNS) { + g_assert_cmpstr(current_namespace_id, ==, p->ns_mnt); + for (j = 0; j < G_N_ELEMENTS(pl); j++) { + char path[600]; + gs_free char *data = NULL; + + nm_sprintf_buf(path, "/sys/devices/virtual/net/%s/ifindex", pl[j].device_name); + + data = _get_sysctl_value(path); + if (p == &pl[j]) + g_assert_cmpstr(data, + ==, + nm_sprintf_buf(path, + "%d", + nmtstp_link_get_typed(p->platform, + 0, + p->device_name, + NM_LINK_TYPE_DUMMY) + ->ifindex)); + else + g_assert(!data); + } + } else + g_assert_not_reached(); + } + + for (i = nstack; i >= 1;) { + i--; + nmp_netns_pop(nm_platform_netns_get(stack[i].pl->platform)); + } +} + +/*****************************************************************************/ + +static void +test_netns_bind_to_path(gpointer fixture, gconstpointer test_data) +{ +#define P_VAR_RUN "/run" +#define P_VAR_RUN_NETNS "/run/netns" +#define P_VAR_RUN_NETNS_BINDNAME "/run/netns/" P_NETNS_BINDNAME +#define P_NETNS_BINDNAME "nmtst-iproute2-netns" + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + NMPlatform * platforms[3]; + NMPNetns * netns; + int i; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop); + + g_assert_cmpint( + mount("tmpfs", P_VAR_RUN, "tmpfs", MS_NOATIME | MS_NODEV | MS_NOSUID, "mode=0755,size=32K"), + ==, + 0); + g_assert_cmpint(mkdir(P_VAR_RUN_NETNS, 755), ==, 0); + + i = (nmtst_get_rand_uint32() % 2) + 1; + netns = nm_platform_netns_get(platforms[i]); + + _ADD_DUMMY(platforms[i], "dummy2b"); + + g_assert(!g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true 2>/dev/null"), + !=, + 0); + + g_assert(nmp_netns_bind_to_path(netns, P_VAR_RUN_NETNS_BINDNAME, NULL)); + + g_assert(g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true"), ==, 0); + g_assert_cmpint( + nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " ip link show dummy2b 1>/dev/null"), + ==, + 0); + + g_assert(nmp_netns_bind_to_path_destroy(netns, P_VAR_RUN_NETNS_BINDNAME)); + + g_assert(!g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true 2>/dev/null"), + !=, + 0); + + g_assert_cmpint(umount(P_VAR_RUN), ==, 0); +} + +/*****************************************************************************/ + +static void +test_sysctl_rename(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME[3] = { + "nm-dummy-0", + "nm-dummy-1", + "nm-dummy-2", + }; + int ifindex[G_N_ELEMENTS(IFNAME)] = {0}; + nm_auto_close int dirfd = -1; + int i; + char ifname_buf[IFNAMSIZ]; + char * s; + const NMPlatformLink *pllink; + + ifindex[0] = nmtstp_link_dummy_add(PL, -1, IFNAME[0])->ifindex; + ifindex[1] = nmtstp_link_dummy_add(PL, -1, IFNAME[1])->ifindex; + + s = (nmtst_get_rand_uint32() % 2) ? NULL : ifname_buf; + + if (nmtst_get_rand_uint32() % 2) { + /* bring the platform cache out of sync */ + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + nm_platform_process_events(PL); + nmtstp_run_command_check("ip link set %s name %s", IFNAME[2], IFNAME[0]); + + pllink = nm_platform_link_get_by_ifname(PL, IFNAME[2]); + g_assert(pllink && pllink->ifindex == ifindex[0]); + pllink = nm_platform_link_get_by_ifname(PL, IFNAME[0]); + g_assert(!pllink); + } + + /* open dirfd for IFNAME[0] */ + i = nmtst_get_rand_uint32() % (2 + G_N_ELEMENTS(IFNAME)); + if (i == 0) { + dirfd = nm_platform_sysctl_open_netdir(PL, ifindex[0], s); + } else { + const char *ifname_guess; + + /* provide a wrong or no guess. */ + ifname_guess = i == 1 ? NULL : IFNAME[i - 2]; + dirfd = nmp_utils_sysctl_open_netdir(ifindex[0], ifname_guess, s); + } + g_assert(dirfd >= 0); + if (s) + g_assert_cmpstr(s, ==, IFNAME[0]); + + /* possibly rename the interfaces. */ + switch (nmtst_get_rand_uint32() % 4) { + case 0: + break; + case 1: + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + break; + case 2: + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + nmtstp_run_command_check("ip link set %s name %s", IFNAME[1], IFNAME[0]); + break; + } + + /* possibly, resync platform cache (should make no difference). */ + if (nmtst_get_rand_uint32() % 2) + nm_platform_process_events(PL); + + /* check that we still read the same file. */ + switch (nmtst_get_rand_uint32() % 2) { + case 0: + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(dirfd, + "ifindex", + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + g_assert_not_reached(); + g_assert_cmpint(ifindex[0], ==, (int) _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -1)); + break; + } + case 1: + { + g_assert_cmpint(ifindex[0], + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "", "ifindex"), + -1)); + break; + } + } + + nm_platform_process_events(PL); + nmtstp_link_delete(PL, -1, ifindex[0], NULL, TRUE); + nmtstp_link_delete(PL, -1, ifindex[1], NULL, TRUE); +} + +/*****************************************************************************/ + +static void +test_sysctl_netns_switch(void) +{ + const char *const IFNAME = "nm-dummy-0"; + int ifindex, ifindex_tmp; + nm_auto_close int dirfd = -1; + char ifname_buf[IFNAMSIZ]; + char * s; + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_1 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_3 = NULL; + NMPlatform * PL; + NMPlatform * platforms[3]; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + PL = platforms[nmtst_get_rand_uint32() % 3]; + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_1); + + ifindex = nmtstp_link_dummy_add(PL, FALSE, IFNAME)->ifindex; + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_2); + + s = (nmtst_get_rand_uint32() % 2) ? NULL : ifname_buf; + dirfd = nm_platform_sysctl_open_netdir(PL, ifindex, s); + g_assert(dirfd >= 0); + if (s) + g_assert_cmpstr(s, ==, IFNAME); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_3); + + /* even if we switch to other namespaces, we can still lookup the path correctly, + * either using dirfd or via the platform instance (which switches namespace as needed). */ + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(dirfd, + "ifindex", + 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + g_assert_not_reached(); + g_assert_cmpint(ifindex, ==, (int) _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -1)); + } + g_assert_cmpint(ifindex, + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "", "ifindex"), + -1)); + g_assert_cmpint( + ifindex, + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE(nm_sprintf_bufa(100, "/sys/class/net/%s/ifindex", IFNAME)), + -1)); + + /* also test that nm_platform_sysctl_get() sets errno to ENOENT for non-existing paths. */ + { + gint64 i64; + int errsv; + char * v; + + errno = ESRCH; + v = nm_platform_sysctl_get( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/not-existing/ifindex")); + errsv = errno; + g_assert(!v); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + i64 = nm_platform_sysctl_get_int_checked( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/not-existing/ifindex"), + 10, + 1, + G_MAXINT, + -1); + errsv = errno; + g_assert_cmpint(i64, ==, -1); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + v = nm_platform_sysctl_get( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/lo/not-existing")); + errsv = errno; + g_assert(!v); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + i64 = nm_platform_sysctl_get_int_checked( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/lo/not-existing"), + 10, + 1, + G_MAXINT, + -1); + errsv = errno; + g_assert_cmpint(i64, ==, -1); + g_assert_cmpint(errsv, ==, ENOENT); + } + + /* accessing the path directly, only succeeds iff the current namespace happens to be the namespace + * in which we created the link. */ + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(-1, + nm_sprintf_bufa(100, "/sys/class/net/%s/ifindex", IFNAME), + 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + ifindex_tmp = -1; + else + ifindex_tmp = _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -2); + } + if (nmp_netns_get_current() == nm_platform_netns_get(PL)) + g_assert_cmpint(ifindex_tmp, ==, ifindex); + else + g_assert_cmpint(ifindex_tmp, ==, -1); + + nmtstp_link_delete(PL, FALSE, ifindex, NULL, TRUE); +} + +typedef struct { + GMainLoop * loop; + const char *path; + gboolean expected_success; + gint32 expected_value; +} SetAsyncData; + +static void +sysctl_set_async_cb(GError *error, gpointer user_data) +{ + SetAsyncData *data = user_data; + + if (data->expected_success) { + g_assert_no_error(error); + g_assert_cmpint(nm_platform_sysctl_get_int32(NM_PLATFORM_GET, + NMP_SYSCTL_PATHID_ABSOLUTE(data->path), + -1), + ==, + data->expected_value); + } else + g_assert(error); + + g_main_loop_quit(data->loop); +} + +static void +test_sysctl_set_async(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME = "nm-dummy-0"; + const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter"; + GMainLoop * loop; + gs_unref_object GCancellable *cancellable = NULL; + gboolean proc_writable; + SetAsyncData data; + int ifindex; + + ifindex = nmtstp_link_dummy_add(PL, -1, IFNAME)->ifindex; + loop = g_main_loop_new(NULL, FALSE); + cancellable = g_cancellable_new(); + proc_writable = access(PATH, W_OK) == 0; + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 2, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 1000)) + g_assert_not_reached(); + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 1, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", "0", "1", "0", "1", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 2000)) + g_assert_not_reached(); + + nmtstp_link_delete(NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref(loop); +} + +static void +test_sysctl_set_async_fail(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME = "nm-dummy-0"; + const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist"; + GMainLoop * loop; + gs_unref_object GCancellable *cancellable = NULL; + SetAsyncData data; + int ifindex; + + ifindex = nmtstp_link_dummy_add(PL, -1, IFNAME)->ifindex; + loop = g_main_loop_new(NULL, FALSE); + cancellable = g_cancellable_new(); + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = FALSE, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 1000)) + g_assert_not_reached(); + + nmtstp_link_delete(NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static gpointer +_test_netns_mt_thread(gpointer data) +{ + NMPNetns * netns1 = data; + gs_unref_object NMPNetns *netns2 = NULL; + NMPNetns * netns_bottom; + NMPNetns * initial; + + netns_bottom = nmp_netns_get_initial(); + g_assert(netns_bottom); + + /* I don't know why, but we need to create a new netns here at least once. + * Otherwise, setns(, CLONE_NEWNS) below fails with EINVAL (???). + * + * Something is not right here, but what? */ + netns2 = nmp_netns_new(); + nmp_netns_pop(netns2); + g_clear_object(&netns2); + + nmp_netns_push(netns1); + nmp_netns_push_type(netns_bottom, CLONE_NEWNET); + nmp_netns_push_type(netns_bottom, CLONE_NEWNS); + nmp_netns_push_type(netns1, CLONE_NEWNS); + nmp_netns_pop(netns1); + nmp_netns_pop(netns_bottom); + nmp_netns_pop(netns_bottom); + nmp_netns_pop(netns1); + + initial = nmp_netns_get_initial(); + g_assert(NMP_IS_NETNS(initial)); + return g_object_ref(initial); +} + +static void +test_netns_mt(void) +{ + gs_unref_object NMPNetns *netns1 = NULL; + NMPNetns * initial_from_other_thread; + GThread * th; + + if (_test_netns_check_skip()) + return; + + netns1 = nmp_netns_new(); + g_assert(NMP_NETNS(netns1)); + nmp_netns_pop(netns1); + + th = g_thread_new("nm-test-netns-mt", _test_netns_mt_thread, netns1); + initial_from_other_thread = g_thread_join(th); + g_assert(NMP_IS_NETNS(initial_from_other_thread)); + + if (nmtst_get_rand_bool()) { + nmp_netns_push(initial_from_other_thread); + nmp_netns_pop(initial_from_other_thread); + } + + g_object_add_weak_pointer(G_OBJECT(initial_from_other_thread), + (gpointer *) &initial_from_other_thread); + g_object_unref(initial_from_other_thread); + g_assert(initial_from_other_thread == NULL); +} + +/*****************************************************************************/ + +static void +ethtool_features_dump(const NMEthtoolFeatureStates *features) +{ + guint i, j; + + g_assert(features); + + _LOGT(">>> %u features (%u ss-features)", features->n_states, features->n_ss_features); + + for (i = 0; i < features->n_states; i++) { + const NMEthtoolFeatureState *s = &features->states_list[i]; + + _LOGT(">>> feature-list[%3u]: %3d = %-32s (%3u) | %s %s %s %s", + i, + (int) s->info->ethtool_id, + s->info->kernel_names[s->idx_kernel_name], + s->idx_ss_features, + s->active ? "ACT" : "act", + s->available ? "AVA" : "ava", + s->never_changed ? "NCH" : "nch", + s->requested ? "REQ" : "req"); + } + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { + _LOGT(">>> feature-idx [%3u]: %-32s = %u features", + i + (guint) _NM_ETHTOOL_ID_FEATURE_FIRST, + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + (guint) NM_PTRARRAY_LEN(features->states_indexed[i])); + for (j = 0; features->states_indexed[i] && features->states_indexed[i][j]; j++) { + const NMEthtoolFeatureState *s = features->states_indexed[i][j]; + + _LOGT(">>> %3u: %-32s | %s %s %s %s", + j, + s->info->kernel_names[s->idx_kernel_name], + s->active ? "ACT" : "act", + s->available ? "AVA" : "ava", + s->never_changed ? "NCH" : "nch", + s->requested ? "REQ" : "req"); + } + } +} + +static void +test_ethtool_features_get(void) +{ + gs_unref_ptrarray GPtrArray *gfree_keeper = g_ptr_array_new_with_free_func(g_free); + const int IFINDEX = 1; + guint i; + guint i_run; + + for (i_run = 0; i_run < 5; i_run++) { + NMEthtoolFeatureStates *features; + NMOptionBool * requested; + gboolean do_set = TRUE; + + requested = g_new(NMOptionBool, _NM_ETHTOOL_ID_FEATURE_NUM); + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + requested[i] = NM_OPTION_BOOL_DEFAULT; + g_ptr_array_add(gfree_keeper, requested); + + if (i_run == 0) { + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_RX)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = + NM_OPTION_BOOL_FALSE; + } else if (i_run == 1) + do_set = FALSE; + else if (i_run == 2) { + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = + NM_OPTION_BOOL_TRUE; + } else if (i_run == 3) + do_set = FALSE; + + _LOGT(">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset"); + + features = nmp_utils_ethtool_get_features(IFINDEX); + g_ptr_array_add(gfree_keeper, features); + + ethtool_features_dump(features); + + if (_LOGT_ENABLED()) + _system("ethtool -k lo"); + + if (!do_set) { + requested = gfree_keeper->pdata[i_run * 2 - 2]; + features = gfree_keeper->pdata[i_run * 2 - 1]; + } + + nmp_utils_ethtool_set_features(IFINDEX, features, requested, do_set); + } +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, SLAVE_NAME, FALSE); + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, PARENT_NAME, FALSE); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, SLAVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, PARENT_NAME)); + + g_test_add_func("/link/bogus", test_bogus); + g_test_add_func("/link/loopback", test_loopback); + g_test_add_func("/link/internal", test_internal); + g_test_add_func("/link/software/bridge", test_bridge); + g_test_add_func("/link/software/bond", test_bond); + g_test_add_func("/link/software/team", test_team); + g_test_add_func("/link/software/vlan", test_vlan); + g_test_add_func("/link/software/bridge/addr", test_bridge_addr); + + if (nmtstp_is_root_test()) { + g_test_add_func("/link/external", test_external); + + test_software_detect_add("/link/software/detect/bridge", NM_LINK_TYPE_BRIDGE, 0); + test_software_detect_add("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0); + test_software_detect_add("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0); + test_software_detect_add("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0); + test_software_detect_add("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1); + test_software_detect_add("/link/software/detect/ip6gre", NM_LINK_TYPE_IP6GRE, 0); + test_software_detect_add("/link/software/detect/ip6gretap", NM_LINK_TYPE_IP6GRETAP, 0); + test_software_detect_add("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0); + test_software_detect_add("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0); + test_software_detect_add("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0); + test_software_detect_add("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0); + test_software_detect_add("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0); + test_software_detect_add("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0); + test_software_detect_add("/link/software/detect/vrf", NM_LINK_TYPE_VRF, 0); + test_software_detect_add("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0); + test_software_detect_add("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1); + test_software_detect_add("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0); + test_software_detect_add("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1); + test_software_detect_add("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2); + + g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress); + + g_test_add_data_func("/link/create-many-links/20", + GUINT_TO_POINTER(20), + test_create_many_links); + g_test_add_data_func("/link/create-many-links/1000", + GUINT_TO_POINTER(1000), + test_create_many_links); + + g_test_add_func("/link/nl-bugs/veth", test_nl_bugs_veth); + g_test_add_func("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink); + g_test_add_func("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink); + + g_test_add_vtable("/general/netns/general", + 0, + NULL, + _test_netns_setup, + test_netns_general, + _test_netns_teardown); + g_test_add_vtable("/general/netns/set-netns", + 0, + NULL, + _test_netns_setup, + test_netns_set_netns, + _test_netns_teardown); + g_test_add_vtable("/general/netns/push", + 0, + NULL, + _test_netns_setup, + test_netns_push, + _test_netns_teardown); + g_test_add_vtable("/general/netns/bind-to-path", + 0, + NULL, + _test_netns_setup, + test_netns_bind_to_path, + _test_netns_teardown); + + g_test_add_func("/general/netns/mt", test_netns_mt); + + g_test_add_func("/general/sysctl/rename", test_sysctl_rename); + g_test_add_func("/general/sysctl/netns-switch", test_sysctl_netns_switch); + g_test_add_func("/general/sysctl/set-async", test_sysctl_set_async); + g_test_add_func("/general/sysctl/set-async-fail", test_sysctl_set_async_fail); + + g_test_add_func("/link/ethtool/features/get", test_ethtool_features_get); + } +} diff --git a/src/core/platform/tests/test-nmp-object.c b/src/core/platform/tests/test-nmp-object.c new file mode 100644 index 0000000..590f41c --- /dev/null +++ b/src/core/platform/tests/test-nmp-object.c @@ -0,0 +1,617 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "platform/nmp-object.h" +#include "nm-udev-aux/nm-udev-utils.h" + +#include "nm-test-utils-core.h" + +struct { + GList *udev_devices; +} global; + +/*****************************************************************************/ + +static void +test_obj_base(void) +{ + static const union { + GObject g; + NMPObject k; + } x = {}; + static const union { + GTypeClass k; + NMPClass c; + } l = {}; + static const GObject * g = &x.g; + static const GTypeClass *k = &l.k; + static const NMPObject * o = &x.k; + static const NMPClass * c = &l.c; + + NMObjBaseInst * obj; + gs_unref_object GCancellable *obj_cancellable = g_cancellable_new(); + nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link(10); + + g_assert(&g->g_type_instance == (void *) &o->_class); + g_assert(&g->g_type_instance.g_class == (void *) &o->_class); + + g_assert(sizeof(o->parent.parent) == sizeof(GTypeInstance)); + + g_assert(&c->parent == (void *) c); + g_assert(&c->parent.parent.g_type_class == (void *) c); + g_assert(&c->parent.parent.g_type == (void *) c); + g_assert(&c->parent.parent.g_type == &k->g_type); + + g_assert(sizeof(c->parent.parent) == sizeof(GTypeClass)); + + g_assert(&o->parent == (void *) o); + g_assert(&o->parent.klass == (void *) &o->_class); + + obj = (NMObjBaseInst *) obj_cancellable; + g_assert(!NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); + g_assert(G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); + + obj = (NMObjBaseInst *) obj_link; + g_assert(NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); + g_assert(!G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); +} + +/*****************************************************************************/ + +static gboolean +_nmp_object_id_equal(const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_id_equal(a, b); + + g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_id_equal(b, a)); + return a_b; +} +#define nmp_object_id_equal _nmp_object_id_equal + +static gboolean +_nmp_object_equal(const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_equal(a, b); + + g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_equal(b, a)); + return a_b; +} +#define nmp_object_equal _nmp_object_equal + +/*****************************************************************************/ + +static void +_assert_cache_multi_lookup_contains(const NMPCache * cache, + const NMDedupMultiHeadEntry *head_entry, + const NMPObject * obj, + gboolean visible_only, + gboolean contains) +{ + NMDedupMultiIter iter; + gboolean found; + guint i, len; + const NMPObject *o; + + g_assert(NMP_OBJECT_IS_VALID(obj)); + + g_assert(nmp_cache_lookup_obj(cache, obj) == obj); + g_assert(!head_entry + || (head_entry->len > 0 + && c_list_length(&head_entry->lst_entries_head) == head_entry->len)); + + len = head_entry ? head_entry->len : 0; + + found = FALSE; + i = 0; + nmp_cache_iter_for_each (&iter, head_entry, &o) { + g_assert(NMP_OBJECT_IS_VALID(o)); + if (obj == o) { + if (!visible_only || nmp_object_is_visible(o)) { + g_assert(!found); + found = TRUE; + } + } + i++; + } + + g_assert(len == i); + g_assert(!!contains == found); +} + +static void +_assert_cache_multi_lookup_contains_link(const NMPCache * cache, + gboolean visible_only, + const NMPObject *obj, + gboolean contains) +{ + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + + g_assert(cache); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + head_entry = nmp_cache_lookup(cache, &lookup); + _assert_cache_multi_lookup_contains(cache, head_entry, obj, visible_only, contains); +} + +/*****************************************************************************/ + +static void +ops_post_check(NMPCache * cache, + NMPCacheOpsType ops_type, + const NMPObject *obj_old, + const NMPObject *obj_new, + const NMPObject *obj_new_expected, + NMPCacheOpsType expected_ops_type) +{ + g_assert(cache); + + g_assert_cmpint(expected_ops_type, ==, ops_type); + + switch (ops_type) { + case NMP_CACHE_OPS_ADDED: + g_assert(!obj_old); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(nmp_object_is_alive(obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + g_assert(nmp_object_equal(obj_new_expected, obj_new)); + break; + case NMP_CACHE_OPS_UPDATED: + g_assert(obj_old != obj_new); + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(nmp_object_is_alive(obj_old)); + g_assert(nmp_object_is_alive(obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); + g_assert(nmp_object_id_equal(obj_old, obj_new)); + g_assert(nmp_object_equal(obj_new_expected, obj_new)); + g_assert(!nmp_object_equal(obj_new_expected, obj_old)); + g_assert(!nmp_object_equal(obj_old, obj_new)); + break; + case NMP_CACHE_OPS_REMOVED: + g_assert(!obj_new); + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(nmp_object_is_alive(obj_old)); + if (obj_new_expected) + g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); + break; + case NMP_CACHE_OPS_UNCHANGED: + g_assert(obj_old == obj_new); + if (obj_old) { + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(nmp_object_is_alive(obj_old)); + g_assert(nmp_object_equal(obj_old, obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + } else + g_assert(!obj_new_expected); + break; + default: + g_assert_not_reached(); + } +} + +static void +_nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + NMPCacheOpsType expected_ops_type) +{ + NMPCacheOpsType ops_type; + const NMPObject *obj_prev; + const NMPObject *obj_old; + const NMPObject *obj_new; + nm_auto_nmpobj NMPObject *obj_new_expected = NULL; + + g_assert(cache); + g_assert(NMP_OBJECT_IS_VALID(obj)); + + obj_prev = nmp_cache_lookup_link(cache, NMP_OBJECT_CAST_LINK(obj)->ifindex); + obj_new_expected = nmp_object_clone(obj, FALSE); + if (obj_prev && obj_prev->_link.udev.device) + obj_new_expected->_link.udev.device = udev_device_ref(obj_prev->_link.udev.device); + _nmp_object_fixup_link_udev_fields(&obj_new_expected, NULL, nmp_cache_use_udev_get(cache)); + + ops_type = nmp_cache_update_netlink(cache, obj, FALSE, &obj_old, &obj_new); + ops_post_check(cache, + ops_type, + obj_old, + obj_new, + nmp_object_is_alive(obj_new_expected) ? obj_new_expected : NULL, + expected_ops_type); + + if (out_obj_new) + *out_obj_new = obj_new; + else + nmp_object_unref(obj_new); + if (out_obj_old) + *out_obj_old = obj_old; + else + nmp_object_unref(obj_old); +} + +static const NMPlatformLink pl_link_2 = { + .ifindex = 2, + .name = "eth0", + .type = NM_LINK_TYPE_ETHERNET, +}; + +static const NMPlatformLink pl_link_3 = { + .ifindex = 3, + .name = "wlan0", + .type = NM_LINK_TYPE_WIFI, +}; + +static void +test_cache_link(void) +{ + NMPCache * cache; + NMPObject * objm1; + const NMPObject * obj_old, *obj_new; + NMPObject objs1; + struct udev_device * udev_device_2 = g_list_nth_data(global.udev_devices, 0); + struct udev_device * udev_device_3 = g_list_nth_data(global.udev_devices, 0); + NMPCacheOpsType ops_type; + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; + gboolean use_udev = nmtst_get_rand_uint32() % 2; + + multi_idx = nm_dedup_multi_index_new(); + + cache = nmp_cache_new(multi_idx, use_udev); + + /* if we have a link, and don't set is_in_netlink, adding it has no effect. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert(NMP_OBJECT_UP_CAST(&objm1->object) == objm1); + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(!obj_old); + g_assert(!obj_new); + g_assert(!nmp_cache_lookup_obj(cache, objm1)); + g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); + nmp_object_unref(objm1); + + /* Only when setting @is_in_netlink the link is added. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(!obj_old); + g_assert(obj_new); + g_assert(objm1 == obj_new); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_new); + + /* updating the same link with identical value, has no effect. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(obj_new); + g_assert(obj_new != objm1); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + nmp_object_unref(objm1); + nmp_object_unref(obj_new); + nmp_object_unref(obj_new); + + /* remove the link from netlink */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_REMOVED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(!obj_new); + g_assert(!nmp_cache_lookup_obj(cache, objm1)); + g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + if (udev_device_2) { + /* now add the link only with aspect UDEV. */ + ops_type = + nmp_cache_update_link_udev(cache, pl_link_2.ifindex, udev_device_2, &obj_old, &obj_new); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); + g_assert(!obj_old); + g_assert(obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(!nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + nmp_object_unref(obj_new); + } + + /* add it in netlink too. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, + objm1, + &obj_old, + &obj_new, + udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + if (udev_device_2) { + g_assert(obj_old); + g_assert(!nmp_object_is_visible(obj_old)); + } else + g_assert(!obj_old); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* remove again from netlink. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = FALSE; + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, + objm1, + &obj_old, + &obj_new, + udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED); + nmtst_assert_nmp_cache_is_consistent(cache); + if (udev_device_2) + g_assert(obj_new == objm1); + else + g_assert(!obj_new); + g_assert(obj_old); + g_assert(nmp_object_is_alive(obj_old)); + if (udev_device_2) { + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(!nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + } else { + g_assert(nmp_cache_lookup_obj(cache, objm1) == NULL); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == NULL); + g_assert(!nmp_object_is_alive(obj_new)); + g_assert(!nmp_object_is_visible(obj_new)); + } + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* now another link only with aspect UDEV. */ + if (udev_device_3) { + /* now add the link only with aspect UDEV. */ + ops_type = + nmp_cache_update_link_udev(cache, pl_link_3.ifindex, udev_device_3, &obj_old, &obj_new); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(!obj_old); + g_assert(!nmp_object_is_visible(obj_new)); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, FALSE); + g_assert_cmpint(obj_new->link.initialized, ==, FALSE); + nmp_object_unref(obj_new); + + /* add it in netlink too. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UPDATED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(obj_new == objm1); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(!obj_old || !nmp_object_is_visible(obj_old)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint(obj_new->link.initialized, ==, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* remove UDEV. */ + ops_type = nmp_cache_update_link_udev(cache, pl_link_3.ifindex, NULL, &obj_old, &obj_new); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_UPDATED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old && nmp_object_is_visible(obj_old)); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint(obj_new->link.initialized, ==, !nmp_cache_use_udev_get(cache)); + nmp_object_unref(obj_new); + nmp_object_unref(obj_old); + } + + nmp_cache_free(cache); +} + +const char noqueue[] = "noqueue"; +const char fq_codel[] = "fq_codel"; +const char ingress[] = "ingress"; + +static const NMPlatformQdisc pl_qdisc_1a = { + .ifindex = 1, + .kind = noqueue, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_1b = { + .ifindex = 1, + .kind = fq_codel, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_1c = { + .ifindex = 1, + .kind = ingress, + .addr_family = AF_UNSPEC, + .handle = TC_H_MAKE(TC_H_INGRESS, 0), + .parent = TC_H_INGRESS, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_2 = { + .ifindex = 2, + .kind = fq_codel, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static void +test_cache_qdisc(void) +{ + NMPCache * cache; + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; + NMPLookup lookup; + const NMDedupMultiHeadEntry * head_entry; + nm_auto_nmpobj NMPObject *obj1a = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1a); + nm_auto_nmpobj NMPObject *obj1b = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1b); + nm_auto_nmpobj NMPObject *obj1c = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1c); + nm_auto_nmpobj NMPObject *obj2 = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2); + + multi_idx = nm_dedup_multi_index_new(); + cache = nmp_cache_new(multi_idx, nmtst_get_rand_uint32() % 2); + + g_assert(nmp_cache_lookup_obj(cache, obj1a) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1a, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1a); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1a); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1b, FALSE, NULL, NULL) == NMP_CACHE_OPS_UPDATED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1c, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1c) == obj1c); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj2, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj2) == obj2); + + head_entry = nmp_cache_lookup(cache, nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, 1)); + g_assert(head_entry->len == 2); + + nmp_cache_free(cache); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + int result; + NMUdevClient *udev_client; + + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + udev_client = nm_udev_client_new(NM_MAKE_STRV("net"), NULL, NULL); + { + struct udev_enumerate * enumerator; + struct udev_list_entry *devices, *l; + + enumerator = nm_udev_client_enumerate_new(udev_client); + + /* Demand that the device is initialized (udev rules ran, + * device has a stable name now) in case udev is running + * (not in a container). */ + if (access("/sys", W_OK) == 0) + udev_enumerate_add_match_is_initialized(enumerator); + + udev_enumerate_scan_devices(enumerator); + + devices = udev_enumerate_get_list_entry(enumerator); + for (l = devices; l != NULL; l = udev_list_entry_get_next(l)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), + udev_list_entry_get_name(l)); + if (udevice == NULL) + continue; + + global.udev_devices = g_list_prepend(global.udev_devices, udevice); + } + global.udev_devices = g_list_reverse(global.udev_devices); + + udev_enumerate_unref(enumerator); + } + + g_test_add_func("/nmp-object/obj-base", test_obj_base); + g_test_add_func("/nmp-object/cache_link", test_cache_link); + g_test_add_func("/nmp-object/cache_qdisc", test_cache_qdisc); + + result = g_test_run(); + + while (global.udev_devices) { + udev_device_unref(global.udev_devices->data); + global.udev_devices = g_list_delete_link(global.udev_devices, global.udev_devices); + } + + nm_udev_client_destroy(udev_client); + + return result; +} diff --git a/src/core/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c new file mode 100644 index 0000000..00671cf --- /dev/null +++ b/src/core/platform/tests/test-platform-general.c @@ -0,0 +1,772 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-platform/nm-platform-utils.h" +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Address, address)); + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Route, network)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Route, network)); + +/*****************************************************************************/ + +static void +test_init_linux_platform(void) +{ + gs_unref_object NMPlatform *platform = NULL; + + platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT); +} + +/*****************************************************************************/ + +static void +test_link_get_all(void) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_unref_ptrarray GPtrArray *links = NULL; + + platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT); + + links = nm_platform_link_get_all(platform, TRUE); +} + +/*****************************************************************************/ + +static void +test_nm_platform_link_flags2str(void) +{ + int i; + + for (i = 0; i < 100; i++) { + char buf[NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 100]; + const char *s; + const guint flags = ((i == 0) ? ~0u : nmtst_get_rand_uint()); + gsize l; + + s = nm_platform_link_flags2str(flags, buf, sizeof(buf)); + g_assert(s); + + l = strlen(s); + if (l > NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN) + g_error( + "nm_platform_link_flags2str(%x) produced a longer output than %zu chars: \"%s\"", + flags, + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, + s); + if (flags == ~0u && l != NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN) + g_error("nm_platform_link_flags2str(%x) is expected to produce %zu chars, but produced " + "%zu: \"%s\"", + flags, + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, + l, + s); + } +} + +/*****************************************************************************/ + +static int +_address_pretty_sort_cmp(gconstpointer a, gconstpointer b, gpointer test_data) +{ + const int TEST_DATA_I = GPOINTER_TO_INT(test_data); + const int addr_family = (TEST_DATA_I == 0 ? AF_INET : AF_INET6); + const int IPV6_PREFER_TEMP = (TEST_DATA_I == 2); + const NMPlatformIPXAddress *a_a = a; + const NMPlatformIPXAddress *a_b = b; + + if (addr_family == AF_INET) + return nm_platform_ip4_address_pretty_sort_cmp(&a_a->a4, &a_b->a4); + + return nm_platform_ip6_address_pretty_sort_cmp(&a_a->a6, &a_b->a6, IPV6_PREFER_TEMP); +} + +static void +test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data) +{ + static const char *const EXPECTED_BUFFER[3] = { + [0] = ("5b1aea34648cabfec7c3523f76cf1ce34ca17a9a32f3f0f218424e48836dd1cb504e03d53e1124c5" + "0065aeb2e6fbf952902383028e3b47f280f062ea1a7e0b7be218d067530e1b0487b8c3b99f2b8a1a" + "8982c42f0000003437c5156e072b2f2f0037c9cfe07c34ddb3980deb14ab7b5af84a034703000000" + "883b0f3fd6ed84d6c959e553b887edcd01c2f5d200000043b809d259e499db7d00f1853bdcb0e4bc" + "0e2b00b667b7b16d8d1e69c803000000b973972c17a47631c169f11ff9119c400368b6630000007a" + "034f43340d01683c0045097aea4a849f060ddf57b24a5be9636360d603000000ad7c499dd538d345" + "74c038404923e75d0209e2fc0000004acc807cdce682f80f00315c45ef817264c89a736ed55ed637" + "b96c200203000000faf1809becd2506315a6da29b2e94d3a031fe0e900000083a36035fb6297dfa7" + "00686b5efd0d53bb6215de4bb6f6f3d031a79028030000008bb836c0a25ea71f5daaed4d99eb2ebe" + "033c432f000000bf4ccf30d3aaaf02a4005d7308b67f91bf9d82c856ba942455e8d07c8403000000" + "f2abb982b001ec16901f55f960c55c22022099a80000002b4d4647f53b1921af0088e3759a08e7a5" + "6663861eea1bf42c12ea3b9503000000fb95e8332fdfff658483a2d039a7bf1402d3481e00000060" + "e89f7abdb682380a00eae374835b4a49a2b980b6aba92da6409969aa03000000e00473755d31e5b2" + "de252167c1c91b3a02ba0c700000007740318db913a353ed006efc068829c0e66ad0143a0554efb4" + "40e55b8b03000000c0cfb2b4386bec092fa5757ecde9348b00c12ebd000000ab667224dae775e5cc" + "0041aca2ff0f576767d3648102b61886d149f07403000000153ece68ade15cec25a59273e7519f34" + "c4458d70000000f3819aa46fbe1439340033ae6dec0fb124f264af67eed7c9a8ecc8fb1103000000" + "fcebbaeb0c56535923f14874042a8aff021f8e5ec3cc13cc36bbe3c9bb0ec36f00e007bb64a2827b" + "7cdd38d0314c178e5a06c40b03000000ab90135fa636af4464d210a256be75e0029c44770000004b" + "2e69220d6c0fc09c25d6534c809829af4a9df58dbfef186d416f3a1e030000002c932e655203d82a" + "3c84c4eb274ed18603780281000000f2235376239daeaacb3cae864b437baaae91921681c2162b9b" + "69e66142030000004fda8a3e0b841cf76391dd68269b53ec0244a831000000b78c54dda9ac3bb1b1" + "d43e6505621b9a7f0422ae3fc8979ee0416f95e70300000057d6249b652ba98c7dc7f17f666969e4" + "023baf7a000000ee0b06fa9e988f80f0de6dc8dfcf2a3ad3bbcc0fa3b314f695111d891d03000000" + "cd897619f51f44e644d7cf1d06b2b1150082549e62c12fba5b1cdec48d10bbb13b8313d8cd2a24d3" + "4fc812bd2f8a59d90fcc00ac030000005292cd32dc096cd5d8a4c5cf3351ee1c03c2056c00000051" + "bbce426cfa4b861cc78592be7b14e7ba9c15acb881ae55f0e5fe7d360300000066a3ae3939762df3" + "3a2d55060c78d55100b110fd00000041b9aca07b6e4925dd27943a272c171ed15abbbe1cd911db7b" + "86ed271803000000a5edc511c1507a141e0f515638c7ba31027609450000003357ae79989870ccec" + "3def0ad92749e016663fe6ee0228c1da82d1595603000000348352d715cf9d411ea012e5307294b6" + "0146dac4000000075efee38dd16f8ee4ccd2f50c30706cae3fdcc2f0ee3d5e26bb20413203000000" + "862573c2303dd1d65c7b2cffeca6d1adaccae11f0000000f855ebf3b772eb2b1c896c9a7304f6645" + "0a5f6abd850b06e3b10123e6030000001dff045298cfee0636674cdefb57b9ae54cfe8f400000038" + "1ba2c4396de60f032bc7f34de2959871c0d4c0d4eb720c4ab550c5db03000000f32f4af595d785d5" + "c1b5aad487c192f08bd7a09b00000043288cd9bf66ec305a225a0c71b2ce78bb16104c8eaf18c565" + "b16c7376030000005219061ab4c5c79489b2cc6a883c146972decb8b000000f5e6d66df46ea13910" + "7754dee62c36d2fc70ccc567df7a49b8585287dc03000000cfb18b2b2cb749e2e03e544d0eb4f73e" + "75039fcf251b32fc79685b05ddd3aa9ba511d2e40edb4d758fb554158ae5c7c0beb42b3403000000" + "895d5f24037d233302ad3b82d639272e0246eadfbd2146bf8cfdb205f90e54b58a6ee136a779f37c" + "30d2c5053c40ecaec38b6b8e03000000bea73223e59bf0193432e9fa7a899f2d8ec7e4b89bf5a5d0" + "6776e66a9d80ab132e1ac921eb76adbb229df32e561fa80a0fc4676703000000a23eb66e720da9e0" + "7ac998b5690807d50217369ee1af4ca5a6a95581af5fd7ceacdca10f47d7b351a36b178aabc78a4a" + "1a0dd8c003000000e2815a1a37a52bddd1c2f1018b587eed03bb58f0e9201f17bd99fcf72909ac9e" + "7a55299e9bb4fd53bc7417940fcffe3f81cafd6302000000d6732578acd14320aefd4503189f7630" + "038e501c0000002b9f3c39f24b0572b100745cb25851429b3bbfb50168dfd04eb62f22ca02000000" + "891715df7fc6a902edae579e2e10c7f7022ba0340436242cbeb0248cee3fbc160032d4f28aa28c08" + "f80dd50c6712dfb4abba4a32020000008ffe423d01883918039249f398f9b37ea091465100000064" + "3722d9b707c0d8a400b7c8307f06b4b29088f20d9ac676d5e4bafc4e02000000fde69eec3af2e6d0" + "bd68ab722af14548b29572e504265f6c72923e22594f3f790008ed2e2ebb0771db46a54cadb245ea" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd6011fa56dfdcfc1155f236c8b9c79a620" + "00660bbf024b03ff0a8e27c405e64244e36f90d402000000fd41fe47684b370b6ec6584d64496089" + "570968ead4d1ae91c819bb068196d59900de3246e43f5e7945aaf95e2ffa3a119c64ed0402000000" + "a660ac824b7fae389861419c50da49bf02092583000000259f9f0251becc987907879cca68fec7bb" + "bb5f8edf248b4995d184e82002000000a19cdf6dd1c173f6078a806d329c9b0000bfc972000000f5" + "b2cd3dbddc74e26de958e48d2ab8b0313e7f8933e315130f641e447502000000b9b68c08a5e4351e" + "a349e1ccf662e058b879a45100000045fcb6a035339d504c9726d80d9c2d89df765b4d9a130257dc" + "d1e1b34902000000bcd7be07d78e6222e45aaf61814f703b40125e6b000000cbaaa37b861e6d46da" + "fe7d6ec4ac1ea051010911915ddb05f2c43bd794010000005bfe47c6f53a54e01b0c1d89414d94d0" + "032d2ec50000000103781b0f294a2b7300421398f4de67e9cee64b38b56e03e01539ce4101000000" + "18238487a417f3da01d99dae5f19009601582cab0000005b2363d13edc5aa115005eb914d8fbe9a4" + "fdb3d117d76b0de5bd82e9ea010000003d1b91caae8cb60b49ba9be338d856a40267c3d400000064" + "9da90bd2fff2f2560046870bd7d5f14870c6d18d6242b356b9ef1b2101000000f90adac616a31dc2" + "e46a234558817151008fc9c900000073e64b0bd761fdf274005dca8ce1bd1871ae17bb4515856092" + "b4d9e89b010000006855676c277cf1bd017c9148da5892bb0351c3a70000009f3391ce7d3fd48469" + "005f0c233dbdd2a97835df1f1782dee86c1de913010000003ee1d76fc1ea76e98c9dfc78997ab53e" + "00c21963f5b4cb2454830c68e44ea74b002b83f9b3bee14d861a4c9eeecc89f65408c1b701000000" + "d9e1825fa638e1af284a024b7f9e85ff00e050a2000000276e08cb887464b93400c3127c750fabdd" + "69121aec129cdc690d58fbcc01000000d0d44ff1e51c35157666c05348e6f50700a8e89800000012" + "b900b2061d0c334b009f2dd1cdf64b0f9a60e0e289f08db3fde6b0250100000039dd8f88152a5845" + "4d9ca9d20f45dfa702e78604000000fbcd7db68b9ae586da00b4070c50320427c4dd3d031e33f22c" + "210aeb09010000007000b96d06992b6a58acd3995b9663d20248f333ef467092818aa77d6732b678" + "00844ef5a943825fcd743f59bd14c89b955e1a13010000007acbce3e3bcdf3824f1b134847ff26ba" + "009a774900000017ec852d59f3d17232edd86ad6c3103a68843a9aef34983882d3d3878501000000" + "5e0dfe491d1ba96742c7b5e02b2271220188b02a06d0dc5504b0595daf37deb499996bfb667f072d" + "ec1e5d9cdc8a11f4409bcfb30100000099d90a8543961b2ccd47724a3c460ba80383f4c500000063" + "eafb1ac4c0982b283aa9986700b2a2b3ed257b8b0489f48f053ec8ca0100000027335a25a364d101" + "5ffac03089f4553902b01784000000d7b83579b8da27345a72437f9b6245de39ec9e71ee4b951507" + "f121014a01000000efc67bce716c856e3973dc42a1003be94f89d8ee0000009b5d5bbe6c10085f3d" + "6176f78a19bb8df1804c122fce5078c156e8f3fe010000000ea8042602a1f8e6f5657f3f9e3eb807" + "cbad7645000000b8df6f628a70456d79f25d5895fb57fa60d9279fb2b8fcbac65ad47b8a01000000" + "da40d88d40a6d75bc404156225b7eedefc2b44574b15e2ae496ad01bf007eacb0a28aec868282510" + "b60291ea6480e356925b568a0000000087bb24e5264fd3ebe9cf9f6df9615189018ee815000000a7" + "5c9555876b6a3f13002b6cb8360feaac1d5c302df59dd32a7a859db500000000362956cd46646a0e" + "222160e5f769bb290366ed370000005b6a813387e99bb834009da86c64fefab2548759d313a5b92d" + "8e47935e0000000034f0386a253c21d94064f6b021281e230094ae20000000d71fd050bf8d85055b" + "00e3756ccdb3455c60ca7b11c66af76e594f24a100000000e143fd52599364e13468f80fd514573f" + "b572671c0000006932d1d5f5d0ce2cf6007a70ba5193a162bc92ec1b11d9172c857ae81200000000" + "3e29535402e9b690c628d048eacce745ea213cb1000000b632ef3be6070dafa200187470e9da5570" + "9427c226d324d9a08487fd0d00000000b7a350f9fc1519defa7db4532545666937c22a3b000000dc" + "c405cbee5016c25200d8901d7a0165fe20744edb6ba04f14a4c73cf500000000a4bab14874afdf54" + "e6aae816430607ca0675e09818e9bbec5918c59068baf76a008940f6fc3bbdc7f6090f756aae660b" + "6e4c699300000000d9c1e67743efb54e54270e46042e911803894e38000000376feecb80ac245409" + "c0becc271d9c2f67179bff0644399ae7e3c9472e00000000c5cfd9f2343b21362c19a0921dce2f83" + "00defa45000000270b9977e166bee737fe73670c439a644c323b59b4cd20eb7dabea74f700000000" + "f6989d2d6a909e986ff7add5df2c93e05459507b0000000f466554d2ae4d52a8c67b2e48b47003c8" + "1785d3ffdbd9a617df6b3542000000004c5cf8107ba282f4f983821918f93e742d08f0550000006f" + "2292362e5d68265d9f98c82d9b7a559be3acf4fc36fa6b5159747cf200000000ab2dadc5a39411fd" + "4ff1116d478987316a553fc2000000cfc6ebe434a7ae8ff040483e310819e3b10db116431ec6f769" + "438a72e1000000002495a609675344f7e2e3a5ebaec3c85f0a1742f70abe95c50345132a61eda239" + "d9d083c3bf085387046ef8a36f0e9e696b382ab0000000009a6ce5d906837dbca6a5ee19d6f63fe9" + "03dd01f7246f13b2050424a2b3a45ef7a029c896b4132bd895072cfeffe9d6815997069500000000" + "0d3c723b91adb0da7c4aa7e7eb5a15bc03015fb98b841fd84cc43c510385b9a4c2aac1d67a909b29" + "7c703915312e9c3cae02dfa000000000dd603bd35e7fa0f02f2f3313d8469d09a92409c0b7f0318a" + "575a4f8e061db3dd7fde25654a4059d565dbc8a91e3b4457b077ddad3108be69f9b97d05c917ad6b" + "10e693bb6e26f2ba90c8e909a9fe20e5c7a4c656482a9b0d00625009a40aeb62a42b6a62548e3c38" + "cd3c72f203000000ca82ac5180101be4f85cef468ea086ea01aafdc3a9fe1ec787bc45db7c52a52d" + "00bd39a44e8e8bc17c01ac63eca0c1cf5ff7f03a03000000c9a89192c1c8be55281a59d1fd338f35" + "0075f8cea9fec34573654ea6624f138ef9531cd9367a02e4d241989477a363d53b02239e03000000" + "24438387def0f4c6544e4b275d9b714600f810d2a9fec17647176b7c07d856e3b883efebc09dd9d6" + "1966b7ae7412041d57393c6f03000000182c0287822a272bec4501a1e27acfee018588ffa9fe6cae" + "426de59560fad65d67c624f285d7174177a47579dda0b6eaa9a84c820300000070b1646d8026e9f1" + "704f1b16286ba2da017ef082a9feed33ef60a8b540b26f66761d1f13badfad0fe8fa8f3c1aad2a82" + "fa40546c03000000df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea9fe3e7d75a2f42b50c6a363" + "40132378b95c59313bacba64dbe996206e6904f50200000047150b9b14010469823acb72bb89182f" + "00112196a9feb9153b36bc60be5b534e006527f67485ab35aca0c7ee419733853cf09e8b02000000" + "e79c10acfce165e332a62384ec04e5ba009185ada9fe0070a36dd51323b2c54200154d12f86c260a" + "9edfa7a74c1c83c1050f63f802000000443cacf59c6379a44b7892f487afa98c0221c19ca9fe942f" + "460bcea75481f25e007d0de9a7afe283bd2f22ead05ff72006c83bc0020000004bdade862c224f6f" + "36506ebd455e679c00369bb8a9fecec3f8c8fa6867a982be8a934f852cc3d4d82bc0ec7303f99f8f" + "def85b7502000000a0bef8675b29a197b7b3cceaf5f1bb120335256aa9fe6e5d58099ffc4a503a71" + "2350acbd48411f0dc15d2f0f49dad345d966279502000000e06302aba042aaa218dc091e9aa1477f" + "6fdc9830a9fec95829a8838314dff34d24c332219a1b163a732d803e0e2f4f916d06412601000000" + "98c39e7cc282208fefc57ff447036b9501adcb22a9fe793f797a3c7dadd1c86e009d0c90bc512e13" + "7dcef5e4a27985bd5cfd5ce601000000152f2b70eaef7443e0f79ab6902dde5301b3ec71a9fe9f25" + "4ac95883195580410062ed564153e17478f8c3344d89c0bbfaa100fd01000000be184524a6bdc878" + "9cf851782d895bcc03a98489a9fe8c1287e6f7bb020ffdb00012098610e52bb2a16a4008aefd545b" + "0d80684e01000000ba8110fb9733cc24904f288262e6ea77032fa5f8a9feeefa701d120523bd98f2" + "00098b43cd68be6e3f81268193fd637e9037d7a701000000c47cf0f551e96770a754ac19ef820fe0" + "0031f2d3a9fe049150b8d10ab700cc3a7cf51be0403b654ba2f56808092069af5f5b481b01000000" + "68cb3bc873b04d937a6ed8f7bc51e54066fed098a9fe048a92d3adc69a84eb47622400207799416a" + "f1f0a086fbd7e2f7dea0077a00000000c386e9c6e6a2cbfa10ee58bdc75183600085d627a9feb1cb" + "e491cbbbf9443fd6007eb3c5bf64b671d6f18dbf463f9b83f512dc1c00000000fbab244735d67c61" + "283031667b2d74a102e0b1b1a9fe2aa590a2312e17f1a35900459582f4ef43c780908872746e39ef" + "a9a89f8700000000fcdf6d9be94030b34774d1d7dddedd9802f0f627a9fe965a87041331b2834bcf" + "00b4e3ce848518c4e3f6cbf25e5e1b992231bb0200000000173c333cd03bc905b7b899afeb760e3f" + "024a2efca9fe96b89b1bc8f415bd4e77be46bae5a1b3cae76665a268abfe8a41a84e27c100000000" + "cb29efdf672d2fa57fc85ebbe276c5660364192da9fee7af5eb888e9eb37bb046686943b101e1f55" + "3215abf8fbdf17c3677e5a3a00000000608df061d45d864d09f4ecf17625f82d03c74828a9fe1e37" + "1051852c972ea7954079884af257b044fd13a6826a4c619f3d136cac000000009402a4c216772167" + "3f2b02b3256ead1f03ea9bc1a9fefca162fb81e733cff620ca7feefe1933631e8e69f6d9d6962d2c"), + [1] = ("54270e46020000006a894e387625da376feecb80ac245409c0becc271d9c2f67179bff0644399ae7" + "9c64ed0432d599eaa660ac824b7fae389861419c7a899f2d010000009bf5a5d06776e66a9d80ab13" + "2e1ac921eb76adbb229df32e561fa80a40e55b8b1dd92e18c0cfb2b4386bec092fa5757ecde9348b" + "c4e1966800000000b3121cd5ef51e696c816290dbaee0e7726d082e1530ff5397c9125f59577d71c" + "4b258a005116d11354edff62ceaa458fc75a91c425a5927300000000c4458d70911714f1819aa46f" + "be143934c933ae6dec0fb124f264af67eed7c9a8c38b6b8ee2614344bea73223e59bf0193432e9fa" + "4cc43c5103000000c2aac1d67a909b297c703915312e9c3c5a06c40b20ea99d5ab90135fa636af44" + "64d210a256be75e0509c4477d19ac64b2e69220d12ea3b9501000000fb95e8332fdfff658483a2d0" + "39a7bf148ed3481e0bccc460e89f7abdb682380a62eae374835b4a49a2b980b6aba92da6409969aa" + "288cd9bf00000000225a0c71b2ce78bb16104c8eaf18c565ecc8fb112087b97cfcebbaeb0c565359" + "23f14874042a8aff1d1f8e5ec3cc13cc36bbe3c9bb0ec36f0000000064a2827b7cdd38d0314c178e" + "925b568ae56bc4e9fefb24e5264fd3ebe9cf9f6df9615189f78ee815b2781ea55c9555876b6a3f13" + "02e9b69000000000eacce745ea213cb1a84035b632ef3be6070dafa2fed87470e9da55709427c226" + "d324d9a086ed27184b443181a5edc511c1507a142fea9bc10300000062fb81e733cff620ca7feefe" + "1933631e8e69f6d9fed62d2c6e6904f57239c09c47150b9b14010469823acb72bb89182f93112196" + "9985d62700000000e491cbbbf9443fd6a77eb3c5bf64b671d6f18dbffeff9b8306c83bc026977911" + "4bdade862c224f6f36506ebd455e679cef369bb8d514573f03000000f6932f6932d1d5f5d0ce2cf6" + "bf7a70ba5193a162fe92ec1b11d9172cf84a03478b82cdd8883b0f3fd6ed84d6c959e553b887edcd" + "6297dfa700000000fd0d53bb6215de4bb6f6f3d031a790287e45e9c1feb836c0a25ea71f5daaed4d" + "99eb2ebebe3c432fec7d3abc4ccf30d3aaaf02a41e0f515601000000f57609453df7803357ae7998" + "9870ccec3def0ad9000000000000000000000000000000019f05a16768cb3bc873b04d937a6ed8f7" + "ae8cb60b0300000038d856a40c67c3d4afa9c8649da90bd2fff2f256000000000000000000000000" + "00000001955e1a13d1111b067acbce3e3bcdf38200154d12010000009edfa7a74c1c83c18e47935e" + "8354846e34f0386a00000000000000000000000000000001ac26c7d41fd050bf8d85055bcfe3756c" + "a2f56808092069affaa100fdfdd68d34be184524a6bdc8789cf85178000000000000000000000000" + "00000000020ffdb0e612098610e52bb2a16a40086f0e9e6903000000ec2e2924348352d715cf9d41" + "1ea012e5307294b600000000000000000000000000000000ccd2f50c30706cae3fdcc2f0ee3d5e26" + "81ae55f0030000000aa20624fbab244735d67c61283031667b2d74a1000000000000000000000000" + "0000000000459582f4ef43c780908872746e39efaefd545b020000007ced4f8b362956cd46646a0e" + "222160e5f769bb2900000000000000000000000000000000a19da86c64fefab2548759d313a5b92d" + "f05d7308020000009d82c856ba942455050f63f8282f3464443cacf5000000000000000000000000" + "0000000069d9942f460bcea75481f25e307d0de96e91b91501000000be5b534e0a6527f67485ab35" + "aca0c7ee4197338500000000000000000000000000000000aefd4503189f7630248e501c9052c22b" + "5d68265d010000009b7a559be3acf4fc36fa6b513cf09e8b99289462000000000000000000000000" + "00000000b49185ad9b7d0070a36dd51323b2c542e5fe7d360100000066a3ae3939762df33a2d5506" + "0c78d551cbb110fd000000000000000000000000000000002c171ed15abbbe1cd911db7b56e8f3fe" + "9a6ce5d900000000a6a5ee19d6f63fe9cbdd01f7246f13b2050424a2000000000000000000000000" + "00000000ffe9d6812231bb02f922259b173c333cbae87d60000000005d31e5b2de252167c1c91b3a" + "36ba0c700ee78477000000000000000000000000000000006ad0143a0554efb46b382ab0404cdf02" + "7c52a52d030000004e8e8bc17c01ac63eca0c1cfbb204132fb7213e4000000000000000000000000" + "00000000accae11f8045a80f855ebf3b772eb2b1d03bc90503000000eb760e3f004a2efc8ffc96b8" + "9b1bc8f415bd4e7700000000000000000000000000000000d149f074a378b881153ece68ade15cec" + "7666c05303000000ffa8e89871fb9510b900b2061d0c334b819f2dd1000000000000000000000000" + "00000000095a3230fde69eec3af2e6d0bd68ab7210613fc90200000016a31dc2e46a234558817151" + "b38fc9c909cf8d7100000000000000000000000000000000ae17bb4515856092b4d9e89b676761b2" + "57d6249b020000007dc7f17f666969e45c3baf7a119a65ee0b06fa9e000000000000000000000000" + "00000000b314f695bd82e9ea8891ed0a3d1b91cab56e03e0010000002f70249eefc67bce716c856e" + "3973dc42a1003be9000000000000000000000000000000006176f78a19bb8df1804c122fce5078c1" + "eecc89f601000000d635edea8ffe423d01883918039249f398f9b37e000000000000000000000000" + "000000001fb7c8307f06b4b29088f20d9ac676d5f90e54b501000000a779f37c30d2c5053c40ecae" + "210aeb0934cb2a2400000000000000000000000000000000df48f333ef467092818aa77d6732b678" + "bcd7be0700000000e45aaf61814f703b40125e6baf4648cbaaa37b86000000000000000000000000" + "000000005ddb05f2a84e27c18a06e2c9cb29efdf50da49bf00000000d94beb259f9f0251becc9879" + "07879cca68fec7bb000000000000000000000000000000009402a4c2167721673f2b02b3256ead1f" + "a349e1cc00000000b879a45176740744fcb6a035339d504c9726d80d000000000000000000000000" + "000000009518862bc9a89192c1c8be55281a59d1ebfdcec30000000067a982be8a934f852cc3d4d8" + "2bc0ec7303f99f8f00000000000000000000000000000000901f55f960c55c22f32099a8020bea2a" + "5b1aea3401000000c7c3523f76cf1ce34ca17a9a32f3f0f218424e48000000000000000000000000" + "00000002e6fbf952902383028e3b47f2111d891d536c75bb01000000720da9e07ac998b5690807d5" + "2617369ee1af4ca500000000000000000000ffff47d7b351a36b178aabc78a4a0d80684ee5dbf45b" + "17a4763102000000f9119c40b468b66331bca67a034f43340d01683c00000000000000000000ffff" + "b24a5be9b10123e6e4f15ecd1dff045298cfee069c88b02a0300000004b0595daf37deb499996bfb" + "667f072dec1e5d9cdc8a11f4409bcfb3c167090e99d90a8543961b2ccd47724a3c460ba85183f4c5" + "a7afe28302000000d05ff720b16c73761a3fcb675219061ab4c5c79489b2cc6a883c146972decb8b" + "5adac8f5e6d66df46ea139107754dee62c36d2fcc7a4c65601000000cd625009a40aeb62a42b6a62" + "548e3c38b96c20022753c5fbfaf1809becd2506315a6da29b2e94d3ab51fe0e9af98b180a36035fb" + "e36f90d401000000fd41fe47684b370b6ec6584d64496089570968ead4d1ae91c819bb068196d599" + "efde3246e43f5e7945aaf95e2ffa3a11b9ef1b21cdb3455c00000000c66af76e80f062ea1a7e0b7b" + "e218d067530e1b0487b8c3b99f2b8a1a8982c42f53700a3437c5156e072b2f2fb337c9cfe07c34dd" + "c896c9a7000000000a5f6abd850b06e3d9662795ce2cc1cee06302aba042aaa218dc091e9aa1477f" + "6fdc9830d7fbc95829a8838314dff34d24c332214d4647f5000000000f88e3759a08e7a56663861e" + "ea1bf42ccd3c72f2ad063857ca82ac5180101be4f85cef468ea086ea9aaafdc388811ec787bc45db" + "7625ca4a00000000e682f80f94315c45ef817264c89a736ed55ed637b077ddad3108be69f9b97d05" + "c917ad6b10e693bb6e26f2ba90c8e909e85e20e5b0916e5d030000004a503a712350acbd48411f0d" + "c15d2f0f49dad345599706954d2471f00d3c723b91adb0da7c4aa7e7eb5a15bcde015fb98b841fd8" + "ba8110fb03000000904f288262e6ea77a22fa5f863eaeefa701d120523bd98f29b098b43cd68be6e" + "3f81268193fd637e585287dc6ca972a2cfb18b2b3cae864b0200000091921681c2162b9bfa40546c" + "db544e44df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea0ab3e7d75a2f42b50c6a36340132378" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd65c1fa56dfdcfc1155f236c8b9c79a620" + "5f660bbf024b03ff0a8e27c405e6424457393c6fdac12ebd01000000667224dae775e5ccd141aca2" + "ff0f576767d3648102b61886b62f22ca33478e1f891715df7fc6a902edae579e2e10c7f7a22ba034" + "abf85f0c0100000008d33a2ea67776d7f88d69c8c7e3b2d3c93ef054e93f8120abb42316c533d9c9" + "4d88189c471a1ff2f0ce3ff66e782110125ae2eddaae7d6300000000c0982b283aa9986700b2a2b3" + "ed257b8b0489f48f053ec8cac72105b327335a25a364d1015ffac03089f45539e1b0178462156cd6" + "6855676c00000000017c9148da5892bb4951c3a7ed55689d3391ce7d3fd48469845f0c233dbdd2a9" + "7835df1f1782dee88487fd0db5971b46b7a350f998c39e7c00000000efc57ff447036b9551adcb22" + "fd50793f797a3c7dadd1c86e759d0c90bc512e137dcef5e4a27985bd8d1e69c83dc21056b973972c" + "bc51e540030000006c3d048a92d3adc69a84eb47622400207799416afef0a086fbd7e2f7dea0077a" + "e9c0c0d9c386e9c6e6a2cbfa10ee58bdc75183600422ae3f03000000c43bd794c697895e5bfe47c6" + "f53a54e01b0c1d89fecd94d0e02d2ec587fc330003781b0f294a2b73c4421398f4de67e9cee64b38" + "69121aec010000000fcc00ac2daa755e5292cd32dc096cd5d8a4c5cffe91ee1cfcc2056cc1ff8e51" + "bbce426cfa4b861cc78592be7b14e7ba9c15acb836674cde0200000054cfe8f42bf0e9381ba2c439" + "6de60f032bc7f34dfe959871c0d4c0d4eb720c4ae3c9472e5f2da5d9c5cfd9f2343b21362c19a092" + "b2cd3dbd01000000e958e48d2ab8b0313e7f8933e315130fa9a84c82feb83f6e70b1646d8026e9f1" + "704f1b16286ba2dabc7ef0820c91ed33ef60a8b5bcfe97e1000000007ad742d3d7320a4f880cf47f" + "5dd0cf69cb22840ffeb3fe7749509cb6752b2cee30b7e7736a0afc9879ea40e69710fc9f6e8e99cd" + "1dce2f83020000003a3efc240b9977e166bee737fe73670c439a644c000000000000000000000000" + "000000015e0dfe491d1ba96742c7b5e02b227122cf844ef501000000cd743f59bd14c89b5cfd5ce6" + "e82e7854152f2b700000000000000000000000000000000127e19f254ac9588319558041fc62ed56" + "b83579b80300000072437f9b6245de39ec9e71ee4b951507beb42b34000000000000000000000000" + "00000001d639272e4a46eadfbd2146bf8cfdb205b3980deb020000006c1de913df1424ce3ee1d76f" + "c1ea76e98c9dfc7800000000000000000000000000000001e44ea74b4f2b83f9b3bee14d861a4c9e" + "0436242c03000000ee3fbc164632d4f28aa28c08f80dd50c6712dfb4000000000000000000000000" + "00000000078a806d329c9b008bbfc9723107f3f52af145480300000004265f6c72923e22594f3f79" + "e208ed2e2ebb07710000000000000000000000000000000039dd8f88152a58454d9ca9d20f45dfa7" + "922b6cb8030000001d5c302df59dd32a677e5a3af4fe297f608df061000000000000000000000000" + "000000008ae21e371051852c972ea7954079884a9a1b163a030000000e2f4f911a0dd8c052ed6b13" + "e2815a1a37a52bdd00000000000000000000000000000000bd99fcf72909ac9e7a55299e9bb4fd53" + "9f3c2c8803000000f51f44e644d7cf1d06b2b115d882549e62c12fba000000000000000000000000" + "000000004fc812bd2f8a59d96d064126666befbbf257b044030000006a4c619f9037d7a754655354" + "c47cf0f551e967700000000000000000000000000000000050b8d10ab700cc3a7cf51be0403b654b" + "74e7860402000000cd7db68b9ae586dacdb4070c50320427c4dd3d03000000000000000000000000" + "00000000d538d34574c038404923e75d0e09e2fcfc1519de010000002545666937c22a3b6e9686dc" + "c405cbee5016c252000000000000000000000000000000006e4c699341b95f41d9c1e67743efb54e" + "6aae660b01000000fc1fb23d24438387def0f4c6544e4b275d9b7146000000000000000000000000" + "00000000b883efebc09dd9d61966b7ae7412041dd7ac5f9901000000822a272bec4501a1e27acfee" + "7a8588ffd5a06cae0000000000000000000000000000000077a47579dda0b6ead1e1b34901eaedd5" + "61c2f5d200000000b809d259e499db7dc8f1853bdcb0e4bc0e2b00b6000000000000000000000000" + "000000005e7fa0f02f2f3313d8469d09a92409c0b4d54acb00000000a417f3da01d99dae5f190096" + "cc582cabd5ddd15a00000000000000000000000000000000fdb3d117d76b0de5416f95e7bb75d72f" + "fc2b445700000000496ad01bf007eacb0a28aec868282510b60291ea000000000000000000000000" + "000000005b29a197b7b3cceaf5f1bb12c535256a74993ade03000000e62112898dab2dade2ab2fc9" + "c56a7c86be5f962200000000000000000000000000000000bdd06352cb2c354434819f4b248eb2b8" + "dc335c8b030000000a3c8e7c91bf821c4b09cee3c37ff4283a631480000000000000000000000000" + "000000005588456e4ead7cbda620a3abae816e34e3ff1a5f03000000b4425486c619147eb0216050" + "ed7afd741024e83600000000000000000000000000000000f493472201e48d163106cc397446a33f" + "bc74179403000000438a72e12ae0436a2495a609675344f7e2e3a5eb000000000000000000000000" + "0000000061eda239d9d083c3bf085387046ef8a34153e174020000004d89c0bb5408c1b7d17fb084" + "d9e1825fa638e1af000000000000000000000000000000006e08cb887464b9344ec3127c750fabdd" + "6c0fc09c02000000809829af4a9df58dbfef186d416f3a1ef170d10f000000000000000000000000" + "0000000087780281d47a32f2235376239daeaacba9a89f8701000000fcdf6d9be94030b34774d1d7" + "dddedd9899f0f62700000000000000000000000000000000848518c4e3f6cbf25e5e1b990fc46767" + "fd338f35010000006e8cc34573654ea6624f138ef9531cd9367a02e4000000000000000000000000" + "00000000da40d88d40a6d75bc404156225b7eede2cb749e2010000000eb4f73e75039fcf251b32fc" + "79685b05ddd3aa9b000000000000000000000000000000000d58fbcc58fc4c2fd0d44ff1e51c3515" + "b7f0318a01000000061db3dd7fde25654a4059d565dbc8a91e3b4457000000000000000000000000" + "00000000c1b5aad487c192f08bd7a09bdd21444359747cf200000000ab2dadc5a39411fd4ff1116d" + "478987316a553fc2000000000000000000000000000000000819e3b10db116431ec6f7691539ce41" + "b95c593100000000dbe9962069e66142e22753344fda8a3e0b841cf7000000000000000000000000" + "000000008c54dda9ac3bb1b1d43e6505621b9a7f672d2fa501000000e276c5661a64192db44ce7af" + "5eb888e9eb37bb0400000000000000000000000000000002641e447514ca565fb9b68c08a5e4351e" + "9f3c39f201000000de745cb25851429b3bbfb50168dfd04edf6b3542000000000000000000000000" + "0000000218f93e742d08f0550b55726f2292362e70ccc56700000000a4c73cf5ffa4ffc8a4bab148" + "74afdf54e6aae8160000000000000000000000000000000268baf76ab08940f6fc3bbdc7f6090f75" + "4f1b134803000000449a774929e86716ec852d59f3d17232edd86ad6000000000000000000000000" + "000000027dba1ee7e143fd52599364e13468f80f40b26f6603000000badfad0fe8fa8f3c1aad2a82" + "530b7447d9c2f401000000000000000000000000000000028147507e1cd59ea1ae6da48b1eba6d16" + "dedea2030100000002a1f8e6f5657f3f9e3eb807cbad76451055edb8000000000000000000000000" + "00000002d9279fb2b8fcbac6abea74f7a6df7979f6989d2d000000006ff7add5df2c93e05459507b" + "5d8c6a0f466554d200000000000000000000ffff1785d3ffdbd9a617857ae8126b028c0e3e295354"), + [2] = ("4cc43c5103000000c2aac1d67a909b297c703915312e9c3c5a06c40b20ea99d5ab90135fa636af44" + "64d210a256be75e0509c4477d19ac64b2e69220d54270e46020000006a894e387625da376feecb80" + "ac245409c0becc271d9c2f67179bff0644399ae79c64ed0432d599eaa660ac824b7fae389861419c" + "12ea3b9501000000fb95e8332fdfff658483a2d039a7bf148ed3481e0bccc460e89f7abdb682380a" + "62eae374835b4a49a2b980b6aba92da6409969aa7a899f2d010000009bf5a5d06776e66a9d80ab13" + "2e1ac921eb76adbb229df32e561fa80a40e55b8b1dd92e18c0cfb2b4386bec092fa5757ecde9348b" + "288cd9bf00000000225a0c71b2ce78bb16104c8eaf18c565ecc8fb112087b97cfcebbaeb0c565359" + "23f14874042a8aff1d1f8e5ec3cc13cc36bbe3c9c4e1966800000000b3121cd5ef51e696c816290d" + "baee0e7726d082e1530ff5397c9125f59577d71c4b258a005116d11354edff62ceaa458fc75a91c4" + "25a5927300000000c4458d70911714f1819aa46fbe143934c933ae6dec0fb124f264af67eed7c9a8" + "c38b6b8ee2614344bea73223e59bf0193432e9fa2fea9bc10300000062fb81e733cff620ca7feefe" + "1933631e8e69f6d9fed62d2c6e6904f57239c09c47150b9b14010469823acb72bb89182f93112196" + "9985d62700000000e491cbbbf9443fd6a77eb3c5bf64b671d6f18dbffeff9b8306c83bc026977911" + "4bdade862c224f6f36506ebd455e679cef369bb8bb0ec36f0000000064a2827b7cdd38d0314c178e" + "925b568ae56bc4e9fefb24e5264fd3ebe9cf9f6df9615189f78ee815b2781ea55c9555876b6a3f13" + "02e9b69000000000eacce745ea213cb1a84035b632ef3be6070dafa2fed87470e9da55709427c226" + "d324d9a086ed27184b443181a5edc511c1507a14d514573f03000000f6932f6932d1d5f5d0ce2cf6" + "bf7a70ba5193a162fe92ec1b11d9172cf84a03478b82cdd8883b0f3fd6ed84d6c959e553b887edcd" + "6297dfa700000000fd0d53bb6215de4bb6f6f3d031a790287e45e9c1feb836c0a25ea71f5daaed4d" + "99eb2ebebe3c432fec7d3abc4ccf30d3aaaf02a4ae8cb60b0300000038d856a40c67c3d4afa9c864" + "9da90bd2fff2f25600000000000000000000000000000001955e1a13d1111b067acbce3e3bcdf382" + "00154d12010000009edfa7a74c1c83c18e47935e8354846e34f0386a000000000000000000000000" + "00000001ac26c7d41fd050bf8d85055bcfe3756c1e0f515601000000f57609453df7803357ae7998" + "9870ccec3def0ad9000000000000000000000000000000019f05a16768cb3bc873b04d937a6ed8f7" + "7c52a52d030000004e8e8bc17c01ac63eca0c1cfbb204132fb7213e4000000000000000000000000" + "00000000accae11f8045a80f855ebf3b772eb2b1d03bc90503000000eb760e3f004a2efc8ffc96b8" + "9b1bc8f415bd4e7700000000000000000000000000000000d149f074a378b881153ece68ade15cec" + "6f0e9e6903000000ec2e2924348352d715cf9d411ea012e5307294b6000000000000000000000000" + "00000000ccd2f50c30706cae3fdcc2f0ee3d5e267666c05303000000ffa8e89871fb9510b900b206" + "1d0c334b819f2dd100000000000000000000000000000000095a3230fde69eec3af2e6d0bd68ab72" + "81ae55f0030000000aa20624fbab244735d67c61283031667b2d74a1000000000000000000000000" + "0000000000459582f4ef43c780908872746e39ef10613fc90200000016a31dc2e46a234558817151" + "b38fc9c909cf8d7100000000000000000000000000000000ae17bb4515856092b4d9e89b676761b2" + "57d6249b020000007dc7f17f666969e45c3baf7a119a65ee0b06fa9e000000000000000000000000" + "00000000b314f695bd82e9ea8891ed0a3d1b91ca6e91b91501000000be5b534e0a6527f67485ab35" + "aca0c7ee4197338500000000000000000000000000000000aefd4503189f7630248e501c9052c22b" + "5d68265d010000009b7a559be3acf4fc36fa6b513cf09e8b99289462000000000000000000000000" + "00000000b49185ad9b7d0070a36dd51323b2c542b56e03e0010000002f70249eefc67bce716c856e" + "3973dc42a1003be9000000000000000000000000000000006176f78a19bb8df1804c122fce5078c1" + "e5fe7d360100000066a3ae3939762df33a2d55060c78d551cbb110fd000000000000000000000000" + "000000002c171ed15abbbe1cd911db7b56e8f3feeecc89f601000000d635edea8ffe423d01883918" + "039249f398f9b37e000000000000000000000000000000001fb7c8307f06b4b29088f20d9ac676d5" + "f90e54b501000000a779f37c30d2c5053c40ecae210aeb0934cb2a24000000000000000000000000" + "00000000df48f333ef467092818aa77d6732b678bcd7be0700000000e45aaf61814f703b40125e6b" + "af4648cbaaa37b86000000000000000000000000000000005ddb05f2a84e27c18a06e2c9cb29efdf" + "50da49bf00000000d94beb259f9f0251becc987907879cca68fec7bb000000000000000000000000" + "000000009402a4c2167721673f2b02b3256ead1fa349e1cc00000000b879a45176740744fcb6a035" + "339d504c9726d80d000000000000000000000000000000009518862bc9a89192c1c8be55281a59d1" + "bae87d60000000005d31e5b2de252167c1c91b3a36ba0c700ee78477000000000000000000000000" + "000000006ad0143a0554efb46b382ab0404cdf02ebfdcec30000000067a982be8a934f852cc3d4d8" + "2bc0ec7303f99f8f00000000000000000000000000000000901f55f960c55c22f32099a8020bea2a" + "a2f56808092069affaa100fdfdd68d34be184524a6bdc8789cf85178000000000000000000000000" + "00000000020ffdb0e612098610e52bb2a16a4008aefd545b020000007ced4f8b362956cd46646a0e" + "222160e5f769bb2900000000000000000000000000000000a19da86c64fefab2548759d313a5b92d" + "f05d7308020000009d82c856ba942455050f63f8282f3464443cacf5000000000000000000000000" + "0000000069d9942f460bcea75481f25e307d0de99a6ce5d900000000a6a5ee19d6f63fe9cbdd01f7" + "246f13b2050424a200000000000000000000000000000000ffe9d6812231bb02f922259b173c333c" + "5b1aea3401000000c7c3523f76cf1ce34ca17a9a32f3f0f218424e48000000000000000000000000" + "00000002e6fbf952902383028e3b47f2111d891d17a4763102000000f9119c40b468b66331bca67a" + "034f43340d01683c00000000000000000000ffffb24a5be9b10123e6e4f15ecd1dff045298cfee06" + "536c75bb01000000720da9e07ac998b5690807d52617369ee1af4ca500000000000000000000ffff" + "47d7b351a36b178aabc78a4a0d80684ee5dbf45bb0916e5d030000004a503a712350acbd48411f0d" + "c15d2f0f49dad345599706954d2471f00d3c723b91adb0da7c4aa7e7eb5a15bcde015fb98b841fd8" + "ba8110fb03000000904f288262e6ea77a22fa5f863eaeefa701d120523bd98f29b098b43cd68be6e" + "3f81268193fd637e585287dc6ca972a2cfb18b2b3cae864b0200000091921681c2162b9bfa40546c" + "db544e44df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea0ab3e7d75a2f42b50c6a36340132378" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd65c1fa56dfdcfc1155f236c8b9c79a620" + "5f660bbf024b03ff0a8e27c405e6424457393c6fdac12ebd01000000667224dae775e5ccd141aca2" + "ff0f576767d3648102b61886b62f22ca33478e1f891715df7fc6a902edae579e2e10c7f7a22ba034" + "abf85f0c0100000008d33a2ea67776d7f88d69c8c7e3b2d3c93ef054e93f8120abb42316c533d9c9" + "4d88189c471a1ff2f0ce3ff66e782110125ae2edc7a4c65601000000cd625009a40aeb62a42b6a62" + "548e3c38b96c20022753c5fbfaf1809becd2506315a6da29b2e94d3ab51fe0e9af98b180a36035fb" + "daae7d6300000000c0982b283aa9986700b2a2b3ed257b8b0489f48f053ec8cac72105b327335a25" + "a364d1015ffac03089f45539e1b0178462156cd66855676c00000000017c9148da5892bb4951c3a7" + "ed55689d3391ce7d3fd48469845f0c233dbdd2a97835df1f1782dee88487fd0db5971b46b7a350f9" + "cdb3455c00000000c66af76e80f062ea1a7e0b7be218d067530e1b0487b8c3b99f2b8a1a8982c42f" + "53700a3437c5156e072b2f2fb337c9cfe07c34ddc896c9a7000000000a5f6abd850b06e3d9662795" + "ce2cc1cee06302aba042aaa218dc091e9aa1477f6fdc9830d7fbc95829a8838314dff34d24c33221" + "98c39e7c00000000efc57ff447036b9551adcb22fd50793f797a3c7dadd1c86e759d0c90bc512e13" + "7dcef5e4a27985bd8d1e69c83dc21056b973972c7625ca4a00000000e682f80f94315c45ef817264" + "c89a736ed55ed637b077ddad3108be69f9b97d05c917ad6b10e693bb6e26f2ba90c8e909e85e20e5" + "9c88b02a0300000004b0595daf37deb499996bfb667f072dec1e5d9cdc8a11f4409bcfb3c167090e" + "99d90a8543961b2ccd47724a3c460ba85183f4c5a7afe28302000000d05ff720b16c73761a3fcb67" + "5219061ab4c5c79489b2cc6a883c146972decb8b5adac8f5e6d66df46ea139107754dee62c36d2fc" + "e36f90d401000000fd41fe47684b370b6ec6584d64496089570968ead4d1ae91c819bb068196d599" + "efde3246e43f5e7945aaf95e2ffa3a11b9ef1b214d4647f5000000000f88e3759a08e7a56663861e" + "ea1bf42ccd3c72f2ad063857ca82ac5180101be4f85cef468ea086ea9aaafdc388811ec787bc45db" + "0422ae3f03000000c43bd794c697895e5bfe47c6f53a54e01b0c1d89fecd94d0e02d2ec587fc3300" + "03781b0f294a2b73c4421398f4de67e9cee64b38bc51e540030000006c3d048a92d3adc69a84eb47" + "622400207799416afef0a086fbd7e2f7dea0077ae9c0c0d9c386e9c6e6a2cbfa10ee58bdc7518360" + "36674cde0200000054cfe8f42bf0e9381ba2c4396de60f032bc7f34dfe959871c0d4c0d4eb720c4a" + "e3c9472e5f2da5d9c5cfd9f2343b21362c19a092b2cd3dbd01000000e958e48d2ab8b0313e7f8933" + "e315130fa9a84c82feb83f6e70b1646d8026e9f1704f1b16286ba2dabc7ef0820c91ed33ef60a8b5" + "bcfe97e1000000007ad742d3d7320a4f880cf47f5dd0cf69cb22840ffeb3fe7749509cb6752b2cee" + "30b7e7736a0afc9879ea40e69710fc9f6e8e99cd69121aec010000000fcc00ac2daa755e5292cd32" + "dc096cd5d8a4c5cffe91ee1cfcc2056cc1ff8e51bbce426cfa4b861cc78592be7b14e7ba9c15acb8" + "b83579b80300000072437f9b6245de39ec9e71ee4b951507beb42b34000000000000000000000000" + "00000001d639272e4a46eadfbd2146bf8cfdb205b3980deb020000006c1de913df1424ce3ee1d76f" + "c1ea76e98c9dfc7800000000000000000000000000000001e44ea74b4f2b83f9b3bee14d861a4c9e" + "cf844ef501000000cd743f59bd14c89b5cfd5ce6e82e7854152f2b70000000000000000000000000" + "0000000127e19f254ac9588319558041fc62ed561dce2f83020000003a3efc240b9977e166bee737" + "fe73670c439a644c000000000000000000000000000000015e0dfe491d1ba96742c7b5e02b227122" + "2af145480300000004265f6c72923e22594f3f79e208ed2e2ebb0771000000000000000000000000" + "0000000039dd8f88152a58454d9ca9d20f45dfa774993ade03000000e62112898dab2dade2ab2fc9" + "c56a7c86be5f962200000000000000000000000000000000bdd06352cb2c354434819f4b248eb2b8" + "922b6cb8030000001d5c302df59dd32a677e5a3af4fe297f608df061000000000000000000000000" + "000000008ae21e371051852c972ea7954079884a9f3c2c8803000000f51f44e644d7cf1d06b2b115" + "d882549e62c12fba000000000000000000000000000000004fc812bd2f8a59d96d064126666befbb" + "dc335c8b030000000a3c8e7c91bf821c4b09cee3c37ff4283a631480000000000000000000000000" + "000000005588456e4ead7cbda620a3abae816e34e3ff1a5f03000000b4425486c619147eb0216050" + "ed7afd741024e83600000000000000000000000000000000f493472201e48d163106cc397446a33f" + "f257b044030000006a4c619f9037d7a754655354c47cf0f551e96770000000000000000000000000" + "0000000050b8d10ab700cc3a7cf51be0403b654bbc74179403000000438a72e12ae0436a2495a609" + "675344f7e2e3a5eb0000000000000000000000000000000061eda239d9d083c3bf085387046ef8a3" + "4153e174020000004d89c0bb5408c1b7d17fb084d9e1825fa638e1af000000000000000000000000" + "000000006e08cb887464b9344ec3127c750fabdd6c0fc09c02000000809829af4a9df58dbfef186d" + "416f3a1ef170d10f0000000000000000000000000000000087780281d47a32f2235376239daeaacb" + "74e7860402000000cd7db68b9ae586dacdb4070c50320427c4dd3d03000000000000000000000000" + "00000000d538d34574c038404923e75d0e09e2fca9a89f8701000000fcdf6d9be94030b34774d1d7" + "dddedd9899f0f62700000000000000000000000000000000848518c4e3f6cbf25e5e1b990fc46767" + "fd338f35010000006e8cc34573654ea6624f138ef9531cd9367a02e4000000000000000000000000" + "00000000da40d88d40a6d75bc404156225b7eede2cb749e2010000000eb4f73e75039fcf251b32fc" + "79685b05ddd3aa9b000000000000000000000000000000000d58fbcc58fc4c2fd0d44ff1e51c3515" + "6aae660b01000000fc1fb23d24438387def0f4c6544e4b275d9b7146000000000000000000000000" + "00000000b883efebc09dd9d61966b7ae7412041db7f0318a01000000061db3dd7fde25654a4059d5" + "65dbc8a91e3b445700000000000000000000000000000000c1b5aad487c192f08bd7a09bdd214443" + "61c2f5d200000000b809d259e499db7dc8f1853bdcb0e4bc0e2b00b6000000000000000000000000" + "000000005e7fa0f02f2f3313d8469d09a92409c0b4d54acb00000000a417f3da01d99dae5f190096" + "cc582cabd5ddd15a00000000000000000000000000000000fdb3d117d76b0de5416f95e7bb75d72f" + "59747cf200000000ab2dadc5a39411fd4ff1116d478987316a553fc2000000000000000000000000" + "000000000819e3b10db116431ec6f7691539ce41b95c593100000000dbe9962069e66142e2275334" + "4fda8a3e0b841cf7000000000000000000000000000000008c54dda9ac3bb1b1d43e6505621b9a7f" + "fc2b445700000000496ad01bf007eacb0a28aec868282510b60291ea000000000000000000000000" + "000000005b29a197b7b3cceaf5f1bb12c535256a0436242c03000000ee3fbc164632d4f28aa28c08" + "f80dd50c6712dfb400000000000000000000000000000000078a806d329c9b008bbfc9723107f3f5" + "9a1b163a030000000e2f4f911a0dd8c052ed6b13e2815a1a37a52bdd000000000000000000000000" + "00000000bd99fcf72909ac9e7a55299e9bb4fd53fc1519de010000002545666937c22a3b6e9686dc" + "c405cbee5016c252000000000000000000000000000000006e4c699341b95f41d9c1e67743efb54e" + "d7ac5f9901000000822a272bec4501a1e27acfee7a8588ffd5a06cae000000000000000000000000" + "0000000077a47579dda0b6ead1e1b34901eaedd54f1b134803000000449a774929e86716ec852d59" + "f3d17232edd86ad6000000000000000000000000000000027dba1ee7e143fd52599364e13468f80f" + "40b26f6603000000badfad0fe8fa8f3c1aad2a82530b7447d9c2f401000000000000000000000000" + "000000028147507e1cd59ea1ae6da48b1eba6d16dedea2030100000002a1f8e6f5657f3f9e3eb807" + "cbad76451055edb800000000000000000000000000000002d9279fb2b8fcbac6abea74f7a6df7979" + "672d2fa501000000e276c5661a64192db44ce7af5eb888e9eb37bb04000000000000000000000000" + "00000002641e447514ca565fb9b68c08a5e4351e9f3c39f201000000de745cb25851429b3bbfb501" + "68dfd04edf6b35420000000000000000000000000000000218f93e742d08f0550b55726f2292362e" + "70ccc56700000000a4c73cf5ffa4ffc8a4bab14874afdf54e6aae816000000000000000000000000" + "0000000268baf76ab08940f6fc3bbdc7f6090f75f6989d2d000000006ff7add5df2c93e05459507b" + "5d8c6a0f466554d200000000000000000000ffff1785d3ffdbd9a617857ae8126b028c0e3e295354"), + }; + const int TEST_DATA_I = GPOINTER_TO_INT(test_data); + const int addr_family = (TEST_DATA_I == 0 ? AF_INET : AF_INET6); + const int IPV6_PREFER_TEMP = (TEST_DATA_I == 2); + const guint N_ADDRESSES = 100; + const gsize ELM_SIZE = + addr_family == AF_INET ? sizeof(NMPlatformIP4Address) : sizeof(NMPlatformIP6Address); + const gboolean DO_REGENERATE = FALSE; + const gboolean PRINT_RESULT = DO_REGENERATE; + const gboolean CHECK_RESULT = !DO_REGENERATE; + gs_free guint8 *addresses = NULL; + gs_free guint64 *rand_map = NULL; + gsize i, j; + +#if !defined(__amd64__) + /* The test generates a random array of NMPlatformIPXAddress (by crudely randomizing the memory, + * not the structures themself) and then compares the sorted result with the expected output. + * The sole purpose is to ensure that the sorting order stays stable. + * + * This only works on an architecture for which the test was made, otherwise + * the expected data does not match (due to different layout of the structures + * in memory). + * + * That's fine. Skip the test. */ + g_test_skip("skip test on non-amd64 architecture"); + return; +#endif + + /* + * First we create a list of addresses filled with (stable) random bytes. + * We tweak some fields explicitly (stable randomly), so that we cover all + * relevant cases. + * + * Then we sort the list of addresses, and compare that the result is + * as our EXPECTED_BUFFER. + */ + + addresses = g_malloc(ELM_SIZE * N_ADDRESSES); + rand_map = g_malloc(sizeof(rand_map[0]) * N_ADDRESSES); + nmtst_stable_rand(258829693, addresses, ELM_SIZE * N_ADDRESSES); + nmtst_stable_rand(710086081, rand_map, sizeof(rand_map[0]) * N_ADDRESSES); + + for (i = 0; i < N_ADDRESSES; i++) { + NMPlatformIPXAddress *a = (gpointer)(&addresses[i * ELM_SIZE]); + guint64 r = rand_map[i]; + struct in6_addr * a6; + +#define CONSUME_BITS(r, nbits) \ + ({ \ + guint64 _r = (r); \ + const int _nbits = (nbits); \ + \ + r >>= (_nbits); \ + (_r & (((((guint64) 1u) << (_nbits)) - 1u))); \ + }) + + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + /* randomly create a link-local address or not. */ + g_assert((NM_IPV4LL_NETWORK & ~NM_IPV4LL_NETMASK) == 0); + a->a4.address = (a->a4.address & ~NM_IPV4LL_NETMASK) | NM_IPV4LL_NETWORK; + } + } else { + a6 = &a->a6.address; + switch (CONSUME_BITS(r, 4)) { + case 0: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = htonl(0xffff); + g_assert(IN6_IS_ADDR_V4MAPPED(a6)); + break; + case 1: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = htonl(2); + g_assert(IN6_IS_ADDR_V4COMPAT(a6)); + break; + case 2: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = 0; + g_assert(IN6_IS_ADDR_UNSPECIFIED(a6)); + break; + case 3: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = htonl(1); + g_assert(IN6_IS_ADDR_LOOPBACK(a6)); + break; + case 4: + a6->s6_addr32[0] = (a6->s6_addr32[0] & ~htonl(0xffc00000)) | htonl(0xfe800000); + g_assert(IN6_IS_ADDR_LINKLOCAL(a6)); + break; + case 6: + a6->s6_addr32[0] = (a6->s6_addr32[0] & ~htonl(0xffc00000)) | htonl(0xfec00000); + g_assert(IN6_IS_ADDR_SITELOCAL(a6)); + break; + case 7: + case 8: + case 9: + case 10: + break; + default: + memset(a6, 0, sizeof(*a6)); + break; + } + } + + if (CONSUME_BITS(r, 5) != 0) { + /* randomly make addr-source the same (so that several addresses compare + * equal). */ + a->a4.addr_source = CONSUME_BITS(r, 2); + } + + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + /* randomly make the label empty or not. */ + a->a4.label[0] = '\0'; + } + } + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 2) != 0) { + /* randomly make the label empty or not. */ + a->a4.plen = CONSUME_BITS(r, 2); + } + } + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + a->a4.address = (a->a4.address & ~0xFFFFFFu) | (CONSUME_BITS(r, 2) << 24); + } + } + } + + g_qsort_with_data(addresses, + N_ADDRESSES, + ELM_SIZE, + _address_pretty_sort_cmp, + (gpointer) test_data); + + for (i = 0; i < N_ADDRESSES; i++) { + const NMPlatformIPXAddress *a = (gconstpointer)(&addresses[i * ELM_SIZE]); + + for (j = i + 1; j < N_ADDRESSES; j++) { + const NMPlatformIPXAddress *b = (gconstpointer)(&addresses[j * ELM_SIZE]); + int c1; + int c2; + + if (addr_family == AF_INET) { + c1 = nm_platform_ip4_address_pretty_sort_cmp(&a->a4, &b->a4); + c2 = nm_platform_ip4_address_pretty_sort_cmp(&b->a4, &a->a4); + } else { + c1 = nm_platform_ip6_address_pretty_sort_cmp(&a->a6, &b->a6, IPV6_PREFER_TEMP); + c2 = nm_platform_ip6_address_pretty_sort_cmp(&b->a6, &a->a6, IPV6_PREFER_TEMP); + } + + g_assert_cmpint(c1, >=, -1); + g_assert_cmpint(c1, <=, 1); + g_assert_cmpint(c1, ==, -c2); + } + } + + if (PRINT_RESULT) { + g_print("\n [%d] = (", TEST_DATA_I); + for (i = 0; i < ELM_SIZE * N_ADDRESSES;) { + if (i > 0) + g_print("\n "); + g_print("\""); + for (j = 0; j < 40 && i < ELM_SIZE * N_ADDRESSES; j++, i++) + g_print("%02x", addresses[i]); + g_print("\""); + } + g_print("),\n"); + return; + } + + if (CHECK_RESULT) { + gs_free guint8 *bin_arr = NULL; + gsize bin_len; + + bin_arr = nm_utils_hexstr2bin_alloc(EXPECTED_BUFFER[TEST_DATA_I], + FALSE, + FALSE, + NULL, + 0, + &bin_len); + + if (bin_len != ELM_SIZE * N_ADDRESSES || memcmp(addresses, bin_arr, bin_len) != 0) { + char *addresses_str = nm_utils_bin2hexstr(addresses, ELM_SIZE * N_ADDRESSES, -1); + + g_error(">>> test_platform_ip_address_pretty_sort_cmp() will fail:\n" + ">>> addresses[%zu]: %s\n" + ">>> expected [%zu]: %s\n", + ELM_SIZE * N_ADDRESSES, + addresses_str, + bin_len, + EXPECTED_BUFFER[TEST_DATA_I]); + } + + g_assert_cmpmem(addresses, ELM_SIZE * N_ADDRESSES, bin_arr, bin_len); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT"); + + g_test_add_func("/general/init_linux_platform", test_init_linux_platform); + g_test_add_func("/general/link_get_all", test_link_get_all); + g_test_add_func("/general/nm_platform_link_flags2str", test_nm_platform_link_flags2str); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/4", + GINT_TO_POINTER(0), + test_platform_ip_address_pretty_sort_cmp); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/6/1", + GINT_TO_POINTER(1), + test_platform_ip_address_pretty_sort_cmp); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/6/2", + GINT_TO_POINTER(2), + test_platform_ip_address_pretty_sort_cmp); + + return g_test_run(); +} diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c new file mode 100644 index 0000000..0aa94ec --- /dev/null +++ b/src/core/platform/tests/test-route.c @@ -0,0 +1,1926 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-core-utils.h" +#include "nm-platform/nm-platform-utils.h" +#include "platform/nmp-rules-manager.h" + +#include "test-common.h" + +#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX +#define EX NMTSTP_ENV1_EX + +static void +_wait_for_ipv4_addr_device_route(NMPlatform *platform, + gint64 timeout_msec, + int ifindex, + in_addr_t addr, + guint8 plen) +{ + /* Wait that the addresses gets a device-route. After adding a address, + * the device route is not added immediately. It takes a moment... */ + + addr = nm_utils_ip4_address_clear_host_address(addr, plen); + NMTST_WAIT_ASSERT(400, { + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject *o; + + nmp_cache_iter_for_each ( + &iter, + nm_platform_lookup(platform, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)), + &o) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(o); + + if (r->plen == plen && addr == nm_utils_ip4_address_clear_host_address(r->network, plen) + && r->metric == 0 && r->scope_inv == nm_platform_route_scope_inv(RT_SCOPE_LINK) + && r->rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL) + return; + } + nmtstp_assert_wait_for_signal(platform, + (nmtst_wait_end_us - g_get_monotonic_time()) / 1000); + }); +} + +static void +_wait_for_ipv6_addr_non_tentative(NMPlatform * platform, + gint64 timeout_msec, + int ifindex, + guint addr_n, + const struct in6_addr *addrs) +{ + guint i; + + /* Wait that the addresses become non-tentative. Dummy interfaces are NOARP + * and thus don't do DAD, but the kernel sets the address as tentative for a + * small amount of time, which prevents the immediate addition of the route + * with RTA_PREFSRC */ + + NMTST_WAIT_ASSERT(timeout_msec, { + gboolean should_wait = FALSE; + const NMPlatformIP6Address *plt_addr; + + for (i = 0; i < addr_n; i++) { + plt_addr = nm_platform_ip6_address_get(platform, ifindex, &addrs[i]); + if (!plt_addr || NM_FLAGS_HAS(plt_addr->n_ifa_flags, IFA_F_TENTATIVE)) { + should_wait = TRUE; + break; + } + } + if (!should_wait) + return; + nmtstp_assert_wait_for_signal(platform, + (nmtst_wait_end_us - g_get_monotonic_time()) / 1000); + }); +} + +static void +ip4_route_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP4Route *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPObject o_id; + nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_IP4_ROUTE); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED); + + /* run code for initializing the ID only */ + nmp_object_stackinit_id(&o_id, NMP_OBJECT_UP_CAST(received)); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), TRUE); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), FALSE); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +ip6_route_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP6Route *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPObject o_id; + nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_IP6_ROUTE); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED); + + /* run code for initializing the ID only */ + nmp_object_stackinit_id(&o_id, NMP_OBJECT_UP_CAST(received)); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), TRUE); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), FALSE); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +test_ip4_route_metric0(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData *route_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_route_callback); + SignalData *route_changed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_route_callback); + SignalData *route_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_route_callback); + in_addr_t network = + nmtst_inet4_from_string("192.0.2.5"); /* from 192.0.2.0/24 (TEST-NET-1) (rfc5737) */ + int plen = 32; + int metric = 22987; + int mss = 1000; + + /* No routes initially */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + + /* add the first route */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + INADDR_ANY, + 0, + metric, + mss); + accept_signal(route_added); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Deleting route with metric 0 does nothing */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + ensure_no_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* add the second route */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + INADDR_ANY, + 0, + 0, + mss); + accept_signal(route_added); + + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete route with metric 0 */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + accept_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete route with metric 0 again (we expect nothing to happen) */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + ensure_no_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete the other route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + accept_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +static void +test_ip4_route(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData * route_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_route_callback); + SignalData * route_changed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_route_callback); + SignalData * route_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_route_callback); + GPtrArray * routes; + NMPlatformIP4Route rts[3]; + in_addr_t network; + guint8 plen = 24; + in_addr_t gateway; + /* Choose a high metric so that we hopefully don't conflict. */ + int metric = 22986; + int mss = 1000; + + inet_pton(AF_INET, "192.0.3.0", &network); + inet_pton(AF_INET, "198.51.100.1", &gateway); + + /* Add route to gateway */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway, + 32, + INADDR_ANY, + 0, + metric, + mss); + accept_signal(route_added); + + /* Add route */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + 0, + metric, + mss); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + accept_signal(route_added); + + /* Add route again */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + 0, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Add default route */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, 0, 0, metric, 0); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway, + 0, + metric, + mss); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, 0, 0, metric, 0); + accept_signal(route_added); + + /* Add default route again */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway, + 0, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Test route listing */ + routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + memset(rts, 0, sizeof(rts)); + rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[0].network = gateway; + rts[0].plen = 32; + rts[0].ifindex = ifindex; + rts[0].gateway = INADDR_ANY; + rts[0].metric = metric; + rts[0].mss = mss; + rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK); + rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[1].network = network; + rts[1].plen = plen; + rts[1].ifindex = ifindex; + rts[1].gateway = gateway; + rts[1].metric = metric; + rts[1].mss = mss; + rts[1].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE); + rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[2].network = 0; + rts[2].plen = 0; + rts[2].ifindex = ifindex; + rts[2].gateway = gateway; + rts[2].metric = metric; + rts[2].mss = mss; + rts[2].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE); + g_assert_cmpint(routes->len, ==, 3); + nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts, + routes->len, + TRUE); + g_ptr_array_unref(routes); + + /* Remove route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + accept_signal(route_removed); + + /* Remove route again */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + + /* Remove default route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, 0, 0, metric)); + accept_signal(route_removed); + + /* Remove route to gateway */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, gateway, 32, metric)); + accept_signal(route_removed); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +static void +test_ip6_route(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData * route_added = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_route_callback); + SignalData * route_changed = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip6_route_callback); + SignalData * route_removed = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_route_callback); + GPtrArray * routes; + NMPlatformIP6Route rts[3]; + struct in6_addr network; + guint8 plen = 64; + struct in6_addr gateway, pref_src; + /* Choose a high metric so that we hopefully don't conflict. */ + const int metric = 22987; + int mss = 1000; + + inet_pton(AF_INET6, "2001:db8:a:b:0:0:0:0", &network); + inet_pton(AF_INET6, "2001:db8:c:d:1:2:3:4", &gateway); + inet_pton(AF_INET6, "::42", &pref_src); + + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + ifindex, + pref_src, + 128, + in6addr_any, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0)); + accept_signals(route_added, 0, 3); + + _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 200, ifindex, 1, &pref_src); + + /* Add route to gateway */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway, + 128, + in6addr_any, + in6addr_any, + metric, + mss); + accept_signals(route_added, 0, 3); + + /* Add route */ + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + pref_src, + metric, + mss); + g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + accept_signal(route_added); + + /* Add route again */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + pref_src, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Add default route */ + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0)); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway, + in6addr_any, + metric, + mss); + g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0)); + accept_signal(route_added); + + /* Add default route again */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway, + in6addr_any, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Test route listing */ + routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + memset(rts, 0, sizeof(rts)); + rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[0].network = gateway; + rts[0].plen = 128; + rts[0].ifindex = ifindex; + rts[0].gateway = in6addr_any; + rts[0].pref_src = in6addr_any; + rts[0].metric = metric; + rts[0].mss = mss; + rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[1].network = network; + rts[1].plen = plen; + rts[1].ifindex = ifindex; + rts[1].gateway = gateway; + rts[1].pref_src = pref_src; + rts[1].metric = metric; + rts[1].mss = mss; + rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[2].network = in6addr_any; + rts[2].plen = 0; + rts[2].ifindex = ifindex; + rts[2].gateway = gateway; + rts[2].pref_src = in6addr_any; + rts[2].metric = metric; + rts[2].mss = mss; + g_assert_cmpint(routes->len, ==, 3); + nmtst_platform_ip6_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts, + routes->len, + TRUE); + g_ptr_array_unref(routes); + + /* Remove route */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + accept_signal(route_removed); + + /* Remove route again */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + + /* Remove default route */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric)); + accept_signal(route_removed); + + /* Remove route to gateway */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, gateway, 128, metric)); + accept_signal(route_removed); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_route_get(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + in_addr_t a; + int result; + nm_auto_nmpobj NMPObject *route = NULL; + const NMPlatformIP4Route *r; + + nmtstp_run_command_check("ip route add 1.2.3.0/24 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.0"), + 24, + 0, + 0)) + break; + }); + + a = nmtst_inet4_from_string("1.2.3.1"); + result = nm_platform_ip_route_get(NM_PLATFORM_GET, + AF_INET, + &a, + nmtst_get_rand_uint32() % 2 ? 0 : ifindex, + &route); + + g_assert(NMTST_NM_ERR_SUCCESS(result)); + g_assert(NMP_OBJECT_GET_TYPE(route) == NMP_OBJECT_TYPE_IP4_ROUTE); + g_assert(!NMP_OBJECT_IS_STACKINIT(route)); + g_assert(route->parent._ref_count == 1); + r = NMP_OBJECT_CAST_IP4_ROUTE(route); + g_assert(NM_FLAGS_HAS(r->r_rtm_flags, RTM_F_CLONED)); + g_assert(r->ifindex == ifindex); + g_assert(r->network == a); + g_assert(r->plen == 32); + + nmtstp_run_command_check("ip route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip4_zero_gateway(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + + nmtstp_run_command_check("ip route add 1.2.3.1/32 via 0.0.0.0 dev %s", DEVICE_NAME); + nmtstp_run_command_check("ip route add 1.2.3.2/32 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.1"), + 32, + 0, + 0) + && nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.2"), + 32, + 0, + 0)) + break; + }); + + nmtstp_run_command_check("ip route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip4_route_options(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + gs_unref_ptrarray GPtrArray *routes = NULL; +#define RTS_MAX 3 + NMPlatformIP4Route rts_add[RTS_MAX] = {}; + NMPlatformIP4Route rts_cmp[RTS_MAX] = {}; + NMPlatformIP4Address addr[1] = {}; + guint i; + guint rts_n = 0; + guint addr_n = 0; + + switch (TEST_IDX) { + case 1: + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.16.1.0"), + .plen = 24, + .metric = 20, + .tos = 0x28, + .window = 10000, + .cwnd = 16, + .initcwnd = 30, + .initrwnd = 50, + .mtu = 1350, + .lock_cwnd = TRUE, + }); + break; + case 2: + addr[addr_n++] = ((NMPlatformIP4Address){ + .ifindex = IFINDEX, + .address = nmtst_inet4_from_string("172.16.1.5"), + .peer_address = nmtst_inet4_from_string("172.16.1.5"), + .plen = 24, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.17.1.0"), + .gateway = nmtst_inet4_from_string("172.16.1.1"), + .plen = 24, + .metric = 20, + }); + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.19.1.0"), + .gateway = nmtst_inet4_from_string("172.18.1.1"), + .r_rtm_flags = RTNH_F_ONLINK, + .plen = 24, + .metric = 20, + }); + break; + default: + g_assert_not_reached(); + break; + } + g_assert(rts_n <= G_N_ELEMENTS(rts_add)); + g_assert(addr_n <= G_N_ELEMENTS(addr)); + + for (i = 0; i < addr_n; i++) { + const NMPlatformIP4Address *a = &addr[i]; + + g_assert(a->ifindex == IFINDEX); + g_assert(nm_platform_ip4_address_add( + NM_PLATFORM_GET, + a->ifindex, + a->address, + a->plen, + a->peer_address, + nm_platform_ip4_broadcast_address_create(a->address, a->plen), + a->lifetime, + a->preferred, + a->n_ifa_flags, + a->label)); + if (a->peer_address == a->address) + _wait_for_ipv4_addr_device_route(NM_PLATFORM_GET, 200, a->ifindex, a->address, a->plen); + } + + for (i = 0; i < rts_n; i++) + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_ip4_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]))); + + for (i = 0; i < rts_n; i++) { + rts_cmp[i] = rts_add[i]; + nm_platform_ip_route_normalize(AF_INET, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i])); + } + + routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, IFINDEX); + g_assert_cmpint(routes->len, ==, rts_n); + nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts_cmp, + routes->len, + TRUE); + + for (i = 0; i < rts_n; i++) { + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, + IFINDEX, + rts_add[i].network, + rts_add[i].plen, + rts_add[i].metric)); + } +#undef RTS_MAX +} + +static void +test_ip6_route_get(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + const struct in6_addr *a; + int result; + nm_auto_nmpobj NMPObject *route = NULL; + const NMPlatformIP6Route *r; + + nmtstp_run_command_check("ip -6 route add fd01:abcd::/64 via fe80::99 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip6_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet6_from_string("fd01:abcd::"), + 64, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + NULL, + 0)) + break; + }); + + a = nmtst_inet6_from_string("fd01:abcd::42"); + result = nm_platform_ip_route_get(NM_PLATFORM_GET, + AF_INET6, + a, + nmtst_get_rand_uint32() % 2 ? 0 : ifindex, + &route); + + g_assert(NMTST_NM_ERR_SUCCESS(result)); + g_assert(NMP_OBJECT_GET_TYPE(route) == NMP_OBJECT_TYPE_IP6_ROUTE); + g_assert(!NMP_OBJECT_IS_STACKINIT(route)); + g_assert(route->parent._ref_count == 1); + r = NMP_OBJECT_CAST_IP6_ROUTE(route); + g_assert(r->ifindex == ifindex); + nmtst_assert_ip6_address(&r->network, "fd01:abcd::42"); + g_assert_cmpint(r->plen, ==, 128); + nmtst_assert_ip6_address(&r->gateway, "fe80::99"); + + nmtstp_run_command_check("ip -6 route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip6_route_options(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + gs_unref_ptrarray GPtrArray *routes = NULL; +#define RTS_MAX 3 + NMPlatformIP6Route rts_add[RTS_MAX] = {}; + NMPlatformIP6Route rts_cmp[RTS_MAX] = {}; + NMPlatformIP6Address addr[1] = {}; + struct in6_addr addr_in6[G_N_ELEMENTS(addr)] = {}; + guint rts_n = 0; + guint addr_n = 0; + guint i; + + switch (TEST_IDX) { + case 1: + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = *nmtst_inet6_from_string("2001:db8:a:b:0:0:0:0"), + .plen = 64, + .gateway = in6addr_any, + .metric = 1024, + .window = 20000, + .cwnd = 8, + .initcwnd = 22, + .initrwnd = 33, + .mtu = 1300, + .lock_mtu = TRUE, + }); + break; + case 2: + addr[addr_n++] = ((NMPlatformIP6Address){ + .ifindex = IFINDEX, + .address = *nmtst_inet6_from_string("2000::2"), + .plen = 128, + .peer_address = in6addr_any, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = *nmtst_inet6_from_string("1010::1"), + .plen = 128, + .gateway = in6addr_any, + .metric = 256, + .pref_src = *nmtst_inet6_from_string("2000::2"), + }); + break; + case 3: + addr[addr_n++] = ((NMPlatformIP6Address){ + .ifindex = IFINDEX, + .address = *nmtst_inet6_from_string("2001:db8:8086::5"), + .plen = 128, + .peer_address = in6addr_any, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER), + .network = *nmtst_inet6_from_string("2001:db8:8086::"), + .plen = 110, + .metric = 10021, + .mss = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER), + .network = *nmtst_inet6_from_string("2001:db8:abad:c0de::"), + .plen = 64, + .gateway = *nmtst_inet6_from_string("2001:db8:8086::1"), + .metric = 21, + .mss = 0, + }); + break; + default: + g_assert_not_reached(); + } + g_assert(rts_n <= G_N_ELEMENTS(rts_add)); + g_assert(addr_n <= G_N_ELEMENTS(addr)); + + for (i = 0; i < addr_n; i++) { + g_assert(addr[i].ifindex == IFINDEX); + addr_in6[i] = addr[i].address; + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + IFINDEX, + addr[i].address, + addr[i].plen, + addr[i].peer_address, + addr[i].lifetime, + addr[i].preferred, + addr[i].n_ifa_flags)); + } + _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6); + + for (i = 0; i < rts_n; i++) + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_ip6_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]))); + + for (i = 0; i < rts_n; i++) { + rts_cmp[i] = rts_add[i]; + nm_platform_ip_route_normalize(AF_INET6, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i])); + } + + routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, IFINDEX); + g_assert_cmpint(routes->len, ==, rts_n); + nmtst_platform_ip6_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts_cmp, + routes->len, + TRUE); + + for (i = 0; i < rts_n; i++) { + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, + IFINDEX, + rts_add[i].network, + rts_add[i].plen, + rts_add[i].metric)); + } + + for (i = 0; i < addr_n; i++) { + nmtstp_ip6_address_del(NM_PLATFORM_GET, EX, IFINDEX, rts_add[i].network, rts_add[i].plen); + } +#undef RTS_MAX +} + +/*****************************************************************************/ + +static void +test_ip(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + guint i, j, k; + const NMPlatformLink *l; + char ifname[IFNAMSIZ]; + char ifname2[IFNAMSIZ]; + char s1[NM_UTILS_INET_ADDRSTRLEN]; + NMPlatform * platform = NM_PLATFORM_GET; + const int EX_ = -1; + struct { + int ifindex; + } iface_data[10] = { + {0}, + }; + int order_idx[G_N_ELEMENTS(iface_data)] = {0}; + guint order_len; + guint try; + + for (i = 0; i < G_N_ELEMENTS(iface_data); i++) { + nm_sprintf_buf(ifname, "v%02u", i); + nm_sprintf_buf(ifname2, "w%02u", i); + + g_assert(!nm_platform_link_get_by_ifname(platform, ifname)); + g_assert(!nm_platform_link_get_by_ifname(platform, ifname2)); + l = nmtstp_link_veth_add(platform, EX_, ifname, ifname2); + iface_data[i].ifindex = l->ifindex; + + nmtstp_link_set_updown(platform, EX_, iface_data[i].ifindex, TRUE); + nmtstp_link_set_updown(platform, + EX_, + nmtstp_link_get(platform, -1, ifname2)->ifindex, + TRUE); + + nm_sprintf_buf(s1, "192.168.7.%d", 100 + i); + nmtstp_ip4_address_add(platform, + EX_, + iface_data[i].ifindex, + nmtst_inet4_from_string(s1), + 24, + nmtst_inet4_from_string(s1), + 3600, + 3600, + 0, + NULL); + } + + order_len = 0; + for (try = 0; try < 5 * G_N_ELEMENTS(order_idx); try++) { + NMPObject o; + NMPlatformIP4Route * r; + guint idx; + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + + nmp_object_stackinit(&o, NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + r = NMP_OBJECT_CAST_IP4_ROUTE(&o); + r->network = nmtst_inet4_from_string("192.168.9.0"); + r->plen = 24; + r->metric = 109; + + if (order_len == 0 + || (order_len < G_N_ELEMENTS(order_idx) && nmtst_get_rand_uint32() % 2)) { +again_find_idx: + idx = nmtst_get_rand_uint32() % G_N_ELEMENTS(iface_data); + for (i = 0; i < order_len; i++) { + if (order_idx[i] == idx) + goto again_find_idx; + } + order_idx[order_len++] = idx; + + r->ifindex = iface_data[idx].ifindex; + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip4_route_add(platform, NMP_NLM_FLAG_APPEND, r))); + } else { + i = nmtst_get_rand_uint32() % order_len; + idx = order_idx[i]; + for (i++; i < order_len; i++) + order_idx[i - 1] = order_idx[i]; + order_len--; + + r->ifindex = iface_data[idx].ifindex; + g_assert(nm_platform_object_delete(platform, &o)); + } + + head_entry = + nm_platform_lookup(platform, + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE)); + for (j = 0; j < G_N_ELEMENTS(iface_data); j++) { + gboolean has; + NMDedupMultiIter iter; + const NMPObject *o_cached; + + has = FALSE; + for (k = 0; k < order_len; k++) { + if (order_idx[k] == j) { + g_assert(!has); + has = TRUE; + } + } + + nmp_cache_iter_for_each (&iter, head_entry, &o_cached) { + const NMPlatformIP4Route *r_cached = NMP_OBJECT_CAST_IP4_ROUTE(o_cached); + + if (r_cached->ifindex != iface_data[j].ifindex || r_cached->metric != 109) + continue; + + g_assert(has); + has = FALSE; + } + g_assert(!has); + } + } + + for (i = 0; i < G_N_ELEMENTS(iface_data); i++) + g_assert(nm_platform_link_delete(platform, iface_data[i].ifindex)); + + (void) TEST_IDX; + (void) IFINDEX; +} + +/*****************************************************************************/ + +#define FRA_SUPPRESS_IFGROUP 13 +#define FRA_SUPPRESS_PREFIXLEN 14 +#define FRA_L3MDEV 19 +#define FRA_UID_RANGE 20 +#define FRA_PROTOCOL 21 +#define FRA_IP_PROTO 22 +#define FRA_SPORT_RANGE 23 +#define FRA_DPORT_RANGE 24 + +static const NMPObject * +_rule_find_by_priority(NMPlatform *platform, guint32 priority) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + const NMPObject * obj = NULL; + NMPLookup lookup; + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + if (NMP_OBJECT_CAST_ROUTING_RULE(o)->priority != priority) + continue; + g_assert(!obj); + obj = o; + } + return obj; +} + +static const NMPObject * +_rule_check_kernel_support_one(NMPlatform *platform, const NMPlatformRoutingRule *rr) +{ + nm_auto_nmpobj const NMPObject *obj = NULL; + int r; + + g_assert(!_rule_find_by_priority(platform, rr->priority)); + + r = nm_platform_routing_rule_add(platform, NMP_NLM_FLAG_ADD, rr); + g_assert_cmpint(r, ==, 0); + + obj = nmp_object_ref(_rule_find_by_priority(platform, rr->priority)); + g_assert(obj); + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + g_assert(!_rule_find_by_priority(platform, rr->priority)); + + return g_steal_pointer(&obj); +} + +static gboolean +_rule_check_kernel_support(NMPlatform *platform, int attribute) +{ + static int support[] = { + [FRA_SUPPRESS_IFGROUP] = 0, + [FRA_SUPPRESS_PREFIXLEN] = 0, + [FRA_L3MDEV] = 0, + [FRA_UID_RANGE] = 0, + [FRA_PROTOCOL] = 0, + [FRA_IP_PROTO] = 0, + [FRA_SPORT_RANGE] = 0, + [FRA_DPORT_RANGE] = 0, + }; + const guint32 PROBE_PRORITY = 12033; + gboolean sup; + int i; + + g_assert(NM_IS_PLATFORM(platform)); + + if (attribute == -1) { + for (i = 0; i < G_N_ELEMENTS(support); i++) { + if (support[i] < 0) { + /* indicate that some test was skipped. */ + return FALSE; + } + } + return TRUE; + } + + g_assert(attribute >= 0 && attribute < G_N_ELEMENTS(support)); + + if (support[attribute] != 0) + return support[attribute] >= 0; + + switch (attribute) { + case FRA_SUPPRESS_IFGROUP: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .suppress_ifgroup_inverse = ~((guint32) 1245), + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->suppress_prefixlen_inverse + == rr.suppress_ifgroup_inverse; + break; + } + case FRA_SUPPRESS_PREFIXLEN: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .suppress_prefixlen_inverse = ~((guint32) 1245), + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->suppress_prefixlen_inverse + == rr.suppress_prefixlen_inverse; + break; + } + case FRA_L3MDEV: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .l3mdev = TRUE, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->l3mdev != 0; + break; + } + case FRA_UID_RANGE: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .uid_range = + { + .start = 0, + .end = 0, + }, + .uid_range_has = TRUE, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->uid_range_has; + break; + } + case FRA_PROTOCOL: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .protocol = 30, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->protocol == 30; + break; + } + case FRA_IP_PROTO: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .ip_proto = 30, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->ip_proto == 30; + break; + } + case FRA_SPORT_RANGE: + case FRA_DPORT_RANGE: + /* these were added at the same time as FRA_IP_PROTO. */ + sup = _rule_check_kernel_support(platform, FRA_IP_PROTO); + break; + default: + g_assert_not_reached(); + return FALSE; + } + + support[attribute] = sup ? 1 : -1; + + _LOGD("kernel support for routing rule attribute #%d %s", + attribute, + sup ? "detected" : "not detected"); + return sup; +} + +static const NMPObject * +_platform_has_routing_rule(NMPlatform *platform, const NMPObject *obj) +{ + const NMPObject *o; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NMP_OBJECT_IS_VALID(obj)); + g_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + + o = nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj); + if (o) + g_assert(nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj), + NMP_OBJECT_CAST_ROUTING_RULE(o), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); + + return o; +} + +static guint32 +_rr_rand_choose_u32(guint32 p) +{ + /* mostly, we just return zero. We want that each rule only has few + * fields set -- having most fields at zero. */ + if ((p % 10000u) < 7500u) + return 0; + + /* give 0xFFFFFFFFu extra probability. */ + if ((p % 10000u) < 8250u) + return 0xFFFFFFFFu; + + /* choose a small number. */ + if ((p % 10000u) < 9125u) + return (~p) % 10; + + /* finally, full random number. */ + return ~p; +} + +#define _rr_rand_choose_u8(p) ((guint8) _rr_rand_choose_u32((p))) + +static const NMPObject * +_rule_create_random(NMPlatform *platform) +{ + NMPObject * obj; + NMPlatformRoutingRule *rr; + guint32 p; + int addr_size; + guint i; + char saddr[NM_UTILS_INET_ADDRSTRLEN]; + static struct { + guint32 uid; + guint32 euid; + bool initialized; + } uids; + + if (G_UNLIKELY(!uids.initialized)) { + uids.uid = getuid(); + uids.euid = geteuid(); + uids.initialized = TRUE; + } + + obj = nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, NULL); + rr = NMP_OBJECT_CAST_ROUTING_RULE(obj); + + rr->addr_family = nmtst_rand_select(AF_INET, AF_INET6); + + addr_size = nm_utils_addr_family_to_size(rr->addr_family); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 50) + rr->priority = 10000 + ((~p) % 20u); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 40) + nm_sprintf_buf(rr->iifname, "t-iif-%u", (~p) % 20); + else if ((p % 1000u) < 80) + nm_sprintf_buf(rr->iifname, "%s", DEVICE_NAME); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 40) + nm_sprintf_buf(rr->oifname, "t-oif-%d", (~p) % 20); + else if ((p % 1000u) < 80) + nm_sprintf_buf(rr->oifname, "%s", DEVICE_NAME); + + for (i = 0; i < 2; i++) { + NMIPAddr *p_addr = i ? &rr->src : &rr->dst; + guint8 * p_len = i ? &rr->src_len : &rr->dst_len; + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 100) { + /* if we set src_len/dst_len to zero, the src/dst is actually ignored. + * + * For fuzzying, still set the address. It shall have no further effect. + * */ + *p_len = (~p) % (addr_size * 8 + 1); + p = nmtst_get_rand_uint32(); + if ((p % 3u) == 0) { + if (rr->addr_family == AF_INET) + p_addr->addr4 = + nmtst_inet4_from_string(nm_sprintf_buf(saddr, "192.192.5.%u", (~p) % 256u)); + else + p_addr->addr6 = *nmtst_inet6_from_string( + nm_sprintf_buf(saddr, "1:2:3:4::f:%02x", (~p) % 256u)); + } else if ((p % 3u) == 1) + nmtst_rand_buf(NULL, p_addr, addr_size); + } + } + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 50) + rr->tun_id = 10000 + ((~p) % 20); + +again_action: + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 500) + rr->action = FR_ACT_UNSPEC; + else if ((p % 1000u) < 750) + rr->action = (~p) % 12u; + else + rr->action = (~p) % 0x100u; + + rr->priority = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (rr->action == FR_ACT_GOTO && rr->priority == G_MAXINT32) + goto again_action; + + p = nmtst_get_rand_uint32(); + if ((p % 10000u) < 100) + rr->goto_target = rr->priority + 1; + else + rr->goto_target = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + if (rr->action == FR_ACT_GOTO && rr->goto_target <= rr->priority) + goto again_action; + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 25) { + if (_rule_check_kernel_support(platform, FRA_L3MDEV)) { + rr->l3mdev = TRUE; + rr->table = RT_TABLE_UNSPEC; + } + } + +again_table: + if (!rr->l3mdev) { + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 700) + rr->table = RT_TABLE_UNSPEC; + else if ((p % 1000u) < 850) + rr->table = RT_TABLE_MAIN; + else + rr->table = 10000 + ((~p) % 10); + if (rr->action == FR_ACT_TO_TBL && rr->table == RT_TABLE_UNSPEC) + goto again_table; + } + + rr->fwmark = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + rr->fwmask = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + rr->flow = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_PROTOCOL)) + rr->protocol = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + +#define IPTOS_TOS_MASK 0x1E + +again_tos: + rr->tos = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + if (rr->addr_family == AF_INET && rr->tos & ~IPTOS_TOS_MASK) + goto again_tos; + + if (_rule_check_kernel_support(platform, FRA_IP_PROTO)) + rr->ip_proto = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_SUPPRESS_PREFIXLEN)) + rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_SUPPRESS_IFGROUP)) + rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_UID_RANGE)) { + p = nmtst_get_rand_uint32(); + rr->uid_range_has = (p % 10000u) < 200; + } + +again_uid_range: + rr->uid_range.start = nmtst_rand_select(0u, uids.uid, uids.euid); + rr->uid_range.end = nmtst_rand_select(0u, uids.uid, uids.euid); + if (rr->uid_range_has) { + if (rr->uid_range.end < rr->uid_range.start) + NM_SWAP(&rr->uid_range.start, &rr->uid_range.end); + if (rr->uid_range.start == ((guint32) -1) || rr->uid_range.end == ((guint32) -1)) + goto again_uid_range; + } + + for (i = 0; i < 2; i++) { + NMFibRulePortRange *range = i ? &rr->sport_range : &rr->dport_range; + int attribute = i ? FRA_SPORT_RANGE : FRA_DPORT_RANGE; + + if (!_rule_check_kernel_support(platform, attribute)) + continue; + + p = nmtst_get_rand_uint32(); + if ((p % 10000u) < 300) { + while (range->start == 0) { + p = p ^ nmtst_get_rand_uint32(); + range->start = nmtst_rand_select(1u, 0xFFFEu, ((p) % 0xFFFEu) + 1); + range->end = + nmtst_rand_select(1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start); + if (range->end < range->start) + NM_SWAP(&range->start, &range->end); + } + } + } + + p = nmtst_get_rand_uint32() % 1000u; + if (p < 100) + rr->flags |= FIB_RULE_INVERT; + + return obj; +} + +static gboolean +_rule_fuzzy_equal(const NMPObject *obj, const NMPObject *obj_comp, int op_type) +{ + const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(obj); + NMPlatformRoutingRule rr_co = *NMP_OBJECT_CAST_ROUTING_RULE(obj_comp); + + switch (op_type) { + case RTM_NEWRULE: + /* when adding rules with RTM_NEWRULE, kernel checks whether an existing + * rule already exists and may fail with EEXIST. This check has issues + * and reject legitimate rules (rh#1686075). + * + * Work around that. */ + if (rr->src_len == 0) + rr_co.src_len = 0; + if (rr->dst_len == 0) + rr_co.dst_len = 0; + if (rr->flow == 0) + rr_co.flow = 0; + if (rr->tos == 0) + rr_co.tos = 0; + rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse; + rr_co.suppress_ifgroup_inverse = rr->suppress_ifgroup_inverse; + if (!NM_FLAGS_HAS(rr->flags, FIB_RULE_INVERT)) + rr_co.flags &= ~((guint32) FIB_RULE_INVERT); + else + rr_co.flags |= ((guint32) FIB_RULE_INVERT); + break; + case RTM_DELRULE: + /* when deleting a rule with RTM_DELRULE, kernel tries to find the + * candidate to delete. It might delete the wrong rule (rh#1685816). */ + if (rr->action == FR_ACT_UNSPEC) + rr_co.action = FR_ACT_UNSPEC; + if (rr->iifname[0] == '\0') + rr_co.iifname[0] = '\0'; + if (rr->oifname[0] == '\0') + rr_co.oifname[0] = '\0'; + if (rr->src_len == 0) + rr_co.src_len = 0; + if (rr->dst_len == 0) + rr_co.dst_len = 0; + if (rr->tun_id == 0) + rr_co.tun_id = 0; + if (rr->fwmark == 0) + rr_co.fwmark = 0; + if (rr->fwmask == 0) + rr_co.fwmask = 0; + if (rr->flow == 0) + rr_co.flow = 0; + if (rr->protocol == 0) + rr_co.protocol = 0; + if (rr->table == RT_TABLE_UNSPEC) + rr_co.table = RT_TABLE_UNSPEC; + if (rr->l3mdev == 0) + rr_co.l3mdev = 0; + if (rr->tos == 0) + rr_co.tos = 0; + if (rr->ip_proto == 0) + rr_co.ip_proto = 0; + rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse; + if (rr->suppress_ifgroup_inverse == 0) + rr_co.suppress_ifgroup_inverse = 0; + if (!rr->uid_range_has) + rr_co.uid_range_has = FALSE; + if (rr->sport_range.start == 0 && rr->sport_range.end == 0) { + rr_co.sport_range.start = 0; + rr_co.sport_range.end = 0; + } + if (rr->dport_range.start == 0 && rr->dport_range.end == 0) { + rr_co.dport_range.start = 0; + rr_co.dport_range.end = 0; + } + if (!NM_FLAGS_HAS(rr->flags, FIB_RULE_INVERT)) + rr_co.flags &= ~((guint32) FIB_RULE_INVERT); + else + rr_co.flags |= ((guint32) FIB_RULE_INVERT); + break; + default: + g_assert_not_reached(); + break; + } + + return nm_platform_routing_rule_cmp(rr, &rr_co, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0; +} + +static void +test_rule(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const gboolean TEST_SYNC = (TEST_IDX == 4); + gs_unref_ptrarray GPtrArray *objs = NULL; + gs_unref_ptrarray GPtrArray *objs_initial = NULL; + NMPlatform * platform = NM_PLATFORM_GET; + guint i, j, n; + int r; + gboolean had_an_issue_exist = FALSE; + + nm_platform_process_events(platform); + + objs_initial = nmtstp_platform_routing_rules_get_all(platform, AF_UNSPEC); + g_assert(objs_initial); + g_assert_cmpint(objs_initial->len, ==, 5); + + nmtstp_run_command_check("ip rule add table 766"); + nm_platform_process_events(platform); + + for (i = 6; i > 0; i--) { + gs_unref_ptrarray GPtrArray *objs_extern = NULL; + const NMPObject * obj; + + objs_extern = nmtstp_platform_routing_rules_get_all(platform, AF_UNSPEC); + + g_assert(objs_extern); + g_assert_cmpint(objs_extern->len, ==, i); + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs_extern->pdata, NULL, sizeof(gpointer), objs_extern->len); + + obj = objs_extern->pdata[0]; + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + g_assert(!_platform_has_routing_rule(platform, obj)); + } + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), ==, 0); + +#define RR(...) \ + nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, \ + (const NMPlatformObject *) &((NMPlatformRoutingRule){__VA_ARGS__})) + + objs = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 10, )); + + g_ptr_array_add( + objs, + RR(.addr_family = AF_INET, .priority = 400, .action = FR_ACT_GOTO, .goto_target = 10000, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, )); + + g_ptr_array_add(objs, + RR(.addr_family = AF_INET6, .action = FR_ACT_TO_TBL, .table = RT_TABLE_MAIN, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 30, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 50, .iifname = "t-iif-1", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 50, .iifname = "t-oif-1", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 50, .iifname = "t-oif-2", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 51, .iifname = DEVICE_NAME, )); + + if (TEST_IDX == 1) { + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .table = 10000, )); + } + + if (TEST_IDX != 1) { + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + g_ptr_array_set_size(objs, nmtst_get_rand_uint32() % (objs->len + 1)); + } + + n = (TEST_IDX != 1) ? nmtst_get_rand_uint32() % 50u : 0u; + for (i = 0; i < n; i++) { + nm_auto_nmpobj const NMPObject *o = NULL; + guint try = 0; + +again: + o = _rule_create_random(platform); + for (j = 0; j < objs->len; j++) { + if (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(o), + NMP_OBJECT_CAST_ROUTING_RULE(objs->pdata[j]), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0) { + try++; + g_assert(try < 200); + nm_clear_pointer(&o, nmp_object_unref); + goto again; + } + } + g_ptr_array_add(objs, (gpointer) g_steal_pointer(&o)); + } + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + + if (TEST_SYNC) { + gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new(NULL, NULL); + nm_auto_unref_rules_manager NMPRulesManager *rules_manager = + nmp_rules_manager_new(platform); + gs_unref_ptrarray GPtrArray *objs_sync = NULL; + gconstpointer USER_TAG_1 = &platform; + gconstpointer USER_TAG_2 = &unique_priorities; + + objs_sync = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + /* ensure that priorities are unique. Otherwise, it confuses the test, because + * kernel may wrongly be unable to add/delete routes based on a wrong match + * (rh#1685816, rh#1685816). */ + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + guint32 prio = NMP_OBJECT_CAST_ROUTING_RULE(obj)->priority; + + if (!NM_IN_SET(prio, 0, 32766, 32767) + && !g_hash_table_contains(unique_priorities, GUINT_TO_POINTER(prio))) { + g_hash_table_add(unique_priorities, GUINT_TO_POINTER(prio)); + g_ptr_array_add(objs_sync, (gpointer) nmp_object_ref(obj)); + } + } + + for (i = 0; i < objs_sync->len; i++) { + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + 1, + USER_TAG_1, + NULL); + if (nmtst_get_rand_bool()) { + /* this has no effect, because a negative priority (of same absolute value) + * has lower priority than the positive priority above. */ + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -1, + USER_TAG_2, + NULL); + } + if (nmtst_get_rand_uint32() % objs_sync->len == 0) { + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + i + 1); + } + } + + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_sync->len); + + for (i = 0; i < objs_sync->len; i++) { + switch (nmtst_get_rand_uint32() % 3) { + case 0: + nmp_rules_manager_untrack(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + USER_TAG_1); + nmp_rules_manager_untrack(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + USER_TAG_1); + break; + case 1: + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -1, + USER_TAG_1, + NULL); + break; + case 2: + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -2, + USER_TAG_2, + NULL); + break; + } + if (nmtst_get_rand_uint32() % objs_sync->len == 0) { + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_sync->len - i - 1); + } + } + + nmp_rules_manager_sync(rules_manager, FALSE); + + } else { + for (i = 0; i < objs->len;) { + const NMPObject *obj = objs->pdata[i]; + + for (j = 0; j < objs->len; j++) + g_assert((j < i) == (!!_platform_has_routing_rule(platform, objs->pdata[j]))); + + r = nm_platform_routing_rule_add(platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj)); + + if (r == -EEXIST) { + g_assert(!_platform_has_routing_rule(platform, obj)); + /* this should not happen, but there are bugs in kernel (rh#1686075). */ + for (j = 0; j < i; j++) { + const NMPObject *obj2 = objs->pdata[j]; + + g_assert(_platform_has_routing_rule(platform, obj2)); + + if (_rule_fuzzy_equal(obj, obj2, RTM_NEWRULE)) { + r = 0; + break; + } + } + if (r == 0) { + /* OK, the rule is shadowed by another rule, and kernel does not allow + * us to add this one (rh#1686075). Drop this from the test. */ + g_ptr_array_remove_index(objs, i); + had_an_issue_exist = TRUE; + continue; + } + } + + if (r != 0) { + NMPLookup lookup; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + + g_print(">>> failing... errno=%d, rule=%s\n", + r, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + char ch = ' '; + + if (NMP_OBJECT_CAST_ROUTING_RULE(o)->addr_family + == NMP_OBJECT_CAST_ROUTING_RULE(obj)->addr_family + && NMP_OBJECT_CAST_ROUTING_RULE(o)->priority + == NMP_OBJECT_CAST_ROUTING_RULE(obj)->priority) + ch = '*'; + g_print(">>> existing rule: %c %s\n", + ch, + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + } + + nmtstp_run_command_check("ip rule"); + nmtstp_run_command_check("ip -6 rule"); + g_assert_cmpint(r, ==, 0); + } + + g_assert(_platform_has_routing_rule(platform, obj)); + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + i + 1); + + i++; + } + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + + if (_LOGD_ENABLED()) { + nmtstp_run_command_check("ip rule"); + nmtstp_run_command_check("ip -6 rule"); + } + + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + const NMPObject *obj2; + + for (j = 0; j < objs->len; j++) + g_assert((j < i) == (!_platform_has_routing_rule(platform, objs->pdata[j]))); + + g_assert(_platform_has_routing_rule(platform, obj)); + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + obj2 = _platform_has_routing_rule(platform, obj); + + if (obj2) { + guint k; + + /* When deleting a rule, kernel does a fuzzy match, ignoring for example: + * - action, if it is FR_ACT_UNSPEC + * - iifname,oifname if it is unspecified + * rh#1685816 + * + * That means, we may have deleted the wrong rule. Which one? */ + k = i; + for (j = i + 1; j < objs->len; j++) { + if (!_platform_has_routing_rule(platform, objs->pdata[j])) { + g_assert_cmpint(k, ==, i); + k = j; + } + } + g_assert_cmpint(k, >, i); + + if (!_rule_fuzzy_equal(obj, objs->pdata[k], RTM_DELRULE)) { + g_print(">>> failing...\n"); + g_print(">>> no fuzzy match between: %s\n", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_print( + ">>> and: %s\n", + nmp_object_to_string(objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_assert_not_reached(); + } + + objs->pdata[i] = objs->pdata[k]; + objs->pdata[k] = (gpointer) obj; + obj2 = NULL; + } + + g_assert(!obj2); + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs->len - i - 1); + } + } + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), ==, 0); + + for (i = 0; i < objs_initial->len; i++) { + const NMPObject *obj = objs_initial->pdata[i]; + + for (j = 0; j < objs_initial->len; j++) + g_assert((j < i) == (!!_platform_has_routing_rule(platform, objs_initial->pdata[j]))); + + r = nm_platform_routing_rule_add(platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj)); + g_assert_cmpint(r, ==, 0); + } + for (j = 0; j < objs_initial->len; j++) + g_assert(_platform_has_routing_rule(platform, objs_initial->pdata[j])); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_initial->len); + + /* the tests passed as good as we could (as good as we implemented workarounds for them). + * Still, with this kernel, not all features were fully tested. Mark the test as skipped. */ + if (had_an_issue_exist) + g_test_skip("adding a rule failed with EEXIST although it should not (rh#1686075)"); + else if (!_rule_check_kernel_support(platform, -1)) + g_test_skip("some kernel features were not available and skipped for the test"); +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ +#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE) +#define add_test_func_data(testpath, test_func, arg) \ + nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE) + add_test_func("/route/ip4", test_ip4_route); + add_test_func("/route/ip6", test_ip6_route); + add_test_func("/route/ip4_metric0", test_ip4_route_metric0); + add_test_func_data("/route/ip4_options/1", test_ip4_route_options, GINT_TO_POINTER(1)); + if (nmtstp_is_root_test()) + add_test_func_data("/route/ip4_options/2", test_ip4_route_options, GINT_TO_POINTER(2)); + add_test_func_data("/route/ip6_options/1", test_ip6_route_options, GINT_TO_POINTER(1)); + add_test_func_data("/route/ip6_options/2", test_ip6_route_options, GINT_TO_POINTER(2)); + add_test_func_data("/route/ip6_options/3", test_ip6_route_options, GINT_TO_POINTER(3)); + + if (nmtstp_is_root_test()) { + add_test_func_data("/route/ip/1", test_ip, GINT_TO_POINTER(1)); + add_test_func("/route/ip4_route_get", test_ip4_route_get); + add_test_func("/route/ip6_route_get", test_ip6_route_get); + add_test_func("/route/ip4_zero_gateway", test_ip4_zero_gateway); + } + + if (nmtstp_is_root_test()) { + add_test_func_data("/route/rule/1", test_rule, GINT_TO_POINTER(1)); + add_test_func_data("/route/rule/2", test_rule, GINT_TO_POINTER(2)); + add_test_func_data("/route/rule/3", test_rule, GINT_TO_POINTER(3)); + add_test_func_data("/route/rule/4", test_rule, GINT_TO_POINTER(4)); + } +} diff --git a/src/core/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c new file mode 100644 index 0000000..c005b06 --- /dev/null +++ b/src/core/platform/tests/test-tc.c @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include + +#include "nm-test-utils-core.h" +#include "platform/nmp-object.h" +#include "nm-platform/nmp-netns.h" +#include "nm-platform/nm-platform-utils.h" +#include "test-common.h" + +static NMPObject * +qdisc_new(int ifindex, const char *kind, guint32 parent) +{ + NMPObject *obj; + + obj = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL); + obj->qdisc = (NMPlatformQdisc){ + .ifindex = ifindex, + .kind = kind, + .parent = parent, + }; + + return obj; +} + +static GPtrArray * +qdiscs_lookup(int ifindex) +{ + NMPLookup lookup; + + return nm_platform_lookup_clone(NM_PLATFORM_GET, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex), + NULL, + NULL); +} + +static void +test_qdisc1(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + nmtstp_run_command_check("tc qdisc add dev %s root sfq", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + g_ptr_array_add(known, qdisc_new(ifindex, "fq_codel", TC_H_ROOT)); + g_ptr_array_add(known, qdisc_new(ifindex, "ingress", TC_H_INGRESS)); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 2); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpstr(qdisc->kind, ==, "fq_codel"); + + obj = plat->pdata[1]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpint(qdisc->parent, ==, TC_H_INGRESS); + g_assert_cmpstr(qdisc->kind, ==, "ingress"); +} + +static void +test_qdisc_fq_codel(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "fq_codel", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8142 << 16, 0); + obj->qdisc.fq_codel.limit = 2048; + obj->qdisc.fq_codel.flows = 64; + obj->qdisc.fq_codel.quantum = 1000; + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 1); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "fq_codel"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8142 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->fq_codel.limit, ==, 2048); + g_assert_cmpint(qdisc->fq_codel.flows, ==, 64); + g_assert_cmpint(qdisc->fq_codel.quantum, ==, 1000); +} + +static void +test_qdisc_sfq(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "sfq", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8143 << 16, 0); + obj->qdisc.sfq.perturb_period = 10; + obj->qdisc.sfq.quantum = 1540; + obj->qdisc.sfq.flows = 256; + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 1); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "sfq"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->sfq.perturb_period, ==, 10); + g_assert_cmpint(qdisc->sfq.quantum, ==, 1540); + g_assert_cmpint(qdisc->sfq.flows, ==, 256); +} + +static void +test_qdisc_tbf(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "tbf", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8143 << 16, 0); + obj->qdisc.tbf.rate = 1000000; + obj->qdisc.tbf.burst = 2000; + obj->qdisc.tbf.limit = 3000; + g_ptr_array_add(known, obj); + + obj = qdisc_new(ifindex, "sfq", TC_H_MAKE(0x8143 << 16, 0)); + obj->qdisc.handle = TC_H_MAKE(0x8005 << 16, 0); + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 2); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "tbf"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->tbf.rate, ==, 1000000); + g_assert_cmpint(qdisc->tbf.burst, ==, 2000); + g_assert_cmpint(qdisc->tbf.limit, ==, 3000); + + obj = plat->pdata[1]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "sfq"); + g_assert_cmpint(qdisc->parent, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8005 << 16, 0)); +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + if (nmtstp_is_root_test()) { + nmtstp_env1_add_test_func("/link/qdisc/1", test_qdisc1, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/fq_codel", test_qdisc_fq_codel, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/sfq", test_qdisc_sfq, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/tbf", test_qdisc_tbf, TRUE); + } +} diff --git a/src/core/platform/wifi/nm-wifi-utils-nl80211.c b/src/core/platform/wifi/nm-wifi-utils-nl80211.c new file mode 100644 index 0000000..aae6af1 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils-nl80211.c @@ -0,0 +1,909 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + * Copyright (C) 2011 Intel Corporation. All rights reserved. + */ + +#include "nm-default.h" + +#include "nm-wifi-utils-nl80211.h" + +#include +#include +#include +#include +#include + +#include "nm-platform/nm-netlink.h" +#include "nm-wifi-utils-private.h" +#include "platform/nm-platform.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-utils.h" + +#define _NMLOG_PREFIX_NAME "wifi-nl80211" +#define _NMLOG_DOMAIN LOGD_PLATFORM | LOGD_WIFI +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _ifname_buf[IFNAMSIZ]; \ + const char *_ifname = \ + self ? nmp_utils_if_indextoname(self->parent.ifindex, _ifname_buf) : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + _ifname ?: NULL, \ + NULL, \ + "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +typedef struct { + NMWifiUtils parent; + struct nl_sock *nl_sock; + guint32 * freqs; + int id; + int num_freqs; + int phy; + bool can_wowlan : 1; +} NMWifiUtilsNl80211; + +typedef struct { + NMWifiUtilsClass parent; +} NMWifiUtilsNl80211Class; + +G_DEFINE_TYPE(NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS) + +static int +ack_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_STOP; +} + +static int +finish_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_SKIP; +} + +static int +error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *done = arg; + *done = err->error; + return NL_SKIP; +} + +static struct nl_msg * +_nl80211_alloc_msg(int id, int ifindex, int phy, guint32 cmd, guint32 flags) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc(); + genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); + if (phy != -1) + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, phy); + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +nl80211_alloc_msg(NMWifiUtilsNl80211 *self, guint32 cmd, guint32 flags) +{ + return _nl80211_alloc_msg(self->id, self->parent.ifindex, self->phy, cmd, flags); +} + +static int +nl80211_send_and_recv(NMWifiUtilsNl80211 *self, + struct nl_msg * msg, + int (*valid_handler)(struct nl_msg *, void *), + void *valid_data) +{ + int err; + int done = 0; + const struct nl_cb cb = { + .err_cb = error_handler, + .err_arg = &done, + .finish_cb = finish_handler, + .finish_arg = &done, + .ack_cb = ack_handler, + .ack_arg = &done, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg != NULL, -ENOMEM); + + err = nl_send_auto(self->nl_sock, msg); + if (err < 0) + return err; + + /* Loop until one of our NL callbacks says we're done; on success + * done will be 1, on error it will be < 0. + */ + while (!done) { + err = nl_recvmsgs(self->nl_sock, &cb); + if (err < 0 && err != -EAGAIN) { + /* Kernel scan list can change while we are dumping it, as new scan + * results from H/W can arrive. BSS info is assured to be consistent + * and we don't need consistent view of whole scan list. Hence do + * not warn on DUMP_INTR error for get scan command. + */ + if (err == -NME_NL_DUMP_INTR + && genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN) + break; + + _LOGW("nl_recvmsgs() error: (%d) %s", err, nm_strerror(err)); + break; + } + } + + if (err >= 0 && done < 0) + err = done; + return err; +} + +static void +dispose(GObject *object) +{ + NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211(object); + + nm_clear_g_free(&self->freqs); +} + +struct nl80211_iface_info { + NM80211Mode mode; + uint32_t freq; +}; + +static int +nl80211_iface_info_handler(struct nl_msg *msg, void *arg) +{ + struct nl80211_iface_info *info = arg; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (!tb[NL80211_ATTR_IFTYPE]) + return NL_SKIP; + + switch (nla_get_u32(tb[NL80211_ATTR_IFTYPE])) { + case NL80211_IFTYPE_ADHOC: + info->mode = NM_802_11_MODE_ADHOC; + break; + case NL80211_IFTYPE_AP: + info->mode = NM_802_11_MODE_AP; + break; + case NL80211_IFTYPE_STATION: + info->mode = NM_802_11_MODE_INFRA; + break; + case NL80211_IFTYPE_MESH_POINT: + info->mode = NM_802_11_MODE_MESH; + break; + } + + if (tb[NL80211_ATTR_WIPHY_FREQ] != NULL) + info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); + + return NL_SKIP; +} + +static NM80211Mode +wifi_nl80211_get_mode(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + struct nl80211_iface_info iface_info = { + .mode = NM_802_11_MODE_UNKNOWN, + }; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) + return NM_802_11_MODE_UNKNOWN; + + return iface_info.mode; +} + +static gboolean +wifi_nl80211_set_mode(NMWifiUtils *data, const NM80211Mode mode) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); + + switch (mode) { + case NM_802_11_MODE_INFRA: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION); + break; + case NM_802_11_MODE_ADHOC: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_ADHOC); + break; + case NM_802_11_MODE_AP: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP); + break; + case NM_802_11_MODE_MESH: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT); + break; + default: + g_assert_not_reached(); + } + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +wifi_nl80211_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_POWER_SAVE, 0); + NLA_PUT_U32(msg, + NL80211_ATTR_PS_STATE, + powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static int +nl80211_get_wake_on_wlan_handler(struct nl_msg *msg, void *arg) +{ + NMSettingWirelessWakeOnWLan *wowl = arg; + struct nlattr * attrs[NL80211_ATTR_MAX + 1]; + struct nlattr * trig[NUM_NL80211_WOWLAN_TRIG]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + nla_parse_arr(attrs, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) + return NL_SKIP; + + nla_parse_arr(trig, + nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + NULL); + + *wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE; + if (trig[NL80211_WOWLAN_TRIG_ANY]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY; + if (trig[NL80211_WOWLAN_TRIG_DISCONNECT]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT; + if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC; + if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE; + if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST; + if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE; + if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE; + if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) + *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP; + + return NL_SKIP; +} + +static NMSettingWirelessWakeOnWLan +wifi_nl80211_get_wake_on_wlan(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + NMSettingWirelessWakeOnWLan wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WOWLAN, 0); + + nl80211_send_and_recv(self, msg, nl80211_get_wake_on_wlan_handler, &wowl); + + return wowl; +} + +static gboolean +wifi_nl80211_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * triggers; + int err; + + if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) + return TRUE; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WOWLAN, 0); + + triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); + if (!triggers) + goto nla_put_failure; + + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); + if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); + + nla_nest_end(msg, triggers); + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static guint32 +wifi_nl80211_get_freq(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + struct nl80211_iface_info iface_info = {}; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) + return 0; + + return iface_info.freq; +} + +static guint32 +wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; + int i; + + for (i = 0; i < self->num_freqs; i++) { + while (*freqs) { + if (self->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + return 0; +} + +/* @divisor: pass what value @xbm should be divided by to get dBm */ +static guint32 +nl80211_xbm_to_percent(gint32 xbm, guint32 divisor) +{ +#define NOISE_FLOOR_DBM -90 +#define SIGNAL_MAX_DBM -20 + + xbm /= divisor; + xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM); + + return 100 + - 70 + * (((float) SIGNAL_MAX_DBM - (float) xbm) + / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM)); +} + +struct nl80211_station_info { + gboolean valid; + guint8 bssid[ETH_ALEN]; + guint32 txrate; + gboolean txrate_valid; + guint8 signal; + gboolean signal_valid; +}; + +static int +nl80211_station_dump_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy stats_policy[] = { + [NL80211_STA_INFO_INACTIVE_TIME] = {.type = NLA_U32}, + [NL80211_STA_INFO_RX_BYTES] = {.type = NLA_U32}, + [NL80211_STA_INFO_TX_BYTES] = {.type = NLA_U32}, + [NL80211_STA_INFO_RX_PACKETS] = {.type = NLA_U32}, + [NL80211_STA_INFO_TX_PACKETS] = {.type = NLA_U32}, + [NL80211_STA_INFO_SIGNAL] = {.type = NLA_U8}, + [NL80211_STA_INFO_TX_BITRATE] = {.type = NLA_NESTED}, + [NL80211_STA_INFO_LLID] = {.type = NLA_U16}, + [NL80211_STA_INFO_PLID] = {.type = NLA_U16}, + [NL80211_STA_INFO_PLINK_STATE] = {.type = NLA_U8}, + [NL80211_STA_INFO_STA_FLAGS] = {.minlen = sizeof(struct nl80211_sta_flag_update)}, + [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = {.type = NLA_U8}, + }; + static const struct nla_policy rate_policy[] = { + [NL80211_RATE_INFO_BITRATE] = {.type = NLA_U16}, + [NL80211_RATE_INFO_MCS] = {.type = NLA_U8}, + [NL80211_RATE_INFO_40_MHZ_WIDTH] = {.type = NLA_FLAG}, + [NL80211_RATE_INFO_SHORT_GI] = {.type = NLA_FLAG}, + }; + struct nlattr * rinfo[G_N_ELEMENTS(rate_policy)]; + struct nlattr * sinfo[G_N_ELEMENTS(stats_policy)]; + struct nl80211_station_info *info = arg; + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_MAC] == NULL) + return NL_SKIP; + + if (tb[NL80211_ATTR_STA_INFO] == NULL) + return NL_SKIP; + + if (nla_parse_nested_arr(sinfo, tb[NL80211_ATTR_STA_INFO], stats_policy)) + return NL_SKIP; + + if (sinfo[NL80211_STA_INFO_STA_FLAGS] != NULL) { + const struct nl80211_sta_flag_update *flags = nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]); + + if (flags->mask & ~flags->set & (1 << NL80211_STA_FLAG_ASSOCIATED)) + return NL_SKIP; + } + + memcpy(info->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN); + info->valid = TRUE; + + if (sinfo[NL80211_STA_INFO_TX_BITRATE] != NULL + && !nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy) + && rinfo[NL80211_RATE_INFO_BITRATE] != NULL) { + /* convert from nl80211's units of 100kbps to NM's kbps */ + info->txrate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100; + info->txrate_valid = TRUE; + } + + if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) { + info->signal = + nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), 1); + info->signal_valid = TRUE; + } else if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG] != NULL) { + /* Fall back to beacon signal strength */ + info->signal = + nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]), + 1); + info->signal_valid = TRUE; + } + + return NL_SKIP; +} + +static gboolean +wifi_nl80211_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nl80211_station_info sta_info = {}; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, NLM_F_DUMP); + + nl80211_send_and_recv(self, msg, nl80211_station_dump_handler, &sta_info); + + if (!sta_info.valid || (out_quality && !sta_info.signal_valid) + || (out_rate && !sta_info.txrate_valid)) + return FALSE; + + if (out_bssid) + memcpy(out_bssid, sta_info.bssid, ETH_ALEN); + + if (out_quality) + *out_quality = sta_info.signal; + + if (out_rate) + *out_rate = sta_info.txrate; + + return TRUE; +} + +static gboolean +wifi_nl80211_indicate_addressing_running(NMWifiUtils *data, gboolean running) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, + running ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */ + : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */, + 0); + /* Despite the DHCP name, we're using this for any type of IP addressing, + * DHCPv4, DHCPv6, and IPv6 SLAAC. + */ + NLA_PUT_U16(msg, 179 /* NL80211_ATTR_CRIT_PROT_ID */, 1 /* NL80211_CRIT_PROTO_DHCP */); + if (running) { + /* Give DHCP 5 seconds to complete */ + NLA_PUT_U16(msg, 180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */, 5000); + } + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +struct nl80211_device_info { + NMWifiUtilsNl80211 *self; + int phy; + guint32 * freqs; + int num_freqs; + guint32 freq; + guint32 caps; + gboolean can_scan; + gboolean can_scan_ssid; + gboolean supported; + gboolean success; + gboolean can_wowlan; +}; + +#define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00 +#define WLAN_CIPHER_SUITE_WEP40 0x000FAC01 +#define WLAN_CIPHER_SUITE_TKIP 0x000FAC02 +#define WLAN_CIPHER_SUITE_CCMP 0x000FAC04 +#define WLAN_CIPHER_SUITE_WEP104 0x000FAC05 +#define WLAN_CIPHER_SUITE_AES_CMAC 0x000FAC06 +#define WLAN_CIPHER_SUITE_GCMP 0x000FAC08 +#define WLAN_CIPHER_SUITE_SMS4 0x00147201 + +static int +nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy freq_policy[] = { + [NL80211_FREQUENCY_ATTR_FREQ] = {.type = NLA_U32}, + [NL80211_FREQUENCY_ATTR_DISABLED] = {.type = NLA_FLAG}, +#ifdef NL80211_FREQUENCY_ATTR_NO_IR + [NL80211_FREQUENCY_ATTR_NO_IR] = {.type = NLA_FLAG}, +#else + [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = {.type = NLA_FLAG}, + [NL80211_FREQUENCY_ATTR_NO_IBSS] = {.type = NLA_FLAG}, +#endif + [NL80211_FREQUENCY_ATTR_RADAR] = {.type = NLA_FLAG}, + [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = {.type = NLA_U32}, + }; + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nl80211_device_info *info = arg; + NMWifiUtilsNl80211 * self = info->self; + struct nlattr * tb_band[NL80211_BAND_ATTR_MAX + 1]; + struct nlattr * tb_freq[G_N_ELEMENTS(freq_policy)]; + struct nlattr * nl_band; + struct nlattr * nl_freq; + int rem_freq; + int rem_band; + int freq_idx; + +#ifdef NL80211_FREQUENCY_ATTR_NO_IR + G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR + && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR); +#else + G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS); +#endif + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_WIPHY] == NULL || tb[NL80211_ATTR_WIPHY_BANDS] == NULL) + return NL_SKIP; + + info->phy = nla_get_u32(tb[NL80211_ATTR_WIPHY]); + + if (tb[NL80211_ATTR_WIPHY_FREQ]) + info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); + else + info->freq = 0; + + if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) { + info->can_scan_ssid = nla_get_u8(tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0; + } else { + /* old kernel that only had mac80211, so assume it can */ + info->can_scan_ssid = TRUE; + } + + if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) { + struct nlattr *nl_cmd; + int i; + + nla_for_each_nested (nl_cmd, tb[NL80211_ATTR_SUPPORTED_COMMANDS], i) { + switch (nla_get_u32(nl_cmd)) { + case NL80211_CMD_TRIGGER_SCAN: + info->can_scan = TRUE; + break; + case NL80211_CMD_CONNECT: + case NL80211_CMD_AUTHENTICATE: + /* Only devices that support CONNECT or AUTH actually support + * 802.11, unlike say ipw2x00 (up to at least kernel 3.4) which + * has minimal info support, but no actual command support. + * This check mirrors what wpa_supplicant does to determine + * whether or not to use the nl80211 driver. + */ + info->supported = TRUE; + break; + default: + break; + } + } + } + + /* Find number of supported frequencies */ + info->num_freqs = 0; + + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0) + return NL_SKIP; + + nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { + if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0) + continue; + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->num_freqs++; + } + } + + /* Read supported frequencies */ + info->freqs = g_malloc0(sizeof(guint32) * info->num_freqs); + + freq_idx = 0; + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0) + return NL_SKIP; + + nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { + if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0) + continue; + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->freqs[freq_idx] = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + + info->caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID; + + if (info->freqs[freq_idx] > 2400 && info->freqs[freq_idx] < 2500) + info->caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ; + if (info->freqs[freq_idx] > 4900 && info->freqs[freq_idx] < 6000) + info->caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ; + + freq_idx++; + } + } + + /* Read security/encryption support */ + if (tb[NL80211_ATTR_CIPHER_SUITES]) { + guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]); + guint i, num; + + num = nla_len(tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof(guint32); + for (i = 0; i < num; i++) { + switch (ciphers[i]) { + case WLAN_CIPHER_SUITE_WEP40: + info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_WPA); + break; + case WLAN_CIPHER_SUITE_CCMP: + info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_SMS4: + break; + default: + _LOGD("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.", ciphers[i]); + break; + } + } + } + + if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) { + struct nlattr *nl_mode; + int i; + + nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) { + switch (nla_type(nl_mode)) { + case NL80211_IFTYPE_AP: + info->caps |= NM_WIFI_DEVICE_CAP_AP; + break; + case NL80211_IFTYPE_ADHOC: + info->caps |= NM_WIFI_DEVICE_CAP_ADHOC; + break; + case NL80211_IFTYPE_MESH_POINT: + info->caps |= NM_WIFI_DEVICE_CAP_MESH; + break; + } + } + } + + if (tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]) + info->can_wowlan = TRUE; + + if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN]) + info->caps |= NM_WIFI_DEVICE_CAP_IBSS_RSN; + + info->success = TRUE; + + return NL_SKIP; +} + +static guint32 +wifi_nl80211_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nl80211_device_info device_info = {.self = self}; + int i; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { + _LOGW("NL80211_CMD_GET_WIPHY request failed"); + return 0; + } + + for (i = 0; i < self->num_freqs; i++) { + if (device_info.freq == self->freqs[i]) + return i + 1; + } + return 0; +} + +static gboolean +wifi_nl80211_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + if (channel > self->num_freqs) + return FALSE; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WIPHY, 0); + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1]); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +wifi_nl80211_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); + NLA_PUT(msg, NL80211_ATTR_MESH_ID, len, ssid); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static void +nm_wifi_utils_nl80211_init(NMWifiUtilsNl80211 *self) +{} + +static void +nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass); + + object_class->dispose = dispose; + + wifi_utils_class->get_mode = wifi_nl80211_get_mode; + wifi_utils_class->set_mode = wifi_nl80211_set_mode; + wifi_utils_class->set_powersave = wifi_nl80211_set_powersave; + wifi_utils_class->get_wake_on_wlan = wifi_nl80211_get_wake_on_wlan, + wifi_utils_class->set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan, + wifi_utils_class->get_freq = wifi_nl80211_get_freq; + wifi_utils_class->find_freq = wifi_nl80211_find_freq; + wifi_utils_class->get_station = wifi_nl80211_get_station; + wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running; + wifi_utils_class->get_mesh_channel = wifi_nl80211_get_mesh_channel; + wifi_utils_class->set_mesh_channel = wifi_nl80211_set_mesh_channel; + wifi_utils_class->set_mesh_ssid = wifi_nl80211_set_mesh_ssid; +} + +NMWifiUtils * +nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl) +{ + gs_unref_object NMWifiUtilsNl80211 *self = NULL; + nm_auto_nlmsg struct nl_msg * msg = NULL; + struct nl80211_device_info device_info = {}; + + if (!genl) + return NULL; + + self = g_object_new(NM_TYPE_WIFI_UTILS_NL80211, NULL); + + self->parent.ifindex = ifindex; + self->nl_sock = genl; + + self->id = genl_ctrl_resolve(self->nl_sock, "nl80211"); + if (self->id < 0) { + _LOGD("genl_ctrl_resolve: failed to resolve \"nl80211\""); + return NULL; + } + + self->phy = -1; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0); + + device_info.self = self; + if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { + _LOGD("NL80211_CMD_GET_WIPHY request failed"); + return NULL; + } + + if (!device_info.success) { + _LOGD("NL80211_CMD_GET_WIPHY request indicated failure"); + return NULL; + } + + if (!device_info.supported) { + _LOGD("driver does not fully support nl80211, falling back to WEXT"); + return NULL; + } + + if (!device_info.can_scan_ssid) { + _LOGE("driver does not support SSID scans"); + return NULL; + } + + if (device_info.num_freqs == 0 || device_info.freqs == NULL) { + _LOGE("driver reports no supported frequencies"); + return NULL; + } + + if (device_info.caps == 0) { + _LOGE("driver doesn't report support of any encryption"); + return NULL; + } + + self->phy = device_info.phy; + self->freqs = device_info.freqs; + self->num_freqs = device_info.num_freqs; + self->parent.caps = device_info.caps; + self->can_wowlan = device_info.can_wowlan; + + _LOGD("using nl80211 for Wi-Fi device control"); + return (NMWifiUtils *) g_steal_pointer(&self); +} diff --git a/src/core/platform/wifi/nm-wifi-utils-nl80211.h b/src/core/platform/wifi/nm-wifi-utils-nl80211.h new file mode 100644 index 0000000..37c32ed --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils-nl80211.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Intel Corporation. All rights reserved. + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_NL80211_H__ +#define __WIFI_UTILS_NL80211_H__ + +#include "nm-wifi-utils.h" +#include "nm-platform/nm-netlink.h" + +#define NM_TYPE_WIFI_UTILS_NL80211 (nm_wifi_utils_nl80211_get_type()) +#define NM_WIFI_UTILS_NL80211(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211)) +#define NM_WIFI_UTILS_NL80211_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class)) +#define NM_IS_WIFI_UTILS_NL80211(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_NL80211)) +#define NM_IS_WIFI_UTILS_NL80211_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_NL80211)) +#define NM_WIFI_UTILS_NL80211_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class)) + +GType nm_wifi_utils_nl80211_get_type(void); + +NMWifiUtils *nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl); + +#endif /* __WIFI_UTILS_NL80211_H__ */ diff --git a/src/core/platform/wifi/nm-wifi-utils-private.h b/src/core/platform/wifi/nm-wifi-utils-private.h new file mode 100644 index 0000000..9ed5745 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils-private.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_PRIVATE_H__ +#define __WIFI_UTILS_PRIVATE_H__ + +#include "nm-dbus-interface.h" +#include "nm-wifi-utils.h" + +typedef struct { + GObjectClass parent; + + NM80211Mode (*get_mode)(NMWifiUtils *data); + + gboolean (*set_mode)(NMWifiUtils *data, const NM80211Mode mode); + + /* Set power saving mode on an interface */ + gboolean (*set_powersave)(NMWifiUtils *data, guint32 powersave); + + /* Get WakeOnWLAN configuration on an interface */ + NMSettingWirelessWakeOnWLan (*get_wake_on_wlan)(NMWifiUtils *data); + + /* Set WakeOnWLAN mode on an interface */ + gboolean (*set_wake_on_wlan)(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl); + + /* Return current frequency in MHz (really associated BSS frequency) */ + guint32 (*get_freq)(NMWifiUtils *data); + + /* Return first supported frequency in the zero-terminated list */ + guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs); + + /* + * @out_bssid: must be NULL or an ETH_ALEN-byte buffer + * @out_quality: receives signal strength percentage 0 - 100% for the current BSSID, if not NULL + * @out_rate: receives current bitrate in Kbps if not NULL + * + * Returns %TRUE on succcess, %FALSE on errors or if not associated. + */ + gboolean (*get_station)(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + + /* OLPC Mesh-only functions */ + + guint32 (*get_mesh_channel)(NMWifiUtils *data); + + /* channel == 0 means "auto channel" */ + gboolean (*set_mesh_channel)(NMWifiUtils *data, guint32 channel); + + /* ssid == NULL means "auto SSID" */ + gboolean (*set_mesh_ssid)(NMWifiUtils *data, const guint8 *ssid, gsize len); + + gboolean (*indicate_addressing_running)(NMWifiUtils *data, gboolean running); +} NMWifiUtilsClass; + +struct NMWifiUtils { + GObject parent; + + int ifindex; + NMDeviceWifiCapabilities caps; +}; + +#endif /* __WIFI_UTILS_PRIVATE_H__ */ diff --git a/src/core/platform/wifi/nm-wifi-utils-wext.c b/src/core/platform/wifi/nm-wifi-utils-wext.c new file mode 100644 index 0000000..2b070b5 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils-wext.c @@ -0,0 +1,836 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-utils-wext.h" + +#include +#include +#include + +/* Hacks necessary to #include wireless.h; yay for WEXT */ +#ifndef __user + #define __user +#endif +#include +#include +#include +#include + +#include "nm-wifi-utils-private.h" +#include "nm-utils.h" +#include "nm-platform/nm-platform-utils.h" +#include "nm-core-internal.h" +#include "nm-core-utils.h" + +typedef struct { + NMWifiUtils parent; + int fd; + struct iw_quality max_qual; + gint8 num_freqs; + guint32 freqs[IW_MAX_FREQUENCIES]; +} NMWifiUtilsWext; + +typedef struct { + NMWifiUtilsClass parent; +} NMWifiUtilsWextClass; + +G_DEFINE_TYPE(NMWifiUtilsWext, nm_wifi_utils_wext, NM_TYPE_WIFI_UTILS) + +/* Until a new wireless-tools comes out that has the defs and the structure, + * need to copy them here. + */ +/* Scan capability flags - in (struct iw_range *)->scan_capa */ +#define NM_IW_SCAN_CAPA_NONE 0x00 +#define NM_IW_SCAN_CAPA_ESSID 0x01 + +struct iw_range_with_scan_capa { + guint32 throughput; + guint32 min_nwid; + guint32 max_nwid; + guint16 old_num_channels; + guint8 old_num_frequency; + + guint8 scan_capa; + /* don't need the rest... */ +}; + +#define _NMLOG_PREFIX_NAME "wifi-wext" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +static guint32 +iw_freq_to_uint32(const struct iw_freq *freq) +{ + if (freq->e == 0) { + /* Some drivers report channel not frequency. Convert to a + * frequency; but this assumes that the device is in b/g mode. + */ + if ((freq->m >= 1) && (freq->m <= 13)) + return 2407 + (5 * freq->m); + else if (freq->m == 14) + return 2484; + } + return (guint32)((((double) freq->m) * nm_utils_exp10(freq->e)) / 1000000.0); +} + +static void +dispose(GObject *object) +{ + NMWifiUtilsWext *wext = NM_WIFI_UTILS_WEXT(object); + + wext->fd = nm_close(wext->fd); +} + +static gboolean +get_ifname(int ifindex, char *buffer, const char *op) +{ + int errsv; + + if (!nmp_utils_if_indextoname(ifindex, buffer)) { + errsv = errno; + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "error getting interface name for ifindex %d, operation '%s': %s (%d)", + ifindex, + op, + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + return TRUE; +} + +static NM80211Mode +wifi_wext_get_mode_ifname(NMWifiUtils *data, const char *ifname) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + int errsv; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (ioctl(wext->fd, SIOCGIWMODE, &wrq) < 0) { + errsv = errno; + if (errsv != ENODEV) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, "(%s): error %d getting card mode", ifname, errsv); + } + return NM_802_11_MODE_UNKNOWN; + } + + switch (wrq.u.mode) { + case IW_MODE_ADHOC: + return NM_802_11_MODE_ADHOC; + case IW_MODE_MASTER: + return NM_802_11_MODE_AP; + case IW_MODE_INFRA: + case IW_MODE_AUTO: /* hack for WEXT devices reporting IW_MODE_AUTO */ + return NM_802_11_MODE_INFRA; + default: + break; + } + return NM_802_11_MODE_UNKNOWN; +} + +static NM80211Mode +wifi_wext_get_mode(NMWifiUtils *data) +{ + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-mode")) + return FALSE; + + return wifi_wext_get_mode_ifname(data, ifname); +} + +static gboolean +wifi_wext_set_mode(NMWifiUtils *data, const NM80211Mode mode) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-mode")) + return FALSE; + + if (wifi_wext_get_mode_ifname(data, ifname) == mode) + return TRUE; + + memset(&wrq, 0, sizeof(struct iwreq)); + switch (mode) { + case NM_802_11_MODE_ADHOC: + wrq.u.mode = IW_MODE_ADHOC; + break; + case NM_802_11_MODE_AP: + wrq.u.mode = IW_MODE_MASTER; + break; + case NM_802_11_MODE_INFRA: + wrq.u.mode = IW_MODE_INFRA; + break; + default: + g_warn_if_reached(); + return FALSE; + } + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWMODE, &wrq) < 0) { + if (errno != ENODEV) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, "(%s): error setting mode %d", ifname, mode); + } + return FALSE; + } + + return TRUE; +} + +static gboolean +wifi_wext_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-powersave")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + if (powersave == 1) { + wrq.u.power.flags = IW_POWER_ALL_R; + } else + wrq.u.power.disabled = 1; + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWPOWER, &wrq) < 0) { + if (errno != ENODEV) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error setting powersave %" G_GUINT32_FORMAT, + ifname, + powersave); + } + return FALSE; + } + + return TRUE; +} + +static guint32 +wifi_wext_get_freq(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-freq")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCGIWFREQ, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting frequency: %s", + ifname, + nm_strerror_native(errno)); + return 0; + } + + return iw_freq_to_uint32(&wrq.u.freq); +} + +static guint32 +wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + int i; + + for (i = 0; i < wext->num_freqs; i++) { + while (*freqs) { + if (wext->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + return 0; +} + +static gboolean +wifi_wext_get_bssid(NMWifiUtils *data, NMEtherAddr *out_bssid) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-bssid")) + return FALSE; + + memset(&wrq, 0, sizeof(wrq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCGIWAP, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting associated BSSID: %s", + ifname, + nm_strerror_native(errno)); + return FALSE; + } + memcpy(out_bssid, &(wrq.u.ap_addr.sa_data), ETH_ALEN); + return TRUE; +} + +static guint32 +wifi_wext_get_rate(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + int err; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-rate")) + return FALSE; + + memset(&wrq, 0, sizeof(wrq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + err = ioctl(wext->fd, SIOCGIWRATE, &wrq); + return ((err == 0) ? wrq.u.bitrate.value / 1000 : 0); +} + +static int +wext_qual_to_percent(const struct iw_quality *qual, const struct iw_quality *max_qual) +{ + int percent = -1; + int level_percent = -1; + + g_return_val_if_fail(qual != NULL, -1); + g_return_val_if_fail(max_qual != NULL, -1); + + /* Magically convert the many different WEXT quality representations to a percentage */ + + _LOGD(LOGD_WIFI, + "QL: qual %d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X ** MAX: qual " + "%d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X", + (__s8) qual->qual, + qual->qual, + qual->qual, + (__s8) qual->level, + qual->level, + qual->level, + (__s8) qual->noise, + qual->noise, + qual->noise, + qual->updated, + (__s8) max_qual->qual, + max_qual->qual, + max_qual->qual, + (__s8) max_qual->level, + max_qual->level, + max_qual->level, + (__s8) max_qual->noise, + max_qual->noise, + max_qual->noise, + max_qual->updated); + + /* Try using the card's idea of the signal quality first as long as it tells us what the max quality is. + * Drivers that fill in quality values MUST treat them as percentages, ie the "Link Quality" MUST be + * bounded by 0 and max_qual->qual, and MUST change in a linear fashion. Within those bounds, drivers + * are free to use whatever they want to calculate "Link Quality". + */ + if ((max_qual->qual != 0) && !(max_qual->updated & IW_QUAL_QUAL_INVALID) + && !(qual->updated & IW_QUAL_QUAL_INVALID)) + percent = (int) (100 * ((double) qual->qual / (double) max_qual->qual)); + + /* If the driver doesn't specify a complete and valid quality, we have two options: + * + * 1) dBm: driver must specify max_qual->level = 0, and have valid values for + * qual->level and (qual->noise OR max_qual->noise) + * 2) raw RSSI: driver must specify max_qual->level > 0, and have valid values for + * qual->level and max_qual->level + * + * This is the WEXT spec. If this interpretation is wrong, I'll fix it. Otherwise, + * If drivers don't conform to it, they are wrong and need to be fixed. + */ + + if ((max_qual->level == 0) + && !(max_qual->updated & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level == 0 */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID) /* Must have valid qual->level */ + && (((max_qual->noise > 0) + && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) /* Must have valid max_qual->noise */ + || ((qual->noise > 0) + && !(qual->updated & IW_QUAL_NOISE_INVALID))) /* OR valid qual->noise */ + ) { +/* Absolute power values (dBm) */ + +/* Reasonable fallbacks for dumb drivers that don't specify either level. */ +#define FALLBACK_NOISE_FLOOR_DBM -90 +#define FALLBACK_SIGNAL_MAX_DBM -20 + int max_level = FALLBACK_SIGNAL_MAX_DBM; + int noise = FALLBACK_NOISE_FLOOR_DBM; + int level = qual->level - 0x100; + + level = CLAMP(level, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM); + + if ((qual->noise > 0) && !(qual->updated & IW_QUAL_NOISE_INVALID)) + noise = qual->noise - 0x100; + else if ((max_qual->noise > 0) && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) + noise = max_qual->noise - 0x100; + noise = CLAMP(noise, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM - 1); + + /* A sort of signal-to-noise ratio calculation */ + level_percent = (int) (100 + - 70 + * (((double) max_level - (double) level) + / ((double) max_level - (double) noise))); + _LOGD(LOGD_WIFI, + "QL1: level_percent is %d. max_level %d, level %d, noise_floor %d.", + level_percent, + max_level, + level, + noise); + } else if ((max_qual->level != 0) + && !(max_qual->updated + & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level as upper bound */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID)) { + /* Relative power values (RSSI) */ + + int level = qual->level; + + /* Signal level is relavtive (0 -> max_qual->level) */ + level = CLAMP(level, 0, max_qual->level); + level_percent = (int) (100 * ((double) level / (double) max_qual->level)); + _LOGD(LOGD_WIFI, + "QL2: level_percent is %d. max_level %d, level %d.", + level_percent, + max_qual->level, + level); + } else if (percent == -1) { + _LOGD(LOGD_WIFI, + "QL: Could not get quality %% value from driver. Driver is probably buggy."); + } + + /* If the quality percent was 0 or doesn't exist, then try to use signal levels instead */ + if ((percent < 1) && (level_percent >= 0)) + percent = level_percent; + + _LOGD(LOGD_WIFI, "QL: Final quality percent is %d (%d).", percent, CLAMP(percent, 0, 100)); + return (CLAMP(percent, 0, 100)); +} + +static int +wifi_wext_get_qual(NMWifiUtils *data) +{ + NMWifiUtilsWext * wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + struct iw_statistics stats; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-qual")) + return FALSE; + + memset(&stats, 0, sizeof(stats)); + wrq.u.data.pointer = &stats; + wrq.u.data.length = sizeof(stats); + wrq.u.data.flags = 1; /* Clear updated flag */ + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (ioctl(wext->fd, SIOCGIWSTATS, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting signal strength: %s", + ifname, + nm_strerror_native(errno)); + return -1; + } + + return wext_qual_to_percent(&stats.qual, &wext->max_qual); +} + +static gboolean +wifi_wext_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + NMEtherAddr local_addr; + + if (!out_bssid && !out_quality && !out_rate) { + /* hm, the caller requested no parameter at all? + * Don't simply return TRUE, but at least check that + * we can successfully fetch the bssid. */ + out_bssid = &local_addr; + } + + if (out_bssid) { + if (!wifi_wext_get_bssid(data, out_bssid)) + return FALSE; + } + if (out_quality) { + *out_quality = wifi_wext_get_qual(data); + if (*out_quality < 0) + return FALSE; + } + if (out_rate) { + *out_rate = wifi_wext_get_rate(data); + if (*out_rate == 0) + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ +/* OLPC Mesh-only functions */ + +static guint32 +wifi_wext_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + guint32 freq; + int i; + + freq = nm_wifi_utils_get_freq(data); + for (i = 0; i < wext->num_freqs; i++) { + if (freq == wext->freqs[i]) + return i + 1; + } + return 0; +} + +static gboolean +wifi_wext_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-mesh-channel")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (channel > 0) { + wrq.u.freq.flags = IW_FREQ_FIXED; + wrq.u.freq.e = 0; + wrq.u.freq.m = channel; + } + + if (ioctl(wext->fd, SIOCSIWFREQ, &wrq) < 0) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC, + "(%s): error setting channel to %d: %s", + ifname, + channel, + nm_strerror_native(errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +wifi_wext_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char buf[IW_ESSID_MAX_SIZE + 1]; + char ifname[IFNAMSIZ]; + int errsv; + + if (!get_ifname(data->ifindex, ifname, "set-mesh-ssid")) + return FALSE; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, ssid, MIN(sizeof(buf) - 1, len)); + + wrq.u.essid.pointer = (caddr_t) buf; + wrq.u.essid.length = len; + wrq.u.essid.flags = (len > 0) ? 1 : 0; /* 1=enable SSID, 0=disable/any */ + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWESSID, &wrq) == 0) + return TRUE; + + errsv = errno; + if (errsv != ENODEV) { + gs_free char *ssid_str = NULL; + + _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC, + "(%s): error setting SSID to %s: %s", + ifname, + (ssid_str = _nm_utils_ssid_to_string_arr(ssid, len)), + nm_strerror_native(errsv)); + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +wext_can_scan_ifname(NMWifiUtilsWext *wext, const char *ifname) +{ + struct iwreq wrq; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWSCAN, &wrq) < 0) { + if (errno == EOPNOTSUPP) + return FALSE; + } + return TRUE; +} + +static gboolean +wext_get_range_ifname(NMWifiUtilsWext *wext, + const char * ifname, + struct iw_range *range, + guint32 * response_len) +{ + int i = 26; + gboolean success = FALSE; + struct iwreq wrq; + int errsv; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + wrq.u.data.pointer = (caddr_t) range; + wrq.u.data.length = sizeof(struct iw_range); + + /* Need to give some drivers time to recover after suspend/resume + * (ex ipw3945 takes a few seconds to talk to its regulatory daemon; + * see rh bz#362421) + */ + while (i-- > 0) { + if (ioctl(wext->fd, SIOCGIWRANGE, &wrq) == 0) { + if (response_len) + *response_len = wrq.u.data.length; + success = TRUE; + break; + } else { + errsv = errno; + if (errsv != EAGAIN) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, + "(%s): couldn't get driver range information (%d).", + ifname, + errsv); + break; + } + } + + g_usleep(G_USEC_PER_SEC / 4); + } + + if (i <= 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver took too long to respond to IWRANGE query.", + ifname); + } + + return success; +} + +#define WPA_CAPS \ + (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_WPA \ + | NM_WIFI_DEVICE_CAP_RSN) + +static guint32 +wext_get_caps(NMWifiUtilsWext *wext, const char *ifname, struct iw_range *range) +{ + guint32 caps = NM_WIFI_DEVICE_CAP_NONE; + + g_return_val_if_fail(wext != NULL, NM_WIFI_DEVICE_CAP_NONE); + g_return_val_if_fail(range != NULL, NM_WIFI_DEVICE_CAP_NONE); + + /* All drivers should support WEP by default */ + caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + + if (range->enc_capa & IW_ENC_CAPA_CIPHER_TKIP) + caps |= NM_WIFI_DEVICE_CAP_CIPHER_TKIP; + + if (range->enc_capa & IW_ENC_CAPA_CIPHER_CCMP) + caps |= NM_WIFI_DEVICE_CAP_CIPHER_CCMP; + + if (range->enc_capa & IW_ENC_CAPA_WPA) + caps |= NM_WIFI_DEVICE_CAP_WPA; + + if (range->enc_capa & IW_ENC_CAPA_WPA2) + caps |= NM_WIFI_DEVICE_CAP_RSN; + + /* Check for cipher support but not WPA support */ + if ((caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + && !(caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN))) { + _LOGW(LOGD_WIFI, + "%s: device supports WPA ciphers but not WPA protocol; WPA unavailable.", + ifname); + caps &= ~WPA_CAPS; + } + + /* Check for WPA support but not cipher support */ + if ((caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN)) + && !(caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP))) { + _LOGW(LOGD_WIFI, + "%s: device supports WPA protocol but not WPA ciphers; WPA unavailable.", + ifname); + caps &= ~WPA_CAPS; + } + + /* There's no way to detect Ad-Hoc/AP mode support with WEXT + * (other than actually trying to do it), so just assume that + * Ad-Hoc is supported and AP isn't. + */ + caps |= NM_WIFI_DEVICE_CAP_ADHOC; + + return caps; +} + +/*****************************************************************************/ + +static void +nm_wifi_utils_wext_init(NMWifiUtilsWext *self) +{} + +static void +nm_wifi_utils_wext_class_init(NMWifiUtilsWextClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass); + + object_class->dispose = dispose; + + wifi_utils_class->get_mode = wifi_wext_get_mode; + wifi_utils_class->set_mode = wifi_wext_set_mode; + wifi_utils_class->set_powersave = wifi_wext_set_powersave; + wifi_utils_class->get_freq = wifi_wext_get_freq; + wifi_utils_class->find_freq = wifi_wext_find_freq; + wifi_utils_class->get_station = wifi_wext_get_station; + wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel; + wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel; + wifi_utils_class->set_mesh_ssid = wifi_wext_set_mesh_ssid; +} + +NMWifiUtils * +nm_wifi_utils_wext_new(int ifindex, gboolean check_scan) +{ + NMWifiUtilsWext * wext; + struct iw_range range; + guint32 response_len = 0; + struct iw_range_with_scan_capa *scan_capa_range; + int i; + gboolean freq_valid = FALSE, has_5ghz = FALSE, has_2ghz = FALSE; + char ifname[IFNAMSIZ]; + + if (!nmp_utils_if_indextoname(ifindex, ifname)) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, "can't determine interface name for ifindex %d", ifindex); + return NULL; + } + + wext = g_object_new(NM_TYPE_WIFI_UTILS_WEXT, NULL); + + wext->parent.ifindex = ifindex; + wext->fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (wext->fd < 0) + goto error; + + memset(&range, 0, sizeof(struct iw_range)); + if (wext_get_range_ifname(wext, ifname, &range, &response_len) == FALSE) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): driver WEXT range request failed", ifname); + goto error; + } + + if ((response_len < 300) || (range.we_version_compiled < 21)) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver WEXT version too old (got %d, expected >= 21)", + ifname, + range.we_version_compiled); + goto error; + } + + wext->max_qual.qual = range.max_qual.qual; + wext->max_qual.level = range.max_qual.level; + wext->max_qual.noise = range.max_qual.noise; + wext->max_qual.updated = range.max_qual.updated; + + wext->num_freqs = MIN(range.num_frequency, IW_MAX_FREQUENCIES); + for (i = 0; i < wext->num_freqs; i++) { + wext->freqs[i] = iw_freq_to_uint32(&range.freq[i]); + freq_valid = TRUE; + if (wext->freqs[i] > 2400 && wext->freqs[i] < 2500) + has_2ghz = TRUE; + else if (wext->freqs[i] > 4900 && wext->freqs[i] < 6000) + has_5ghz = TRUE; + } + + /* Check for scanning capability; cards that can't scan are not supported */ + if (check_scan && (wext_can_scan_ifname(wext, ifname) == FALSE)) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): drivers that cannot scan are unsupported", ifname); + goto error; + } + + /* Check for the ability to scan specific SSIDs. Until the scan_capa + * field gets added to wireless-tools, need to work around that by casting + * to the custom structure. + */ + scan_capa_range = (struct iw_range_with_scan_capa *) ⦥ + if (scan_capa_range->scan_capa & NM_IW_SCAN_CAPA_ESSID) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver supports SSID scans (scan_capa 0x%02X).", + ifname, + scan_capa_range->scan_capa); + } else { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver does not support SSID scans (scan_capa 0x%02X).", + ifname, + scan_capa_range->scan_capa); + } + + wext->parent.caps = wext_get_caps(wext, ifname, &range); + if (freq_valid) + wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID; + if (has_2ghz) + wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ; + if (has_5ghz) + wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ; + + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): using WEXT for Wi-Fi device control", ifname); + + return (NMWifiUtils *) wext; + +error: + g_object_unref(wext); + return NULL; +} + +gboolean +nm_wifi_utils_wext_is_wifi(const char *iface) +{ + int fd; + struct iwreq iwr; + gboolean is_wifi = FALSE; + + /* performing an ioctl on a non-existing name may cause the automatic + * loading of kernel modules, which should be avoided. + * + * Usually, we should thus make sure that an interface with this name + * exists. + * + * Note that wifi_wext_is_wifi() has only one caller which just verified + * that an interface with this name exists. + */ + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd >= 0) { + nm_utils_ifname_cpy(iwr.ifr_ifrn.ifrn_name, iface); + if (ioctl(fd, SIOCGIWNAME, &iwr) == 0) + is_wifi = TRUE; + nm_close(fd); + } + return is_wifi; +} diff --git a/src/core/platform/wifi/nm-wifi-utils-wext.h b/src/core/platform/wifi/nm-wifi-utils-wext.h new file mode 100644 index 0000000..d6f3453 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils-wext.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_WEXT_H__ +#define __WIFI_UTILS_WEXT_H__ + +#include "nm-wifi-utils.h" + +#define NM_TYPE_WIFI_UTILS_WEXT (nm_wifi_utils_wext_get_type()) +#define NM_WIFI_UTILS_WEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWext)) +#define NM_WIFI_UTILS_WEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass)) +#define NM_IS_WIFI_UTILS_WEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_WEXT)) +#define NM_IS_WIFI_UTILS_WEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_WEXT)) +#define NM_WIFI_UTILS_WEXT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass)) + +GType nm_wifi_utils_wext_get_type(void); + +NMWifiUtils *nm_wifi_utils_wext_new(int ifindex, gboolean check_scan); + +gboolean nm_wifi_utils_wext_is_wifi(const char *iface); + +#endif /* __WIFI_UTILS_WEXT_H__ */ diff --git a/src/core/platform/wifi/nm-wifi-utils.c b/src/core/platform/wifi/nm-wifi-utils.c new file mode 100644 index 0000000..66d6a05 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils.c @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-wifi-utils.h" + +#include +#include +#include + +#include "nm-wifi-utils-private.h" +#include "nm-wifi-utils-nl80211.h" +#if HAVE_WEXT + #include "nm-wifi-utils-wext.h" +#endif +#include "nm-core-utils.h" + +#include "nm-platform/nm-platform-utils.h" + +G_DEFINE_ABSTRACT_TYPE(NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static void +nm_wifi_utils_init(NMWifiUtils *self) +{} + +static void +nm_wifi_utils_class_init(NMWifiUtilsClass *klass) +{} + +NMWifiUtils * +nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan) +{ + NMWifiUtils *ret; + + g_return_val_if_fail(ifindex > 0, NULL); + + ret = nm_wifi_utils_nl80211_new(ifindex, genl); + +#if HAVE_WEXT + if (ret == NULL) + ret = nm_wifi_utils_wext_new(ifindex, check_scan); +#endif + + return ret; +} + +NMDeviceWifiCapabilities +nm_wifi_utils_get_caps(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, NM_WIFI_DEVICE_CAP_NONE); + + return data->caps; +} + +NM80211Mode +nm_wifi_utils_get_mode(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, NM_802_11_MODE_UNKNOWN); + return NM_WIFI_UTILS_GET_CLASS(data)->get_mode(data); +} + +gboolean +nm_wifi_utils_set_mode(NMWifiUtils *data, const NM80211Mode mode) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + g_return_val_if_fail((mode == NM_802_11_MODE_INFRA) || (mode == NM_802_11_MODE_AP) + || (mode == NM_802_11_MODE_ADHOC) || (mode == NM_802_11_MODE_MESH), + FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + + /* nl80211 probably doesn't need this */ + return klass->set_mode ? klass->set_mode(data, mode) : TRUE; +} + +gboolean +nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->set_powersave ? klass->set_powersave(data, powersave) : TRUE; +} + +NMSettingWirelessWakeOnWLan +nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + + return klass->get_wake_on_wlan ? klass->get_wake_on_wlan(data) + : NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; +} + +gboolean +nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->set_wake_on_wlan ? klass->set_wake_on_wlan(data, wowl) : FALSE; +} + +guint32 +nm_wifi_utils_get_freq(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, 0); + return NM_WIFI_UTILS_GET_CLASS(data)->get_freq(data); +} + +guint32 +nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + g_return_val_if_fail(data != NULL, 0); + g_return_val_if_fail(freqs != NULL, 0); + return NM_WIFI_UTILS_GET_CLASS(data)->find_freq(data, freqs); +} + +gboolean +nm_wifi_utils_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + g_return_val_if_fail(data != NULL, FALSE); + + return NM_WIFI_UTILS_GET_CLASS(data)->get_station(data, out_bssid, out_quality, out_rate); +} + +gboolean +nm_wifi_utils_is_wifi(int dirfd, const char *ifname) +{ + g_return_val_if_fail(dirfd >= 0, FALSE); + + if (faccessat(dirfd, "phy80211", F_OK, 0) == 0) + return TRUE; +#if HAVE_WEXT + if (nm_wifi_utils_wext_is_wifi(ifname)) + return TRUE; +#endif + return FALSE; +} + +/* OLPC Mesh-only functions */ + +guint32 +nm_wifi_utils_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->get_mesh_channel != NULL, FALSE); + + return klass->get_mesh_channel(data); +} + +gboolean +nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + g_return_val_if_fail(channel <= 13, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->set_mesh_channel != NULL, FALSE); + + return klass->set_mesh_channel(data, channel); +} + +gboolean +nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->set_mesh_ssid != NULL, FALSE); + + return klass->set_mesh_ssid(data, ssid, len); +} + +gboolean +nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->indicate_addressing_running ? klass->indicate_addressing_running(data, running) + : FALSE; +} diff --git a/src/core/platform/wifi/nm-wifi-utils.h b/src/core/platform/wifi/nm-wifi-utils.h new file mode 100644 index 0000000..a252e43 --- /dev/null +++ b/src/core/platform/wifi/nm-wifi-utils.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __WIFI_UTILS_H__ +#define __WIFI_UTILS_H__ + +#include + +#include "nm-dbus-interface.h" +#include "nm-setting-wireless.h" +#include "nm-platform/nm-netlink.h" + +typedef struct NMWifiUtils NMWifiUtils; + +#define NM_TYPE_WIFI_UTILS (nm_wifi_utils_get_type()) +#define NM_WIFI_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS, NMWifiUtils)) +#define NM_WIFI_UTILS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass)) +#define NM_IS_WIFI_UTILS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS)) +#define NM_IS_WIFI_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS)) +#define NM_WIFI_UTILS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass)) + +GType nm_wifi_utils_get_type(void); + +gboolean nm_wifi_utils_is_wifi(int dirfd, const char *ifname); + +NMWifiUtils *nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan); + +NMDeviceWifiCapabilities nm_wifi_utils_get_caps(NMWifiUtils *data); + +NM80211Mode nm_wifi_utils_get_mode(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_mode(NMWifiUtils *data, const NM80211Mode mode); + +/* Returns frequency in MHz */ +guint32 nm_wifi_utils_get_freq(NMWifiUtils *data); + +/* Return the first supported frequency in the zero-terminated list. + * Frequencies are specified in MHz. */ +guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs); + +/* + * @out_bssid: must be NULL or an ETH_ALEN-byte buffer + * @out_quality: receives signal quality in 0 - 100% range if not NULL + * @out_rate: receives current bitrate in Kbps if not NULL + * + * Returns %TRUE on succcess. + */ +gboolean nm_wifi_utils_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + +/* Tells the driver DHCP or SLAAC is running */ +gboolean nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running); + +gboolean nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave); + +NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl); + +/* OLPC Mesh-only functions */ +guint32 nm_wifi_utils_get_mesh_channel(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel); + +gboolean nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len); + +#endif /* __WIFI_UTILS_H__ */ diff --git a/src/core/platform/wpan/nm-wpan-utils.c b/src/core/platform/wpan/nm-wpan-utils.c new file mode 100644 index 0000000..96897ae --- /dev/null +++ b/src/core/platform/wpan/nm-wpan-utils.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-wpan-utils.h" + +#include + +#include "nm-platform/nm-netlink.h" +#include "platform/linux/nl802154.h" +#include "nm-platform/nm-platform-utils.h" + +#define _NMLOG_PREFIX_NAME "wpan-nl802154" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + char _ifname_buf[IFNAMSIZ]; \ + const char *_ifname = self ? nmp_utils_if_indextoname(self->ifindex, _ifname_buf) : NULL; \ + \ + nm_log((level), \ + (domain), \ + _ifname ?: NULL, \ + NULL, \ + "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +struct NMWpanUtils { + GObject parent; + int ifindex; + struct nl_sock *nl_sock; + int id; +}; + +typedef struct { + GObjectClass parent; +} NMWpanUtilsClass; + +G_DEFINE_TYPE(NMWpanUtils, nm_wpan_utils, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static int +ack_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_STOP; +} + +static int +finish_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_SKIP; +} + +static int +error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *done = arg; + *done = err->error; + return NL_SKIP; +} + +static struct nl_msg * +_nl802154_alloc_msg(int id, int ifindex, guint32 cmd, guint32 flags) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc(); + genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32(msg, NL802154_ATTR_IFINDEX, ifindex); + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +nl802154_alloc_msg(NMWpanUtils *self, guint32 cmd, guint32 flags) +{ + return _nl802154_alloc_msg(self->id, self->ifindex, cmd, flags); +} + +static int +nl802154_send_and_recv(NMWpanUtils * self, + struct nl_msg *msg, + int (*valid_handler)(struct nl_msg *, void *), + void *valid_data) +{ + int err; + int done = 0; + const struct nl_cb cb = { + .err_cb = error_handler, + .err_arg = &done, + .finish_cb = finish_handler, + .finish_arg = &done, + .ack_cb = ack_handler, + .ack_arg = &done, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg != NULL, -ENOMEM); + + err = nl_send_auto(self->nl_sock, msg); + if (err < 0) + return err; + + /* Loop until one of our NL callbacks says we're done; on success + * done will be 1, on error it will be < 0. + */ + while (!done) { + err = nl_recvmsgs(self->nl_sock, &cb); + if (err < 0 && err != -EAGAIN) { + _LOGW(LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s", err, nm_strerror(err)); + break; + } + } + + if (err >= 0 && done < 0) + err = done; + return err; +} + +struct nl802154_interface { + guint16 pan_id; + guint16 short_addr; + + gboolean valid; +}; + +static int +nl802154_get_interface_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy nl802154_policy[] = { + [NL802154_ATTR_PAN_ID] = {.type = NLA_U16}, + [NL802154_ATTR_SHORT_ADDR] = {.type = NLA_U16}, + }; + struct nlattr * tb[G_N_ELEMENTS(nl802154_policy)]; + struct nl802154_interface *info = arg; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nl802154_policy) < 0) + return NL_SKIP; + + if (tb[NL802154_ATTR_PAN_ID]) + info->pan_id = le16toh(nla_get_u16(tb[NL802154_ATTR_PAN_ID])); + + if (tb[NL802154_ATTR_SHORT_ADDR]) + info->short_addr = le16toh(nla_get_u16(tb[NL802154_ATTR_SHORT_ADDR])); + + info->valid = TRUE; + + return NL_SKIP; +} + +static void +nl802154_get_interface(NMWpanUtils *self, struct nl802154_interface *interface) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + memset(interface, 0, sizeof(*interface)); + + msg = nl802154_alloc_msg(self, NL802154_CMD_GET_INTERFACE, 0); + + nl802154_send_and_recv(self, msg, nl802154_get_interface_handler, interface); +} + +/*****************************************************************************/ + +guint16 +nm_wpan_utils_get_pan_id(NMWpanUtils *self) +{ + struct nl802154_interface interface; + + nl802154_get_interface(self, &interface); + + return interface.pan_id; +} + +gboolean +nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_PAN_ID, 0); + NLA_PUT_U16(msg, NL802154_ATTR_PAN_ID, htole16(pan_id)); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +guint16 +nm_wpan_utils_get_short_addr(NMWpanUtils *self) +{ + struct nl802154_interface interface; + + nl802154_get_interface(self, &interface); + + return interface.short_addr; +} + +gboolean +nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_SHORT_ADDR, 0); + NLA_PUT_U16(msg, NL802154_ATTR_SHORT_ADDR, htole16(short_addr)); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +gboolean +nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_CHANNEL, 0); + NLA_PUT_U8(msg, NL802154_ATTR_PAGE, page); + NLA_PUT_U8(msg, NL802154_ATTR_CHANNEL, channel); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +/*****************************************************************************/ + +static void +nm_wpan_utils_init(NMWpanUtils *self) +{} + +static void +nm_wpan_utils_class_init(NMWpanUtilsClass *klass) +{} + +NMWpanUtils * +nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan) +{ + NMWpanUtils *self; + + g_return_val_if_fail(ifindex > 0, NULL); + + if (!genl) + return NULL; + + self = g_object_new(NM_TYPE_WPAN_UTILS, NULL); + self->ifindex = ifindex; + self->nl_sock = genl; + self->id = genl_ctrl_resolve(genl, "nl802154"); + + if (self->id < 0) { + _LOGD(LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\""); + g_object_unref(self); + return NULL; + } + + return self; +} diff --git a/src/core/platform/wpan/nm-wpan-utils.h b/src/core/platform/wpan/nm-wpan-utils.h new file mode 100644 index 0000000..940c2c9 --- /dev/null +++ b/src/core/platform/wpan/nm-wpan-utils.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __WPAN_UTILS_H__ +#define __WPAN_UTILS_H__ + +#include + +#include "nm-dbus-interface.h" +#include "nm-platform/nm-netlink.h" + +typedef struct NMWpanUtils NMWpanUtils; + +#define NM_TYPE_WPAN_UTILS (nm_wpan_utils_get_type()) +#define NM_WPAN_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WPAN_UTILS, NMWpanUtils)) +#define NM_WPAN_UTILS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass)) +#define NM_IS_WPAN_UTILS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WPAN_UTILS)) +#define NM_IS_WPAN_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WPAN_UTILS)) +#define NM_WPAN_UTILS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass)) + +GType nm_wpan_utils_get_type(void); + +NMWpanUtils *nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan); + +guint16 nm_wpan_utils_get_pan_id(NMWpanUtils *self); +gboolean nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id); + +guint16 nm_wpan_utils_get_short_addr(NMWpanUtils *self); +gboolean nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr); + +gboolean nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel); + +#endif /* __WPAN_UTILS_H__ */ diff --git a/src/core/ppp/meson.build b/src/core/ppp/meson.build new file mode 100644 index 0000000..991f0b3 --- /dev/null +++ b/src/core/ppp/meson.build @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +nm_pppd_plugin = shared_module( + 'nm-pppd-plugin', + name_prefix: '', + sources: 'nm-pppd-plugin.c', + dependencies: libnm_core_nm_default_dep, + c_args: [ + '-DG_LOG_DOMAIN="nm-pppd-plugin"', + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB', + ], + install: true, + install_dir: pppd_plugin_dir, +) + +linker_script = join_paths(meson.current_source_dir(), 'nm-ppp-plugin.ver') + +core_plugins += shared_module( + 'nm-ppp-plugin', + sources: [ + 'nm-ppp-manager.c', + ], + dependencies: core_plugin_dep, + c_args: daemon_c_flags, + link_args: '-Wl,--version-script,@0@'.format(linker_script), + link_depends: linker_script, + install: true, + install_dir: nm_plugindir, +) diff --git a/src/core/ppp/nm-ppp-manager-call.c b/src/core/ppp/nm-ppp-manager-call.c new file mode 100644 index 0000000..b07c3df --- /dev/null +++ b/src/core/ppp/nm-ppp-manager-call.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-ppp-manager-call.h" + +#include +#include + +#include "nm-manager.h" +#include "nm-core-utils.h" +#include "nm-ppp-plugin-api.h" + +#define PPP_PLUGIN_PATH NMPLUGINDIR "/libnm-ppp-plugin.so" + +/*****************************************************************************/ + +static NMPPPOps *ppp_ops = NULL; + +NMPPPManager * +nm_ppp_manager_create(const char *iface, GError **error) +{ + NMPPPManager *ret; + GModule * plugin; + GError * error_local = NULL; + NMPPPOps * ops; + struct stat st; + + if (G_UNLIKELY(!ppp_ops)) { + if (stat(PPP_PLUGIN_PATH, &st) != 0) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_MISSING_PLUGIN, + "the PPP plugin " PPP_PLUGIN_PATH " is not installed"); + return NULL; + } + + if (!nm_utils_validate_plugin(PPP_PLUGIN_PATH, &st, &error_local)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_MISSING_PLUGIN, + "could not load the PPP plugin " PPP_PLUGIN_PATH ": %s", + error_local->message); + g_clear_error(&error_local); + return NULL; + } + + plugin = g_module_open(PPP_PLUGIN_PATH, G_MODULE_BIND_LOCAL); + if (!plugin) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_MISSING_PLUGIN, + "could not load the PPP plugin " PPP_PLUGIN_PATH ": %s", + g_module_error()); + return NULL; + } + + if (!g_module_symbol(plugin, "ppp_ops", (gpointer) &ops)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_MISSING_PLUGIN, + "error loading the PPP plugin: %s", + g_module_error()); + return NULL; + } + + /* after loading glib types from the plugin, we cannot unload the library anymore. + * Make it resident. */ + g_module_make_resident(plugin); + + nm_assert(ops); + nm_assert(ops->create); + nm_assert(ops->start); + nm_assert(ops->stop); + nm_assert(ops->stop_cancel); + + ppp_ops = ops; + + nm_log_info(LOGD_CORE | LOGD_PPP, "loaded PPP plugin " PPP_PLUGIN_PATH); + } + + ret = ppp_ops->create(iface); + g_return_val_if_fail(ret, NULL); + return ret; +} + +void +nm_ppp_manager_set_route_parameters(NMPPPManager *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric) +{ + g_return_if_fail(ppp_ops); + + ppp_ops->set_route_parameters(self, + ip4_route_table, + ip4_route_metric, + ip6_route_table, + ip6_route_metric); +} + +gboolean +nm_ppp_manager_start(NMPPPManager *self, + NMActRequest *req, + const char * ppp_name, + guint32 timeout_secs, + guint baud_override, + GError ** err) +{ + g_return_val_if_fail(ppp_ops, FALSE); + + return ppp_ops->start(self, req, ppp_name, timeout_secs, baud_override, err); +} + +NMPPPManagerStopHandle * +nm_ppp_manager_stop(NMPPPManager * self, + GCancellable * cancellable, + NMPPPManagerStopCallback callback, + gpointer user_data) +{ + g_return_val_if_fail(ppp_ops, NULL); + + return ppp_ops->stop(self, cancellable, callback, user_data); +} + +void +nm_ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle) +{ + g_return_if_fail(ppp_ops); + g_return_if_fail(handle); + + ppp_ops->stop_cancel(handle); +} diff --git a/src/core/ppp/nm-ppp-manager-call.h b/src/core/ppp/nm-ppp-manager-call.h new file mode 100644 index 0000000..c831cf0 --- /dev/null +++ b/src/core/ppp/nm-ppp-manager-call.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_PPP_MANAGER_CALL_H__ +#define __NM_PPP_MANAGER_CALL_H__ + +#include "nm-ppp-manager.h" + +NMPPPManager *nm_ppp_manager_create(const char *iface, GError **error); + +void nm_ppp_manager_set_route_parameters(NMPPPManager *ppp_manager, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric); + +gboolean nm_ppp_manager_start(NMPPPManager *self, + NMActRequest *req, + const char * ppp_name, + guint32 timeout_secs, + guint baud_override, + GError ** error); + +NMPPPManagerStopHandle *nm_ppp_manager_stop(NMPPPManager * self, + GCancellable * cancellable, + NMPPPManagerStopCallback callback, + gpointer user_data); + +void nm_ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle); + +#endif /* __NM_PPP_MANAGER_CALL_H__ */ diff --git a/src/core/ppp/nm-ppp-manager.c b/src/core/ppp/nm-ppp-manager.c new file mode 100644 index 0000000..939eca3 --- /dev/null +++ b/src/core/ppp/nm-ppp-manager.c @@ -0,0 +1,1458 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-ppp-manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef aligned_u64 + #define aligned_u64 unsigned long long __attribute__((aligned(8))) +#endif +#include +#include +#include + +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-core-internal.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-dbus-object.h" + +#include "nm-pppd-plugin.h" +#include "nm-ppp-plugin-api.h" +#include "nm-ppp-status.h" + +#define NM_PPPD_PLUGIN PPPD_PLUGIN_DIR "/nm-pppd-plugin.so" + +static NM_CACHED_QUARK_FCN("ppp-manager-secret-tries", ppp_manager_secret_tries_quark); + +/*****************************************************************************/ + +#define NM_TYPE_PPP_MANAGER (nm_ppp_manager_get_type()) +#define NM_PPP_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PPP_MANAGER, NMPPPManager)) +#define NM_PPP_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PPP_MANAGER, NMPPPManagerClass)) +#define NM_IS_PPP_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PPP_MANAGER)) +#define NM_IS_PPP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PPP_MANAGER)) +#define NM_PPP_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PPP_MANAGER, NMPPPManagerClass)) + +GType nm_ppp_manager_get_type(void); + +/*****************************************************************************/ + +enum { + STATE_CHANGED, + IFINDEX_SET, + IP4_CONFIG, + IP6_CONFIG, + STATS, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT_IFACE, ); + +typedef struct { + GPid pid; + + char *parent_iface; + char *ip_iface; + int ifindex; + + NMActRequest * act_req; + GDBusMethodInvocation * pending_secrets_context; + NMActRequestGetSecretsCallId *secrets_id; + const char * secrets_setting_name; + + guint ppp_watch_id; + guint ppp_timeout_handler; + + /* Monitoring */ + int monitor_fd; + guint monitor_id; + + guint32 ip4_route_table; + guint32 ip4_route_metric; + guint32 ip6_route_table; + guint32 ip6_route_metric; +} NMPPPManagerPrivate; + +struct _NMPPPManager { + NMDBusObject parent; + NMPPPManagerPrivate _priv; +}; + +typedef struct { + NMDBusObjectClass parent; +} NMPPPManagerClass; + +G_DEFINE_TYPE(NMPPPManager, nm_ppp_manager, NM_TYPE_DBUS_OBJECT) + +#define NM_PPP_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMPPPManager, NM_IS_PPP_MANAGER, NMDBusObject) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PPP +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ppp-manager", __VA_ARGS__) + +/*****************************************************************************/ + +static void _ppp_cleanup(NMPPPManager *self); + +static NMPPPManagerStopHandle *_ppp_manager_stop(NMPPPManager * self, + GCancellable * cancellable, + NMPPPManagerStopCallback callback, + gpointer user_data); + +static void _ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle); + +/*****************************************************************************/ + +static void +_ppp_manager_set_route_parameters(NMPPPManager *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric) +{ + NMPPPManagerPrivate *priv; + + g_return_if_fail(NM_IS_PPP_MANAGER(self)); + + priv = NM_PPP_MANAGER_GET_PRIVATE(self); + if (priv->ip4_route_table != ip4_route_table || priv->ip4_route_metric != ip4_route_metric + || priv->ip6_route_table != ip6_route_table || priv->ip6_route_metric != ip6_route_metric) { + priv->ip4_route_table = ip4_route_table; + priv->ip4_route_metric = ip4_route_metric; + priv->ip6_route_table = ip6_route_table; + priv->ip6_route_metric = ip6_route_metric; + + _LOGT("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u", + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } +} + +/*****************************************************************************/ + +static gboolean +monitor_cb(gpointer user_data) +{ + NMPPPManager * self = NM_PPP_MANAGER(user_data); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + const char * ifname; + int errsv; + + ifname = nm_platform_link_get_name(NM_PLATFORM_GET, priv->ifindex); + + if (ifname) { + struct ppp_stats stats = {}; + struct ifreq req = { + .ifr_data = (caddr_t) &stats, + }; + + nm_utils_ifname_cpy(req.ifr_name, ifname); + if (ioctl(priv->monitor_fd, SIOCGPPPSTATS, &req) < 0) { + errsv = errno; + if (errsv != ENODEV) + _LOGW("could not read ppp stats: %s", nm_strerror_native(errsv)); + } else { + g_signal_emit(self, + signals[STATS], + 0, + (guint) stats.p.ppp_ibytes, + (guint) stats.p.ppp_obytes); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +monitor_stats(NMPPPManager *self) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + int errsv; + + /* already monitoring */ + if (priv->monitor_fd >= 0) + return; + + priv->monitor_fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (priv->monitor_fd < 0) { + errsv = errno; + _LOGW("could not monitor PPP stats: %s", nm_strerror_native(errsv)); + return; + } + + g_warn_if_fail(priv->monitor_id == 0); + if (priv->monitor_id) + g_source_remove(priv->monitor_id); + priv->monitor_id = g_timeout_add_seconds(5, monitor_cb, self); +} + +/*****************************************************************************/ + +static void +cancel_get_secrets(NMPPPManager *self) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + + if (priv->secrets_id) + nm_act_request_cancel_secrets(priv->act_req, priv->secrets_id); + + g_return_if_fail(!priv->secrets_id && !priv->secrets_setting_name); +} + +static gboolean +extract_details_from_connection(NMConnection *connection, + const char * secrets_setting_name, + const char ** username, + const char ** password, + GError ** error) +{ + NMSettingConnection *s_con; + NMSetting * setting; + const char * setting_name; + + g_return_val_if_fail(connection != NULL, FALSE); + g_return_val_if_fail(username != NULL, FALSE); + g_return_val_if_fail(password != NULL, FALSE); + + if (secrets_setting_name) + setting_name = secrets_setting_name; + else { + /* Get the setting matching the connection type */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + setting_name = nm_setting_connection_get_connection_type(s_con); + g_assert(setting_name); + + /* In case of bluetooth connection, use GSM or CDMA setting */ + if (strcmp(setting_name, NM_SETTING_BLUETOOTH_SETTING_NAME) == 0) { + if (nm_connection_get_setting_gsm(connection)) + setting_name = NM_SETTING_GSM_SETTING_NAME; + else + setting_name = NM_SETTING_CDMA_SETTING_NAME; + } + } + + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (!setting) { + /* This shouldn't ever happen */ + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Missing type-specific setting; no secrets could be found."); + return FALSE; + } + + if (NM_IS_SETTING_PPPOE(setting)) { + *username = nm_setting_pppoe_get_username(NM_SETTING_PPPOE(setting)); + *password = nm_setting_pppoe_get_password(NM_SETTING_PPPOE(setting)); + } else if (NM_IS_SETTING_ADSL(setting)) { + *username = nm_setting_adsl_get_username(NM_SETTING_ADSL(setting)); + *password = nm_setting_adsl_get_password(NM_SETTING_ADSL(setting)); + } else if (NM_IS_SETTING_GSM(setting)) { + *username = nm_setting_gsm_get_username(NM_SETTING_GSM(setting)); + *password = nm_setting_gsm_get_password(NM_SETTING_GSM(setting)); + } else if (NM_IS_SETTING_CDMA(setting)) { + *username = nm_setting_cdma_get_username(NM_SETTING_CDMA(setting)); + *password = nm_setting_cdma_get_password(NM_SETTING_CDMA(setting)); + } + + return TRUE; +} + +static void +ppp_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * settings_connection, /* unused (we pass NULL here) */ + GError * error, + gpointer user_data) +{ + NMPPPManager * self = NM_PPP_MANAGER(user_data); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + const char * username = NULL; + const char * password = NULL; + GError * local = NULL; + NMConnection * applied_connection; + + g_return_if_fail(priv->pending_secrets_context != NULL); + g_return_if_fail(req == priv->act_req); + g_return_if_fail(call_id == priv->secrets_id); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto out; + + if (error) { + _LOGW("%s", error->message); + g_dbus_method_invocation_return_gerror(priv->pending_secrets_context, error); + goto out; + } + + applied_connection = nm_act_request_get_applied_connection(req); + + if (!extract_details_from_connection(applied_connection, + priv->secrets_setting_name, + &username, + &password, + &local)) { + _LOGW("%s", local->message); + g_dbus_method_invocation_take_error(priv->pending_secrets_context, local); + goto out; + } + + /* This is sort of a hack but... + * pppd plugin only ever needs username and password. Passing the full + * connection there would mean some bloat: the plugin would need to link + * against libnm just to parse this. So instead, let's just send what + * it needs. + */ + g_dbus_method_invocation_return_value(priv->pending_secrets_context, + g_variant_new("(ss)", username ?: "", password ?: "")); + +out: + priv->pending_secrets_context = NULL; + priv->secrets_id = NULL; + priv->secrets_setting_name = NULL; +} + +static void +impl_ppp_manager_need_secrets(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMPPPManager * self = NM_PPP_MANAGER(obj); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + NMConnection * applied_connection; + const char * username = NULL; + const char * password = NULL; + guint32 tries; + gs_unref_ptrarray GPtrArray *hints = NULL; + GError * error = NULL; + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(priv->act_req)); + + applied_connection = nm_act_request_get_applied_connection(priv->act_req); + + priv->secrets_setting_name = nm_connection_need_secrets(applied_connection, &hints); + if (!priv->secrets_setting_name) { + /* Use existing secrets from the connection */ + if (extract_details_from_connection(applied_connection, + NULL, + &username, + &password, + &error)) { + /* Send existing secrets to the PPP plugin */ + priv->pending_secrets_context = invocation; + ppp_secrets_cb(priv->act_req, priv->secrets_id, NULL, NULL, self); + } else { + _LOGW("%s", error->message); + g_dbus_method_invocation_take_error(priv->pending_secrets_context, error); + } + return; + } + + /* Only ask for completely new secrets after retrying them once; some devices + * appear to ask a few times when they actually don't even care what you + * pass back. + */ + tries = GPOINTER_TO_UINT( + g_object_get_qdata(G_OBJECT(applied_connection), ppp_manager_secret_tries_quark())); + if (tries > 1) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + + if (hints) + g_ptr_array_add(hints, NULL); + + priv->secrets_id = nm_act_request_get_secrets(priv->act_req, + FALSE, + priv->secrets_setting_name, + flags, + hints ? (const char *const *) hints->pdata : NULL, + ppp_secrets_cb, + self); + g_object_set_qdata(G_OBJECT(applied_connection), + ppp_manager_secret_tries_quark(), + GUINT_TO_POINTER(++tries)); + priv->pending_secrets_context = invocation; +} + +static void +impl_ppp_manager_set_state(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMPPPManager *self = NM_PPP_MANAGER(obj); + guint32 state; + + g_variant_get(parameters, "(u)", &state); + g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) state); + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static void +impl_ppp_manager_set_ifindex(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMPPPManager * self = NM_PPP_MANAGER(obj); + NMPPPManagerPrivate * priv = NM_PPP_MANAGER_GET_PRIVATE(self); + const NMPlatformLink *plink = NULL; + nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL; + gint32 ifindex; + + g_variant_get(parameters, "(i)", &ifindex); + + if (priv->ifindex >= 0) { + if (priv->ifindex == ifindex) + _LOGD("set-ifindex: ignore repeated calls setting ifindex to %d", (int) ifindex); + else + _LOGW("set-ifindex: can't change the ifindex from %d to %d", + priv->ifindex, + (int) ifindex); + goto out; + } + + if (ifindex > 0) { + plink = nm_platform_link_get(NM_PLATFORM_GET, ifindex); + if (!plink) { + nm_platform_process_events(NM_PLATFORM_GET); + plink = nm_platform_link_get(NM_PLATFORM_GET, ifindex); + } + } + + if (!plink) { + _LOGW("set-ifindex: unknown interface with ifindex %d", ifindex); + ifindex = 0; + } else { + obj_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink)); + _LOGD("set-ifindex: %d, name \"%s\"", (int) ifindex, plink->name); + } + + priv->ifindex = ifindex; + + g_signal_emit(self, signals[IFINDEX_SET], 0, ifindex, plink ? plink->name : NULL); + +out: + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static gboolean +set_ip_config_common(NMPPPManager *self, GVariant *config_dict, guint32 *out_mtu) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + NMConnection * applied_connection; + NMSettingPpp * s_ppp; + + if (priv->ifindex <= 0) + return FALSE; + + /* Got successful IP config; obviously the secrets worked */ + applied_connection = nm_act_request_get_applied_connection(priv->act_req); + g_object_set_qdata(G_OBJECT(applied_connection), ppp_manager_secret_tries_quark(), NULL); + + if (out_mtu) { + /* Get any custom MTU */ + s_ppp = nm_connection_get_setting_ppp(applied_connection); + *out_mtu = s_ppp ? nm_setting_ppp_get_mtu(s_ppp) : 0; + } + + monitor_stats(self); + return TRUE; +} + +static void +impl_ppp_manager_set_ip4_config(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMPPPManager * self = NM_PPP_MANAGER(obj); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + gs_unref_object NMIP4Config *config = NULL; + NMPlatformIP4Address address; + guint32 u32, mtu; + GVariantIter * iter; + gs_unref_variant GVariant *config_dict = NULL; + + _LOGI("(IPv4 Config Get) reply received."); + + g_variant_get(parameters, "(@a{sv})", &config_dict); + + nm_clear_g_source(&priv->ppp_timeout_handler); + + if (!set_ip_config_common(self, config_dict, &mtu)) + goto out; + + config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), priv->ifindex); + + if (mtu) + nm_ip4_config_set_mtu(config, mtu, NM_IP_CONFIG_SOURCE_PPP); + + memset(&address, 0, sizeof(address)); + address.plen = 32; + + if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_ADDRESS, "u", &u32)) + address.address = u32; + + if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_GATEWAY, "u", &u32)) { + const NMPlatformIP4Route r = { + .ifindex = priv->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_PPP, + .gateway = u32, + .table_coerced = nm_platform_route_table_coerce(priv->ip4_route_table), + .metric = priv->ip4_route_metric, + }; + + nm_ip4_config_add_route(config, &r, NULL); + address.peer_address = u32; + } else + address.peer_address = address.address; + + if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_PREFIX, "u", &u32)) + address.plen = u32; + + if (address.address && address.plen && address.plen <= 32) { + address.addr_source = NM_IP_CONFIG_SOURCE_PPP; + nm_ip4_config_add_address(config, &address); + } else { + _LOGE("invalid IPv4 address received!"); + goto out; + } + + if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_DNS, "au", &iter)) { + while (g_variant_iter_next(iter, "u", &u32)) + nm_ip4_config_add_nameserver(config, u32); + g_variant_iter_free(iter); + } + + if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_WINS, "au", &iter)) { + while (g_variant_iter_next(iter, "u", &u32)) + nm_ip4_config_add_wins(config, u32); + g_variant_iter_free(iter); + } + + /* Push the IP4 config up to the device */ + g_signal_emit(self, signals[IP4_CONFIG], 0, config); + +out: + g_dbus_method_invocation_return_value(invocation, NULL); +} + +/* Converts the named Interface Identifier item to an IPv6 LL address and + * returns the IID. + */ +static gboolean +iid_value_to_ll6_addr(GVariant * dict, + const char * prop, + struct in6_addr * out_addr, + NMUtilsIPv6IfaceId *out_iid) +{ + guint64 iid; + + if (!g_variant_lookup(dict, prop, "t", &iid)) { + _LOGD("pppd plugin property '%s' missing or not a uint64", prop); + return FALSE; + } + g_return_val_if_fail(iid != 0, FALSE); + + /* Construct an IPv6 LL address from the interface identifier. See + * http://tools.ietf.org/html/rfc4291#section-2.5.1 (IPv6) and + * http://tools.ietf.org/html/rfc5072#section-4.1 (IPv6 over PPP). + */ + memset(out_addr->s6_addr, 0, sizeof(out_addr->s6_addr)); + out_addr->s6_addr16[0] = htons(0xfe80); + memcpy(out_addr->s6_addr + 8, &iid, sizeof(iid)); + if (out_iid) + nm_utils_ipv6_interface_identifier_get_from_addr(out_iid, out_addr); + return TRUE; +} + +static void +impl_ppp_manager_set_ip6_config(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMPPPManager * self = NM_PPP_MANAGER(obj); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + gs_unref_object NMIP6Config *config = NULL; + NMPlatformIP6Address addr; + struct in6_addr a; + NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT; + gboolean has_peer = FALSE; + gs_unref_variant GVariant *config_dict = NULL; + + _LOGI("(IPv6 Config Get) reply received."); + + g_variant_get(parameters, "(@a{sv})", &config_dict); + + nm_clear_g_source(&priv->ppp_timeout_handler); + + if (!set_ip_config_common(self, config_dict, NULL)) + goto out; + + config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), priv->ifindex); + + memset(&addr, 0, sizeof(addr)); + addr.plen = 64; + + if (iid_value_to_ll6_addr(config_dict, NM_PPP_IP6_CONFIG_PEER_IID, &a, NULL)) { + const NMPlatformIP6Route r = { + .ifindex = priv->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_PPP, + .gateway = a, + .table_coerced = nm_platform_route_table_coerce(priv->ip6_route_table), + .metric = priv->ip6_route_metric, + }; + + nm_ip6_config_add_route(config, &r, NULL); + addr.peer_address = a; + has_peer = TRUE; + } + + if (iid_value_to_ll6_addr(config_dict, NM_PPP_IP6_CONFIG_OUR_IID, &addr.address, &iid)) { + if (!has_peer) + addr.peer_address = addr.address; + nm_ip6_config_add_address(config, &addr); + + /* Push the IPv6 config and interface identifier up to the device */ + g_signal_emit(self, signals[IP6_CONFIG], 0, &iid, config); + } else + _LOGE("invalid IPv6 address received!"); + +out: + g_dbus_method_invocation_return_value(invocation, NULL); +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + pppd_exit_code_to_str, + int, + NM_UTILS_LOOKUP_DEFAULT("Unknown error"), + NM_UTILS_LOOKUP_STR_ITEM(1, "Fatal pppd error"); + NM_UTILS_LOOKUP_STR_ITEM(2, "pppd options error"), + NM_UTILS_LOOKUP_STR_ITEM(3, "No root priv error"), + NM_UTILS_LOOKUP_STR_ITEM(4, "No ppp module error"), + NM_UTILS_LOOKUP_STR_ITEM(5, "pppd received a signal"), + NM_UTILS_LOOKUP_STR_ITEM(6, "Serial port lock failed"), + NM_UTILS_LOOKUP_STR_ITEM(7, "Serial port open failed"), + NM_UTILS_LOOKUP_STR_ITEM(8, "Connect script failed"), + NM_UTILS_LOOKUP_STR_ITEM(9, "Pty program error"), + NM_UTILS_LOOKUP_STR_ITEM(10, "PPP negotiation failed"), + NM_UTILS_LOOKUP_STR_ITEM(11, "Peer didn't authenticatie itself"), + NM_UTILS_LOOKUP_STR_ITEM(12, "Link idle: Idle Seconds reached."), + NM_UTILS_LOOKUP_STR_ITEM(13, "Connect time limit reached."), + NM_UTILS_LOOKUP_STR_ITEM(14, "Callback negotiated, call should come back."), + NM_UTILS_LOOKUP_STR_ITEM(15, "Lack of LCP echo responses"), + NM_UTILS_LOOKUP_STR_ITEM(16, "A modem hung up the phone"), + NM_UTILS_LOOKUP_STR_ITEM(17, "Loopback detected"), + NM_UTILS_LOOKUP_STR_ITEM(18, "The init script failed"), + NM_UTILS_LOOKUP_STR_ITEM(19, + "Authentication error. " + "We failed to authenticate ourselves to the peer. " + "Maybe bad account or password?"), ); + +static void +ppp_watch_cb(GPid pid, int status, gpointer user_data) +{ + NMPPPManager * self = NM_PPP_MANAGER(user_data); + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + int err; + const long long lpid = (long long) pid; + + g_return_if_fail(pid == priv->pid); + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + if (err) { + _LOGW("pppd pid %lld exited with error %d: %s", lpid, err, pppd_exit_code_to_str(err)); + } else + _LOGD("pppd pid %lld exited with success", lpid); + } else if (WIFSTOPPED(status)) { + _LOGW("pppd pid %lld stopped unexpectedly with signal %d", lpid, WSTOPSIG(status)); + } else if (WIFSIGNALED(status)) { + _LOGW("pppd pid %lld died with signal %d", lpid, WTERMSIG(status)); + } else + _LOGW("pppd pid %lld died from an unknown cause", lpid); + + priv->pid = 0; + priv->ppp_watch_id = 0; + _ppp_cleanup(self); + g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) NM_PPP_STATUS_DEAD); +} + +static gboolean +pppd_timed_out(gpointer data) +{ + NMPPPManager *self = NM_PPP_MANAGER(data); + + _LOGW("pppd timed out or didn't initialize our dbus module"); + _ppp_manager_stop(self, NULL, NULL, NULL); + + g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) NM_PPP_STATUS_DEAD); + + return FALSE; +} + +static GPtrArray * +create_pppd_cmd_line(NMPPPManager * self, + NMSettingPpp * setting, + NMSettingPppoe *pppoe, + NMSettingAdsl * adsl, + const char * ppp_name, + guint baud_override, + gboolean ip4_enabled, + gboolean ip6_enabled, + GError ** err) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + const char * pppd_binary = NULL; + gs_unref_ptrarray GPtrArray *cmd = NULL; + gboolean ppp_debug; + + g_return_val_if_fail(setting != NULL, NULL); + +#ifndef PPPD_PATH + #define PPPD_PATH NULL +#endif + + pppd_binary = nm_utils_find_helper("pppd", PPPD_PATH, err); + if (!pppd_binary) + return NULL; + + if (!ip4_enabled && !ip6_enabled) { + g_set_error_literal(err, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Neither IPv4 or IPv6 allowed."); + return NULL; + } + + cmd = g_ptr_array_new_with_free_func(g_free); + + nm_strv_ptrarray_add_string_dup(cmd, pppd_binary); + + nm_strv_ptrarray_add_string_dup(cmd, "nodetach"); + nm_strv_ptrarray_add_string_dup(cmd, "lock"); + + /* NM handles setting the default route */ + nm_strv_ptrarray_add_string_dup(cmd, "nodefaultroute"); + + if (!ip4_enabled) + nm_strv_ptrarray_add_string_dup(cmd, "noip"); + + if (ip6_enabled) { + /* Allow IPv6 to be configured by IPV6CP */ + nm_strv_ptrarray_add_string_dup(cmd, "ipv6"); + nm_strv_ptrarray_add_string_dup(cmd, ","); + } else + nm_strv_ptrarray_add_string_dup(cmd, "noipv6"); + + ppp_debug = !!getenv("NM_PPP_DEBUG"); + if (nm_logging_enabled(LOGL_DEBUG, LOGD_PPP)) + ppp_debug = TRUE; + + if (ppp_debug) + nm_strv_ptrarray_add_string_dup(cmd, "debug"); + + if (ppp_name) { + nm_strv_ptrarray_add_string_dup(cmd, "user"); + nm_strv_ptrarray_add_string_dup(cmd, ppp_name); + } + + if (pppoe) { + const char *pppoe_service; + + nm_strv_ptrarray_add_string_dup(cmd, "plugin"); + nm_strv_ptrarray_add_string_dup(cmd, "rp-pppoe.so"); + + nm_strv_ptrarray_add_string_concat(cmd, "nic-", priv->parent_iface); + + pppoe_service = nm_setting_pppoe_get_service(pppoe); + if (pppoe_service) { + nm_strv_ptrarray_add_string_dup(cmd, "rp_pppoe_service"); + nm_strv_ptrarray_add_string_dup(cmd, pppoe_service); + } + } else if (adsl) { + const char *protocol = nm_setting_adsl_get_protocol(adsl); + + if (!strcmp(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) { + guint32 vpi = nm_setting_adsl_get_vpi(adsl); + guint32 vci = nm_setting_adsl_get_vci(adsl); + const char *encaps = nm_setting_adsl_get_encapsulation(adsl); + + nm_strv_ptrarray_add_string_dup(cmd, "plugin"); + nm_strv_ptrarray_add_string_dup(cmd, "pppoatm.so"); + + nm_strv_ptrarray_add_string_printf(cmd, "%d.%d", vpi, vci); + + if (g_strcmp0(encaps, NM_SETTING_ADSL_ENCAPSULATION_LLC) == 0) + nm_strv_ptrarray_add_string_dup(cmd, "llc-encaps"); + else /*if (g_strcmp0 (encaps, NM_SETTING_ADSL_ENCAPSULATION_VCMUX) == 0)*/ + nm_strv_ptrarray_add_string_dup(cmd, "vc-encaps"); + + } else if (!strcmp(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) { + nm_strv_ptrarray_add_string_dup(cmd, "plugin"); + nm_strv_ptrarray_add_string_dup(cmd, "rp-pppoe.so"); + nm_strv_ptrarray_add_string_dup(cmd, priv->parent_iface); + } + + nm_strv_ptrarray_add_string_dup(cmd, "noipdefault"); + } else { + nm_strv_ptrarray_add_string_dup(cmd, priv->parent_iface); + /* Don't send some random address as the local address */ + nm_strv_ptrarray_add_string_dup(cmd, "noipdefault"); + } + + if (nm_setting_ppp_get_baud(setting)) + nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_baud(setting)); + else if (baud_override) + nm_strv_ptrarray_add_int(cmd, baud_override); + + /* noauth by default, because we certainly don't have any information + * with which to verify anything the peer gives us if we ask it to + * authenticate itself, which is what 'auth' really means. + */ + nm_strv_ptrarray_add_string_dup(cmd, "noauth"); + + if (nm_setting_ppp_get_refuse_eap(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "refuse-eap"); + if (nm_setting_ppp_get_refuse_pap(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "refuse-pap"); + if (nm_setting_ppp_get_refuse_chap(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "refuse-chap"); + if (nm_setting_ppp_get_refuse_mschap(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "refuse-mschap"); + if (nm_setting_ppp_get_refuse_mschapv2(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "refuse-mschap-v2"); + if (nm_setting_ppp_get_nobsdcomp(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "nobsdcomp"); + if (nm_setting_ppp_get_no_vj_comp(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "novj"); + if (nm_setting_ppp_get_nodeflate(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "nodeflate"); + if (nm_setting_ppp_get_require_mppe(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "require-mppe"); + if (nm_setting_ppp_get_require_mppe_128(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "require-mppe-128"); + if (nm_setting_ppp_get_mppe_stateful(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "mppe-stateful"); + if (nm_setting_ppp_get_crtscts(setting)) + nm_strv_ptrarray_add_string_dup(cmd, "crtscts"); + + /* Always ask for DNS, we don't have to use them if the connection + * overrides the returned servers. + */ + nm_strv_ptrarray_add_string_dup(cmd, "usepeerdns"); + + if (nm_setting_ppp_get_mru(setting)) { + nm_strv_ptrarray_add_string_dup(cmd, "mru"); + nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_mru(setting)); + } + + if (nm_setting_ppp_get_mtu(setting)) { + nm_strv_ptrarray_add_string_dup(cmd, "mtu"); + nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_mtu(setting)); + } + + nm_strv_ptrarray_add_string_dup(cmd, "lcp-echo-failure"); + nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_lcp_echo_failure(setting)); + + nm_strv_ptrarray_add_string_dup(cmd, "lcp-echo-interval"); + nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_lcp_echo_interval(setting)); + + /* Avoid pppd to exit if no traffic going through */ + nm_strv_ptrarray_add_string_dup(cmd, "idle"); + nm_strv_ptrarray_add_string_dup(cmd, "0"); + + nm_strv_ptrarray_add_string_dup(cmd, "ipparam"); + nm_strv_ptrarray_add_string_dup(cmd, nm_dbus_object_get_path(NM_DBUS_OBJECT(self))); + + nm_strv_ptrarray_add_string_dup(cmd, "plugin"); + nm_strv_ptrarray_add_string_dup(cmd, NM_PPPD_PLUGIN); + + if (pppoe && nm_setting_pppoe_get_parent(pppoe)) { + static int unit; + + /* The PPP interface is going to be renamed, so pass a + * different unit each time so that activations don't + * race with each others. */ + nm_strv_ptrarray_add_string_dup(cmd, "unit"); + nm_strv_ptrarray_add_int(cmd, unit); + unit = unit < G_MAXINT ? unit + 1 : 0; + } + + g_ptr_array_add(cmd, NULL); + return g_steal_pointer(&cmd); +} + +static void +pppoe_fill_defaults(NMSettingPpp *setting) +{ + if (!nm_setting_ppp_get_mtu(setting)) + g_object_set(setting, NM_SETTING_PPP_MTU, (guint32) 1492, NULL); + + if (!nm_setting_ppp_get_mru(setting)) + g_object_set(setting, NM_SETTING_PPP_MRU, (guint32) 1492, NULL); + + g_object_set(setting, NM_SETTING_PPP_NOAUTH, TRUE, NM_SETTING_PPP_NODEFLATE, TRUE, NULL); + + /* FIXME: These commented settings should be set as well, update NMSettingPpp first. */ +#if 0 + setting->noipdefault = TRUE; + setting->default_asyncmap = TRUE; + setting->defaultroute = TRUE; + setting->hide_password = TRUE; + setting->noaccomp = TRUE; + setting->nopcomp = TRUE; + setting->novj = TRUE; + setting->novjccomp = TRUE; +#endif +} + +static gboolean +_ppp_manager_start(NMPPPManager *self, + NMActRequest *req, + const char * ppp_name, + guint32 timeout_secs, + guint baud_override, + GError ** err) +{ + NMPPPManagerPrivate *priv; + NMConnection * connection; + NMSettingPpp * s_ppp; + gs_unref_object NMSettingPpp *s_ppp_free = NULL; + NMSettingPppoe * pppoe_setting; + NMSettingAdsl * adsl_setting; + gs_unref_ptrarray GPtrArray *ppp_cmd = NULL; + gs_free char * cmd_str = NULL; + struct stat st; + const char * ip6_method, *ip4_method; + gboolean ip6_enabled = FALSE; + gboolean ip4_enabled = FALSE; + + g_return_val_if_fail(NM_IS_PPP_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE); + + priv = NM_PPP_MANAGER_GET_PRIVATE(self); + +#if !WITH_PPP + /* PPP support disabled */ + g_set_error_literal(err, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "PPP support is not enabled."); + return FALSE; +#endif + + nm_dbus_object_export(NM_DBUS_OBJECT(self)); + + priv->pid = 0; + + /* Make sure /dev/ppp exists (bgo #533064) */ + if (stat("/dev/ppp", &st) || !S_ISCHR(st.st_mode)) + nm_utils_modprobe(NULL, FALSE, "ppp_generic", NULL); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, FALSE); + + s_ppp = nm_connection_get_setting_ppp(connection); + if (!s_ppp) { + /* If the PPP settings are all default we may not have a PPP setting yet, + * so just make a default one here. + */ + s_ppp = s_ppp_free = NM_SETTING_PPP(nm_setting_ppp_new()); + } + + pppoe_setting = nm_connection_get_setting_pppoe(connection); + if (pppoe_setting) { + /* We can't modify the applied connection's setting, make a copy */ + if (!s_ppp_free) + s_ppp = s_ppp_free = NM_SETTING_PPP(nm_setting_duplicate((NMSetting *) s_ppp)); + pppoe_fill_defaults(s_ppp); + } + + adsl_setting = (NMSettingAdsl *) nm_connection_get_setting(connection, NM_TYPE_SETTING_ADSL); + + /* Figure out what address methods should be enabled */ + ip4_method = nm_utils_get_ip_config_method(connection, AF_INET); + ip4_enabled = nm_streq(ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + ip6_method = nm_utils_get_ip_config_method(connection, AF_INET6); + ip6_enabled = nm_streq(ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + + ppp_cmd = create_pppd_cmd_line(self, + s_ppp, + pppoe_setting, + adsl_setting, + ppp_name, + baud_override, + ip4_enabled, + ip6_enabled, + err); + if (!ppp_cmd) + goto fail; + + _LOGI("starting PPP connection"); + + _LOGD("command line: %s", (cmd_str = g_strjoinv(" ", (char **) ppp_cmd->pdata))); + + priv->pid = 0; + if (!g_spawn_async(NULL, + (char **) ppp_cmd->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + nm_utils_setpgid, + NULL, + &priv->pid, + err)) + goto fail; + + nm_assert(priv->pid > 0); + + _LOGI("pppd started with pid %lld", (long long) priv->pid); + + priv->ppp_watch_id = g_child_watch_add(priv->pid, (GChildWatchFunc) ppp_watch_cb, self); + priv->ppp_timeout_handler = g_timeout_add_seconds(timeout_secs, pppd_timed_out, self); + priv->act_req = g_object_ref(req); + + return TRUE; +fail: + nm_dbus_object_unexport(NM_DBUS_OBJECT(self)); + return FALSE; +} + +static void +_ppp_cleanup(NMPPPManager *self) +{ + NMPPPManagerPrivate *priv; + + g_return_if_fail(NM_IS_PPP_MANAGER(self)); + + priv = NM_PPP_MANAGER_GET_PRIVATE(self); + + cancel_get_secrets(self); + + nm_clear_g_source(&priv->monitor_id); + + if (priv->monitor_fd >= 0) { + /* Get the stats one last time */ + monitor_cb(self); + nm_close(priv->monitor_fd); + priv->monitor_fd = -1; + } + + nm_clear_g_source(&priv->ppp_timeout_handler); + nm_clear_g_source(&priv->ppp_watch_id); +} + +/*****************************************************************************/ + +struct _NMPPPManagerStopHandle { + NMPPPManager * self; + NMPPPManagerStopCallback callback; + gpointer user_data; + + /* this object delays shutdown, because we still need to wait until + * pppd process terminated. */ + GObject *shutdown_waitobj; + + GCancellable *cancellable; + + gulong cancellable_id; + + guint idle_id; +}; + +static void +_stop_handle_complete(NMPPPManagerStopHandle *handle, gboolean was_cancelled) +{ + gs_unref_object NMPPPManager *self = NULL; + NMPPPManagerStopCallback callback; + + if (handle->cancellable_id) { + g_cancellable_disconnect(handle->cancellable, nm_steal_int(&handle->cancellable_id)); + } + + g_clear_object(&handle->cancellable); + + self = g_steal_pointer(&handle->self); + if (!self) + return; + + if (!handle->callback) + return; + + callback = handle->callback; + handle->callback = NULL; + callback(self, handle, was_cancelled, handle->user_data); +} + +static void +_stop_handle_destroy(NMPPPManagerStopHandle *handle, gboolean was_cancelled) +{ + _stop_handle_complete(handle, was_cancelled); + nm_clear_g_source(&handle->idle_id); + g_clear_object(&handle->shutdown_waitobj); + g_slice_free(NMPPPManagerStopHandle, handle); +} + +static void +_stop_child_cb(pid_t pid, gboolean success, int child_status, gpointer user_data) +{ + _stop_handle_destroy(user_data, FALSE); +} + +static gboolean +_stop_idle_cb(gpointer user_data) +{ + NMPPPManagerStopHandle *handle = user_data; + + handle->idle_id = 0; + _stop_handle_destroy(handle, FALSE); + return G_SOURCE_REMOVE; +} + +static void +_stop_cancelled_cb(GCancellable *cancellable, gpointer user_data) +{ + NMPPPManagerStopHandle *handle = user_data; + + nm_clear_g_signal_handler(handle->cancellable, &handle->cancellable_id); + _ppp_manager_stop_cancel(handle); +} + +static NMPPPManagerStopHandle * +_ppp_manager_stop(NMPPPManager * self, + GCancellable * cancellable, + NMPPPManagerStopCallback callback, + gpointer user_data) +{ + NMPPPManagerPrivate * priv = NM_PPP_MANAGER_GET_PRIVATE(self); + NMDBusObject * dbus = NM_DBUS_OBJECT(self); + NMPPPManagerStopHandle *handle; + + if (nm_dbus_object_is_exported(dbus)) + nm_dbus_object_unexport(dbus); + + _ppp_cleanup(self); + + if (!priv->pid && !callback) { + /* nothing to do further... + * + * In this case, we return a %NULL handle. The caller cannot cancel this + * event, but clearly he is not waiting for a callback anyway. */ + return NULL; + } + + handle = g_slice_new0(NMPPPManagerStopHandle); + handle->self = g_object_ref(self); + handle->callback = callback; + handle->user_data = user_data; + if (cancellable) { + handle->cancellable = g_object_ref(cancellable); + handle->cancellable_id = + g_cancellable_connect(cancellable, G_CALLBACK(_stop_cancelled_cb), handle, NULL); + } + + if (!priv->pid) { + /* No PID. There is nothing to kill, however, invoke the callback in + * an idle handler. + * + * Note that we don't register nm_shutdown_wait_obj_register_object(). + * In order for shutdown to work properly, the caller must always + * explicitly cancel the action to go down. With the idle-handler, + * cancelling the handle completes the request. */ + handle->idle_id = g_idle_add(_stop_idle_cb, handle); + return handle; + } + + /* we really want to kill the process and delay shutdown of NetworkManager + * until the process terminated. We do that, by registering an object + * that delays shutdown. */ + handle->shutdown_waitobj = g_object_new(G_TYPE_OBJECT, NULL); + nm_shutdown_wait_obj_register_object(handle->shutdown_waitobj, "ppp-manager-wait-kill-pppd"); + nm_utils_kill_child_async(nm_steal_int(&priv->pid), + SIGTERM, + LOGD_PPP, + "pppd", + NM_SHUTDOWN_TIMEOUT_MS, + _stop_child_cb, + handle); + + return handle; +} + +/*****************************************************************************/ + +static void +_ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle) +{ + g_return_if_fail(handle); + g_return_if_fail(NM_IS_PPP_MANAGER(handle->self)); + + if (handle->idle_id) { + /* we can complete this fake handle right away. */ + _stop_handle_destroy(handle, TRUE); + return; + } + + /* a real handle. Only invoke the callback (synchronously). This marks + * the handle as handled, but it keeps shutdown_waitobj around, until + * nm_utils_kill_child_async() returns. */ + _stop_handle_complete(handle, TRUE); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PARENT_IFACE: + g_value_set_string(value, priv->parent_iface); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PARENT_IFACE: + /* construct-only */ + priv->parent_iface = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_ppp_manager_init(NMPPPManager *self) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + + priv->ifindex = -1; + priv->monitor_fd = -1; + priv->ip4_route_table = RT_TABLE_MAIN; + priv->ip4_route_metric = 460; + priv->ip6_route_table = RT_TABLE_MAIN; + priv->ip6_route_metric = 460; +} + +static NMPPPManager * +_ppp_manager_new(const char *iface) +{ + g_return_val_if_fail(iface != NULL, NULL); + + return g_object_new(NM_TYPE_PPP_MANAGER, NM_PPP_MANAGER_PARENT_IFACE, iface, NULL); +} + +static void +dispose(GObject *object) +{ + NMPPPManager * self = (NMPPPManager *) object; + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self); + + /* we expect the user to first stop the manager. As fallback, + * still stop. */ + g_warn_if_fail(!priv->pid); + g_warn_if_fail(!nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))); + _ppp_manager_stop(self, NULL, NULL, NULL); + + g_clear_object(&priv->act_req); + + G_OBJECT_CLASS(nm_ppp_manager_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object); + + g_free(priv->parent_iface); + + G_OBJECT_CLASS(nm_ppp_manager_parent_class)->finalize(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_ppp = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_PPP, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "NeedSecrets", + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("username", "s"), + NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), + .handle = impl_ppp_manager_need_secrets, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "SetIp4Config", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ), + .handle = impl_ppp_manager_set_ip4_config, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "SetIp6Config", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ), + .handle = impl_ppp_manager_set_ip6_config, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetState", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ), + .handle = impl_ppp_manager_set_state, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetIfindex", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("ifindex", "i"), ), ), + .handle = impl_ppp_manager_set_ifindex, ), ), ), +}; + +static void +nm_ppp_manager_class_init(NMPPPManagerClass *manager_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(manager_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(manager_class); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/PPP"); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_ppp); + + obj_properties[PROP_PARENT_IFACE] = + g_param_spec_string(NM_PPP_MANAGER_PARENT_IFACE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[STATE_CHANGED] = g_signal_new(NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[IFINDEX_SET] = g_signal_new(NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_STRING); + + signals[IP4_CONFIG] = g_signal_new(NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + signals[IP6_CONFIG] = g_signal_new(NM_PPP_MANAGER_SIGNAL_IP6_CONFIG, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_OBJECT); + + signals[STATS] = g_signal_new(NM_PPP_MANAGER_SIGNAL_STATS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT /*guint32 in_bytes*/, + G_TYPE_UINT /*guint32 out_bytes*/); +} + +NMPPPOps ppp_ops = { + .create = _ppp_manager_new, + .set_route_parameters = _ppp_manager_set_route_parameters, + .start = _ppp_manager_start, + .stop = _ppp_manager_stop, + .stop_cancel = _ppp_manager_stop_cancel, +}; diff --git a/src/core/ppp/nm-ppp-manager.h b/src/core/ppp/nm-ppp-manager.h new file mode 100644 index 0000000..c41dda2 --- /dev/null +++ b/src/core/ppp/nm-ppp-manager.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2016 Red Hat, Inc. + */ + +#ifndef __NM_PPP_MANAGER_H__ +#define __NM_PPP_MANAGER_H__ + +#define NM_PPP_MANAGER_PARENT_IFACE "parent-iface" + +#define NM_PPP_MANAGER_SIGNAL_STATE_CHANGED "state-changed" +#define NM_PPP_MANAGER_SIGNAL_IFINDEX_SET "ifindex-set" +#define NM_PPP_MANAGER_SIGNAL_IP4_CONFIG "ip4-config" +#define NM_PPP_MANAGER_SIGNAL_IP6_CONFIG "ip6-config" +#define NM_PPP_MANAGER_SIGNAL_STATS "stats" + +typedef struct _NMPPPManager NMPPPManager; + +typedef struct _NMPPPManagerStopHandle NMPPPManagerStopHandle; + +typedef void (*NMPPPManagerStopCallback)(NMPPPManager * manager, + NMPPPManagerStopHandle *handle, + gboolean was_cancelled, + gpointer user_data); + +#endif /* __NM_PPP_MANAGER_H__ */ diff --git a/src/core/ppp/nm-ppp-plugin-api.h b/src/core/ppp/nm-ppp-plugin-api.h new file mode 100644 index 0000000..647e1c2 --- /dev/null +++ b/src/core/ppp/nm-ppp-plugin-api.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_PPP_PLUGIN_API_H__ +#define __NM_PPP_PLUGIN_API_H__ + +#include "nm-ppp-manager.h" + +typedef const struct { + NMPPPManager *(*create)(const char *iface); + + void (*set_route_parameters)(NMPPPManager *manager, + guint32 route_table_v4, + guint32 route_metric_v4, + guint32 route_table_v6, + guint32 route_metric_v6); + + gboolean (*start)(NMPPPManager *manager, + NMActRequest *req, + const char * ppp_name, + guint32 timeout_secs, + guint baud_override, + GError ** err); + + NMPPPManagerStopHandle *(*stop)(NMPPPManager * manager, + GCancellable * cancellable, + NMPPPManagerStopCallback callback, + gpointer user_data); + + void (*stop_cancel)(NMPPPManagerStopHandle *handle); + +} NMPPPOps; + +#endif /* __NM_PPP_PLUGIN_API_H__ */ diff --git a/src/core/ppp/nm-ppp-plugin.ver b/src/core/ppp/nm-ppp-plugin.ver new file mode 100644 index 0000000..fee5578 --- /dev/null +++ b/src/core/ppp/nm-ppp-plugin.ver @@ -0,0 +1,6 @@ +{ +global: + ppp_ops; +local: + *; +}; diff --git a/src/core/ppp/nm-ppp-status.h b/src/core/ppp/nm-ppp-status.h new file mode 100644 index 0000000..c346b11 --- /dev/null +++ b/src/core/ppp/nm-ppp-status.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2016 Red Hat, Inc. + */ + +#ifndef __NM_PPP_STATUS_H__ +#define __NM_PPP_STATUS_H__ + +typedef enum { + NM_PPP_STATUS_UNKNOWN, + + NM_PPP_STATUS_DEAD, + NM_PPP_STATUS_INITIALIZE, + NM_PPP_STATUS_SERIALCONN, + NM_PPP_STATUS_DORMANT, + NM_PPP_STATUS_ESTABLISH, + NM_PPP_STATUS_AUTHENTICATE, + NM_PPP_STATUS_CALLBACK, + NM_PPP_STATUS_NETWORK, + NM_PPP_STATUS_RUNNING, + NM_PPP_STATUS_TERMINATE, + NM_PPP_STATUS_DISCONNECT, + NM_PPP_STATUS_HOLDOFF, + NM_PPP_STATUS_MASTER +} NMPPPStatus; + +#endif /* __NM_PPP_STATUS_H__ */ diff --git a/src/core/ppp/nm-pppd-plugin.c b/src/core/ppp/nm-pppd-plugin.c new file mode 100644 index 0000000..519e637 --- /dev/null +++ b/src/core/ppp/nm-pppd-plugin.c @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#include +#define ___CONFIG_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define INET6 +#include +#include + +#include "nm-default.h" + +#include "nm-dbus-interface.h" + +#include "nm-pppd-plugin.h" +#include "nm-ppp-status.h" + +int plugin_init(void); + +char pppd_version[] = VERSION; + +static struct { + GDBusConnection *dbus_connection; + char * ipparam; +} gl; + +static void +nm_phasechange(int arg) +{ + NMPPPStatus ppp_status = NM_PPP_STATUS_UNKNOWN; + char * ppp_phase; + + g_return_if_fail(G_IS_DBUS_CONNECTION(gl.dbus_connection)); + + switch (arg) { + case PHASE_DEAD: + ppp_status = NM_PPP_STATUS_DEAD; + ppp_phase = "dead"; + break; + case PHASE_INITIALIZE: + ppp_status = NM_PPP_STATUS_INITIALIZE; + ppp_phase = "initialize"; + break; + case PHASE_SERIALCONN: + ppp_status = NM_PPP_STATUS_SERIALCONN; + ppp_phase = "serial connection"; + break; + case PHASE_DORMANT: + ppp_status = NM_PPP_STATUS_DORMANT; + ppp_phase = "dormant"; + break; + case PHASE_ESTABLISH: + ppp_status = NM_PPP_STATUS_ESTABLISH; + ppp_phase = "establish"; + break; + case PHASE_AUTHENTICATE: + ppp_status = NM_PPP_STATUS_AUTHENTICATE; + ppp_phase = "authenticate"; + break; + case PHASE_CALLBACK: + ppp_status = NM_PPP_STATUS_CALLBACK; + ppp_phase = "callback"; + break; + case PHASE_NETWORK: + ppp_status = NM_PPP_STATUS_NETWORK; + ppp_phase = "network"; + break; + case PHASE_RUNNING: + ppp_status = NM_PPP_STATUS_RUNNING; + ppp_phase = "running"; + break; + case PHASE_TERMINATE: + ppp_status = NM_PPP_STATUS_TERMINATE; + ppp_phase = "terminate"; + break; + case PHASE_DISCONNECT: + ppp_status = NM_PPP_STATUS_DISCONNECT; + ppp_phase = "disconnect"; + break; + case PHASE_HOLDOFF: + ppp_status = NM_PPP_STATUS_HOLDOFF; + ppp_phase = "holdoff"; + break; + case PHASE_MASTER: + ppp_status = NM_PPP_STATUS_MASTER; + ppp_phase = "master"; + break; + + default: + ppp_phase = "unknown"; + break; + } + + g_message("nm-ppp-plugin: status %d / phase '%s'", ppp_status, ppp_phase); + + if (ppp_status != NM_PPP_STATUS_UNKNOWN) { + g_dbus_connection_call(gl.dbus_connection, + NM_DBUS_SERVICE, + gl.ipparam, + NM_DBUS_INTERFACE_PPP, + "SetState", + g_variant_new("(u)", ppp_status), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); + } + + if (ppp_status == NM_PPP_STATUS_RUNNING) { + gs_unref_variant GVariant *ret = NULL; + char new_name[IF_NAMESIZE]; + int ifindex; + + ifindex = if_nametoindex(ifname); + + /* Make a sync call to ensure that when the call + * terminates the interface already has its final + * name. */ + ret = g_dbus_connection_call_sync(gl.dbus_connection, + NM_DBUS_SERVICE, + gl.ipparam, + NM_DBUS_INTERFACE_PPP, + "SetIfindex", + g_variant_new("(i)", ifindex), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + 25000, + NULL, + NULL); + + /* Update the name in pppd if NM changed it */ + if (if_indextoname(ifindex, new_name) && !nm_streq0(ifname, new_name)) { + g_message("nm-ppp-plugin: interface name changed from '%s' to '%s'", ifname, new_name); + g_strlcpy(ifname, new_name, IF_NAMESIZE); + } + } +} + +static void +nm_phasechange_hook(void *data, int arg) +{ + /* We send the nofication in exitnotify instead */ + if (arg == PHASE_DEAD) + return; + + nm_phasechange(arg); +} + +static void +nm_ip_up(void *data, int arg) +{ + ipcp_options opts = ipcp_gotoptions[0]; + ipcp_options peer_opts = ipcp_hisoptions[0]; + GVariantBuilder builder; + guint32 pppd_made_up_address = htonl(0x0a404040 + ifunit); + + g_return_if_fail(G_IS_DBUS_CONNECTION(gl.dbus_connection)); + + g_message("nm-ppp-plugin: ip-up event"); + + if (!opts.ouraddr) { + g_warning("nm-ppp-plugin: didn't receive an internal IP from pppd!"); + nm_phasechange(PHASE_DEAD); + return; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + /* Keep sending the interface name to be backwards compatible + * with older versions of NM during a package upgrade, where + * NM is not restarted and the pppd plugin was not loaded. */ + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP4_CONFIG_INTERFACE, + g_variant_new_string(ifname)); + + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP4_CONFIG_ADDRESS, + g_variant_new_uint32(opts.ouraddr)); + + /* Prefer the peer options remote address first, _unless_ pppd made the + * address up, at which point prefer the local options remote address, + * and if that's not right, use the made-up address as a last resort. + */ + if (peer_opts.hisaddr && (peer_opts.hisaddr != pppd_made_up_address)) { + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP4_CONFIG_GATEWAY, + g_variant_new_uint32(peer_opts.hisaddr)); + } else if (opts.hisaddr) { + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP4_CONFIG_GATEWAY, + g_variant_new_uint32(opts.hisaddr)); + } else if (peer_opts.hisaddr == pppd_made_up_address) { + /* As a last resort, use the made-up address */ + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP4_CONFIG_GATEWAY, + g_variant_new_uint32(peer_opts.ouraddr)); + } + + g_variant_builder_add(&builder, "{sv}", NM_PPP_IP4_CONFIG_PREFIX, g_variant_new_uint32(32)); + + if (opts.dnsaddr[0] || opts.dnsaddr[1]) { + guint32 dns[2]; + int len = 0; + + if (opts.dnsaddr[0]) + dns[len++] = opts.dnsaddr[0]; + if (opts.dnsaddr[1]) + dns[len++] = opts.dnsaddr[1]; + + g_variant_builder_add( + &builder, + "{sv}", + NM_PPP_IP4_CONFIG_DNS, + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, dns, len, sizeof(guint32))); + } + + if (opts.winsaddr[0] || opts.winsaddr[1]) { + guint32 wins[2]; + int len = 0; + + if (opts.winsaddr[0]) + wins[len++] = opts.winsaddr[0]; + if (opts.winsaddr[1]) + wins[len++] = opts.winsaddr[1]; + + g_variant_builder_add( + &builder, + "{sv}", + NM_PPP_IP4_CONFIG_WINS, + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, wins, len, sizeof(guint32))); + } + + g_message("nm-ppp-plugin: sending IPv4 config to NetworkManager..."); + + g_dbus_connection_call(gl.dbus_connection, + NM_DBUS_SERVICE, + gl.ipparam, + NM_DBUS_INTERFACE_PPP, + "SetIp4Config", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +static GVariant * +eui64_to_variant(eui64_t eui) +{ + guint64 iid; + + G_STATIC_ASSERT(sizeof(iid) == sizeof(eui)); + + memcpy(&iid, &eui, sizeof(eui)); + return g_variant_new_uint64(iid); +} + +static void +nm_ip6_up(void *data, int arg) +{ + ipv6cp_options *ho = &ipv6cp_hisoptions[0]; + ipv6cp_options *go = &ipv6cp_gotoptions[0]; + GVariantBuilder builder; + + g_return_if_fail(G_IS_DBUS_CONNECTION(gl.dbus_connection)); + + g_message("nm-ppp-plugin: ip6-up event"); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + /* Keep sending the interface name to be backwards compatible + * with older versions of NM during a package upgrade, where + * NM is not restarted and the pppd plugin was not loaded. */ + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP6_CONFIG_INTERFACE, + g_variant_new_string(ifname)); + g_variant_builder_add(&builder, "{sv}", NM_PPP_IP6_CONFIG_OUR_IID, eui64_to_variant(go->ourid)); + g_variant_builder_add(&builder, + "{sv}", + NM_PPP_IP6_CONFIG_PEER_IID, + eui64_to_variant(ho->hisid)); + + /* DNS is done via DHCPv6 or router advertisements */ + + g_message("nm-ppp-plugin: sending IPv6 config to NetworkManager..."); + + g_dbus_connection_call(gl.dbus_connection, + NM_DBUS_SERVICE, + gl.ipparam, + NM_DBUS_INTERFACE_PPP, + "SetIp6Config", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +static int +get_chap_check(void) +{ + return 1; +} + +static int +get_pap_check(void) +{ + return 1; +} + +static int +get_credentials(char *username, char *password) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + const char * my_username; + const char * my_password; + + if (!password) { + /* pppd is checking pap support; return 1 for supported */ + g_return_val_if_fail(username, -1); + return 1; + } + + g_return_val_if_fail(username, -1); + g_return_val_if_fail(G_IS_DBUS_CONNECTION(gl.dbus_connection), -1); + + g_message("nm-ppp-plugin: passwd-hook, requesting credentials..."); + + ret = g_dbus_connection_call_sync(gl.dbus_connection, + NM_DBUS_SERVICE, + gl.ipparam, + NM_DBUS_INTERFACE_PPP, + "NeedSecrets", + NULL, + G_VARIANT_TYPE("(ss)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (!ret) { + g_warning("nm-ppp-plugin: could not get secrets: %s", error->message); + return -1; + } + + g_message("nm-ppp-plugin: got credentials from NetworkManager"); + + g_variant_get(ret, "(&s&s)", &my_username, &my_password); + + g_strlcpy(username, my_username, MAXNAMELEN); + g_strlcpy(password, my_password, MAXSECRETLEN); + + return 1; +} + +static void +nm_exit_notify(void *data, int arg) +{ + g_return_if_fail(G_IS_DBUS_CONNECTION(gl.dbus_connection)); + + /* We wait until this point to notify dead phase to make sure that + * the serial port has recovered already its original settings. + */ + nm_phasechange(PHASE_DEAD); + + g_message("nm-ppp-plugin: cleaning up"); + + g_clear_object(&gl.dbus_connection); + nm_clear_g_free(&gl.ipparam); +} + +static void +add_ip6_notifier(void) +{ + static struct notifier **notifier = NULL; + static gsize load_once = 0; + + if (g_once_init_enter(&load_once)) { + void *handle = dlopen(NULL, RTLD_NOW | RTLD_GLOBAL); + + if (handle) { + notifier = dlsym(handle, "ipv6_up_notifier"); + dlclose(handle); + } + g_once_init_leave(&load_once, 1); + } + if (notifier) + add_notifier(notifier, nm_ip6_up, NULL); + else + g_message("nm-ppp-plugin: no IPV6CP notifier support; IPv6 not available"); +} + +int +plugin_init(void) +{ + gs_free_error GError *err = NULL; + + g_message("nm-ppp-plugin: initializing"); + + nm_assert(!gl.dbus_connection); + nm_assert(!gl.ipparam); + + gl.dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err); + if (!gl.dbus_connection) { + g_warning("nm-pppd-plugin: couldn't connect to system bus: %s", err->message); + return -1; + } + + gl.ipparam = g_strdup(ipparam); + + chap_passwd_hook = get_credentials; + chap_check_hook = get_chap_check; + pap_passwd_hook = get_credentials; + pap_check_hook = get_pap_check; + + add_notifier(&phasechange, nm_phasechange_hook, NULL); + add_notifier(&ip_up_notifier, nm_ip_up, NULL); + add_notifier(&exitnotify, nm_exit_notify, NULL); + add_ip6_notifier(); + + return 0; +} diff --git a/src/core/ppp/nm-pppd-plugin.h b/src/core/ppp/nm-pppd-plugin.h new file mode 100644 index 0000000..b56afbe --- /dev/null +++ b/src/core/ppp/nm-pppd-plugin.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2014 Red Hat, Inc. + */ + +#define NM_DBUS_INTERFACE_PPP "org.freedesktop.NetworkManager.PPP" + +#define NM_PPP_IP4_CONFIG_INTERFACE "interface" +#define NM_PPP_IP4_CONFIG_ADDRESS "address" +#define NM_PPP_IP4_CONFIG_PREFIX "prefix" +#define NM_PPP_IP4_CONFIG_GATEWAY "gateway" +#define NM_PPP_IP4_CONFIG_DNS "dns" +#define NM_PPP_IP4_CONFIG_WINS "wins" + +#define NM_PPP_IP6_CONFIG_INTERFACE "interface" +#define NM_PPP_IP6_CONFIG_OUR_IID "our-iid" +#define NM_PPP_IP6_CONFIG_PEER_IID "peer-iid" diff --git a/src/core/settings/nm-agent-manager.c b/src/core/settings/nm-agent-manager.c new file mode 100644 index 0000000..cd96808 --- /dev/null +++ b/src/core/settings/nm-agent-manager.c @@ -0,0 +1,1618 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-agent-manager.h" + +#include + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-dbus-interface.h" +#include "nm-secret-agent.h" +#include "nm-auth-utils.h" +#include "nm-setting-vpn.h" +#include "nm-auth-manager.h" +#include "nm-dbus-manager.h" +#include "nm-session-monitor.h" +#include "nm-simple-connection.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "c-list/src/c-list.h" + +/*****************************************************************************/ + +enum { + AGENT_REGISTERED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + NMAuthManager * auth_mgr; + NMSessionMonitor *session_monitor; + + CList agent_lst_head; + + CList request_lst_head; + + guint64 agent_version_id; +} NMAgentManagerPrivate; + +struct _NMAgentManager { + NMDBusObject parent; + NMAgentManagerPrivate _priv; +}; + +struct _NMAgentManagerClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMAgentManager, nm_agent_manager, NM_TYPE_DBUS_OBJECT) + +#define NM_AGENT_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMAgentManager, NM_IS_AGENT_MANAGER) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMAgentManager, nm_agent_manager_get, NM_TYPE_AGENT_MANAGER); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "agent-manager" +#define _NMLOG_DOMAIN LOGD_AGENTS +#define _NMLOG(level, agent, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + char __prefix1[32]; \ + char __prefix2[128]; \ + NMSecretAgent *__agent = (agent); \ + \ + if (!(self)) \ + g_snprintf(__prefix1, \ + sizeof(__prefix1), \ + "%s%s", \ + ""_NMLOG_PREFIX_NAME \ + "", \ + "[]"); \ + else if ((self) != singleton_instance) \ + g_snprintf(__prefix1, \ + sizeof(__prefix1), \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]", \ + ""_NMLOG_PREFIX_NAME \ + "", \ + NM_HASH_OBFUSCATE_PTR(self)); \ + else \ + g_strlcpy(__prefix1, _NMLOG_PREFIX_NAME, sizeof(__prefix1)); \ + if (__agent) { \ + g_snprintf(__prefix2, \ + sizeof(__prefix2), \ + ": agent[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]", \ + NM_HASH_OBFUSCATE_PTR(__agent), \ + nm_secret_agent_get_description(__agent)); \ + } else \ + __prefix2[0] = '\0'; \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix1, \ + __prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define LOG_REQ_FMT "[" NM_HASH_OBFUSCATE_PTR_FMT "/%s%s%s%s%s%s]" +#define LOG_REQ_ARG(req) \ + NM_HASH_OBFUSCATE_PTR(req), NM_PRINT_FMT_QUOTE_STRING((req)->detail), \ + NM_PRINT_FMT_QUOTED(((req)->request_type == REQUEST_TYPE_CON_GET) \ + && (req)->con.get.setting_name, \ + "/\"", \ + (req)->con.get.setting_name, \ + "\"", \ + ((req)->request_type == REQUEST_TYPE_CON_GET \ + ? "/(none)" \ + : _request_type_to_string((req)->request_type, FALSE))) + +/*****************************************************************************/ + +typedef struct _NMAgentManagerCallId Request; + +static void request_add_agent(Request *req, NMSecretAgent *agent); + +static void request_remove_agent(Request *req, NMSecretAgent *agent); + +static void request_next_agent(Request *req); + +static void _con_get_request_start(Request *req); +static void _con_save_request_start(Request *req); +static void _con_del_request_start(Request *req); + +static gboolean _con_get_try_complete_early(Request *req); + +static void agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data); + +/*****************************************************************************/ + +guint64 +nm_agent_manager_get_agent_version_id(NMAgentManager *self) +{ + g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), 0); + + return NM_AGENT_MANAGER_GET_PRIVATE(self)->agent_version_id; +} + +/*****************************************************************************/ + +typedef enum { + REQUEST_TYPE_INVALID, + REQUEST_TYPE_CON_GET, + REQUEST_TYPE_CON_SAVE, + REQUEST_TYPE_CON_DEL, +} RequestType; + +static const char * +_request_type_to_string(RequestType request_type, gboolean verbose) +{ + switch (request_type) { + case REQUEST_TYPE_CON_GET: + return verbose ? "getting" : "get"; + case REQUEST_TYPE_CON_SAVE: + return verbose ? "saving" : "sav"; + case REQUEST_TYPE_CON_DEL: + return verbose ? "deleting" : "del"; + default: + return "??"; + } +} + +/*****************************************************************************/ + +struct _NMAgentManagerCallId { + CList request_lst; + + NMAgentManager *self; + + RequestType request_type; + + char *detail; + + NMAuthSubject *subject; + + /* Current agent being asked for secrets */ + NMSecretAgent * current; + NMSecretAgentCallId *current_call_id; + + /* Stores the sorted list of NMSecretAgents which will be asked for secrets */ + GSList *pending; + + guint idle_id; + + union { + struct { + char * path; + NMConnection *connection; + + NMAuthChain *chain; + + /* Whether the agent currently being asked for secrets + * has the system.modify privilege. + */ + gboolean current_has_modify; + + union { + struct { + NMSecretAgentGetSecretsFlags flags; + char * setting_name; + char ** hints; + + GVariant *existing_secrets; + + NMAgentSecretsResultFunc callback; + gpointer callback_data; + } get; + }; + } con; + }; +}; + +/*****************************************************************************/ + +static NMSecretAgent * +_agent_find_by_owner(NMAgentManagerPrivate *priv, const char *owner) +{ + NMSecretAgent *agent; + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) { + if (nm_streq(nm_secret_agent_get_dbus_owner(agent), owner)) + return agent; + } + return NULL; +} + +static NMSecretAgent * +_agent_find_by_identifier_and_uid(NMAgentManagerPrivate *priv, + const char * identifier, + gulong sender_uid) +{ + NMSecretAgent *agent; + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) { + if (nm_streq0(nm_secret_agent_get_identifier(agent), identifier) + && sender_uid == nm_secret_agent_get_owner_uid(agent)) + return agent; + } + return NULL; +} + +/*****************************************************************************/ + +static void +_agent_remove(NMAgentManager *self, NMSecretAgent *agent) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + CList * iter, *safe; + + nm_assert(NM_IS_SECRET_AGENT(agent)); + nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst)); + + _LOGD(agent, "agent unregistered or disappeared"); + + nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy); + + c_list_unlink(&agent->agent_lst); + + g_signal_handlers_disconnect_by_func(agent, G_CALLBACK(agent_disconnected_cb), self); + + /* Remove this agent from any in-progress secrets requests */ + c_list_for_each_safe (iter, safe, &priv->request_lst_head) + request_remove_agent(c_list_entry(iter, Request, request_lst), agent); + + g_object_unref(agent); +} + +/* Call this *after* calling request_next_agent() */ +static void +maybe_remove_agent_on_error(NMAgentManager *self, NMSecretAgent *agent, GError *error) +{ + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CLOSED) + && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED) + && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)) + return; + + if (!c_list_is_empty(&agent->agent_lst)) + _agent_remove(self, agent); +} + +/*****************************************************************************/ + +static gboolean +validate_identifier(const char *identifier, GError **error) +{ + const char *p = identifier; + size_t id_len; + + if (!identifier) { + g_set_error_literal(error, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, + "No identifier was given"); + return FALSE; + } + + /* Length between 3 and 255 characters inclusive */ + id_len = strlen(identifier); + if (id_len < 3 || id_len > 255) { + g_set_error_literal(error, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, + "Identifier length not between 3 and 255 characters (inclusive)"); + return FALSE; + } + + if ((identifier[0] == '.') || (identifier[id_len - 1] == '.')) { + g_set_error_literal(error, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, + "Identifier must not start or end with '.'"); + return FALSE; + } + + /* FIXME: do complete validation here */ + while (p && *p) { + if (!g_ascii_isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.')) { + g_set_error(error, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, + "Identifier contains invalid character '%c'", + *p); + return FALSE; + } + + if ((*p == '.') && (*(p + 1) == '.')) { + g_set_error_literal(error, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, + "Identifier contains two '.' characters in sequence"); + return FALSE; + } + p++; + } + + return TRUE; +} + +static void +_agent_permissions_check_done(NMAuthChain * chain, + GDBusMethodInvocation *context, + gpointer user_data) +{ + NMAgentManager * self = NM_AGENT_MANAGER(user_data); + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + NMSecretAgent * agent; + Request * request; + + nm_assert(!context || G_IS_DBUS_METHOD_INVOCATION(context)); + + agent = nm_auth_chain_steal_data(chain, "agent"); + + nm_assert(NM_IS_SECRET_AGENT(agent)); + nm_assert(agent->auth_chain == chain); + nm_assert(agent->fully_registered == (!context)); + nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst)); + + agent->auth_chain = NULL; + + nm_secret_agent_add_permission( + agent, + NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, + (nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) + == NM_AUTH_CALL_RESULT_YES)); + nm_secret_agent_add_permission( + agent, + NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, + (nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) + == NM_AUTH_CALL_RESULT_YES)); + + if (agent->fully_registered) { + _LOGD(agent, "updated agent permissions"); + return; + } + + _LOGI(agent, "agent registered"); + + agent->fully_registered = TRUE; + + priv->agent_version_id += 1; + + g_dbus_method_invocation_return_value(context, NULL); + + c_list_for_each_entry (request, &priv->request_lst_head, request_lst) + request_add_agent(request, agent); + + g_signal_emit(self, signals[AGENT_REGISTERED], 0, agent); +} + +static NMAuthChain * +_agent_create_auth_chain(NMAgentManager *self, NMSecretAgent *agent, GDBusMethodInvocation *context) +{ + NMAuthChain *chain; + + _LOGD(agent, "requesting permissions"); + + nm_assert(!agent->auth_chain + || (agent->fully_registered == (!nm_auth_chain_get_context(agent->auth_chain)))); + + if (agent->auth_chain && !context && !agent->fully_registered) { + /* we restart the authorization check (without a @context), but the currently + * pending auth-chain carries a context. We need to pass it on as we replace + * the auth-chain. */ + context = nm_auth_chain_get_context(agent->auth_chain); + nm_assert(context); + } + + chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(agent), + context, + _agent_permissions_check_done, + self); + + nm_auth_chain_set_data(chain, "agent", agent, NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE); + + nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy); + agent->auth_chain = chain; + return chain; +} + +static void +agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data) +{ + _agent_remove(NM_AGENT_MANAGER(user_data), agent); +} + +static void +agent_manager_register_with_capabilities(NMAgentManager * self, + GDBusMethodInvocation *context, + const char * identifier, + guint32 capabilities) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + gs_unref_object NMAuthSubject *subject = NULL; + gulong sender_uid = G_MAXULONG; + GError * error = NULL; + NMSecretAgent * agent; + + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + error = g_error_new_literal(NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + g_dbus_method_invocation_take_error(context, error); + return; + } + sender_uid = nm_auth_subject_get_unix_process_uid(subject); + + /* Validate the identifier */ + if (!validate_identifier(identifier, &error)) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + /* Only one agent for each identifier is allowed per user */ + if (_agent_find_by_identifier_and_uid(priv, identifier, sender_uid)) { + error = g_error_new_literal(NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED, + "An agent with this ID is already registered for this user."); + g_dbus_method_invocation_take_error(context, error); + return; + } + + agent = nm_secret_agent_new(context, subject, identifier, capabilities); + + g_signal_connect(agent, NM_SECRET_AGENT_DISCONNECTED, G_CALLBACK(agent_disconnected_cb), self); + + c_list_link_tail(&priv->agent_lst_head, &agent->agent_lst); + + _agent_create_auth_chain(self, agent, context); +} + +static void +impl_agent_manager_register(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + const char *identifier; + + g_variant_get(parameters, "(&s)", &identifier); + agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj), invocation, identifier, 0); +} + +static void +impl_agent_manager_register_with_capabilities(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + const char *identifier; + guint32 capabilities; + + g_variant_get(parameters, "(&su)", &identifier, &capabilities); + agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj), + invocation, + identifier, + capabilities); +} + +static void +impl_agent_manager_unregister(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMAgentManager * self = NM_AGENT_MANAGER(obj); + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + NMSecretAgent * agent; + + agent = _agent_find_by_owner(priv, sender); + if (!agent) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_NOT_REGISTERED, + "Caller is not registered as an Agent"); + return; + } + + _agent_remove(self, agent); + + g_dbus_method_invocation_return_value(invocation, NULL); +} + +/*****************************************************************************/ + +static Request * +request_new(NMAgentManager *self, + RequestType request_type, + const char * detail, + NMAuthSubject * subject) +{ + Request *req; + + req = g_slice_new0(Request); + req->self = g_object_ref(self); + req->request_type = request_type; + req->detail = g_strdup(detail); + req->subject = g_object_ref(subject); + c_list_link_tail(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst); + return req; +} + +static void +request_free(Request *req) +{ + switch (req->request_type) { + case REQUEST_TYPE_CON_GET: + case REQUEST_TYPE_CON_SAVE: + case REQUEST_TYPE_CON_DEL: + g_object_unref(req->con.connection); + g_free(req->con.path); + nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy); + if (req->request_type == REQUEST_TYPE_CON_GET) { + g_free(req->con.get.setting_name); + g_strfreev(req->con.get.hints); + if (req->con.get.existing_secrets) + g_variant_unref(req->con.get.existing_secrets); + } + break; + default: + g_assert_not_reached(); + } + + if (req->idle_id) + g_source_remove(req->idle_id); + + /* cancel-secrets invokes the done-callback synchronously -- in which case + * the handler just return. + * Hence, we can proceed to free @req... */ + nm_secret_agent_cancel_call(req->current, req->current_call_id); + + g_object_unref(req->subject); + + g_free(req->detail); + g_slist_free_full(req->pending, g_object_unref); + + g_object_unref(req->self); + + if (req->current) + g_object_unref(req->current); + + memset(req, 0, sizeof(Request)); + g_slice_free(Request, req); +} + +static void +req_complete_release(Request * req, + GVariant * secrets, + const char *agent_dbus_owner, + const char *agent_username, + GError * error) +{ + NMAgentManager *self = req->self; + + switch (req->request_type) { + case REQUEST_TYPE_CON_GET: + req->con.get.callback(self, + req, + agent_dbus_owner, + agent_username, + req->con.current_has_modify, + req->con.get.setting_name, + req->con.get.flags, + error ? NULL : secrets, + error, + req->con.get.callback_data); + + break; + case REQUEST_TYPE_CON_SAVE: + case REQUEST_TYPE_CON_DEL: + break; + default: + g_return_if_reached(); + } + + request_free(req); +} + +static void +req_complete_cancel(Request *req, gboolean is_disposing) +{ + gs_free_error GError *error = NULL; + + nm_assert(req && req->self); + nm_assert(!c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(req->self)->request_lst_head, + &req->request_lst)); + + nm_utils_error_set_cancelled(&error, is_disposing, "NMAgentManager"); + req_complete_release(req, NULL, NULL, NULL, error); +} + +static void +req_complete(Request * req, + GVariant * secrets, + const char *agent_dbus_owner, + const char *agent_username, + GError * error) +{ + NMAgentManager *self = req->self; + + nm_assert( + c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst)); + + c_list_unlink(&req->request_lst); + + req_complete_release(req, secrets, agent_dbus_owner, agent_username, error); +} + +static void +req_complete_error(Request *req, GError *error) +{ + req_complete(req, NULL, NULL, NULL, error); +} + +static int +agent_compare_func(gconstpointer aa, gconstpointer bb, gpointer user_data) +{ + NMSecretAgent * a = (NMSecretAgent *) aa; + NMSecretAgent * b = (NMSecretAgent *) bb; + Request * req = user_data; + NMSessionMonitor *sm; + gboolean a_active, b_active; + gulong a_pid, b_pid, requester; + guint64 a_start, b_start; + + a_pid = nm_secret_agent_get_pid(a); + b_pid = nm_secret_agent_get_pid(b); + + /* Prefer agents in the process the request came from */ + if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { + requester = nm_auth_subject_get_unix_process_pid(req->subject); + + if (a_pid != b_pid) { + if (a_pid == requester) + return -1; + else if (b_pid == requester) + return 1; + } + } + + /* Prefer agents in active sessions */ + sm = NM_AGENT_MANAGER_GET_PRIVATE(req->self)->session_monitor; + a_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(a), TRUE); + b_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(b), TRUE); + if (a_active && !b_active) + return -1; + else if (!a_active && b_active) + return 1; + + /* Prefer agents launched later (this is essentially to ease agent debugging) */ + a_start = nm_utils_get_start_time_for_pid(a_pid, NULL, NULL); + b_start = nm_utils_get_start_time_for_pid(b_pid, NULL, NULL); + if (a_start > b_start) + return -1; + else if (a_start < b_start) + return 1; + + return 0; +} + +static void +request_add_agent(Request *req, NMSecretAgent *agent) +{ + NMAgentManager *self; + + g_return_if_fail(req != NULL); + g_return_if_fail(agent != NULL); + + self = req->self; + + if (req->request_type == REQUEST_TYPE_CON_GET) { + NMAuthSubject *subject = nm_secret_agent_get_subject(agent); + + /* Ensure the caller's username exists in the connection's permissions, + * or that the permissions is empty (ie, visible by everyone). + */ + if (!nm_auth_is_subject_in_acl(req->con.connection, subject, NULL)) { + _LOGD(agent, + "agent ignored for secrets request " LOG_REQ_FMT " (not in ACL)", + LOG_REQ_ARG(req)); + /* Connection not visible to this agent's user */ + return; + } + } + + /* If the request should filter agents by UID, do that now */ + if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { + uid_t agent_uid, subject_uid; + + agent_uid = nm_secret_agent_get_owner_uid(agent); + subject_uid = nm_auth_subject_get_unix_process_uid(req->subject); + if (agent_uid != subject_uid) { + _LOGD(agent, + "agent ignored for secrets request " LOG_REQ_FMT " " + "(uid %ld not required %ld)", + LOG_REQ_ARG(req), + (long) agent_uid, + (long) subject_uid); + return; + } + } + + _LOGD(agent, "agent allowed for secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + + /* Add this agent to the list, sorted appropriately */ + req->pending = + g_slist_insert_sorted_with_data(req->pending, g_object_ref(agent), agent_compare_func, req); +} + +static void +request_add_agents(NMAgentManager *self, Request *req) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + NMSecretAgent * agent; + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) { + if (agent->fully_registered) + request_add_agent(req, agent); + } +} + +static void +request_next_agent(Request *req) +{ + NMAgentManager *self; + GError * error = NULL; + + self = req->self; + + nm_secret_agent_cancel_call(req->current, req->current_call_id); + nm_assert(!req->current_call_id); + g_clear_object(&req->current); + + if (req->pending) { + /* Send the request to the next agent */ + req->current = req->pending->data; + req->pending = g_slist_remove(req->pending, req->current); + + _LOGD(req->current, + "agent %s secrets for request " LOG_REQ_FMT, + _request_type_to_string(req->request_type, TRUE), + LOG_REQ_ARG(req)); + + switch (req->request_type) { + case REQUEST_TYPE_CON_GET: + _con_get_request_start(req); + break; + case REQUEST_TYPE_CON_SAVE: + _con_save_request_start(req); + break; + case REQUEST_TYPE_CON_DEL: + _con_del_request_start(req); + break; + default: + g_assert_not_reached(); + } + } else { + /* No more secret agents are available to fulfill this secrets request */ + error = g_error_new_literal(NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_NO_SECRETS, + "No agents were available for this request."); + req_complete_error(req, error); + g_error_free(error); + } +} + +static void +request_remove_agent(Request *req, NMSecretAgent *agent) +{ + NMAgentManager *self; + + g_return_if_fail(req != NULL); + g_return_if_fail(agent != NULL); + + self = req->self; + + if (agent == req->current) { + nm_assert(!g_slist_find(req->pending, agent)); + + _LOGD(agent, "current agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + + switch (req->request_type) { + case REQUEST_TYPE_CON_GET: + case REQUEST_TYPE_CON_SAVE: + case REQUEST_TYPE_CON_DEL: + /* This cancels the pending authorization requests. */ + nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy); + break; + default: + g_assert_not_reached(); + } + + request_next_agent(req); + } else if (g_slist_find(req->pending, agent)) { + req->pending = g_slist_remove(req->pending, agent); + + _LOGD(agent, "agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + + g_object_unref(agent); + } +} + +static gboolean +request_start(gpointer user_data) +{ + Request *req = user_data; + + req->idle_id = 0; + + switch (req->request_type) { + case REQUEST_TYPE_CON_GET: + if (_con_get_try_complete_early(req)) + goto out; + break; + default: + break; + } + request_next_agent(req); + +out: + return FALSE; +} + +/*****************************************************************************/ + +static void +_con_get_request_done(NMSecretAgent * agent, + NMSecretAgentCallId *call_id, + GVariant * secrets, + GError * error, + gpointer user_data) +{ + NMAgentManager *self; + Request * req = user_data; + GVariant * setting_secrets; + const char * agent_dbus_owner; + gs_free char * agent_name = NULL; + + g_return_if_fail(call_id == req->current_call_id); + g_return_if_fail(agent == req->current); + g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET); + + self = req->self; + + req->current_call_id = NULL; + + if (error) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + _LOGD(agent, "get secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req)); + return; + } + + _LOGD(agent, + "agent failed secrets request " LOG_REQ_FMT ": %s", + LOG_REQ_ARG(req), + error->message); + + if (g_error_matches(error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_USER_CANCELED)) { + error = g_error_new_literal(NM_AGENT_MANAGER_ERROR, + NM_AGENT_MANAGER_ERROR_USER_CANCELED, + "User canceled the secrets request."); + req_complete_error(req, error); + g_error_free(error); + } else { + /* Tell the failed agent we're no longer interested. */ + nm_secret_agent_cancel_call(req->current, req->current_call_id); + + /* Try the next agent */ + request_next_agent(req); + maybe_remove_agent_on_error(self, agent, error); + } + return; + } + + /* Ensure the setting we wanted secrets for got returned and has something in it */ + setting_secrets = + g_variant_lookup_value(secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING); + if (!setting_secrets || !g_variant_n_children(setting_secrets)) { + _LOGD(agent, "agent returned no secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + /* Try the next agent */ + request_next_agent(req); + return; + } + + _LOGD(agent, "agent returned secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + + agent_name = nm_utils_uid_to_name(nm_secret_agent_get_owner_uid(agent)); + if (agent_name && !g_utf8_validate(agent_name, -1, NULL)) { + /* Needs to be UTF-8 valid since it may be pushed through D-Bus */ + nm_clear_g_free(&agent_name); + } + + agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent); + + req_complete(req, secrets, agent_dbus_owner, agent_name, NULL); +} + +static void +set_secrets_not_required(NMConnection *connection, GVariant *dict) +{ + GVariantIter iter, setting_iter; + const char * setting_name = NULL; + GVariant * setting_dict = NULL; + + /* Iterate through the settings dicts */ + g_variant_iter_init(&iter, dict); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) { + const char *key_name = NULL; + NMSetting * setting; + GVariant * val; + + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (setting) { + /* Now through each secret in the setting and mark it as not required */ + g_variant_iter_init(&setting_iter, setting_dict); + while (g_variant_iter_next(&setting_iter, "{&sv}", &key_name, &val)) { + /* For each secret, set the flag that it's not required; VPN + * secrets need slightly different treatment here since the + * "secrets" property is actually a dictionary of secrets. + */ + if (strcmp(setting_name, NM_SETTING_VPN_SETTING_NAME) == 0 + && strcmp(key_name, NM_SETTING_VPN_SECRETS) == 0 + && g_variant_is_of_type(val, G_VARIANT_TYPE("a{ss}"))) { + GVariantIter vpn_secret_iter; + const char * secret_name, *secret; + + g_variant_iter_init(&vpn_secret_iter, val); + while (g_variant_iter_next(&vpn_secret_iter, "{&s&s}", &secret_name, &secret)) + nm_setting_set_secret_flags(setting, + secret_name, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + NULL); + } else + nm_setting_set_secret_flags(setting, + key_name, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + NULL); + g_variant_unref(val); + } + } + g_variant_unref(setting_dict); + } +} + +static void +_con_get_request_start_proceed(Request *req, gboolean include_system_secrets) +{ + NMConnection *tmp; + + g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET); + + tmp = nm_simple_connection_new_clone(req->con.connection); + nm_connection_clear_secrets(tmp); + if (include_system_secrets) { + if (req->con.get.existing_secrets) + (void) nm_connection_update_secrets(tmp, + req->con.get.setting_name, + req->con.get.existing_secrets, + NULL); + } else { + /* Update secret flags in the temporary connection to indicate that + * the system secrets we're not sending to the agent aren't required, + * so the agent can properly validate UI controls and such. + */ + if (req->con.get.existing_secrets) + set_secrets_not_required(tmp, req->con.get.existing_secrets); + } + + req->current_call_id = nm_secret_agent_get_secrets(req->current, + req->con.path, + tmp, + req->con.get.setting_name, + (const char **) req->con.get.hints, + req->con.get.flags, + _con_get_request_done, + req); + if (!req->current_call_id) { + g_warn_if_reached(); + request_next_agent(req); + } + + g_object_unref(tmp); +} + +static void +_con_get_request_start_validated(NMAuthChain * chain, + GDBusMethodInvocation *context, + gpointer user_data) +{ + NMAgentManager *self; + Request * req = user_data; + const char * perm; + + g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET); + + self = req->self; + + req->con.chain = NULL; + + /* If the agent obtained the 'modify' permission, we send all system secrets + * to it. If it didn't, we still ask it for secrets, but we don't send + * any system secrets. + */ + perm = nm_auth_chain_get_data(chain, "perm"); + g_assert(perm); + if (nm_auth_chain_get_result(chain, perm) == NM_AUTH_CALL_RESULT_YES) + req->con.current_has_modify = TRUE; + + _LOGD(req->current, + "agent " LOG_REQ_FMT " MODIFY check result %s", + LOG_REQ_ARG(req), + req->con.current_has_modify ? "YES" : "NO"); + + _con_get_request_start_proceed(req, req->con.current_has_modify); +} + +static void +_con_get_request_start(Request *req) +{ + NMAgentManager * self; + NMSettingConnection *s_con; + const char * agent_dbus_owner, *perm; + + self = req->self; + + req->con.current_has_modify = FALSE; + + agent_dbus_owner = nm_secret_agent_get_dbus_owner(req->current); + + /* If the request flags allow user interaction, and there are existing + * system secrets (or blank secrets that are supposed to be system-owned), + * check whether the agent has the 'modify' permission before sending those + * secrets to the agent. We shouldn't leak system-owned secrets to + * unprivileged users. + */ + if ((req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) + && (req->con.get.existing_secrets + || _nm_connection_aggregate(req->con.connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL))) { + _LOGD(NULL, + "(" LOG_REQ_FMT ") request has system secrets; checking agent %s for MODIFY", + LOG_REQ_ARG(req), + agent_dbus_owner); + + req->con.chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(req->current), + NULL, + _con_get_request_start_validated, + req); + nm_assert(req->con.chain); + + /* If the caller is the only user in the connection's permissions, then + * we use the 'modify.own' permission instead of 'modify.system'. If the + * request affects more than just the caller, require 'modify.system'. + */ + s_con = nm_connection_get_setting_connection(req->con.connection); + g_assert(s_con); + if (nm_setting_connection_get_num_permissions(s_con) == 1) + perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN; + else + perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM; + nm_auth_chain_set_data(req->con.chain, "perm", (gpointer) perm, NULL); + + nm_auth_chain_add_call_unsafe(req->con.chain, perm, TRUE); + } else { + _LOGD(NULL, + "(" LOG_REQ_FMT ") requesting user-owned secrets from agent %s", + LOG_REQ_ARG(req), + agent_dbus_owner); + + _con_get_request_start_proceed(req, FALSE); + } +} + +static gboolean +_con_get_try_complete_early(Request *req) +{ + NMAgentManager * self; + gs_unref_variant GVariant *setting_secrets = NULL; + gs_unref_object NMConnection *tmp = NULL; + GError * error = NULL; + + self = req->self; + + /* Check if there are any existing secrets */ + if (req->con.get.existing_secrets) + setting_secrets = g_variant_lookup_value(req->con.get.existing_secrets, + req->con.get.setting_name, + NM_VARIANT_TYPE_SETTING); + + if (!setting_secrets || !g_variant_n_children(setting_secrets)) + return FALSE; + + /* The connection already had secrets; check if any more are required. + * If no more are required, we're done. If secrets are still needed, + * ask a secret agent for more. This allows admins to provide generic + * secrets but allow additional user-specific ones as well. + */ + tmp = nm_simple_connection_new_clone(req->con.connection); + g_assert(tmp); + + if (!nm_connection_update_secrets(tmp, + req->con.get.setting_name, + req->con.get.existing_secrets, + &error)) { + req_complete_error(req, error); + g_clear_error(&error); + return TRUE; + } + /* Do we have everything we need? */ + if (NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM) + || ((nm_connection_need_secrets(tmp, NULL) == NULL) + && !NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))) { + _LOGD(NULL, "(" LOG_REQ_FMT ") system settings secrets sufficient", LOG_REQ_ARG(req)); + + /* Got everything, we're done */ + req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL); + return TRUE; + } + + _LOGD(NULL, + "(" LOG_REQ_FMT ") system settings secrets insufficient, asking agents", + LOG_REQ_ARG(req)); + + /* We don't, so ask some agents for additional secrets */ + if (req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS && !req->pending) { + /* The request initiated from GetSecrets() via DBus, + * don't error out if any secrets are missing. */ + req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL); + return TRUE; + } + + /* Couldn't get secrets from system settings, so now we ask the + * agents for secrets. Let the Agent Manager handle which agents + * we'll ask and in which order. + */ + return FALSE; +} + +/** + * nm_agent_manager_get_secrets: + * @self: + * @path: + * @connection: + * @subject: + * @existing_secrets: + * @flags: + * @hints: + * @callback: + * @callback_data: + * + * Requests secrets for a connection. + * + * This function cannot fail. The callback will be invoked + * asynchronously, but it will always be invoked exactly once. + * Even for cancellation and disposing of @self. In those latter + * cases, the callback is invoked synchronously during the cancellation/ + * disposal. + * + * Returns: a call-id to cancel the call. + */ +NMAgentManagerCallId +nm_agent_manager_get_secrets(NMAgentManager * self, + const char * path, + NMConnection * connection, + NMAuthSubject * subject, + GVariant * existing_secrets, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMAgentSecretsResultFunc callback, + gpointer callback_data) +{ + Request *req; + + g_return_val_if_fail(self != NULL, NULL); + g_return_val_if_fail(path && *path, NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(callback != NULL, NULL); + + nm_log_dbg(LOGD_SETTINGS, + "Secrets requested for connection %s (%s/%s)", + path, + nm_connection_get_id(connection), + setting_name); + + /* NOTE: a few things in the Request handling depend on existing_secrets + * being NULL if there aren't any system-owned secrets for this connection. + * This in turn depends on nm_connection_to_dbus() and nm_setting_to_hash() + * both returning NULL if they didn't hash anything. + */ + req = request_new(self, REQUEST_TYPE_CON_GET, nm_connection_get_id(connection), subject); + + req->con.path = g_strdup(path); + req->con.connection = g_object_ref(connection); + if (existing_secrets) + req->con.get.existing_secrets = g_variant_ref(existing_secrets); + req->con.get.setting_name = g_strdup(setting_name); + req->con.get.hints = g_strdupv((char **) hints); + req->con.get.flags = flags; + req->con.get.callback = callback; + req->con.get.callback_data = callback_data; + + if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)) + request_add_agents(self, req); + req->idle_id = g_idle_add(request_start, req); + return req; +} + +void +nm_agent_manager_cancel_secrets(NMAgentManager *self, NMAgentManagerCallId request_id) +{ + g_return_if_fail(self != NULL); + g_return_if_fail(request_id); + g_return_if_fail(request_id->request_type == REQUEST_TYPE_CON_GET); + + nm_assert(c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, + &request_id->request_lst)); + + c_list_unlink(&request_id->request_lst); + + req_complete_cancel(request_id, FALSE); +} + +/*****************************************************************************/ + +static void +_con_save_request_done(NMSecretAgent * agent, + NMSecretAgentCallId *call_id, + GVariant * secrets, + GError * error, + gpointer user_data) +{ + NMAgentManager *self; + Request * req = user_data; + const char * agent_dbus_owner; + + g_return_if_fail(call_id == req->current_call_id); + g_return_if_fail(agent == req->current); + g_return_if_fail(req->request_type == REQUEST_TYPE_CON_SAVE); + + self = req->self; + + req->current_call_id = NULL; + + if (error) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + _LOGD(agent, "save secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req)); + return; + } + + _LOGD(agent, + "agent failed save secrets request " LOG_REQ_FMT ": %s", + LOG_REQ_ARG(req), + error->message); + /* Try the next agent */ + request_next_agent(req); + maybe_remove_agent_on_error(self, agent, error); + return; + } + + _LOGD(agent, "agent saved secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + + agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent); + req_complete(req, NULL, NULL, agent_dbus_owner, NULL); +} + +static void +_con_save_request_start(Request *req) +{ + req->current_call_id = nm_secret_agent_save_secrets(req->current, + req->con.path, + req->con.connection, + _con_save_request_done, + req); + if (!req->current_call_id) { + g_warn_if_reached(); + request_next_agent(req); + } +} + +void +nm_agent_manager_save_secrets(NMAgentManager *self, + const char * path, + NMConnection * connection, + NMAuthSubject * subject) +{ + Request *req; + + g_return_if_fail(self); + g_return_if_fail(path && *path); + g_return_if_fail(NM_IS_CONNECTION(connection)); + + nm_log_dbg(LOGD_SETTINGS, + "Saving secrets for connection %s (%s)", + path, + nm_connection_get_id(connection)); + + req = request_new(self, REQUEST_TYPE_CON_SAVE, nm_connection_get_id(connection), subject); + req->con.path = g_strdup(path); + req->con.connection = g_object_ref(connection); + + request_add_agents(self, req); + req->idle_id = g_idle_add(request_start, req); +} + +/*****************************************************************************/ + +static void +_con_del_request_done(NMSecretAgent * agent, + NMSecretAgentCallId *call_id, + GVariant * secrets, + GError * error, + gpointer user_data) +{ + NMAgentManager *self; + Request * req = user_data; + + g_return_if_fail(call_id == req->current_call_id); + g_return_if_fail(agent == req->current); + g_return_if_fail(req->request_type == REQUEST_TYPE_CON_DEL); + + self = req->self; + + req->current_call_id = NULL; + + if (error) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + _LOGD(agent, "delete secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req)); + return; + } + + _LOGD(agent, + "agent failed delete secrets request " LOG_REQ_FMT ": %s", + LOG_REQ_ARG(req), + error->message); + } else { + _LOGD(agent, "agent deleted secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req)); + } + + /* Tell the next agent to delete secrets */ + request_next_agent(req); + if (error) + maybe_remove_agent_on_error(self, agent, error); +} + +static void +_con_del_request_start(Request *req) +{ + req->current_call_id = nm_secret_agent_delete_secrets(req->current, + req->con.path, + req->con.connection, + _con_del_request_done, + req); + if (!req->current_call_id) { + g_warn_if_reached(); + request_next_agent(req); + } +} + +void +nm_agent_manager_delete_secrets(NMAgentManager *self, const char *path, NMConnection *connection) +{ + NMAuthSubject *subject; + Request * req; + + g_return_if_fail(self != NULL); + g_return_if_fail(path && *path); + g_return_if_fail(NM_IS_CONNECTION(connection)); + + nm_log_dbg(LOGD_SETTINGS, + "Deleting secrets for connection %s (%s)", + path, + nm_connection_get_id(connection)); + + subject = nm_auth_subject_new_internal(); + req = request_new(self, REQUEST_TYPE_CON_DEL, nm_connection_get_id(connection), subject); + req->con.path = g_strdup(path); + req->con.connection = g_object_ref(connection); + g_object_unref(subject); + + request_add_agents(self, req); + req->idle_id = g_idle_add(request_start, req); +} + +/*****************************************************************************/ + +gboolean +nm_agent_manager_has_agent_with_permission(NMAgentManager *self, + const char * username, + const char * permission) +{ + NMAgentManagerPrivate *priv; + NMSecretAgent * agent; + + g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), FALSE); + g_return_val_if_fail(username, FALSE); + g_return_val_if_fail(permission, FALSE); + + priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) { + if (!agent->fully_registered) + continue; + if (!nm_streq0(nm_secret_agent_get_owner_username(agent), username)) + continue; + if (nm_secret_agent_has_permission(agent, permission)) + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +gboolean +nm_agent_manager_all_agents_have_capability(NMAgentManager * manager, + NMAuthSubject * subject, + NMSecretAgentCapabilities capability) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(manager); + NMSecretAgent * agent; + gboolean subject_is_unix_process = + (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + gulong subject_uid = + subject_is_unix_process ? nm_auth_subject_get_unix_process_uid(subject) : 0u; + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) { + if (!agent->fully_registered) + continue; + if (subject_is_unix_process && nm_secret_agent_get_owner_uid(agent) != subject_uid) + continue; + if (!(nm_secret_agent_get_capabilities(agent) & capability)) + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +authority_changed_cb(NMAuthManager *auth_manager, NMAgentManager *self) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + NMSecretAgent * agent; + + c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) + _agent_create_auth_chain(self, agent, NULL); +} + +/*****************************************************************************/ + +static void +nm_agent_manager_init(NMAgentManager *self) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + + priv->agent_version_id = 1; + c_list_init(&priv->agent_lst_head); + c_list_init(&priv->request_lst_head); +} + +static void +constructed(GObject *object) +{ + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(object); + + G_OBJECT_CLASS(nm_agent_manager_parent_class)->constructed(object); + + priv->auth_mgr = g_object_ref(nm_auth_manager_get()); + priv->session_monitor = g_object_ref(nm_session_monitor_get()); + + nm_dbus_object_export(NM_DBUS_OBJECT(object)); + + g_signal_connect(priv->auth_mgr, + NM_AUTH_MANAGER_SIGNAL_CHANGED, + G_CALLBACK(authority_changed_cb), + object); +} + +static void +dispose(GObject *object) +{ + NMAgentManager * self = NM_AGENT_MANAGER(object); + NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self); + Request * request; + NMSecretAgent * agent; + + while ((request = c_list_first_entry(&priv->request_lst_head, Request, request_lst))) { + c_list_unlink(&request->request_lst); + req_complete_cancel(request, TRUE); + } + + while ((agent = c_list_first_entry(&priv->agent_lst_head, NMSecretAgent, agent_lst))) + _agent_remove(self, agent); + + if (priv->auth_mgr) { + g_signal_handlers_disconnect_by_func(priv->auth_mgr, + G_CALLBACK(authority_changed_cb), + object); + g_clear_object(&priv->auth_mgr); + } + + nm_dbus_object_unexport(NM_DBUS_OBJECT(object)); + + g_clear_object(&priv->session_monitor); + + G_OBJECT_CLASS(nm_agent_manager_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_agent_manager = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_AGENT_MANAGER, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "Register", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"), ), ), + .handle = impl_agent_manager_register, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "RegisterWithCapabilities", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"), + NM_DEFINE_GDBUS_ARG_INFO("capabilities", "u"), ), ), + .handle = impl_agent_manager_register_with_capabilities, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Unregister", ), + .handle = impl_agent_manager_unregister, ), ), ), +}; + +static void +nm_agent_manager_class_init(NMAgentManagerClass *agent_manager_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(agent_manager_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(agent_manager_class); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH_AGENT_MANAGER); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_agent_manager); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + signals[AGENT_REGISTERED] = g_signal_new(NM_AGENT_MANAGER_AGENT_REGISTERED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); +} diff --git a/src/core/settings/nm-agent-manager.h b/src/core/settings/nm-agent-manager.h new file mode 100644 index 0000000..b26af4e --- /dev/null +++ b/src/core/settings/nm-agent-manager.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_AGENT_MANAGER_H__ +#define __NETWORKMANAGER_AGENT_MANAGER_H__ + +#include "nm-connection.h" + +#include "nm-dbus-object.h" +#include "nm-secret-agent.h" + +#define NM_TYPE_AGENT_MANAGER (nm_agent_manager_get_type()) +#define NM_AGENT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_AGENT_MANAGER, NMAgentManager)) +#define NM_AGENT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_AGENT_MANAGER, NMAgentManagerClass)) +#define NM_IS_AGENT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_AGENT_MANAGER)) +#define NM_IS_AGENT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_AGENT_MANAGER)) +#define NM_AGENT_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_AGENT_MANAGER, NMAgentManagerClass)) + +#define NM_AGENT_MANAGER_AGENT_REGISTERED "agent-registered" + +typedef struct _NMAgentManagerCallId *NMAgentManagerCallId; + +typedef struct _NMAgentManagerClass NMAgentManagerClass; + +GType nm_agent_manager_get_type(void); + +NMAgentManager *nm_agent_manager_get(void); + +guint64 nm_agent_manager_get_agent_version_id(NMAgentManager *self); + +/* If no agent fulfilled the secrets request, agent_dbus_owner will be NULL */ +typedef void (*NMAgentSecretsResultFunc)(NMAgentManager * manager, + NMAgentManagerCallId call_id, + const char * agent_dbus_owner, + const char * agent_uname, + gboolean agent_has_modify, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + GVariant * secrets, + GError * error, + gpointer user_data); + +NMAgentManagerCallId nm_agent_manager_get_secrets(NMAgentManager * manager, + const char * path, + NMConnection * connection, + NMAuthSubject * subject, + GVariant * existing_secrets, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMAgentSecretsResultFunc callback, + gpointer callback_data); + +void nm_agent_manager_cancel_secrets(NMAgentManager *manager, NMAgentManagerCallId request_id); + +void nm_agent_manager_save_secrets(NMAgentManager *manager, + const char * path, + NMConnection * connection, + NMAuthSubject * subject); + +void nm_agent_manager_delete_secrets(NMAgentManager *manager, + const char * path, + NMConnection * connection); + +gboolean nm_agent_manager_has_agent_with_permission(NMAgentManager *self, + const char * username, + const char * permission); + +gboolean nm_agent_manager_all_agents_have_capability(NMAgentManager * manager, + NMAuthSubject * subject, + NMSecretAgentCapabilities capability); + +#endif /* __NETWORKMANAGER_AGENT_MANAGER_H__ */ diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c new file mode 100644 index 0000000..7d31272 --- /dev/null +++ b/src/core/settings/nm-secret-agent.c @@ -0,0 +1,808 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-secret-agent.h" + +#include +#include + +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-dbus-interface.h" +#include "nm-core-internal.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-simple-connection.h" +#include "NetworkManagerUtils.h" +#include "c-list/src/c-list.h" + +/*****************************************************************************/ + +#define METHOD_GET_SECRETS "GetSecrets" +#define METHOD_CANCEL_GET_SECRETS "CancelGetSecrets" +#define METHOD_SAVE_SECRETS "SaveSecrets" +#define METHOD_DELETE_SECRETS "DeleteSecrets" + +enum { + DISCONNECTED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct _NMSecretAgentPrivate { + CList permissions; + CList requests; + GDBusConnection * dbus_connection; + char * description; + NMAuthSubject * subject; + char * identifier; + char * owner_username; + char * dbus_owner; + GCancellable * name_owner_cancellable; + guint name_owner_changed_id; + NMSecretAgentCapabilities capabilities; + bool shutdown_wait_obj_registered : 1; +} NMSecretAgentPrivate; + +struct _NMSecretAgentClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT) + +#define NM_SECRET_AGENT_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMSecretAgent, NM_IS_SECRET_AGENT) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "secret-agent" +#define _NMLOG_DOMAIN LOGD_AGENTS +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + char _prefix[64]; \ + \ + if ((self)) { \ + g_snprintf(_prefix, \ + sizeof(_prefix), \ + _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT "]", \ + NM_HASH_OBFUSCATE_PTR(self)); \ + } else \ + g_strlcpy(_prefix, _NMLOG_PREFIX_NAME, sizeof(_prefix)); \ + \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, call_id, ...) \ + G_STMT_START \ + { \ + NMSecretAgentCallId *const _call_id = (call_id); \ + \ + nm_assert(_call_id); \ + \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] request [" NM_HASH_OBFUSCATE_PTR_FMT \ + ",%s,%s%s%s%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_HASH_OBFUSCATE_PTR(_call_id->self), \ + NM_HASH_OBFUSCATE_PTR(_call_id), \ + _call_id->method_name, \ + NM_PRINT_FMT_QUOTE_STRING(_call_id->path), \ + (_call_id->cancellable ? "" : " (cancelled)") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static NM_UTILS_FLAGS2STR_DEFINE(_capabilities_to_string, + NMSecretAgentCapabilities, + NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_VPN_HINTS, + "vpn-hints"), ); + +/*****************************************************************************/ + +struct _NMSecretAgentCallId { + CList lst; + NMSecretAgent * self; + GCancellable * cancellable; + char * path; + const char * method_name; + char * setting_name; + NMSecretAgentCallback callback; + gpointer callback_data; +}; + +static NMSecretAgentCallId * +_call_id_new(NMSecretAgent * self, + const char * method_name, /* this must be a static string. */ + const char * path, + const char * setting_name, + NMSecretAgentCallback callback, + gpointer callback_data) +{ + NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); + NMSecretAgentCallId * call_id; + + call_id = g_slice_new(NMSecretAgentCallId); + *call_id = (NMSecretAgentCallId){ + .self = g_object_ref(self), + .path = g_strdup(path), + .setting_name = g_strdup(setting_name), + .method_name = method_name, + .callback = callback, + .callback_data = callback_data, + .cancellable = g_cancellable_new(), + }; + c_list_link_tail(&priv->requests, &call_id->lst); + + _LOG2T(call_id, "new request..."); + + if (!priv->shutdown_wait_obj_registered) { + /* self has async requests (that keep self alive). As long as + * we have pending requests, shutdown is blocked. */ + priv->shutdown_wait_obj_registered = TRUE; + nm_shutdown_wait_obj_register_object(G_OBJECT(self), "secret-agent"); + } + + return call_id; +} + +#define _call_id_new(self, method_name, path, setting_name, callback, callback_data) \ + _call_id_new(self, "" method_name "", path, setting_name, callback, callback_data) + +static void +_call_id_free(NMSecretAgentCallId *call_id) +{ + c_list_unlink_stale(&call_id->lst); + g_free(call_id->path); + g_free(call_id->setting_name); + nm_g_object_unref(call_id->cancellable); + g_object_unref(call_id->self); + nm_g_slice_free(call_id); +} + +static void +_call_id_invoke_callback(NMSecretAgentCallId *call_id, + GVariant * secrets, + GError * error, + gboolean cancelled, + gboolean free_call_id) +{ + gs_free_error GError *error_cancelled = NULL; + + nm_assert(call_id); + nm_assert(!c_list_is_empty(&call_id->lst)); + + c_list_unlink(&call_id->lst); + + if (cancelled) { + nm_assert(!secrets); + nm_assert(!error); + if (call_id->callback) { + nm_utils_error_set_cancelled(&error_cancelled, FALSE, "NMSecretAgent"); + error = error_cancelled; + } + _LOG2T(call_id, "cancelled"); + } else if (error) { + nm_assert(!secrets); + _LOG2T(call_id, "completed with failure: %s", error->message); + } else { + nm_assert(!secrets || g_variant_is_of_type(secrets, G_VARIANT_TYPE("a{sa{sv}}"))); + nm_assert((!!secrets) == nm_streq0(call_id->method_name, METHOD_GET_SECRETS)); + _LOG2T(call_id, "completed successfully"); + } + + if (call_id->callback) + call_id->callback(call_id->self, call_id, secrets, error, call_id->callback_data); + + if (free_call_id) + _call_id_free(call_id); +} + +/*****************************************************************************/ + +static char * +_create_description(const char *dbus_owner, const char *identifier, gulong uid) +{ + return g_strdup_printf("%s/%s/%lu", dbus_owner, identifier, uid); +} + +const char * +nm_secret_agent_get_description(NMSecretAgent *agent) +{ + NMSecretAgentPrivate *priv; + + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(agent); + if (!priv->description) { + priv->description = + _create_description(priv->dbus_owner, + priv->identifier, + nm_auth_subject_get_unix_process_uid(priv->subject)); + } + + return priv->description; +} + +/*****************************************************************************/ + +const char * +nm_secret_agent_get_dbus_owner(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); + + return NM_SECRET_AGENT_GET_PRIVATE(agent)->dbus_owner; +} + +const char * +nm_secret_agent_get_identifier(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); + + return NM_SECRET_AGENT_GET_PRIVATE(agent)->identifier; +} + +gulong +nm_secret_agent_get_owner_uid(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG); + + return nm_auth_subject_get_unix_process_uid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject); +} + +const char * +nm_secret_agent_get_owner_username(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); + + return NM_SECRET_AGENT_GET_PRIVATE(agent)->owner_username; +} + +gulong +nm_secret_agent_get_pid(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG); + + return nm_auth_subject_get_unix_process_pid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject); +} + +NMSecretAgentCapabilities +nm_secret_agent_get_capabilities(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NM_SECRET_AGENT_CAPABILITY_NONE); + + return NM_SECRET_AGENT_GET_PRIVATE(agent)->capabilities; +} + +NMAuthSubject * +nm_secret_agent_get_subject(NMSecretAgent *agent) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); + + return NM_SECRET_AGENT_GET_PRIVATE(agent)->subject; +} + +/*****************************************************************************/ + +/** + * nm_secret_agent_add_permission: + * @agent: A #NMSecretAgent. + * @permission: The name of the permission + * + * Records whether or not the agent has a given permission. + */ +void +nm_secret_agent_add_permission(NMSecretAgent *agent, const char *permission, gboolean allowed) +{ + NMSecretAgentPrivate *priv; + NMCListElem * elem; + + g_return_if_fail(agent != NULL); + g_return_if_fail(permission != NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(agent); + + elem = nm_c_list_elem_find_first(&priv->permissions, p, nm_streq(p, permission)); + + if (elem) { + if (!allowed) + nm_c_list_elem_free_full(elem, g_free); + return; + } + + if (allowed) { + c_list_link_tail(&priv->permissions, &nm_c_list_elem_new_stale(g_strdup(permission))->lst); + } +} + +/** + * nm_secret_agent_has_permission: + * @agent: A #NMSecretAgent. + * @permission: The name of the permission to check for + * + * Returns whether or not the agent has the given permission. + * + * Returns: %TRUE if the agent has the given permission, %FALSE if it does not + * or if the permission was not previous recorded with + * nm_secret_agent_add_permission(). + */ +gboolean +nm_secret_agent_has_permission(NMSecretAgent *agent, const char *permission) +{ + g_return_val_if_fail(agent != NULL, FALSE); + g_return_val_if_fail(permission != NULL, FALSE); + + return !!nm_c_list_elem_find_first(&NM_SECRET_AGENT_GET_PRIVATE(agent)->permissions, + p, + nm_streq(p, permission)); +} + +/*****************************************************************************/ + +static void +_dbus_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSecretAgentCallId *call_id; + gs_unref_variant GVariant *ret = NULL; + gs_unref_variant GVariant *secrets = NULL; + gs_free_error GError *error = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!ret && nm_utils_error_is_cancelled(error)) + return; + + call_id = user_data; + + if (!ret) + g_dbus_error_strip_remote_error(error); + else { + if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) { + g_variant_get(ret, "(@a{sa{sv}})", &secrets); + } + } + + _call_id_invoke_callback(call_id, secrets, error, FALSE, TRUE); +} + +/*****************************************************************************/ + +NMSecretAgentCallId * +nm_secret_agent_get_secrets(NMSecretAgent * self, + const char * path, + NMConnection * connection, + const char * setting_name, + const char ** hints, + NMSecretAgentGetSecretsFlags flags, + NMSecretAgentCallback callback, + gpointer callback_data) +{ + NMSecretAgentPrivate *priv; + GVariant * dict; + NMSecretAgentCallId * call_id; + + g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(path && *path, NULL); + g_return_val_if_fail(setting_name, NULL); + g_return_val_if_fail(callback, NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + + /* Mask off the private flags if present */ + flags &= ~(NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM + | NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS); + + call_id = _call_id_new(self, METHOD_GET_SECRETS, path, setting_name, callback, callback_data); + + g_dbus_connection_call(priv->dbus_connection, + priv->dbus_owner, + NM_DBUS_PATH_SECRET_AGENT, + NM_DBUS_INTERFACE_SECRET_AGENT, + call_id->method_name, + g_variant_new("(@a{sa{sv}}os^asu)", + dict, + path, + setting_name, + hints ?: NM_PTRARRAY_EMPTY(const char *), + (guint32) flags), + G_VARIANT_TYPE("(a{sa{sv}})"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 120000, + call_id->cancellable, + _dbus_call_cb, + call_id); + + return call_id; +} + +/*****************************************************************************/ + +static void +_call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSecretAgentCallId *call_id = user_data; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (ret) + _LOG2T(call_id, "success cancelling GetSecrets"); + else if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) + _LOG2T(call_id, "cancelling GetSecrets no longer works as service disconnected"); + else { + _LOG2T(call_id, "failed to cancel GetSecrets: %s", error->message); + } + + _call_id_free(call_id); +} + +/** + * nm_secret_agent_cancel_call: + * @self: the #NMSecretAgent instance for the @call_id. + * Maybe be %NULL if @call_id is %NULL. + * @call_id: (allow-none): the call id to cancel. May be %NULL for convenience, + * in which case it does nothing. + * + * It is an error to pass an invalid @call_id or a @call_id for an operation + * that already completed. It is also an error to cancel the call from inside + * the callback, at that point the call is already completed. + * In case of nm_secret_agent_cancel_call() this will synchronously invoke the + * callback before nm_secret_agent_cancel_call() returns. + */ +void +nm_secret_agent_cancel_call(NMSecretAgent *self, NMSecretAgentCallId *call_id) +{ + NMSecretAgentPrivate *priv; + gboolean free_call_id = TRUE; + + if (!call_id) { + /* for convenience, %NULL is accepted fine. */ + nm_assert(!self || NM_IS_SECRET_AGENT(self)); + return; + } + + g_return_if_fail(NM_IS_SECRET_AGENT(call_id->self)); + g_return_if_fail(!c_list_is_empty(&call_id->lst)); + + /* Theoretically, call-id already has a self pointer. But nm_secret_agent_cancel_call() has only + * one user: NMAgentManager. And that one has the self-pointer at hand, so the only purpose of + * the @self argument is to assert that we are cancelling the expected call. + * + * We could drop the @self argument, but that just remove an additional assert-check from + * our code, without making a simplification for the only caller of this function. */ + g_return_if_fail(self == call_id->self); + + priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + nm_assert(c_list_contains(&priv->requests, &call_id->lst)); + + nm_clear_g_cancellable(&call_id->cancellable); + + if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) { + g_dbus_connection_call( + priv->dbus_connection, + priv->dbus_owner, + NM_DBUS_PATH_SECRET_AGENT, + NM_DBUS_INTERFACE_SECRET_AGENT, + METHOD_CANCEL_GET_SECRETS, + g_variant_new("(os)", call_id->path, call_id->setting_name), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + NM_SHUTDOWN_TIMEOUT_MS, + NULL, /* this operation is not cancellable. We rely on the timeout. */ + _call_cancel_cb, + call_id); + /* we keep call-id alive, but it will be unlinked from priv->requests. + * _call_cancel_cb() will finally free it later. */ + free_call_id = FALSE; + } + + _call_id_invoke_callback(call_id, NULL, NULL, TRUE, free_call_id); +} + +/*****************************************************************************/ + +NMSecretAgentCallId * +nm_secret_agent_save_secrets(NMSecretAgent * self, + const char * path, + NMConnection * connection, + NMSecretAgentCallback callback, + gpointer callback_data) +{ + NMSecretAgentPrivate *priv; + GVariant * dict; + NMSecretAgentCallId * call_id; + + g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(path && *path, NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + /* Caller should have ensured that only agent-owned secrets exist in 'connection' */ + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + + call_id = _call_id_new(self, METHOD_SAVE_SECRETS, path, NULL, callback, callback_data); + + g_dbus_connection_call(priv->dbus_connection, + priv->dbus_owner, + NM_DBUS_PATH_SECRET_AGENT, + NM_DBUS_INTERFACE_SECRET_AGENT, + call_id->method_name, + g_variant_new("(@a{sa{sv}}o)", dict, path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 60000, + call_id->cancellable, + _dbus_call_cb, + call_id); + + return call_id; +} + +/*****************************************************************************/ + +NMSecretAgentCallId * +nm_secret_agent_delete_secrets(NMSecretAgent * self, + const char * path, + NMConnection * connection, + NMSecretAgentCallback callback, + gpointer callback_data) +{ + NMSecretAgentPrivate *priv; + GVariant * dict; + NMSecretAgentCallId * call_id; + + g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(path && *path, NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + /* No secrets sent; agents must be smart enough to track secrets using the UUID or something */ + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_NO_SECRETS); + + call_id = _call_id_new(self, METHOD_DELETE_SECRETS, path, NULL, callback, callback_data); + + g_dbus_connection_call(priv->dbus_connection, + priv->dbus_owner, + NM_DBUS_PATH_SECRET_AGENT, + NM_DBUS_INTERFACE_SECRET_AGENT, + call_id->method_name, + g_variant_new("(@a{sa{sv}}o)", dict, path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 60000, + call_id->cancellable, + _dbus_call_cb, + call_id); + return call_id; +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMSecretAgent *self, const char *owner) +{ + NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + nm_assert(!priv->name_owner_cancellable); + + owner = nm_str_not_empty(owner); + + _LOGT("name-owner-changed: %s%s%s", + NM_PRINT_FMT_QUOTED(owner, "has ", owner, "", "disconnected")); + + if (owner) + return; + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + g_signal_emit(self, signals[DISCONNECTED], 0); +} + +static void +name_owner_changed_cb(GDBusConnection *dbus_connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSecretAgent *self = NM_SECRET_AGENT(user_data); + const char * new_owner = NULL; + + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) { + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + } + + nm_clear_g_cancellable(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable); + + name_owner_changed(self, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + NMSecretAgent *self; + + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + + g_clear_object(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable); + + name_owner_changed(self, name_owner); +} + +/*****************************************************************************/ + +NMSecretAgent * +nm_secret_agent_new(GDBusMethodInvocation * context, + NMAuthSubject * subject, + const char * identifier, + NMSecretAgentCapabilities capabilities) +{ + NMSecretAgent * self; + NMSecretAgentPrivate *priv; + const char * dbus_owner; + gs_free char * owner_username = NULL; + char * description = NULL; + char buf_subject[64]; + char buf_caps[150]; + gulong uid; + GDBusConnection * dbus_connection; + + g_return_val_if_fail(context != NULL, NULL); + g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL); + g_return_val_if_fail(nm_auth_subject_get_subject_type(subject) + == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NULL); + g_return_val_if_fail(identifier != NULL, NULL); + + dbus_connection = g_dbus_method_invocation_get_connection(context); + + g_return_val_if_fail(G_IS_DBUS_CONNECTION(dbus_connection), NULL); + + uid = nm_auth_subject_get_unix_process_uid(subject); + + owner_username = nm_utils_uid_to_name(uid); + + dbus_owner = nm_auth_subject_get_unix_process_dbus_sender(subject); + + self = g_object_new(NM_TYPE_SECRET_AGENT, NULL); + + priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + priv->dbus_connection = g_object_ref(dbus_connection); + + _LOGT("constructed: %s, owner=%s%s%s (%s), unique-name=%s%s%s, capabilities=%s", + (description = _create_description(dbus_owner, identifier, uid)), + NM_PRINT_FMT_QUOTE_STRING(owner_username), + nm_auth_subject_to_string(subject, buf_subject, sizeof(buf_subject)), + NM_PRINT_FMT_QUOTE_STRING(g_dbus_connection_get_unique_name(priv->dbus_connection)), + _capabilities_to_string(capabilities, buf_caps, sizeof(buf_caps))); + + priv->identifier = g_strdup(identifier); + priv->owner_username = g_steal_pointer(&owner_username); + priv->dbus_owner = g_strdup(dbus_owner); + priv->description = description; + priv->capabilities = capabilities; + priv->subject = g_object_ref(subject); + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + priv->dbus_owner, + name_owner_changed_cb, + self, + NULL); + + priv->name_owner_cancellable = g_cancellable_new(); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + priv->dbus_owner, + -1, + priv->name_owner_cancellable, + get_name_owner_cb, + self); + + return self; +} + +static void +nm_secret_agent_init(NMSecretAgent *self) +{ + NMSecretAgentPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SECRET_AGENT, NMSecretAgentPrivate); + + self->_priv = priv; + + c_list_init(&self->agent_lst); + c_list_init(&priv->permissions); + c_list_init(&priv->requests); +} + +static void +dispose(GObject *object) +{ + NMSecretAgent * self = NM_SECRET_AGENT(object); + NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + nm_assert(c_list_is_empty(&self->agent_lst)); + nm_assert(!self->auth_chain); + nm_assert(c_list_is_empty(&priv->requests)); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_cancellable(&priv->name_owner_cancellable); + + G_OBJECT_CLASS(nm_secret_agent_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMSecretAgent * self = NM_SECRET_AGENT(object); + NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); + + g_free(priv->description); + g_free(priv->identifier); + g_free(priv->owner_username); + g_free(priv->dbus_owner); + + nm_c_list_elem_free_all(&priv->permissions, g_free); + + g_clear_object(&priv->subject); + + g_clear_object(&priv->dbus_connection); + + G_OBJECT_CLASS(nm_secret_agent_parent_class)->finalize(object); + + _LOGT("finalized"); +} + +static void +nm_secret_agent_class_init(NMSecretAgentClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(config_class); + + g_type_class_add_private(object_class, sizeof(NMSecretAgentPrivate)); + + object_class->dispose = dispose; + object_class->finalize = finalize; + + signals[DISCONNECTED] = g_signal_new(NM_SECRET_AGENT_DISCONNECTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/settings/nm-secret-agent.h b/src/core/settings/nm-secret-agent.h new file mode 100644 index 0000000..ce59142 --- /dev/null +++ b/src/core/settings/nm-secret-agent.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SECRET_AGENT_H__ +#define __NETWORKMANAGER_SECRET_AGENT_H__ + +#include "nm-connection.h" + +#include "c-list/src/c-list.h" + +#define NM_TYPE_SECRET_AGENT (nm_secret_agent_get_type()) +#define NM_SECRET_AGENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SECRET_AGENT, NMSecretAgent)) +#define NM_SECRET_AGENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SECRET_AGENT, NMSecretAgentClass)) +#define NM_IS_SECRET_AGENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SECRET_AGENT)) +#define NM_IS_SECRET_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SECRET_AGENT)) +#define NM_SECRET_AGENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SECRET_AGENT, NMSecretAgentClass)) + +#define NM_SECRET_AGENT_DISCONNECTED "disconnected" + +typedef struct _NMSecretAgentClass NMSecretAgentClass; +typedef struct _NMSecretAgentCallId NMSecretAgentCallId; + +struct _NMAuthChain; +struct _NMSecretAgentPrivate; + +struct _NMSecretAgent { + GObject parent; + CList agent_lst; + struct _NMAuthChain * auth_chain; + struct _NMSecretAgentPrivate *_priv; + bool fully_registered : 1; +}; + +GType nm_secret_agent_get_type(void); + +NMSecretAgent *nm_secret_agent_new(GDBusMethodInvocation * context, + NMAuthSubject * subject, + const char * identifier, + NMSecretAgentCapabilities capabilities); + +const char *nm_secret_agent_get_description(NMSecretAgent *agent); + +const char *nm_secret_agent_get_dbus_owner(NMSecretAgent *agent); + +const char *nm_secret_agent_get_identifier(NMSecretAgent *agent); + +gulong nm_secret_agent_get_owner_uid(NMSecretAgent *agent); + +const char *nm_secret_agent_get_owner_username(NMSecretAgent *agent); + +gulong nm_secret_agent_get_pid(NMSecretAgent *agent); + +NMSecretAgentCapabilities nm_secret_agent_get_capabilities(NMSecretAgent *agent); + +NMAuthSubject *nm_secret_agent_get_subject(NMSecretAgent *agent); + +void nm_secret_agent_add_permission(NMSecretAgent *agent, const char *permission, gboolean allowed); + +gboolean nm_secret_agent_has_permission(NMSecretAgent *agent, const char *permission); + +typedef void (*NMSecretAgentCallback)(NMSecretAgent * agent, + NMSecretAgentCallId *call_id, + GVariant * new_secrets, /* NULL for save & delete */ + GError * error, + gpointer user_data); + +NMSecretAgentCallId *nm_secret_agent_get_secrets(NMSecretAgent * agent, + const char * path, + NMConnection * connection, + const char * setting_name, + const char ** hints, + NMSecretAgentGetSecretsFlags flags, + NMSecretAgentCallback callback, + gpointer callback_data); + +NMSecretAgentCallId *nm_secret_agent_save_secrets(NMSecretAgent * agent, + const char * path, + NMConnection * connection, + NMSecretAgentCallback callback, + gpointer callback_data); + +NMSecretAgentCallId *nm_secret_agent_delete_secrets(NMSecretAgent * agent, + const char * path, + NMConnection * connection, + NMSecretAgentCallback callback, + gpointer callback_data); + +void nm_secret_agent_cancel_call(NMSecretAgent *self, NMSecretAgentCallId *call_id); + +#endif /* __NETWORKMANAGER_SECRET_AGENT_H__ */ diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c new file mode 100644 index 0000000..a5761bf --- /dev/null +++ b/src/core/settings/nm-settings-connection.c @@ -0,0 +1,2810 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-settings-connection.h" + +#include "c-list/src/c-list.h" + +#include "nm-glib-aux/nm-keyfile-aux.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-config.h" +#include "nm-config-data.h" +#include "nm-dbus-interface.h" +#include "nm-session-monitor.h" +#include "nm-auth-manager.h" +#include "nm-auth-utils.h" +#include "nm-agent-manager.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-audit-manager.h" +#include "nm-settings.h" +#include "nm-dbus-manager.h" +#include "settings/plugins/keyfile/nms-keyfile-storage.h" + +#define AUTOCONNECT_RETRIES_UNSET -2 +#define AUTOCONNECT_RETRIES_FOREVER -1 +#define AUTOCONNECT_RESET_RETRIES_TIMER 300 + +#define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES \ + ((NMSettingsUpdate2Flags)( \ + NM_SETTINGS_UPDATE2_FLAG_TO_DISK | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY \ + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) + +/*****************************************************************************/ + +NMConnection ** +nm_settings_connections_array_to_connections(NMSettingsConnection *const *connections, + gssize n_connections) +{ + NMConnection **arr; + gssize i; + + if (n_connections < 0) + n_connections = NM_PTRARRAY_LEN(connections); + if (n_connections == 0) + return NULL; + + arr = g_new(NMConnection *, n_connections + 1); + for (i = 0; i < n_connections; i++) + arr[i] = nm_settings_connection_get_connection(connections[i]); + arr[i] = NULL; + return arr; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, PROP_UNSAVED, PROP_FLAGS, PROP_FILENAME, ); + +enum { UPDATED_INTERNAL, FLAGS_CHANGED, LAST_SIGNAL }; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct _NMSettingsConnectionPrivate { + NMSettings *settings; + + NMKeyFileDB *kf_db_timestamps; + NMKeyFileDB *kf_db_seen_bssids; + + NMAgentManager *agent_mgr; + + /* List of pending authentication requests */ + CList auth_lst_head; + + CList call_ids_lst_head; /* in-progress secrets requests */ + + NMConnection *connection; + + NMSettingsStorage *storage; + + char *filename; + + NMDevice *default_wired_device; + + /* Caches secrets from on-disk connections; were they not cached any + * call to nm_connection_clear_secrets() wipes them out and we'd have + * to re-read them from disk which defeats the purpose of having the + * connection in-memory at all. + */ + GVariant *system_secrets; + + /* Caches secrets from agents during the activation process; if new system + * secrets are returned from an agent, they get written out to disk, + * triggering a re-read of the connection, which reads only system + * secrets, and would wipe out any agent-owned or not-saved secrets the + * agent also returned. + */ + GVariant *agent_secrets; + + GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */ + + guint64 timestamp; /* Up-to-date timestamp of connection use */ + + guint64 last_secret_agent_version_id; + + int autoconnect_retries; + + gint32 autoconnect_retries_blocked_until; + + bool timestamp_set : 1; + + NMSettingsAutoconnectBlockedReason autoconnect_blocked_reason : 4; + + NMSettingsConnectionIntFlags flags : 5; + +} NMSettingsConnectionPrivate; + +struct _NMSettingsConnectionClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT) + +#define NM_SETTINGS_CONNECTION_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMSettingsConnection, NM_IS_SETTINGS_CONNECTION) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG_PREFIX_NAME "settings-connection" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + char __prefix[128]; \ + const char *__p_prefix = _NMLOG_PREFIX_NAME; \ + const char *__uuid = (self) ? nm_settings_connection_get_uuid(self) : NULL; \ + \ + if (self) { \ + g_snprintf(__prefix, \ + sizeof(__prefix), \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "%s%s]", \ + _NMLOG_PREFIX_NAME, \ + NM_HASH_OBFUSCATE_PTR(self), \ + __uuid ? "," : "", \ + __uuid ?: ""); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + __uuid, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static const GDBusSignalInfo signal_info_updated; +static const GDBusSignalInfo signal_info_removed; +static const NMDBusInterfaceInfoExtended interface_info_settings_connection; + +static void update_system_secrets_cache(NMSettingsConnection *self, NMConnection *new); +static void update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new); + +/*****************************************************************************/ + +NMDevice * +nm_settings_connection_default_wired_get_device(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + nm_assert(!priv->default_wired_device || NM_IS_DEVICE(priv->default_wired_device)); + + return priv->default_wired_device; +} + +void +nm_settings_connection_default_wired_set_device(NMSettingsConnection *self, NMDevice *device) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + nm_assert(!priv->default_wired_device || NM_IS_DEVICE(priv->default_wired_device)); + nm_assert(!device || NM_IS_DEVICE(device)); + + nm_assert((!!priv->default_wired_device) != (!!device)); + + priv->default_wired_device = device; +} + +/*****************************************************************************/ + +NMSettingsStorage * +nm_settings_connection_get_storage(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->storage; +} + +void +_nm_settings_connection_set_storage(NMSettingsConnection *self, NMSettingsStorage *storage) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + const char * filename; + + nm_assert(NM_IS_SETTINGS_STORAGE(storage)); + nm_assert(!priv->storage + || nm_streq(nm_settings_storage_get_uuid(storage), + nm_settings_storage_get_uuid(priv->storage))); + + nm_g_object_ref_set(&priv->storage, storage); + + filename = nm_settings_storage_get_filename(priv->storage); + + if (!nm_streq0(priv->filename, filename)) { + g_free(priv->filename); + priv->filename = g_strdup(filename); + _notify(self, PROP_FILENAME); + } +} + +/*****************************************************************************/ + +gboolean +nm_settings_connection_still_valid(NMSettingsConnection *self) +{ + gboolean valid; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + + valid = !c_list_is_empty(&self->_connections_lst); + + nm_assert( + valid + == nm_settings_has_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings, self)); + + return valid; +} + +/*****************************************************************************/ + +static GHashTable * +_seen_bssids_hash_new(void) +{ + return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); +} + +/*****************************************************************************/ + +NMConnection * +nm_settings_connection_get_connection(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection; +} + +void +_nm_settings_connection_set_connection(NMSettingsConnection * self, + NMConnection * new_connection, + NMConnection ** out_connection_old, + NMSettingsConnectionUpdateReason update_reason) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_unref_object NMConnection *connection_old = NULL; + + nm_assert(NM_IS_CONNECTION(new_connection)); + nm_assert(NM_IS_SETTINGS_STORAGE(priv->storage)); + nm_assert(nm_streq0(nm_settings_storage_get_uuid(priv->storage), + nm_connection_get_uuid(new_connection))); + nm_assert(!out_connection_old || !*out_connection_old); + + if (!priv->connection + || !nm_connection_compare(priv->connection, + new_connection, + NM_SETTING_COMPARE_FLAG_EXACT)) { + connection_old = priv->connection; + priv->connection = g_object_ref(new_connection); + nmtst_connection_assert_unchanging(priv->connection); + + /* note that we only return @connection_old if the new connection actually differs from + * before. + * + * So, there are three cases: + * + * - return %NULL when setting the connection the first time. + * - return %NULL if setting a profile with the same content that we already have. + * - return the previous pointer if the connection changed. */ + NM_SET_OUT(out_connection_old, g_steal_pointer(&connection_old)); + } + + if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS)) + update_system_secrets_cache(self, NULL); + else if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS)) + update_system_secrets_cache(self, priv->connection); + + if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS)) + update_agent_secrets_cache(self, NULL); + else if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS)) + update_agent_secrets_cache(self, priv->connection); +} + +/*****************************************************************************/ + +gboolean +nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self, + NMConnection * applied_connection, + NMSettingCompareFlags compare_flags) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(applied_connection), FALSE); + + /* for convenience, we *always* ignore certain settings. */ + compare_flags |= + NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS | NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP; + + return nm_connection_compare(nm_settings_connection_get_connection(self), + applied_connection, + compare_flags); +} + +/*****************************************************************************/ + +guint64 +nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->last_secret_agent_version_id; +} + +/*****************************************************************************/ + +gboolean +nm_settings_connection_check_visibility(NMSettingsConnection *self, + NMSessionMonitor * session_monitor) +{ + NMSettingConnection *s_con; + guint32 num, i; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + + nm_assert(NM_IS_SESSION_MONITOR(session_monitor)); + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self)); + + /* Check every user in the ACL for a session */ + num = nm_setting_connection_get_num_permissions(s_con); + if (num == 0) + return TRUE; + + for (i = 0; i < num; i++) { + const char *ptype; + const char *user; + uid_t uid; + + if (!nm_setting_connection_get_permission(s_con, i, &ptype, &user, NULL)) + continue; + if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER)) + continue; + if (!nm_utils_name_to_uid(user, &uid)) + continue; + if (!nm_session_monitor_session_exists(session_monitor, uid, FALSE)) + continue; + + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +/* Return TRUE if any active user in the connection's ACL has the given + * permission without having to authorize for it via PolicyKit. Connections + * visible to everyone automatically pass the check. + */ +gboolean +nm_settings_connection_check_permission(NMSettingsConnection *self, const char *permission) +{ + NMSettingsConnectionPrivate *priv; + NMSettingConnection * s_con; + guint32 num, i; + const char * puser; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(self), + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) + return FALSE; + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self)); + + /* Check every user in the ACL for a session */ + num = nm_setting_connection_get_num_permissions(s_con); + if (num == 0) { + /* Visible to all so it's OK to auto-activate */ + return TRUE; + } + + for (i = 0; i < num; i++) { + const char *ptype; + + /* For each user get their secret agent and check if that agent has the + * required permission. + * + * FIXME: what if the user isn't running an agent? PolKit needs a bus + * name or a PID but if the user isn't running an agent they won't have + * either. + */ + if (!nm_setting_connection_get_permission(s_con, i, &ptype, &puser, NULL)) + continue; + if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER)) + continue; + + if (nm_agent_manager_has_agent_with_permission(priv->agent_mgr, puser, permission)) + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +static void +update_system_secrets_cache(NMSettingsConnection *self, NMConnection *new) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_unref_object NMConnection *connection_cloned = NULL; + gs_unref_variant GVariant *old_secrets = NULL; + + old_secrets = g_steal_pointer(&priv->system_secrets); + + if (!new) + goto out; + + /* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection, + * in particular if there are no secrets to begin with. */ + + connection_cloned = nm_simple_connection_new_clone(new); + + /* Clear out non-system-owned and not-saved secrets */ + _nm_connection_clear_secrets_by_secret_flags(connection_cloned, NM_SETTING_SECRET_FLAG_NONE); + + priv->system_secrets = nm_g_variant_ref_sink( + nm_connection_to_dbus(connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)); + +out: + if (_LOGT_ENABLED()) { + if ((!!old_secrets) != (!!priv->system_secrets)) { + _LOGT("update system secrets: secrets %s", old_secrets ? "cleared" : "set"); + } else if (priv->system_secrets && !g_variant_equal(old_secrets, priv->system_secrets)) + _LOGT("update system secrets: secrets updated"); + } +} + +static void +update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_unref_object NMConnection *connection_cloned = NULL; + gs_unref_variant GVariant *old_secrets = NULL; + + old_secrets = g_steal_pointer(&priv->agent_secrets); + + if (!new) + goto out; + + /* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection, + * in particular if there are no secrets to begin with. */ + + connection_cloned = nm_simple_connection_new_clone(new); + + /* Clear out non-system-owned secrets */ + _nm_connection_clear_secrets_by_secret_flags(connection_cloned, + NM_SETTING_SECRET_FLAG_NOT_SAVED + | NM_SETTING_SECRET_FLAG_AGENT_OWNED); + + priv->agent_secrets = nm_g_variant_ref_sink( + nm_connection_to_dbus(connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)); + +out: + if (_LOGT_ENABLED()) { + if ((!!old_secrets) != (!!priv->agent_secrets)) { + _LOGT("update agent secrets: secrets %s", old_secrets ? "cleared" : "set"); + } else if (priv->agent_secrets && !g_variant_equal(old_secrets, priv->agent_secrets)) + _LOGT("update agent secrets: secrets updated"); + } +} + +void +nm_settings_connection_clear_secrets(NMSettingsConnection *self, + gboolean clear_cached_system_secrets, + gboolean persist) +{ + gs_unref_object NMConnection *connection_cloned = NULL; + + if (!nm_settings_connection_still_valid(self)) + return; + + /* FIXME: add API to NMConnection so that we can clone a profile without secrets. */ + + connection_cloned = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self)); + + nm_connection_clear_secrets(connection_cloned); + + if (!nm_settings_connection_update( + self, + connection_cloned, + persist ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP + : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE + | (clear_cached_system_secrets + ? NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS + : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE) + | NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS, + "clear-secrets", + NULL)) + nm_assert_not_reached(); +} + +static gboolean +_secrets_update(NMConnection * connection, + const char * setting_name, + GVariant * secrets, + NMConnection **out_new_connection, + GError ** error) +{ + gs_unref_variant GVariant *secrets_setting = NULL; + + nm_assert(NM_IS_CONNECTION(connection)); + + if (setting_name && !nm_connection_get_setting_by_name(connection, setting_name)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + setting_name); + return FALSE; + } + + if (!secrets) + return TRUE; + + nm_assert(g_variant_is_of_type(secrets, NM_VARIANT_TYPE_SETTING) + || g_variant_is_of_type(secrets, NM_VARIANT_TYPE_CONNECTION)); + + if (g_variant_n_children(secrets) == 0) + return TRUE; + + if (setting_name && g_variant_is_of_type(secrets, NM_VARIANT_TYPE_CONNECTION)) { + secrets_setting = g_variant_lookup_value(secrets, setting_name, NM_VARIANT_TYPE_SETTING); + if (!secrets_setting) { + /* The connection dictionary didn't contain any secrets for + * @setting_name; just return success. + */ + return TRUE; + } + secrets = secrets_setting; + } + + /* if @out_new_connection is provided, we don't modify @connection but clone + * and return it. Otherwise, we update @connection inplace. */ + if (out_new_connection) { + nm_assert(!*out_new_connection); + connection = nm_simple_connection_new_clone(connection); + *out_new_connection = connection; + } + + if (!nm_connection_update_secrets(connection, setting_name, secrets, error)) + return FALSE; + + return TRUE; +} + +gboolean +nm_settings_connection_update(NMSettingsConnection * self, + NMConnection * new_connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + NMSettingsConnectionUpdateReason update_reason, + const char * log_context_name, + GError ** error) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + + return nm_settings_update_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings, + self, + new_connection, + persist_mode, + sett_flags, + sett_mask, + update_reason, + log_context_name, + error); +} + +void +nm_settings_connection_delete(NMSettingsConnection *self, gboolean allow_add_to_no_auto_default) +{ + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + + nm_settings_delete_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings, + self, + allow_add_to_no_auto_default); +} + +/*****************************************************************************/ + +typedef enum { + CALL_ID_TYPE_REQ, + CALL_ID_TYPE_IDLE, +} CallIdType; + +struct _NMSettingsConnectionCallId { + NMSettingsConnection * self; + CList call_ids_lst; + gboolean had_applied_connection; + NMConnection * applied_connection; + NMSettingsConnectionSecretsFunc callback; + gpointer callback_data; + + CallIdType type; + union { + struct { + NMAgentManagerCallId id; + } req; + struct { + guint32 id; + GError *error; + } idle; + } t; +}; + +static void +_get_secrets_info_callback(NMSettingsConnectionCallId *call_id, + const char * agent_username, + const char * setting_name, + GError * error) +{ + if (call_id->callback) { + call_id->callback(call_id->self, + call_id, + agent_username, + setting_name, + error, + call_id->callback_data); + } +} + +static void +_get_secrets_info_free(NMSettingsConnectionCallId *call_id) +{ + g_return_if_fail(call_id && call_id->self); + nm_assert(!c_list_is_linked(&call_id->call_ids_lst)); + + if (call_id->applied_connection) + g_object_remove_weak_pointer(G_OBJECT(call_id->applied_connection), + (gpointer *) &call_id->applied_connection); + + if (call_id->type == CALL_ID_TYPE_IDLE) + g_clear_error(&call_id->t.idle.error); + + memset(call_id, 0, sizeof(*call_id)); + g_slice_free(NMSettingsConnectionCallId, call_id); +} + +typedef struct { + NMSettingSecretFlags required; + NMSettingSecretFlags forbidden; +} ForEachSecretFlags; + +static gboolean +validate_secret_flags_cb(NMSettingSecretFlags flags, gpointer user_data) +{ + ForEachSecretFlags *cmp_flags = user_data; + + if (!NM_FLAGS_ALL(flags, cmp_flags->required)) + return FALSE; + if (NM_FLAGS_ANY(flags, cmp_flags->forbidden)) + return FALSE; + return TRUE; +} + +static GVariant * +validate_secret_flags(NMConnection *connection, GVariant *secrets, ForEachSecretFlags *cmp_flags) +{ + return g_variant_ref_sink(_nm_connection_for_each_secret(connection, + secrets, + TRUE, + validate_secret_flags_cb, + cmp_flags)); +} + +static gboolean +secret_is_system_owned(NMSettingSecretFlags flags, gpointer user_data) +{ + return !NM_FLAGS_HAS(flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED); +} + +static void +get_cmp_flags(NMSettingsConnection * self, /* only needed for logging */ + NMSettingsConnectionCallId * call_id, /* only needed for logging */ + NMConnection * connection, + const char * agent_dbus_owner, + gboolean agent_has_modify, + const char * setting_name, /* only needed for logging */ + NMSecretAgentGetSecretsFlags flags, + GVariant * secrets, + gboolean * agent_had_system, + ForEachSecretFlags * cmp_flags) +{ + gboolean is_self = (nm_settings_connection_get_connection(self) == connection); + + g_return_if_fail(secrets); + + cmp_flags->required = NM_SETTING_SECRET_FLAG_NONE; + cmp_flags->forbidden = NM_SETTING_SECRET_FLAG_NONE; + + *agent_had_system = FALSE; + + if (agent_dbus_owner) { + if (is_self) { + _LOGD("(%s:%p) secrets returned from agent %s", + setting_name, + call_id, + agent_dbus_owner); + } + + /* If the agent returned any system-owned secrets (initial connect and no + * secrets given when the connection was created, or something like that) + * make sure the agent's UID has the 'modify' permission before we use or + * save those system-owned secrets. If not, discard them and use the + * existing secrets, or fail the connection. + */ + *agent_had_system = + _nm_connection_find_secret(connection, secrets, secret_is_system_owned, NULL); + if (*agent_had_system) { + if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) { + /* No user interaction was allowed when requesting secrets; the + * agent is being bad. Remove system-owned secrets. + */ + if (is_self) { + _LOGD("(%s:%p) interaction forbidden but agent %s returned system secrets", + setting_name, + call_id, + agent_dbus_owner); + } + + cmp_flags->required |= NM_SETTING_SECRET_FLAG_AGENT_OWNED; + } else if (agent_has_modify == FALSE) { + /* Agent didn't successfully authenticate; clear system-owned secrets + * from the secrets the agent returned. + */ + if (is_self) { + _LOGD("(%s:%p) agent failed to authenticate but provided system secrets", + setting_name, + call_id); + } + + cmp_flags->required |= NM_SETTING_SECRET_FLAG_AGENT_OWNED; + } + } + } else { + if (is_self) { + _LOGD("(%s:%p) existing secrets returned", setting_name, call_id); + } + } + + /* If no user interaction was allowed, make sure that no "unsaved" secrets + * came back. Unsaved secrets by definition require user interaction. + */ + if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) { + cmp_flags->forbidden |= + (NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED); + } +} + +gboolean +nm_settings_connection_new_secrets(NMSettingsConnection *self, + NMConnection * applied_connection, + const char * setting_name, + GVariant * secrets, + GError ** error) +{ + gs_unref_object NMConnection *new_connection = NULL; + NMConnection * connection; + + if (!nm_settings_connection_has_unmodified_applied_connection(self, + applied_connection, + NM_SETTING_COMPARE_FLAG_NONE)) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "The connection was modified since activation"); + return FALSE; + } + + connection = nm_settings_connection_get_connection(self); + + if (!_secrets_update(connection, setting_name, secrets, &new_connection, error)) + return FALSE; + + if (!nm_settings_connection_update( + self, + new_connection ?: connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS, + "new-secrets", + NULL)) + nm_assert_not_reached(); + return TRUE; +} + +static void +get_secrets_done_cb(NMAgentManager * manager, + NMAgentManagerCallId call_id_a, + const char * agent_dbus_owner, + const char * agent_username, + gboolean agent_has_modify, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + GVariant * secrets, + GError * error, + gpointer user_data) +{ + NMSettingsConnectionCallId * call_id = user_data; + NMSettingsConnection * self; + NMSettingsConnectionPrivate *priv; + NMConnection * applied_connection; + gs_free_error GError *local = NULL; + gs_unref_variant GVariant *system_secrets = NULL; + gs_unref_object NMConnection *new_connection = NULL; + gboolean agent_had_system = FALSE; + ForEachSecretFlags cmp_flags = {NM_SETTING_SECRET_FLAG_NONE, NM_SETTING_SECRET_FLAG_NONE}; + gs_unref_variant GVariant *filtered_secrets = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = call_id->self; + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst)); + + c_list_unlink(&call_id->call_ids_lst); + + if (error) { + _LOGD("(%s:%p) secrets request error: %s", setting_name, call_id, error->message); + + _get_secrets_info_callback(call_id, NULL, setting_name, error); + goto out; + } + + if (call_id->had_applied_connection && !call_id->applied_connection) { + g_set_error_literal(&local, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + "Applied connection deleted since requesting secrets"); + _get_secrets_info_callback(call_id, NULL, setting_name, local); + goto out; + } + + if (call_id->had_applied_connection + && !nm_settings_connection_has_unmodified_applied_connection( + self, + call_id->applied_connection, + NM_SETTING_COMPARE_FLAG_NONE)) { + g_set_error_literal(&local, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "The connection was modified since activation"); + _get_secrets_info_callback(call_id, NULL, setting_name, local); + goto out; + } + + if (!nm_connection_get_setting_by_name(nm_settings_connection_get_connection(self), + setting_name)) { + g_set_error(&local, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + "Connection didn't have requested setting '%s'.", + setting_name); + _get_secrets_info_callback(call_id, NULL, setting_name, local); + goto out; + } + + get_cmp_flags(self, + call_id, + nm_settings_connection_get_connection(self), + agent_dbus_owner, + agent_has_modify, + setting_name, + flags, + secrets, + &agent_had_system, + &cmp_flags); + + _LOGD("(%s:%p) secrets request completed", setting_name, call_id); + + system_secrets = nm_g_variant_ref(priv->system_secrets); + + new_connection = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self)); + + nm_connection_clear_secrets(new_connection); + + if (!_secrets_update(new_connection, setting_name, system_secrets, NULL, &local)) { + _LOGD("(%s:%p) failed to update with existing secrets: %s", + setting_name, + call_id, + local->message); + } + + /* Update the connection with the agent's secrets; by this point if any + * system-owned secrets exist in 'secrets' the agent that provided them + * will have been authenticated, so those secrets can replace the existing + * system secrets. + */ + filtered_secrets = validate_secret_flags(new_connection, secrets, &cmp_flags); + + if (!_secrets_update(new_connection, setting_name, filtered_secrets, NULL, &local)) { + _LOGD("(%s:%p) failed to update with agent secrets: %s", + setting_name, + call_id, + local->message); + } + + /* Only save secrets to backing storage if the agent returned any + * new system secrets. If it didn't, then the secrets are agent- + * owned and there's no point to writing out the connection when + * nothing has changed, since agent-owned secrets don't get saved here. + */ + if (agent_had_system) { + _LOGD("(%s:%p) saving new secrets to backing storage", setting_name, call_id); + } else { + _LOGD("(%s:%p) new agent secrets processed", setting_name, call_id); + } + if (!nm_settings_connection_update( + self, + new_connection, + agent_had_system ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP + : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS, + "get-new-secrets", + NULL)) + nm_assert_not_reached(); + + applied_connection = call_id->applied_connection; + if (applied_connection) { + get_cmp_flags(self, + call_id, + applied_connection, + agent_dbus_owner, + agent_has_modify, + setting_name, + flags, + secrets, + &agent_had_system, + &cmp_flags); + + nm_connection_clear_secrets(applied_connection); + + if (!system_secrets + || nm_connection_update_secrets(applied_connection, + setting_name, + system_secrets, + NULL)) { + gs_unref_variant GVariant *filtered_secrets2 = NULL; + + filtered_secrets2 = validate_secret_flags(applied_connection, secrets, &cmp_flags); + nm_connection_update_secrets(applied_connection, setting_name, filtered_secrets2, NULL); + } + } + + _get_secrets_info_callback(call_id, agent_username, setting_name, local); + g_clear_error(&local); + +out: + _get_secrets_info_free(call_id); +} + +static gboolean +get_secrets_idle_cb(NMSettingsConnectionCallId *call_id) +{ + NMSettingsConnectionPrivate *priv; + + g_return_val_if_fail(call_id && NM_IS_SETTINGS_CONNECTION(call_id->self), G_SOURCE_REMOVE); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(call_id->self); + + nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst)); + + c_list_unlink(&call_id->call_ids_lst); + + _get_secrets_info_callback(call_id, NULL, NULL, call_id->t.idle.error); + + _get_secrets_info_free(call_id); + return G_SOURCE_REMOVE; +} + +/** + * nm_settings_connection_get_secrets: + * @self: the #NMSettingsConnection + * @applied_connection: (allow-none): if provided, only request secrets + * if @self equals to @applied_connection. Also, update the secrets + * in the @applied_connection. + * @subject: the #NMAuthSubject originating the request + * @setting_name: the setting to return secrets for + * @flags: flags to modify the secrets request + * @hints: key names in @setting_name for which secrets may be required, or some + * other information about the request + * @callback: the function to call with returned secrets + * @callback_data: user data to pass to @callback + * + * Retrieves secrets from persistent storage and queries any secret agents for + * additional secrets. + * + * With the returned call-id, the call can be cancelled. It is an error + * to cancel a call more then once or a call that already completed. + * The callback will always be invoked exactly once, also for cancellation + * and disposing of @self. In those latter cases, the callback will be invoked + * synchronously during cancellation/disposing. + * + * Returns: a call ID which may be used to cancel the ongoing secrets request. + **/ +NMSettingsConnectionCallId * +nm_settings_connection_get_secrets(NMSettingsConnection * self, + NMConnection * applied_connection, + NMAuthSubject * subject, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMSettingsConnectionSecretsFunc callback, + gpointer callback_data) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + NMAgentManagerCallId call_id_a; + gs_free char * joined_hints = NULL; + NMSettingsConnectionCallId * call_id; + GError * local = NULL; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + g_return_val_if_fail( + !applied_connection + || (NM_IS_CONNECTION(applied_connection) + && (nm_settings_connection_get_connection(self) != applied_connection)), + NULL); + + call_id = g_slice_new0(NMSettingsConnectionCallId); + call_id->self = self; + if (applied_connection) { + call_id->had_applied_connection = TRUE; + call_id->applied_connection = applied_connection; + g_object_add_weak_pointer(G_OBJECT(applied_connection), + (gpointer *) &call_id->applied_connection); + } + call_id->callback = callback; + call_id->callback_data = callback_data; + c_list_link_tail(&priv->call_ids_lst_head, &call_id->call_ids_lst); + + /* Make sure the request actually requests something we can return */ + if (!nm_connection_get_setting_by_name(nm_settings_connection_get_connection(self), + setting_name)) { + g_set_error(&local, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + "Connection didn't have requested setting '%s'.", + setting_name); + goto schedule_dummy; + } + + if (applied_connection + && !nm_settings_connection_has_unmodified_applied_connection( + self, + applied_connection, + NM_SETTING_COMPARE_FLAG_NONE)) { + g_set_error_literal(&local, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "The connection was modified since activation"); + goto schedule_dummy; + } + + /* we remember the current version-id of the secret-agents. The version-id is strictly increasing, + * as new agents register the number. We know hence, that this request was made against a certain + * set of secret-agents. + * If after making this request a new secret-agent registers, the version-id increases. + * Then we know that the this request probably did not yet include the latest secret-agent. */ + priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id(priv->agent_mgr); + + /* Use priv->system_secrets to work around the fact that nm_connection_clear_secrets() + * will clear secrets on this object's settings. + */ + call_id_a = nm_agent_manager_get_secrets(priv->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), + nm_settings_connection_get_connection(self), + subject, + priv->system_secrets, + setting_name, + flags, + hints, + get_secrets_done_cb, + call_id); + nm_assert(call_id_a); + + _LOGD("(%s:%p) secrets requested flags 0x%X hints '%s'", + setting_name, + call_id_a, + flags, + (hints && hints[0]) ? (joined_hints = g_strjoinv(",", (char **) hints)) : "(none)"); + + if (call_id_a) { + call_id->type = CALL_ID_TYPE_REQ; + call_id->t.req.id = call_id_a; + } else { +schedule_dummy: + call_id->type = CALL_ID_TYPE_IDLE; + g_propagate_error(&call_id->t.idle.error, local); + call_id->t.idle.id = g_idle_add((GSourceFunc) get_secrets_idle_cb, call_id); + } + return call_id; +} + +static void +_get_secrets_cancel(NMSettingsConnection * self, + NMSettingsConnectionCallId *call_id, + gboolean is_disposing) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + + nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst)); + + c_list_unlink(&call_id->call_ids_lst); + + if (call_id->type == CALL_ID_TYPE_REQ) + nm_agent_manager_cancel_secrets(priv->agent_mgr, call_id->t.req.id); + else + g_source_remove(call_id->t.idle.id); + + nm_utils_error_set_cancelled(&error, is_disposing, "NMSettingsConnection"); + + _get_secrets_info_callback(call_id, NULL, NULL, error); + + _get_secrets_info_free(call_id); +} + +void +nm_settings_connection_cancel_secrets(NMSettingsConnection * self, + NMSettingsConnectionCallId *call_id) +{ + _LOGD("(%p) secrets canceled", call_id); + + _get_secrets_cancel(self, call_id, FALSE); +} + +/*****************************************************************************/ + +typedef void (*AuthCallback)(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer data); + +typedef struct { + CList auth_lst; + NMAuthManagerCallId * call_id; + NMSettingsConnection * self; + AuthCallback callback; + gpointer callback_data; + GDBusMethodInvocation *invocation; + NMAuthSubject * subject; +} AuthData; + +static void +pk_auth_cb(NMAuthManager * auth_manager, + NMAuthManagerCallId *auth_call_id, + gboolean is_authorized, + gboolean is_challenge, + GError * auth_error, + gpointer user_data) +{ + AuthData * auth_data = user_data; + NMSettingsConnection *self; + gs_free_error GError *error = NULL; + + nm_assert(auth_data); + nm_assert(NM_IS_SETTINGS_CONNECTION(auth_data->self)); + + self = auth_data->self; + + auth_data->call_id = NULL; + + c_list_unlink(&auth_data->auth_lst); + + if (g_error_matches(auth_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + error = g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Error checking authorization: connection was deleted"); + } else if (auth_error) { + error = g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Error checking authorization: %s", + auth_error->message); + } else if (nm_auth_call_result_eval(is_authorized, is_challenge, auth_error) + != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + } + + auth_data->callback(self, + auth_data->invocation, + auth_data->subject, + error, + auth_data->callback_data); + + g_object_unref(auth_data->invocation); + g_object_unref(auth_data->subject); + g_slice_free(AuthData, auth_data); +} + +/** + * _new_auth_subject: + * @context: the D-Bus method invocation context + * @error: on failure, a #GError + * + * Creates an NMAuthSubject for the caller. + * + * Returns: the #NMAuthSubject on success, or %NULL on failure and sets @error + */ +static NMAuthSubject * +_new_auth_subject(GDBusMethodInvocation *context, GError **error) +{ + NMAuthSubject *subject; + + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + } + + return subject; +} + +/* may either invoke callback synchronously or asynchronously. */ +static void +auth_start(NMSettingsConnection * self, + GDBusMethodInvocation *invocation, + NMAuthSubject * subject, + const char * check_permission, + AuthCallback callback, + gpointer callback_data) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + AuthData * auth_data; + GError * error = NULL; + + nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))); + nm_assert(G_IS_DBUS_METHOD_INVOCATION(invocation)); + nm_assert(NM_IS_AUTH_SUBJECT(subject)); + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(self), + subject, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + &error)) { + callback(self, invocation, subject, error, callback_data); + g_clear_error(&error); + return; + } + + if (!check_permission) { + /* Don't need polkit auth, automatic success */ + callback(self, invocation, subject, NULL, callback_data); + return; + } + + auth_data = g_slice_new(AuthData); + auth_data->self = self; + auth_data->callback = callback; + auth_data->callback_data = callback_data; + auth_data->invocation = g_object_ref(invocation); + auth_data->subject = g_object_ref(subject); + c_list_link_tail(&priv->auth_lst_head, &auth_data->auth_lst); + auth_data->call_id = nm_auth_manager_check_authorization(nm_auth_manager_get(), + subject, + check_permission, + TRUE, + pk_auth_cb, + auth_data); +} + +/**** DBus method handlers ************************************/ + +static void +get_settings_auth_cb(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer data) +{ + gs_free const char ** seen_bssids = NULL; + NMConnectionSerializationOptions options = {}; + GVariant * settings; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + /* Timestamp is not updated in connection's 'timestamp' property, + * because it would force updating the connection and in turn + * writing to /etc periodically, which we want to avoid. Rather real + * timestamps are kept track of in a private variable. So, substitute + * timestamp property with the real one here before returning the settings. + */ + options.timestamp.has = TRUE; + nm_settings_connection_get_timestamp(self, &options.timestamp.val); + + /* Seen BSSIDs are not updated in 802-11-wireless 'seen-bssids' property + * from the same reason as timestamp. Thus we put it here to GetSettings() + * return settings too. + */ + seen_bssids = nm_settings_connection_get_seen_bssids(self); + options.seen_bssids = seen_bssids; + + /* Secrets should *never* be returned by the GetSettings method, they + * get returned by the GetSecrets method which can be better + * protected against leakage of secrets to unprivileged callers. + */ + settings = nm_connection_to_dbus_full(nm_settings_connection_get_connection(self), + NM_CONNECTION_SERIALIZE_NO_SECRETS, + &options); + g_dbus_method_invocation_return_value(context, g_variant_new("(@a{sa{sv}})", settings)); +} + +static void +impl_settings_connection_get_settings(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + + subject = _new_auth_subject(invocation, &error); + if (!subject) { + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + auth_start(self, invocation, subject, NULL, get_settings_auth_cb, NULL); +} + +typedef struct { + GDBusMethodInvocation *context; + NMAgentManager * agent_mgr; + NMAuthSubject * subject; + NMConnection * new_settings; + NMSettingsUpdate2Flags flags; + char * audit_args; + bool is_update2 : 1; +} UpdateInfo; + +static void +update_complete(NMSettingsConnection *self, UpdateInfo *info, GError *error) +{ + if (error) + g_dbus_method_invocation_return_gerror(info->context, error); + else if (info->is_update2) { + GVariantBuilder result; + + g_variant_builder_init(&result, G_VARIANT_TYPE("a{sv}")); + g_dbus_method_invocation_return_value(info->context, g_variant_new("(a{sv})", &result)); + } else + g_dbus_method_invocation_return_value(info->context, NULL); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_UPDATE, + self, + !error, + info->audit_args, + info->subject, + error ? error->message : NULL); + + g_clear_object(&info->subject); + g_clear_object(&info->agent_mgr); + g_clear_object(&info->new_settings); + g_free(info->audit_args); + g_slice_free(UpdateInfo, info); +} + +static int +_autoconnect_retries_initial(NMSettingsConnection *self) +{ + NMSettingConnection *s_con; + int retries = -1; + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self)); + if (s_con) + retries = nm_setting_connection_get_autoconnect_retries(s_con); + + /* -1 means 'default' */ + if (retries == -1) + retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA); + + /* 0 means 'forever', which is translated to a retry count of -1 */ + if (retries == 0) + retries = AUTOCONNECT_RETRIES_FOREVER; + + nm_assert(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0); + return retries; +} + +static void +_autoconnect_retries_set(NMSettingsConnection *self, int retries, gboolean is_reset) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + g_return_if_fail(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0); + + if (priv->autoconnect_retries != retries) { + _LOGT("autoconnect: retries set %d%s", retries, is_reset ? " (reset)" : ""); + priv->autoconnect_retries = retries; + } + + if (retries) + priv->autoconnect_retries_blocked_until = 0; + else { + /* NOTE: the blocked time must be identical for all connections, otherwise + * the tracking of resetting the retry count in NMPolicy needs adjustment + * in _connection_autoconnect_retries_set() (as it would need to re-evaluate + * the next-timeout every time a connection gets blocked). */ + priv->autoconnect_retries_blocked_until = + nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER; + } +} + +static void +update_auth_cb(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer data) +{ + NMSettingsConnectionPrivate *priv; + UpdateInfo * info = data; + gs_free_error GError * local = NULL; + NMSettingsConnectionPersistMode persist_mode; + + if (error) { + update_complete(self, info, error); + return; + } + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + if (info->new_settings) { + if (!_nm_connection_aggregate(info->new_settings, + NM_CONNECTION_AGGREGATE_ANY_SECRETS, + NULL)) { + /* If the new connection has no secrets, we do not want to remove all + * secrets, rather we keep all the existing ones. Do that by merging + * them in to the new connection. + */ + if (priv->agent_secrets) + nm_connection_update_secrets(info->new_settings, NULL, priv->agent_secrets, NULL); + if (priv->system_secrets) + nm_connection_update_secrets(info->new_settings, NULL, priv->system_secrets, NULL); + } else { + /* Cache the new secrets from the agent, as stuff like inotify-triggered + * changes to connection's backing config files will blow them away if + * they're in the main connection. + */ + update_agent_secrets_cache(self, info->new_settings); + + /* New secrets, allow autoconnection again */ + if (nm_settings_connection_autoconnect_blocked_reason_set( + self, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, + FALSE) + && !nm_settings_connection_autoconnect_blocked_reason_get(self)) + nm_settings_connection_autoconnect_retries_reset(self); + } + } + + if (info->new_settings) { + if (nm_audit_manager_audit_enabled(nm_audit_manager_get())) { + gs_unref_hashtable GHashTable *diff = NULL; + gboolean same; + + same = nm_connection_diff(nm_settings_connection_get_connection(self), + info->new_settings, + NM_SETTING_COMPARE_FLAG_EXACT + | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, + &diff); + if (!same && diff) + info->audit_args = nm_utils_format_con_diff_for_audit(diff); + } + } + + nm_assert( + !NM_FLAGS_ANY(info->flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES) + || nm_utils_is_power_of_two(info->flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)); + + if (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_TO_DISK)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + else if (NM_FLAGS_ANY(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY; + else if (NM_FLAGS_ANY(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED; + else if (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) { + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + } else + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP; + + nm_settings_connection_update( + self, + info->new_settings, + persist_mode, + (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE) + ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE), + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME + | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY) + ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE + : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL) + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS + | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT) + ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT + : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE), + "update-from-dbus", + &local); + + if (!local) { + gs_unref_object NMConnection *for_agent = NULL; + + /* Dupe the connection so we can clear out non-agent-owned secrets, + * as agent-owned secrets are the only ones we send back be saved. + * Only send secrets to agents of the same UID that called update too. + */ + for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self)); + _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED); + nm_agent_manager_save_secrets(info->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), + for_agent, + info->subject); + } + + /* Reset auto retries back to default since connection was updated */ + nm_settings_connection_autoconnect_retries_reset(self); + + update_complete(self, info, local); +} + +static const char * +get_update_modify_permission(NMConnection *old, NMConnection *new) +{ + NMSettingConnection *s_con; + guint32 orig_num = 0, new_num = 0; + + s_con = nm_connection_get_setting_connection(old); + orig_num = nm_setting_connection_get_num_permissions(s_con); + + s_con = nm_connection_get_setting_connection(new); + new_num = nm_setting_connection_get_num_permissions(s_con); + + /* If the caller is the only user in either connection's permissions, then + * we use the 'modify.own' permission instead of 'modify.system'. + */ + if (orig_num == 1 && new_num == 1) + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN; + + /* If the update request affects more than just the caller (ie if the old + * settings were system-wide, or the new ones are), require 'modify.system'. + */ + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM; +} + +static void +settings_connection_update(NMSettingsConnection * self, + gboolean is_update2, + GDBusMethodInvocation *context, + GVariant * new_settings, + NMSettingsUpdate2Flags flags) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + NMAuthSubject * subject = NULL; + NMConnection * tmp = NULL; + GError * error = NULL; + UpdateInfo * info; + const char * permission; + + /* Check if the settings are valid first */ + if (new_settings) { + if (!g_variant_is_of_type(new_settings, NM_VARIANT_TYPE_CONNECTION)) { + g_set_error_literal(&error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "settings is of invalid type"); + goto error; + } + + if (g_variant_n_children(new_settings) > 0) { + tmp = _nm_simple_connection_new_from_dbus(new_settings, + NM_SETTING_PARSE_FLAGS_STRICT + | NM_SETTING_PARSE_FLAGS_NORMALIZE, + &error); + if (!tmp) + goto error; + + if (!nm_connection_verify_secrets(tmp, &error)) + goto error; + } + } + + subject = _new_auth_subject(context, &error); + if (!subject) + goto error; + + /* And that the new connection settings will be visible to the user + * that's sending the update request. You can't make a connection + * invisible to yourself. + */ + if (!nm_auth_is_subject_in_acl_set_error(tmp ?: nm_settings_connection_get_connection(self), + subject, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + &error)) + goto error; + + info = g_slice_new0(UpdateInfo); + info->is_update2 = is_update2; + info->context = context; + info->agent_mgr = g_object_ref(priv->agent_mgr); + info->subject = subject; + info->flags = flags; + info->new_settings = tmp; + + permission = get_update_modify_permission(nm_settings_connection_get_connection(self), + tmp ?: nm_settings_connection_get_connection(self)); + auth_start(self, context, subject, permission, update_auth_cb, info); + return; + +error: + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_UPDATE, self, FALSE, NULL, subject, error->message); + + g_clear_object(&tmp); + g_clear_object(&subject); + + g_dbus_method_invocation_take_error(context, error); +} + +static void +impl_settings_connection_update(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_variant GVariant *settings = NULL; + + g_variant_get(parameters, "(@a{sa{sv}})", &settings); + settings_connection_update(self, FALSE, invocation, settings, NM_SETTINGS_UPDATE2_FLAG_TO_DISK); +} + +static void +impl_settings_connection_update_unsaved(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_variant GVariant *settings = NULL; + + g_variant_get(parameters, "(@a{sa{sv}})", &settings); + settings_connection_update(self, + FALSE, + invocation, + settings, + NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY); +} + +static void +impl_settings_connection_save(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + + settings_connection_update(self, FALSE, invocation, NULL, NM_SETTINGS_UPDATE2_FLAG_TO_DISK); +} + +static void +impl_settings_connection_update2(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_variant GVariant *settings = NULL; + gs_unref_variant GVariant *args = NULL; + guint32 flags_u; + GError * error = NULL; + GVariantIter iter; + const char * args_name; + NMSettingsUpdate2Flags flags; + + g_variant_get(parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args); + + if (NM_FLAGS_ANY(flags_u, + ~((guint32)(_NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES + | NM_SETTINGS_UPDATE2_FLAG_VOLATILE + | NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT + | NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)))) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Unknown flags"); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + flags = (NMSettingsUpdate2Flags) flags_u; + + if ((NM_FLAGS_ANY(flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES) + && !nm_utils_is_power_of_two(flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)) + || (NM_FLAGS_HAS(flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE) + && !NM_FLAGS_ANY(flags, + NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Conflicting flags"); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + nm_assert(g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}"))); + + g_variant_iter_init(&iter, args); + while (g_variant_iter_next(&iter, "{&sv}", &args_name, NULL)) { + error = g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Unsupported argument '%s'", + args_name); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + settings_connection_update(self, TRUE, invocation, settings, flags); +} + +static void +delete_auth_cb(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer data) +{ + gs_unref_object NMSettingsConnection *self_keep_alive = NULL; + + self_keep_alive = g_object_ref(self); + + if (error) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE, + self, + FALSE, + NULL, + subject, + error->message); + g_dbus_method_invocation_return_gerror(context, error); + return; + } + + nm_settings_connection_delete(self, TRUE); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE, self, TRUE, NULL, subject, NULL); + g_dbus_method_invocation_return_value(context, NULL); +} + +static const char * +get_modify_permission_basic(NMSettingsConnection *self) +{ + NMSettingConnection *s_con; + + /* If the caller is the only user in the connection's permissions, then + * we use the 'modify.own' permission instead of 'modify.system'. If the + * request affects more than just the caller, require 'modify.system'. + */ + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self)); + if (nm_setting_connection_get_num_permissions(s_con) == 1) + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN; + + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM; +} + +static void +impl_settings_connection_delete(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + + nm_assert(nm_settings_connection_still_valid(self)); + + subject = _new_auth_subject(invocation, &error); + if (!subject) + goto err; + + auth_start(self, invocation, subject, get_modify_permission_basic(self), delete_auth_cb, NULL); + return; +err: + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE, self, FALSE, NULL, subject, error->message); + g_dbus_method_invocation_take_error(invocation, error); +} + +/*****************************************************************************/ + +static void +dbus_get_agent_secrets_cb(NMSettingsConnection * self, + NMSettingsConnectionCallId *call_id, + const char * agent_username, + const char * setting_name, + GError * error, + gpointer user_data) +{ + GDBusMethodInvocation *context = user_data; + GVariant * dict; + + if (error) + g_dbus_method_invocation_return_gerror(context, error); + else { + /* Return secrets from agent and backing storage to the D-Bus caller; + * nm_settings_connection_get_secrets() will have updated itself with + * secrets from backing storage and those returned from the agent + * by the time we get here. + */ + dict = nm_connection_to_dbus(nm_settings_connection_get_connection(self), + NM_CONNECTION_SERIALIZE_ONLY_SECRETS); + if (!dict) + dict = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); + g_dbus_method_invocation_return_value(context, g_variant_new("(@a{sa{sv}})", dict)); + } +} + +static void +dbus_get_secrets_auth_cb(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + char *setting_name = user_data; + + if (!error) { + nm_settings_connection_get_secrets(self, + NULL, + subject, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED + | NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS, + NULL, + dbus_get_agent_secrets_cb, + context); + } + + if (error) + g_dbus_method_invocation_return_gerror(context, error); + + g_free(setting_name); +} + +static void +impl_settings_connection_get_secrets(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + const char * setting_name; + + subject = _new_auth_subject(invocation, &error); + if (!subject) { + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + g_variant_get(parameters, "(&s)", &setting_name); + + auth_start(self, + invocation, + subject, + get_modify_permission_basic(self), + dbus_get_secrets_auth_cb, + g_strdup(setting_name)); +} + +static void +dbus_clear_secrets_auth_cb(NMSettingsConnection * self, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + GError * error, + gpointer user_data) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_free_error GError *local = NULL; + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS, + self, + FALSE, + NULL, + subject, + error->message); + return; + } + + nm_settings_connection_clear_secrets(self, TRUE, TRUE); + + /* Tell agents to remove secrets for this connection */ + nm_agent_manager_delete_secrets(priv->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), + nm_settings_connection_get_connection(self)); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS, + self, + !local, + NULL, + subject, + local ? local->message : NULL); + + if (local) + g_dbus_method_invocation_return_gerror(context, local); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_settings_connection_clear_secrets(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj); + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + + subject = _new_auth_subject(invocation, &error); + if (!subject) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS, + self, + FALSE, + NULL, + NULL, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + auth_start(self, + invocation, + subject, + get_modify_permission_basic(self), + dbus_clear_secrets_auth_cb, + NULL); +} + +/*****************************************************************************/ + +void +_nm_settings_connection_emit_dbus_signal_updated(NMSettingsConnection *self) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_settings_connection, + &signal_info_updated, + "()"); +} + +void +_nm_settings_connection_emit_dbus_signal_removed(NMSettingsConnection *self) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_settings_connection, + &signal_info_removed, + "()"); +} + +void +_nm_settings_connection_emit_signal_updated_internal(NMSettingsConnection * self, + NMSettingsConnectionUpdateReason update_reason) +{ + g_signal_emit(self, signals[UPDATED_INTERNAL], 0, (guint) update_reason); +} + +/*****************************************************************************/ + +static NM_UTILS_FLAGS2STR_DEFINE( + _settings_connection_flags_to_string, + NMSettingsConnectionIntFlags, + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED, "unsaved"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, "nm-generated"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE, "volatile"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE, "visible"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, "external"), ); + +NMSettingsConnectionIntFlags +nm_settings_connection_get_flags(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->flags; +} + +NMSettingsConnectionIntFlags +nm_settings_connection_set_flags_full(NMSettingsConnection * self, + NMSettingsConnectionIntFlags mask, + NMSettingsConnectionIntFlags value) +{ + NMSettingsConnectionPrivate *priv; + NMSettingsConnectionIntFlags old_flags; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE); + + nm_assert(!NM_FLAGS_ANY(mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL)); + nm_assert(!NM_FLAGS_ANY(value, ~mask)); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + value = (priv->flags & ~mask) | value; + + old_flags = priv->flags; + if (old_flags != value) { + gboolean notify_unsaved = FALSE; + char buf1[255], buf2[255]; + + _LOGT("update settings-connection flags to %s (was %s)", + _settings_connection_flags_to_string(value, buf1, sizeof(buf1)), + _settings_connection_flags_to_string(priv->flags, buf2, sizeof(buf2))); + priv->flags = value; + nm_assert(priv->flags == value); + + if (NM_FLAGS_HAS(old_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED) + != NM_FLAGS_HAS(value, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED)) { + g_object_freeze_notify(G_OBJECT(self)); + _notify(self, PROP_UNSAVED); + notify_unsaved = TRUE; + } + _notify(self, PROP_FLAGS); + if (notify_unsaved) + g_object_thaw_notify(G_OBJECT(self)); + + g_signal_emit(self, signals[FLAGS_CHANGED], 0); + } + return old_flags; +} + +/*****************************************************************************/ + +static int +_cmp_timestamp(NMSettingsConnection *a, NMSettingsConnection *b) +{ + gboolean a_has_ts, b_has_ts; + guint64 ats = 0, bts = 0; + + nm_assert(NM_IS_SETTINGS_CONNECTION(a)); + nm_assert(NM_IS_SETTINGS_CONNECTION(b)); + + a_has_ts = !!nm_settings_connection_get_timestamp(a, &ats); + b_has_ts = !!nm_settings_connection_get_timestamp(b, &bts); + if (a_has_ts != b_has_ts) + return a_has_ts ? -1 : 1; + if (a_has_ts && ats != bts) + return (ats > bts) ? -1 : 1; + return 0; +} + +static int +_cmp_last_resort(NMSettingsConnection *a, NMSettingsConnection *b) +{ + NM_CMP_DIRECT_STRCMP0(nm_settings_connection_get_uuid(a), nm_settings_connection_get_uuid(b)); + + /* hm, same UUID. Use their pointer value to give them a stable + * order. */ + return (a > b) ? -1 : 1; +} + +/* sorting for "best" connections. + * The function sorts connections in descending timestamp order. + * That means an older connection (lower timestamp) goes after + * a newer one. + */ +int +nm_settings_connection_cmp_timestamp(NMSettingsConnection *a, NMSettingsConnection *b) +{ + NM_CMP_SELF(a, b); + + NM_CMP_RETURN(_cmp_timestamp(a, b)); + NM_CMP_RETURN( + nm_utils_cmp_connection_by_autoconnect_priority(nm_settings_connection_get_connection(a), + nm_settings_connection_get_connection(b))); + return _cmp_last_resort(a, b); +} + +int +nm_settings_connection_cmp_timestamp_p_with_data(gconstpointer pa, + gconstpointer pb, + gpointer user_data) +{ + return nm_settings_connection_cmp_timestamp(*((NMSettingsConnection **) pa), + *((NMSettingsConnection **) pb)); +} + +int +nm_settings_connection_cmp_autoconnect_priority(NMSettingsConnection *a, NMSettingsConnection *b) +{ + if (a == b) + return 0; + NM_CMP_RETURN( + nm_utils_cmp_connection_by_autoconnect_priority(nm_settings_connection_get_connection(a), + nm_settings_connection_get_connection(b))); + NM_CMP_RETURN(_cmp_timestamp(a, b)); + return _cmp_last_resort(a, b); +} + +int +nm_settings_connection_cmp_autoconnect_priority_p_with_data(gconstpointer pa, + gconstpointer pb, + gpointer user_data) +{ + return nm_settings_connection_cmp_autoconnect_priority(*((NMSettingsConnection **) pa), + *((NMSettingsConnection **) pb)); +} + +/*****************************************************************************/ + +/** + * nm_settings_connection_get_timestamp: + * @self: the #NMSettingsConnection + * @out_timestamp: the connection's timestamp + * + * Returns the time (in seconds since the Unix epoch) when the connection + * was last successfully activated. + * + * Returns: %TRUE if the timestamp has ever been set, otherwise %FALSE. + **/ +gboolean +nm_settings_connection_get_timestamp(NMSettingsConnection *self, guint64 *out_timestamp) +{ + NMSettingsConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + NM_SET_OUT(out_timestamp, priv->timestamp); + return priv->timestamp_set; +} + +/** + * nm_settings_connection_update_timestamp: + * @self: the #NMSettingsConnection + * @timestamp: timestamp to set into the connection and to store into + * the timestamps database + * + * Updates the connection and timestamps database with the provided timestamp. + **/ +void +nm_settings_connection_update_timestamp(NMSettingsConnection *self, guint64 timestamp) +{ + NMSettingsConnectionPrivate *priv; + const char * connection_uuid; + char sbuf[60]; + + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + if (priv->timestamp == timestamp && priv->timestamp_set) + return; + + priv->timestamp = timestamp; + priv->timestamp_set = TRUE; + + _LOGT("timestamp: set timestamp %" G_GUINT64_FORMAT, timestamp); + + if (!priv->kf_db_timestamps) + return; + + connection_uuid = nm_settings_connection_get_uuid(self); + if (connection_uuid) { + nm_key_file_db_set_value(priv->kf_db_timestamps, + connection_uuid, + nm_sprintf_buf(sbuf, "%" G_GUINT64_FORMAT, timestamp)); + } +} + +void +_nm_settings_connection_register_kf_dbs(NMSettingsConnection *self, + NMKeyFileDB * kf_db_timestamps, + NMKeyFileDB * kf_db_seen_bssids) +{ + NMSettingsConnectionPrivate *priv; + const char * connection_uuid; + + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + g_return_if_fail(kf_db_timestamps); + g_return_if_fail(kf_db_seen_bssids); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + connection_uuid = nm_settings_connection_get_uuid(self); + + if (priv->kf_db_timestamps != kf_db_timestamps) { + gs_free char *tmp_str = NULL; + guint64 timestamp; + + nm_key_file_db_unref(priv->kf_db_timestamps); + priv->kf_db_timestamps = nm_key_file_db_ref(kf_db_timestamps); + + tmp_str = nm_key_file_db_get_value(priv->kf_db_timestamps, connection_uuid); + + timestamp = _nm_utils_ascii_str_to_uint64(tmp_str, 10, 0, G_MAXUINT64, G_MAXUINT64); + if (timestamp != G_MAXUINT64) { + priv->timestamp = timestamp; + priv->timestamp_set = TRUE; + _LOGT("timestamp: read timestamp %" G_GUINT64_FORMAT " from keyfile database \"%s\"", + timestamp, + nm_key_file_db_get_filename(priv->kf_db_timestamps)); + } else + _LOGT("timestamp: no timestamp from keyfile database \"%s\"", + nm_key_file_db_get_filename(priv->kf_db_timestamps)); + } + + if (priv->kf_db_seen_bssids != kf_db_seen_bssids) { + gs_strfreev char **tmp_strv = NULL; + gsize i, len; + + nm_key_file_db_unref(priv->kf_db_seen_bssids); + priv->kf_db_seen_bssids = nm_key_file_db_ref(kf_db_seen_bssids); + + tmp_strv = nm_key_file_db_get_string_list(priv->kf_db_seen_bssids, connection_uuid, &len); + + nm_clear_pointer(&priv->seen_bssids, g_hash_table_unref); + + if (len > 0) { + _LOGT("read %zu seen-bssids from keyfile database \"%s\"", + len, + nm_key_file_db_get_filename(priv->kf_db_seen_bssids)); + priv->seen_bssids = _seen_bssids_hash_new(); + for (i = len; i > 0;) + g_hash_table_add(priv->seen_bssids, g_steal_pointer(&tmp_strv[--i])); + nm_clear_g_free(&tmp_strv); + } else { + NMSettingWireless *s_wifi; + + _LOGT("no seen-bssids from keyfile database \"%s\"", + nm_key_file_db_get_filename(priv->kf_db_seen_bssids)); + + /* If this connection didn't have an entry in the seen-bssids database, + * maybe this is the first time we've read it in, so populate the + * seen-bssids list from the deprecated seen-bssids property of the + * wifi setting. + */ + s_wifi = + nm_connection_get_setting_wireless(nm_settings_connection_get_connection(self)); + if (s_wifi) { + len = nm_setting_wireless_get_num_seen_bssids(s_wifi); + if (len > 0) { + priv->seen_bssids = _seen_bssids_hash_new(); + for (i = 0; i < len; i++) { + const char *bssid = nm_setting_wireless_get_seen_bssid(s_wifi, i); + + g_hash_table_add(priv->seen_bssids, g_strdup(bssid)); + } + } + } + } + } +} + +/** + * nm_settings_connection_get_seen_bssids: + * @self: the #NMSettingsConnection + * + * Returns current list of seen BSSIDs for the connection. + * + * Returns: (transfer container) list of seen BSSIDs (in the standard hex-digits-and-colons notation). + * The caller is responsible for freeing the list, but not the content. + **/ +const char ** +nm_settings_connection_get_seen_bssids(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + return nm_utils_strdict_get_keys(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->seen_bssids, + TRUE, + NULL); +} + +/** + * nm_settings_connection_has_seen_bssid: + * @self: the #NMSettingsConnection + * @bssid: the BSSID to check the seen BSSID list for + * + * Returns: %TRUE if the given @bssid is in the seen BSSIDs list + **/ +gboolean +nm_settings_connection_has_seen_bssid(NMSettingsConnection *self, const char *bssid) +{ + NMSettingsConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE); + g_return_val_if_fail(bssid, FALSE); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + return priv->seen_bssids + && g_hash_table_contains(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->seen_bssids, bssid); +} + +/** + * nm_settings_connection_add_seen_bssid: + * @self: the #NMSettingsConnection + * @seen_bssid: BSSID to set into the connection and to store into + * the seen-bssids database + * + * Updates the connection and seen-bssids database with the provided BSSID. + **/ +void +nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + gs_free const char ** strv = NULL; + const char * connection_uuid; + + g_return_if_fail(seen_bssid != NULL); + + if (!priv->seen_bssids) + priv->seen_bssids = _seen_bssids_hash_new(); + + g_hash_table_add(priv->seen_bssids, g_strdup(seen_bssid)); + + if (!priv->kf_db_seen_bssids) + return; + + connection_uuid = nm_settings_connection_get_uuid(self); + if (!connection_uuid) + return; + + strv = nm_utils_strdict_get_keys(priv->seen_bssids, TRUE, NULL); + + nm_key_file_db_set_string_list(priv->kf_db_seen_bssids, + connection_uuid, + strv ?: NM_PTRARRAY_EMPTY(const char *), + -1); +} + +/*****************************************************************************/ + +/** + * nm_settings_connection_autoconnect_retries_get: + * @self: the settings connection + * + * Returns the number of autoconnect retries left. If the value is + * not yet set, initialize it with the value from the connection or + * with the global default. + */ +int +nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + if (G_UNLIKELY(priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET)) { + _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE); + } + return priv->autoconnect_retries; +} + +void +nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries) +{ + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + g_return_if_fail(retries >= 0); + + _autoconnect_retries_set(self, retries, FALSE); +} + +void +nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self) +{ + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self)); + + _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE); +} + +gint32 +nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self) +{ + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_retries_blocked_until; +} + +static NM_UTILS_FLAGS2STR_DEFINE( + _autoconnect_blocked_reason_to_string, + NMSettingsAutoconnectBlockedReason, + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, "failed"), + NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), ); + +NMSettingsAutoconnectBlockedReason +nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self) +{ + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_blocked_reason; +} + +gboolean +nm_settings_connection_autoconnect_blocked_reason_set_full(NMSettingsConnection * self, + NMSettingsAutoconnectBlockedReason mask, + NMSettingsAutoconnectBlockedReason value) +{ + NMSettingsAutoconnectBlockedReason v; + NMSettingsConnectionPrivate * priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + char buf[100]; + + nm_assert(mask); + nm_assert(!NM_FLAGS_ANY(value, ~mask)); + + v = priv->autoconnect_blocked_reason; + v = (v & ~mask) | (value & mask); + + if (priv->autoconnect_blocked_reason == v) + return FALSE; + + _LOGT("autoconnect: blocked reason: %s", + _autoconnect_blocked_reason_to_string(v, buf, sizeof(buf))); + priv->autoconnect_blocked_reason = v; + return TRUE; +} + +gboolean +nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv; + NMSettingsConnectionIntFlags flags; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), TRUE); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) + return TRUE; + if (priv->autoconnect_retries == 0) + return TRUE; + + flags = priv->flags; + if (NM_FLAGS_ANY(flags, + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return TRUE; + if (!NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) + return TRUE; + + return FALSE; +} + +/*****************************************************************************/ + +/** + * nm_settings_connection_get_filename: + * @self: an #NMSettingsConnection + * + * Gets the filename that @self was read from/written to. This may be + * %NULL if @self is unsaved, or if it is associated with a backend that + * does not store each connection in a separate file. + * + * Returns: @self's filename. + */ +const char * +nm_settings_connection_get_filename(NMSettingsConnection *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->filename; +} + +const char * +nm_settings_connection_get_id(NMSettingsConnection *self) +{ + return nm_connection_get_id(nm_settings_connection_get_connection(self)); +} + +const char * +nm_settings_connection_get_uuid(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv; + const char * uuid; + + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL); + + priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + + uuid = nm_settings_storage_get_uuid(priv->storage); + + nm_assert( + uuid + && nm_streq0(uuid, nm_connection_get_uuid(nm_settings_connection_get_connection(self)))); + + return uuid; +} + +const char * +nm_settings_connection_get_connection_type(NMSettingsConnection *self) +{ + return nm_connection_get_connection_type(nm_settings_connection_get_connection(self)); +} + +/*****************************************************************************/ + +void +_nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + AuthData * auth_data; + + while ((auth_data = c_list_first_entry(&priv->auth_lst_head, AuthData, auth_lst))) + nm_auth_manager_check_authorization_cancel(auth_data->call_id); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object); + + switch (prop_id) { + case PROP_UNSAVED: + g_value_set_boolean(value, nm_settings_connection_get_unsaved(self)); + break; + case PROP_FLAGS: + g_value_set_uint(value, + nm_settings_connection_get_flags(self) + & _NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK); + break; + case PROP_FILENAME: + g_value_set_string(value, nm_settings_connection_get_filename(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_settings_connection_init(NMSettingsConnection *self) +{ + NMSettingsConnectionPrivate *priv; + + priv = + G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionPrivate); + self->_priv = priv; + + c_list_init(&self->_connections_lst); + + c_list_init(&priv->call_ids_lst_head); + c_list_init(&priv->auth_lst_head); + + priv->agent_mgr = g_object_ref(nm_agent_manager_get()); + priv->settings = g_object_ref(nm_settings_get()); + + priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET; +} + +NMSettingsConnection * +nm_settings_connection_new(void) +{ + return g_object_new(NM_TYPE_SETTINGS_CONNECTION, NULL); +} + +static void +dispose(GObject *object) +{ + NMSettingsConnection * self = NM_SETTINGS_CONNECTION(object); + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self); + NMSettingsConnectionCallId * call_id, *call_id_safe; + + _LOGD("disposing"); + + nm_assert(!priv->default_wired_device); + + nm_assert(c_list_is_empty(&self->_connections_lst)); + nm_assert(c_list_is_empty(&priv->auth_lst_head)); + + /* Cancel in-progress secrets requests */ + if (priv->agent_mgr) { + c_list_for_each_entry_safe (call_id, call_id_safe, &priv->call_ids_lst_head, call_ids_lst) + _get_secrets_cancel(self, call_id, TRUE); + } + + nm_clear_pointer(&priv->system_secrets, g_variant_unref); + nm_clear_pointer(&priv->agent_secrets, g_variant_unref); + + nm_clear_pointer(&priv->seen_bssids, g_hash_table_destroy); + + g_clear_object(&priv->agent_mgr); + + g_clear_object(&priv->connection); + + nm_clear_pointer(&priv->kf_db_timestamps, nm_key_file_db_unref); + nm_clear_pointer(&priv->kf_db_seen_bssids, nm_key_file_db_unref); + + G_OBJECT_CLASS(nm_settings_connection_parent_class)->dispose(object); + + g_clear_object(&priv->storage); + + nm_clear_g_free(&priv->filename); + + g_clear_object(&priv->settings); +} + +/*****************************************************************************/ + +static const GDBusSignalInfo signal_info_updated = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("Updated", ); + +static const GDBusSignalInfo signal_info_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("Removed", ); + +static const NMDBusInterfaceInfoExtended interface_info_settings_connection = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_SETTINGS_CONNECTION, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "Update", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("properties", "a{sa{sv}}"), ), ), + .handle = impl_settings_connection_update, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "UpdateUnsaved", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("properties", "a{sa{sv}}"), ), ), + .handle = impl_settings_connection_update_unsaved, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ), + .handle = impl_settings_connection_delete, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetSettings", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"), ), ), + .handle = impl_settings_connection_get_settings, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetSecrets", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("setting_name", "s"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("secrets", "a{sa{sv}}"), ), ), + .handle = impl_settings_connection_get_secrets, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("ClearSecrets", ), + .handle = impl_settings_connection_clear_secrets, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Save", ), + .handle = impl_settings_connection_save, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "Update2", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), + NM_DEFINE_GDBUS_ARG_INFO("args", "a{sv}"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ), + .handle = impl_settings_connection_update2, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_updated, + &signal_info_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Unsaved", + "b", + NM_SETTINGS_CONNECTION_UNSAVED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Flags", + "u", + NM_SETTINGS_CONNECTION_FLAGS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Filename", + "s", + NM_SETTINGS_CONNECTION_FILENAME), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_settings_connection_class_init(NMSettingsConnectionClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingsConnectionPrivate)); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH_SETTINGS); + dbus_object_class->interface_infos = + NM_DBUS_INTERFACE_INFOS(&interface_info_settings_connection); + + object_class->dispose = dispose; + object_class->get_property = get_property; + + obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_SETTINGS_CONNECTION_UNSAVED, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_SETTINGS_CONNECTION_FLAGS, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FILENAME] = g_param_spec_string(NM_SETTINGS_CONNECTION_FILENAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + /* internal signal, with an argument (NMSettingsConnectionUpdateReason update_reason) as + * guint. */ + signals[UPDATED_INTERNAL] = g_signal_new(NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[FLAGS_CHANGED] = g_signal_new(NM_SETTINGS_CONNECTION_FLAGS_CHANGED, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h new file mode 100644 index 0000000..3954fd8 --- /dev/null +++ b/src/core/settings/nm-settings-connection.h @@ -0,0 +1,394 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__ +#define __NETWORKMANAGER_SETTINGS_CONNECTION_H__ + +#include "nm-dbus-object.h" +#include "nm-connection.h" + +#include "nm-settings-storage.h" + +/*****************************************************************************/ + +typedef enum { + + NM_SETTINGS_CONNECTION_ADD_REASON_NONE = 0, + + NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT = (1u << 0), + +} NMSettingsConnectionAddReason; + +typedef enum { + + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE = 0, + + /* with persist-mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, and + * update tries to update the profile on disk (which can always fail). + * In some cases we want to ignore such failure and proceed. For example, + * when we receive secrets from a secret-agent, we want to update the connection + * at all cost and ignore failures to write them to disk. */ + NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE = (1u << 0), + + /* When updating the profile, force renaming the file on disk. That matters + * only for keyfile plugin. Keyfile prefers a filename based on connection.id. + * When the connection.id changes we might want to rename the file on disk + * (that is, don't overwrite the existing file, but delete it and write it + * with the new name). + * This flag forces such rename. */ + NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME = (1u << 1), + + /* Usually, changing a profile that is currently active does not immediately + * reapply the changes. The exception are connection.zone and connection.metered + * properties. When this flag is set, then these two properties are reapplied + * right away. + * + * See also %NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY flag, to prevent partial reapply + * during Update2(). */ + NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL = (1u << 2), + + NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS = (1u << 3), + NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS = (1u << 4), + + NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS = (1u << 5), + NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS = (1u << 6), + + /* if a profile was greated as default-wired connection for a device, then + * when the user modifies it via D-Bus, the profile should become persisted + * to disk and it the purpose why the profile was created should be forgotten. */ + NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_DEFAULT_WIRED = (1u << 7), + + NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT = (1u << 8), + +} NMSettingsConnectionUpdateReason; + +typedef enum { + + /* if the profile is in-memory, update it in-memory and keep it. + * if the profile is on-disk, update it on-disk, and keep it. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + + /* persist to disk. If the profile is currently in-memory, remove + * it from /run. Depending on the shadowed-storage, the pre-existing + * file is reused when moving the storage. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_TO_DISK. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + + /* Update in-memory (i.e. persist to /run). If the profile is currently on disk, + * then a reference to the profile is remembered as "shadowed-storage". + * Later, when storing again to persistent storage, the shadowed-storage is + * updated. When deleting the profile, the shadowed-storage is also deleted + * from disk. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + + /* Update in-memory (i.e. persist to /run). This is almost like + * %NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, except the in-memory profile + * remembers not to own the shadowed-storage ("shadowed-owned"). + * The difference is that when deleting the in-memory profile, the original + * profile is not deleted but instead the nmmeta tombstone remembers the + * shadowed-storage and re-used it when re-adding the profile. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + + /* Update in-memory (i.e. persist to /run). If the profile is currently on disk, + * delete it from disk. + * + * If the profile is in-memory and has a shadowed-storage, the original profile + * will be deleted from disk. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + + /* This only updates the connection in-memory. Note that "in-memory" above + * means to write to keyfile in /run. This mode really means to not notify the + * settings plugin about the change. This should be only used for updating + * secrets. + */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, + +} NMSettingsConnectionPersistMode; + +/*****************************************************************************/ + +#define NM_TYPE_SETTINGS_CONNECTION (nm_settings_connection_get_type()) +#define NM_SETTINGS_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnection)) +#define NM_SETTINGS_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass)) +#define NM_IS_SETTINGS_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_CONNECTION)) +#define NM_IS_SETTINGS_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_CONNECTION)) +#define NM_SETTINGS_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass)) + +#define NM_SETTINGS_CONNECTION_GET_SECRETS "get-secrets" +#define NM_SETTINGS_CONNECTION_CANCEL_SECRETS "cancel-secrets" +#define NM_SETTINGS_CONNECTION_UPDATED_INTERNAL "updated-internal" +#define NM_SETTINGS_CONNECTION_FLAGS_CHANGED "flags-changed" + +/* Properties */ +#define NM_SETTINGS_CONNECTION_UNSAVED "unsaved" +#define NM_SETTINGS_CONNECTION_FLAGS "flags" +#define NM_SETTINGS_CONNECTION_FILENAME "filename" + +/** + * NMSettingsConnectionIntFlags: + * @NM_SETTINGS_CONNECTION_INT_FLAGS_NONE: no flag set + * @NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED: the connection is not saved to disk. + * See also #NM_SETTINGS_CONNECTION_FLAG_UNSAVED. + * @NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED: A connection is "nm-generated" if + * it was generated by NetworkManger. If the connection gets modified or saved + * by the user, the flag gets cleared. A nm-generated is implicitly unsaved. + * See also #NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED. + * @NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE: The connection will be deleted + * when it disconnects. That is for in-memory connections (unsaved), which are + * currently active but cleanup on disconnect. + * See also #NM_SETTINGS_CONNECTION_FLAG_VOLATILE. + * @NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL: the profile was generated to + * represent the external activation of a device. See also #NM_SETTINGS_CONNECTION_FLAG_EXTERNAL. + * @NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE: The connection is visible + * @_NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK: the entire enum is + * internal, however, parts of it is public API as #NMSettingsConnectionFlags. + * This mask, are the public flags. + * @_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL: special mask, for all known flags + * + * #NMSettingsConnection flags. + **/ +typedef enum _NMSettingsConnectionIntFlags { + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE = 0, + + NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED = NM_SETTINGS_CONNECTION_FLAG_UNSAVED, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED = NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED, + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE = NM_SETTINGS_CONNECTION_FLAG_VOLATILE, + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL = NM_SETTINGS_CONNECTION_FLAG_EXTERNAL, + + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE = 0x10, + + _NM_SETTINGS_CONNECTION_INT_FLAGS_LAST, + + _NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK = + 0 | NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL | 0, + + _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK = + 0 | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL | 0, + + _NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1, +} NMSettingsConnectionIntFlags; + +typedef enum { + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE = 0, + + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0), + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED = (1LL << 1), + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS = (1LL << 2), + + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL = + (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST + | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED + | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS), +} NMSettingsAutoconnectBlockedReason; + +typedef struct _NMSettingsConnectionCallId NMSettingsConnectionCallId; + +typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass; + +struct _NMSettingsConnectionPrivate; + +struct _NMSettingsConnection { + NMDBusObject parent; + CList _connections_lst; + struct _NMSettingsConnectionPrivate *_priv; +}; + +GType nm_settings_connection_get_type(void); + +NMSettingsConnection *nm_settings_connection_new(void); + +NMConnection *nm_settings_connection_get_connection(NMSettingsConnection *self); + +void _nm_settings_connection_set_connection(NMSettingsConnection * self, + NMConnection * new_connection, + NMConnection ** out_old_connection, + NMSettingsConnectionUpdateReason update_reason); + +NMSettingsStorage *nm_settings_connection_get_storage(NMSettingsConnection *self); + +void _nm_settings_connection_set_storage(NMSettingsConnection *self, NMSettingsStorage *storage); + +gboolean nm_settings_connection_still_valid(NMSettingsConnection *self); + +const char *nm_settings_connection_get_filename(NMSettingsConnection *self); + +guint64 nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self); + +gboolean +nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self, + NMConnection * applied_connection, + NMSettingCompareFlags compare_flage); + +gboolean nm_settings_connection_update(NMSettingsConnection * self, + NMConnection * new_connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + NMSettingsConnectionUpdateReason update_reason, + const char * log_context_name, + GError ** error); + +void nm_settings_connection_delete(NMSettingsConnection *self, + gboolean allow_add_to_no_auto_default); + +typedef void (*NMSettingsConnectionSecretsFunc)(NMSettingsConnection * self, + NMSettingsConnectionCallId *call_id, + const char * agent_username, + const char * setting_name, + GError * error, + gpointer user_data); + +gboolean nm_settings_connection_new_secrets(NMSettingsConnection *self, + NMConnection * applied_connection, + const char * setting_name, + GVariant * secrets, + GError ** error); + +NMSettingsConnectionCallId * +nm_settings_connection_get_secrets(NMSettingsConnection * self, + NMConnection * applied_connection, + NMAuthSubject * subject, + const char * setting_name, + NMSecretAgentGetSecretsFlags flags, + const char *const * hints, + NMSettingsConnectionSecretsFunc callback, + gpointer callback_data); + +void nm_settings_connection_cancel_secrets(NMSettingsConnection * self, + NMSettingsConnectionCallId *call_id); + +void nm_settings_connection_clear_secrets(NMSettingsConnection *self, + gboolean clear_cached_system_secrets, + gboolean persist); + +gboolean nm_settings_connection_check_visibility(NMSettingsConnection *self, + NMSessionMonitor * session_monitor); + +gboolean nm_settings_connection_check_permission(NMSettingsConnection *self, + const char * permission); + +/*****************************************************************************/ + +NMDevice *nm_settings_connection_default_wired_get_device(NMSettingsConnection *self); +void nm_settings_connection_default_wired_set_device(NMSettingsConnection *self, NMDevice *device); + +/*****************************************************************************/ + +NMSettingsConnectionIntFlags nm_settings_connection_get_flags(NMSettingsConnection *self); + +static inline gboolean +nm_settings_connection_get_unsaved(NMSettingsConnection *self) +{ + return NM_FLAGS_HAS(nm_settings_connection_get_flags(self), + NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED); +} + +NMSettingsConnectionIntFlags +nm_settings_connection_set_flags_full(NMSettingsConnection * self, + NMSettingsConnectionIntFlags mask, + NMSettingsConnectionIntFlags value); + +static inline NMSettingsConnectionIntFlags +nm_settings_connection_set_flags(NMSettingsConnection * self, + NMSettingsConnectionIntFlags flags, + gboolean set) +{ + return nm_settings_connection_set_flags_full(self, + flags, + set ? flags + : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE); +} + +/*****************************************************************************/ + +int nm_settings_connection_cmp_timestamp(NMSettingsConnection *ac, NMSettingsConnection *ab); +int nm_settings_connection_cmp_timestamp_p_with_data(gconstpointer pa, + gconstpointer pb, + gpointer user_data); +int nm_settings_connection_cmp_autoconnect_priority(NMSettingsConnection *a, + NMSettingsConnection *b); +int nm_settings_connection_cmp_autoconnect_priority_p_with_data(gconstpointer pa, + gconstpointer pb, + gpointer user_data); + +struct _NMKeyFileDB; + +void _nm_settings_connection_register_kf_dbs(NMSettingsConnection *self, + struct _NMKeyFileDB * kf_db_timestamps, + struct _NMKeyFileDB * kf_db_seen_bssids); + +gboolean nm_settings_connection_get_timestamp(NMSettingsConnection *self, guint64 *out_timestamp); + +void nm_settings_connection_update_timestamp(NMSettingsConnection *self, guint64 timestamp); + +const char **nm_settings_connection_get_seen_bssids(NMSettingsConnection *self); + +gboolean nm_settings_connection_has_seen_bssid(NMSettingsConnection *self, const char *bssid); + +void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid); + +int nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self); +void nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries); +void nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self); + +gint32 nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self); + +NMSettingsAutoconnectBlockedReason + nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self); +gboolean nm_settings_connection_autoconnect_blocked_reason_set_full( + NMSettingsConnection * self, + NMSettingsAutoconnectBlockedReason mask, + NMSettingsAutoconnectBlockedReason value); + +static inline gboolean +nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection * self, + NMSettingsAutoconnectBlockedReason mask, + gboolean set) +{ + return nm_settings_connection_autoconnect_blocked_reason_set_full( + self, + mask, + set ? mask : NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE); +} + +gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self); + +const char *nm_settings_connection_get_id(NMSettingsConnection *connection); +const char *nm_settings_connection_get_uuid(NMSettingsConnection *connection); +const char *nm_settings_connection_get_connection_type(NMSettingsConnection *connection); + +/*****************************************************************************/ + +NMConnection ** +nm_settings_connections_array_to_connections(NMSettingsConnection *const *connections, + gssize n_connections); + +/*****************************************************************************/ + +void _nm_settings_connection_emit_dbus_signal_updated(NMSettingsConnection *self); +void _nm_settings_connection_emit_dbus_signal_removed(NMSettingsConnection *self); + +void _nm_settings_connection_emit_signal_updated_internal( + NMSettingsConnection * self, + NMSettingsConnectionUpdateReason update_reason); + +void _nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self); + +#endif /* __NETWORKMANAGER_SETTINGS_CONNECTION_H__ */ diff --git a/src/core/settings/nm-settings-plugin.c b/src/core/settings/nm-settings-plugin.c new file mode 100644 index 0000000..74ae314 --- /dev/null +++ b/src/core/settings/nm-settings-plugin.c @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2018 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-settings-plugin.h" + +#include "nm-utils.h" +#include "nm-core-internal.h" + +#include "nm-settings-connection.h" + +/*****************************************************************************/ + +enum { + UNMANAGED_SPECS_CHANGED, + UNRECOGNIZED_SPECS_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE(NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT) + +/*****************************************************************************/ + +int +nm_settings_plugin_cmp_by_priority(const NMSettingsPlugin *a, + const NMSettingsPlugin *b, + const GSList * plugin_list) +{ + nm_assert(NM_IS_SETTINGS_PLUGIN(a)); + nm_assert(NM_IS_SETTINGS_PLUGIN(b)); + + if (a != b) { + int idx_a = g_slist_index((GSList *) plugin_list, a); + int idx_b = g_slist_index((GSList *) plugin_list, b); + + /* the plugins must be found in the list. */ + nm_assert(idx_a >= 0); + nm_assert(idx_b >= 0); + + /* plugins that appear first in @plugin_list have higher priority. + * That means: smaller index -> higher priority. Reverse sort. */ + NM_CMP_DIRECT(idx_b, idx_a); + } + + return 0; +} + +/*****************************************************************************/ + +GSList * +nm_settings_plugin_get_unmanaged_specs(NMSettingsPlugin *self) +{ + NMSettingsPluginClass *klass; + + g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (!klass->get_unmanaged_specs) + return NULL; + return klass->get_unmanaged_specs(self); +} + +GSList * +nm_settings_plugin_get_unrecognized_specs(NMSettingsPlugin *self) +{ + NMSettingsPluginClass *klass; + + g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (!klass->get_unrecognized_specs) + return NULL; + return klass->get_unrecognized_specs(self); +} + +void +nm_settings_plugin_reload_connections(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSettingsPluginClass *klass; + + g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self)); + g_return_if_fail(callback); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (klass->reload_connections) + klass->reload_connections(self, callback, user_data); +} + +NMSettingsPluginConnectionLoadEntry * +nm_settings_plugin_create_connection_load_entries(const char *const *filenames, gsize *out_len) +{ + NMSettingsPluginConnectionLoadEntry *entries; + gsize len; + gsize i; + + len = NM_PTRARRAY_LEN(filenames); + if (len == 0) { + *out_len = 0; + return NULL; + } + + entries = g_new(NMSettingsPluginConnectionLoadEntry, len); + for (i = 0; i < len; i++) { + entries[i] = (NMSettingsPluginConnectionLoadEntry){ + .filename = filenames[i], + .error = NULL, + .handled = FALSE, + }; + } + + *out_len = len; + return entries; +} + +void +nm_settings_plugin_load_connections(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadEntry * entries, + gsize n_entries, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSettingsPluginClass *klass; + + g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self)); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (klass->load_connections) + klass->load_connections(self, entries, n_entries, callback, user_data); +} + +void +nm_settings_plugin_load_connections_done(NMSettingsPlugin *self) +{ + NMSettingsPluginClass *klass; + + g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self)); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (klass->load_connections_done) + klass->load_connections_done(self); +} + +gboolean +nm_settings_plugin_add_connection(NMSettingsPlugin * self, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSettingsPluginClass *klass; + + g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + +#if NM_MORE_ASSERTS > 5 + nm_assert(nm_connection_verify(connection, NULL)); +#endif + + NM_SET_OUT(out_storage, NULL); + NM_SET_OUT(out_connection, NULL); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + if (!klass->add_connection) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_NOT_SUPPORTED, + "settings plugin does not support adding connections"); + return FALSE; + } + return klass->add_connection(self, connection, out_storage, out_connection, error); +} + +gboolean +nm_settings_plugin_update_connection(NMSettingsPlugin * self, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSettingsPluginClass *klass = NULL; + + g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE); + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE); + g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + +#if NM_MORE_ASSERTS > 5 + nm_assert(nm_connection_verify(connection, NULL)); + nm_assert(nm_streq(nm_connection_get_uuid(connection), nm_settings_storage_get_uuid(storage))); +#endif + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + + NM_SET_OUT(out_storage, NULL); + NM_SET_OUT(out_connection, NULL); + + if (!klass->update_connection) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_NOT_SUPPORTED, + "settings plugin does not support modifying connections"); + return FALSE; + } + return klass->update_connection(self, storage, connection, out_storage, out_connection, error); +} + +gboolean +nm_settings_plugin_delete_connection(NMSettingsPlugin * self, + NMSettingsStorage *storage, + GError ** error) +{ + NMSettingsPluginClass *klass = NULL; + + g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE); + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE); + g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + + if (!klass->delete_connection) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_NOT_SUPPORTED, + "settings plugin does not support deleting connections"); + return FALSE; + } + + return klass->delete_connection(self, storage, error); +} + +/*****************************************************************************/ + +void +_nm_settings_plugin_emit_signal_unmanaged_specs_changed(NMSettingsPlugin *self) +{ + nm_assert(NM_IS_SETTINGS_PLUGIN(self)); + + g_signal_emit(self, signals[UNMANAGED_SPECS_CHANGED], 0); +} + +void +_nm_settings_plugin_emit_signal_unrecognized_specs_changed(NMSettingsPlugin *self) +{ + nm_assert(NM_IS_SETTINGS_PLUGIN(self)); + + g_signal_emit(self, signals[UNRECOGNIZED_SPECS_CHANGED], 0); +} + +/*****************************************************************************/ + +static void +nm_settings_plugin_init(NMSettingsPlugin *self) +{} + +static void +nm_settings_plugin_class_init(NMSettingsPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + signals[UNMANAGED_SPECS_CHANGED] = g_signal_new(NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals[UNRECOGNIZED_SPECS_CHANGED] = + g_signal_new(NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/core/settings/nm-settings-plugin.h b/src/core/settings/nm-settings-plugin.h new file mode 100644 index 0000000..a9b9b2a --- /dev/null +++ b/src/core/settings/nm-settings-plugin.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2018 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#ifndef __NM_SETTINGS_PLUGIN_H__ +#define __NM_SETTINGS_PLUGIN_H__ + +#include "nm-connection.h" + +#include "nm-settings-storage.h" + +typedef struct _NMSettingsPlugin NMSettingsPlugin; + +typedef void (*NMSettingsPluginConnectionLoadCallback)(NMSettingsPlugin * self, + NMSettingsStorage *storage, + NMConnection * connection, + gpointer user_data); + +typedef struct { + const char *filename; + GError * error; + bool handled : 1; +} NMSettingsPluginConnectionLoadEntry; + +#define NM_TYPE_SETTINGS_PLUGIN (nm_settings_plugin_get_type()) +#define NM_SETTINGS_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPlugin)) +#define NM_SETTINGS_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass)) +#define NM_IS_SETTINGS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_PLUGIN)) +#define NM_IS_SETTINGS_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_PLUGIN)) +#define NM_SETTINGS_PLUGIN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass)) + +#define NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED "unmanaged-specs-changed" +#define NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED "unrecognized-specs-changed" + +struct _NMSettingsPlugin { + GObject parent; +}; + +typedef struct { + GObjectClass parent; + + /* + * Return a string list of specifications of devices which NetworkManager + * should not manage. Returned list will be freed by the system settings + * service, and each element must be allocated using g_malloc() or its + * variants (g_strdup, g_strdup_printf, etc). + * + * Each string in the list must be in one of the formats recognized by + * nm_device_spec_match_list(). + */ + GSList *(*get_unmanaged_specs)(NMSettingsPlugin *self); + + /* + * Return a string list of specifications of devices for which at least + * one non-NetworkManager-based configuration is defined. Returned list + * will be freed by the system settings service, and each element must be + * allocated using g_malloc() or its variants (g_strdup, g_strdup_printf, + * etc). + * + * Each string in the list must be in one of the formats recognized by + * nm_device_spec_match_list(). + */ + GSList *(*get_unrecognized_specs)(NMSettingsPlugin *self); + + /* Requests that the plugin load/reload a set of filenames. + */ + void (*load_connections)(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadEntry * entries, + gsize n_entries, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data); + + /* Requests that the plugin reload all connection files from disk, + * and emit signals reflecting new, changed, and removed connections. + */ + void (*reload_connections)(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data); + + void (*load_connections_done)(NMSettingsPlugin *self); + + gboolean (*add_connection)(NMSettingsPlugin * self, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + + gboolean (*update_connection)(NMSettingsPlugin * self, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + + gboolean (*delete_connection)(NMSettingsPlugin * self, + NMSettingsStorage *storage, + GError ** error); + + const char *plugin_name; + +} NMSettingsPluginClass; + +/*****************************************************************************/ + +GType nm_settings_plugin_get_type(void); + +/*****************************************************************************/ + +#define NM_SETTINGS_STORAGE_PRINT_FMT NM_HASH_OBFUSCATE_PTR_FMT "/%s" + +#define NM_SETTINGS_STORAGE_PRINT_ARG(storage) \ + NM_HASH_OBFUSCATE_PTR(storage), \ + nm_settings_plugin_get_plugin_name(nm_settings_storage_get_plugin(storage)) + +static inline const char * +nm_settings_plugin_get_plugin_name(NMSettingsPlugin *self) +{ + NMSettingsPluginClass *klass; + + nm_assert(NM_SETTINGS_PLUGIN(self)); + + klass = NM_SETTINGS_PLUGIN_GET_CLASS(self); + + nm_assert(klass && klass->plugin_name && strlen(klass->plugin_name) > 0); + + return klass->plugin_name; +} + +/*****************************************************************************/ + +GSList *nm_settings_plugin_get_unmanaged_specs(NMSettingsPlugin *self); +GSList *nm_settings_plugin_get_unrecognized_specs(NMSettingsPlugin *self); + +void nm_settings_plugin_reload_connections(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data); + +NMSettingsPluginConnectionLoadEntry * +nm_settings_plugin_create_connection_load_entries(const char *const *filenames, gsize *out_len); + +void nm_settings_plugin_load_connections(NMSettingsPlugin * self, + NMSettingsPluginConnectionLoadEntry * entries, + gsize n_entries, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data); + +void nm_settings_plugin_load_connections_done(NMSettingsPlugin *self); + +gboolean nm_settings_plugin_add_connection(NMSettingsPlugin * self, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + +gboolean nm_settings_plugin_update_connection(NMSettingsPlugin * self, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + +gboolean nm_settings_plugin_delete_connection(NMSettingsPlugin * self, + NMSettingsStorage *storage, + GError ** error); + +/*****************************************************************************/ + +typedef NMSettingsPlugin *(*NMSettingsPluginFactoryFunc)(void); + +NMSettingsPlugin *nm_settings_plugin_factory(void); + +/***************************************************************************** + * Internal API + *****************************************************************************/ + +void _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NMSettingsPlugin *self); + +void _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NMSettingsPlugin *self); + +/*****************************************************************************/ + +int nm_settings_plugin_cmp_by_priority(const NMSettingsPlugin *a, + const NMSettingsPlugin *b, + const GSList * plugin_list); + +/*****************************************************************************/ + +/* forward declare this function from NMSettings. It's used by the ifcfg-rh plugin, + * but that shouldn't include all "nm-settings.h" header. */ +NMSettings *nm_settings_get(void); + +const char *nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid); + +#endif /* __NM_SETTINGS_PLUGIN_H__ */ diff --git a/src/core/settings/nm-settings-storage.c b/src/core/settings/nm-settings-storage.c new file mode 100644 index 0000000..bea440c --- /dev/null +++ b/src/core/settings/nm-settings-storage.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-settings-storage.h" + +#include "nm-utils.h" +#include "nm-settings-plugin.h" + +#include "settings/plugins/keyfile/nms-keyfile-storage.h" + +/*****************************************************************************/ + +int +nm_settings_storage_cmp(NMSettingsStorage *a, NMSettingsStorage *b, const GSList *plugin_list) +{ + NMSettingsStorageClass *klass; + + /* Sort by priority. + * + * If a > b (by priority), we return a positive number (as one + * would expect by a cmp() function). */ + + nm_assert(NM_IS_SETTINGS_STORAGE(a)); + nm_assert(NM_IS_SETTINGS_STORAGE(b)); + nm_assert(a != b); + nm_assert(nm_streq(nm_settings_storage_get_uuid(a), nm_settings_storage_get_uuid(b))); + + /* in-memory has always higher priority */ + NM_CMP_DIRECT(nm_settings_storage_is_keyfile_run(a), nm_settings_storage_is_keyfile_run(b)); + + NM_CMP_RETURN(nm_settings_plugin_cmp_by_priority(nm_settings_storage_get_plugin(a), + nm_settings_storage_get_plugin(b), + plugin_list)); + + klass = NM_SETTINGS_STORAGE_GET_CLASS(a); + if (klass != NM_SETTINGS_STORAGE_GET_CLASS(b)) { + /* one plugin must return storages of the same type. Otherwise, it's + * unclear how cmp_fcn() should compare them. */ + nm_assert_not_reached(); + return 0; + } + + if (klass->cmp_fcn) + NM_CMP_RETURN(klass->cmp_fcn(a, b)); + + return 0; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLUGIN, PROP_UUID, PROP_FILENAME, ); + +G_DEFINE_TYPE(NMSettingsStorage, nm_settings_storage, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingsStorage *self = NM_SETTINGS_STORAGE(object); + + switch (prop_id) { + case PROP_PLUGIN: + /* construct-only */ + self->_plugin = g_object_ref(g_value_get_object(value)); + nm_assert(NM_IS_SETTINGS_PLUGIN(self->_plugin)); + break; + case PROP_UUID: + /* construct-only */ + self->_uuid = g_value_dup_string(value); + nm_assert(!self->_uuid || nm_utils_is_uuid(self->_uuid)); + break; + case PROP_FILENAME: + /* construct-only */ + self->_filename = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_settings_storage_init(NMSettingsStorage *self) +{ + c_list_init(&self->_storage_lst); + c_list_init(&self->_storage_by_uuid_lst); +} + +NMSettingsStorage * +nm_settings_storage_new(NMSettingsPlugin *plugin, const char *uuid, const char *filename) +{ + nm_assert(NM_IS_SETTINGS_PLUGIN(plugin)); + nm_assert(nm_utils_is_uuid(uuid)); + + return g_object_new(NM_TYPE_SETTINGS_STORAGE, + NM_SETTINGS_STORAGE_PLUGIN, + plugin, + NM_SETTINGS_STORAGE_UUID, + uuid, + NM_SETTINGS_STORAGE_FILENAME, + filename, + NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingsStorage *self = NM_SETTINGS_STORAGE(object); + + c_list_unlink_stale(&self->_storage_lst); + c_list_unlink_stale(&self->_storage_by_uuid_lst); + + g_object_unref(self->_plugin); + g_free(self->_uuid); + g_free(self->_filename); + + G_OBJECT_CLASS(nm_settings_storage_parent_class)->finalize(object); +} + +static void +nm_settings_storage_class_init(NMSettingsStorageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->set_property = set_property; + object_class->finalize = finalize; + + obj_properties[PROP_PLUGIN] = + g_param_spec_object(NM_SETTINGS_STORAGE_PLUGIN, + "", + "", + NM_TYPE_SETTINGS_PLUGIN, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_UUID] = + g_param_spec_string(NM_SETTINGS_STORAGE_UUID, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_FILENAME] = + g_param_spec_string(NM_SETTINGS_STORAGE_FILENAME, + "", + "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/settings/nm-settings-storage.h b/src/core/settings/nm-settings-storage.h new file mode 100644 index 0000000..970cfd7 --- /dev/null +++ b/src/core/settings/nm-settings-storage.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_SETTINGS_STORAGE_H__ +#define __NM_SETTINGS_STORAGE_H__ + +/*****************************************************************************/ + +#include "c-list/src/c-list.h" + +#define NM_TYPE_SETTINGS_STORAGE (nm_settings_storage_get_type()) +#define NM_SETTINGS_STORAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorage)) +#define NM_SETTINGS_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass)) +#define NM_IS_SETTINGS_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_STORAGE)) +#define NM_IS_SETTINGS_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_STORAGE)) +#define NM_SETTINGS_STORAGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass)) + +#define NM_SETTINGS_STORAGE_PLUGIN "plugin" +#define NM_SETTINGS_STORAGE_UUID "uuid" +#define NM_SETTINGS_STORAGE_FILENAME "filename" + +struct _NMSettingsPlugin; + +typedef struct NMSettingsStorage { + GObject parent; + struct _NMSettingsPlugin *_plugin; + char * _uuid; + char * _filename; + CList _storage_lst; + CList _storage_by_uuid_lst; +} NMSettingsStorage; + +typedef struct { + GObjectClass parent; + + int (*cmp_fcn)(NMSettingsStorage *a, NMSettingsStorage *b); + +} NMSettingsStorageClass; + +GType nm_settings_storage_get_type(void); + +NMSettingsStorage * +nm_settings_storage_new(struct _NMSettingsPlugin *plugin, const char *uuid, const char *filename); + +static inline struct _NMSettingsPlugin * +nm_settings_storage_get_plugin(const NMSettingsStorage *self) +{ + GType nm_settings_plugin_get_type(void); + + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL); + + nm_assert(G_TYPE_CHECK_INSTANCE_TYPE(self->_plugin, nm_settings_plugin_get_type())); + return self->_plugin; +} + +static inline const char * +nm_settings_storage_get_uuid(const NMSettingsStorage *self) +{ + gboolean nm_utils_is_uuid(const char *str); + + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL); + + nm_assert(nm_utils_is_uuid(self->_uuid)); + return self->_uuid; +} + +static inline const char * +nm_settings_storage_get_uuid_opt(const NMSettingsStorage *self) +{ + gboolean nm_utils_is_uuid(const char *str); + + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL); + + nm_assert(!self->_uuid || nm_utils_is_uuid(self->_uuid)); + return self->_uuid; +} + +static inline const char * +nm_settings_storage_get_filename(const NMSettingsStorage *self) +{ + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL); + + return self->_filename; +} + +/*****************************************************************************/ + +#define nm_assert_valid_settings_storage(plugin, storage) \ + G_STMT_START \ + { \ + NMSettingsPlugin *const _plugin = (plugin); \ + NMSettingsStorage *const _storage = (storage); \ + \ + nm_assert(!_plugin || NM_IS_SETTINGS_PLUGIN(_plugin)); \ + nm_assert(NM_IS_SETTINGS_STORAGE(_storage)); \ + nm_assert(!_plugin || nm_settings_storage_get_plugin(_storage) == _plugin); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +int nm_settings_storage_cmp(NMSettingsStorage *sd_a, + NMSettingsStorage *sd_b, + const GSList * plugin_list); + +#endif /* __NM_SETTINGS_STORAGE_H__ */ diff --git a/src/core/settings/nm-settings-utils.c b/src/core/settings/nm-settings-utils.c new file mode 100644 index 0000000..f6c2f60 --- /dev/null +++ b/src/core/settings/nm-settings-utils.c @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-settings-utils.h" + +#include +#include +#include +#include + +#include "nm-settings-plugin.h" + +/*****************************************************************************/ + +const struct timespec * +nm_sett_util_stat_mtime(const char *filename, gboolean do_lstat, struct timespec *out_val) +{ + struct stat st; + struct timeval now_tv; + + if (filename) { + if (do_lstat) { + if (lstat(filename, &st) == 0) { + *out_val = st.st_mtim; + return out_val; + } + } else { + if (stat(filename, &st) == 0) { + *out_val = st.st_mtim; + return out_val; + } + } + } + + if (gettimeofday(&now_tv, NULL) == 0) { + *out_val = (struct timespec){ + .tv_sec = now_tv.tv_sec, + .tv_nsec = now_tv.tv_usec * 1000u, + }; + return out_val; + } + + *out_val = (struct timespec){}; + return out_val; +} + +/*****************************************************************************/ + +gboolean +nm_sett_util_allow_filename_cb(const char *filename, gpointer user_data) +{ + const NMSettUtilAllowFilenameData *allow_filename_data = user_data; + + if (allow_filename_data->allowed_filename + && nm_streq(allow_filename_data->allowed_filename, filename)) + return TRUE; + + return !g_hash_table_contains(allow_filename_data->idx_by_filename, filename); +} + +/*****************************************************************************/ + +void +nm_sett_util_storage_by_uuid_head_destroy(NMSettUtilStorageByUuidHead *sbuh) +{ + CList *iter; + + while ((iter = c_list_first(&sbuh->_storage_by_uuid_lst_head))) + c_list_unlink(iter); + g_free(sbuh); +} + +/*****************************************************************************/ + +void +nm_sett_util_storages_clear(NMSettUtilStorages *storages) +{ + nm_clear_pointer(&storages->idx_by_uuid, g_hash_table_destroy); + nm_clear_pointer(&storages->idx_by_filename, g_hash_table_destroy); + nm_assert(c_list_is_empty(&storages->_storage_lst_head)); +} + +void +nm_sett_util_storages_add_take(NMSettUtilStorages *storages, + gpointer storage_take_p /* NMSettingsStorage *, take reference */) +{ + NMSettingsStorage * storage_take = storage_take_p; + NMSettUtilStorageByUuidHead *sbuh; + const char * uuid; + + nm_assert(storage_take); + nm_assert(c_list_is_empty(&storage_take->_storage_lst)); + nm_assert(c_list_is_empty(&storage_take->_storage_by_uuid_lst)); + nm_assert(nm_settings_storage_get_filename(storage_take)); + + if (!g_hash_table_replace(storages->idx_by_filename, + (char *) nm_settings_storage_get_filename(storage_take), + storage_take /* takes ownership of reference. */)) + nm_assert_not_reached(); + + uuid = nm_settings_storage_get_uuid_opt(storage_take); + + if (uuid) { + sbuh = nm_sett_util_storages_lookup_by_uuid(storages, uuid); + if (!sbuh) { + gsize l = strlen(uuid) + 1; + + sbuh = g_malloc(sizeof(NMSettUtilStorageByUuidHead) + l); + sbuh->uuid = sbuh->uuid_data; + c_list_init(&sbuh->_storage_by_uuid_lst_head); + memcpy(sbuh->uuid_data, uuid, l); + g_hash_table_add(storages->idx_by_uuid, sbuh); + } + c_list_link_tail(&sbuh->_storage_by_uuid_lst_head, &storage_take->_storage_by_uuid_lst); + } + + c_list_link_tail(&storages->_storage_lst_head, &storage_take->_storage_lst); +} + +gpointer /* NMSettingsStorage * */ +nm_sett_util_storages_steal(NMSettUtilStorages *storages, + gpointer storage_p /* NMSettingsStorage **/) +{ + NMSettingsStorage * storage = storage_p; + NMSettUtilStorageByUuidHead *sbuh; + const char * uuid; + + nm_assert(storage); + nm_assert(nm_sett_util_storages_lookup_by_filename(storages, + nm_settings_storage_get_filename(storage)) + == storage); + nm_assert(c_list_contains(&storages->_storage_lst_head, &storage->_storage_lst)); + + uuid = nm_settings_storage_get_uuid_opt(storage); + + if (!uuid) { + nm_assert(c_list_is_empty(&storage->_storage_by_uuid_lst)); + } else { + nm_assert(!c_list_is_empty(&storage->_storage_by_uuid_lst)); + + sbuh = nm_sett_util_storages_lookup_by_uuid(storages, uuid); + + nm_assert(sbuh); + nm_assert( + c_list_contains(&sbuh->_storage_by_uuid_lst_head, &storage->_storage_by_uuid_lst)); + c_list_unlink(&storage->_storage_by_uuid_lst); + + if (c_list_is_empty(&sbuh->_storage_by_uuid_lst_head)) + g_hash_table_remove(storages->idx_by_uuid, sbuh); + } + + c_list_unlink(&storage->_storage_lst); + + g_hash_table_steal(storages->idx_by_filename, nm_settings_storage_get_filename(storage)); + + return storage; +} diff --git a/src/core/settings/nm-settings-utils.h b/src/core/settings/nm-settings-utils.h new file mode 100644 index 0000000..d3f50dd --- /dev/null +++ b/src/core/settings/nm-settings-utils.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_SETTINGS_UTILS_H__ +#define __NM_SETTINGS_UTILS_H__ + +#include "nm-settings-storage.h" + +/*****************************************************************************/ + +struct timespec; + +const struct timespec * +nm_sett_util_stat_mtime(const char *filename, gboolean do_lstat, struct timespec *out_val); + +/*****************************************************************************/ + +typedef struct { + const char *uuid; + + CList _storage_by_uuid_lst_head; + + char uuid_data[]; +} NMSettUtilStorageByUuidHead; + +typedef struct { + CList _storage_lst_head; + GHashTable *idx_by_filename; + GHashTable *idx_by_uuid; +} NMSettUtilStorages; + +void nm_sett_util_storage_by_uuid_head_destroy(NMSettUtilStorageByUuidHead *sbuh); + +#define NM_SETT_UTIL_STORAGES_INIT(storages, storage_destroy_fcn) \ + { \ + ._storage_lst_head = C_LIST_INIT(((storages)._storage_lst_head)), \ + .idx_by_filename = g_hash_table_new_full(nm_str_hash, \ + g_str_equal, \ + NULL, \ + (GDestroyNotify) storage_destroy_fcn), \ + .idx_by_uuid = \ + g_hash_table_new_full(nm_pstr_hash, \ + nm_pstr_equal, \ + NULL, \ + (GDestroyNotify) nm_sett_util_storage_by_uuid_head_destroy), \ + } + +void nm_sett_util_storages_clear(NMSettUtilStorages *storages); + +#define nm_auto_clear_sett_util_storages nm_auto(nm_sett_util_storages_clear) + +void nm_sett_util_storages_add_take(NMSettUtilStorages *storages, gpointer storage_take_p); + +gpointer nm_sett_util_storages_steal(NMSettUtilStorages *storages, gpointer storage_p); + +/*****************************************************************************/ + +static inline gpointer /* NMSettingsStorage * */ +nm_sett_util_storages_lookup_by_filename(NMSettUtilStorages *storages, const char *filename) +{ + nm_assert(filename); + + return g_hash_table_lookup(storages->idx_by_filename, filename); +} + +static inline NMSettUtilStorageByUuidHead * +nm_sett_util_storages_lookup_by_uuid(NMSettUtilStorages *storages, const char *uuid) +{ + nm_assert(uuid); + + return g_hash_table_lookup(storages->idx_by_uuid, &uuid); +} + +/*****************************************************************************/ + +typedef struct { + GHashTable *idx_by_filename; + const char *allowed_filename; +} NMSettUtilAllowFilenameData; + +#define NM_SETT_UTIL_ALLOW_FILENAME_DATA(_storages, _allowed_filename) \ + (&((NMSettUtilAllowFilenameData){ \ + .idx_by_filename = (_storages)->idx_by_filename, \ + .allowed_filename = (_allowed_filename), \ + })) + +gboolean nm_sett_util_allow_filename_cb(const char *filename, gpointer user_data); + +#endif /* __NM_SETTINGS_UTILS_H__ */ diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c new file mode 100644 index 0000000..eda3789 --- /dev/null +++ b/src/core/settings/nm-settings.c @@ -0,0 +1,4144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Søren Sandmann + * Dan Williams + * Tambet Ingo + * Copyright (C) 2007 - 2011 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-settings.h" + +#include +#include +#include +#include + +#if HAVE_SELINUX + #include +#endif + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-glib-aux/nm-keyfile-aux.h" +#include "nm-keyfile/nm-keyfile-internal.h" +#include "nm-dbus-interface.h" +#include "nm-connection.h" +#include "nm-setting-8021x.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-gsm.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-serial.h" +#include "nm-setting-vpn.h" +#include "nm-setting-wired.h" +#include "nm-setting-adsl.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-proxy.h" +#include "nm-setting-bond.h" +#include "nm-utils.h" +#include "nm-core-internal.h" + +#include "nm-std-aux/c-list-util.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-dbus-object.h" +#include "devices/nm-device-ethernet.h" +#include "nm-settings-connection.h" +#include "nm-settings-plugin.h" +#include "nm-dbus-manager.h" +#include "nm-auth-utils.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-session-monitor.h" +#include "plugins/keyfile/nms-keyfile-plugin.h" +#include "plugins/keyfile/nms-keyfile-storage.h" +#include "nm-agent-manager.h" +#include "nm-config.h" +#include "nm-manager.h" +#include "nm-audit-manager.h" +#include "NetworkManagerUtils.h" +#include "nm-dispatcher.h" +#include "nm-hostname-manager.h" + +/*****************************************************************************/ + +static NM_CACHED_QUARK_FCN("default-wired-connection", _default_wired_connection_quark); + +static NM_CACHED_QUARK_FCN("default-wired-connection-blocked", + _default_wired_connection_blocked_quark); + +/*****************************************************************************/ + +typedef struct _StorageData { + CList sd_lst; + NMSettingsStorage *storage; + NMConnection * connection; + bool prioritize : 1; +} StorageData; + +static StorageData * +_storage_data_new_stale(NMSettingsStorage *storage, NMConnection *connection) +{ + StorageData *sd; + + sd = g_slice_new(StorageData); + sd->storage = g_object_ref(storage); + sd->connection = nm_g_object_ref(connection); + sd->prioritize = FALSE; + return sd; +} + +static void +_storage_data_destroy(StorageData *sd) +{ + c_list_unlink_stale(&sd->sd_lst); + g_object_unref(sd->storage); + nm_g_object_unref(sd->connection); + g_slice_free(StorageData, sd); +} + +static StorageData * +_storage_data_find_in_lst(CList *head, NMSettingsStorage *storage) +{ + StorageData *sd; + + nm_assert(head); + nm_assert(NM_IS_SETTINGS_STORAGE(storage)); + + c_list_for_each_entry (sd, head, sd_lst) { + if (sd->storage == storage) + return sd; + } + return NULL; +} + +static void +nm_assert_storage_data_lst(CList *head) +{ +#if NM_MORE_ASSERTS > 5 + const char * uuid = NULL; + StorageData *sd; + CList * iter; + + nm_assert(head); + + if (c_list_is_empty(head)) + return; + + c_list_for_each_entry (sd, head, sd_lst) { + const char *u; + + nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage)); + nm_assert(!sd->connection || NM_IS_CONNECTION(sd->connection)); + u = nm_settings_storage_get_uuid(sd->storage); + if (!uuid) { + uuid = u; + nm_assert(nm_utils_is_uuid(uuid)); + } else + nm_assert(nm_streq0(uuid, u)); + } + + /* assert that all storages are unique. */ + c_list_for_each_entry (sd, head, sd_lst) { + for (iter = sd->sd_lst.next; iter != head; iter = iter->next) + nm_assert(c_list_entry(iter, StorageData, sd_lst)->storage != sd->storage); + } +#endif +} + +static gboolean +_storage_data_is_alive(StorageData *sd) +{ + /* If the storage tracks a connection, it is considered alive. + * + * Meta-data storages are special: they never track a connection. + * We need to check them specially to know when to drop them. */ + return sd->connection || nm_settings_storage_is_meta_data_alive(sd->storage); +} + +/*****************************************************************************/ + +typedef struct { + const char * uuid; + NMSettingsConnection *sett_conn; + NMSettingsStorage * storage; + CList sd_lst_head; + CList dirty_sd_lst_head; + + CList sce_dirty_lst; + + char _uuid_data[]; +} SettConnEntry; + +static SettConnEntry * +_sett_conn_entry_new(const char *uuid) +{ + SettConnEntry *sett_conn_entry; + gsize l_p_1; + + nm_assert(nm_utils_is_uuid(uuid)); + + l_p_1 = strlen(uuid) + 1; + + sett_conn_entry = g_malloc(sizeof(SettConnEntry) + l_p_1); + sett_conn_entry->uuid = sett_conn_entry->_uuid_data; + sett_conn_entry->sett_conn = NULL; + sett_conn_entry->storage = NULL; + c_list_init(&sett_conn_entry->sd_lst_head); + c_list_init(&sett_conn_entry->dirty_sd_lst_head); + c_list_init(&sett_conn_entry->sce_dirty_lst); + memcpy(sett_conn_entry->_uuid_data, uuid, l_p_1); + return sett_conn_entry; +} + +static void +_sett_conn_entry_free(SettConnEntry *sett_conn_entry) +{ + c_list_unlink_stale(&sett_conn_entry->sce_dirty_lst); + nm_c_list_free_all(&sett_conn_entry->sd_lst_head, StorageData, sd_lst, _storage_data_destroy); + nm_c_list_free_all(&sett_conn_entry->dirty_sd_lst_head, + StorageData, + sd_lst, + _storage_data_destroy); + nm_g_object_unref(sett_conn_entry->sett_conn); + nm_g_object_unref(sett_conn_entry->storage); + g_free(sett_conn_entry); +} + +static NMSettingsConnection * +_sett_conn_entry_get_conn(SettConnEntry *sett_conn_entry) +{ + return sett_conn_entry ? sett_conn_entry->sett_conn : NULL; +} + +/** + * _sett_conn_entry_storage_find_conflicting_storage: + * @sett_conn_entry: the list of settings-storages for the given UUID. + * @target_plugin: the settings plugin to check + * @storage_check_including: (allow-none): optionally compare against this storage. + * @plugins: the list of plugins sorted in descending priority. This determines + * the priority and whether a storage conflicts. + * + * If we were to add the a storage to @target_plugin, then this function checks + * whether there are already other storages that would hide the storage after we + * add it. Those conflicting/hiding storages are a problem, because they have higher + * priority, so we cannot add the storage. + * + * @storage_check_including is optional, and if given then it checks whether updating + * the profile in this storage would result in confict. This is the check before + * update-connection. If this parameter is omitted, then it's about what happens + * when adding a new profile (add-connection). + * + * Returns: the conflicting storage or %NULL if there is none. + */ +static NMSettingsStorage * +_sett_conn_entry_storage_find_conflicting_storage(SettConnEntry * sett_conn_entry, + NMSettingsPlugin * target_plugin, + NMSettingsStorage *storage_check_including, + const GSList * plugins) +{ + StorageData *sd; + + if (!sett_conn_entry) + return NULL; + + if (storage_check_including && nm_settings_storage_is_keyfile_run(storage_check_including)) { + /* the storage we check against is in-memory. It always has highest + * priority, so there can be no other conflicting storages. */ + return NULL; + } + + /* Finds the first (highest priority) storage that has a connection. + * Note that due to tombstones (that have a high priority), the connection + * may not actually be exposed. This is to find hidden/shadowed storages + * that provide a connection. */ + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage)); + + if (!sd->connection) { + /* We only consider storages with connection. In particular, + * tombstones are not relevant, because we can delete them to + * resolve the conflict. */ + continue; + } + + if (sd->storage == storage_check_including) { + /* ok, the storage is the one we are about to check. All other + * storages are lower priority, so there is no storage that hides + * our storage_check_including. */ + return NULL; + } + + if (nm_settings_plugin_cmp_by_priority(nm_settings_storage_get_plugin(sd->storage), + target_plugin, + plugins) + <= 0) { + /* the plugin of the existing storage is less important than @target_plugin. + * We have no conflicting/hiding storage. */ + return NULL; + } + + /* Found. If we would add the profile to @target_plugin, then it would be hidden + * by existing_storage. */ + return sd->storage; + } + + return NULL; +} + +static NMSettingsStorage * +_sett_conn_entry_find_shadowed_storage(SettConnEntry * sett_conn_entry, + const char * shadowed_storage_filename, + NMSettingsStorage *blacklisted_storage) +{ + StorageData *sd; + + if (!shadowed_storage_filename) + return NULL; + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage)); + + if (!sd->connection) + continue; + + if (blacklisted_storage == sd->storage) + continue; + + if (!nm_streq0(nm_settings_storage_get_filename_for_shadowed_storage(sd->storage), + shadowed_storage_filename)) + continue; + + return sd->storage; + } + + return NULL; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettings, + PROP_MANAGER, + PROP_UNMANAGED_SPECS, + PROP_HOSTNAME, + PROP_CAN_MODIFY, + PROP_CONNECTIONS, + PROP_STARTUP_COMPLETE, ); + +enum { + CONNECTION_ADDED, + CONNECTION_UPDATED, + CONNECTION_REMOVED, + CONNECTION_FLAGS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + NMAgentManager *agent_mgr; + + NMConfig *config; + + NMPlatform *platform; + + NMManager *manager; + + NMHostnameManager *hostname_manager; + + NMSessionMonitor *session_monitor; + + CList auth_lst_head; + + NMSKeyfilePlugin *keyfile_plugin; + + GSList *plugins; + + NMKeyFileDB *kf_db_timestamps; + NMKeyFileDB *kf_db_seen_bssids; + + GHashTable *sce_idx; + + CList sce_dirty_lst_head; + + CList connections_lst_head; + + NMSettingsConnection **connections_cached_list; + + GSList *unmanaged_specs; + GSList *unrecognized_specs; + + gint64 startup_complete_start_timestamp_msec; + GHashTable *startup_complete_idx; + CList startup_complete_scd_lst_head; + guint startup_complete_timeout_id; + + guint connections_len; + + guint connections_generation; + + guint kf_db_flush_idle_id_timestamps; + guint kf_db_flush_idle_id_seen_bssids; + + bool started : 1; + +} NMSettingsPrivate; + +struct _NMSettings { + NMDBusObject parent; + NMSettingsPrivate _priv; +}; + +struct _NMSettingsClass { + NMDBusObjectClass parent; +}; + +G_DEFINE_TYPE(NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT); + +#define NM_SETTINGS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMSettings, NM_IS_SETTINGS) + +/*****************************************************************************/ + +/* FIXME: a lot of logging lines are directly connected to a profile. Set the @con_uuid + * argument for structured logging. */ + +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "settings", __VA_ARGS__) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_settings; +static const GDBusSignalInfo signal_info_new_connection; +static const GDBusSignalInfo signal_info_connection_removed; + +static void default_wired_clear_tag(NMSettings * self, + NMDevice * device, + NMSettingsConnection *sett_conn, + gboolean add_to_no_auto_default); + +static void _clear_connections_cached_list(NMSettingsPrivate *priv); + +static void _startup_complete_check(NMSettings *self, gint64 now_msec); + +/*****************************************************************************/ + +static void +_emit_connection_added(NMSettings *self, NMSettingsConnection *sett_conn) +{ + g_signal_emit(self, signals[CONNECTION_ADDED], 0, sett_conn); +} + +static void +_emit_connection_updated(NMSettings * self, + NMSettingsConnection * sett_conn, + NMSettingsConnectionUpdateReason update_reason) +{ + _nm_settings_connection_emit_signal_updated_internal(sett_conn, update_reason); + g_signal_emit(self, signals[CONNECTION_UPDATED], 0, sett_conn, (guint) update_reason); +} + +static void +_emit_connection_removed(NMSettings *self, NMSettingsConnection *sett_conn) +{ + g_signal_emit(self, signals[CONNECTION_REMOVED], 0, sett_conn); +} + +static void +_emit_connection_flags_changed(NMSettings *self, NMSettingsConnection *sett_conn) +{ + g_signal_emit(self, signals[CONNECTION_FLAGS_CHANGED], 0, sett_conn); +} + +/*****************************************************************************/ + +typedef struct { + NMSettingsConnection *sett_conn; + CList scd_lst; + gint64 timeout_msec; +} StartupCompleteData; + +static void +_startup_complete_data_destroy(StartupCompleteData *scd) +{ + c_list_unlink_stale(&scd->scd_lst); + g_object_unref(scd->sett_conn); + nm_g_slice_free(scd); +} + +static gboolean +_startup_complete_check_is_ready(NMSettings * self, + NMSettingsConnection *sett_conn, + gboolean ignore_pending_actions) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMConnection * conn; + const CList * tmp_lst; + NMDevice * device; + + if (!priv->manager) + return TRUE; + + conn = nm_settings_connection_get_connection(sett_conn); + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (!nm_device_is_real(device)) + continue; + + if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE + || (!ignore_pending_actions && nm_device_has_pending_action(device))) { + /* while a device is not yet available and still has a pending + * action itself, it's not a suitable candidate. */ + continue; + } + + if (!nm_device_check_connection_compatible(device, conn, NULL)) + continue; + + return TRUE; + } + + return FALSE; +} + +static gboolean +_startup_complete_timeout_cb(gpointer user_data) +{ + NMSettings * self = user_data; + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + priv->startup_complete_timeout_id = 0; + _startup_complete_check(self, 0); + return G_SOURCE_REMOVE; +} + +static void +_startup_complete_check(NMSettings *self, gint64 now_msec) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + StartupCompleteData *scd_not_ready; + StartupCompleteData *scd_safe; + StartupCompleteData *scd; + gint64 elapsed_msec; + CList ready_lst; + + if (priv->startup_complete_start_timestamp_msec == 0) { + /* we are already done for good or didn't start yet. */ + return; + } + + if (!priv->started) { + /* before we are started there is no need to evaluate our list because + * we are anyway blocking startup-complete. */ + return; + } + + if (c_list_is_empty(&priv->startup_complete_scd_lst_head)) + goto ready; + + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + + elapsed_msec = now_msec - priv->startup_complete_start_timestamp_msec; + + /* We search the entire list whether they all timed-out or found a compatible device. + * We do that by appending elements that are ready to the end of the list, so that + * we hopefully keep testing the elements that are ready already (and can shortcut + * the test in common cases). + * + * Note that all profiles that we wait for need to have their dependencies satisfied + * at the same time. For example, consider connection A is waiting for device A' which is ready. + * Connection B waits for device B', which isn't ready. Once B'/B becomes ready, A/A' must + * still be ready. Otherwise, we would wait for A/A' to become ready again. */ + scd_not_ready = NULL; + c_list_init(&ready_lst); + c_list_for_each_entry_safe (scd, scd_safe, &priv->startup_complete_scd_lst_head, scd_lst) { + if (scd->timeout_msec <= elapsed_msec) + goto next_with_ready; + + if (_startup_complete_check_is_ready(self, scd->sett_conn, FALSE)) + goto next_with_ready; + + scd_not_ready = scd; + break; + +next_with_ready: + /* this element is ready. We move it to a temporary list, so that we + * can reorder the list (to next time evaluate the non-ready element first). */ + nm_c_list_move_tail(&ready_lst, &scd->scd_lst); + } + c_list_splice(&priv->startup_complete_scd_lst_head, &ready_lst); + + nm_clear_g_source(&priv->startup_complete_timeout_id); + + if (scd_not_ready) { + gint64 timeout_msec; + + timeout_msec = priv->startup_complete_start_timestamp_msec + scd_not_ready->timeout_msec + - nm_utils_get_monotonic_timestamp_msec(); + priv->startup_complete_timeout_id = + g_timeout_add(NM_CLAMP(0, timeout_msec, 60000), _startup_complete_timeout_cb, self); + _LOGT("startup-complete: wait for suitable device for connection \"%s\" (%s) which has " + "\"connection.wait-device-timeout\" set", + nm_settings_connection_get_id(scd_not_ready->sett_conn), + nm_settings_connection_get_uuid(scd_not_ready->sett_conn)); + return; + } + + if (_LOGW_ENABLED()) { + c_list_for_each_entry (scd, &priv->startup_complete_scd_lst_head, scd_lst) { + if (!_startup_complete_check_is_ready(self, scd->sett_conn, TRUE)) { + _LOGW("startup-complete: profile \"%s\" (%s) was waiting for non-existing device " + "(with timeout \"connection.wait-device-timeout=%" G_GINT64_FORMAT "\")", + nm_settings_connection_get_id(scd->sett_conn), + nm_settings_connection_get_uuid(scd->sett_conn), + scd->timeout_msec); + } + } + } + +ready: + nm_clear_pointer(&priv->startup_complete_idx, g_hash_table_destroy); + nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head)); + nm_assert(priv->started); + _LOGT("startup-complete: ready, no more profiles to wait for"); + priv->startup_complete_start_timestamp_msec = 0; + nm_assert(!priv->startup_complete_idx); + nm_assert(priv->startup_complete_timeout_id == 0); + _notify(self, PROP_STARTUP_COMPLETE); +} + +static void +_startup_complete_notify_connection(NMSettings * self, + NMSettingsConnection *sett_conn, + gboolean forget) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + StartupCompleteData *scd; + gint64 timeout_msec; + gint64 now_msec = 0; + NMSettingConnection *s_con; + gint32 v; + + nm_assert(priv->startup_complete_start_timestamp_msec != 0); + + if (forget) { + if (!priv->startup_complete_idx) + return; + if (!g_hash_table_remove(priv->startup_complete_idx, &sett_conn)) + return; + goto check; + } + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + v = nm_setting_connection_get_wait_device_timeout(s_con); + if (v > 0) + timeout_msec = v; + else + timeout_msec = 0; + + if (!priv->startup_complete_idx) { + nm_assert(!priv->started); + + if (timeout_msec == 0) + return; + + priv->startup_complete_idx = + g_hash_table_new_full(nm_pdirect_hash, + nm_pdirect_equal, + NULL, + (GDestroyNotify) _startup_complete_data_destroy); + scd = NULL; + } else + scd = g_hash_table_lookup(priv->startup_complete_idx, &sett_conn); + + if (!scd) { + if (timeout_msec == 0) + return; + scd = g_slice_new(StartupCompleteData); + *scd = (StartupCompleteData){ + .sett_conn = g_object_ref(sett_conn), + .timeout_msec = timeout_msec, + }; + g_hash_table_add(priv->startup_complete_idx, scd); + c_list_link_tail(&priv->startup_complete_scd_lst_head, &scd->scd_lst); + } else { + scd->timeout_msec = timeout_msec; + nm_c_list_move_front(&priv->startup_complete_scd_lst_head, &scd->scd_lst); + } + +check: + _startup_complete_check(self, now_msec); +} + +const char * +nm_settings_get_startup_complete_blocked_reason(NMSettings *self, gboolean force_reload) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + StartupCompleteData *scd; + const char * uuid; + + if (priv->startup_complete_start_timestamp_msec == 0) + goto out_done; + + if (force_reload) + _startup_complete_check(self, 0); + + if (c_list_is_empty(&priv->startup_complete_scd_lst_head)) + goto out_done; + + scd = c_list_first_entry(&priv->startup_complete_scd_lst_head, StartupCompleteData, scd_lst); + + nm_assert(scd); + nm_assert(NM_IS_SETTINGS_CONNECTION(scd->sett_conn)); + nm_assert(scd == nm_g_hash_table_lookup(priv->startup_complete_idx, &scd->sett_conn)); + + uuid = nm_settings_connection_get_uuid(scd->sett_conn); + if (uuid) + return uuid; + + g_return_val_if_reached("settings-starting"); + +out_done: + if (!priv->started) + return "settings-starting"; + return NULL; +} + +/*****************************************************************************/ + +const GSList * +nm_settings_get_unmanaged_specs(NMSettings *self) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + return priv->unmanaged_specs; +} + +static gboolean +update_specs(NMSettings *self, GSList **specs_ptr, GSList *(*get_specs_func)(NMSettingsPlugin *) ) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + GSList *new = NULL; + GSList *iter; + + for (iter = priv->plugins; iter; iter = g_slist_next(iter)) { + GSList *specs; + + specs = get_specs_func(iter->data); + while (specs) { + GSList *s = specs; + + specs = g_slist_remove_link(specs, s); + if (nm_utils_g_slist_find_str(new, s->data)) { + g_free(s->data); + g_slist_free_1(s); + continue; + } + s->next = new; + new = s; + } + } + + if (nm_utils_g_slist_strlist_cmp(new, *specs_ptr) == 0) { + g_slist_free_full(new, g_free); + return FALSE; + } + + g_slist_free_full(*specs_ptr, g_free); + *specs_ptr = new; + return TRUE; +} + +static void +_plugin_unmanaged_specs_changed(NMSettingsPlugin *config, gpointer user_data) +{ + NMSettings * self = NM_SETTINGS(user_data); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + if (update_specs(self, &priv->unmanaged_specs, nm_settings_plugin_get_unmanaged_specs)) + _notify(self, PROP_UNMANAGED_SPECS); +} + +static void +_plugin_unrecognized_specs_changed(NMSettingsPlugin *config, gpointer user_data) +{ + NMSettings * self = NM_SETTINGS(user_data); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + update_specs(self, &priv->unrecognized_specs, nm_settings_plugin_get_unrecognized_specs); +} + +/*****************************************************************************/ + +static void +connection_flags_changed(NMSettingsConnection *sett_conn, gpointer user_data) +{ + _emit_connection_flags_changed(NM_SETTINGS(user_data), sett_conn); +} + +/*****************************************************************************/ + +static SettConnEntry * +_sett_conn_entries_get(NMSettings *self, const char *uuid) +{ + nm_assert(uuid); + return g_hash_table_lookup(NM_SETTINGS_GET_PRIVATE(self)->sce_idx, &uuid); +} + +static SettConnEntry * +_sett_conn_entries_create_and_add(NMSettings *self, const char *uuid) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + SettConnEntry * sett_conn_entry; + + sett_conn_entry = _sett_conn_entry_new(uuid); + + if (!g_hash_table_add(priv->sce_idx, sett_conn_entry)) + nm_assert_not_reached(); + else if (g_hash_table_size(priv->sce_idx) == 1) + g_object_ref(self); + + return sett_conn_entry; +} + +static void +_sett_conn_entries_remove_and_destroy(NMSettings *self, SettConnEntry *sett_conn_entry) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + if (!g_hash_table_remove(priv->sce_idx, sett_conn_entry)) + nm_assert_not_reached(); + else if (g_hash_table_size(priv->sce_idx) == 0) + g_object_unref(self); +} + +/*****************************************************************************/ + +static int +_sett_conn_entry_sds_update_cmp_ascending(const StorageData *sd_a, + const StorageData *sd_b, + const GSList * plugins) +{ + const NMSettingsMetaData *meta_data_a; + const NMSettingsMetaData *meta_data_b; + bool is_keyfile_run_a; + bool is_keyfile_run_b; + + /* Sort storages by priority. More important storages are sorted + * higher (ascending sort). For example, if "sd_a" is more important than + * "sd_b" (sd_a>sd_b), a positive integer is returned. */ + + meta_data_a = nm_settings_storage_is_meta_data(sd_a->storage); + meta_data_b = nm_settings_storage_is_meta_data(sd_b->storage); + + /* runtime storages (both connections and meta-data) are always more + * important. */ + is_keyfile_run_a = nm_settings_storage_is_keyfile_run(sd_a->storage); + is_keyfile_run_b = nm_settings_storage_is_keyfile_run(sd_b->storage); + if (is_keyfile_run_a != is_keyfile_run_b) { + if (!meta_data_a && !meta_data_b) { + /* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in + * another storage. In this case we first honor whether one of the storages is explicitly + * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities + * contrary to what exists on-disk. + * + * This is done because when we use explicit D-Bus API (like update-connection) + * to update a profile, then we really want to prioritize the candidate + * despite having multiple other profiles. + * + * The example is if you have the same UUID twice in /run (one of them shadowed). + * If you move it to disk, then one of the profiles gets deleted and re-created + * on disk, but that on-disk profile must win against the remainging profile in + * /run. At least until the next reload/restart. */ + NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize); + } + + /* in-memory has higher priority. That is regardless of whether any of + * them is meta-data/tombstone or a profile. + * + * That works, because if any of them are tombstones/metadata, then we are in full + * control. There can by only one meta-data file, which is fully owned (and accordingly + * created/deleted) by NetworkManager. + * + * The only case where this might not be right is if we have profiles + * in /run that are shadowed. When we move such a profile to disk, then + * a conflict might arise. That is handled by "prioritize" above! */ + NM_CMP_DIRECT(is_keyfile_run_a, is_keyfile_run_b); + } + + /* After we determined that both profiles are either in /run or not, + * tombstones are always more important than non-tombstones. */ + NM_CMP_DIRECT(meta_data_a && meta_data_a->is_tombstone, + meta_data_b && meta_data_b->is_tombstone); + + /* Again, prioritized entries are sorted first (higher priority). */ + NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize); + + /* finally, compare the storages. This basically honors the timestamp + * of the profile and the relative order of the source plugin (via the + * @plugins list). */ + return nm_settings_storage_cmp(sd_a->storage, sd_b->storage, plugins); +} + +static int +_sett_conn_entry_sds_update_cmp(const CList *ls_a, const CList *ls_b, gconstpointer user_data) +{ + /* we sort highest priority storages first (descending). Hence, the order is swapped. */ + return _sett_conn_entry_sds_update_cmp_ascending(c_list_entry(ls_b, StorageData, sd_lst), + c_list_entry(ls_a, StorageData, sd_lst), + user_data); +} + +static void +_sett_conn_entry_sds_update(NMSettings *self, SettConnEntry *sett_conn_entry) +{ + StorageData *sd; + StorageData *sd_safe; + StorageData *sd_dirty; + gboolean reprioritize; + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + nm_assert_storage_data_lst(&sett_conn_entry->dirty_sd_lst_head); + + /* we merge the dirty list with the previous list. + * + * The idea is: + * + * - _connection_changed_track() appends events for the same UUID. Meaning: + * if the storage is new, it get appended (having lower priority). + * If it already exist and is an update for an event that we already + * track it, it keeps the list position in @dirty_sd_lst_head unchanged. + * + * - during merge, we want to preserve the previous order (with higher + * priority first in the list). + */ + + /* first go through all storages that we track and check whether they + * got an update...*/ + + reprioritize = FALSE; + c_list_for_each_entry (sd, &sett_conn_entry->dirty_sd_lst_head, sd_lst) { + if (sd->prioritize) { + reprioritize = TRUE; + break; + } + } + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + + c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) { + sd_dirty = _storage_data_find_in_lst(&sett_conn_entry->dirty_sd_lst_head, sd->storage); + if (!sd_dirty) { + /* there is no update for this storage (except maybe reprioritize). */ + if (reprioritize) + sd->prioritize = FALSE; + continue; + } + + nm_g_object_ref_set(&sd->connection, sd_dirty->connection); + sd->prioritize = sd_dirty->prioritize; + + _storage_data_destroy(sd_dirty); + } + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + + /* all remaining (so far unseen) dirty entries are appended to the merged list. + * (append means lower priority). */ + + c_list_splice(&sett_conn_entry->sd_lst_head, &sett_conn_entry->dirty_sd_lst_head); + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + + /* we drop the entries that are no longer "alive" (meaning, they no longer + * indicate a connection and are not a tombstone). */ + c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) { + if (!_storage_data_is_alive(sd)) + _storage_data_destroy(sd); + } + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + nm_assert(c_list_is_empty(&sett_conn_entry->dirty_sd_lst_head)); + + /* as last, we sort the entries. Note that this is a stable-sort... */ + c_list_sort(&sett_conn_entry->sd_lst_head, + _sett_conn_entry_sds_update_cmp, + NM_SETTINGS_GET_PRIVATE(self)->plugins); + + nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head); + nm_assert(c_list_is_empty(&sett_conn_entry->dirty_sd_lst_head)); +} + +/*****************************************************************************/ + +static NMConnection * +_connection_changed_normalize_connection(NMSettingsStorage *storage, + NMConnection * connection, + GVariant * secrets_to_merge, + NMConnection ** out_connection_cloned) +{ + gs_unref_object NMConnection *connection_cloned = NULL; + gs_free_error GError *error = NULL; + const char * uuid; + + nm_assert(NM_IS_SETTINGS_STORAGE(storage)); + nm_assert(out_connection_cloned && !*out_connection_cloned); + + if (!connection) + return NULL; + + nm_assert(NM_IS_CONNECTION(connection)); + + uuid = nm_settings_storage_get_uuid(storage); + + if (secrets_to_merge) { + connection_cloned = nm_simple_connection_new_clone(connection); + connection = connection_cloned; + nm_connection_update_secrets(connection, NULL, secrets_to_merge, NULL); + } + + if (!_nm_connection_ensure_normalized(connection, + !!connection_cloned, + uuid, + FALSE, + connection_cloned ? NULL : &connection_cloned, + &error)) { + /* this is most likely a bug in the plugin. It provided a connection that no longer verifies. + * Well, I guess it could also happen when we merge @secrets_to_merge above. In any case + * somewhere is a bug. */ + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: plugin provided an invalid connection: %s", + uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + error->message); + return NULL; + } + if (connection_cloned) + connection = connection_cloned; + + *out_connection_cloned = g_steal_pointer(&connection_cloned); + return connection; +} + +/*****************************************************************************/ + +static void +_connection_changed_update(NMSettings * self, + SettConnEntry * sett_conn_entry, + NMConnection * connection, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + NMSettingsConnectionUpdateReason update_reason) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_object NMConnection *connection_old = NULL; + NMSettingsStorage * storage = sett_conn_entry->storage; + gs_unref_object NMSettingsConnection *sett_conn = g_object_ref(sett_conn_entry->sett_conn); + const char * path; + gboolean is_new; + + nm_assert(!NM_FLAGS_ANY(sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + nm_assert(!NM_FLAGS_ANY(sett_flags, ~sett_mask)); + + is_new = c_list_is_empty(&sett_conn->_connections_lst); + + _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")", + nm_settings_storage_get_uuid(storage), + is_new ? "adding" : "updating", + nm_connection_get_id(connection), + NM_SETTINGS_STORAGE_PRINT_ARG(storage)); + + _nm_settings_connection_set_storage(sett_conn, storage); + + _nm_settings_connection_set_connection(sett_conn, connection, &connection_old, update_reason); + + if (is_new) { + _nm_settings_connection_register_kf_dbs(sett_conn, + priv->kf_db_timestamps, + priv->kf_db_seen_bssids); + + _clear_connections_cached_list(priv); + c_list_link_tail(&priv->connections_lst_head, &sett_conn->_connections_lst); + priv->connections_len++; + priv->connections_generation++; + + g_signal_connect(sett_conn, + NM_SETTINGS_CONNECTION_FLAGS_CHANGED, + G_CALLBACK(connection_flags_changed), + self); + } + + if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) { + nm_settings_connection_autoconnect_blocked_reason_set( + sett_conn, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + TRUE); + } + + sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE; + if (nm_settings_connection_check_visibility(sett_conn, priv->session_monitor)) + sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE; + else + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)); + + sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED; + if (nm_settings_storage_is_keyfile_run(storage)) + sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED; + else { + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED)); + + /* Profiles that don't reside in /run, are never nm-generated, + * volatile, and external. */ + sett_mask |= (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + } + + nm_settings_connection_set_flags_full(sett_conn, sett_mask, sett_flags); + + if (is_new) { + /* FIXME(shutdown): The NMSettings instance can't be disposed + * while there is any exported connection. Ideally we should + * unexport all connections on NMSettings' disposal, but for now + * leak @self on termination when there are connections alive. */ + path = nm_dbus_object_export(NM_DBUS_OBJECT(sett_conn)); + } else + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)); + + if (is_new || connection_old) { + nm_utils_log_connection_diff(nm_settings_connection_get_connection(sett_conn), + connection_old, + LOGL_DEBUG, + LOGD_CORE, + is_new ? "new connection" : "update connection", + "++ ", + path); + } + + if (is_new) { + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_settings, + &signal_info_new_connection, + "(o)", + path); + _notify(self, PROP_CONNECTIONS); + _emit_connection_added(self, sett_conn); + } else { + _nm_settings_connection_emit_dbus_signal_updated(sett_conn); + _emit_connection_updated(self, sett_conn, update_reason); + } + + if (priv->startup_complete_start_timestamp_msec != 0) { + if (nm_settings_has_connection(self, sett_conn)) + _startup_complete_notify_connection(self, sett_conn, FALSE); + } +} + +static void +_connection_changed_delete(NMSettings * self, + NMSettingsStorage * storage, + NMSettingsConnection *sett_conn, + gboolean allow_add_to_no_auto_default) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_object NMConnection *connection_for_agents = NULL; + NMDevice * device; + const char * uuid; + + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(c_list_contains(&priv->connections_lst_head, &sett_conn->_connections_lst)); + nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(sett_conn))); + + uuid = nm_settings_storage_get_uuid(storage); + + _LOGT("update[%s]: delete connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")", + uuid, + nm_settings_connection_get_id(sett_conn), + NM_SETTINGS_STORAGE_PRINT_ARG(storage)); + + /* When the default wired sett_conn is removed (either deleted or saved to + * a new persistent sett_conn by a plugin), write the MAC address of the + * wired device to the config file and don't create a new default wired + * sett_conn for that device again. + */ + device = nm_settings_connection_default_wired_get_device(sett_conn); + if (device) + default_wired_clear_tag(self, device, sett_conn, allow_add_to_no_auto_default); + + g_signal_handlers_disconnect_by_func(sett_conn, G_CALLBACK(connection_flags_changed), self); + + _clear_connections_cached_list(priv); + c_list_unlink(&sett_conn->_connections_lst); + priv->connections_len--; + priv->connections_generation++; + + /* Tell agents to remove secrets for this connection */ + connection_for_agents = + nm_simple_connection_new_clone(nm_settings_connection_get_connection(sett_conn)); + nm_connection_clear_secrets(connection_for_agents); + nm_agent_manager_delete_secrets(priv->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(self)), + connection_for_agents); + + _notify(self, PROP_CONNECTIONS); + _nm_settings_connection_emit_dbus_signal_removed(sett_conn); + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_settings, + &signal_info_connection_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn))); + + nm_dbus_object_unexport(NM_DBUS_OBJECT(sett_conn)); + + nm_settings_connection_set_flags(sett_conn, + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + FALSE); + + _emit_connection_removed(self, sett_conn); + + _nm_settings_connection_cleanup_after_remove(sett_conn); + + nm_key_file_db_remove_key(priv->kf_db_timestamps, uuid); + nm_key_file_db_remove_key(priv->kf_db_seen_bssids, uuid); + + if (priv->startup_complete_start_timestamp_msec != 0) + _startup_complete_notify_connection(self, sett_conn, TRUE); +} + +static void +_connection_changed_process_one(NMSettings * self, + SettConnEntry * sett_conn_entry, + gboolean allow_add_to_no_auto_default, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + gboolean override_sett_flags, + NMSettingsConnectionUpdateReason update_reason) +{ + StorageData *sd_best; + + c_list_unlink(&sett_conn_entry->sce_dirty_lst); + + _sett_conn_entry_sds_update(self, sett_conn_entry); + + sd_best = c_list_first_entry(&sett_conn_entry->sd_lst_head, StorageData, sd_lst); + + if (!sd_best || !sd_best->connection) { + gs_unref_object NMSettingsConnection *sett_conn = NULL; + gs_unref_object NMSettingsStorage *storage = NULL; + + if (!sett_conn_entry->sett_conn) { + if (!sd_best) { + _sett_conn_entries_remove_and_destroy(self, sett_conn_entry); + return; + } + + if (sett_conn_entry->storage != sd_best->storage) { + _LOGT("update[%s]: shadow UUID (" NM_SETTINGS_STORAGE_PRINT_FMT ")", + sett_conn_entry->uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(sd_best->storage)); + } + + nm_g_object_ref_set(&sett_conn_entry->storage, sd_best->storage); + return; + } + + sett_conn = g_steal_pointer(&sett_conn_entry->sett_conn); + if (sd_best) { + storage = g_object_ref(sd_best->storage); + nm_g_object_ref_set(&sett_conn_entry->storage, storage); + nm_assert_valid_settings_storage(NULL, storage); + } else { + storage = g_object_ref(sett_conn_entry->storage); + _sett_conn_entries_remove_and_destroy(self, sett_conn_entry); + } + + _connection_changed_delete(self, storage, sett_conn, allow_add_to_no_auto_default); + return; + } + + if (override_sett_flags) { + NMSettingsConnectionIntFlags s_f, s_m; + + nm_settings_storage_load_sett_flags(sd_best->storage, &s_f, &s_m); + + nm_assert(!NM_FLAGS_ANY(s_f, ~s_m)); + + sett_mask |= s_m; + sett_flags = (sett_flags & ~s_m) | (s_f & s_m); + } + + nm_g_object_ref_set(&sett_conn_entry->storage, sd_best->storage); + + if (!sett_conn_entry->sett_conn) + sett_conn_entry->sett_conn = nm_settings_connection_new(); + + _connection_changed_update(self, + sett_conn_entry, + sd_best->connection, + sett_flags, + sett_mask, + update_reason); +} + +static void +_connection_changed_process_all_dirty(NMSettings * self, + gboolean allow_add_to_no_auto_default, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + gboolean override_sett_flags, + NMSettingsConnectionUpdateReason update_reason) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + SettConnEntry * sett_conn_entry; + + while ((sett_conn_entry = + c_list_first_entry(&priv->sce_dirty_lst_head, SettConnEntry, sce_dirty_lst))) { + _connection_changed_process_one(self, + sett_conn_entry, + allow_add_to_no_auto_default, + sett_flags, + sett_mask, + override_sett_flags, + update_reason); + } +} + +static SettConnEntry * +_connection_changed_track(NMSettings * self, + NMSettingsStorage *storage, + NMConnection * connection, + gboolean prioritize) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + SettConnEntry * sett_conn_entry; + StorageData * sd; + const char * uuid; + + nm_assert_valid_settings_storage(NULL, storage); + + uuid = nm_settings_storage_get_uuid(storage); + + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(!connection + || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS)); + nm_assert(!connection || nm_streq0(uuid, nm_connection_get_uuid(connection))); + + nmtst_connection_assert_unchanging(connection); + + sett_conn_entry = + _sett_conn_entries_get(self, uuid) ?: _sett_conn_entries_create_and_add(self, uuid); + + if (_LOGT_ENABLED()) { + const char * filename; + const NMSettingsMetaData *meta_data; + const char * shadowed_storage; + gboolean shadowed_owned; + + filename = nm_settings_storage_get_filename(storage); + if (connection) { + shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned); + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: change event with connection \"%s\"%s%s%s%s%s%s", + sett_conn_entry->uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + nm_connection_get_id(connection), + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""), + NM_PRINT_FMT_QUOTED(shadowed_storage, + shadowed_owned ? " (owns \"" : " (shadows \"", + shadowed_storage, + "\")", + "")); + } else if ((meta_data = nm_settings_storage_is_meta_data(storage))) { + nm_assert(meta_data->is_tombstone); + shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned); + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: change event for %shiding profile%s%s%s%s%s%s", + sett_conn_entry->uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + nm_settings_storage_is_meta_data_alive(storage) ? "" : "dropping ", + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""), + NM_PRINT_FMT_QUOTED(shadowed_storage, + shadowed_owned ? " (owns \"" : " (shadows \"", + shadowed_storage, + "\")", + "")); + } else { + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: change event for dropping profile%s%s%s", + sett_conn_entry->uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", "")); + } + } + + /* see _sett_conn_entry_sds_update() for why we append the new events + * and leave existing ones at their position. */ + sd = _storage_data_find_in_lst(&sett_conn_entry->dirty_sd_lst_head, storage); + if (sd) + nm_g_object_ref_set(&sd->connection, connection); + else { + sd = _storage_data_new_stale(storage, connection); + c_list_link_tail(&sett_conn_entry->dirty_sd_lst_head, &sd->sd_lst); + } + + if (prioritize) { + StorageData *sd2; + + /* only one entry can be prioritized. */ + c_list_for_each_entry (sd2, &sett_conn_entry->dirty_sd_lst_head, sd_lst) + sd2->prioritize = FALSE; + sd->prioritize = TRUE; + } + + nm_c_list_move_tail(&priv->sce_dirty_lst_head, &sett_conn_entry->sce_dirty_lst); + + return sett_conn_entry; +} + +/*****************************************************************************/ + +static void +_plugin_connections_reload_cb(NMSettingsPlugin * plugin, + NMSettingsStorage *storage, + NMConnection * connection, + gpointer user_data) +{ + _connection_changed_track(user_data, storage, connection, FALSE); +} + +static void +_plugin_connections_reload(NMSettings *self) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + GSList * iter; + + for (iter = priv->plugins; iter; iter = iter->next) { + nm_settings_plugin_reload_connections(iter->data, _plugin_connections_reload_cb, self); + } + + _connection_changed_process_all_dirty( + self, + FALSE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + TRUE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS); + + for (iter = priv->plugins; iter; iter = iter->next) + nm_settings_plugin_load_connections_done(iter->data); +} + +/*****************************************************************************/ + +static gboolean +_add_connection_to_first_plugin(NMSettings * self, + SettConnEntry * sett_conn_entry, + NMConnection * new_connection, + gboolean in_memory, + NMSettingsConnectionIntFlags sett_flags, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage ** out_new_storage, + NMConnection ** out_new_connection, + GError ** error) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_free_error GError *first_error = NULL; + GSList * iter; + const char * uuid; + + uuid = nm_connection_get_uuid(new_connection); + + nm_assert(nm_utils_is_uuid(uuid)); + + for (iter = priv->plugins; iter; iter = iter->next) { + NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN(iter->data); + gs_unref_object NMSettingsStorage *storage = NULL; + gs_unref_object NMConnection *connection_to_add = NULL; + gs_unref_object NMConnection *connection_to_add_cloned = NULL; + NMConnection * connection_to_add_real = NULL; + gs_unref_variant GVariant *agent_owned_secrets = NULL; + gs_free_error GError *add_error = NULL; + gboolean success; + const char * filename; + + if (!in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage(sett_conn_entry, + plugin, + NULL, + priv->plugins); + if (conflicting_storage) { + /* we have a connection provided by a plugin with higher priority than the one + * we would want to add the connection. We cannot do that, because doing so + * would result in adding a connection that gets hidden by the existing profile. + * Also, since we test the plugins in order of priority, all following plugins + * are unsuitable. + * + * Multiple connection plugins are so cumbersome, especially if they are unable + * to add the connection. I suggest to disable all plugins except keyfile. */ + _LOGT("add-connection: failed to add %s/'%s': there is an existing " + "storage " NM_SETTINGS_STORAGE_PRINT_FMT " with higher priority", + nm_connection_get_uuid(new_connection), + nm_connection_get_id(new_connection), + NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage)); + nm_assert(first_error); + break; + } + } + + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { + success = nms_keyfile_plugin_add_connection( + priv->keyfile_plugin, + new_connection, + in_memory, + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + shadowed_storage, + shadowed_owned, + &storage, + &connection_to_add, + &add_error); + } else { + if (in_memory) + continue; + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)); + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)); + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)); + success = nm_settings_plugin_add_connection(plugin, + new_connection, + &storage, + &connection_to_add, + &add_error); + } + + if (!success) { + _LOGT("add-connection: failed to add %s/'%s': %s", + nm_connection_get_uuid(new_connection), + nm_connection_get_id(new_connection), + add_error->message); + if (!first_error) + first_error = g_steal_pointer(&add_error); + continue; + } + + if (!nm_streq0(nm_settings_storage_get_uuid(storage), uuid)) { + nm_assert_not_reached(); + continue; + } + + agent_owned_secrets = + nm_connection_to_dbus(new_connection, + NM_CONNECTION_SERIALIZE_ONLY_SECRETS + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED); + connection_to_add_real = + _connection_changed_normalize_connection(storage, + connection_to_add, + agent_owned_secrets, + &connection_to_add_cloned); + if (!connection_to_add_real) { + nm_assert_not_reached(); + continue; + } + + filename = nm_settings_storage_get_filename(storage); + _LOGT( + "add-connection: successfully added connection %s,'%s' (" NM_SETTINGS_STORAGE_PRINT_FMT + "%s%s%s", + nm_settings_storage_get_uuid(storage), + nm_connection_get_id(new_connection), + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + NM_PRINT_FMT_QUOTED(filename, ", \"", filename, "\")", ")")); + + *out_new_storage = g_steal_pointer(&storage); + *out_new_connection = + g_steal_pointer(&connection_to_add_cloned) ?: g_steal_pointer(&connection_to_add); + nm_assert(NM_IS_CONNECTION(*out_new_connection)); + return TRUE; + } + + nm_assert(first_error); + g_propagate_error(error, g_steal_pointer(&first_error)); + return FALSE; +} + +static gboolean +_update_connection_to_plugin(NMSettings * self, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsConnectionIntFlags sett_flags, + gboolean force_rename, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage ** out_new_storage, + NMConnection ** out_new_connection, + GError ** error) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMSettingsPlugin * plugin; + gboolean success; + + plugin = nm_settings_storage_get_plugin(storage); + + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { + success = nms_keyfile_plugin_update_connection( + priv->keyfile_plugin, + storage, + connection, + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + shadowed_storage, + shadowed_owned, + force_rename, + out_new_storage, + out_new_connection, + error); + } else { + nm_assert(!shadowed_storage); + nm_assert(!shadowed_owned); + success = nm_settings_plugin_update_connection(plugin, + storage, + connection, + out_new_storage, + out_new_connection, + error); + } + + return success; +} + +static void +_set_nmmeta_tombstone(NMSettings *self, + const char *uuid, + gboolean tombstone_on_disk, + gboolean tombstone_in_memory, + const char *shadowed_storage) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL; + gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL; + + if (tombstone_on_disk) { + if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + FALSE, + uuid, + FALSE, + TRUE, + NULL, + &tombstone_1_storage, + NULL)) + tombstone_in_memory = TRUE; + if (tombstone_1_storage) + _connection_changed_track(self, tombstone_1_storage, NULL, FALSE); + } + + if (tombstone_in_memory) { + if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + FALSE, + uuid, + TRUE, + TRUE, + shadowed_storage, + &tombstone_2_storage, + NULL)) { + nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + TRUE, + uuid, + TRUE, + TRUE, + shadowed_storage, + &tombstone_2_storage, + NULL); + } + _connection_changed_track(self, tombstone_2_storage, NULL, FALSE); + } +} + +/** + * nm_settings_add_connection: + * @self: the #NMSettings object + * @connection: the source connection to create a new #NMSettingsConnection from + * @persist_mode: the persist-mode for this profile. + * @add_reason: the add-reason flags. + * @sett_flags: the settings flags to set. + * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success. + * @error: on return, a location to store any errors that may occur + * + * Creates a new #NMSettingsConnection for the given source @connection. + * The returned object is owned by @self and the caller must reference + * the object to continue using it. + * + * Returns: TRUE on success. + */ +gboolean +nm_settings_add_connection(NMSettings * self, + NMConnection * connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionAddReason add_reason, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnection ** out_sett_conn, + GError ** error) +{ + NMSettingsPrivate *priv; + gs_unref_object NMConnection *connection_cloned_1 = NULL; + gs_unref_object NMConnection *new_connection = NULL; + gs_unref_object NMSettingsStorage *new_storage = NULL; + gs_unref_object NMSettingsStorage *shadowed_storage = NULL; + NMSettingsStorage * update_storage = NULL; + gs_free_error GError *local = NULL; + SettConnEntry * sett_conn_entry; + const char * uuid; + StorageData * sd; + gboolean new_in_memory; + gboolean success; + const char * shadowed_storage_filename = NULL; + + priv = NM_SETTINGS_GET_PRIVATE(self); + + nm_assert(NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); + + new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK); + + nm_assert(!NM_FLAGS_ANY(sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + + if (NM_FLAGS_ANY(sett_flags, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + nm_assert(new_in_memory); + new_in_memory = TRUE; + } + + nm_assert(!NM_FLAGS_ANY(add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)); + + NM_SET_OUT(out_sett_conn, NULL); + + uuid = nm_connection_get_uuid(connection); + + sett_conn_entry = _sett_conn_entries_get(self, uuid); + if (_sett_conn_entry_get_conn(sett_conn_entry)) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_UUID_EXISTS, + "a connection with this UUID already exists"); + return FALSE; + } + + if (!_nm_connection_ensure_normalized(connection, + FALSE, + NULL, + FALSE, + &connection_cloned_1, + &local)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "connection is invalid: %s", + local->message); + return FALSE; + } + if (connection_cloned_1) + connection = connection_cloned_1; + + if (sett_conn_entry) { + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + if (!nm_settings_storage_is_meta_data(sd->storage)) + continue; + shadowed_storage = nm_g_object_ref(_sett_conn_entry_find_shadowed_storage( + sett_conn_entry, + nm_settings_storage_get_shadowed_storage(sd->storage, NULL), + NULL)); + if (shadowed_storage) { + /* We have a nmmeta tombstone that indicates that a storage is shadowed. + * + * This happens when deleting a in-memory profile that was decoupled from + * the persistent storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to take over this storage again... */ + break; + } + } + } + + if (shadowed_storage && !new_in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage( + sett_conn_entry, + nm_settings_storage_get_plugin(shadowed_storage), + shadowed_storage, + priv->plugins); + if (conflicting_storage) { + /* We cannot add the profile as @shadowed_storage, because there is another, existing storage + * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles, + * but the circumstances where this happens are very exotic (you need at least one additional settings + * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it, + * and finally adding the conflicting storage outside of NM and restart/reload). */ + _LOGT("ignore shadowed storage " NM_SETTINGS_STORAGE_PRINT_FMT + " due to conflicting storage " NM_SETTINGS_STORAGE_PRINT_FMT, + NM_SETTINGS_STORAGE_PRINT_ARG(shadowed_storage), + NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage)); + } else + update_storage = shadowed_storage; + } + + shadowed_storage_filename = + (shadowed_storage && !update_storage) + ? nm_settings_storage_get_filename_for_shadowed_storage(shadowed_storage) + : NULL; + +again_add_connection: + + if (!update_storage) { + success = _add_connection_to_first_plugin(self, + sett_conn_entry, + connection, + new_in_memory, + sett_flags, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + } else { + success = _update_connection_to_plugin(self, + update_storage, + connection, + sett_flags, + FALSE, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + if (!success) { + if (!NMS_IS_KEYFILE_STORAGE(update_storage)) { + /* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings + * plugin may not support the new connection. So step back and retry adding + * the profile anew. */ + _LOGT("failure to add profile as existing storage \"%s\": %s", + nm_settings_storage_get_filename(update_storage), + local->message); + update_storage = NULL; + g_clear_object(&shadowed_storage); + shadowed_storage_filename = NULL; + g_clear_error(&local); + goto again_add_connection; + } + } + } + + if (!success) { + if (!update_storage) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure adding connection: %s", + local->message); + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure writing connection to existing storage \"%s\": %s", + nm_settings_storage_get_filename(update_storage), + local->message); + } + return FALSE; + } + + sett_conn_entry = _connection_changed_track(self, new_storage, new_connection, TRUE); + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + const NMSettingsMetaData *meta_data; + gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL; + gboolean in_memory; + gboolean simulate; + + meta_data = nm_settings_storage_is_meta_data_alive(sd->storage); + if (!meta_data || !meta_data->is_tombstone) + continue; + + if (nm_settings_storage_is_keyfile_run(sd->storage)) + in_memory = TRUE; + else { + if (nm_settings_storage_is_keyfile_run(new_storage)) { + /* Don't remove the file from /etc if we just wrote an in-memory connection */ + continue; + } + in_memory = FALSE; + } + + simulate = FALSE; +again_delete_tombstone: + if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + simulate, + uuid, + in_memory, + FALSE, + NULL, + &new_tombstone_storage, + NULL)) { + /* Ups, something went wrong. We really need to get rid of the tombstone. At least + * forget about it in-memory. Upong next restart/reload, this might be reverted + * however :( .*/ + if (!simulate) { + simulate = TRUE; + goto again_delete_tombstone; + } + } + if (new_tombstone_storage) + _connection_changed_track(self, new_tombstone_storage, NULL, FALSE); + } + + _connection_changed_process_all_dirty( + self, + FALSE, + sett_flags, + _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK, + FALSE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS + | (NM_FLAGS_HAS(add_reason, NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT) + ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT + : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE)); + + nm_assert(sett_conn_entry == _sett_conn_entries_get(self, sett_conn_entry->uuid)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn_entry->sett_conn)); + + NM_SET_OUT(out_sett_conn, _sett_conn_entry_get_conn(sett_conn_entry)); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_settings_update_connection(NMSettings * self, + NMSettingsConnection * sett_conn, + NMConnection * connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + NMSettingsConnectionUpdateReason update_reason, + const char * log_context_name, + GError ** error) +{ + gs_unref_object NMConnection *connection_cloned_1 = NULL; + gs_unref_object NMConnection *new_connection_cloned = NULL; + gs_unref_object NMConnection *new_connection = NULL; + NMConnection * new_connection_real; + gs_unref_object NMSettingsStorage *cur_storage = NULL; + gs_unref_object NMSettingsStorage *new_storage = NULL; + NMSettingsStorage * drop_storage = NULL; + SettConnEntry * sett_conn_entry; + gboolean cur_in_memory; + gboolean new_in_memory; + const char * uuid; + gboolean tombstone_in_memory = FALSE; + gboolean tombstone_on_disk = FALSE; + + g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), FALSE); + g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), FALSE); + + nm_assert(!NM_FLAGS_ANY(sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + nm_assert(!NM_FLAGS_ANY(sett_flags, ~sett_mask)); + nm_assert(NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); + + cur_storage = g_object_ref(nm_settings_connection_get_storage(sett_conn)); + + uuid = nm_settings_storage_get_uuid(cur_storage); + + nm_assert(NM_IS_SETTINGS_STORAGE(cur_storage)); + + sett_conn_entry = _sett_conn_entries_get(self, uuid); + + nm_assert(_sett_conn_entry_get_conn(sett_conn_entry) == sett_conn); + + if (connection) { + gs_free_error GError *local = NULL; + + if (!_nm_connection_ensure_normalized(connection, + FALSE, + uuid, + TRUE, + &connection_cloned_1, + &local)) { + _LOGT("update[%s]: %s: failed because profile is invalid: %s", + nm_settings_storage_get_uuid(cur_storage), + log_context_name, + local->message); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "connection is invalid: %s", + local->message); + return FALSE; + } + if (connection_cloned_1) + connection = connection_cloned_1; + } else + connection = nm_settings_connection_get_connection(sett_conn); + + cur_in_memory = nm_settings_storage_is_keyfile_run(cur_storage); + + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) { + persist_mode = cur_in_memory ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY + : NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + } + + if (NM_FLAGS_HAS(sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED) + && !NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + NMDevice *device; + + /* The connection has been changed by the user, it should no longer be + * considered a default wired connection, and should no longer affect + * the no-auto-default configuration option. + */ + device = nm_settings_connection_default_wired_get_device(sett_conn); + if (device) { + nm_assert(cur_in_memory); + nm_assert(NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)); + + default_wired_clear_tag(self, device, sett_conn, FALSE); + + if (NM_IN_SET(persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) { + /* making a default-wired-connection a regular connection implies persisting + * it to disk (unless specified differently). + * + * Actually, this line is probably unreached, because we should not use + * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated + * flag. */ + nm_assert_not_reached(); + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + } + } + } + + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST + && NM_FLAGS_ANY(sett_mask, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL) + && NM_FLAGS_ANY((sett_flags ^ nm_settings_connection_get_flags(sett_conn)) & sett_mask, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + /* we update the nm-generated/volatile setting of a profile (which is inherently + * in-memory. The caller did not request to persist this to disk, however we need + * to store the flags in run. */ + nm_assert(cur_in_memory); + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY; + } + + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK) + new_in_memory = FALSE; + else if (NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)) + new_in_memory = TRUE; + else { + nm_assert(persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST); + new_in_memory = cur_in_memory; + } + + if (!new_in_memory) { + /* Persistent connections cannot be volatile nor nm-generated. + * + * That is obviously true for volatile, as it is enforced by Update2() API. + * + * For nm-generated profiles also, because the nm-generated flag is only stored + * for in-memory profiles. If we would persist the profile to /etc it would loose + * the nm-generated flag after restart/reload, and that cannot be right. If a profile + * ends up on disk, the information who created it gets lost. */ + nm_assert(!NM_FLAGS_ANY(sett_flags, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)); + sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL; + sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + } + + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) { + new_storage = g_object_ref(cur_storage); + new_connection_real = connection; + _LOGT("update[%s]: %s: update profile \"%s\" (not persisted)", + nm_settings_storage_get_uuid(cur_storage), + log_context_name, + nm_connection_get_id(connection)); + } else { + NMSettingsStorage *shadowed_storage; + const char * cur_shadowed_storage_filename; + const char * new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; + gboolean new_shadowed_owned = FALSE; + NMSettingsStorage *update_storage = NULL; + gs_free_error GError *local = NULL; + gboolean success; + + cur_shadowed_storage_filename = + nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry, + cur_shadowed_storage_filename, + cur_storage); + if (!shadowed_storage) { + cur_shadowed_storage_filename = NULL; + cur_shadowed_owned = FALSE; + } + + if (new_in_memory && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + if (cur_in_memory) { + drop_storage = shadowed_storage; + update_storage = cur_storage; + } else + drop_storage = cur_storage; + } else if (!new_in_memory && cur_in_memory && shadowed_storage) { + drop_storage = cur_storage; + update_storage = shadowed_storage; + } else if (new_in_memory != cur_in_memory) { + if (!new_in_memory) + drop_storage = cur_storage; + else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) + drop_storage = cur_storage; + else { + nm_assert(NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)); + } + } else if (nm_settings_storage_is_keyfile_lib(cur_storage)) { + /* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it + * from /usr/lib to /etc. */ + } else + update_storage = cur_storage; + + if (new_in_memory) { + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + /* pass */ + } else if (!cur_in_memory) { + new_shadowed_storage_filename = + nm_settings_storage_get_filename_for_shadowed_storage(cur_storage); + if (new_shadowed_storage_filename + && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED) + new_shadowed_owned = TRUE; + } else { + new_shadowed_storage_filename = cur_shadowed_storage_filename; + if (new_shadowed_storage_filename + && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY) + new_shadowed_owned = TRUE; + } + } + + if (!update_storage) { + success = _add_connection_to_first_plugin(self, + sett_conn_entry, + connection, + new_in_memory, + sett_flags, + new_shadowed_storage_filename, + new_shadowed_owned, + &new_storage, + &new_connection, + &local); + } else { + success = _update_connection_to_plugin(self, + update_storage, + connection, + sett_flags, + update_reason, + new_shadowed_storage_filename, + new_shadowed_owned, + &new_storage, + &new_connection, + &local); + } + if (!success) { + gboolean ignore_failure; + + ignore_failure = + NM_FLAGS_ANY(update_reason, + NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE); + + _LOGT("update[%s]: %s: %sfailure to %s connection \"%s\" on storage: %s", + nm_settings_storage_get_uuid(cur_storage), + log_context_name, + ignore_failure ? "ignore " : "", + update_storage ? "update" : "write", + nm_connection_get_id(connection), + local->message); + if (!ignore_failure) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "failed to %s connection: %s", + update_storage ? "update" : "write", + local->message); + return FALSE; + } + + new_storage = g_object_ref(cur_storage); + new_connection_real = connection; + } else { + gs_unref_variant GVariant *agent_owned_secrets = NULL; + + _LOGT("update[%s]: %s: %s profile \"%s\"", + nm_settings_storage_get_uuid(cur_storage), + log_context_name, + update_storage ? "update" : "write", + nm_connection_get_id(connection)); + + nm_assert_valid_settings_storage(NULL, new_storage); + nm_assert(NM_IS_CONNECTION(new_connection)); + nm_assert(nm_streq(uuid, nm_settings_storage_get_uuid(new_storage))); + + agent_owned_secrets = + nm_connection_to_dbus(connection, + NM_CONNECTION_SERIALIZE_ONLY_SECRETS + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED); + new_connection_real = _connection_changed_normalize_connection(new_storage, + new_connection, + agent_owned_secrets, + &new_connection_cloned); + if (!new_connection_real) { + nm_assert_not_reached(); + new_connection_real = new_connection; + } + } + } + + nm_assert(NM_IS_SETTINGS_STORAGE(new_storage)); + nm_assert(NM_IS_CONNECTION(new_connection_real)); + + _connection_changed_track(self, new_storage, new_connection_real, TRUE); + + if (drop_storage && drop_storage != new_storage) { + gs_free_error GError *local = NULL; + + if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(drop_storage), + drop_storage, + &local)) { + const char *filename; + + filename = nm_settings_storage_get_filename(drop_storage); + _LOGT("update[%s]: failed to delete moved storage " NM_SETTINGS_STORAGE_PRINT_FMT + "%s%s%s: %s", + nm_settings_storage_get_uuid(drop_storage), + NM_SETTINGS_STORAGE_PRINT_ARG(drop_storage), + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""), + local->message); + /* there is no aborting back form this. We must get rid of the connection and + * cannot do better than log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run(cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + } else + _connection_changed_track(self, drop_storage, NULL, FALSE); + } + + _set_nmmeta_tombstone(self, uuid, tombstone_on_disk, tombstone_in_memory, NULL); + + _connection_changed_process_all_dirty(self, FALSE, sett_flags, sett_mask, FALSE, update_reason); + + return TRUE; +} + +void +nm_settings_delete_connection(NMSettings * self, + NMSettingsConnection *sett_conn, + gboolean allow_add_to_no_auto_default) +{ + NMSettingsStorage *cur_storage; + NMSettingsStorage *shadowed_storage; + NMSettingsStorage *shadowed_storage_unowned = NULL; + NMSettingsStorage *drop_storages[2] = {}; + gs_free_error GError *local = NULL; + SettConnEntry * sett_conn_entry; + const char * cur_shadowed_storage_filename; + const char * new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; + const char * uuid; + gboolean tombstone_in_memory = FALSE; + gboolean tombstone_on_disk = FALSE; + int i; + + g_return_if_fail(NM_IS_SETTINGS(self)); + g_return_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn)); + g_return_if_fail(nm_settings_has_connection(self, sett_conn)); + + cur_storage = nm_settings_connection_get_storage(sett_conn); + + nm_assert(NM_IS_SETTINGS_STORAGE(cur_storage)); + + uuid = nm_settings_storage_get_uuid(cur_storage); + nm_assert(nm_utils_is_uuid(uuid)); + + sett_conn_entry = _sett_conn_entries_get(self, uuid); + + g_return_if_fail(sett_conn_entry); + nm_assert(sett_conn_entry->sett_conn == sett_conn); + g_return_if_fail(sett_conn_entry->storage == cur_storage); + + if (NMS_IS_KEYFILE_STORAGE(cur_storage)) { + NMSKeyfileStorage *s = NMS_KEYFILE_STORAGE(cur_storage); + + if (NM_IN_SET(s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)) + drop_storages[0] = cur_storage; + else + tombstone_on_disk = TRUE; + } else + drop_storages[0] = cur_storage; + + cur_shadowed_storage_filename = + nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry, + cur_shadowed_storage_filename, + cur_storage); + if (shadowed_storage) { + if (!cur_shadowed_owned) + shadowed_storage_unowned = g_steal_pointer(&shadowed_storage); + } + drop_storages[1] = shadowed_storage; + + for (i = 0; i < (int) G_N_ELEMENTS(drop_storages); i++) { + NMSettingsStorage *storage; + StorageData * sd; + + storage = drop_storages[i]; + if (!storage) + continue; + + if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(storage), + storage, + &local)) { + _LOGT("delete-connection: failed to delete storage " NM_SETTINGS_STORAGE_PRINT_FMT + ": %s", + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + local->message); + g_clear_error(&local); + /* there is no aborting back form this. We must get rid of the connection and + * cannot do better than log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run(cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + sett_conn_entry = _sett_conn_entries_get(self, uuid); + } else + sett_conn_entry = _connection_changed_track(self, storage, NULL, FALSE); + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + if (NM_IN_SET(sd->storage, drop_storages[0], drop_storages[1])) + continue; + if (!_storage_data_is_alive(sd)) + continue; + if (nm_settings_storage_is_meta_data(sd->storage)) + continue; + + if (sd->storage == shadowed_storage_unowned) { + /* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to write a tombstone and remember the shadowed-storage. */ + tombstone_in_memory = TRUE; + new_shadowed_storage_filename = + nm_settings_storage_get_filename(shadowed_storage_unowned); + continue; + } + + /* we have still conflicting storages. We need to hide them with tombstones. */ + if (nm_settings_storage_is_keyfile_run(sd->storage)) { + tombstone_in_memory = TRUE; + continue; + } + tombstone_on_disk = TRUE; + } + } + + _set_nmmeta_tombstone(self, + uuid, + tombstone_on_disk, + tombstone_in_memory, + new_shadowed_storage_filename); + + _connection_changed_process_all_dirty(self, + allow_add_to_no_auto_default, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + FALSE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE); +} + +/*****************************************************************************/ + +static void +send_agent_owned_secrets(NMSettings *self, NMSettingsConnection *sett_conn, NMAuthSubject *subject) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_object NMConnection *for_agent = NULL; + + /* Dupe the connection so we can clear out non-agent-owned secrets, + * as agent-owned secrets are the only ones we send back to be saved. + * Only send secrets to agents of the same UID that called update too. + */ + for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(sett_conn)); + _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED); + nm_agent_manager_save_secrets(priv->agent_mgr, + nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)), + for_agent, + subject); +} + +static void +pk_add_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMSettings * self = NM_SETTINGS(user_data); + NMAuthCallResult result; + gs_free_error GError *error = NULL; + NMConnection * connection = NULL; + gs_unref_object NMSettingsConnection *added = NULL; + NMSettingsAddCallback callback; + gpointer callback_data; + NMAuthSubject * subject; + const char * perm; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + perm = nm_auth_chain_get_data(chain, "perm"); + nm_assert(perm); + + result = nm_auth_chain_get_result(chain, perm); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + } else { + /* Authorized */ + connection = nm_auth_chain_get_data(chain, "connection"); + nm_assert(NM_IS_CONNECTION(connection)); + + nm_settings_add_connection(self, + connection, + GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "persist-mode")), + GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "add-reason")), + GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "sett-flags")), + &added, + &error); + + /* The callback may remove the connection from the settings manager (e.g. + * because it's found to be incompatible with the device on AddAndActivate). + * But we need to keep it alive for a bit longer, precisely to check wehther + * it's still known to the setting manager. */ + nm_g_object_ref(added); + } + + callback = nm_auth_chain_get_data(chain, "callback"); + callback_data = nm_auth_chain_get_data(chain, "callback-data"); + subject = nm_auth_chain_get_data(chain, "subject"); + + callback(self, added, error, context, subject, callback_data); + + /* Send agent-owned secrets to the agents */ + if (added && nm_settings_has_connection(self, added)) + send_agent_owned_secrets(self, added, subject); +} + +void +nm_settings_add_connection_dbus(NMSettings * self, + NMConnection * connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionAddReason add_reason, + NMSettingsConnectionIntFlags sett_flags, + NMAuthSubject * subject, + GDBusMethodInvocation * context, + NMSettingsAddCallback callback, + gpointer user_data) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + NMSettingConnection *s_con; + NMAuthChain * chain; + GError * error = NULL, *tmp_error = NULL; + const char * perm; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(NM_IS_AUTH_SUBJECT(subject)); + g_return_if_fail(G_IS_DBUS_METHOD_INVOCATION(context)); + + nm_assert(!NM_FLAGS_ANY(sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + + /* Connection must be valid, of course */ + if (_nm_connection_verify(connection, &tmp_error) != NM_SETTING_VERIFY_SUCCESS) { + error = g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "The connection was invalid: %s", + tmp_error->message); + g_error_free(tmp_error); + goto done; + } + + if (!nm_auth_is_subject_in_acl_set_error(connection, + subject, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + &error)) + goto done; + + /* If the caller is the only user in the connection's permissions, then + * we use the 'modify.own' permission instead of 'modify.system'. If the + * request affects more than just the caller, require 'modify.system'. + */ + s_con = nm_connection_get_setting_connection(connection); + nm_assert(s_con); + if (nm_setting_connection_get_num_permissions(s_con) == 1) + perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN; + else + perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM; + + chain = nm_auth_chain_new_subject(subject, context, pk_add_cb, self); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "perm", (gpointer) perm, NULL); + nm_auth_chain_set_data(chain, "connection", g_object_ref(connection), g_object_unref); + nm_auth_chain_set_data(chain, "callback", callback, NULL); + nm_auth_chain_set_data(chain, "callback-data", user_data, NULL); + nm_auth_chain_set_data(chain, "subject", g_object_ref(subject), g_object_unref); + nm_auth_chain_set_data(chain, "persist-mode", GUINT_TO_POINTER(persist_mode), NULL); + nm_auth_chain_set_data(chain, "add-reason", GUINT_TO_POINTER(add_reason), NULL); + nm_auth_chain_set_data(chain, "sett-flags", GUINT_TO_POINTER(sett_flags), NULL); + nm_auth_chain_add_call_unsafe(chain, perm, TRUE); + return; + +done: + nm_assert(error); + callback(self, NULL, error, context, subject, user_data); + g_error_free(error); +} + +static void +settings_add_connection_add_cb(NMSettings * self, + NMSettingsConnection * connection, + GError * error, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + gpointer user_data) +{ + gboolean is_add_connection_2 = GPOINTER_TO_INT(user_data); + + if (error) { + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD, + NULL, + FALSE, + NULL, + subject, + error->message); + return; + } + + if (is_add_connection_2) { + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_dbus_method_invocation_return_value( + context, + g_variant_new("(oa{sv})", + nm_dbus_object_get_path(NM_DBUS_OBJECT(connection)), + &builder)); + } else { + g_dbus_method_invocation_return_value( + context, + g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(connection)))); + } + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL, subject, NULL); +} + +static void +settings_add_connection_helper(NMSettings * self, + GDBusMethodInvocation * context, + gboolean is_add_connection_2, + GVariant * settings, + NMSettingsAddConnection2Flags flags) +{ + gs_unref_object NMConnection *connection = NULL; + GError * error = NULL; + gs_unref_object NMAuthSubject * subject = NULL; + NMSettingsConnectionPersistMode persist_mode; + + connection = _nm_simple_connection_new_from_dbus(settings, + NM_SETTING_PARSE_FLAGS_STRICT + | NM_SETTING_PARSE_FLAGS_NORMALIZE, + &error); + + if (!connection || !nm_connection_verify_secrets(connection, &error)) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_dbus_method_invocation_return_error_literal(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + return; + } + + if (NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + else { + nm_assert(NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)); + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + } + + nm_settings_add_connection_dbus( + self, + connection, + persist_mode, + NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT) + ? NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT + : NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + subject, + context, + settings_add_connection_add_cb, + GINT_TO_POINTER(!!is_add_connection_2)); +} + +static void +impl_settings_add_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + gs_unref_variant GVariant *settings = NULL; + + g_variant_get(parameters, "(@a{sa{sv}})", &settings); + settings_add_connection_helper(self, + invocation, + FALSE, + settings, + NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK); +} + +static void +impl_settings_add_connection_unsaved(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + gs_unref_variant GVariant *settings = NULL; + + g_variant_get(parameters, "(@a{sa{sv}})", &settings); + settings_add_connection_helper(self, + invocation, + FALSE, + settings, + NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY); +} + +static void +impl_settings_add_connection2(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + gs_unref_variant GVariant *settings = NULL; + gs_unref_variant GVariant * args = NULL; + NMSettingsAddConnection2Flags flags; + const char * args_name; + GVariantIter iter; + guint32 flags_u; + + g_variant_get(parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args); + + if (NM_FLAGS_ANY(flags_u, + ~((guint32)(NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK + | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY + | NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)))) { + g_dbus_method_invocation_take_error(invocation, + g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Unknown flags")); + return; + } + + flags = flags_u; + + if (!NM_FLAGS_ANY(flags, + NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK + | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) { + g_dbus_method_invocation_take_error( + invocation, + g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Requires either to-disk (0x1) or in-memory (0x2) flags")); + return; + } + + if (NM_FLAGS_ALL(flags, + NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK + | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) { + g_dbus_method_invocation_take_error( + invocation, + g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Cannot set to-disk (0x1) and in-memory (0x2) flags together")); + return; + } + + nm_assert(g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}"))); + + g_variant_iter_init(&iter, args); + while (g_variant_iter_next(&iter, "{&sv}", &args_name, NULL)) { + g_dbus_method_invocation_take_error(invocation, + g_error_new(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_ARGUMENTS, + "Unsupported argument '%s'", + args_name)); + return; + } + + settings_add_connection_helper(self, invocation, TRUE, settings, flags); +} + +/*****************************************************************************/ + +static void +impl_settings_load_connections(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *failures = NULL; + gs_free const char ** filenames = NULL; + gs_free char * op_result_str = NULL; + + g_variant_get(parameters, "(^a&s)", &filenames); + + /* The permission is already enforced by the D-Bus daemon, but we ensure + * that the caller is still alive so that clients are forced to wait and + * we'll be able to switch to polkit without breaking behavior. + */ + if (!nm_dbus_manager_ensure_uid(nm_dbus_object_get_manager(obj), + invocation, + G_MAXULONG, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED)) + return; + + if (filenames && filenames[0]) { + NMSettingsPluginConnectionLoadEntry *entries; + gsize n_entries; + gsize i; + GSList * iter; + + entries = nm_settings_plugin_create_connection_load_entries(filenames, &n_entries); + + for (iter = priv->plugins; iter; iter = iter->next) { + NMSettingsPlugin *plugin = iter->data; + + nm_settings_plugin_load_connections(plugin, + entries, + n_entries, + _plugin_connections_reload_cb, + self); + } + + for (i = 0; i < n_entries; i++) { + NMSettingsPluginConnectionLoadEntry *entry = &entries[i]; + + if (!entry->handled) { + _LOGW("load: no settings plugin could load \"%s\"", entry->filename); + nm_assert(!entry->error); + } else if (entry->error) { + _LOGW("load: failure to load \"%s\": %s", entry->filename, entry->error->message); + g_clear_error(&entry->error); + } else + continue; + + if (!failures) + failures = g_ptr_array_new(); + g_ptr_array_add(failures, (char *) entry->filename); + } + + nm_clear_g_free(&entries); + + _connection_changed_process_all_dirty( + self, + TRUE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + TRUE, + NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS + | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS); + + for (iter = priv->plugins; iter; iter = iter->next) + nm_settings_plugin_load_connections_done(iter->data); + } + + if (failures) + g_ptr_array_add(failures, NULL); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONNS_LOAD, + NULL, + !failures, + (op_result_str = g_strjoinv(",", (char **) filenames)), + invocation, + NULL); + + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(b^as)", + (gboolean)(!failures), + failures + ? (const char **) failures->pdata + : NM_PTRARRAY_EMPTY(const char *))); +} + +static void +impl_settings_reload_connections(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings *self = NM_SETTINGS(obj); + + /* The permission is already enforced by the D-Bus daemon, but we ensure + * that the caller is still alive so that clients are forced to wait and + * we'll be able to switch to polkit without breaking behavior. + */ + if (!nm_dbus_manager_ensure_uid(nm_dbus_object_get_manager(obj), + invocation, + G_MAXULONG, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED)) + return; + + _plugin_connections_reload(self); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONNS_RELOAD, NULL, TRUE, NULL, invocation, NULL); + + /* We MUST return %TRUE here, otherwise older libnm versions might misbehave. */ + g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", TRUE)); +} + +/*****************************************************************************/ + +static void +_clear_connections_cached_list(NMSettingsPrivate *priv) +{ + if (!priv->connections_cached_list) + return; + + nm_assert(priv->connections_len == NM_PTRARRAY_LEN(priv->connections_cached_list)); + +#if NM_MORE_ASSERTS + /* set the pointer to a bogus value. This makes it more apparent + * if somebody has a reference to the cached list and still uses + * it. That is a bug, this code just tries to make it blow up + * more eagerly. */ + memset(priv->connections_cached_list, + 0x43, + sizeof(NMSettingsConnection *) * (priv->connections_len + 1)); +#endif + + nm_clear_g_free(&priv->connections_cached_list); +} + +static void +impl_settings_list_connections(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + gs_free const char **strv = NULL; + + strv = + nm_dbus_utils_get_paths_for_clist(&priv->connections_lst_head, + priv->connections_len, + G_STRUCT_OFFSET(NMSettingsConnection, _connections_lst), + TRUE); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", strv)); +} + +NMSettingsConnection * +nm_settings_get_connection_by_uuid(NMSettings *self, const char *uuid) +{ + g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); + g_return_val_if_fail(uuid != NULL, NULL); + + return _sett_conn_entry_get_conn(_sett_conn_entries_get(self, uuid)); +} + +const char * +nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid) +{ + NMSettingsConnection *sett_conn; + + sett_conn = nm_settings_get_connection_by_uuid(self, uuid); + + if (!sett_conn) + return NULL; + + return nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)); +} + +static void +impl_settings_get_connection_by_uuid(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + NMSettingsConnection *sett_conn; + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + const char * uuid; + + g_variant_get(parameters, "(&s)", &uuid); + + sett_conn = nm_settings_get_connection_by_uuid(self, uuid); + if (!sett_conn) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "No connection with the UUID was found."); + goto error; + } + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto error; + } + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn), + subject, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + &error)) + goto error; + + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)))); + return; + +error: + g_dbus_method_invocation_take_error(invocation, error); +} + +/** + * nm_settings_get_connections: + * @self: the #NMSettings + * @out_len: (out) (allow-none): returns the number of returned + * connections. + * + * Returns: (transfer none): a list of NMSettingsConnections. The list is + * unsorted and NULL terminated. The result is never %NULL, in case of no + * connections, it returns an empty list. + * The returned list is cached internally, only valid until the next + * NMSettings operation. + */ +NMSettingsConnection *const * +nm_settings_get_connections(NMSettings *self, guint *out_len) +{ + NMSettingsPrivate * priv; + NMSettingsConnection **v; + NMSettingsConnection * con; + guint i; + + g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); + + priv = NM_SETTINGS_GET_PRIVATE(self); + + nm_assert(priv->connections_len == c_list_length(&priv->connections_lst_head)); + + if (G_UNLIKELY(!priv->connections_cached_list)) { + v = g_new(NMSettingsConnection *, priv->connections_len + 1); + + i = 0; + c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) { + nm_assert(i < priv->connections_len); + v[i++] = con; + } + nm_assert(i == priv->connections_len); + v[i] = NULL; + + priv->connections_cached_list = v; + } + + NM_SET_OUT(out_len, priv->connections_len); + return priv->connections_cached_list; +} + +/** + * nm_settings_get_connections_clone: + * @self: the #NMSetting + * @out_len: (allow-none): optional output argument + * @func: caller-supplied function for filtering connections + * @func_data: caller-supplied data passed to @func + * @sort_compare_func: (allow-none): optional function pointer for + * sorting the returned list. + * @sort_data: user data for @sort_compare_func. + * + * Returns: (transfer container) (element-type NMSettingsConnection): + * an NULL terminated array of #NMSettingsConnection objects that were + * filtered by @func (or all connections if no filter was specified). + * The order is arbitrary. + * Caller is responsible for freeing the returned array with free(), + * the contained values do not need to be unrefed. + */ +NMSettingsConnection ** +nm_settings_get_connections_clone(NMSettings * self, + guint * out_len, + NMSettingsConnectionFilterFunc func, + gpointer func_data, + GCompareDataFunc sort_compare_func, + gpointer sort_data) +{ + NMSettingsConnection *const *list_cached; + NMSettingsConnection ** list; + guint len, i, j; + + g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); + + list_cached = nm_settings_get_connections(self, &len); + +#if NM_MORE_ASSERTS + nm_assert(list_cached); + for (i = 0; i < len; i++) + nm_assert(NM_IS_SETTINGS_CONNECTION(list_cached[i])); + nm_assert(!list_cached[i]); +#endif + + list = g_new(NMSettingsConnection *, ((gsize) len + 1)); + if (func) { + for (i = 0, j = 0; i < len; i++) { + if (func(self, list_cached[i], func_data)) + list[j++] = list_cached[i]; + } + list[j] = NULL; + len = j; + } else + memcpy(list, list_cached, sizeof(list[0]) * ((gsize) len + 1)); + + if (len > 1 && sort_compare_func) { + g_qsort_with_data(list, len, sizeof(NMSettingsConnection *), sort_compare_func, sort_data); + } + NM_SET_OUT(out_len, len); + return list; +} + +NMSettingsConnection * +nm_settings_get_connection_by_path(NMSettings *self, const char *path) +{ + NMSettingsPrivate * priv; + NMSettingsConnection *connection; + + g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); + g_return_val_if_fail(path, NULL); + + priv = NM_SETTINGS_GET_PRIVATE(self); + + connection = + nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path); + if (!connection || !NM_IS_SETTINGS_CONNECTION(connection)) + return NULL; + + nm_assert(c_list_contains(&priv->connections_lst_head, &connection->_connections_lst)); + return connection; +} + +gboolean +nm_settings_has_connection(NMSettings *self, NMSettingsConnection *connection) +{ + gboolean has; + + g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(connection), FALSE); + + has = !c_list_is_empty(&connection->_connections_lst); + + nm_assert(has + == nm_c_list_contains_entry(&NM_SETTINGS_GET_PRIVATE(self)->connections_lst_head, + connection, + _connections_lst)); + nm_assert(({ + NMSettingsConnection *candidate = NULL; + const char * path; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(connection)); + if (path) + candidate = nm_settings_get_connection_by_path(self, path); + + (has == (connection == candidate)); + })); + + return has; +} + +/*****************************************************************************/ + +static void +add_plugin(NMSettings *self, NMSettingsPlugin *plugin, const char *pname, const char *path) +{ + NMSettingsPrivate *priv; + + nm_assert(NM_IS_SETTINGS(self)); + nm_assert(NM_IS_SETTINGS_PLUGIN(plugin)); + + nm_assert(pname); + nm_assert(nm_streq0(pname, nm_settings_plugin_get_plugin_name(plugin))); + + priv = NM_SETTINGS_GET_PRIVATE(self); + + nm_assert(!g_slist_find(priv->plugins, plugin)); + + priv->plugins = g_slist_append(priv->plugins, g_object_ref(plugin)); + + nm_shutdown_wait_obj_register_object_full(plugin, + g_strdup_printf("%s-settings-plugin", pname), + TRUE); + + _LOGI("Loaded settings plugin: %s (%s%s%s)", + pname, + NM_PRINT_FMT_QUOTED(path, "\"", path, "\"", "internal")); +} + +static gboolean +add_plugin_load_file(NMSettings *self, const char *pname, GError **error) +{ + gs_free char * full_name = NULL; + gs_free char * path = NULL; + gs_unref_object NMSettingsPlugin *plugin = NULL; + GModule * module; + NMSettingsPluginFactoryFunc factory_func; + struct stat st; + int errsv; + + full_name = g_strdup_printf("nm-settings-plugin-%s", pname); + path = g_module_build_path(NMPLUGINDIR, full_name); + + if (stat(path, &st) != 0) { + errsv = errno; + _LOGW("could not load plugin '%s' from file '%s': %s", + pname, + path, + nm_strerror_native(errsv)); + return TRUE; + } + if (!S_ISREG(st.st_mode)) { + _LOGW("could not load plugin '%s' from file '%s': not a file", pname, path); + return TRUE; + } + if (st.st_uid != 0) { + _LOGW("could not load plugin '%s' from file '%s': file must be owned by root", pname, path); + return TRUE; + } + if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { + _LOGW("could not load plugin '%s' from file '%s': invalid file permissions", pname, path); + return TRUE; + } + + module = g_module_open(path, G_MODULE_BIND_LOCAL); + if (!module) { + _LOGW("could not load plugin '%s' from file '%s': %s", pname, path, g_module_error()); + return TRUE; + } + + /* errors after this point are fatal, because we loaded the shared library already. */ + + if (!g_module_symbol(module, "nm_settings_plugin_factory", (gpointer)(&factory_func))) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Could not find plugin '%s' factory function.", + pname); + g_module_close(module); + return FALSE; + } + + /* after accessing the plugin we cannot unload it anymore, because the glib + * types cannot be properly unregistered. */ + g_module_make_resident(module); + + plugin = (*factory_func)(); + if (!NM_IS_SETTINGS_PLUGIN(plugin)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "plugin '%s' returned invalid settings plugin", + pname); + return FALSE; + } + + add_plugin(self, NM_SETTINGS_PLUGIN(plugin), pname, path); + return TRUE; +} + +static void +add_plugin_keyfile(NMSettings *self) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + if (priv->keyfile_plugin) + return; + priv->keyfile_plugin = nms_keyfile_plugin_new(); + add_plugin(self, NM_SETTINGS_PLUGIN(priv->keyfile_plugin), "keyfile", NULL); +} + +static gboolean +load_plugins(NMSettings *self, const char *const *plugins, GError **error) +{ + const char *const *iter; + gboolean success = TRUE; + + for (iter = plugins; iter && *iter; iter++) { + const char *pname = *iter; + + if (!*pname || strchr(pname, '/')) { + _LOGW("ignore invalid plugin \"%s\"", pname); + continue; + } + + if (NM_IN_STRSET(pname, "ifcfg-suse", "ifnet", "ibft", "no-ibft")) { + _LOGW("skipping deprecated plugin %s", pname); + continue; + } + + /* keyfile plugin is built-in now */ + if (nm_streq(pname, "keyfile")) { + add_plugin_keyfile(self); + continue; + } + + if (nm_utils_strv_find_first((char **) plugins, iter - plugins, pname) >= 0) { + /* the plugin is already mentioned in the list previously. + * Don't load a duplicate. */ + continue; + } + + success = add_plugin_load_file(self, pname, error); + if (!success) + break; + } + + /* If keyfile plugin was not among configured plugins, add it as the last one */ + if (success) + add_plugin_keyfile(self); + + return success; +} + +/*****************************************************************************/ + +static void +pk_hostname_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMSettings * self = NM_SETTINGS(user_data); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMAuthCallResult result; + GError * error = NULL; + const char * hostname; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME); + hostname = nm_auth_chain_get_data(chain, "hostname"); + + /* If our NMSettingsConnection is already gone, do nothing */ + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + } else { + if (!nm_hostname_manager_write_hostname(priv->hostname_manager, hostname)) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Saving the hostname failed."); + } + } + + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, + hostname, + !error, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + + if (error) + g_dbus_method_invocation_take_error(context, error); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_settings_save_hostname(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMSettings * self = NM_SETTINGS(obj); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMAuthChain * chain; + const char * hostname; + const char * error_reason; + int error_code; + + g_variant_get(parameters, "(&s)", &hostname); + + /* Minimal validation of the hostname */ + if (!nm_hostname_manager_validate_hostname(hostname)) { + error_code = NM_SETTINGS_ERROR_INVALID_HOSTNAME; + error_reason = "The hostname was too long or contained invalid characters"; + goto err; + } + + chain = nm_auth_chain_new_context(invocation, pk_hostname_cb, self); + if (!chain) { + error_code = NM_SETTINGS_ERROR_PERMISSION_DENIED; + error_reason = NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED; + goto err; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE); + nm_auth_chain_set_data(chain, "hostname", g_strdup(hostname), g_free); + return; +err: + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, hostname, FALSE, invocation, error_reason); + g_dbus_method_invocation_return_error_literal(invocation, + NM_SETTINGS_ERROR, + error_code, + error_reason); +} + +/*****************************************************************************/ + +static void +_hostname_changed_cb(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data) +{ + _notify(user_data, PROP_HOSTNAME); +} + +/*****************************************************************************/ + +static gboolean +have_connection_for_device(NMSettings *self, NMDevice *device) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + NMSettingsConnection *sett_conn; + + g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE); + + /* Find a wired connection matching for the device, if any */ + c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) { + NMConnection *connection = nm_settings_connection_get_connection(sett_conn); + + if (!nm_device_check_connection_compatible(device, connection, NULL)) + continue; + + if (nm_settings_connection_default_wired_get_device(sett_conn)) + continue; + + if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + continue; + + return TRUE; + } + + /* See if there's a known non-NetworkManager configuration for the device */ + if (nm_device_spec_match_list(device, priv->unrecognized_specs)) + return TRUE; + + return FALSE; +} + +static void +default_wired_clear_tag(NMSettings * self, + NMDevice * device, + NMSettingsConnection *sett_conn, + gboolean add_to_no_auto_default) +{ + nm_assert(NM_IS_SETTINGS(self)); + nm_assert(NM_IS_DEVICE(device)); + nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(device == nm_settings_connection_default_wired_get_device(sett_conn)); + nm_assert(sett_conn == g_object_get_qdata(G_OBJECT(device), _default_wired_connection_quark())); + + _LOGT("auto-default: forget association between %s (%s) and device %s (%s)", + nm_settings_connection_get_uuid(sett_conn), + nm_settings_connection_get_id(sett_conn), + nm_device_get_iface(device), + add_to_no_auto_default ? "persisted" : "temporary"); + + nm_settings_connection_default_wired_set_device(sett_conn, NULL); + + g_object_set_qdata(G_OBJECT(device), _default_wired_connection_quark(), NULL); + + if (add_to_no_auto_default) + nm_config_set_no_auto_default_for_device(NM_SETTINGS_GET_PRIVATE(self)->config, device); +} + +static void +device_realized(NMDevice *device, GParamSpec *pspec, NMSettings *self) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingsPrivate * priv; + NMSettingsConnection * added; + GError * error = NULL; + + if (!nm_device_is_real(device)) + return; + + g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_realized), self); + + priv = NM_SETTINGS_GET_PRIVATE(self); + + /* If the device isn't managed or it already has a default wired connection, + * ignore it. + */ + if (!NM_DEVICE_GET_CLASS(device)->new_default_connection + || !nm_device_get_managed(device, FALSE) + || g_object_get_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark())) + return; + + /* we only check once whether to create the auto-default connection. If we reach this point, + * we mark the creation of the default-wired-connection as blocked. */ + g_object_set_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark(), device); + + if (nm_config_get_no_auto_default_for_device(priv->config, device)) { + _LOGT("auto-default: cannot create auto-default connection for device %s: disabled by " + "\"no-auto-default\"", + nm_device_get_iface(device)); + return; + } + + if (have_connection_for_device(self, device)) { + _LOGT("auto-default: cannot create auto-default connection for device %s: already has a " + "profile", + nm_device_get_iface(device)); + return; + } + + connection = nm_device_new_default_connection(device); + if (!connection) { + _LOGT("auto-default: cannot create auto-default connection for device %s", + nm_device_get_iface(device)); + return; + } + + _LOGT("auto-default: creating in-memory connection %s (%s) for device %s", + nm_connection_get_uuid(connection), + nm_connection_get_id(connection), + nm_device_get_iface(device)); + + nm_settings_add_connection(self, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, + &added, + &error); + if (!added) { + if (!g_error_matches(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS)) { + _LOGW("(%s) couldn't create default wired connection: %s", + nm_device_get_iface(device), + error->message); + } + g_clear_error(&error); + return; + } + + nm_settings_connection_default_wired_set_device(added, device); + + g_object_set_qdata(G_OBJECT(device), _default_wired_connection_quark(), added); + + _LOGI("(%s): created default wired connection '%s'", + nm_device_get_iface(device), + nm_settings_connection_get_id(added)); +} + +void +nm_settings_device_added(NMSettings *self, NMDevice *device) +{ + if (nm_device_is_real(device)) + device_realized(device, NULL, self); + else { + /* FIXME(shutdown): we need to disconnect this signal handler during + * shutdown. */ + g_signal_connect_after(device, + "notify::" NM_DEVICE_REAL, + G_CALLBACK(device_realized), + self); + } +} + +void +nm_settings_device_removed(NMSettings *self, NMDevice *device, gboolean quitting) +{ + NMSettingsConnection *connection; + + g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_realized), self); + + connection = g_object_get_qdata(G_OBJECT(device), _default_wired_connection_quark()); + if (connection) { + default_wired_clear_tag(self, device, connection, FALSE); + + /* Don't delete the default wired connection on shutdown, so that it + * remains up and can be assumed if NM starts again. + */ + if (quitting == FALSE) + nm_settings_connection_delete(connection, TRUE); + } +} + +/*****************************************************************************/ + +static void +session_monitor_changed_cb(NMSessionMonitor *session_monitor, NMSettings *self) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + NMSettingsConnection *const *list; + guint i, len; + guint generation; + +again: + list = nm_settings_get_connections(self, &len); + generation = priv->connections_generation; + for (i = 0; i < len; i++) { + gboolean is_visible; + + is_visible = nm_settings_connection_check_visibility(list[i], session_monitor); + nm_settings_connection_set_flags(list[i], + NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE, + is_visible); + if (generation != priv->connections_generation) { + /* the cached list was invalidated. Start again. + * + * Note that nm_settings_connection_recheck_visibility() will do nothing + * if the visibility didn't change (including emitting no signals, + * and not invalidating the list). + * + * Hence, for this to be an endless loop, the settings would have + * to constantly change the visibility flag and also invalidate the list. */ + goto again; + } + } +} + +/*****************************************************************************/ + +G_GNUC_PRINTF(4, 5) +static void +_kf_db_log_fcn(NMKeyFileDB *kf_db, int syslog_level, gpointer user_data, const char *fmt, ...) +{ + NMSettings *self = user_data; + NMLogLevel level = nm_log_level_from_syslog(syslog_level); + + if (_NMLOG_ENABLED(level)) { + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_free char * msg = NULL; + va_list ap; + const char * prefix; + + va_start(ap, fmt); + msg = g_strdup_vprintf(fmt, ap); + va_end(ap); + + if (priv->kf_db_timestamps == kf_db) + prefix = "timestamps"; + else if (priv->kf_db_seen_bssids == kf_db) + prefix = "seen-bssids"; + else { + nm_assert_not_reached(); + prefix = "???"; + } + + _NMLOG(level, "[%s-keyfile]: %s", prefix, msg); + } +} + +static gboolean +_kf_db_got_dirty_flush(NMSettings *self, gboolean is_timestamps) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + const char * prefix; + NMKeyFileDB * kf_db; + + if (is_timestamps) { + prefix = "timestamps"; + kf_db = priv->kf_db_timestamps; + priv->kf_db_flush_idle_id_timestamps = 0; + } else { + prefix = "seen-bssids"; + kf_db = priv->kf_db_seen_bssids; + priv->kf_db_flush_idle_id_seen_bssids = 0; + } + + if (nm_key_file_db_is_dirty(kf_db)) + nm_key_file_db_to_file(kf_db, FALSE); + else { + _LOGT("[%s-keyfile]: skip saving changes to \"%s\"", + prefix, + nm_key_file_db_get_filename(kf_db)); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +_kf_db_got_dirty_flush_timestamps_cb(gpointer user_data) +{ + return _kf_db_got_dirty_flush(user_data, TRUE); +} + +static gboolean +_kf_db_got_dirty_flush_seen_bssids_cb(gpointer user_data) +{ + return _kf_db_got_dirty_flush(user_data, FALSE); +} + +static void +_kf_db_got_dirty_fcn(NMKeyFileDB *kf_db, gpointer user_data) +{ + NMSettings * self = user_data; + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + GSourceFunc idle_func; + guint * p_id; + const char * prefix; + + if (priv->kf_db_timestamps == kf_db) { + prefix = "timestamps"; + p_id = &priv->kf_db_flush_idle_id_timestamps; + idle_func = _kf_db_got_dirty_flush_timestamps_cb; + } else if (priv->kf_db_seen_bssids == kf_db) { + prefix = "seen-bssids"; + p_id = &priv->kf_db_flush_idle_id_seen_bssids; + idle_func = _kf_db_got_dirty_flush_seen_bssids_cb; + } else { + nm_assert_not_reached(); + return; + } + + if (*p_id != 0) + return; + _LOGT("[%s-keyfile]: schedule flushing changes to disk", prefix); + *p_id = g_idle_add_full(G_PRIORITY_LOW, idle_func, self, NULL); +} + +void +nm_settings_kf_db_write(NMSettings *self) +{ + NMSettingsPrivate *priv; + + g_return_if_fail(NM_IS_SETTINGS(self)); + + priv = NM_SETTINGS_GET_PRIVATE(self); + if (priv->kf_db_timestamps) + nm_key_file_db_to_file(priv->kf_db_timestamps, TRUE); + if (priv->kf_db_seen_bssids) + nm_key_file_db_to_file(priv->kf_db_seen_bssids, TRUE); +} + +/*****************************************************************************/ + +gboolean +nm_settings_start(NMSettings *self, GError **error) +{ + NMSettingsPrivate *priv; + gs_strfreev char **plugins = NULL; + GSList * iter; + + priv = NM_SETTINGS_GET_PRIVATE(self); + + nm_assert(!priv->started); + + priv->startup_complete_start_timestamp_msec = nm_utils_get_monotonic_timestamp_msec(); + + priv->hostname_manager = g_object_ref(nm_hostname_manager_get()); + + priv->kf_db_timestamps = nm_key_file_db_new(NMSTATEDIR "/timestamps", + "timestamps", + _kf_db_log_fcn, + _kf_db_got_dirty_fcn, + self); + priv->kf_db_seen_bssids = nm_key_file_db_new(NMSTATEDIR "/seen-bssids", + "seen-bssids", + _kf_db_log_fcn, + _kf_db_got_dirty_fcn, + self); + nm_key_file_db_start(priv->kf_db_timestamps); + nm_key_file_db_start(priv->kf_db_seen_bssids); + + /* Load the plugins; fail if a plugin is not found. */ + plugins = nm_config_data_get_plugins(nm_config_get_data_orig(priv->config), TRUE); + + if (!load_plugins(self, (const char *const *) plugins, error)) + return FALSE; + + for (iter = priv->plugins; iter; iter = iter->next) { + NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN(iter->data); + + g_signal_connect(plugin, + NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED, + G_CALLBACK(_plugin_unmanaged_specs_changed), + self); + g_signal_connect(plugin, + NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED, + G_CALLBACK(_plugin_unrecognized_specs_changed), + self); + } + + _plugin_unmanaged_specs_changed(NULL, self); + _plugin_unrecognized_specs_changed(NULL, self); + + _plugin_connections_reload(self); + + g_signal_connect(priv->hostname_manager, + "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, + G_CALLBACK(_hostname_changed_cb), + self); + if (nm_hostname_manager_get_hostname(priv->hostname_manager)) + _notify(self, PROP_HOSTNAME); + + priv->started = TRUE; + _startup_complete_check(self, 0); + + /* FIXME(shutdown): we also need a nm_settings_stop() during shutdown. + * + * In particular, we need to remove all in-memory keyfiles from /run that are nm-generated. + * alternatively, the nm-generated flag must also be persisted and loaded to /run. */ + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettings * self = NM_SETTINGS(object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + const char ** strv; + + switch (prop_id) { + case PROP_UNMANAGED_SPECS: + g_value_take_boxed(value, + _nm_utils_slist_to_strv(nm_settings_get_unmanaged_specs(self), TRUE)); + break; + case PROP_HOSTNAME: + g_value_set_string(value, + priv->hostname_manager + ? nm_hostname_manager_get_hostname(priv->hostname_manager) + : NULL); + break; + case PROP_CAN_MODIFY: + g_value_set_boolean(value, TRUE); + break; + case PROP_CONNECTIONS: + strv = nm_dbus_utils_get_paths_for_clist( + &priv->connections_lst_head, + priv->connections_len, + G_STRUCT_OFFSET(NMSettingsConnection, _connections_lst), + TRUE); + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(strv)); + break; + case PROP_STARTUP_COMPLETE: + g_value_set_boolean(value, !nm_settings_get_startup_complete_blocked_reason(self, FALSE)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettings * self = NM_SETTINGS(object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MANAGER: + /* construct-only */ + priv->manager = g_value_get_pointer(value); + nm_assert(NM_IS_MANAGER(priv->manager)); + g_object_add_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_settings_init(NMSettings *self) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + c_list_init(&priv->auth_lst_head); + c_list_init(&priv->connections_lst_head); + c_list_init(&priv->startup_complete_scd_lst_head); + + c_list_init(&priv->sce_dirty_lst_head); + priv->sce_idx = g_hash_table_new_full(nm_pstr_hash, + nm_pstr_equal, + NULL, + (GDestroyNotify) _sett_conn_entry_free); + + priv->config = g_object_ref(nm_config_get()); + + priv->agent_mgr = g_object_ref(nm_agent_manager_get()); + + priv->platform = g_object_ref(NM_PLATFORM_GET); + + priv->session_monitor = g_object_ref(nm_session_monitor_get()); + g_signal_connect(priv->session_monitor, + NM_SESSION_MONITOR_CHANGED, + G_CALLBACK(session_monitor_changed_cb), + self); +} + +NMSettings * +nm_settings_new(NMManager *manager) +{ + nm_assert(NM_IS_MANAGER(manager)); + + return g_object_new(NM_TYPE_SETTINGS, NM_SETTINGS_MANAGER, manager, NULL); +} + +static void +dispose(GObject *object) +{ + NMSettings * self = NM_SETTINGS(object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + CList * iter; + + nm_assert(c_list_is_empty(&priv->sce_dirty_lst_head)); + nm_assert(g_hash_table_size(priv->sce_idx) == 0); + + nm_clear_g_source(&priv->startup_complete_timeout_id); + nm_clear_pointer(&priv->startup_complete_idx, g_hash_table_destroy); + nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head)); + + while ((iter = c_list_first(&priv->auth_lst_head))) + nm_auth_chain_destroy(nm_auth_chain_parent_lst_entry(iter)); + + if (priv->hostname_manager) { + g_signal_handlers_disconnect_by_func(priv->hostname_manager, + G_CALLBACK(_hostname_changed_cb), + self); + g_clear_object(&priv->hostname_manager); + } + + if (priv->session_monitor) { + g_signal_handlers_disconnect_by_func(priv->session_monitor, + G_CALLBACK(session_monitor_changed_cb), + self); + g_clear_object(&priv->session_monitor); + } + + G_OBJECT_CLASS(nm_settings_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMSettings * self = NM_SETTINGS(object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + GSList * iter; + + _clear_connections_cached_list(priv); + + nm_assert(c_list_is_empty(&priv->connections_lst_head)); + + nm_assert(c_list_is_empty(&priv->sce_dirty_lst_head)); + nm_assert(g_hash_table_size(priv->sce_idx) == 0); + + nm_clear_pointer(&priv->sce_idx, g_hash_table_destroy); + + g_slist_free_full(priv->unmanaged_specs, g_free); + g_slist_free_full(priv->unrecognized_specs, g_free); + + while ((iter = priv->plugins)) { + gs_unref_object NMSettingsPlugin *plugin = iter->data; + + priv->plugins = g_slist_delete_link(priv->plugins, iter); + g_signal_handlers_disconnect_by_data(plugin, self); + } + + g_clear_object(&priv->keyfile_plugin); + + g_clear_object(&priv->agent_mgr); + + nm_clear_g_source(&priv->kf_db_flush_idle_id_timestamps); + nm_clear_g_source(&priv->kf_db_flush_idle_id_seen_bssids); + nm_key_file_db_to_file(priv->kf_db_timestamps, FALSE); + nm_key_file_db_to_file(priv->kf_db_seen_bssids, FALSE); + nm_key_file_db_destroy(priv->kf_db_timestamps); + nm_key_file_db_destroy(priv->kf_db_seen_bssids); + + G_OBJECT_CLASS(nm_settings_parent_class)->finalize(object); + + g_clear_object(&priv->config); + + g_clear_object(&priv->platform); + + if (priv->manager) { + g_object_remove_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager); + priv->manager = NULL; + } +} + +static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "NewConnection", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), ); + +static const GDBusSignalInfo signal_info_connection_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "ConnectionRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_settings = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_SETTINGS, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "ListConnections", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connections", "ao"), ), ), + .handle = impl_settings_list_connections, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetConnectionByUuid", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("uuid", "s"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), ), + .handle = impl_settings_get_connection_by_uuid, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ), + .handle = impl_settings_add_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddConnectionUnsaved", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ), + .handle = impl_settings_add_connection_unsaved, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddConnection2", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), + NM_DEFINE_GDBUS_ARG_INFO("args", "a{sv}"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ), + .handle = impl_settings_add_connection2, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "LoadConnections", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("filenames", "as"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("status", "b"), + NM_DEFINE_GDBUS_ARG_INFO("failures", "as"), ), ), + .handle = impl_settings_load_connections, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("ReloadConnections", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("status", "b"), ), ), + .handle = impl_settings_reload_connections, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "SaveHostname", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("hostname", "s"), ), ), + .handle = impl_settings_save_hostname, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_new_connection, + &signal_info_connection_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Connections", + "ao", + NM_SETTINGS_CONNECTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Hostname", "s", NM_SETTINGS_HOSTNAME), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CanModify", + "b", + NM_SETTINGS_CAN_MODIFY), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_settings_class_init(NMSettingsClass *class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(class); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH_SETTINGS); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_settings); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_MANAGER] = + g_param_spec_pointer(NM_SETTINGS_MANAGER, + "", + "", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_UNMANAGED_SPECS] = + g_param_spec_boxed(NM_SETTINGS_UNMANAGED_SPECS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_SETTINGS_HOSTNAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAN_MODIFY] = + g_param_spec_boolean(NM_SETTINGS_CAN_MODIFY, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIONS] = + g_param_spec_boxed(NM_SETTINGS_CONNECTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STARTUP_COMPLETE] = + g_param_spec_boolean(NM_SETTINGS_STARTUP_COMPLETE, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[CONNECTION_ADDED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NM_TYPE_SETTINGS_CONNECTION); + + signals[CONNECTION_UPDATED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + NM_TYPE_SETTINGS_CONNECTION, + G_TYPE_UINT); + + signals[CONNECTION_REMOVED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NM_TYPE_SETTINGS_CONNECTION); + + signals[CONNECTION_FLAGS_CHANGED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NM_TYPE_SETTINGS_CONNECTION); +} diff --git a/src/core/settings/nm-settings.h b/src/core/settings/nm-settings.h new file mode 100644 index 0000000..09a0af5 --- /dev/null +++ b/src/core/settings/nm-settings.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Søren Sandmann + * Dan Williams + * Tambet Ingo + * Copyright (C) 2007 - 2011 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#ifndef __NM_SETTINGS_H__ +#define __NM_SETTINGS_H__ + +#include "nm-connection.h" + +#include "nm-settings-connection.h" + +#define NM_TYPE_SETTINGS (nm_settings_get_type()) +#define NM_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS, NMSettings)) +#define NM_SETTINGS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS, NMSettingsClass)) +#define NM_IS_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS)) +#define NM_IS_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS)) +#define NM_SETTINGS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS, NMSettingsClass)) + +#define NM_SETTINGS_UNMANAGED_SPECS "unmanaged-specs" +#define NM_SETTINGS_HOSTNAME "hostname" +#define NM_SETTINGS_CAN_MODIFY "can-modify" +#define NM_SETTINGS_CONNECTIONS "connections" +#define NM_SETTINGS_STARTUP_COMPLETE "startup-complete" +#define NM_SETTINGS_MANAGER "manager" + +#define NM_SETTINGS_SIGNAL_CONNECTION_ADDED "connection-added" +#define NM_SETTINGS_SIGNAL_CONNECTION_UPDATED "connection-updated" +#define NM_SETTINGS_SIGNAL_CONNECTION_REMOVED "connection-removed" +#define NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED "connection-flags-changed" + +/** + * NMConnectionFilterFunc: + * @settings: The #NMSettings requesting the filtering + * @connection: the connection to be filtered + * @func_data: the caller-provided data pointer + * + * Returns: %TRUE to allow the connection, %FALSE to ignore it + */ +typedef gboolean (*NMSettingsConnectionFilterFunc)(NMSettings * settings, + NMSettingsConnection *connection, + gpointer func_data); + +typedef struct _NMSettingsClass NMSettingsClass; + +typedef void (*NMSettingsSetHostnameCb)(const char *name, gboolean result, gpointer user_data); + +GType nm_settings_get_type(void); + +NMSettings *nm_settings_get(void); +#define NM_SETTINGS_GET (nm_settings_get()) + +NMSettings *nm_settings_new(NMManager *manager); + +gboolean nm_settings_start(NMSettings *self, GError **error); + +typedef void (*NMSettingsAddCallback)(NMSettings * settings, + NMSettingsConnection * connection, + GError * error, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + gpointer user_data); + +void nm_settings_add_connection_dbus(NMSettings * self, + NMConnection * connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionAddReason add_reason, + NMSettingsConnectionIntFlags sett_flags, + NMAuthSubject * subject, + GDBusMethodInvocation * context, + NMSettingsAddCallback callback, + gpointer user_data); + +NMSettingsConnection *const *nm_settings_get_connections(NMSettings *settings, guint *out_len); + +NMSettingsConnection **nm_settings_get_connections_clone(NMSettings * self, + guint * out_len, + NMSettingsConnectionFilterFunc func, + gpointer func_data, + GCompareDataFunc sort_compare_func, + gpointer sort_data); + +gboolean nm_settings_add_connection(NMSettings * settings, + NMConnection * connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionAddReason add_reason, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnection ** out_sett_conn, + GError ** error); + +gboolean nm_settings_update_connection(NMSettings * self, + NMSettingsConnection * sett_conn, + NMConnection * new_connection, + NMSettingsConnectionPersistMode persist_mode, + NMSettingsConnectionIntFlags sett_flags, + NMSettingsConnectionIntFlags sett_mask, + NMSettingsConnectionUpdateReason update_reason, + const char * log_context_name, + GError ** error); + +void nm_settings_delete_connection(NMSettings * self, + NMSettingsConnection *sett_conn, + gboolean allow_add_to_no_auto_default); + +NMSettingsConnection *nm_settings_get_connection_by_path(NMSettings *settings, const char *path); + +NMSettingsConnection *nm_settings_get_connection_by_uuid(NMSettings *settings, const char *uuid); + +const char *nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid); + +gboolean nm_settings_has_connection(NMSettings *self, NMSettingsConnection *connection); + +const GSList *nm_settings_get_unmanaged_specs(NMSettings *self); + +void nm_settings_device_added(NMSettings *self, NMDevice *device); + +void nm_settings_device_removed(NMSettings *self, NMDevice *device, gboolean quitting); + +const char *nm_settings_get_startup_complete_blocked_reason(NMSettings *self, + gboolean force_reload); + +void nm_settings_kf_db_write(NMSettings *settings); + +#endif /* __NM_SETTINGS_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/meson.build b/src/core/settings/plugins/ifcfg-rh/meson.build new file mode 100644 index 0000000..a2f11a9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/meson.build @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +install_data( + 'nm-ifcfg-rh.conf', + install_dir: dbus_conf_dir, +) + +dbus_sources = gnome.gdbus_codegen( + 'nmdbus-ifcfg-rh', + 'nm-ifcfg-rh.xml', + interface_prefix: 'com.redhat', + namespace: 'NMDBus', +) + +libnmdbus_ifcfg_rh = static_library( + 'nmdbus-ifcfg-rh', + sources: dbus_sources, + dependencies: glib_dep, + c_args: introspection_extra_cflags, +) + +libnms_ifcfg_rh_core = static_library( + 'nms-ifcfg-rh-core', + sources: files( + 'nms-ifcfg-rh-reader.c', + 'nms-ifcfg-rh-utils.c', + 'nms-ifcfg-rh-writer.c', + 'shvar.c', + ), + dependencies: core_default_dep, + c_args: daemon_c_flags, +) + +libnm_settings_plugin_ifcfg_rh = shared_module( + 'nm-settings-plugin-ifcfg-rh', + sources: files( + 'nms-ifcfg-rh-storage.c', + 'nms-ifcfg-rh-plugin.c', + ), + dependencies: core_plugin_dep, + c_args: daemon_c_flags, + link_with: libnms_ifcfg_rh_core, + link_args: ldflags_linker_script_settings, + link_depends: linker_script_settings, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_settings_plugin_ifcfg_rh + +install_data( + [ + 'nm-ifdown', + 'nm-ifup', + ], + install_dir: nm_libexecdir, + install_mode: 'rwxr-xr-x', +) + +if enable_tests + subdir('tests') +endif diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf new file mode 100644 index 0000000..c7a1730 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml new file mode 100644 index 0000000..033fbfa --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifdown b/src/core/settings/plugins/ifcfg-rh/nm-ifdown new file mode 100644 index 0000000..e7bd73a --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nm-ifdown @@ -0,0 +1,3 @@ +#!/bin/sh +nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" && +exec nmcli connection down filename "/etc/sysconfig/network-scripts/ifcfg-$1" diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifup b/src/core/settings/plugins/ifcfg-rh/nm-ifup new file mode 100644 index 0000000..96637a9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nm-ifup @@ -0,0 +1,3 @@ +#!/bin/sh +nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" && +exec nmcli connection up filename "/etc/sysconfig/network-scripts/ifcfg-$1" diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h new file mode 100644 index 0000000..79c67e4 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2013 Red Hat, Inc. + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#define IFCFG_TAG "ifcfg-" +#define KEYS_TAG "keys-" +#define ROUTE_TAG "route-" +#define RULE_TAG "rule-" +#define ROUTE6_TAG "route6-" +#define RULE6_TAG "rule6-" + +#define BAK_TAG ".bak" +#define TILDE_TAG "~" +#define ORIG_TAG ".orig" +#define REJ_TAG ".rej" +#define RPMNEW_TAG ".rpmnew" +#define AUGNEW_TAG ".augnew" +#define AUGTMP_TAG ".augtmp" + +#define IFCFG_DIR SYSCONFDIR "/sysconfig/network-scripts" + +#define TYPE_ETHERNET "Ethernet" +#define TYPE_WIRELESS "Wireless" +#define TYPE_INFINIBAND "InfiniBand" +#define TYPE_BRIDGE "Bridge" +#define TYPE_BOND "Bond" +#define TYPE_VLAN "Vlan" +#define TYPE_TEAM "Team" +#define TYPE_TEAM_PORT "TeamPort" + +#define SECRET_FLAG_AGENT "user" +#define SECRET_FLAG_NOT_SAVED "ask" +#define SECRET_FLAG_NOT_REQUIRED "unused" + +/* DCB key names */ +#define KEY_DCB_APP_FCOE_ENABLE "DCB_APP_FCOE_ENABLE" +#define KEY_DCB_APP_FCOE_ADVERTISE "DCB_APP_FCOE_ADVERTISE" +#define KEY_DCB_APP_FCOE_WILLING "DCB_APP_FCOE_WILLING" +#define KEY_DCB_APP_FCOE_MODE "DCB_APP_FCOE_MODE" +#define KEY_DCB_APP_ISCSI_ENABLE "DCB_APP_ISCSI_ENABLE" +#define KEY_DCB_APP_ISCSI_ADVERTISE "DCB_APP_ISCSI_ADVERTISE" +#define KEY_DCB_APP_ISCSI_WILLING "DCB_APP_ISCSI_WILLING" +#define KEY_DCB_APP_FIP_ENABLE "DCB_APP_FIP_ENABLE" +#define KEY_DCB_APP_FIP_ADVERTISE "DCB_APP_FIP_ADVERTISE" +#define KEY_DCB_APP_FIP_WILLING "DCB_APP_FIP_WILLING" +#define KEY_DCB_PFC_ENABLE "DCB_PFC_ENABLE" +#define KEY_DCB_PFC_ADVERTISE "DCB_PFC_ADVERTISE" +#define KEY_DCB_PFC_WILLING "DCB_PFC_WILLING" +#define KEY_DCB_PFC_UP "DCB_PFC_UP" +#define KEY_DCB_PG_ENABLE "DCB_PG_ENABLE" +#define KEY_DCB_PG_ADVERTISE "DCB_PG_ADVERTISE" +#define KEY_DCB_PG_WILLING "DCB_PG_WILLING" +#define KEY_DCB_PG_ID "DCB_PG_ID" +#define KEY_DCB_PG_PCT "DCB_PG_PCT" +#define KEY_DCB_PG_UPPCT "DCB_PG_UPPCT" +#define KEY_DCB_PG_STRICT "DCB_PG_STRICT" +#define KEY_DCB_PG_UP2TC "DCB_PG_UP2TC" + +#endif /* __COMMON_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c new file mode 100644 index 0000000..b5c205c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c @@ -0,0 +1,1253 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Dan Williams + * Søren Sandmann + * Copyright (C) 2007 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifcfg-rh-plugin.h" + +#include +#include +#include + +#include "nm-std-aux/c-list-util.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-config.h" +#include "nm-dbus-manager.h" +#include "settings/nm-settings-plugin.h" +#include "settings/nm-settings-utils.h" +#include "NetworkManagerUtils.h" + +#include "nms-ifcfg-rh-storage.h" +#include "nms-ifcfg-rh-common.h" +#include "nms-ifcfg-rh-utils.h" +#include "nms-ifcfg-rh-reader.h" +#include "nms-ifcfg-rh-writer.h" + +#define IFCFGRH1_BUS_NAME "com.redhat.ifcfgrh1" +#define IFCFGRH1_OBJECT_PATH "/com/redhat/ifcfgrh1" +#define IFCFGRH1_IFACE1_NAME "com.redhat.ifcfgrh1" +#define IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS "GetIfcfgDetails" + +/*****************************************************************************/ + +typedef struct { + NMConfig *config; + + struct { + GDBusConnection *connection; + GCancellable * cancellable; + gulong signal_id; + guint regist_id; + } dbus; + + NMSettUtilStorages storages; + + GHashTable *unmanaged_specs; + GHashTable *unrecognized_specs; + +} NMSIfcfgRHPluginPrivate; + +struct _NMSIfcfgRHPlugin { + NMSettingsPlugin parent; + NMSIfcfgRHPluginPrivate _priv; +}; + +struct _NMSIfcfgRHPluginClass { + NMSettingsPluginClass parent; +}; + +G_DEFINE_TYPE(NMSIfcfgRHPlugin, nms_ifcfg_rh_plugin, NM_TYPE_SETTINGS_PLUGIN) + +#define NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSIfcfgRHPlugin, NMS_IS_IFCFG_RH_PLUGIN, NMSettingsPlugin) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + "ifcfg-rh: " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _unhandled_specs_reset(NMSIfcfgRHPlugin *self); + +static void _unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages); + +/*****************************************************************************/ + +static void +nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent) +{ + nm_assert(NMS_IS_IFCFG_RH_PLUGIN(self)); + +#if NM_MORE_ASSERTS > 5 + { + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + NMSIfcfgRHStorage * storage; + gsize n_uuid; + gs_unref_hashtable GHashTable *h_unmanaged = NULL; + gs_unref_hashtable GHashTable *h_unrecognized = NULL; + + nm_assert(g_hash_table_size(priv->storages.idx_by_filename) + == c_list_length(&priv->storages._storage_lst_head)); + + h_unmanaged = g_hash_table_new(nm_str_hash, g_str_equal); + h_unrecognized = g_hash_table_new(nm_str_hash, g_str_equal); + + n_uuid = 0; + + c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) { + const char *uuid; + const char *filename; + + filename = nms_ifcfg_rh_storage_get_filename(storage); + + nm_assert(filename && NM_STR_HAS_PREFIX(filename, IFCFG_DIR "/")); + + uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); + + nm_assert((!!uuid) + (!!storage->unmanaged_spec) + (!!storage->unrecognized_spec) == 1); + + nm_assert(storage + == nm_sett_util_storages_lookup_by_filename(&priv->storages, filename)); + + if (uuid) { + NMSettUtilStorageByUuidHead *sbuh; + NMSettUtilStorageByUuidHead *sbuh2; + + if (storage->connection) + nm_assert(nm_streq0(nm_connection_get_uuid(storage->connection), uuid)); + + if (!g_hash_table_lookup_extended(priv->storages.idx_by_uuid, + &uuid, + (gpointer *) &sbuh, + (gpointer *) &sbuh2)) + nm_assert_not_reached(); + + nm_assert(sbuh); + nm_assert(nm_streq(uuid, sbuh->uuid)); + nm_assert(sbuh == sbuh2); + nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head, + &storage->parent._storage_by_uuid_lst)); + + if (c_list_first(&sbuh->_storage_by_uuid_lst_head) + == &storage->parent._storage_by_uuid_lst) + n_uuid++; + } else if (storage->unmanaged_spec) { + nm_assert(strlen(storage->unmanaged_spec) > 0); + g_hash_table_add(h_unmanaged, storage->unmanaged_spec); + } else if (storage->unrecognized_spec) { + nm_assert(strlen(storage->unrecognized_spec) > 0); + g_hash_table_add(h_unrecognized, storage->unrecognized_spec); + } else + nm_assert_not_reached(); + + nm_assert(!storage->connection); + } + + nm_assert(g_hash_table_size(priv->storages.idx_by_uuid) == n_uuid); + + if (unhandled_specs_consistent) { + nm_assert(nm_utils_hashtable_same_keys(h_unmanaged, priv->unmanaged_specs)); + nm_assert(nm_utils_hashtable_same_keys(h_unrecognized, priv->unrecognized_specs)); + } + } +#endif +} + +/*****************************************************************************/ + +static NMSIfcfgRHStorage * +_load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free_error GError *load_error = NULL; + gs_free char * unhandled_spec = NULL; + gboolean load_error_ignore; + struct stat st; + + if (stat(filename, &st) != 0) { + int errsv = errno; + + if (error) { + nm_utils_error_set_errno(error, errsv, "failure to stat file \%s\": %s", filename); + } else + _LOGT("load[%s]: failure to stat file: %s", filename, nm_strerror_native(errsv)); + return NULL; + } + + connection = connection_from_file(filename, &unhandled_spec, &load_error, &load_error_ignore); + if (load_error) { + if (error) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "failure to read file \"%s\": %s", + filename, + load_error->message); + } else { + _NMLOG(load_error_ignore ? LOGL_TRACE : LOGL_WARN, + "load[%s]: failure to read file: %s", + filename, + load_error->message); + } + return NULL; + } + + if (unhandled_spec) { + const char *unmanaged_spec; + const char *unrecognized_spec; + + if (!nms_ifcfg_rh_utils_parse_unhandled_spec(unhandled_spec, + &unmanaged_spec, + &unrecognized_spec)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "invalid unhandled spec \"%s\"", + unhandled_spec); + nm_assert_not_reached(); + return NULL; + } + return nms_ifcfg_rh_storage_new_unhandled(self, + filename, + unmanaged_spec, + unrecognized_spec); + } + + return nms_ifcfg_rh_storage_new_connection(self, + filename, + g_steal_pointer(&connection), + &st.st_mtim); +} + +static void +_load_dir(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages) +{ + gs_unref_hashtable GHashTable *dupl_filenames = NULL; + gs_free_error GError *local = NULL; + const char * f_filename; + GDir * dir; + + dir = g_dir_open(IFCFG_DIR, 0, &local); + if (!dir) { + _LOGT("Could not read directory '%s': %s", IFCFG_DIR, local->message); + return; + } + + dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); + + while ((f_filename = g_dir_read_name(dir))) { + gs_free char * full_path = NULL; + NMSIfcfgRHStorage *storage; + char * full_filename; + + full_path = g_build_filename(IFCFG_DIR, f_filename, NULL); + full_filename = utils_detect_ifcfg_path(full_path, TRUE); + if (!full_filename) + continue; + + if (!g_hash_table_add(dupl_filenames, full_filename)) + continue; + + nm_assert(!nm_sett_util_storages_lookup_by_filename(storages, full_filename)); + + storage = _load_file(self, full_filename, NULL); + if (storage) + nm_sett_util_storages_add_take(storages, storage); + } + g_dir_close(dir); +} + +static void +_storages_consolidate(NMSIfcfgRHPlugin * self, + NMSettUtilStorages * storages_new, + gboolean replace_all, + GHashTable * storages_replaced, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + CList lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted); + gs_unref_ptrarray GPtrArray *storages_modified = NULL; + CList storages_deleted; + NMSIfcfgRHStorage * storage_safe; + NMSIfcfgRHStorage * storage_new; + NMSIfcfgRHStorage * storage_old; + NMSIfcfgRHStorage * storage; + guint i; + + /* when we reload all files, we must signal add/update/modify of profiles one-by-one. + * NMSettings then goes ahead and emits further signals and a lot of things happen. + * + * So, first, emit an update of the unmanaged/unrecognized specs that contains *all* + * the unmanaged/unrecognized devices from before and after. Since both unmanaged/unrecognized + * specs have the meaning of "not doing something", it makes sense that we temporarily + * disable that action for the sum of before and after. */ + _unhandled_specs_merge_storages(self, storages_new); + + storages_modified = g_ptr_array_new_with_free_func(g_object_unref); + c_list_init(&storages_deleted); + + c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst) + storage_old->dirty = TRUE; + + c_list_for_each_entry_safe (storage_new, + storage_safe, + &storages_new->_storage_lst_head, + parent._storage_lst) { + storage_old = nm_sett_util_storages_lookup_by_filename( + &priv->storages, + nms_ifcfg_rh_storage_get_filename(storage_new)); + + nm_sett_util_storages_steal(storages_new, storage_new); + + if (!storage_old || !nms_ifcfg_rh_storage_equal_type(storage_new, storage_old)) { + if (storage_old) { + nm_sett_util_storages_steal(&priv->storages, storage_old); + if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old)) + c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst); + else + nms_ifcfg_rh_storage_destroy(storage_old); + } + storage_new->dirty = FALSE; + nm_sett_util_storages_add_take(&priv->storages, storage_new); + g_ptr_array_add(storages_modified, g_object_ref(storage_new)); + continue; + } + + storage_old->dirty = FALSE; + nms_ifcfg_rh_storage_copy_content(storage_old, storage_new); + nms_ifcfg_rh_storage_destroy(storage_new); + g_ptr_array_add(storages_modified, g_object_ref(storage_old)); + } + + c_list_for_each_entry_safe (storage_old, + storage_safe, + &priv->storages._storage_lst_head, + parent._storage_lst) { + if (!storage_old->dirty) + continue; + if (replace_all + || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) { + nm_sett_util_storages_steal(&priv->storages, storage_old); + if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old)) + c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst); + else + nms_ifcfg_rh_storage_destroy(storage_old); + } + } + + /* raise events. */ + + for (i = 0; i < storages_modified->len; i++) { + storage = storages_modified->pdata[i]; + storage->dirty = TRUE; + } + + for (i = 0; i < storages_modified->len; i++) { + gs_unref_object NMConnection *connection = NULL; + storage = storages_modified->pdata[i]; + + if (!storage->dirty) { + /* the entry is no longer dirty. In the meantime we already emitted + * another signal for it. */ + continue; + } + storage->dirty = FALSE; + if (storage + != nm_sett_util_storages_lookup_by_filename( + &priv->storages, + nms_ifcfg_rh_storage_get_filename(storage))) { + /* hm? The profile was deleted in the meantime? That is only possible + * if the signal handler called again into the plugin. In any case, the event + * was already emitted. Skip. */ + continue; + } + + connection = nms_ifcfg_rh_storage_steal_connection(storage); + if (!connection) { + nm_assert(!nms_ifcfg_rh_storage_get_uuid_opt(storage)); + continue; + } + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage)); + callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data); + } + + while ( + (storage = c_list_first_entry(&storages_deleted, NMSIfcfgRHStorage, parent._storage_lst))) { + c_list_unlink(&storage->parent._storage_lst); + callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data); + nms_ifcfg_rh_storage_destroy(storage); + } +} + +/*****************************************************************************/ + +static void +load_connections(NMSettingsPlugin * plugin, + NMSettingsPluginConnectionLoadEntry * entries, + gsize n_entries, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(plugin); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = + NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy); + gs_unref_hashtable GHashTable *dupl_filenames = NULL; + gs_unref_hashtable GHashTable *storages_replaced = NULL; + gs_unref_hashtable GHashTable *loaded_uuids = NULL; + const char * loaded_uuid; + GHashTableIter h_iter; + gsize i; + + if (n_entries == 0) + return; + + dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + + loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal); + + storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); + + for (i = 0; i < n_entries; i++) { + NMSettingsPluginConnectionLoadEntry *const entry = &entries[i]; + gs_free_error GError * local = NULL; + const char * full_filename; + const char * uuid; + gs_free char * full_filename_keep = NULL; + NMSettingsPluginConnectionLoadEntry *dupl_content_entry; + gs_unref_object NMSIfcfgRHStorage *storage = NULL; + + if (entry->handled) + continue; + + if (entry->filename[0] != '/') + continue; + + full_filename_keep = utils_detect_ifcfg_path(entry->filename, FALSE); + + if (!full_filename_keep) { + if (nm_utils_file_is_in_path(entry->filename, IFCFG_DIR)) { + nm_utils_error_set(&entry->error, + NM_UTILS_ERROR_UNKNOWN, + ("path is not a valid name for an ifcfg-rh file")); + entry->handled = TRUE; + } + continue; + } + + if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) { + /* we already visited this file. */ + entry->handled = dupl_content_entry->handled; + if (dupl_content_entry->error) { + g_set_error_literal(&entry->error, + dupl_content_entry->error->domain, + dupl_content_entry->error->code, + dupl_content_entry->error->message); + } + continue; + } + + entry->handled = TRUE; + + full_filename = full_filename_keep; + if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry)) + nm_assert_not_reached(); + + storage = _load_file(self, full_filename, &local); + if (!storage) { + if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) { + NMSIfcfgRHStorage *storage2; + + /* the file does not exist. We take that as indication to unload the file + * that was previously loaded... */ + storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename); + if (storage2) + g_hash_table_add(storages_replaced, g_object_ref(storage2)); + continue; + } + g_propagate_error(&entry->error, g_steal_pointer(&local)); + continue; + } + + uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); + if (uuid) + g_hash_table_add(loaded_uuids, (char *) uuid); + + nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage)); + } + + /* now we visit all UUIDs that are about to change... */ + g_hash_table_iter_init(&h_iter, loaded_uuids); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) { + NMSIfcfgRHStorage * storage; + NMSettUtilStorageByUuidHead *sbuh; + + sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid); + if (!sbuh) + continue; + + c_list_for_each_entry (storage, + &sbuh->_storage_by_uuid_lst_head, + parent._storage_by_uuid_lst) { + const char * full_filename = nms_ifcfg_rh_storage_get_filename(storage); + gs_unref_object NMSIfcfgRHStorage *storage_new = NULL; + gs_free_error GError *local = NULL; + + if (g_hash_table_contains(dupl_filenames, full_filename)) { + /* already re-loaded. */ + continue; + } + + /* @storage has a UUID that was just loaded from disk, but we have an entry in cache. + * Reload that file too despite not being told to do so. The reason is to get + * the latest file timestamp so that we get the priorities right. */ + + storage_new = _load_file(self, full_filename, &local); + if (storage_new + && !nm_streq0(loaded_uuid, nms_ifcfg_rh_storage_get_uuid_opt(storage_new))) { + /* the file now references a different UUID. We are not told to reload + * that file, so this means the existing storage (with the previous + * filename and UUID tuple) is no longer valid. */ + g_clear_object(&storage_new); + } + + g_hash_table_add(storages_replaced, g_object_ref(storage)); + if (storage_new) + nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new)); + } + } + + nm_clear_pointer(&loaded_uuids, g_hash_table_destroy); + nm_clear_pointer(&dupl_filenames, g_hash_table_destroy); + + _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data); +} + +static void +reload_connections(NMSettingsPlugin * plugin, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(plugin); + nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = + NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy); + + nm_assert_self(self, TRUE); + + _load_dir(self, &storages_new); + + _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data); + + nm_assert_self(self, FALSE); +} + +static void +load_connections_done(NMSettingsPlugin *plugin) +{ + NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); + + /* at the beginning of a load, we emit a change signal for unmanaged/unrecognized + * specs that contain the sum of before and after (_unhandled_specs_merge_storages()). + * + * The idea is that while we emit signals about changes to connection, we have + * the sum of all unmanaged/unrecognized devices from before and after. + * + * This if triggered at the end, to reset the specs. */ + _unhandled_specs_reset(self); + + nm_assert_self(self, TRUE); +} + +/*****************************************************************************/ + +static gboolean +add_connection(NMSettingsPlugin * plugin, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(plugin); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + gs_unref_object NMSIfcfgRHStorage *storage = NULL; + gs_unref_object NMConnection *reread = NULL; + gs_free char * full_filename = NULL; + GError * local = NULL; + gboolean reread_same; + struct timespec mtime; + + nm_assert_self(self, TRUE); + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(out_storage && !*out_storage); + nm_assert(out_connection && !*out_connection); + + if (!nms_ifcfg_rh_writer_write_connection( + connection, + IFCFG_DIR, + NULL, + nm_sett_util_allow_filename_cb, + NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL), + &full_filename, + &reread, + &reread_same, + &local)) { + _LOGT("commit: %s (%s): failed to add: %s", + nm_connection_get_uuid(connection), + nm_connection_get_id(connection), + local->message); + g_propagate_error(error, local); + return FALSE; + } + + if (!reread || reread_same) + nm_g_object_ref_set(&reread, connection); + + nm_assert(full_filename && full_filename[0] == '/'); + + _LOGT("commit: %s (%s) added as \"%s\"", + nm_connection_get_uuid(reread), + nm_connection_get_id(reread), + full_filename); + + storage = + nms_ifcfg_rh_storage_new_connection(self, + full_filename, + g_steal_pointer(&reread), + nm_sett_util_stat_mtime(full_filename, FALSE, &mtime)); + + nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage)); + + *out_connection = nms_ifcfg_rh_storage_steal_connection(storage); + *out_storage = NM_SETTINGS_STORAGE(g_steal_pointer(&storage)); + + nm_assert_self(self, TRUE); + + return TRUE; +} + +static gboolean +update_connection(NMSettingsPlugin * plugin, + NMSettingsStorage * storage_x, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(plugin); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + NMSIfcfgRHStorage * storage = NMS_IFCFG_RH_STORAGE(storage_x); + const char * full_filename; + const char * uuid; + GError * local = NULL; + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same; + struct timespec mtime; + + nm_assert_self(self, TRUE); + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage)); + nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert(!error || !*error); + + uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); + + nm_assert(uuid && nm_streq0(uuid, nm_connection_get_uuid(connection))); + + full_filename = nms_ifcfg_rh_storage_get_filename(storage); + + nm_assert(full_filename); + nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename)); + + if (!nms_ifcfg_rh_writer_write_connection( + connection, + IFCFG_DIR, + full_filename, + nm_sett_util_allow_filename_cb, + NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, full_filename), + NULL, + &reread, + &reread_same, + &local)) { + _LOGT("commit: failure to write %s (%s) to \"%s\": %s", + nm_connection_get_uuid(connection), + nm_connection_get_id(connection), + full_filename, + local->message); + g_propagate_error(error, local); + return FALSE; + } + + if (!reread || reread_same) + nm_g_object_ref_set(&reread, connection); + + _LOGT("commit: \"%s\": profile %s (%s) written", + full_filename, + uuid, + nm_connection_get_id(connection)); + + storage->stat_mtime = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime); + + *out_storage = NM_SETTINGS_STORAGE(g_object_ref(storage)); + *out_connection = g_steal_pointer(&reread); + + nm_assert_self(self, TRUE); + + return TRUE; +} + +static gboolean +delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(plugin); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + NMSIfcfgRHStorage * storage = NMS_IFCFG_RH_STORAGE(storage_x); + const char * operation_message; + const char * full_filename; + + nm_assert_self(self, TRUE); + nm_assert(!error || !*error); + nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage)); + + full_filename = nms_ifcfg_rh_storage_get_filename(storage); + nm_assert(full_filename); + + nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage)); + + nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename)); + + { + gs_free char * keyfile = utils_get_keys_path(full_filename); + gs_free char * routefile = utils_get_route_path(full_filename); + gs_free char * route6file = utils_get_route6_path(full_filename); + const char *const files[] = {full_filename, keyfile, routefile, route6file}; + gboolean any_deleted = FALSE; + gboolean any_failure = FALSE; + int i; + + for (i = 0; i < G_N_ELEMENTS(files); i++) { + int errsv; + + if (unlink(files[i]) == 0) { + any_deleted = TRUE; + continue; + } + errsv = errno; + if (errsv == ENOENT) + continue; + + _LOGW("commit: failure to delete file \"%s\": %s", files[i], nm_strerror_native(errsv)); + any_failure = TRUE; + } + if (any_failure) + operation_message = "failed to delete files from disk"; + else if (any_deleted) + operation_message = "deleted from disk"; + else + operation_message = "does not exist on disk"; + } + + _LOGT("commit: deleted \"%s\", profile %s (%s)", + full_filename, + nms_ifcfg_rh_storage_get_uuid_opt(storage), + operation_message); + + nm_sett_util_storages_steal(&priv->storages, storage); + nms_ifcfg_rh_storage_destroy(storage); + + nm_assert_self(self, TRUE); + + return TRUE; +} + +/*****************************************************************************/ + +static void +_unhandled_specs_reset(NMSIfcfgRHPlugin *self) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *unmanaged_specs = NULL; + gs_unref_hashtable GHashTable *unrecognized_specs = NULL; + NMSIfcfgRHStorage * storage; + + unmanaged_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + + c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) { + if (storage->unmanaged_spec) + g_hash_table_add(unmanaged_specs, g_strdup(storage->unmanaged_spec)); + if (storage->unrecognized_spec) + g_hash_table_add(unrecognized_specs, g_strdup(storage->unrecognized_spec)); + } + + if (!nm_utils_hashtable_same_keys(unmanaged_specs, priv->unmanaged_specs)) { + g_hash_table_unref(priv->unmanaged_specs); + priv->unmanaged_specs = g_steal_pointer(&unmanaged_specs); + } + if (!nm_utils_hashtable_same_keys(unrecognized_specs, priv->unrecognized_specs)) { + g_hash_table_unref(priv->unrecognized_specs); + priv->unrecognized_specs = g_steal_pointer(&unrecognized_specs); + } + + if (!unmanaged_specs) + _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self)); + if (!unrecognized_specs) + _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self)); +} + +static void +_unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + gboolean unmanaged_changed = FALSE; + gboolean unrecognized_changed = FALSE; + NMSIfcfgRHStorage * storage; + + c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) { + if (storage->unmanaged_spec + && !g_hash_table_contains(priv->unmanaged_specs, storage->unmanaged_spec)) { + unmanaged_changed = TRUE; + g_hash_table_add(priv->unmanaged_specs, g_strdup(storage->unmanaged_spec)); + } + if (storage->unrecognized_spec + && !g_hash_table_contains(priv->unrecognized_specs, storage->unrecognized_spec)) { + unrecognized_changed = TRUE; + g_hash_table_add(priv->unrecognized_specs, g_strdup(storage->unrecognized_spec)); + } + } + + if (unmanaged_changed) + _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self)); + if (unrecognized_changed) + _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self)); +} + +static GSList * +_unhandled_specs_from_hashtable(GHashTable *hash) +{ + gs_free const char **keys = NULL; + GSList * list = NULL; + guint i, l; + + keys = nm_utils_strdict_get_keys(hash, TRUE, &l); + for (i = l; i > 0;) { + i--; + list = g_slist_prepend(list, g_strdup(keys[i])); + } + return list; +} + +static GSList * +get_unmanaged_specs(NMSettingsPlugin *plugin) +{ + return _unhandled_specs_from_hashtable( + NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unmanaged_specs); +} + +static GSList * +get_unrecognized_specs(NMSettingsPlugin *plugin) +{ + return _unhandled_specs_from_hashtable( + NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unrecognized_specs); +} + +/*****************************************************************************/ + +static void +impl_ifcfgrh_get_ifcfg_details(NMSIfcfgRHPlugin * self, + GDBusMethodInvocation *context, + const char * in_ifcfg) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + gs_free char * ifcfg_path = NULL; + NMSIfcfgRHStorage * storage; + const char * uuid; + const char * path; + + if (in_ifcfg[0] != '/') { + g_dbus_method_invocation_return_error(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "ifcfg path '%s' is not absolute", + in_ifcfg); + return; + } + + ifcfg_path = utils_detect_ifcfg_path(in_ifcfg, TRUE); + if (!ifcfg_path) { + g_dbus_method_invocation_return_error(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "ifcfg path '%s' is not an ifcfg base file", + in_ifcfg); + return; + } + + storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, ifcfg_path); + if (!storage) { + g_dbus_method_invocation_return_error(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "ifcfg file '%s' unknown", + in_ifcfg); + return; + } + + uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); + if (!uuid) { + g_dbus_method_invocation_return_error(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "ifcfg file '%s' not managed by NetworkManager", + in_ifcfg); + return; + } + + /* It is ugly that the ifcfg-rh plugin needs to call back into NMSettings this + * way. + * There are alternatives (like invoking a signal), but they are all significant + * extra code (and performance overhead). So the quick and dirty solution here + * is likely to be simpler than getting this right (also from point of readability!). + */ + path = nm_settings_get_dbus_path_for_uuid(nm_settings_get(), uuid); + + if (!path) { + g_dbus_method_invocation_return_error(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "unable to get the connection D-Bus path"); + return; + } + + g_dbus_method_invocation_return_value(context, g_variant_new("(so)", uuid, path)); +} + +/*****************************************************************************/ + +static void +_dbus_clear(NMSIfcfgRHPlugin *self) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + guint id; + + nm_clear_g_signal_handler(priv->dbus.connection, &priv->dbus.signal_id); + + nm_clear_g_cancellable(&priv->dbus.cancellable); + + if ((id = nm_steal_int(&priv->dbus.regist_id))) { + if (!g_dbus_connection_unregister_object(priv->dbus.connection, id)) + _LOGW("dbus: unexpected failure to unregister object"); + } + + g_clear_object(&priv->dbus.connection); +} + +static void +_dbus_connection_closed(GDBusConnection *connection, + gboolean remote_peer_vanished, + GError * error, + gpointer user_data) +{ + _LOGW("dbus: %s bus closed", IFCFGRH1_BUS_NAME); + _dbus_clear(NMS_IFCFG_RH_PLUGIN(user_data)); + + /* Retry or recover? */ +} + +static void +_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(user_data); + + if (nm_streq(interface_name, IFCFGRH1_IFACE1_NAME)) { + if (nm_streq(method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) { + const char *ifcfg; + + g_variant_get(parameters, "(&s)", &ifcfg); + impl_ifcfgrh_get_ifcfg_details(self, invocation, ifcfg); + return; + } + } + + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); +} + +static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( + IFCFGRH1_IFACE1_NAME, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO( + IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS, + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("ifcfg", "s"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("uuid", "s"), + NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ), ), ); + +static void +_dbus_request_name_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GDBusConnection * connection = G_DBUS_CONNECTION(source_object); + NMSIfcfgRHPlugin * self; + NMSIfcfgRHPluginPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + guint32 result; + + ret = g_dbus_connection_call_finish(connection, res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NMS_IFCFG_RH_PLUGIN(user_data); + priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + + g_clear_object(&priv->dbus.cancellable); + + if (!ret) { + _LOGW("dbus: couldn't acquire D-Bus service: %s", error->message); + _dbus_clear(self); + return; + } + + g_variant_get(ret, "(u)", &result); + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOGW("dbus: couldn't acquire ifcfgrh1 D-Bus service (already taken)"); + _dbus_clear(self); + return; + } + + { + static const GDBusInterfaceVTable interface_vtable = { + .method_call = _method_call, + }; + + priv->dbus.regist_id = g_dbus_connection_register_object( + connection, + IFCFGRH1_OBJECT_PATH, + interface_info, + NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable), + self, + NULL, + &error); + if (!priv->dbus.regist_id) { + _LOGW("dbus: couldn't register D-Bus service: %s", error->message); + _dbus_clear(self); + return; + } + } + + _LOGD("dbus: acquired D-Bus service %s and exported %s object", + IFCFGRH1_BUS_NAME, + IFCFGRH1_OBJECT_PATH); +} + +static void +_dbus_create_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMSIfcfgRHPlugin * self; + NMSIfcfgRHPluginPrivate *priv; + gs_free_error GError *error = NULL; + GDBusConnection * connection; + + connection = g_dbus_connection_new_for_address_finish(res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NMS_IFCFG_RH_PLUGIN(user_data); + priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + + g_clear_object(&priv->dbus.cancellable); + + if (!connection) { + _LOGW("dbus: couldn't initialize system bus: %s", error->message); + return; + } + + priv->dbus.connection = connection; + priv->dbus.cancellable = g_cancellable_new(); + + priv->dbus.signal_id = g_signal_connect(priv->dbus.connection, + "closed", + G_CALLBACK(_dbus_connection_closed), + self); + + g_dbus_connection_call(priv->dbus.connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName", + g_variant_new("(su)", IFCFGRH1_BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->dbus.cancellable, + _dbus_request_name_done, + self); +} + +static void +_dbus_setup(NMSIfcfgRHPlugin *self) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + gs_free char * address = NULL; + gs_free_error GError *error = NULL; + + _dbus_clear(self); + + if (!NM_MAIN_DBUS_CONNECTION_GET) { + _LOGW("dbus: don't use D-Bus for %s service", IFCFGRH1_BUS_NAME); + return; + } + + /* We use a separate D-Bus connection so that org.freedesktop.NetworkManager and com.redhat.ifcfgrh1 + * are exported by different connections. */ + address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (address == NULL) { + _LOGW("dbus: failed getting address for system bus: %s", error->message); + return; + } + + priv->dbus.cancellable = g_cancellable_new(); + + g_dbus_connection_new_for_address(address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT + | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, + priv->dbus.cancellable, + _dbus_create_done, + self); +} + +static void +config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMSIfcfgRHPlugin * self) +{ + NMSIfcfgRHPluginPrivate *priv; + + /* If the dbus connection for some reason is borked the D-Bus service + * won't be offered. + * + * On SIGHUP and SIGUSR1 try to re-connect to D-Bus. So in the unlikely + * event that the D-Bus connection is broken, that allows for recovery + * without need for restarting NetworkManager. */ + if (!NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_SIGUSR1)) + return; + + priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + if (!priv->dbus.connection && !priv->dbus.cancellable) + _dbus_setup(self); +} + +/*****************************************************************************/ + +static void +nms_ifcfg_rh_plugin_init(NMSIfcfgRHPlugin *self) +{ + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + + priv->config = g_object_ref(nm_config_get()); + + priv->unmanaged_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + priv->unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + + priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages, + nms_ifcfg_rh_storage_destroy); +} + +static void +constructed(GObject *object) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(object); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + + G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->constructed(object); + + g_signal_connect(priv->config, + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed_cb), + self); + + _dbus_setup(self); +} + +static void +dispose(GObject *object) +{ + NMSIfcfgRHPlugin * self = NMS_IFCFG_RH_PLUGIN(object); + NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); + + if (priv->config) + g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); + + /* FIXME(shutdown) we need a stop method so that we can unregistering the D-Bus service + * when NMSettings is shutting down, and not when the instance gets destroyed. */ + _dbus_clear(self); + + nm_sett_util_storages_clear(&priv->storages); + + g_clear_object(&priv->config); + + G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->dispose(object); + + nm_clear_pointer(&priv->unmanaged_specs, g_hash_table_destroy); + nm_clear_pointer(&priv->unrecognized_specs, g_hash_table_destroy); +} + +static void +nms_ifcfg_rh_plugin_class_init(NMSIfcfgRHPluginClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + plugin_class->plugin_name = "ifcfg-rh"; + plugin_class->get_unmanaged_specs = get_unmanaged_specs; + plugin_class->get_unrecognized_specs = get_unrecognized_specs; + plugin_class->reload_connections = reload_connections; + plugin_class->load_connections = load_connections; + plugin_class->load_connections_done = load_connections_done; + plugin_class->add_connection = add_connection; + plugin_class->update_connection = update_connection; + plugin_class->delete_connection = delete_connection; +} + +/*****************************************************************************/ + +G_MODULE_EXPORT NMSettingsPlugin * + nm_settings_plugin_factory(void) +{ + return g_object_new(NMS_TYPE_IFCFG_RH_PLUGIN, NULL); +} diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h new file mode 100644 index 0000000..dc1a01c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Dan Williams + * Søren Sandmann + * Copyright (C) 2007 - 2008 Red Hat, Inc. + */ + +#ifndef __NMS_IFCFG_RH_PLUGIN_H__ +#define __NMS_IFCFG_RH_PLUGIN_H__ + +#define NMS_TYPE_IFCFG_RH_PLUGIN (nms_ifcfg_rh_plugin_get_type()) +#define NMS_IFCFG_RH_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPlugin)) +#define NMS_IFCFG_RH_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass)) +#define NMS_IS_IFCFG_RH_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFCFG_RH_PLUGIN)) +#define NMS_IS_IFCFG_RH_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFCFG_RH_PLUGIN)) +#define NMS_IFCFG_RH_PLUGIN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass)) + +typedef struct _NMSIfcfgRHPlugin NMSIfcfgRHPlugin; +typedef struct _NMSIfcfgRHPluginClass NMSIfcfgRHPluginClass; + +GType nms_ifcfg_rh_plugin_get_type(void); + +#endif /* __NMS_IFCFG_RH_PLUGIN_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c new file mode 100644 index 0000000..bce47ef --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -0,0 +1,6642 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifcfg-rh-reader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-connection.h" +#include "nm-dbus-interface.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-vlan.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-8021x.h" +#include "nm-setting-bond.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" +#include "nm-setting-bridge.h" +#include "nm-setting-bridge-port.h" +#include "nm-setting-dcb.h" +#include "nm-setting-user.h" +#include "nm-setting-proxy.h" +#include "nm-setting-generic.h" +#include "nm-core-internal.h" +#include "nm-utils.h" +#include "nm-base/nm-ethtool-base.h" + +#include "platform/nm-platform.h" +#include "NetworkManagerUtils.h" + +#include "nms-ifcfg-rh-common.h" +#include "nms-ifcfg-rh-utils.h" +#include "shvar.h" + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG_PREFIX_NAME "ifcfg-rh" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define PARSE_WARNING(...) \ + _LOGW("%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), " " _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static char * +get_full_file_path(const char *ifcfg_path, const char *file_path) +{ + const char * base = file_path; + gs_free char *dirname = NULL; + char * p; + + g_return_val_if_fail(ifcfg_path != NULL, NULL); + g_return_val_if_fail(file_path != NULL, NULL); + + if (file_path[0] == '/') + return g_strdup(file_path); + + p = strrchr(file_path, '/'); + if (p) + base = p + 1; + + dirname = g_path_get_dirname(ifcfg_path); + return g_build_path("/", dirname, base, NULL); +} + +/*****************************************************************************/ + +static NMSettingSecretFlags +_secret_read_ifcfg_flags(shvarFile *ifcfg, const char *flags_key) +{ + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + gs_free char * val_free = NULL; + const char * val; + + nm_assert(flags_key); + nm_assert(g_str_has_suffix(flags_key, "_FLAGS")); + + val = svGetValueStr(ifcfg, flags_key, &val_free); + if (val) { + if (strstr(val, SECRET_FLAG_AGENT)) + flags |= NM_SETTING_SECRET_FLAG_AGENT_OWNED; + if (strstr(val, SECRET_FLAG_NOT_SAVED)) + flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED; + if (strstr(val, SECRET_FLAG_NOT_REQUIRED)) + flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED; + } + return flags; +} + +static void +_secret_read_ifcfg(shvarFile * ifcfg, + shvarFile * keys_ifcfg, + const char * name, + char ** value, + NMSettingSecretFlags *flags) +{ + char flags_key[250]; + + nm_sprintf_buf(flags_key, "%s_FLAGS", name); + + *flags = _secret_read_ifcfg_flags(ifcfg, flags_key); + + if (*flags != NM_SETTING_SECRET_FLAG_NONE) + *value = NULL; + else { + *value = svGetValue_cp(ifcfg, name); + if (!*value && keys_ifcfg) + *value = svGetValue_cp(keys_ifcfg, name); + } +} + +static void +_secret_set_from_ifcfg(gpointer setting, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + const char *ifcfg_key, + const char *property_name) +{ + nm_auto_free_secret char *secret = NULL; + NMSettingSecretFlags flags; + char flags_key[250]; + + nm_assert(NM_IS_SETTING(setting)); + + _secret_read_ifcfg(ifcfg, keys_ifcfg, ifcfg_key, &secret, &flags); + + g_object_set(setting, + property_name, + secret, + nm_sprintf_buf(flags_key, "%s-flags", property_name), + flags, + NULL); +} + +static gboolean +_secret_password_raw_to_bytes(const char *ifcfg_key, + const char *password_raw, + GBytes ** out_bytes, + GError ** error) +{ + nm_auto_free_secret_buf NMSecretBuf *secret = NULL; + gsize len; + + if (!password_raw) { + NM_SET_OUT(out_bytes, NULL); + return TRUE; + } + + if (password_raw[0] == '0' && password_raw[1] == 'x') + password_raw += 2; + + secret = nm_secret_buf_new(strlen(password_raw) / 2 + 3); + if (!nm_utils_hexstr2bin_full(password_raw, + FALSE, + FALSE, + FALSE, + ":", + 0, + secret->bin, + secret->len, + &len)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid hex password in %s", + ifcfg_key); + return FALSE; + } + + NM_SET_OUT(out_bytes, nm_secret_buf_to_gbytes_take(g_steal_pointer(&secret), len)); + return TRUE; +} + +/*****************************************************************************/ + +static GBytes * +_cert_get_cert_bytes(const char *ifcfg_path, const char *value, GError **error) +{ + gs_free char *path = NULL; + + if (NM_STR_HAS_PREFIX(value, "pkcs11:")) + return _nm_setting_802_1x_cert_value_to_bytes(NM_SETTING_802_1X_CK_SCHEME_PKCS11, + (guint8 *) value, + -1, + error); + + path = get_full_file_path(ifcfg_path, value); + return _nm_setting_802_1x_cert_value_to_bytes(NM_SETTING_802_1X_CK_SCHEME_PATH, + (guint8 *) path, + -1, + error); +} + +static gboolean +_cert_get_cert(shvarFile * ifcfg, + const char * ifcfg_key, + GBytes ** out_cert, + NMSetting8021xCKScheme *out_scheme, + GError ** error) +{ + nm_auto_free_secret char *val_free = NULL; + const char * val; + gs_unref_bytes GBytes *cert = NULL; + GError * local = NULL; + NMSetting8021xCKScheme scheme; + + val = svGetValueStr(ifcfg, ifcfg_key, &val_free); + if (!val) { + NM_SET_OUT(out_cert, NULL); + NM_SET_OUT(out_scheme, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); + return TRUE; + } + + cert = _cert_get_cert_bytes(svFileGetName(ifcfg), val, &local); + if (!cert) + goto err; + + scheme = _nm_setting_802_1x_cert_get_scheme(cert, &local); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) + goto err; + + NM_SET_OUT(out_cert, g_steal_pointer(&cert)); + NM_SET_OUT(out_scheme, scheme); + return TRUE; + +err: + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid certificate %s: %s", + ifcfg_key, + local->message); + g_error_free(local); + return FALSE; +} + +static gboolean +_cert_set_from_ifcfg(gpointer setting, + shvarFile * ifcfg, + const char *ifcfg_key, + const char *property_name, + GBytes ** out_cert, + GError ** error) +{ + gs_unref_bytes GBytes *cert = NULL; + + if (!_cert_get_cert(ifcfg, ifcfg_key, &cert, NULL, error)) + return FALSE; + + g_object_set(setting, property_name, cert, NULL); + + NM_SET_OUT(out_cert, g_steal_pointer(&cert)); + return TRUE; +} + +/*****************************************************************************/ + +static void +check_if_bond_slave(shvarFile *ifcfg, NMSettingConnection *s_con) +{ + gs_free char *value = NULL; + const char * v; + const char * master; + + v = svGetValueStr(ifcfg, "MASTER_UUID", &value); + if (!v) + v = svGetValueStr(ifcfg, "MASTER", &value); + + if (v) { + master = nm_setting_connection_get_master(s_con); + if (master) { + PARSE_WARNING("Already configured as slave of %s. Ignoring MASTER{_UUID}=\"%s\"", + master, + v); + return; + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + v, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BOND_SETTING_NAME, + NULL); + } + + /* We should be checking for SLAVE=yes as well, but NM used to not set that, + * so for backward-compatibility, we don't check. + */ +} + +static void +check_if_team_slave(shvarFile *ifcfg, NMSettingConnection *s_con) +{ + gs_free char *value = NULL; + const char * v; + const char * master; + + v = svGetValueStr(ifcfg, "TEAM_MASTER_UUID", &value); + if (!v) + v = svGetValueStr(ifcfg, "TEAM_MASTER", &value); + if (!v) + return; + + master = nm_setting_connection_get_master(s_con); + if (master) { + PARSE_WARNING("Already configured as slave of %s. Ignoring TEAM_MASTER{_UUID}=\"%s\"", + master, + v); + return; + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + v, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_TEAM_SETTING_NAME, + NULL); +} + +static char * +make_connection_name(shvarFile * ifcfg, + const char *ifcfg_name, + const char *suggested, + const char *prefix) +{ + char *full_name = NULL, *name; + + /* If the ifcfg file already has a NAME, always use that */ + name = svGetValueStr_cp(ifcfg, "NAME"); + if (name) + return name; + + /* Otherwise, construct a new NAME */ + if (!prefix) + prefix = "System"; + + /* For cosmetic reasons, if the suggested name is the same as + * the ifcfg files name, don't use it. Mainly for wifi so that + * the SSID is shown in the connection ID instead of just "wlan0". + */ + if (suggested && strcmp(ifcfg_name, suggested)) + full_name = g_strdup_printf("%s %s (%s)", prefix, suggested, ifcfg_name); + else + full_name = g_strdup_printf("%s %s", prefix, ifcfg_name); + + return full_name; +} + +static NMSetting * +make_connection_setting(const char *file, + shvarFile * ifcfg, + const char *type, + const char *suggested, + const char *prefix) +{ + NMSettingConnection * s_con; + NMSettingConnectionLldp lldp; + const char * ifcfg_name = NULL; + gs_free char * new_id = NULL; + const char * uuid; + gs_free char * uuid_free = NULL; + gs_free char * value = NULL; + const char * v; + gs_free char * stable_id = NULL; + const char *const * iter; + int vint64, i_val; + + ifcfg_name = utils_get_ifcfg_name(file, TRUE); + if (!ifcfg_name) + return NULL; + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + + new_id = make_connection_name(ifcfg, ifcfg_name, suggested, prefix); + g_object_set(s_con, NM_SETTING_CONNECTION_ID, new_id, NULL); + + /* Try for a UUID key before falling back to hashing the file name */ + uuid = svGetValueStr(ifcfg, "UUID", &uuid_free); + if (!uuid) { + uuid_free = nm_utils_uuid_generate_from_string(svFileGetName(ifcfg), + -1, + NM_UTILS_UUID_TYPE_LEGACY, + NULL); + uuid = uuid_free; + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_TYPE, + type, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_STABLE_ID, + svGetValue(ifcfg, "STABLE_ID", &stable_id), + NULL); + + v = svGetValueStr(ifcfg, "DEVICE", &value); + if (v) { + GError *error = NULL; + + /* Only validate for NMU_IFACE_KERNEL, because ifcfg plugin anyway + * doesn't support OVS types. */ + if (nm_utils_ifname_valid(v, NMU_IFACE_KERNEL, &error)) { + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, v, NULL); + } else { + PARSE_WARNING("invalid DEVICE name '%s': %s", v, error->message); + g_error_free(error); + } + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "LLDP", &value); + if (nm_streq0(v, "rx")) + lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX; + else + lldp = svParseBoolean(v, NM_SETTING_CONNECTION_LLDP_DEFAULT); + + /* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */ + g_object_set(s_con, + NM_SETTING_CONNECTION_AUTOCONNECT, + svGetValueBoolean(ifcfg, "ONBOOT", TRUE), + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + (int) svGetValueInt64(ifcfg, + "AUTOCONNECT_PRIORITY", + 10, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT), + NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, + (int) svGetValueInt64(ifcfg, "AUTOCONNECT_RETRIES", 10, -1, G_MAXINT32, -1), + NM_SETTING_CONNECTION_MULTI_CONNECT, + (int) svGetValueInt64(ifcfg, + "MULTI_CONNECT", + 10, + G_MININT32, + G_MAXINT32, + NM_CONNECTION_MULTI_CONNECT_DEFAULT), + NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, + svGetValueBoolean(ifcfg, + "AUTOCONNECT_SLAVES", + NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT), + NM_SETTING_CONNECTION_LLDP, + lldp, + NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "USERS", &value); + if (v) { + gs_free const char **items = NULL; + + items = nm_utils_strsplit_set(v, " "); + for (iter = items; iter && *iter; iter++) { + if (!nm_setting_connection_add_permission(s_con, "user", *iter, NULL)) + PARSE_WARNING("invalid USERS item '%s'", *iter); + } + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "ZONE", &value); + g_object_set(s_con, NM_SETTING_CONNECTION_ZONE, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "SECONDARY_UUIDS", &value); + if (v) { + gs_free const char **items = NULL; + + items = nm_utils_strsplit_set(v, " \t"); + for (iter = items; iter && *iter; iter++) { + if (!nm_setting_connection_add_secondary(s_con, *iter)) + PARSE_WARNING("secondary connection UUID '%s' already added", *iter); + } + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "BRIDGE_UUID", &value); + if (!v) + v = svGetValueStr(ifcfg, "BRIDGE", &value); + if (v) { + const char *old_value; + + if ((old_value = nm_setting_connection_get_master(s_con))) { + PARSE_WARNING("Already configured as slave of %s. Ignoring BRIDGE=\"%s\"", + old_value, + v); + } else { + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL); + g_object_set(s_con, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + } + } + + check_if_bond_slave(ifcfg, s_con); + check_if_team_slave(ifcfg, s_con); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "OVS_PORT_UUID", &value); + if (!v) + v = svGetValueStr(ifcfg, "OVS_PORT", &value); + if (v) { + const char *old_value; + + if ((old_value = nm_setting_connection_get_master(s_con))) { + PARSE_WARNING("Already configured as slave of %s. Ignoring OVS_PORT=\"%s\"", + old_value, + v); + } else { + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL); + g_object_set(s_con, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + } + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "VRF_UUID", &value); + if (!v) + v = svGetValueStr(ifcfg, "VRF", &value); + if (v) { + const char *old_value; + + if ((old_value = nm_setting_connection_get_master(s_con))) { + PARSE_WARNING("Already configured as slave of %s. Ignoring VRF{_UUID}=\"%s\"", + old_value, + v); + } else { + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL); + g_object_set(s_con, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_VRF_SETTING_NAME, + NULL); + } + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "GATEWAY_PING_TIMEOUT", &value); + if (v) { + gint64 tmp; + + tmp = _nm_utils_ascii_str_to_int64(v, 10, 0, G_MAXINT32 - 1, -1); + if (tmp >= 0) { + if (tmp > 600) { + tmp = 600; + PARSE_WARNING("invalid GATEWAY_PING_TIMEOUT time"); + } + g_object_set(s_con, NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, (guint) tmp, NULL); + } else + PARSE_WARNING("invalid GATEWAY_PING_TIMEOUT time"); + } + + switch (svGetValueBoolean(ifcfg, "CONNECTION_METERED", -1)) { + case TRUE: + g_object_set(s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_YES, NULL); + break; + case FALSE: + g_object_set(s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_NO, NULL); + break; + } + + vint64 = svGetValueInt64(ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1); + g_object_set(s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL); + + nm_clear_g_free(&value); + v = svGetValue(ifcfg, "DEVTIMEOUT", &value); + if (v) { + v = nm_str_skip_leading_spaces(v); + vint64 = _nm_utils_ascii_str_to_int64(v, 10, 0, ((gint64) G_MAXINT32) / 1000, -1); + if (vint64 != -1) + vint64 *= 1000; + else if (v[0] != '\0') { + char * endptr; + double d; + + d = nm_g_ascii_strtod(v, &endptr); + endptr = nm_str_skip_leading_spaces(endptr); + if (errno == 0 && endptr[0] == '\0' && d >= 0.0) { + d *= 1000.0; + + /* We round. Yes, this is not correct to round IEEE 754 floats in general, + * but sufficient for our case where we know that NetworkManager wrote the + * setting with up to 3 digits for the milliseconds. */ + d += 0.5; + if (d >= 0.0 && d <= (double) G_MAXINT32) + vint64 = (gint64) d; + } + } + if (vint64 == -1) + PARSE_WARNING("invalid DEVTIMEOUT setting"); + else + g_object_set(s_con, NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) vint64, NULL); + } + + nm_clear_g_free(&value); + v = svGetValue(ifcfg, "MUD_URL", &value); + if (v) + g_object_set(s_con, NM_SETTING_CONNECTION_MUD_URL, v, NULL); + + i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT; + if (!svGetValueEnum(ifcfg, "MDNS", nm_setting_connection_mdns_get_type(), &i_val, NULL)) + PARSE_WARNING("invalid MDNS setting"); + g_object_set(s_con, NM_SETTING_CONNECTION_MDNS, i_val, NULL); + + i_val = NM_SETTING_CONNECTION_LLMNR_DEFAULT; + if (!svGetValueEnum(ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type(), &i_val, NULL)) + PARSE_WARNING("invalid LLMNR setting"); + g_object_set(s_con, NM_SETTING_CONNECTION_LLMNR, i_val, NULL); + + return NM_SETTING(s_con); +} + +static gboolean +read_ip4_address(shvarFile * ifcfg, + const char *tag, + gboolean * out_has_key, + guint32 * out_addr, + GError ** error) +{ + gs_free char *value_to_free = NULL; + const char * value; + in_addr_t a; + + nm_assert(ifcfg); + nm_assert(tag); + nm_assert(!error || !*error); + + value = svGetValueStr(ifcfg, tag, &value_to_free); + if (!value) { + NM_SET_OUT(out_has_key, FALSE); + NM_SET_OUT(out_addr, 0); + return TRUE; + } + + if (inet_pton(AF_INET, value, &a) != 1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid %s IP4 address '%s'", + tag, + value); + return FALSE; + } + + NM_SET_OUT(out_has_key, TRUE); + NM_SET_OUT(out_addr, a); + return TRUE; +} + +static gboolean +is_any_ip4_address_defined(shvarFile *ifcfg, int *idx) +{ + int i, ignore, *ret_idx; + + ret_idx = idx ?: &ignore; + + for (i = -1; i <= 2; i++) { + gs_free char *value = NULL; + char tag[256]; + + if (svGetValueStr(ifcfg, numbered_tag(tag, "IPADDR", i), &value)) { + *ret_idx = i; + return TRUE; + } + + if (svGetValueStr(ifcfg, numbered_tag(tag, "PREFIX", i), &value)) { + *ret_idx = i; + return TRUE; + } + + if (svGetValueStr(ifcfg, numbered_tag(tag, "NETMASK", i), &value)) { + *ret_idx = i; + return TRUE; + } + } + return FALSE; +} + +/* Returns TRUE on missing address or valid address */ +static gboolean +read_full_ip4_address(shvarFile * ifcfg, + gint32 which, + NMIPAddress * base_addr, + NMIPAddress **out_address, + char ** out_gateway, + GError ** error) +{ + char tag[256]; + char prefix_tag[256]; + guint32 ipaddr; + gs_free char *value = NULL; + const char * v; + int prefix = 0; + gboolean has_key; + guint32 a; + char inet_buf[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_val_if_fail(which >= -1, FALSE); + g_return_val_if_fail(ifcfg != NULL, FALSE); + g_return_val_if_fail(out_address != NULL, FALSE); + g_return_val_if_fail(*out_address == NULL, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* IP address */ + if (!read_ip4_address(ifcfg, numbered_tag(tag, "IPADDR", which), &has_key, &ipaddr, error)) + return FALSE; + if (!has_key) { + if (!base_addr) + return TRUE; + nm_ip_address_get_address_binary(base_addr, &ipaddr); + } + + /* Gateway */ + if (out_gateway && !*out_gateway) { + if (!read_ip4_address(ifcfg, numbered_tag(tag, "GATEWAY", which), &has_key, &a, error)) + return FALSE; + if (has_key) + *out_gateway = nm_utils_inet4_ntop_dup(a); + } + + /* Prefix */ + numbered_tag(prefix_tag, "PREFIX", which); + v = svGetValueStr(ifcfg, prefix_tag, &value); + if (v) { + prefix = _nm_utils_ascii_str_to_int64(v, 10, 0, 32, -1); + if (prefix < 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IP4 prefix '%s'", + v); + return FALSE; + } + } else { + /* Fall back to NETMASK if no PREFIX was specified */ + if (!read_ip4_address(ifcfg, numbered_tag(tag, "NETMASK", which), &has_key, &a, error)) + return FALSE; + if (has_key) + prefix = nm_utils_ip4_netmask_to_prefix(a); + else { + if (base_addr) + prefix = nm_ip_address_get_prefix(base_addr); + else { + /* Try to autodetermine the prefix for the address' class */ + prefix = _nm_utils_ip4_get_default_prefix(ipaddr); + PARSE_WARNING("missing %s, assuming %s/%d", + prefix_tag, + _nm_utils_inet4_ntop(ipaddr, inet_buf), + prefix); + } + } + } + + *out_address = nm_ip_address_new_binary(AF_INET, &ipaddr, prefix, error); + if (*out_address) + return TRUE; + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +parse_route_line_is_comment(const char *line) +{ + /* we obtained the line from a legacy route file. Here we skip + * empty lines and comments. + * + * initscripts compares: "$line" =~ '^[[:space:]]*(\#.*)?$' + */ + while (nm_utils_is_separator(line[0])) + line++; + if (NM_IN_SET(line[0], '\0', '#')) + return TRUE; + return FALSE; +} + +/*****************************************************************************/ + +typedef enum { + PARSE_LINE_AF_FLAG_FOR_IPV4 = 0x01, + PARSE_LINE_AF_FLAG_FOR_IPV6 = 0x02, +} ParseLineAFFlag; + +typedef struct { + const char *key; + + /* the element is not available in this case. */ + ParseLineAFFlag disabled : 3; + + bool disabled_with_options_route : 1; + + /* whether the element is to be ignored. Ignord is different from + * "disabled", because we still parse the option, but don't use it. */ + ParseLineAFFlag ignore : 3; + + bool int_base_16 : 1; + + /* the type, one of PARSE_LINE_TYPE_* */ + char type; + +} ParseLineInfo; + +typedef struct { + /* whether the command line option was found, and @v is + * initialized. */ + bool has : 1; + + union { + guint8 uint8; + guint32 uint32; + const char *str; + struct { + guint32 uint32; + bool lock : 1; + } uint32_with_lock; + struct { + NMIPAddr addr; + guint8 plen; + bool has_plen : 1; + } addr; + } v; + +} ParseLineData; + +enum { + /* route attributes */ + PARSE_LINE_ATTR_ROUTE_TYPE, + PARSE_LINE_ATTR_ROUTE_TABLE, + PARSE_LINE_ATTR_ROUTE_SRC, + PARSE_LINE_ATTR_ROUTE_FROM, + PARSE_LINE_ATTR_ROUTE_TOS, + PARSE_LINE_ATTR_ROUTE_SCOPE, + PARSE_LINE_ATTR_ROUTE_ONLINK, + PARSE_LINE_ATTR_ROUTE_WINDOW, + PARSE_LINE_ATTR_ROUTE_CWND, + PARSE_LINE_ATTR_ROUTE_INITCWND, + PARSE_LINE_ATTR_ROUTE_INITRWND, + PARSE_LINE_ATTR_ROUTE_MTU, + + /* iproute2 arguments that only matter when parsing the file. */ + PARSE_LINE_ATTR_ROUTE_TO, + PARSE_LINE_ATTR_ROUTE_VIA, + PARSE_LINE_ATTR_ROUTE_METRIC, + + /* iproute2 parameters that are well known and that we silently ignore. */ + PARSE_LINE_ATTR_ROUTE_DEV, +}; + +#define PARSE_LINE_TYPE_UINT8 '8' +#define PARSE_LINE_TYPE_UINT32 'u' +#define PARSE_LINE_TYPE_UINT32_WITH_LOCK 'l' +#define PARSE_LINE_TYPE_ADDR 'a' +#define PARSE_LINE_TYPE_ADDR_WITH_PREFIX 'p' +#define PARSE_LINE_TYPE_IFNAME 'i' +#define PARSE_LINE_TYPE_FLAG 'f' +#define PARSE_LINE_TYPE_ROUTE_SCOPE 'S' +#define PARSE_LINE_TYPE_STRING 's' + +/** + * parse_route_line: + * @line: the line to parse. This is either a line from the route-* or route6-* file, + * or the numbered OPTIONS setting. + * @addr_family: the address family. + * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created + * route object that is completed with the settings from options. Otherwise, + * it shall point to %NULL and a new route is created and returned. + * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance. + * In case a @options_route is passed in, it returns the input route that was modified + * in-place. But the caller must unref the returned route in either case. + * @error: the failure description. + * + * Parsing the route options line has two modes: one for the numbered OPTIONS + * setting, and one for initscript's handle_ip_file(), which takes the lines + * and passes them to `ip route add`. The modes are similar, but certain properties + * are not allowed for OPTIONS. + * The mode is differentiated by having an @options_route argument. + * + * Returns: returns a negative errno on failure. On success, it returns 0 + * and @out_route. + */ +static int +parse_route_line(const char *line, + int addr_family, + NMIPRoute * options_route, + NMIPRoute **out_route, + GError ** error) +{ + static const ParseLineInfo parse_infos[] = { + [PARSE_LINE_ATTR_ROUTE_TYPE] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_TYPE, + .type = PARSE_LINE_TYPE_STRING, + }, + [PARSE_LINE_ATTR_ROUTE_TABLE] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_TABLE, + .type = PARSE_LINE_TYPE_UINT32, + }, + [PARSE_LINE_ATTR_ROUTE_SRC] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_SRC, + .type = PARSE_LINE_TYPE_ADDR, + }, + [PARSE_LINE_ATTR_ROUTE_FROM] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_FROM, + .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX, + .disabled = PARSE_LINE_AF_FLAG_FOR_IPV4, + }, + [PARSE_LINE_ATTR_ROUTE_TOS] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_TOS, + .type = PARSE_LINE_TYPE_UINT8, + .int_base_16 = TRUE, + .ignore = PARSE_LINE_AF_FLAG_FOR_IPV6, + }, + [PARSE_LINE_ATTR_ROUTE_SCOPE] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_SCOPE, + .type = PARSE_LINE_TYPE_ROUTE_SCOPE, + .ignore = PARSE_LINE_AF_FLAG_FOR_IPV6, + }, + [PARSE_LINE_ATTR_ROUTE_ONLINK] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_ONLINK, + .type = PARSE_LINE_TYPE_FLAG, + }, + [PARSE_LINE_ATTR_ROUTE_WINDOW] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_WINDOW, + .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, + }, + [PARSE_LINE_ATTR_ROUTE_CWND] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_CWND, + .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, + }, + [PARSE_LINE_ATTR_ROUTE_INITCWND] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_INITCWND, + .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, + }, + [PARSE_LINE_ATTR_ROUTE_INITRWND] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_INITRWND, + .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, + }, + [PARSE_LINE_ATTR_ROUTE_MTU] = + { + .key = NM_IP_ROUTE_ATTRIBUTE_MTU, + .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, + }, + + [PARSE_LINE_ATTR_ROUTE_TO] = + { + .key = "to", + .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX, + .disabled_with_options_route = TRUE, + }, + [PARSE_LINE_ATTR_ROUTE_VIA] = + { + .key = "via", + .type = PARSE_LINE_TYPE_ADDR, + .disabled_with_options_route = TRUE, + }, + [PARSE_LINE_ATTR_ROUTE_METRIC] = + { + .key = "metric", + .type = PARSE_LINE_TYPE_UINT32, + .disabled_with_options_route = TRUE, + }, + + [PARSE_LINE_ATTR_ROUTE_DEV] = + { + .key = "dev", + .type = PARSE_LINE_TYPE_IFNAME, + .ignore = PARSE_LINE_AF_FLAG_FOR_IPV4 | PARSE_LINE_AF_FLAG_FOR_IPV6, + .disabled_with_options_route = TRUE, + }, + }; + nm_auto_unref_ip_route NMIPRoute *route = NULL; + gs_free const char ** words_free = NULL; + const char *const * words; + const char * s; + gsize i_words; + guint i; + char buf1[256]; + char buf2[256]; + ParseLineData parse_datas[G_N_ELEMENTS(parse_infos)] = {}; + const ParseLineAFFlag af_flag = + (addr_family == AF_INET) ? PARSE_LINE_AF_FLAG_FOR_IPV4 : PARSE_LINE_AF_FLAG_FOR_IPV6; + + nm_assert(line); + nm_assert_addr_family(addr_family); + nm_assert(!options_route || nm_ip_route_get_family(options_route) == addr_family); + + /* initscripts read the legacy route file line-by-line and + * use it as `ip route add $line`, thus doing split+glob. + * Splitting on IFS (which we consider '') + * and globbing (which we obviously don't do). + * + * I think it's a mess, because it doesn't support escaping or + * quoting. In fact, it can only encode benign values. + * + * We also use the same form for the numbered OPTIONS + * variable. I think it's bad not to support any form of + * escaping. But do that for now. + * + * Maybe later we want to support some form of quotation here. + * Which of course, would be incompatible with initscripts. + */ + words_free = nm_utils_strsplit_set(line, " \t\n"); + + words = words_free ?: NM_PTRARRAY_EMPTY(const char *); + + for (i_words = 0; words[i_words];) { + const gsize i_words0 = i_words; + const char *const w = words[i_words0]; + const ParseLineInfo *p_info; + ParseLineData * p_data; + gboolean unqualified_addr = FALSE; + + for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) { + p_info = &parse_infos[i]; + p_data = &parse_datas[i]; + + if ((p_info->disabled & af_flag) + || (p_info->disabled_with_options_route && options_route)) + continue; + + if (!nm_streq(w, p_info->key)) + continue; + + if (p_data->has) { + /* iproute2 for most arguments allows specifying them multiple times. + * Let's not do that. */ + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Duplicate option \"%s\"", + w); + return -EINVAL; + } + + p_data->has = TRUE; + switch (p_info->type) { + case PARSE_LINE_TYPE_UINT8: + i_words++; + goto parse_line_type_uint8; + case PARSE_LINE_TYPE_UINT32: + i_words++; + goto parse_line_type_uint32; + case PARSE_LINE_TYPE_UINT32_WITH_LOCK: + i_words++; + goto parse_line_type_uint32_with_lock; + case PARSE_LINE_TYPE_ADDR: + i_words++; + goto parse_line_type_addr; + case PARSE_LINE_TYPE_ADDR_WITH_PREFIX: + i_words++; + goto parse_line_type_addr_with_prefix; + case PARSE_LINE_TYPE_IFNAME: + i_words++; + goto parse_line_type_ifname; + case PARSE_LINE_TYPE_FLAG: + i_words++; + goto next; + case PARSE_LINE_TYPE_ROUTE_SCOPE: + i_words++; + goto parse_line_type_route_scope; + default: + nm_assert_not_reached(); + } + } + + p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TYPE]; + p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TYPE]; + if (!p_data->has + && NM_IN_STRSET(w, + "local", + "unicast", + "broadcast" + "multicast", + "throw", + "unreachable", + "prohibit", + "blackhole", + "nat")) { + p_data->has = TRUE; + goto parse_line_type_string; + } + + /* "to" is also accepted unqualified... (once) */ + p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TO]; + p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TO]; + if (!p_data->has && !(p_info->disabled & af_flag) + && !(p_info->disabled_with_options_route && options_route)) { + unqualified_addr = TRUE; + p_data->has = TRUE; + goto parse_line_type_addr; + } + + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unrecognized argument (\"to\" is duplicate or \"%s\" is garbage)", + w); + return -EINVAL; + +parse_line_type_route_scope: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + if (nm_streq(s, "global")) + p_data->v.uint8 = RT_SCOPE_UNIVERSE; + else if (nm_streq(s, "nowhere")) + p_data->v.uint8 = RT_SCOPE_NOWHERE; + else if (nm_streq(s, "host")) + p_data->v.uint8 = RT_SCOPE_HOST; + else if (nm_streq(s, "link")) + p_data->v.uint8 = RT_SCOPE_LINK; + else if (nm_streq(s, "site")) + p_data->v.uint8 = RT_SCOPE_SITE; + else { + p_data->v.uint8 = _nm_utils_ascii_str_to_int64(s, 0, 0, G_MAXUINT8, 0); + if (errno) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Argument for \"%s\" is not a valid number", + w); + return -EINVAL; + } + } + i_words++; + goto next; + +parse_line_type_uint8: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + p_data->v.uint8 = + _nm_utils_ascii_str_to_int64(s, p_info->int_base_16 ? 16 : 10, 0, G_MAXUINT8, 0); + if (errno) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Argument for \"%s\" is not a valid number", + w); + return -EINVAL; + } + i_words++; + goto next; + +parse_line_type_uint32: +parse_line_type_uint32_with_lock: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + if (p_info->type == PARSE_LINE_TYPE_UINT32_WITH_LOCK) { + if (nm_streq(s, "lock")) { + s = words[++i_words]; + if (!s) + goto err_word_missing_argument; + p_data->v.uint32_with_lock.lock = TRUE; + } else + p_data->v.uint32_with_lock.lock = FALSE; + p_data->v.uint32_with_lock.uint32 = + _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT32, 0); + } else { + p_data->v.uint32 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT32, 0); + } + if (errno) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Argument for \"%s\" is not a valid number", + w); + return -EINVAL; + } + i_words++; + goto next; + +parse_line_type_ifname: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + i_words++; + goto next; + +parse_line_type_addr: +parse_line_type_addr_with_prefix: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + { + int prefix = -1; + + if (p_info->type == PARSE_LINE_TYPE_ADDR) { + if (!nm_utils_parse_inaddr_bin(addr_family, s, NULL, &p_data->v.addr.addr)) { + if (p_info == &parse_infos[PARSE_LINE_ATTR_ROUTE_VIA] + && nm_streq(s, "(null)")) { + /* Due to a bug, would older versions of NM write "via (null)" + * (rh#1452648). Workaround that, and accept it.*/ + memset(&p_data->v.addr.addr, 0, sizeof(p_data->v.addr.addr)); + } else { + if (unqualified_addr) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unrecognized argument (inet prefix is expected rather " + "then \"%s\")", + w); + return -EINVAL; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Argument for \"%s\" is not a valid IPv%c address", + w, + addr_family == AF_INET ? '4' : '6'); + } + return -EINVAL; + } + } + } else { + nm_assert(p_info->type == PARSE_LINE_TYPE_ADDR_WITH_PREFIX); + if (p_info == &parse_infos[PARSE_LINE_ATTR_ROUTE_TO] && nm_streq(s, "default")) { + memset(&p_data->v.addr.addr, 0, sizeof(p_data->v.addr.addr)); + prefix = 0; + } else if (!nm_utils_parse_inaddr_prefix_bin(addr_family, + s, + NULL, + &p_data->v.addr.addr, + &prefix)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Argument for \"%s\" is not ADDR/PREFIX format", + w); + return -EINVAL; + } + } + if (prefix == -1) + p_data->v.addr.has_plen = FALSE; + else { + p_data->v.addr.has_plen = TRUE; + p_data->v.addr.plen = prefix; + } + } + i_words++; + goto next; + +parse_line_type_string: + s = words[i_words]; + if (!s) + goto err_word_missing_argument; + + p_data->v.str = s; + i_words++; + goto next; + +err_word_missing_argument: + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing argument for \"%s\"", + w); + return -EINVAL; +next:; + } + + if (options_route) { + route = options_route; + nm_ip_route_ref(route); + } else { + ParseLineData *data_to = &parse_datas[PARSE_LINE_ATTR_ROUTE_TO]; + ParseLineData *data_via = &parse_datas[PARSE_LINE_ATTR_ROUTE_VIA]; + ParseLineData *data_metric = &parse_datas[PARSE_LINE_ATTR_ROUTE_METRIC]; + guint prefix; + + if (!data_to->has) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing destination prefix"); + return -EINVAL; + } + + prefix = + data_to->v.addr.has_plen ? data_to->v.addr.plen : (addr_family == AF_INET ? 32 : 128); + + route = + nm_ip_route_new_binary(addr_family, + &data_to->v.addr.addr, + prefix, + data_via->has ? &data_via->v.addr.addr : NULL, + data_metric->has ? (gint64) data_metric->v.uint32 : (gint64) -1, + error); + data_to->has = FALSE; + data_via->has = FALSE; + data_metric->has = FALSE; + if (!route) + return -EINVAL; + } + + for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) { + const ParseLineInfo *p_info = &parse_infos[i]; + ParseLineData * p_data = &parse_datas[i]; + + if (!p_data->has) + continue; + + if ((p_info->ignore & af_flag) || (p_info->disabled & af_flag) + || (p_info->disabled_with_options_route && options_route)) + continue; + + switch (p_info->type) { + case PARSE_LINE_TYPE_UINT8: + case PARSE_LINE_TYPE_ROUTE_SCOPE: + nm_ip_route_set_attribute(route, p_info->key, g_variant_new_byte(p_data->v.uint8)); + break; + case PARSE_LINE_TYPE_UINT32: + nm_ip_route_set_attribute(route, p_info->key, g_variant_new_uint32(p_data->v.uint32)); + break; + case PARSE_LINE_TYPE_UINT32_WITH_LOCK: + if (p_data->v.uint32_with_lock.lock) { + nm_ip_route_set_attribute(route, + nm_sprintf_buf(buf1, "lock-%s", p_info->key), + g_variant_new_boolean(TRUE)); + } + nm_ip_route_set_attribute(route, + p_info->key, + g_variant_new_uint32(p_data->v.uint32_with_lock.uint32)); + break; + case PARSE_LINE_TYPE_ADDR: + case PARSE_LINE_TYPE_ADDR_WITH_PREFIX: + nm_ip_route_set_attribute( + route, + p_info->key, + g_variant_new_printf( + "%s%s", + inet_ntop(addr_family, &p_data->v.addr.addr, buf1, sizeof(buf1)), + p_data->v.addr.has_plen + ? nm_sprintf_buf(buf2, "/%u", (unsigned) p_data->v.addr.plen) + : "")); + break; + case PARSE_LINE_TYPE_FLAG: + /* NOTE: the flag (for "onlink") only allows to explicitly set "TRUE". + * There is no way to express an explicit "FALSE" setting + * of this attribute, hence, the file format cannot encode + * that configuration. */ + nm_ip_route_set_attribute(route, p_info->key, g_variant_new_boolean(TRUE)); + break; + case PARSE_LINE_TYPE_STRING: + nm_ip_route_set_attribute(route, p_info->key, g_variant_new_string(p_data->v.str)); + break; + default: + nm_assert_not_reached(); + break; + } + } + + nm_assert(_nm_ip_route_attribute_validate_all(route, NULL)); + + NM_SET_OUT(out_route, g_steal_pointer(&route)); + return 0; +} + +/* Returns TRUE on missing route or valid route */ +static gboolean +read_one_ip4_route(shvarFile *ifcfg, guint32 which, NMIPRoute **out_route, GError **error) +{ + char tag[256]; + char netmask_tag[256]; + guint32 dest; + guint32 next_hop; + guint32 netmask; + gboolean has_key; + const char * v; + gs_free char *value = NULL; + gint64 prefix, metric; + char inet_buf[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_val_if_fail(ifcfg != NULL, FALSE); + g_return_val_if_fail(out_route && !*out_route, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* Destination */ + if (!read_ip4_address(ifcfg, numbered_tag(tag, "ADDRESS", which), &has_key, &dest, error)) + return FALSE; + if (!has_key) { + /* missing route = success */ + *out_route = NULL; + return TRUE; + } + + /* Next hop */ + if (!read_ip4_address(ifcfg, numbered_tag(tag, "GATEWAY", which), NULL, &next_hop, error)) + return FALSE; + /* We don't make distinction between missing GATEWAY IP and 0.0.0.0 */ + + /* Prefix */ + if (!read_ip4_address(ifcfg, + numbered_tag(netmask_tag, "NETMASK", which), + &has_key, + &netmask, + error)) + return FALSE; + if (has_key) { + prefix = nm_utils_ip4_netmask_to_prefix(netmask); + if (netmask != _nm_utils_ip4_prefix_to_netmask(prefix)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IP4 netmask '%s' \"%s\"", + netmask_tag, + _nm_utils_inet4_ntop(netmask, inet_buf)); + return FALSE; + } + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing IP4 route element '%s'", + netmask_tag); + return FALSE; + } + + /* Metric */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, numbered_tag(tag, "METRIC", which), &value); + if (v) { + metric = _nm_utils_ascii_str_to_int64(v, 10, 0, G_MAXUINT32, -1); + if (metric < 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IP4 route metric '%s'", + v); + return FALSE; + } + } else + metric = -1; + + *out_route = nm_ip_route_new_binary(AF_INET, &dest, prefix, &next_hop, metric, error); + if (!*out_route) + return FALSE; + + /* Options */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, numbered_tag(tag, "OPTIONS", which), &value); + if (v) { + if (parse_route_line(v, AF_INET, *out_route, NULL, error) < 0) { + nm_clear_pointer(out_route, nm_ip_route_unref); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +read_route_file_parse(int addr_family, + const char * filename, + const char * contents, + gsize len, + NMSettingIPConfig *s_ip, + GError ** error) +{ + gsize line_num; + + nm_assert(filename); + nm_assert(addr_family == nm_setting_ip_config_get_addr_family(s_ip)); + nm_assert(!error || !*error); + + if (len <= 0) + return TRUE; /* missing/empty = success */ + + line_num = 0; + while (TRUE) { + nm_auto_unref_ip_route NMIPRoute *route = NULL; + gs_free_error GError *local = NULL; + const char * line = contents; + char * eol; + int e; + + eol = strchr(contents, '\n'); + if (eol) { + eol[0] = '\0'; + contents = &eol[1]; + } + + line_num++; + + if (parse_route_line_is_comment(line)) + goto next; + + e = parse_route_line(line, addr_family, NULL, &route, &local); + + if (e < 0) { + if (e == -ERANGE) + PARSE_WARNING("ignoring manual default route: '%s' (%s)", line, filename); + else { + /* we accept all unrecognized lines, because otherwise we would reject the + * entire connection. */ + PARSE_WARNING("ignoring invalid route at \"%s\" (%s:%lu): %s", + line, + filename, + (long unsigned) line_num, + local->message); + } + goto next; + } + + if (!nm_setting_ip_config_add_route(s_ip, route)) + PARSE_WARNING("duplicate IPv%c route", addr_family == AF_INET ? '4' : '6'); + +next: + if (!eol) + return TRUE; + + /* restore original content. */ + eol[0] = '\n'; + } +} + +static gboolean +read_route_file(int addr_family, const char *filename, NMSettingIPConfig *s_ip, GError **error) +{ + gs_free char *contents = NULL; + gsize len; + + nm_assert(filename); + nm_assert(addr_family == nm_setting_ip_config_get_addr_family(s_ip)); + nm_assert(!error || !*error); + + if (!g_file_get_contents(filename, &contents, &len, NULL)) + return TRUE; /* missing/empty = success */ + + return read_route_file_parse(addr_family, filename, contents, len, s_ip, error); +} + +static void +parse_dns_options(NMSettingIPConfig *ip_config, const char *value) +{ + gs_free const char **options = NULL; + const char *const * item; + + g_return_if_fail(ip_config); + + if (!value) + return; + + if (!nm_setting_ip_config_has_dns_options(ip_config)) + nm_setting_ip_config_clear_dns_options(ip_config, TRUE); + + options = nm_utils_strsplit_set(value, " "); + if (options) { + for (item = options; *item; item++) { + if (!nm_setting_ip_config_add_dns_option(ip_config, *item)) + PARSE_WARNING("can't add DNS option '%s'", *item); + } + } +} + +static gboolean +parse_full_ip6_address(shvarFile * ifcfg, + const char * addr_str, + int i, + NMIPAddress **out_address, + GError ** error) +{ + NMIPAddress *addr; + NMIPAddr addr_bin; + int prefix; + + nm_assert(addr_str); + nm_assert(out_address && !*out_address); + nm_assert(!error || !*error); + + if (!nm_utils_parse_inaddr_prefix_bin(AF_INET6, addr_str, NULL, &addr_bin, &prefix)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IP6 address '%s'", + addr_str); + return FALSE; + } + + if (prefix < 0) + prefix = 64; + + addr = nm_ip_address_new_binary(AF_INET6, &addr_bin, prefix, error); + if (!addr) + return FALSE; + + *out_address = addr; + return TRUE; +} + +static NMSetting * +make_user_setting(shvarFile *ifcfg) +{ + gboolean has_user_data = FALSE; + gs_unref_object NMSettingUser *s_user = NULL; + gs_unref_hashtable GHashTable *keys = NULL; + GHashTableIter iter; + const char * key; + nm_auto_free_gstring GString *str = NULL; + + keys = svGetKeys(ifcfg, SV_KEY_TYPE_USER); + if (!keys) + return NULL; + + g_hash_table_iter_init(&iter, keys); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { + const char * value; + gs_free char *value_to_free = NULL; + + value = svGetValue(ifcfg, key, &value_to_free); + + if (!value) + continue; + + if (!str) + str = g_string_sized_new(100); + else + g_string_set_size(str, 0); + + if (!nms_ifcfg_rh_utils_user_key_decode(key + NM_STRLEN("NM_USER_"), str)) + continue; + + if (!s_user) + s_user = NM_SETTING_USER(nm_setting_user_new()); + + if (nm_setting_user_set_data(s_user, str->str, value, NULL)) + has_user_data = TRUE; + } + + return has_user_data ? NM_SETTING(g_steal_pointer(&s_user)) : NULL; +} + +static void +make_match_setting_prop(const char * v, + NMSettingMatch **s_match, + void (*add_fcn)(NMSettingMatch *s_match, const char *value)) +{ + gs_free const char **strv = NULL; + gsize i; + + strv = nm_utils_escaped_tokens_split(v, NM_ASCII_SPACES); + if (strv) { + for (i = 0; strv[i]; i++) { + if (!(*s_match)) + *s_match = NM_SETTING_MATCH(nm_setting_match_new()); + add_fcn(*s_match, strv[i]); + } + } +} + +static NMSetting * +make_match_setting(shvarFile *ifcfg) +{ + NMSettingMatch *s_match = NULL; + gs_free char * value_ifn = NULL; + gs_free char * value_kcl = NULL; + gs_free char * value_d = NULL; + gs_free char * value_p = NULL; + const char * v; + + v = svGetValueStr(ifcfg, "MATCH_INTERFACE_NAME", &value_ifn); + make_match_setting_prop(v, &s_match, nm_setting_match_add_interface_name); + v = svGetValueStr(ifcfg, "MATCH_KERNEL_COMMAND_LINE", &value_kcl); + make_match_setting_prop(v, &s_match, nm_setting_match_add_kernel_command_line); + v = svGetValueStr(ifcfg, "MATCH_DRIVER", &value_d); + make_match_setting_prop(v, &s_match, nm_setting_match_add_driver); + v = svGetValueStr(ifcfg, "MATCH_PATH", &value_p); + make_match_setting_prop(v, &s_match, nm_setting_match_add_path); + + return NM_SETTING(s_match); +} + +static NMSetting * +make_proxy_setting(shvarFile *ifcfg) +{ + NMSettingProxy * s_proxy = NULL; + gs_free char * value = NULL; + const char * v; + NMSettingProxyMethod method; + + v = svGetValueStr(ifcfg, "PROXY_METHOD", &value); + if (!v) + return NULL; + + if (!g_ascii_strcasecmp(v, "auto")) + method = NM_SETTING_PROXY_METHOD_AUTO; + else + method = NM_SETTING_PROXY_METHOD_NONE; + + s_proxy = (NMSettingProxy *) nm_setting_proxy_new(); + + switch (method) { + case NM_SETTING_PROXY_METHOD_AUTO: + g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_AUTO, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "PAC_URL", &value); + if (v) + g_object_set(s_proxy, NM_SETTING_PROXY_PAC_URL, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "PAC_SCRIPT", &value); + if (v) + g_object_set(s_proxy, NM_SETTING_PROXY_PAC_SCRIPT, v, NULL); + + break; + case NM_SETTING_PROXY_METHOD_NONE: + g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_NONE, NULL); + break; + } + + if (svGetValueBoolean(ifcfg, "BROWSER_ONLY", FALSE)) + g_object_set(s_proxy, NM_SETTING_PROXY_BROWSER_ONLY, TRUE, NULL); + + return NM_SETTING(s_proxy); +} + +static NMSetting * +make_ip4_setting(shvarFile *ifcfg, + shvarFile *network_ifcfg, + gboolean routes_read, + gboolean * out_has_defroute, + GError ** error) +{ + gs_unref_object NMSettingIPConfig *s_ip4 = NULL; + gs_free char * route_path = NULL; + gs_free char * value = NULL; + const char * v; + char * method; + gs_free char * dns_options_free = NULL; + const char * dns_options = NULL; + gs_free char * gateway = NULL; + int i; + guint32 a; + gboolean has_key; + gboolean never_default; + gint64 i64; + int priority; + const char *const * item; + guint32 route_table; + + nm_assert(out_has_defroute && !*out_has_defroute); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + + /* First check if DEFROUTE is set for this device; DEFROUTE has the + * opposite meaning from never-default. The default if DEFROUTE is not + * specified is DEFROUTE=yes which means that this connection can be used + * as a default route + */ + i = svGetValueBoolean(ifcfg, "DEFROUTE", -1); + if (i == -1) + never_default = FALSE; + else { + never_default = !i; + *out_has_defroute = TRUE; + } + + /* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */ + if (network_ifcfg) { + gs_free char *gatewaydev_value = NULL; + const char * gatewaydev; + + /* Get the connection ifcfg device name and the global gateway device */ + v = svGetValueStr(ifcfg, "DEVICE", &value); + gatewaydev = svGetValueStr(network_ifcfg, "GATEWAYDEV", &gatewaydev_value); + dns_options = svGetValue(network_ifcfg, "RES_OPTIONS", &dns_options_free); + + /* If there was a global gateway device specified, then only connections + * for that device can be the default connection. + */ + if (gatewaydev && v) + never_default = !!strcmp(v, gatewaydev); + + nm_clear_g_free(&value); + } + + v = svGetValueStr(ifcfg, "BOOTPROTO", &value); + + if (!v || !*v || !g_ascii_strcasecmp(v, "none")) { + if (is_any_ip4_address_defined(ifcfg, NULL)) + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + else + method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + } else if (!g_ascii_strcasecmp(v, "bootp") || !g_ascii_strcasecmp(v, "dhcp")) { + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + } else if (!g_ascii_strcasecmp(v, "static")) { + if (is_any_ip4_address_defined(ifcfg, NULL)) + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + else + method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + } else if (!g_ascii_strcasecmp(v, "autoip")) { + method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL; + } else if (!g_ascii_strcasecmp(v, "shared")) { + method = NM_SETTING_IP4_CONFIG_METHOD_SHARED; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unknown BOOTPROTO '%s'", + v); + return NULL; + } + + /* the route table (policy routing) is ignored if we don't handle routes. */ + route_table = svGetValueInt64(ifcfg, "IPV4_ROUTE_TABLE", 10, 0, G_MAXUINT32, 0); + if (route_table != 0 && !routes_read) { + PARSE_WARNING( + "'rule-' or 'rule6-' files are present; Policy routing (IPV4_ROUTE_TABLE) is ignored"); + route_table = 0; + } + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + method, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + !svGetValueBoolean(ifcfg, "PEERDNS", TRUE), + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, + !svGetValueBoolean(ifcfg, "PEERROUTES", TRUE), + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, + never_default, + NM_SETTING_IP_CONFIG_MAY_FAIL, + !svGetValueBoolean(ifcfg, "IPV4_FAILURE_FATAL", FALSE), + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + svGetValueInt64(ifcfg, "IPV4_ROUTE_METRIC", 10, -1, G_MAXUINT32, -1), + NM_SETTING_IP_CONFIG_ROUTE_TABLE, + (guint) route_table, + NULL); + + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + return NM_SETTING(g_steal_pointer(&s_ip4)); + + /* Handle DHCP settings */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_HOSTNAME", &value); + if (v) + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_FQDN", &value); + if (v) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + NULL, + NM_SETTING_IP4_CONFIG_DHCP_FQDN, + v, + NULL); + } + + i64 = svGetValueInt64(ifcfg, "DHCP_HOSTNAME_FLAGS", 10, 0, G_MAXUINT32, -1); + if (i64 > -1) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, (guint) i64, NULL); + } + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, + svGetValueBoolean(ifcfg, "DHCP_SEND_HOSTNAME", TRUE), + NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, + (int) svGetValueInt64(ifcfg, "IPV4_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0), + NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_CLIENT_ID", &value); + if (v) + g_object_set(s_ip4, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_VENDOR_CLASS_IDENTIFIER", &value); + if (v) + g_object_set(s_ip4, NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_IAID", &value); + if (v) + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL); + + /* Read static IP addresses. + * Read them even for AUTO method - in this case the addresses are + * added to the automatic ones. Note that this is not currently supported by + * the legacy 'network' service (ifup-eth). + */ + for (i = -1;; i++) { + NMIPAddress *addr = NULL; + + /* gateway will only be set if still unset. Hence, we don't leak gateway + * here by calling read_full_ip4_address() repeatedly */ + if (!read_full_ip4_address(ifcfg, i, NULL, &addr, &gateway, error)) + return NULL; + + if (!addr) { + /* The first mandatory variable is 2-indexed (IPADDR2) + * Variables IPADDR, IPADDR0 and IPADDR1 are optional */ + if (i > 1) + break; + continue; + } + + if (!nm_setting_ip_config_add_address(s_ip4, addr)) + PARSE_WARNING("duplicate IP4 address"); + nm_ip_address_unref(addr); + } + + /* Gateway */ + if (!gateway) { + if (network_ifcfg) { + gboolean read_success; + + read_success = read_ip4_address(network_ifcfg, "GATEWAY", &has_key, &a, error); + if (!read_success) + return NULL; + if (has_key) { + if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) { + gs_free char *f = g_path_get_basename(svFileGetName(ifcfg)); + PARSE_WARNING("ignoring GATEWAY (/etc/sysconfig/network) for %s " + "because the connection has no static addresses", + f); + } else + gateway = nm_utils_inet4_ntop_dup(a); + } + } + } + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL); + + if (gateway && never_default) + PARSE_WARNING("GATEWAY will be ignored when DEFROUTE is disabled"); + + /* We used to skip saving a lot of unused properties for the ipv4 shared method. + * We want now to persist them but... unfortunately loading DNS or DOMAIN options + * would cause a fail in the ipv4 verify() function. As we don't want any regression + * in the unlikely event that someone has a working ifcfg file for an IPv4 shared ip + * connection with a crafted "DNS" entry... don't load it. So we will avoid failing + * the connection) */ + if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) { + /* DNS servers + * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting()) + */ + for (i = 1; i <= 10; i++) { + char tag[256]; + + numbered_tag(tag, "DNS", i); + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, tag, &value); + if (v) { + if (nm_utils_ipaddr_is_valid(AF_INET, v)) { + if (!nm_setting_ip_config_add_dns(s_ip4, v)) + PARSE_WARNING("duplicate DNS server %s", tag); + } else if (nm_utils_ipaddr_is_valid(AF_INET6, v)) { + /* Ignore IPv6 addresses */ + } else { + PARSE_WARNING("invalid DNS server address %s", v); + return NULL; + } + } + } + + /* DNS searches */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DOMAIN", &value); + if (v) { + gs_free const char **searches = NULL; + + searches = nm_utils_strsplit_set(v, " "); + if (searches) { + for (item = searches; *item; item++) { + if (!nm_setting_ip_config_add_dns_search(s_ip4, *item)) + PARSE_WARNING("duplicate DNS domain '%s'", *item); + } + } + } + } + + /* DNS options */ + nm_clear_g_free(&value); + parse_dns_options(s_ip4, svGetValue(ifcfg, "RES_OPTIONS", &value)); + parse_dns_options(s_ip4, dns_options); + + /* DNS priority */ + priority = svGetValueInt64(ifcfg, "IPV4_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DNS_PRIORITY, priority, NULL); + + /* Static routes - route- file */ + route_path = utils_get_route_path(svFileGetName(ifcfg)); + + if (routes_read) { + gs_free char *contents = NULL; + gsize len; + + if (!g_file_get_contents(route_path, &contents, &len, NULL)) + len = 0; + + if (utils_has_route_file_new_syntax_content(contents, len)) { + nm_auto_shvar_file_close shvarFile *route_ifcfg = NULL; + + /* Parse route file in new syntax */ + route_ifcfg = svFile_new(route_path, -1, contents); + for (i = 0;; i++) { + nm_auto_unref_ip_route NMIPRoute *route = NULL; + + if (!read_one_ip4_route(route_ifcfg, i, &route, error)) + return NULL; + + if (!route) + break; + + if (!nm_setting_ip_config_add_route(s_ip4, route)) + PARSE_WARNING("duplicate IP4 route"); + } + } else { + if (!read_route_file_parse(AF_INET, route_path, contents, len, s_ip4, error)) + return NULL; + } + } + + /* Legacy value NM used for a while but is incorrect (rh #459370) */ + if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) + && !nm_setting_ip_config_get_num_dns_searches(s_ip4)) { + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "SEARCH", &value); + if (v) { + gs_free const char **searches = NULL; + + searches = nm_utils_strsplit_set(v, " "); + if (searches) { + for (item = searches; *item; item++) { + if (!nm_setting_ip_config_add_dns_search(s_ip4, *item)) + PARSE_WARNING("duplicate DNS search '%s'", *item); + } + } + } + } + + i64 = svGetValueInt64(ifcfg, "ACD_TIMEOUT", 10, -1, NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, -2); + if (i64 == -2) { + i64 = svGetValueInt64(ifcfg, + "ARPING_WAIT", + 10, + -1, + NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX / 1000, + -1); + if (i64 > 0) + i64 *= 1000; + } + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (int) i64, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_REJECT_SERVERS", &value); + if (v) { + gs_free const char **strv = NULL; + + strv = nm_utils_escaped_tokens_split(v, NM_ASCII_SPACES); + if (strv) { + for (item = strv; *item; item++) + nm_setting_ip_config_add_dhcp_reject_server(s_ip4, *item); + } + } + + return NM_SETTING(g_steal_pointer(&s_ip4)); +} + +static void +read_aliases(NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *filename) +{ + GDir * dir; + gs_free char *dirname = NULL; + gs_free char *base = NULL; + NMIPAddress * base_addr = NULL; + GError * err = NULL; + + g_return_if_fail(s_ip4 != NULL); + g_return_if_fail(filename != NULL); + + if (nm_setting_ip_config_get_num_addresses(s_ip4) > 0) + base_addr = nm_setting_ip_config_get_address(s_ip4, 0); + + dirname = g_path_get_dirname(filename); + nm_assert(dirname != NULL); + base = g_path_get_basename(filename); + nm_assert(base != NULL); + + dir = g_dir_open(dirname, 0, &err); + if (dir) { + const char * item; + NMIPAddress *addr; + gboolean ok; + + while ((item = g_dir_read_name(dir))) { + nm_auto_shvar_file_close shvarFile *parsed = NULL; + gs_free char * gateway = NULL; + gs_free char * device_value = NULL; + gs_free char * full_path = NULL; + const char * device; + const char * p; + + if (!utils_is_ifcfg_alias_file(item, base)) + continue; + + full_path = g_build_filename(dirname, item, NULL); + + p = strchr(item, ':'); + g_assert(p != NULL); /* we know this is true from utils_is_ifcfg_alias_file() */ + for (p++; *p; p++) { + if (!g_ascii_isalnum(*p) && *p != '_') + break; + } + if (*p) { + PARSE_WARNING("ignoring alias file '%s' with invalid name", full_path); + continue; + } + + parsed = svOpenFile(full_path, &err); + if (!parsed) { + PARSE_WARNING("couldn't parse alias file '%s': %s", full_path, err->message); + g_clear_error(&err); + continue; + } + + device = svGetValueStr(parsed, "DEVICE", &device_value); + if (!device) { + PARSE_WARNING("alias file '%s' has no DEVICE", full_path); + continue; + } + /* We know that item starts with IFCFG_TAG from utils_is_ifcfg_alias_file() */ + if (strcmp(device, item + strlen(IFCFG_TAG)) != 0) { + PARSE_WARNING("alias file '%s' has invalid DEVICE (%s) for filename", + full_path, + device); + continue; + } + + addr = NULL; + ok = read_full_ip4_address(parsed, + -1, + base_addr, + &addr, + read_defroute ? &gateway : NULL, + &err); + if (ok) { + nm_ip_address_set_attribute(addr, + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string(device)); + if (!nm_setting_ip_config_add_address(s_ip4, addr)) + PARSE_WARNING("duplicate IP4 address in alias file %s", item); + if (nm_streq0(nm_setting_ip_config_get_method(s_ip4), + NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + if (read_defroute) { + int i; + + if (gateway) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL); + read_defroute = FALSE; + } + i = svGetValueBoolean(parsed, "DEFROUTE", -1); + if (i != -1) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, + (gboolean) !i, + NULL); + read_defroute = FALSE; + } + } + } else { + PARSE_WARNING("error reading IP4 address from alias file '%s': %s", + full_path, + err ? err->message : "no address"); + g_clear_error(&err); + } + nm_ip_address_unref(addr); + } + + g_dir_close(dir); + } else { + PARSE_WARNING("can not read directory '%s': %s", dirname, err->message); + g_error_free(err); + } +} + +static NMSetting * +make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_read, GError **error) +{ + gs_unref_object NMSettingIPConfig *s_ip6 = NULL; + const char * v; + gs_free char * value = NULL; + gboolean ipv6init; + gboolean ipv6forwarding; + gboolean disabled; + gboolean dhcp6 = FALSE; + char * method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + const char * ipv6addr, *ipv6addr_secondaries; + gs_free char * ipv6addr_to_free = NULL; + gs_free char * ipv6addr_secondaries_to_free = NULL; + gs_free const char ** list = NULL; + const char *const * iter; + guint32 i; + gint64 i64; + int i_val; + GError * local = NULL; + int priority; + gboolean never_default = FALSE; + gboolean ip6_privacy = FALSE, ip6_privacy_prefer_public_ip; + NMSettingIP6ConfigPrivacy ip6_privacy_val; + guint32 route_table; + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + + /* First check if IPV6_DEFROUTE is set for this device; IPV6_DEFROUTE has the + * opposite meaning from never-default. The default if IPV6_DEFROUTE is not + * specified is IPV6_DEFROUTE=yes which means that this connection can be used + * as a default route + */ + never_default = !svGetValueBoolean(ifcfg, "IPV6_DEFROUTE", TRUE); + + /* Then check if IPV6_DEFAULTGW or IPV6_DEFAULTDEV is specified; + * they are global and override IPV6_DEFROUTE + * When both are set, the device specified in IPV6_DEFAULTGW takes preference. + */ + if (network_ifcfg) { + const char * ipv6_defaultgw, *ipv6_defaultdev; + gs_free char *ipv6_defaultgw_to_free = NULL; + gs_free char *ipv6_defaultdev_to_free = NULL; + const char * default_dev = NULL; + + /* Get the connection ifcfg device name and the global default route device */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DEVICE", &value); + ipv6_defaultgw = svGetValueStr(network_ifcfg, "IPV6_DEFAULTGW", &ipv6_defaultgw_to_free); + ipv6_defaultdev = svGetValueStr(network_ifcfg, "IPV6_DEFAULTDEV", &ipv6_defaultdev_to_free); + + if (ipv6_defaultgw) { + default_dev = strchr(ipv6_defaultgw, '%'); + if (default_dev) + default_dev++; + } + if (!default_dev) + default_dev = ipv6_defaultdev; + + /* If there was a global default route device specified, then only connections + * for that device can be the default connection. + */ + if (default_dev && v) + never_default = !!strcmp(v, default_dev); + } + + /* Find out method property */ + /* Is IPV6 enabled? Set method to "ignored", when not enabled */ + disabled = svGetValueBoolean(ifcfg, "IPV6_DISABLED", FALSE); + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6INIT", &value); + ipv6init = svGetValueBoolean(ifcfg, "IPV6INIT", FALSE); + if (!v) { + if (network_ifcfg) + ipv6init = svGetValueBoolean(network_ifcfg, "IPV6INIT", FALSE); + } + + if (disabled) + method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED; + else if (!ipv6init) + method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE; + else { + ipv6forwarding = svGetValueBoolean(ifcfg, "IPV6FORWARDING", FALSE); + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6_AUTOCONF", &value); + dhcp6 = svGetValueBoolean(ifcfg, "DHCPV6C", FALSE); + + if (!g_strcmp0(v, "shared")) + method = NM_SETTING_IP6_CONFIG_METHOD_SHARED; + else if (svParseBoolean(v, !ipv6forwarding)) + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + else if (dhcp6) + method = NM_SETTING_IP6_CONFIG_METHOD_DHCP; + else { + /* IPV6_AUTOCONF=no and no IPv6 address -> method 'link-local' */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6ADDR", &value); + if (!v) { + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", &value); + } + + if (!v) + method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; + } + } + /* TODO - handle other methods */ + + /* Read IPv6 Privacy Extensions configuration */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6_PRIVACY", &value); + if (v) { + ip6_privacy = svParseBoolean(v, FALSE); + if (!ip6_privacy) + ip6_privacy = (g_strcmp0(v, "rfc4941") == 0) || (g_strcmp0(v, "rfc3041") == 0); + } + ip6_privacy_prefer_public_ip = svGetValueBoolean(ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", FALSE); + ip6_privacy_val = v ? (ip6_privacy ? (ip6_privacy_prefer_public_ip + ? NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR + : NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR) + : NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED) + : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* the route table (policy routing) is ignored if we don't handle routes. */ + route_table = svGetValueInt64(ifcfg, "IPV6_ROUTE_TABLE", 10, 0, G_MAXUINT32, 0); + if (route_table != 0 && !routes_read) { + PARSE_WARNING( + "'rule-' or 'rule6-' files are present; Policy routing (IPV6_ROUTE_TABLE) is ignored"); + route_table = 0; + } + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + method, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + !svGetValueBoolean(ifcfg, "IPV6_PEERDNS", TRUE), + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, + !svGetValueBoolean(ifcfg, "IPV6_PEERROUTES", TRUE), + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, + never_default, + NM_SETTING_IP_CONFIG_MAY_FAIL, + !svGetValueBoolean(ifcfg, "IPV6_FAILURE_FATAL", FALSE), + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + svGetValueInt64(ifcfg, "IPV6_ROUTE_METRIC", 10, -1, G_MAXUINT32, -1), + NM_SETTING_IP_CONFIG_ROUTE_TABLE, + (guint) route_table, + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + ip6_privacy_val, + NULL); + + /* Don't bother to read IP, DNS and routes when IPv6 is disabled */ + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + return NM_SETTING(g_steal_pointer(&s_ip6)); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCPV6_DUID", &value); + if (v) + g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_DHCP_DUID, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCPV6_IAID", &value); + if (v) + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCPV6_HOSTNAME", &value); + /* Use DHCP_HOSTNAME as fallback if it is in FQDN format and ipv6.method is + * auto or dhcp: this is required to support old ifcfg files + */ + if (!v + && (!strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) + || !strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP))) { + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DHCP_HOSTNAME", &value); + if (v && !strchr(v, '.')) + v = NULL; + } + if (v) + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, + svGetValueBoolean(ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), + NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, + (int) svGetValueInt64(ifcfg, "IPV6_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0), + NM_SETTING_IP6_CONFIG_RA_TIMEOUT, + (int) svGetValueInt64(ifcfg, "IPV6_RA_TIMEOUT", 10, 0, G_MAXINT32, 0), + NULL); + + i64 = svGetValueInt64(ifcfg, "DHCPV6_HOSTNAME_FLAGS", 10, 0, G_MAXUINT32, -1); + if (i64 > -1) { + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, (guint) i64, NULL); + } + + /* Read static IP addresses. + * Read them even for AUTO and DHCP methods - in this case the addresses are + * added to the automatic ones. Note that this is not currently supported by + * the legacy 'network' service (ifup-eth). + */ + ipv6addr = svGetValueStr(ifcfg, "IPV6ADDR", &ipv6addr_to_free); + ipv6addr_secondaries = + svGetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", &ipv6addr_secondaries_to_free); + + nm_clear_g_free(&value); + value = g_strjoin(ipv6addr && ipv6addr_secondaries ? " " : NULL, + ipv6addr ?: "", + ipv6addr_secondaries ?: "", + NULL); + + list = nm_utils_strsplit_set(value, " "); + for (iter = list, i = 0; iter && *iter; iter++, i++) { + NMIPAddress *addr = NULL; + + if (!parse_full_ip6_address(ifcfg, *iter, i, &addr, error)) + return NULL; + + if (!nm_setting_ip_config_add_address(s_ip6, addr)) + PARSE_WARNING("duplicate IP6 address"); + nm_ip_address_unref(addr); + } + + /* Gateway */ + if (nm_setting_ip_config_get_num_addresses(s_ip6)) { + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6_DEFAULTGW", &value); + if (!v) { + /* If no gateway in the ifcfg, try global /etc/sysconfig/network instead */ + if (network_ifcfg) { + nm_clear_g_free(&value); + v = svGetValueStr(network_ifcfg, "IPV6_DEFAULTGW", &value); + } + } + if (v) { + char *ptr; + if ((ptr = strchr(v, '%')) != NULL) + *ptr = '\0'; /* remove %interface prefix if present */ + if (!nm_utils_ipaddr_is_valid(AF_INET6, v)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IP6 address '%s'", + v); + return NULL; + } + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL); + } + } + + i_val = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64; + if (!svGetValueEnum(ifcfg, + "IPV6_ADDR_GEN_MODE", + nm_setting_ip6_config_addr_gen_mode_get_type(), + &i_val, + &local)) { + PARSE_WARNING("%s", local->message); + g_clear_error(&local); + } + g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, i_val, NULL); + + /* IPv6 tokenized interface identifier */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6_TOKEN", &value); + if (v) + g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, v, NULL); + + /* DNS servers + * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting()) + */ + for (i = 1; i <= 10; i++) { + char tag[256]; + + numbered_tag(tag, "DNS", i); + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, tag, &value); + if (!v) { + /* all done */ + break; + } + + if (nm_utils_ipaddr_is_valid(AF_INET6, v)) { + if (!nm_setting_ip_config_add_dns(s_ip6, v)) + PARSE_WARNING("duplicate DNS server %s", tag); + } else if (nm_utils_ipaddr_is_valid(AF_INET, v)) { + /* Ignore IPv4 addresses */ + } else { + PARSE_WARNING("invalid DNS server address %s", v); + return NULL; + } + } + + if (!routes_read) { + /* NOP */ + } else { + gs_free char *route6_path = NULL; + + /* Read static routes from route6- file */ + route6_path = utils_get_route6_path(svFileGetName(ifcfg)); + if (!read_route_file(AF_INET6, route6_path, s_ip6, error)) + return NULL; + } + + /* DNS searches */ + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IPV6_DOMAIN", &value); + if (v) { + gs_free const char **searches = NULL; + + searches = nm_utils_strsplit_set(v, " "); + if (searches) { + for (iter = searches; *iter; iter++) { + if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter)) + PARSE_WARNING("duplicate DNS domain '%s'", *iter); + } + } + } + + /* DNS options */ + nm_clear_g_free(&value); + parse_dns_options(s_ip6, svGetValue(ifcfg, "IPV6_RES_OPTIONS", &value)); + + /* DNS priority */ + priority = svGetValueInt64(ifcfg, "IPV6_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DNS_PRIORITY, priority, NULL); + + return NM_SETTING(g_steal_pointer(&s_ip6)); +} + +static NMSetting * +make_hostname_setting(shvarFile *ifcfg) +{ + NMSetting *setting; + NMTernary from_dhcp; + NMTernary from_dns_lookup; + NMTernary only_from_default; + int priority; + + priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0); + + from_dhcp = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP"); + from_dns_lookup = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP"); + only_from_default = svGetValueTernary(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT"); + + /* Create the setting when at least one key is not default*/ + if (priority == 0 && from_dhcp == NM_TERNARY_DEFAULT && from_dns_lookup == NM_TERNARY_DEFAULT + && only_from_default == NM_TERNARY_DEFAULT) + return NULL; + + setting = nm_setting_hostname_new(); + + g_object_set(setting, + NM_SETTING_HOSTNAME_PRIORITY, + priority, + NM_SETTING_HOSTNAME_FROM_DHCP, + from_dhcp, + NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP, + from_dns_lookup, + NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT, + only_from_default, + NULL); + + return setting; +} + +static NMSetting * +make_sriov_setting(shvarFile *ifcfg) +{ + gs_unref_hashtable GHashTable *keys = NULL; + gs_unref_ptrarray GPtrArray *vfs = NULL; + int autoprobe_drivers; + NMSettingSriov * s_sriov; + gint64 total_vfs; + + total_vfs = svGetValueInt64(ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXUINT32, -1); + + autoprobe_drivers = svGetValueInt64(ifcfg, + "SRIOV_AUTOPROBE_DRIVERS", + 10, + NM_TERNARY_DEFAULT, + NM_TERNARY_TRUE, + -2); + + keys = svGetKeys(ifcfg, SV_KEY_TYPE_SRIOV_VF); + if (keys) { + GHashTableIter iter; + const char * key; + + g_hash_table_iter_init(&iter, keys); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { + gs_free_error GError *error = NULL; + gs_free char * value_to_free = NULL; + const char * value; + NMSriovVF * vf; + + nm_assert(g_str_has_prefix(key, "SRIOV_VF")); + + value = svGetValue(ifcfg, key, &value_to_free); + if (!value) + continue; + + key += NM_STRLEN("SRIOV_VF"); + + vf = _nm_utils_sriov_vf_from_strparts(key, value, TRUE, &error); + if (!vf) { + PARSE_WARNING("ignoring invalid SR-IOV VF '%s %s': %s", key, value, error->message); + continue; + } + if (!vfs) + vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); + g_ptr_array_add(vfs, vf); + } + } + + /* Create the setting when at least one key is set */ + if (total_vfs < 0 && !vfs && autoprobe_drivers < NM_TERNARY_DEFAULT) + return NULL; + + s_sriov = (NMSettingSriov *) nm_setting_sriov_new(); + + autoprobe_drivers = NM_MAX(autoprobe_drivers, NM_TERNARY_DEFAULT); + total_vfs = NM_MAX(total_vfs, 0); + + g_object_set(s_sriov, + NM_SETTING_SRIOV_TOTAL_VFS, + (guint) total_vfs, + NM_SETTING_SRIOV_VFS, + vfs, + NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, + autoprobe_drivers, + NULL); + + return (NMSetting *) s_sriov; +} + +static NMSetting * +make_tc_setting(shvarFile *ifcfg) +{ + NMSettingTCConfig *s_tc = NULL; + char tag[256]; + int i; + + s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new(); + + for (i = 1;; i++) { + NMTCQdisc * qdisc = NULL; + gs_free char *value_to_free = NULL; + const char * value = NULL; + GError * local = NULL; + + value = svGetValueStr(ifcfg, numbered_tag(tag, "QDISC", i), &value_to_free); + if (!value) + break; + + qdisc = nm_utils_tc_qdisc_from_str(value, &local); + if (!qdisc) { + PARSE_WARNING("ignoring bad tc qdisc: '%s': %s", value, local->message); + continue; + } + + if (!nm_setting_tc_config_add_qdisc(s_tc, qdisc)) + PARSE_WARNING("duplicate tc qdisc"); + + nm_tc_qdisc_unref(qdisc); + } + + for (i = 1;; i++) { + NMTCTfilter * tfilter = NULL; + gs_free char *value_to_free = NULL; + const char * value = NULL; + gs_free_error GError *local = NULL; + + value = svGetValueStr(ifcfg, numbered_tag(tag, "FILTER", i), &value_to_free); + if (!value) + break; + + tfilter = nm_utils_tc_tfilter_from_str(value, &local); + if (!tfilter) { + PARSE_WARNING("ignoring bad tc filter: '%s': %s", value, local->message); + continue; + } + + if (!nm_setting_tc_config_add_tfilter(s_tc, tfilter)) + PARSE_WARNING("duplicate tc filter"); + + nm_tc_tfilter_unref(tfilter); + } + + if (nm_setting_tc_config_get_num_qdiscs(s_tc) > 0 + || nm_setting_tc_config_get_num_tfilters(s_tc) > 0) + return NM_SETTING(s_tc); + + g_object_unref(s_tc); + return NULL; +} + +typedef struct { + const char *enable_key; + const char *advertise_key; + const char *willing_key; + const char *flags_prop; +} DcbFlagsProperty; + +enum { + DCB_APP_FCOE_FLAGS = 0, + DCB_APP_ISCSI_FLAGS = 1, + DCB_APP_FIP_FLAGS = 2, + DCB_PFC_FLAGS = 3, + DCB_PG_FLAGS = 4, +}; + +static DcbFlagsProperty dcb_flags_props[] = { + {KEY_DCB_APP_FCOE_ENABLE, + KEY_DCB_APP_FCOE_ADVERTISE, + KEY_DCB_APP_FCOE_WILLING, + NM_SETTING_DCB_APP_FCOE_FLAGS}, + {KEY_DCB_APP_ISCSI_ENABLE, + KEY_DCB_APP_ISCSI_ADVERTISE, + KEY_DCB_APP_ISCSI_WILLING, + NM_SETTING_DCB_APP_ISCSI_FLAGS}, + {KEY_DCB_APP_FIP_ENABLE, + KEY_DCB_APP_FIP_ADVERTISE, + KEY_DCB_APP_FIP_WILLING, + NM_SETTING_DCB_APP_FIP_FLAGS}, + {KEY_DCB_PFC_ENABLE, + KEY_DCB_PFC_ADVERTISE, + KEY_DCB_PFC_WILLING, + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS}, + {KEY_DCB_PG_ENABLE, + KEY_DCB_PG_ADVERTISE, + KEY_DCB_PG_WILLING, + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS}, + {NULL}, +}; + +static NMSettingDcbFlags +read_dcb_flags(shvarFile *ifcfg, DcbFlagsProperty *property) +{ + NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE; + + if (svGetValueBoolean(ifcfg, property->enable_key, FALSE)) + flags |= NM_SETTING_DCB_FLAG_ENABLE; + if (svGetValueBoolean(ifcfg, property->advertise_key, FALSE)) + flags |= NM_SETTING_DCB_FLAG_ADVERTISE; + if (svGetValueBoolean(ifcfg, property->willing_key, FALSE)) + flags |= NM_SETTING_DCB_FLAG_WILLING; + + return flags; +} + +static gboolean +read_dcb_app(shvarFile * ifcfg, + NMSettingDcb * s_dcb, + const char * app, + DcbFlagsProperty *flags_prop, + const char * priority_prop, + GError ** error) +{ + NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE; + gs_free char * value = NULL; + const char * v; + gboolean success = TRUE; + int priority = -1; + char key[255]; + + flags = read_dcb_flags(ifcfg, flags_prop); + + /* Priority */ + nm_sprintf_buf(key, "DCB_APP_%s_PRIORITY", app); + v = svGetValueStr(ifcfg, key, &value); + if (v) { + priority = _nm_utils_ascii_str_to_int64(v, 0, 0, 7, -1); + if (priority < 0) { + success = FALSE; + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid %s value '%s' (expected 0 - 7)", + key, + v); + } + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) + PARSE_WARNING("ignoring DCB %s priority; app not enabled", app); + } + + if (success) { + g_object_set(G_OBJECT(s_dcb), + flags_prop->flags_prop, + flags, + priority_prop, + (guint) priority, + NULL); + } + + return success; +} + +typedef void (*DcbSetBoolFunc)(NMSettingDcb *, guint, gboolean); + +static gboolean +read_dcb_bool_array(shvarFile * ifcfg, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + const char * prop, + const char * desc, + DcbSetBoolFunc set_func, + GError ** error) +{ + gs_free char *value = NULL; + const char * v; + guint i; + + v = svGetValueStr(ifcfg, prop, &value); + if (!v) + return TRUE; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) { + PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc); + return TRUE; + } + + if (strlen(v) != 8) { + PARSE_WARNING("%s value '%s' must be 8 characters long", prop, v); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "boolean array must be 8 characters"); + return FALSE; + } + + /* All characters must be either 0 or 1 */ + for (i = 0; i < 8; i++) { + if (v[i] != '0' && v[i] != '1') { + PARSE_WARNING("invalid %s value '%s': not all 0s and 1s", prop, v); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid boolean digit"); + return FALSE; + } + set_func(s_dcb, i, (v[i] == '1')); + } + return TRUE; +} + +typedef void (*DcbSetUintFunc)(NMSettingDcb *, guint, guint); + +static gboolean +read_dcb_uint_array(shvarFile * ifcfg, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + const char * prop, + const char * desc, + gboolean f_allowed, + DcbSetUintFunc set_func, + GError ** error) +{ + gs_free char *val = NULL; + guint i; + + val = svGetValueStr_cp(ifcfg, prop); + if (!val) + return TRUE; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) { + PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc); + return TRUE; + } + + if (strlen(val) != 8) { + PARSE_WARNING("%s value '%s' must be 8 characters long", prop, val); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "uint array must be 8 characters"); + return FALSE; + } + + /* All characters must be either 0 - 7 or (optionally) f */ + for (i = 0; i < 8; i++) { + if (val[i] >= '0' && val[i] <= '7') + set_func(s_dcb, i, val[i] - '0'); + else if (f_allowed && (val[i] == 'f' || val[i] == 'F')) + set_func(s_dcb, i, 15); + else { + PARSE_WARNING("invalid %s value '%s': not 0 - 7%s", + prop, + val, + f_allowed ? " or 'f'" : ""); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid uint digit"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +read_dcb_percent_array(shvarFile * ifcfg, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + const char * prop, + const char * desc, + gboolean sum_pct, + DcbSetUintFunc set_func, + GError ** error) +{ + gs_free char * val = NULL; + gs_free const char **split = NULL; + const char *const * iter; + guint i, sum = 0; + + val = svGetValueStr_cp(ifcfg, prop); + if (!val) + return TRUE; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) { + PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc); + return TRUE; + } + + split = nm_utils_strsplit_set(val, ","); + if (NM_PTRARRAY_LEN(split) != 8) { + PARSE_WARNING("invalid %s percentage list value '%s'", prop, val); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "percent array must be 8 elements"); + return FALSE; + } + + for (iter = split, i = 0; iter && *iter; iter++, i++) { + int tmp; + + tmp = _nm_utils_ascii_str_to_int64(*iter, 0, 0, 100, -1); + if (tmp < 0) { + PARSE_WARNING("invalid %s percentage value '%s'", prop, *iter); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid percent element"); + return FALSE; + } + set_func(s_dcb, i, (guint) tmp); + sum += (guint) tmp; + } + + if (sum_pct && (sum != 100)) { + PARSE_WARNING("%s percentages do not equal 100%%", prop); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid percentage sum"); + return FALSE; + } + + return TRUE; +} + +static gboolean +make_dcb_setting(shvarFile *ifcfg, NMSetting **out_setting, GError **error) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + gboolean dcb_on; + NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE; + + g_return_val_if_fail(out_setting, FALSE); + *out_setting = NULL; + + dcb_on = !!svGetValueBoolean(ifcfg, "DCB", FALSE); + if (!dcb_on) + return TRUE; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + + /* FCOE */ + if (!read_dcb_app(ifcfg, + s_dcb, + "FCOE", + &dcb_flags_props[DCB_APP_FCOE_FLAGS], + NM_SETTING_DCB_APP_FCOE_PRIORITY, + error)) { + return FALSE; + } + if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) { + gs_free char *val = NULL; + + val = svGetValueStr_cp(ifcfg, KEY_DCB_APP_FCOE_MODE); + if (val) { + if (NM_IN_STRSET(val, NM_SETTING_DCB_FCOE_MODE_FABRIC, NM_SETTING_DCB_FCOE_MODE_VN2VN)) + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL); + else { + PARSE_WARNING("invalid FCoE mode '%s'", val); + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid FCoE mode"); + return FALSE; + } + } + } + + /* iSCSI */ + if (!read_dcb_app(ifcfg, + s_dcb, + "ISCSI", + &dcb_flags_props[DCB_APP_ISCSI_FLAGS], + NM_SETTING_DCB_APP_ISCSI_PRIORITY, + error)) { + return FALSE; + } + + /* FIP */ + if (!read_dcb_app(ifcfg, + s_dcb, + "FIP", + &dcb_flags_props[DCB_APP_FIP_FLAGS], + NM_SETTING_DCB_APP_FIP_PRIORITY, + error)) { + return FALSE; + } + + /* Priority Flow Control */ + flags = read_dcb_flags(ifcfg, &dcb_flags_props[DCB_PFC_FLAGS]); + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, flags, NULL); + + if (!read_dcb_bool_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PFC_UP, + "PFC", + nm_setting_dcb_set_priority_flow_control, + error)) { + return FALSE; + } + + /* Priority Groups */ + flags = read_dcb_flags(ifcfg, &dcb_flags_props[DCB_PG_FLAGS]); + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, flags, NULL); + + if (!read_dcb_uint_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PG_ID, + "PGID", + TRUE, + nm_setting_dcb_set_priority_group_id, + error)) { + return FALSE; + } + + /* Group bandwidth */ + if (!read_dcb_percent_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PG_PCT, + "PGPCT", + TRUE, + nm_setting_dcb_set_priority_group_bandwidth, + error)) { + return FALSE; + } + + /* Priority bandwidth */ + if (!read_dcb_percent_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PG_UPPCT, + "UPPCT", + FALSE, + nm_setting_dcb_set_priority_bandwidth, + error)) { + return FALSE; + } + + /* Strict Bandwidth */ + if (!read_dcb_bool_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PG_STRICT, + "STRICT", + nm_setting_dcb_set_priority_strict_bandwidth, + error)) { + return FALSE; + } + + if (!read_dcb_uint_array(ifcfg, + s_dcb, + flags, + KEY_DCB_PG_UP2TC, + "UP2TC", + FALSE, + nm_setting_dcb_set_priority_traffic_class, + error)) { + return FALSE; + } + + *out_setting = NM_SETTING(g_steal_pointer(&s_dcb)); + return TRUE; +} + +static gboolean +add_one_wep_key(shvarFile * ifcfg, + const char * shvar_key, + guint8 key_idx, + gboolean passphrase, + NMSettingWirelessSecurity *s_wsec, + GError ** error) +{ + gs_free char *value_free = NULL; + const char * value; + const char * key = NULL; + + g_return_val_if_fail(ifcfg != NULL, FALSE); + g_return_val_if_fail(shvar_key != NULL, FALSE); + g_return_val_if_fail(key_idx <= 3, FALSE); + g_return_val_if_fail(s_wsec != NULL, FALSE); + + value = svGetValueStr(ifcfg, shvar_key, &value_free); + if (!value) + return TRUE; + + /* Validate keys */ + if (passphrase) { + if (value[0] && strlen(value) < 64) + key = value; + } else { + if (NM_IN_SET(strlen(value), 10, 26)) { + /* Hexadecimal WEP key */ + if (NM_STRCHAR_ANY(value, ch, !g_ascii_isxdigit(ch))) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid hexadecimal WEP key"); + return FALSE; + } + key = value; + } else if (!strncmp(value, "s:", 2) && NM_IN_SET(strlen(value), 7, 15)) { + /* ASCII key */ + if (NM_STRCHAR_ANY(value + 2, ch, !g_ascii_isprint(ch))) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid ASCII WEP key"); + return FALSE; + } + + /* Remove 's:' prefix. + * Don't convert to hex string. wpa_supplicant takes 'wep_key0' option over D-Bus as byte array + * and converts it to hex string itself. Even though we convert hex string keys into a bin string + * before passing to wpa_supplicant, this prevents two unnecessary conversions. And mainly, + * ASCII WEP key doesn't change to HEX WEP key in UI, which could confuse users. + */ + key = value + 2; + } + } + + if (!key) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid WEP key length"); + return FALSE; + } + + nm_setting_wireless_security_set_wep_key(s_wsec, key_idx, key); + return TRUE; +} + +static gboolean +read_wep_keys(shvarFile * ifcfg, + NMWepKeyType key_type, + guint8 def_idx, + NMSettingWirelessSecurity *s_wsec, + GError ** error) +{ + if (key_type != NM_WEP_KEY_TYPE_PASSPHRASE) { + if (!add_one_wep_key(ifcfg, "KEY1", 0, FALSE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY2", 1, FALSE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY3", 2, FALSE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY4", 3, FALSE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY", def_idx, FALSE, s_wsec, error)) + return FALSE; + } + + if (key_type != NM_WEP_KEY_TYPE_KEY) { + if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE1", 0, TRUE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE2", 1, TRUE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE3", 2, TRUE, s_wsec, error)) + return FALSE; + if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE4", 3, TRUE, s_wsec, error)) + return FALSE; + } + + return TRUE; +} + +static NMSetting * +make_wep_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + gs_unref_object NMSettingWirelessSecurity *s_wsec = NULL; + gs_free char * value = NULL; + shvarFile * keys_ifcfg = NULL; + int default_key_idx = 0; + gboolean has_default_key = FALSE; + NMSettingSecretFlags key_flags; + + s_wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new()); + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + + value = svGetValueStr_cp(ifcfg, "DEFAULTKEY"); + if (value) { + default_key_idx = _nm_utils_ascii_str_to_int64(value, 0, 1, 4, 0); + if (default_key_idx == 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid default WEP key '%s'", + value); + return NULL; + } + has_default_key = TRUE; + default_key_idx--; /* convert to [0...3] */ + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + (guint) default_key_idx, + NULL); + nm_clear_g_free(&value); + } + + /* Read WEP key flags */ + key_flags = _secret_read_ifcfg_flags(ifcfg, "WEP_KEY_FLAGS"); + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, key_flags, NULL); + + /* Read keys in the ifcfg file if they are system-owned */ + if (key_flags == NM_SETTING_SECRET_FLAG_NONE) { + NMWepKeyType key_type; + const char * v; + gs_free char *to_free = NULL; + + v = svGetValueStr(ifcfg, "KEY_TYPE", &to_free); + if (!v) + key_type = NM_WEP_KEY_TYPE_UNKNOWN; + else if (nm_streq(v, "key")) + key_type = NM_WEP_KEY_TYPE_KEY; + else if (nm_streq(v, "passphrase")) + key_type = NM_WEP_KEY_TYPE_PASSPHRASE; + else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid KEY_TYPE value '%s'", + v); + return FALSE; + } + + if (!read_wep_keys(ifcfg, key_type, default_key_idx, s_wsec, error)) + return NULL; + + /* Try to get keys from the "shadow" key file */ + keys_ifcfg = utils_get_keys_ifcfg(file, FALSE); + if (keys_ifcfg) { + if (!read_wep_keys(keys_ifcfg, key_type, default_key_idx, s_wsec, error)) { + svCloseFile(keys_ifcfg); + return NULL; + } + svCloseFile(keys_ifcfg); + g_assert(error == NULL || *error == NULL); + } + + g_object_set(G_OBJECT(s_wsec), NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, key_type, NULL); + } + + value = svGetValueStr_cp(ifcfg, "SECURITYMODE"); + if (value) { + gs_free char *lcase = NULL; + + lcase = g_ascii_strdown(value, -1); + nm_clear_g_free(&value); + + if (nm_streq(lcase, "open")) { + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); + } else if (nm_streq(lcase, "restricted")) { + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", NULL); + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid WEP authentication algorithm '%s'", + lcase); + return NULL; + } + } + + /* If no WEP keys were given, and the keys are not agent-owned, and no + * default WEP key index was given, then the connection is unencrypted. + */ + if (!nm_setting_wireless_security_get_wep_key(s_wsec, 0) + && !nm_setting_wireless_security_get_wep_key(s_wsec, 1) + && !nm_setting_wireless_security_get_wep_key(s_wsec, 2) + && !nm_setting_wireless_security_get_wep_key(s_wsec, 3) && (has_default_key == FALSE) + && (key_flags == NM_SETTING_SECRET_FLAG_NONE)) { + const char *auth_alg; + + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + if (auth_alg && !strcmp(auth_alg, "shared")) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "WEP Shared Key authentication is invalid for " + "unencrypted connections"); + return NULL; + } + + /* Unencrypted */ + return NULL; + } + + return NM_SETTING(g_steal_pointer(&s_wsec)); +} + +static gboolean +fill_wpa_ciphers(shvarFile *ifcfg, NMSettingWirelessSecurity *wsec, gboolean group, gboolean adhoc) +{ + gs_free char * value = NULL; + const char * p; + gs_free const char **list = NULL; + const char *const * iter; + int i = 0; + + p = svGetValueStr(ifcfg, group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE", &value); + if (!p) + return TRUE; + + list = nm_utils_strsplit_set(p, " "); + for (iter = list; iter && *iter; iter++, i++) { + if (!strcmp(*iter, "CCMP")) { + if (group) + nm_setting_wireless_security_add_group(wsec, "ccmp"); + else + nm_setting_wireless_security_add_pairwise(wsec, "ccmp"); + } else if (!strcmp(*iter, "TKIP")) { + if (group) + nm_setting_wireless_security_add_group(wsec, "tkip"); + else + nm_setting_wireless_security_add_pairwise(wsec, "tkip"); + } else if (group && !strcmp(*iter, "WEP104")) + nm_setting_wireless_security_add_group(wsec, "wep104"); + else if (group && !strcmp(*iter, "WEP40")) + nm_setting_wireless_security_add_group(wsec, "wep40"); + else { + PARSE_WARNING("ignoring invalid %s cipher '%s'", + group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE", + *iter); + } + } + + return TRUE; +} + +#define WPA_PMK_LEN 32 + +static char * +parse_wpa_psk(shvarFile *ifcfg, const char *file, GBytes *ssid, GError **error) +{ + shvarFile * keys_ifcfg; + gs_free char *psk = NULL; + size_t plen; + + /* Passphrase must be between 10 and 66 characters in length because WPA + * hex keys are exactly 64 characters (no quoting), and WPA passphrases + * are between 8 and 63 characters (inclusive), plus optional quoting if + * the passphrase contains spaces. + */ + + /* Try to get keys from the "shadow" key file */ + keys_ifcfg = utils_get_keys_ifcfg(file, FALSE); + if (keys_ifcfg) { + psk = svGetValueStr_cp(keys_ifcfg, "WPA_PSK"); + svCloseFile(keys_ifcfg); + } + + /* Fall back to the original ifcfg */ + if (!psk) + psk = svGetValueStr_cp(ifcfg, "WPA_PSK"); + + if (!psk) + return NULL; + + plen = strlen(psk); + + if (plen == 64) { + /* Verify the hex PSK; 64 digits */ + if (!NM_STRCHAR_ALL(psk, ch, g_ascii_isxdigit(ch))) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid WPA_PSK (contains non-hexadecimal characters)"); + return NULL; + } + } else { + if (plen < 8 || plen > 63) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid WPA_PSK (passphrases must be between " + "8 and 63 characters long (inclusive))"); + return NULL; + } + } + + return g_steal_pointer(&psk); +} + +static gboolean +eap_simple_reader(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error) +{ + NMSettingSecretFlags flags; + gs_free char * identity_free = NULL; + nm_auto_free_secret char *password_raw_str = NULL; + gs_unref_bytes GBytes *password_raw_bytes = NULL; + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + svGetValueStr(ifcfg, "IEEE_8021X_IDENTITY", &identity_free), + NULL); + + _secret_set_from_ifcfg(s_8021x, + ifcfg, + keys_ifcfg, + "IEEE_8021X_PASSWORD", + NM_SETTING_802_1X_PASSWORD); + + _secret_read_ifcfg(ifcfg, keys_ifcfg, "IEEE_8021X_PASSWORD_RAW", &password_raw_str, &flags); + if (!_secret_password_raw_to_bytes("IEEE_8021X_PASSWORD_RAW", + password_raw_str, + &password_raw_bytes, + error)) + return FALSE; + + g_object_set(s_8021x, + NM_SETTING_802_1X_PASSWORD_RAW_FLAGS, + flags, + NM_SETTING_802_1X_PASSWORD_RAW, + password_raw_bytes, + NULL); + + return TRUE; +} + +static gboolean +eap_tls_reader(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error) +{ + gs_unref_bytes GBytes *privkey = NULL; + gs_unref_bytes GBytes *client_cert = NULL; + gs_free char * identity_free = NULL; + gs_free char * value_to_free = NULL; + const char * client_cert_var; + const char * client_cert_prop; + NMSetting8021xCKFormat format; + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + svGetValueStr(ifcfg, "IEEE_8021X_IDENTITY", &identity_free), + NULL); + + /* CA certificate */ + if (!_cert_set_from_ifcfg(s_8021x, + ifcfg, + phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT", + phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT : NM_SETTING_802_1X_CA_CERT, + NULL, + error)) + return FALSE; + _secret_set_from_ifcfg( + s_8021x, + ifcfg, + keys_ifcfg, + phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD", + phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD); + + /* Private key */ + if (!_cert_set_from_ifcfg(s_8021x, + ifcfg, + phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY", + phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY + : NM_SETTING_802_1X_PRIVATE_KEY, + &privkey, + error)) + return FALSE; + _secret_set_from_ifcfg(s_8021x, + ifcfg, + keys_ifcfg, + phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD" + : "IEEE_8021X_PRIVATE_KEY_PASSWORD", + phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD + : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + + /* Client certificate */ + client_cert_var = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT"; + client_cert_prop = + phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT; + if (!_cert_set_from_ifcfg(s_8021x, + ifcfg, + client_cert_var, + client_cert_prop, + &client_cert, + error)) + return FALSE; + _secret_set_from_ifcfg(s_8021x, + ifcfg, + keys_ifcfg, + phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD" + : "IEEE_8021X_CLIENT_CERT_PASSWORD", + phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD + : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD); + + /* In the past when the private key and client certificate + * were the same PKCS #12 file we used to write only the + * private key variable. Still support that even if it means + * that we have to look into the file content, which makes + * the connection not self-contained. + */ + if (!client_cert && privkey && !svGetValue(ifcfg, client_cert_var, &value_to_free)) { + if (phase2) + format = nm_setting_802_1x_get_phase2_private_key_format(s_8021x); + else + format = nm_setting_802_1x_get_private_key_format(s_8021x); + + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + g_object_set(s_8021x, client_cert_prop, privkey, NULL); + } + + return TRUE; +} + +static gboolean +parse_8021x_phase2_auth(shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + GError ** error) +{ + gs_free char * inner_auth = NULL; + gs_free char * v_free = NULL; + const char * v; + gs_free const char **list = NULL; + const char *const * iter; + guint num_auth = 0; + guint num_autheap = 0; + + v = svGetValueStr(ifcfg, "IEEE_8021X_INNER_AUTH_METHODS", &v_free); + if (!v) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing IEEE_8021X_INNER_AUTH_METHODS"); + return FALSE; + } + + inner_auth = g_ascii_strdown(v, -1); + list = nm_utils_strsplit_set(inner_auth, " "); + for (iter = list; iter && *iter; iter++) { + if (NM_IN_STRSET(*iter, "pap", "chap", "mschap", "mschapv2", "gtc", "otp", "md5")) { + if (num_auth == 0) { + if (!eap_simple_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error)) + return FALSE; + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, *iter, NULL); + } + num_auth++; + } else if (nm_streq(*iter, "tls")) { + if (num_auth == 0) { + if (!eap_tls_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error)) + return FALSE; + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "tls", NULL); + } + num_auth++; + } else if (NM_IN_STRSET(*iter, "eap-md5", "eap-mschapv2", "eap-otp", "eap-gtc")) { + if (num_autheap == 0) { + if (!eap_simple_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error)) + return FALSE; + g_object_set(s_8021x, + NM_SETTING_802_1X_PHASE2_AUTHEAP, + (*iter + NM_STRLEN("eap-")), + NULL); + } + num_autheap++; + } else if (nm_streq(*iter, "eap-tls")) { + if (num_autheap == 0) { + if (!eap_tls_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error)) + return FALSE; + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, "tls", NULL); + } + num_autheap++; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'", + *iter); + return FALSE; + } + } + + if (num_auth > 1) + PARSE_WARNING("Discarded extra phase2 authentication methods"); + if (num_auth > 1) + PARSE_WARNING("Discarded extra phase2 EAP authentication methods"); + + if (!num_auth && !num_autheap) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "No phase2 authentication method found"); + return FALSE; + } + + return TRUE; +} + +static gboolean +eap_peap_reader(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error) +{ + gs_free char *value = NULL; + const char * v; + + if (!_cert_set_from_ifcfg(s_8021x, + ifcfg, + "IEEE_8021X_CA_CERT", + NM_SETTING_802_1X_CA_CERT, + NULL, + error)) + return FALSE; + _secret_set_from_ifcfg(s_8021x, + ifcfg, + keys_ifcfg, + "IEEE_8021X_CA_CERT_PASSWORD", + NM_SETTING_802_1X_CA_CERT_PASSWORD); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_PEAP_VERSION", &value); + if (v) { + if (!strcmp(v, "0")) + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL); + else if (!strcmp(v, "1")) + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL); + else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unknown IEEE_8021X_PEAP_VERSION value '%s'", + v); + return FALSE; + } + } + + if (svGetValueBoolean(ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", FALSE)) + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1", NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_ANON_IDENTITY", &value); + if (v) + g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL); + + if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error)) + return FALSE; + + return TRUE; +} + +static gboolean +eap_ttls_reader(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error) +{ + gs_free char *value = NULL; + const char * v; + + if (!_cert_set_from_ifcfg(s_8021x, + ifcfg, + "IEEE_8021X_CA_CERT", + NM_SETTING_802_1X_CA_CERT, + NULL, + error)) + return FALSE; + _secret_set_from_ifcfg(s_8021x, + ifcfg, + keys_ifcfg, + "IEEE_8021X_CA_CERT_PASSWORD", + NM_SETTING_802_1X_CA_CERT_PASSWORD); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_ANON_IDENTITY", &value); + if (v) + g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL); + + if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error)) + return FALSE; + + return TRUE; +} + +static gboolean +eap_fast_reader(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error) +{ + gs_free char * anon_ident = NULL; + gs_free char * pac_file = NULL; + gs_free char * real_pac_path = NULL; + gs_free char * fast_provisioning = NULL; + const char *const *iter; + const char * pac_prov_str; + gboolean allow_unauth = FALSE, allow_auth = FALSE; + + pac_file = svGetValueStr_cp(ifcfg, "IEEE_8021X_PAC_FILE"); + if (pac_file) { + real_pac_path = get_full_file_path(svFileGetName(ifcfg), pac_file); + g_object_set(s_8021x, NM_SETTING_802_1X_PAC_FILE, real_pac_path, NULL); + } + + fast_provisioning = svGetValueStr_cp(ifcfg, "IEEE_8021X_FAST_PROVISIONING"); + if (fast_provisioning) { + gs_free const char **list = NULL; + + list = nm_utils_strsplit_set(fast_provisioning, " \t"); + for (iter = list; iter && *iter; iter++) { + if (strcmp(*iter, "allow-unauth") == 0) + allow_unauth = TRUE; + else if (strcmp(*iter, "allow-auth") == 0) + allow_auth = TRUE; + else { + PARSE_WARNING( + "invalid IEEE_8021X_FAST_PROVISIONING '%s' " + "(space-separated list of these values [allow-auth, allow-unauth] expected)", + *iter); + } + } + } + pac_prov_str = allow_unauth ? (allow_auth ? "3" : "1") : (allow_auth ? "2" : "0"); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, pac_prov_str, NULL); + + if (!pac_file && !(allow_unauth || allow_auth)) { + g_set_error( + error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "IEEE_8021X_PAC_FILE not provided and EAP-FAST automatic PAC provisioning disabled"); + return FALSE; + } + + anon_ident = svGetValueStr_cp(ifcfg, "IEEE_8021X_ANON_IDENTITY"); + if (anon_ident) + g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, anon_ident, NULL); + + if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error)) + return FALSE; + + return TRUE; +} + +typedef struct { + const char *method; + gboolean (*reader)(const char * eap_method, + shvarFile * ifcfg, + shvarFile * keys_ifcfg, + NMSetting8021x *s_8021x, + gboolean phase2, + GError ** error); + gboolean wifi_phase2_only; +} EAPReader; + +static EAPReader eap_readers[] = {{"md5", eap_simple_reader, TRUE}, + {"pap", eap_simple_reader, TRUE}, + {"chap", eap_simple_reader, TRUE}, + {"mschap", eap_simple_reader, TRUE}, + {"mschapv2", eap_simple_reader, TRUE}, + {"leap", eap_simple_reader, FALSE}, + {"pwd", eap_simple_reader, FALSE}, + {"tls", eap_tls_reader, FALSE}, + {"peap", eap_peap_reader, FALSE}, + {"ttls", eap_ttls_reader, FALSE}, + {"fast", eap_fast_reader, FALSE}, + {NULL, NULL}}; + +static void +read_8021x_list_value(shvarFile * ifcfg, + const char * ifcfg_var_name, + NMSetting8021x *setting, + const char * prop_name) +{ + gs_free char * value = NULL; + gs_free const char **strv = NULL; + const char * v; + + g_return_if_fail(ifcfg != NULL); + g_return_if_fail(ifcfg_var_name != NULL); + g_return_if_fail(prop_name != NULL); + + v = svGetValueStr(ifcfg, ifcfg_var_name, &value); + if (!v) + return; + + strv = nm_utils_strsplit_set(v, " \t"); + if (strv) + g_object_set(setting, prop_name, strv, NULL); +} + +static NMSetting8021x * +fill_8021x(shvarFile *ifcfg, const char *file, const char *key_mgmt, gboolean wifi, GError **error) +{ + nm_auto_shvar_file_close shvarFile *keys_ifcfg = NULL; + gs_unref_object NMSetting8021x *s_8021x = NULL; + gs_free char * value = NULL; + const char * v; + gs_free const char ** list = NULL; + const char *const * iter; + gint64 timeout; + int i_val; + + v = svGetValueStr(ifcfg, "IEEE_8021X_EAP_METHODS", &value); + if (!v) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing IEEE_8021X_EAP_METHODS for key management '%s'", + key_mgmt); + return NULL; + } + + list = nm_utils_strsplit_set(v, " "); + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + + /* Read in the lookaside keys_ifcfg file, if present */ + keys_ifcfg = utils_get_keys_ifcfg(file, FALSE); + + /* Validate and handle each EAP method */ + for (iter = list; iter && *iter; iter++) { + EAPReader * eap = &eap_readers[0]; + gboolean found = FALSE; + gs_free char *lower = NULL; + + lower = g_ascii_strdown(*iter, -1); + while (eap->method) { + if (strcmp(eap->method, lower)) + goto next; + + /* Some EAP methods don't provide keying material, thus they + * cannot be used with Wi-Fi unless they are an inner method + * used with TTLS or PEAP or whatever. + */ + if (wifi && eap->wifi_phase2_only) { + PARSE_WARNING("ignored invalid IEEE_8021X_EAP_METHOD '%s'; not allowed for wifi", + lower); + goto next; + } + + /* Parse EAP method specific options */ + if (!(*eap->reader)(lower, ifcfg, keys_ifcfg, s_8021x, FALSE, error)) + return NULL; + + nm_setting_802_1x_add_eap_method(s_8021x, lower); + found = TRUE; + break; + +next: + eap++; + } + + if (!found) + PARSE_WARNING("ignored unknown IEEE_8021X_EAP_METHOD '%s'", lower); + } + + if (nm_setting_802_1x_get_num_eap_methods(s_8021x) == 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "No valid EAP methods found in IEEE_8021X_EAP_METHODS"); + return NULL; + } + + g_object_set(s_8021x, + NM_SETTING_802_1X_SYSTEM_CA_CERTS, + svGetValueBoolean(ifcfg, "IEEE_8021X_SYSTEM_CA_CERTS", FALSE), + NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_SUBJECT_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_SUBJECT_MATCH, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_SUBJECT_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, v, NULL); + + i_val = NM_SETTING_802_1X_AUTH_FLAGS_NONE; + if (!svGetValueEnum(ifcfg, + "IEEE_8021X_PHASE1_AUTH_FLAGS", + nm_setting_802_1x_auth_flags_get_type(), + &i_val, + error)) + return NULL; + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, (guint) i_val, NULL); + + read_8021x_list_value(ifcfg, + "IEEE_8021X_ALTSUBJECT_MATCHES", + s_8021x, + NM_SETTING_802_1X_ALTSUBJECT_MATCHES); + read_8021x_list_value(ifcfg, + "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES", + s_8021x, + NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_DOMAIN_SUFFIX_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_DOMAIN_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_DOMAIN_MATCH, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_DOMAIN_MATCH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH, v, NULL); + + timeout = svGetValueInt64(ifcfg, "IEEE_8021X_AUTH_TIMEOUT", 10, 0, G_MAXINT32, 0); + g_object_set(s_8021x, NM_SETTING_802_1X_AUTH_TIMEOUT, (int) timeout, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_CA_PATH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_CA_PATH, v, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_CA_PATH", &value); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_CA_PATH, v, NULL); + + g_object_set(s_8021x, + NM_SETTING_802_1X_OPTIONAL, + svGetValueBoolean(ifcfg, "IEEE_8021X_OPTIONAL", FALSE), + NULL); + + _secret_set_from_ifcfg(s_8021x, ifcfg, keys_ifcfg, "IEEE_8021X_PIN", NM_SETTING_802_1X_PIN); + + return g_steal_pointer(&s_8021x); +} + +static NMSetting * +make_wpa_setting(shvarFile * ifcfg, + const char * file, + GBytes * ssid, + gboolean adhoc, + NMSetting8021x **s_8021x, + GError ** error) +{ + gs_unref_object NMSettingWirelessSecurity *wsec = NULL; + gs_free char * value = NULL; + const char * v; + gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_owe = FALSE, wpa_eap = FALSE, ieee8021x = FALSE, + wpa3_eap = FALSE; + int i_val; + GError *local = NULL; + + wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new()); + + v = svGetValueStr(ifcfg, "KEY_MGMT", &value); + wpa_psk = nm_streq0(v, "WPA-PSK"); + wpa_sae = nm_streq0(v, "SAE"); + wpa_owe = nm_streq0(v, "OWE"); + wpa_eap = nm_streq0(v, "WPA-EAP"); + wpa3_eap = nm_streq0(v, "WPA-EAP-SUITE-B-192"); + ieee8021x = nm_streq0(v, "IEEE8021X"); + if (!wpa_psk && !wpa_sae && !wpa_owe && !wpa_eap && !wpa3_eap && !ieee8021x) + return NULL; /* Not WPA or Dynamic WEP */ + + /* WPS */ + i_val = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT; + if (!svGetValueEnum(ifcfg, + "WPS_METHOD", + nm_setting_wireless_security_wps_method_get_type(), + &i_val, + error)) + return NULL; + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, (guint) i_val, NULL); + + /* Pairwise and Group ciphers (only relevant for WPA/RSN) */ + if (wpa_psk || wpa_sae || wpa_owe || wpa_eap) { + fill_wpa_ciphers(ifcfg, wsec, FALSE, adhoc); + fill_wpa_ciphers(ifcfg, wsec, TRUE, adhoc); + } + + /* WPA and/or RSN */ + if (adhoc) { + /* Ad-Hoc mode only supports RSN proto */ + nm_setting_wireless_security_add_proto(wsec, "rsn"); + } else { + gs_free char *value2 = NULL; + const char * v2; + + v2 = svGetValueStr(ifcfg, "WPA_ALLOW_WPA", &value2); + if (v2 && svParseBoolean(v2, TRUE)) + nm_setting_wireless_security_add_proto(wsec, "wpa"); + + nm_clear_g_free(&value2); + v2 = svGetValueStr(ifcfg, "WPA_ALLOW_WPA2", &value2); + if (v2 && svParseBoolean(v2, TRUE)) + nm_setting_wireless_security_add_proto(wsec, "rsn"); + } + + if (wpa_psk || wpa_sae) { + NMSettingSecretFlags psk_flags; + + psk_flags = _secret_read_ifcfg_flags(ifcfg, "WPA_PSK_FLAGS"); + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, psk_flags, NULL); + + /* Read PSK if it's system-owned */ + if (psk_flags == NM_SETTING_SECRET_FLAG_NONE) { + gs_free char *psk = NULL; + + psk = parse_wpa_psk(ifcfg, file, ssid, &local); + if (psk) + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk, NULL); + else if (local) { + g_propagate_error(error, local); + return NULL; + } + } + + if (wpa_psk) + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL); + else { + nm_assert(wpa_sae); + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL); + } + } else { + nm_assert(wpa_eap || wpa3_eap || ieee8021x || wpa_owe); + + /* Adhoc mode is mutually exclusive with any 802.1x-based authentication */ + if (adhoc) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ad-Hoc mode cannot be used with KEY_MGMT type '%s'", + v); + return NULL; + } + + if (wpa_owe) { + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL); + } else { + *s_8021x = fill_8021x(ifcfg, file, v, TRUE, error); + if (!*s_8021x) + return NULL; + + { + gs_free char *lower = g_ascii_strdown(v, -1); + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL); + } + } + } + + i_val = NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT; + if (!svGetValueEnum(ifcfg, "PMF", nm_setting_wireless_security_pmf_get_type(), &i_val, error)) + return NULL; + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PMF, i_val, NULL); + + i_val = NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT; + if (!svGetValueEnum(ifcfg, "FILS", nm_setting_wireless_security_fils_get_type(), &i_val, error)) + return NULL; + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_FILS, i_val, NULL); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "SECURITYMODE", &value); + if (NM_IN_STRSET(v, NULL, "open")) + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, v, NULL); + + return (NMSetting *) g_steal_pointer(&wsec); +} + +static NMSetting * +make_leap_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + gs_unref_object NMSettingWirelessSecurity *wsec = NULL; + shvarFile * keys_ifcfg; + gs_free char * value = NULL; + NMSettingSecretFlags flags; + + wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new()); + + value = svGetValueStr_cp(ifcfg, "KEY_MGMT"); + if (!value || strcmp(value, "IEEE8021X")) + return NULL; + nm_clear_g_free(&value); + + value = svGetValueStr_cp(ifcfg, "SECURITYMODE"); + if (!value || g_ascii_strcasecmp(value, "leap")) + return NULL; /* Not LEAP */ + nm_clear_g_free(&value); + + flags = _secret_read_ifcfg_flags(ifcfg, "IEEE_8021X_PASSWORD_FLAGS"); + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, flags, NULL); + + /* Read LEAP password if it's system-owned */ + if (flags == NM_SETTING_SECRET_FLAG_NONE) { + value = svGetValueStr_cp(ifcfg, "IEEE_8021X_PASSWORD"); + if (!value) { + /* Try to get keys from the "shadow" key file */ + keys_ifcfg = utils_get_keys_ifcfg(file, FALSE); + if (keys_ifcfg) { + value = svGetValueStr_cp(keys_ifcfg, "IEEE_8021X_PASSWORD"); + svCloseFile(keys_ifcfg); + } + } + if (value && strlen(value)) + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, value, NULL); + nm_clear_g_free(&value); + } + + value = svGetValueStr_cp(ifcfg, "IEEE_8021X_IDENTITY"); + if (!value) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing LEAP identity"); + return NULL; + } + g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, value, NULL); + nm_clear_g_free(&value); + + g_object_set(wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "leap", + NULL); + + return (NMSetting *) g_steal_pointer(&wsec); +} + +static NMSetting * +make_wireless_security_setting(shvarFile * ifcfg, + const char * file, + GBytes * ssid, + gboolean adhoc, + NMSetting8021x **s_8021x, + GError ** error) +{ + NMSetting *wsec; + + g_return_val_if_fail(error && !*error, NULL); + + if (!adhoc) { + wsec = make_leap_setting(ifcfg, file, error); + if (wsec) + return wsec; + else if (*error) + return NULL; + } + + wsec = make_wpa_setting(ifcfg, file, ssid, adhoc, s_8021x, error); + if (wsec) + return wsec; + else if (*error) + return NULL; + + wsec = make_wep_setting(ifcfg, file, error); + if (wsec) + return wsec; + else if (*error) + return NULL; + + return NULL; /* unencrypted */ +} + +static const char ** +transform_hwaddr_blacklist(const char *blacklist) +{ + const char **strv; + gsize i, j; + + strv = nm_utils_strsplit_set(blacklist, " \t"); + if (!strv) + return NULL; + for (i = 0, j = 0; strv[j]; j++) { + const char *s = strv[j]; + + if (!nm_utils_hwaddr_valid(s, ETH_ALEN)) { + PARSE_WARNING("invalid MAC in HWADDR_BLACKLIST '%s'", s); + continue; + } + strv[i++] = s; + } + strv[i] = NULL; + return strv; +} + +static NMSetting * +make_wireless_setting(shvarFile *ifcfg, GError **error) +{ + NMSettingWireless * s_wireless; + const char * cvalue; + char * value = NULL; + gint64 chan = 0; + NMSettingMacRandomization mac_randomization; + NMSettingWirelessPowersave powersave = NM_SETTING_WIRELESS_POWERSAVE_DEFAULT; + NMTernary ternary; + + s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + + value = svGetValueStr_cp(ifcfg, "HWADDR"); + if (value) { + value = g_strstrip(value); + g_object_set(s_wireless, NM_SETTING_WIRELESS_MAC_ADDRESS, value, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "MACADDR"); + if (value) { + value = g_strstrip(value); + g_object_set(s_wireless, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, value, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "GENERATE_MAC_ADDRESS_MASK"); + g_object_set(s_wireless, NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK, value, NULL); + g_free(value); + + cvalue = svGetValueStr(ifcfg, "HWADDR_BLACKLIST", &value); + if (cvalue) { + gs_free const char **strv = NULL; + + strv = transform_hwaddr_blacklist(cvalue); + g_object_set(s_wireless, NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST, strv, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "ESSID"); + if (value) { + gs_unref_bytes GBytes *bytes = NULL; + gsize ssid_len = 0; + gsize value_len = strlen(value); + + if (value_len > 2 && (value_len % 2) == 0 && g_str_has_prefix(value, "0x") + && NM_STRCHAR_ALL(&value[2], ch, g_ascii_isxdigit(ch))) { + /* interpret the value as hex-digits iff value starts + * with "0x" followed by pairs of hex digits */ + bytes = nm_utils_hexstr2bin(&value[2]); + } else + bytes = g_bytes_new(value, value_len); + + ssid_len = g_bytes_get_size(bytes); + if (ssid_len > 32 || ssid_len == 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid SSID '%s' (size %zu not between 1 and 32 inclusive)", + value, + ssid_len); + g_free(value); + goto error; + } + + g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, bytes, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "MODE"); + if (value) { + char * lcase; + const char *mode = NULL; + + lcase = g_ascii_strdown(value, -1); + g_free(value); + + if (!strcmp(lcase, "ad-hoc")) { + mode = "adhoc"; + } else if (!strcmp(lcase, "ap")) { + mode = "ap"; + } else if (!strcmp(lcase, "managed") || !strcmp(lcase, "auto")) { + mode = "infrastructure"; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')", + lcase); + g_free(lcase); + goto error; + } + g_free(lcase); + + g_object_set(s_wireless, NM_SETTING_WIRELESS_MODE, mode, NULL); + } + + value = svGetValueStr_cp(ifcfg, "BSSID"); + if (value) { + value = g_strstrip(value); + g_object_set(s_wireless, NM_SETTING_WIRELESS_BSSID, value, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "CHANNEL"); + if (value) { + chan = _nm_utils_ascii_str_to_int64(value, 10, 1, 196, 0); + if (chan == 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid wireless channel '%s'", + value); + g_free(value); + goto error; + } + g_object_set(s_wireless, NM_SETTING_WIRELESS_CHANNEL, (guint32) chan, NULL); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "BAND"); + if (value) { + if (!strcmp(value, "a")) { + if (chan && chan <= 14) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Band '%s' invalid for channel %u", + value, + (guint32) chan); + g_free(value); + goto error; + } + } else if (!strcmp(value, "bg")) { + if (chan && chan > 14) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Band '%s' invalid for channel %u", + value, + (guint32) chan); + g_free(value); + goto error; + } + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid wireless band '%s'", + value); + g_free(value); + goto error; + } + g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, value, NULL); + g_free(value); + } else if (chan > 0) { + if (chan > 14) + g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "a", NULL); + else + g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "bg", NULL); + } + + value = svGetValueStr_cp(ifcfg, "MTU"); + if (value) { + int mtu; + + mtu = _nm_utils_ascii_str_to_int64(value, 10, 0, 50000, -1); + if (mtu == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid wireless MTU '%s'", + value); + g_free(value); + goto error; + } + g_object_set(s_wireless, NM_SETTING_WIRELESS_MTU, (guint) mtu, NULL); + g_free(value); + } + + g_object_set(s_wireless, + NM_SETTING_WIRELESS_HIDDEN, + svGetValueBoolean(ifcfg, "SSID_HIDDEN", FALSE), + NULL); + + cvalue = svGetValue(ifcfg, "POWERSAVE", &value); + if (cvalue) { + if (!strcmp(cvalue, "default")) + powersave = NM_SETTING_WIRELESS_POWERSAVE_DEFAULT; + else if (!strcmp(cvalue, "ignore")) + powersave = NM_SETTING_WIRELESS_POWERSAVE_IGNORE; + else if (!strcmp(cvalue, "disable") || !strcmp(cvalue, "no")) + powersave = NM_SETTING_WIRELESS_POWERSAVE_DISABLE; + else if (!strcmp(cvalue, "enable") || !strcmp(cvalue, "yes")) + powersave = NM_SETTING_WIRELESS_POWERSAVE_ENABLE; + else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid POWERSAVE value '%s'", + cvalue); + g_free(value); + goto error; + } + g_free(value); + } + + g_object_set(s_wireless, NM_SETTING_WIRELESS_POWERSAVE, powersave, NULL); + + cvalue = svGetValue(ifcfg, "MAC_ADDRESS_RANDOMIZATION", &value); + if (cvalue) { + if (strcmp(cvalue, "default") == 0) + mac_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT; + else if (strcmp(cvalue, "never") == 0) + mac_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER; + else if (strcmp(cvalue, "always") == 0) + mac_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS; + else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid MAC_ADDRESS_RANDOMIZATION value '%s'", + cvalue); + g_free(value); + goto error; + } + g_free(value); + } else + mac_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT; + + g_object_set(s_wireless, + NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION, + mac_randomization, + NULL); + + ternary = svGetValueTernary(ifcfg, "AP_ISOLATION"); + if (ternary != NM_TERNARY_DEFAULT) { + g_object_set(s_wireless, NM_SETTING_WIRELESS_AP_ISOLATION, ternary, NULL); + } + + return NM_SETTING(s_wireless); + +error: + if (s_wireless) + g_object_unref(s_wireless); + return NULL; +} + +static NMConnection * +wireless_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * wireless_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GBytes * ssid; + NMSetting * security_setting = NULL; + gs_free char * ssid_utf8 = NULL; + const char * mode; + gboolean adhoc = FALSE; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + connection = nm_simple_connection_new(); + + /* Wireless */ + wireless_setting = make_wireless_setting(ifcfg, error); + if (!wireless_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, wireless_setting); + + ssid = nm_setting_wireless_get_ssid(NM_SETTING_WIRELESS(wireless_setting)); + mode = nm_setting_wireless_get_mode(NM_SETTING_WIRELESS(wireless_setting)); + if (mode && !strcmp(mode, "adhoc")) + adhoc = TRUE; + + /* Wireless security */ + security_setting = make_wireless_security_setting(ifcfg, file, ssid, adhoc, &s_8021x, &local); + if (local) { + g_object_unref(connection); + g_propagate_error(error, local); + return NULL; + } + if (security_setting) { + nm_connection_add_setting(connection, security_setting); + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + } + + if (ssid) + ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); + + /* Connection */ + con_setting = make_connection_setting(file, + ifcfg, + NM_SETTING_WIRELESS_SETTING_NAME, + nm_str_not_empty(ssid_utf8) ?: "unmanaged", + NULL); + + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + return connection; +} + +typedef struct { + const char *optname; + union { + guint32 u32; + NMTernary nmternary; + } v; + gboolean has_value; +} NMEthtoolIfcfgOption; + +/* returns an 'iterator' to words + * pointing to the next unprocessed option or NULL + * in case of failure */ +static const char ** +_next_ethtool_options_nmternary(const char ** words, + NMEthtoolType ethtool_type, + NMEthtoolIfcfgOption *out_value) +{ + const char * opt; + const char * opt_val; + const NMEthtoolData *d = NULL; + NMTernary onoff = NM_TERNARY_DEFAULT; + + nm_assert(out_value); + + out_value->has_value = FALSE; + out_value->optname = NULL; + + if (!words || !words[0] || !words[1]) + return NULL; + + opt = *words; + opt_val = *(++words); + + if (nm_streq0(opt_val, "on")) + onoff = NM_TERNARY_TRUE; + else if (nm_streq0(opt_val, "off")) + onoff = NM_TERNARY_FALSE; + + d = nms_ifcfg_rh_utils_get_ethtool_by_name(opt, ethtool_type); + if (!d) { + if (onoff != NM_TERNARY_DEFAULT) { + /* the next value is just the on/off argument. Skip it too. */ + ++words; + } + + /* silently ignore unsupported offloading features. */ + return words; + } + + if (onoff == NM_TERNARY_DEFAULT) { + PARSE_WARNING("Expects on/off argument for feature '%s'", opt); + return words; + } + + out_value->has_value = TRUE; + out_value->optname = d->optname; + out_value->v.nmternary = onoff; + + return ++words; +} + +/* returns an 'iterator' to words + * pointing to the next unprocessed option or NULL + * in case of failure */ +static const char ** +_next_ethtool_options_uint32(const char ** words, + NMEthtoolType ethtool_type, + NMEthtoolIfcfgOption *out_value) +{ + gint64 i64; + const char * opt; + const char * opt_val; + const NMEthtoolData *d = NULL; + + nm_assert(out_value); + + out_value->has_value = FALSE; + out_value->optname = NULL; + + if (!words || !words[0] || !words[1]) + return NULL; + + opt = *words; + opt_val = *(++words); + + i64 = _nm_utils_ascii_str_to_int64(opt_val, 10, 0, G_MAXUINT32, -1); + + d = nms_ifcfg_rh_utils_get_ethtool_by_name(opt, ethtool_type); + if (!d) { + if (i64 != -1) { + /* the next value is just the on/off argument. Skip it too. */ + ++words; + } + + /* silently ignore unsupported offloading features. */ + return words; + } + + out_value->has_value = TRUE; + out_value->optname = d->optname; + out_value->v.u32 = (guint32) i64; + + return ++words; +} + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _get_ethtool_type_by_name, + NMEthtoolType, + { nm_assert(name); }, + { return NM_ETHTOOL_TYPE_UNKNOWN; }, + {"--coalesce", NM_ETHTOOL_TYPE_COALESCE}, + {"--features", NM_ETHTOOL_TYPE_FEATURE}, + {"--offload", NM_ETHTOOL_TYPE_FEATURE}, + {"--set-ring", NM_ETHTOOL_TYPE_RING}, + {"-C", NM_ETHTOOL_TYPE_COALESCE}, + {"-G", NM_ETHTOOL_TYPE_RING}, + {"-K", NM_ETHTOOL_TYPE_FEATURE}, ); + +static void +parse_ethtool_option(const char * value, + NMSettingWiredWakeOnLan *out_flags, + char ** out_password, + gboolean * out_autoneg, + guint32 * out_speed, + const char ** out_duplex, + NMSettingEthtool ** out_s_ethtool) +{ + guint i; + const char ** w_iter; + NMEthtoolIfcfgOption ifcfg_option; + gs_free const char **words = NULL; + NMEthtoolType ethtool_type = NM_ETHTOOL_TYPE_UNKNOWN; + + words = nm_utils_strsplit_set(value, " \t\n"); + if (!words) + return; + + if (words[0]) + ethtool_type = _get_ethtool_type_by_name(words[0]); + + if (ethtool_type != NM_ETHTOOL_TYPE_UNKNOWN) { + if (!words[1]) { + /* first argument must be the interface name. This is invalid. */ + return; + } + + if (!*out_s_ethtool) + *out_s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + + /* skip ethtool type && interface name */ + w_iter = &words[2]; + + while (w_iter && *w_iter) { + if (ethtool_type == NM_ETHTOOL_TYPE_FEATURE) { + w_iter = _next_ethtool_options_nmternary(w_iter, ethtool_type, &ifcfg_option); + + if (ifcfg_option.has_value) { + nm_setting_option_set_boolean(NM_SETTING(*out_s_ethtool), + ifcfg_option.optname, + ifcfg_option.v.nmternary != NM_TERNARY_FALSE); + } + } + if (NM_IN_SET(ethtool_type, NM_ETHTOOL_TYPE_COALESCE, NM_ETHTOOL_TYPE_RING)) { + w_iter = _next_ethtool_options_uint32(w_iter, ethtool_type, &ifcfg_option); + + if (ifcfg_option.has_value) { + nm_setting_option_set_uint32(NM_SETTING(*out_s_ethtool), + ifcfg_option.optname, + ifcfg_option.v.u32); + } + } + } + + return; + } + + /* /sbin/ethtool -s ${REALDEVICE} $opts */ + for (i = 0; words[i];) { + const char *opt = words[i]; + const char *opt_val = words[++i]; + + if (nm_streq(opt, "autoneg")) { + if (!opt_val) { + PARSE_WARNING("Auto-negotiation option missing"); + break; + } + i++; + + if (nm_streq(opt_val, "off")) + *out_autoneg = FALSE; + else if (nm_streq(opt_val, "on")) + *out_autoneg = TRUE; + else + PARSE_WARNING("Auto-negotiation unknown value: %s", opt_val); + continue; + } + + if (nm_streq(opt, "speed")) { + guint32 speed; + + if (!opt_val) { + PARSE_WARNING("Speed option missing"); + break; + } + i++; + + speed = _nm_utils_ascii_str_to_int64(opt_val, 10, 0, G_MAXUINT32, 0); + if (errno == 0) + *out_speed = speed; + else + PARSE_WARNING("Speed value '%s' is invalid", opt_val); + continue; + } + + if (nm_streq(opt, "duplex")) { + if (!opt_val) { + PARSE_WARNING("Duplex option missing"); + break; + } + i++; + + if (nm_streq(opt_val, "half")) + *out_duplex = "half"; + else if (nm_streq(opt_val, "full")) + *out_duplex = "full"; + else + PARSE_WARNING("Duplex unknown value: %s", opt_val); + continue; + } + + if (nm_streq(opt, "wol")) { + NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE; + + if (!opt_val) { + PARSE_WARNING("Wake-on-LAN options missing"); + break; + } + i++; + + for (; *opt_val; opt_val++) { + switch (*opt_val) { + case 'p': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_PHY; + break; + case 'u': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST; + break; + case 'm': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST; + break; + case 'b': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST; + break; + case 'a': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_ARP; + break; + case 'g': + wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC; + break; + case 's': + break; + case 'd': + wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE; + break; + default: + PARSE_WARNING("unrecognized Wake-on-LAN option '%c'", *opt_val); + } + } + + *out_flags = wol_flags; + continue; + } + + if (nm_streq(opt, "sopass")) { + if (!opt_val) { + PARSE_WARNING("Wake-on-LAN password missing"); + break; + } + i++; + + if (nm_utils_hwaddr_valid(opt_val, ETH_ALEN)) { + nm_clear_g_free(out_password); + *out_password = g_strdup(opt_val); + } else + PARSE_WARNING("Wake-on-LAN password '%s' is invalid", opt_val); + continue; + } + + /* Silently skip unknown options */ + } +} + +static GPtrArray * +read_routing_rules_parse(shvarFile *ifcfg, gboolean routes_read) +{ + gs_unref_ptrarray GPtrArray *arr = NULL; + gs_free const char ** keys = NULL; + guint i, len; + + keys = svGetKeysSorted(ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6, &len); + if (len == 0) + return NULL; + + if (!routes_read) { + PARSE_WARNING("'rule-' or 'rule6-' files are present; Policy routing rules (ROUTING_RULE*) " + "settings are ignored"); + return NULL; + } + + arr = g_ptr_array_new_full(len, (GDestroyNotify) nm_ip_routing_rule_unref); + for (i = 0; i < len; i++) { + const char * key = keys[i]; + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free_error GError *local = NULL; + gs_free char * value_to_free = NULL; + const char * value; + gboolean key_is_ipv4; + + key_is_ipv4 = (key[NM_STRLEN("ROUTING_RULE")] == '_'); + nm_assert(key_is_ipv4 == NM_STR_HAS_PREFIX(key, "ROUTING_RULE_")); + nm_assert((!key_is_ipv4) == NM_STR_HAS_PREFIX(key, "ROUTING_RULE6_")); + + value = svGetValueStr(ifcfg, key, &value_to_free); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string( + value, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (key_is_ipv4 ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6), + NULL, + &local); + if (!rule) { + PARSE_WARNING("invalid routing rule %s=\"%s\": %s", key, value, local->message); + continue; + } + + g_ptr_array_add(arr, g_steal_pointer(&rule)); + } + + if (arr->len == 0) + return NULL; + + return g_steal_pointer(&arr); +} + +static void +read_routing_rules(shvarFile * ifcfg, + gboolean routes_read, + NMSettingIPConfig *s_ip4, + NMSettingIPConfig *s_ip6) +{ + gs_unref_ptrarray GPtrArray *routing_rules = NULL; + guint i; + + routing_rules = read_routing_rules_parse(ifcfg, routes_read); + if (!routing_rules) + return; + + for (i = 0; i < routing_rules->len; i++) { + NMIPRoutingRule *rule = routing_rules->pdata[i]; + + nm_setting_ip_config_add_routing_rule( + (nm_ip_routing_rule_get_addr_family(rule) == AF_INET) ? s_ip4 : s_ip6, + rule); + } +} + +static void +parse_ethtool_options(shvarFile *ifcfg, NMConnection *connection) +{ + NMSettingWired *s_wired; + gs_unref_object NMSettingEthtool *s_ethtool = NULL; + NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT; + gs_free char * ethtool_opts_free = NULL; + const char * ethtool_opts; + gs_free char * wol_password = NULL; + gs_free char * wol_value_free = NULL; + const char * tmp; + gboolean autoneg = FALSE; + guint32 speed = 0; + const char * duplex = NULL; + gboolean wired_found = FALSE; + + ethtool_opts = svGetValue(ifcfg, "ETHTOOL_OPTS", ðtool_opts_free); + if (ethtool_opts) { + wired_found = TRUE; + /* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */ + if (!ethtool_opts[0]) + wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE; + else { + gs_free const char **opts = NULL; + const char *const * iter; + + opts = nm_utils_strsplit_set(ethtool_opts, ";"); + for (iter = opts; iter && iter[0]; iter++) { + /* in case of repeated wol_passwords, parse_ethtool_option() + * will do the right thing and clear wol_password before resetting. */ + parse_ethtool_option(iter[0], + &wol_flags, + &wol_password, + &autoneg, + &speed, + &duplex, + &s_ethtool); + } + } + } + + /* ETHTOOL_WAKE_ON_LAN = ignore overrides WoL settings in ETHTOOL_OPTS */ + tmp = svGetValue(ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free); + if (tmp) + wired_found = TRUE; + if (nm_streq0(tmp, "ignore")) + wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE; + else if (tmp) + PARSE_WARNING("invalid ETHTOOL_WAKE_ON_LAN value '%s'", tmp); + + if (wol_password && !NM_FLAGS_HAS(wol_flags, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) { + PARSE_WARNING("Wake-on-LAN password not expected"); + nm_clear_g_free(&wol_password); + } + + s_wired = nm_connection_get_setting_wired(connection); + if (!s_wired && wired_found) { + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + } + if (s_wired) { + g_object_set(s_wired, + NM_SETTING_WIRED_WAKE_ON_LAN, + wol_flags, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, + wol_password, + NM_SETTING_WIRED_AUTO_NEGOTIATE, + autoneg, + NM_SETTING_WIRED_SPEED, + speed, + NM_SETTING_WIRED_DUPLEX, + duplex, + NULL); + } + + if (s_ethtool) { + nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ethtool))); + } +} + +static NMSetting * +make_wired_setting(shvarFile *ifcfg, const char *file, NMSetting8021x **s_8021x, GError **error) +{ + gs_unref_object NMSettingWired *s_wired = NULL; + const char * cvalue; + gs_free char * value = NULL; + gboolean found = FALSE; + + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + + cvalue = svGetValue(ifcfg, "MTU", &value); + if (cvalue) { + int mtu; + + mtu = _nm_utils_ascii_str_to_int64(cvalue, 0, 0, 65535, -1); + if (mtu >= 0) + g_object_set(s_wired, NM_SETTING_WIRED_MTU, (guint) mtu, NULL); + else + PARSE_WARNING("invalid MTU '%s'", cvalue); + nm_clear_g_free(&value); + found = TRUE; + } + + value = svGetValue_cp(ifcfg, "HWADDR"); + if (value) { + if (value[0] != '\0') { + value = g_strstrip(value); + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL); + } + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValue(ifcfg, "SUBCHANNELS", &value); + if (cvalue) { + if (cvalue[0] != '\0') { + const char *p = cvalue; + gboolean success = TRUE; + + /* basic sanity checks */ + while (*p) { + if (!g_ascii_isxdigit(*p) && (*p != ',') && (*p != '.')) { + PARSE_WARNING("invalid SUBCHANNELS '%s'", cvalue); + success = FALSE; + break; + } + p++; + } + + if (success) { + gs_free const char **chans = NULL; + guint32 num_chans; + + chans = nm_utils_strsplit_set(cvalue, ","); + num_chans = NM_PTRARRAY_LEN(chans); + if (num_chans < 2 || num_chans > 3) { + PARSE_WARNING("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)", + cvalue, + (unsigned) NM_PTRARRAY_LEN(chans)); + } else + g_object_set(s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL); + } + } + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValue(ifcfg, "PORTNAME", &value); + if (cvalue) { + if (cvalue[0] != '\0') + nm_setting_wired_add_s390_option(s_wired, "portname", cvalue); + found = TRUE; + nm_clear_g_free(&value); + } + + cvalue = svGetValue(ifcfg, "CTCPROT", &value); + if (cvalue) { + if (cvalue[0] != '\0') + nm_setting_wired_add_s390_option(s_wired, "ctcprot", cvalue); + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValue(ifcfg, "NETTYPE", &value); + if (cvalue) { + if (NM_IN_STRSET(cvalue, "qeth", "lcs", "ctc")) + g_object_set(s_wired, NM_SETTING_WIRED_S390_NETTYPE, cvalue, NULL); + else + PARSE_WARNING("unknown s390 NETTYPE '%s'", cvalue); + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValue(ifcfg, "OPTIONS", &value); + if (cvalue) + found = TRUE; + if (cvalue && cvalue[0]) { + gs_free const char **options = NULL; + gsize i; + + options = nm_utils_escaped_tokens_split(cvalue, NM_ASCII_SPACES); + for (i = 0; options && options[i]; i++) { + const char *line = options[i]; + const char *equals; + gboolean valid = FALSE; + + equals = strchr(line, '='); + if (equals) { + ((char *) equals)[0] = '\0'; + valid = nm_setting_wired_add_s390_option(s_wired, line, equals + 1); + } + if (!valid) + PARSE_WARNING("invalid s390 OPTION '%s'", line); + } + found = TRUE; + } + nm_clear_g_free(&value); + + cvalue = svGetValueStr(ifcfg, "MACADDR", &value); + if (cvalue) { + if (cvalue[0] != '\0') { + g_object_set(s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, cvalue, NULL); + } + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValueStr(ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value); + if (cvalue) { + if (cvalue[0] != '\0') { + g_object_set(s_wired, NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, cvalue, NULL); + } + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValueStr(ifcfg, "HWADDR_BLACKLIST", &value); + if (cvalue) { + gs_free const char **strv = NULL; + + strv = transform_hwaddr_blacklist(cvalue); + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, strv, NULL); + nm_clear_g_free(&value); + found = TRUE; + } + + cvalue = svGetValue(ifcfg, "KEY_MGMT", &value); + if (cvalue) + found = TRUE; + if (cvalue && cvalue[0] != '\0') { + if (!strcmp(cvalue, "IEEE8021X")) { + *s_8021x = fill_8021x(ifcfg, file, cvalue, FALSE, error); + if (!*s_8021x) + return NULL; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Unknown wired KEY_MGMT type '%s'", + cvalue); + return NULL; + } + } + nm_clear_g_free(&value); + + if (!found) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_SETTING_MISSING, + "The setting is missing"); + return NULL; + } + + return (NMSetting *) g_steal_pointer(&s_wired); +} + +static NMConnection * +wired_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * wired_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = make_connection_setting(file, ifcfg, NM_SETTING_WIRED_SETTING_NAME, NULL, NULL); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local); + if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) { + g_propagate_error(error, local); + g_object_unref(connection); + return NULL; + } + g_clear_error(&local); + + if (wired_setting) + nm_connection_add_setting(connection, wired_setting); + + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + return connection; +} + +static gboolean +parse_infiniband_p_key(shvarFile *ifcfg, int *out_p_key, char **out_parent, GError **error) +{ + char * device = NULL, *physdev = NULL, *pkey_id = NULL; + char * ifname = NULL; + int id; + gboolean ret = FALSE; + + device = svGetValueStr_cp(ifcfg, "DEVICE"); + if (!device) { + PARSE_WARNING("InfiniBand connection specified PKEY but not DEVICE"); + goto done; + } + + physdev = svGetValueStr_cp(ifcfg, "PHYSDEV"); + if (!physdev) { + PARSE_WARNING("InfiniBand connection specified PKEY but not PHYSDEV"); + goto done; + } + + pkey_id = svGetValueStr_cp(ifcfg, "PKEY_ID"); + if (!pkey_id) { + PARSE_WARNING("InfiniBand connection specified PKEY but not PKEY_ID"); + goto done; + } + + id = _nm_utils_ascii_str_to_int64(pkey_id, 0, 0, 0xFFFF, -1); + if (id == -1) { + PARSE_WARNING("invalid InfiniBand PKEY_ID '%s'", pkey_id); + goto done; + } + id = (id | 0x8000); + + ifname = g_strdup_printf("%s.%04x", physdev, (unsigned) id); + if (strcmp(device, ifname) != 0) { + PARSE_WARNING("InfiniBand DEVICE (%s) does not match PHYSDEV+PKEY_ID (%s)", device, ifname); + goto done; + } + + *out_p_key = id; + *out_parent = g_strdup(physdev); + ret = TRUE; + +done: + g_free(device); + g_free(physdev); + g_free(pkey_id); + g_free(ifname); + + if (!ret) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create InfiniBand setting"); + } + return ret; +} + +static NMSetting * +make_infiniband_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + NMSettingInfiniband *s_infiniband; + char * value = NULL; + + s_infiniband = NM_SETTING_INFINIBAND(nm_setting_infiniband_new()); + + value = svGetValueStr_cp(ifcfg, "MTU"); + if (value) { + int mtu; + + mtu = _nm_utils_ascii_str_to_int64(value, 0, 0, 65535, -1); + if (mtu >= 0) + g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MTU, (guint) mtu, NULL); + else + PARSE_WARNING("invalid MTU '%s'", value); + g_free(value); + } + + value = svGetValueStr_cp(ifcfg, "HWADDR"); + if (value) { + value = g_strstrip(value); + g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, value, NULL); + g_free(value); + } + + if (svGetValueBoolean(ifcfg, "CONNECTED_MODE", FALSE)) + g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", NULL); + else + g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL); + + if (svGetValueBoolean(ifcfg, "PKEY", FALSE)) { + gs_free char *parent = NULL; + int p_key; + + if (!parse_infiniband_p_key(ifcfg, &p_key, &parent, error)) { + g_object_unref(s_infiniband); + return NULL; + } + + g_object_set(s_infiniband, + NM_SETTING_INFINIBAND_P_KEY, + p_key, + NM_SETTING_INFINIBAND_PARENT, + parent, + NULL); + } + + return (NMSetting *) s_infiniband; +} + +static NMConnection * +infiniband_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection *connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * infiniband_setting = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = + make_connection_setting(file, ifcfg, NM_SETTING_INFINIBAND_SETTING_NAME, NULL, NULL); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + infiniband_setting = make_infiniband_setting(ifcfg, file, error); + if (!infiniband_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, infiniband_setting); + + return connection; +} + +static void +handle_bond_option(NMSettingBond *s_bond, const char *key, const char *value) +{ + gs_free char *sanitized = NULL; + const char * p = value; + + /* Remove any quotes or +/- from arp_ip_target */ + if (nm_streq0(key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET) && value && value[0]) { + char *j; + + if (*p == '\'' || *p == '"') + p++; + j = sanitized = g_malloc(strlen(p) + 1); + while (*p) { + if (*p != '+' && *p != '-' && *p != '\'' && *p != '"') + *j++ = *p; + p++; + } + *j++ = '\0'; + value = sanitized; + } + + if (!_nm_setting_bond_validate_option(key, value, NULL)) { + PARSE_WARNING("invalid bonding option '%s' = %s", key, value); + return; + } + + nm_setting_bond_add_option(s_bond, key, value); +} + +static NMSetting * +make_bond_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + NMSettingBond *s_bond; + gs_free char * value = NULL; + const char * v; + + v = svGetValueStr(ifcfg, "DEVICE", &value); + if (!v) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "mandatory DEVICE keyword missing"); + return NULL; + } + + s_bond = NM_SETTING_BOND(nm_setting_bond_new()); + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "BONDING_OPTS", &value); + if (v) { + gs_free const char **items = NULL; + const char *const * iter; + + items = nm_utils_strsplit_set(v, " "); + for (iter = items; iter && *iter; iter++) { + gs_free char *key = NULL; + const char * val; + + val = strchr(*iter, '='); + if (!val) + continue; + key = g_strndup(*iter, val - *iter); + val++; + if (key[0] && val[0]) + handle_bond_option(s_bond, key, val); + } + } + + return (NMSetting *) s_bond; +} + +static NMConnection * +bond_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * bond_setting = NULL; + NMSetting * wired_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = + make_connection_setting(file, ifcfg, NM_SETTING_BOND_SETTING_NAME, NULL, _("Bond")); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + bond_setting = make_bond_setting(ifcfg, file, error); + if (!bond_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, bond_setting); + + wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local); + if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) { + g_propagate_error(error, local); + g_object_unref(connection); + return NULL; + } + g_clear_error(&local); + + if (wired_setting) + nm_connection_add_setting(connection, wired_setting); + + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + return connection; +} + +static NMSetting * +make_team_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + NMSetting * s_team; + gs_free char *value_device = NULL; + gs_free char *value = NULL; + + if (!svGetValueStr(ifcfg, "DEVICE", &value_device)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "mandatory DEVICE keyword missing"); + return NULL; + } + + s_team = nm_setting_team_new(); + g_object_set(s_team, NM_SETTING_TEAM_CONFIG, svGetValue(ifcfg, "TEAM_CONFIG", &value), NULL); + return s_team; +} + +static NMConnection * +team_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * team_setting = NULL; + NMSetting * wired_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = + make_connection_setting(file, ifcfg, NM_SETTING_TEAM_SETTING_NAME, NULL, _("Team")); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + team_setting = make_team_setting(ifcfg, file, error); + if (!team_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, team_setting); + + wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local); + if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) { + g_propagate_error(error, local); + g_object_unref(connection); + return NULL; + } + g_clear_error(&local); + + if (wired_setting) + nm_connection_add_setting(connection, wired_setting); + + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + return connection; +} + +typedef enum { + BRIDGE_OPT_TYPE_MAIN, + BRIDGE_OPT_TYPE_OPTION, + BRIDGE_OPT_TYPE_PORT_MAIN, + BRIDGE_OPT_TYPE_PORT_OPTION, +} BridgeOptType; + +typedef void (*BridgeOptFunc)(NMSetting * setting, + gboolean stp, + const char * key, + const char * value, + BridgeOptType opt_type); + +static void +handle_bridge_option(NMSetting * setting, + gboolean stp, + const char * key, + const char * value, + BridgeOptType opt_type) +{ + static const struct { + const char * key; + const char * property_name; + BridgeOptType opt_type; + gboolean only_with_stp; + gboolean extended_bool; + } m /*etadata*/[] = { + {"DELAY", NM_SETTING_BRIDGE_FORWARD_DELAY, BRIDGE_OPT_TYPE_MAIN, .only_with_stp = TRUE}, + {"priority", NM_SETTING_BRIDGE_PRIORITY, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE}, + {"hello_time", NM_SETTING_BRIDGE_HELLO_TIME, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE}, + {"max_age", NM_SETTING_BRIDGE_MAX_AGE, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE}, + {"ageing_time", NM_SETTING_BRIDGE_AGEING_TIME, BRIDGE_OPT_TYPE_OPTION}, + {"multicast_last_member_count", + NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_last_member_interval", + NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_membership_interval", + NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_hash_max", NM_SETTING_BRIDGE_MULTICAST_HASH_MAX, BRIDGE_OPT_TYPE_OPTION}, + {"multicast_querier", NM_SETTING_BRIDGE_MULTICAST_QUERIER, BRIDGE_OPT_TYPE_OPTION}, + {"multicast_querier_interval", + NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_query_interval", + NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_query_response_interval", + NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_query_use_ifaddr", + NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_snooping", NM_SETTING_BRIDGE_MULTICAST_SNOOPING, BRIDGE_OPT_TYPE_OPTION}, + {"multicast_router", NM_SETTING_BRIDGE_MULTICAST_ROUTER, BRIDGE_OPT_TYPE_OPTION}, + {"multicast_startup_query_count", + NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT, + BRIDGE_OPT_TYPE_OPTION}, + {"multicast_startup_query_interval", + NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL, + BRIDGE_OPT_TYPE_OPTION}, + {"vlan_filtering", NM_SETTING_BRIDGE_VLAN_FILTERING, BRIDGE_OPT_TYPE_OPTION}, + {"default_pvid", NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, BRIDGE_OPT_TYPE_OPTION}, + {"group_address", NM_SETTING_BRIDGE_GROUP_ADDRESS, BRIDGE_OPT_TYPE_OPTION}, + {"group_fwd_mask", NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, BRIDGE_OPT_TYPE_OPTION}, + {"vlan_protocol", NM_SETTING_BRIDGE_VLAN_PROTOCOL, BRIDGE_OPT_TYPE_OPTION}, + {"vlan_stats_enabled", NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, BRIDGE_OPT_TYPE_OPTION}, + {"priority", NM_SETTING_BRIDGE_PORT_PRIORITY, BRIDGE_OPT_TYPE_PORT_OPTION}, + {"path_cost", NM_SETTING_BRIDGE_PORT_PATH_COST, BRIDGE_OPT_TYPE_PORT_OPTION}, + { + "hairpin_mode", + NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, + BRIDGE_OPT_TYPE_PORT_OPTION, + .extended_bool = TRUE, + }, + }; + const char *error_message = NULL; + int i; + gint64 v; + + for (i = 0; i < G_N_ELEMENTS(m); i++) { + GParamSpec *param_spec; + + if (opt_type != m[i].opt_type) + continue; + if (!nm_streq(key, m[i].key)) + continue; + if (m[i].only_with_stp && !stp) { + PARSE_WARNING("'%s' invalid when STP is disabled", key); + return; + } + + param_spec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), m[i].property_name); + switch (param_spec->value_type) { + case G_TYPE_BOOLEAN: + if (m[i].extended_bool) { + if (!g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "yes") + || !strcmp(value, "1")) + v = TRUE; + else if (!g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "no")) + v = FALSE; + else { + error_message = "is not a boolean"; + goto warn; + } + } else { + v = _nm_utils_ascii_str_to_int64(value, 10, 0, 1, -1); + if (v == -1) { + error_message = nm_strerror_native(errno); + goto warn; + } + } + if (!nm_g_object_set_property_boolean(G_OBJECT(setting), m[i].property_name, v, NULL)) { + error_message = "number is out of range"; + goto warn; + } + return; + case G_TYPE_UINT: + v = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT, -1); + if (v == -1) { + error_message = nm_strerror_native(errno); + goto warn; + } + if (!nm_g_object_set_property_uint(G_OBJECT(setting), m[i].property_name, v, NULL)) { + error_message = "number is out of range"; + goto warn; + } + return; + case G_TYPE_UINT64: + { + guint64 vu64; + + vu64 = _nm_utils_ascii_str_to_uint64(value, 10, 0, G_MAXUINT64, 0); + if (!nm_g_object_set_property_uint64(G_OBJECT(setting), + m[i].property_name, + vu64, + NULL)) { + error_message = "number is out of range"; + goto warn; + } + } + return; + case G_TYPE_STRING: + nm_g_object_set_property_string(G_OBJECT(setting), m[i].property_name, value, NULL); + return; + default: + nm_assert_not_reached(); + continue; + } + +warn: + PARSE_WARNING("invalid %s value '%s': %s", key, value, error_message); + return; + } + + PARSE_WARNING("unhandled bridge option '%s'", key); +} + +static void +handle_bridging_opts(NMSetting * setting, + gboolean stp, + const char * value, + BridgeOptFunc func, + BridgeOptType opt_type) +{ + gs_free const char **items = NULL; + const char *const * iter; + + items = nm_utils_strsplit_set(value, " "); + for (iter = items; iter && *iter; iter++) { + gs_free char *key = NULL; + const char * val; + + val = strchr(*iter, '='); + if (!val) + continue; + key = g_strndup(*iter, val - *iter); + val++; + if (key[0] && val[0]) + func(setting, stp, key, val, opt_type); + } +} + +static void +read_bridge_vlans(shvarFile *ifcfg, const char *key, NMSetting *setting, const char *property) +{ + gs_unref_ptrarray GPtrArray *array = NULL; + gs_free char * value_to_free = NULL; + const char * value; + + value = svGetValueStr(ifcfg, key, &value_to_free); + if (value) { + gs_free const char **strv = NULL; + const char *const * iter; + GError * local = NULL; + NMBridgeVlan * vlan; + + array = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + + strv = nm_utils_escaped_tokens_split(value, ","); + if (strv) { + for (iter = strv; *iter; iter++) { + vlan = nm_bridge_vlan_from_str(*iter, &local); + if (!vlan) { + PARSE_WARNING("invalid bridge VLAN: %s", local->message); + g_clear_error(&local); + continue; + } + g_ptr_array_add(array, vlan); + } + } + nm_clear_g_free(&value_to_free); + } + + g_object_set(setting, property, array, NULL); +} + +static NMSetting * +make_bridge_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + gs_unref_object NMSettingBridge *s_bridge = NULL; + gs_free char * value_to_free = NULL; + const char * value; + gboolean stp = FALSE; + gboolean stp_set = FALSE; + + value = svGetValueStr(ifcfg, "DEVICE", &value_to_free); + if (!value) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "mandatory DEVICE keyword missing"); + return NULL; + } + nm_clear_g_free(&value_to_free); + + s_bridge = NM_SETTING_BRIDGE(nm_setting_bridge_new()); + + value = svGetValueStr(ifcfg, "BRIDGE_MACADDR", &value_to_free); + if (value) { + g_object_set(s_bridge, NM_SETTING_BRIDGE_MAC_ADDRESS, value, NULL); + nm_clear_g_free(&value_to_free); + } + + value = svGetValueStr(ifcfg, "STP", &value_to_free); + if (value) { + if (!g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "yes")) { + g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, TRUE, NULL); + stp = TRUE; + stp_set = TRUE; + } else if (!g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "no")) { + g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL); + stp_set = TRUE; + } else + PARSE_WARNING("invalid STP value '%s'", value); + nm_clear_g_free(&value_to_free); + } + + if (!stp_set) { + /* Missing or invalid STP property means "no" */ + g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL); + } + + value = svGetValueStr(ifcfg, "DELAY", &value_to_free); + if (value) { + handle_bridge_option(NM_SETTING(s_bridge), stp, "DELAY", value, BRIDGE_OPT_TYPE_MAIN); + nm_clear_g_free(&value_to_free); + } + + value = svGetValueStr(ifcfg, "BRIDGING_OPTS", &value_to_free); + if (value) { + handle_bridging_opts(NM_SETTING(s_bridge), + stp, + value, + handle_bridge_option, + BRIDGE_OPT_TYPE_OPTION); + nm_clear_g_free(&value_to_free); + } + + read_bridge_vlans(ifcfg, "BRIDGE_VLANS", NM_SETTING(s_bridge), NM_SETTING_BRIDGE_VLANS); + + return (NMSetting *) g_steal_pointer(&s_bridge); +} + +static NMConnection * +bridge_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * bridge_setting = NULL; + NMSetting * wired_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = + make_connection_setting(file, ifcfg, NM_SETTING_BRIDGE_SETTING_NAME, NULL, _("Bridge")); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + bridge_setting = make_bridge_setting(ifcfg, file, error); + if (!bridge_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, bridge_setting); + + wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local); + if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) { + g_propagate_error(error, local); + g_object_unref(connection); + return NULL; + } + g_clear_error(&local); + + if (wired_setting) + nm_connection_add_setting(connection, wired_setting); + + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + return connection; +} + +static NMSetting * +make_bridge_port_setting(shvarFile *ifcfg) +{ + NMSetting * s_port = NULL; + gs_free char *value_to_free = NULL; + const char * value; + + g_return_val_if_fail(ifcfg != NULL, FALSE); + + value = svGetValueStr(ifcfg, "BRIDGE_UUID", &value_to_free); + if (!value) + value = svGetValueStr(ifcfg, "BRIDGE", &value_to_free); + if (value) { + nm_clear_g_free(&value_to_free); + + s_port = nm_setting_bridge_port_new(); + value = svGetValueStr(ifcfg, "BRIDGING_OPTS", &value_to_free); + if (value) { + handle_bridging_opts(s_port, + FALSE, + value, + handle_bridge_option, + BRIDGE_OPT_TYPE_PORT_OPTION); + nm_clear_g_free(&value_to_free); + } + + read_bridge_vlans(ifcfg, "BRIDGE_PORT_VLANS", s_port, NM_SETTING_BRIDGE_PORT_VLANS); + } + + return s_port; +} + +static NMSetting * +make_team_port_setting(shvarFile *ifcfg) +{ + NMSetting * s_port; + gs_free char *value = NULL; + + value = svGetValueStr_cp(ifcfg, "TEAM_PORT_CONFIG"); + if (!value) + return NULL; + + s_port = nm_setting_team_port_new(); + g_object_set(s_port, NM_SETTING_TEAM_PORT_CONFIG, value, NULL); + return s_port; +} + +static gboolean +is_bond_device(const char *name, shvarFile *parsed) +{ + g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(parsed != NULL, FALSE); + + if (svGetValueBoolean(parsed, "BONDING_MASTER", FALSE)) + return TRUE; + + return FALSE; +} + +static gboolean +is_vlan_device(const char *name, shvarFile *parsed) +{ + g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(parsed != NULL, FALSE); + + if (svGetValueBoolean(parsed, "VLAN", FALSE)) + return TRUE; + + return FALSE; +} + +static gboolean +is_wifi_device(const char *name, shvarFile *parsed) +{ + const NMPlatformLink *pllink; + + g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(parsed != NULL, FALSE); + + pllink = nm_platform_link_get_by_ifname(NM_PLATFORM_GET, name); + return pllink && pllink->type == NM_LINK_TYPE_WIFI; +} + +static void +parse_prio_map_list(NMSettingVlan *s_vlan, shvarFile *ifcfg, const char *key, NMVlanPriorityMap map) +{ + gs_free char * value = NULL; + gs_free const char **list = NULL; + const char *const * iter; + const char * v; + + v = svGetValueStr(ifcfg, key, &value); + if (!v) + return; + list = nm_utils_strsplit_set(v, ","); + + for (iter = list; iter && *iter; iter++) { + if (!strchr(*iter, ':')) + continue; + if (!nm_setting_vlan_add_priority_str(s_vlan, map, *iter)) + PARSE_WARNING("invalid %s priority map item '%s'", key, *iter); + } +} + +static NMSetting * +make_vlan_setting(shvarFile *ifcfg, const char *file, GError **error) +{ + gs_unref_object NMSettingVlan *s_vlan = NULL; + gs_free char * parent = NULL; + gs_free char * iface_name = NULL; + gs_free char * value = NULL; + const char * v = NULL; + int vlan_id = -1; + guint32 vlan_flags = 0; + int gvrp, reorder_hdr; + + v = svGetValueStr(ifcfg, "VLAN_ID", &value); + if (v) { + vlan_id = _nm_utils_ascii_str_to_int64(v, 10, 0, 4095, -1); + if (vlan_id == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid VLAN_ID '%s'", + v); + return NULL; + } + } + + /* Need DEVICE if we don't have a separate VLAN_ID property */ + iface_name = svGetValueStr_cp(ifcfg, "DEVICE"); + if (!iface_name && vlan_id < 0) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing DEVICE property; cannot determine VLAN ID"); + return NULL; + } + + s_vlan = NM_SETTING_VLAN(nm_setting_vlan_new()); + + /* Parent interface from PHYSDEV takes precedence if it exists */ + parent = svGetValueStr_cp(ifcfg, "PHYSDEV"); + + if (iface_name) { + v = strchr(iface_name, '.'); + if (v) { + /* eth0.43; PHYSDEV is assumed from it if unknown */ + if (!parent) { + parent = g_strndup(iface_name, v - iface_name); + if (g_str_has_prefix(parent, "vlan")) { + /* Like initscripts, if no PHYSDEV and we get an obviously + * invalid parent interface from DEVICE, fail. + */ + nm_clear_g_free(&parent); + } + } + v++; + } else { + /* format like vlan43; PHYSDEV must be set */ + if (g_str_has_prefix(iface_name, "vlan")) + v = iface_name + 4; + } + + if (v) { + int device_vlan_id; + + /* Grab VLAN ID from interface name; this takes precedence over the + * separate VLAN_ID property for backwards compat. + */ + device_vlan_id = _nm_utils_ascii_str_to_int64(v, 10, 0, 4095, -1); + if (device_vlan_id != -1) + vlan_id = device_vlan_id; + } + } + + if (vlan_id < 0) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to determine VLAN ID from DEVICE or VLAN_ID"); + return NULL; + } + g_object_set(s_vlan, NM_SETTING_VLAN_ID, vlan_id, NULL); + + if (parent == NULL) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to determine VLAN parent from DEVICE or PHYSDEV"); + return NULL; + } + g_object_set(s_vlan, NM_SETTING_VLAN_PARENT, parent, NULL); + + vlan_flags |= NM_VLAN_FLAG_REORDER_HEADERS; + + gvrp = svGetValueBoolean(ifcfg, "GVRP", -1); + if (gvrp > 0) + vlan_flags |= NM_VLAN_FLAG_GVRP; + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "VLAN_FLAGS", &value); + if (v) { + gs_free const char **strv = NULL; + const char *const * ptr; + + strv = nm_utils_strsplit_set(v, ", "); + for (ptr = strv; ptr && *ptr; ptr++) { + if (nm_streq(*ptr, "GVRP") && gvrp == -1) + vlan_flags |= NM_VLAN_FLAG_GVRP; + if (nm_streq(*ptr, "LOOSE_BINDING")) + vlan_flags |= NM_VLAN_FLAG_LOOSE_BINDING; + if (nm_streq(*ptr, "NO_REORDER_HDR")) + vlan_flags &= ~NM_VLAN_FLAG_REORDER_HEADERS; + } + } + + reorder_hdr = svGetValueBoolean(ifcfg, "REORDER_HDR", -1); + if (reorder_hdr != -1 && reorder_hdr != NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS)) + PARSE_WARNING("REORDER_HDR key is deprecated, use VLAN_FLAGS"); + + if (svGetValueBoolean(ifcfg, "MVRP", FALSE)) + vlan_flags |= NM_VLAN_FLAG_MVRP; + + g_object_set(s_vlan, NM_SETTING_VLAN_FLAGS, vlan_flags, NULL); + + parse_prio_map_list(s_vlan, ifcfg, "VLAN_INGRESS_PRIORITY_MAP", NM_VLAN_INGRESS_MAP); + parse_prio_map_list(s_vlan, ifcfg, "VLAN_EGRESS_PRIORITY_MAP", NM_VLAN_EGRESS_MAP); + + return NM_SETTING(g_steal_pointer(&s_vlan)); +} + +static NMConnection * +vlan_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error) +{ + NMConnection * connection = NULL; + NMSetting * con_setting = NULL; + NMSetting * wired_setting = NULL; + NMSetting * vlan_setting = NULL; + NMSetting8021x *s_8021x = NULL; + GError * local = NULL; + + g_return_val_if_fail(file != NULL, NULL); + g_return_val_if_fail(ifcfg != NULL, NULL); + + connection = nm_simple_connection_new(); + + con_setting = make_connection_setting(file, ifcfg, NM_SETTING_VLAN_SETTING_NAME, NULL, "Vlan"); + if (!con_setting) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to create connection setting"); + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, con_setting); + + vlan_setting = make_vlan_setting(ifcfg, file, error); + if (!vlan_setting) { + g_object_unref(connection); + return NULL; + } + nm_connection_add_setting(connection, vlan_setting); + + wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local); + if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) { + g_propagate_error(error, local); + g_object_unref(connection); + return NULL; + } + g_clear_error(&local); + + if (wired_setting) + nm_connection_add_setting(connection, wired_setting); + + if (s_8021x) + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + return connection; +} + +static NMConnection * +create_unhandled_connection(const char *filename, + shvarFile * ifcfg, + const char *type, + char ** out_spec) +{ + NMConnection *connection; + NMSetting * s_con; + gs_free char *value = NULL; + const char * v; + + nm_assert(out_spec && !*out_spec); + + connection = nm_simple_connection_new(); + + /* Get NAME, UUID, etc. We need to set a connection type (generic) and add + * an empty type-specific setting as well, to make sure it passes + * nm_connection_verify() later. + */ + s_con = make_connection_setting(filename, ifcfg, NM_SETTING_GENERIC_SETTING_NAME, NULL, NULL); + nm_connection_add_setting(connection, s_con); + + nm_connection_add_setting(connection, nm_setting_generic_new()); + + /* Get a spec */ + v = svGetValueStr(ifcfg, "HWADDR", &value); + if (v) { + gs_free char *lower = g_ascii_strdown(v, -1); + + *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_MAC_TAG "%s", type, lower); + return connection; + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "SUBCHANNELS", &value); + if (v) { + *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_S390_SUBCHANNELS_TAG "%s", type, v); + return connection; + } + + nm_clear_g_free(&value); + v = svGetValueStr(ifcfg, "DEVICE", &value); + if (v) { + *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", type, v); + return connection; + } + + g_object_unref(connection); + return NULL; +} + +static void +check_dns_search_domains(shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6) +{ + if (!s_ip6) + return; + + /* If there is no IPv4 config or it doesn't contain DNS searches, + * read DOMAIN and put the domains into IPv6. + */ + if (!s_ip4 || nm_setting_ip_config_get_num_dns_searches(NM_SETTING_IP_CONFIG(s_ip4)) == 0) { + /* DNS searches */ + gs_free char *value = NULL; + const char * v; + + v = svGetValueStr(ifcfg, "DOMAIN", &value); + if (v) { + gs_free const char **searches = NULL; + const char *const * item; + + searches = nm_utils_strsplit_set(v, " "); + if (searches) { + for (item = searches; *item; item++) { + if (!nm_setting_ip_config_add_dns_search(NM_SETTING_IP_CONFIG(s_ip6), *item)) + PARSE_WARNING("duplicate DNS domain '%s'", *item); + } + } + } + } +} + +static NMConnection * +connection_from_file_full(const char *filename, + const char *network_file, /* for unit tests only */ + const char *test_type, /* for unit tests only */ + char ** out_unhandled, + GError ** error, + gboolean * out_ignore_error) +{ + nm_auto_shvar_file_close shvarFile *main_ifcfg = NULL; + nm_auto_shvar_file_close shvarFile *network_ifcfg = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_free char * type = NULL; + char * devtype, *bootproto; + NMSetting * setting; + NMSetting * s_ip4; + NMSetting * s_ip6; + const char * ifcfg_name = NULL; + gboolean has_ip4_defroute = FALSE; + gboolean has_complex_routes_v4; + gboolean has_complex_routes_v6; + + g_return_val_if_fail(filename != NULL, NULL); + g_return_val_if_fail(out_unhandled && !*out_unhandled, NULL); + + NM_SET_OUT(out_ignore_error, FALSE); + + /* Non-NULL only for unit tests; normally use /etc/sysconfig/network */ + if (!network_file) + network_file = SYSCONFDIR "/sysconfig/network"; + + ifcfg_name = utils_get_ifcfg_name(filename, TRUE); + if (!ifcfg_name) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ignoring connection '%s' because it's not an ifcfg file", + filename); + return NULL; + } + + main_ifcfg = svOpenFile(filename, error); + if (!main_ifcfg) + return NULL; + + network_ifcfg = svOpenFile(network_file, NULL); + + if (!svGetValueBoolean(main_ifcfg, "NM_CONTROLLED", TRUE)) { + connection = create_unhandled_connection(filename, main_ifcfg, "unmanaged", out_unhandled); + if (!connection) { + NM_SET_OUT(out_ignore_error, TRUE); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "NM_CONTROLLED was false but device was not uniquely identified; device " + "will be managed"); + } + return g_steal_pointer(&connection); + } + + /* iBFT is handled by nm-initrd-generator during boot. */ + bootproto = svGetValueStr_cp(main_ifcfg, "BOOTPROTO"); + if (bootproto && !g_ascii_strcasecmp(bootproto, "ibft")) { + NM_SET_OUT(out_ignore_error, TRUE); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ignoring iBFT configuration"); + g_free(bootproto); + return NULL; + } + g_free(bootproto); + + devtype = svGetValueStr_cp(main_ifcfg, "DEVICETYPE"); + if (devtype) { + if (!g_ascii_strcasecmp(devtype, TYPE_TEAM)) + type = g_strdup(TYPE_TEAM); + else if (!g_ascii_strcasecmp(devtype, TYPE_TEAM_PORT)) { + gs_free char *device = NULL; + + type = svGetValueStr_cp(main_ifcfg, "TYPE"); + device = svGetValueStr_cp(main_ifcfg, "DEVICE"); + + if (type) { + /* nothing to do */ + } else if (device && is_vlan_device(device, main_ifcfg)) + type = g_strdup(TYPE_VLAN); + else + type = g_strdup(TYPE_ETHERNET); + } + g_free(devtype); + } + if (!type) { + gs_free char *t = NULL; + + /* Team and TeamPort types are also accepted by the mere + * presence of TEAM_CONFIG/TEAM_MASTER. They don't require + * DEVICETYPE. */ + t = svGetValueStr_cp(main_ifcfg, "TEAM_CONFIG"); + if (t) + type = g_strdup(TYPE_TEAM); + } + + if (!type) + type = svGetValueStr_cp(main_ifcfg, "TYPE"); + + if (!type) { + gs_free char *tmp = NULL; + char * device; + + if ((tmp = svGetValueStr_cp(main_ifcfg, "IPV6TUNNELIPV4"))) { + NM_SET_OUT(out_ignore_error, TRUE); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ignoring unsupported connection due to IPV6TUNNELIPV4"); + return NULL; + } + + device = svGetValueStr_cp(main_ifcfg, "DEVICE"); + if (!device) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "File '%s' had neither TYPE nor DEVICE keys", + filename); + return NULL; + } + + if (!strcmp(device, "lo")) { + NM_SET_OUT(out_ignore_error, TRUE); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ignoring loopback device config"); + g_free(device); + return NULL; + } + + if (!test_type) { + if (is_bond_device(device, main_ifcfg)) + type = g_strdup(TYPE_BOND); + else if (is_vlan_device(device, main_ifcfg)) + type = g_strdup(TYPE_VLAN); + else if (is_wifi_device(device, main_ifcfg)) + type = g_strdup(TYPE_WIRELESS); + else { + gs_free char *p_path = NULL; + char * p_device; + gsize i; + + /* network-functions detects DEVICETYPE based on the ifcfg-* name and the existence + * of a ifup script: + * [ -z "$DEVICETYPE" ] && DEVICETYPE=$(echo ${DEVICE} | sed "s/[0-9]*$//") + * later... + * OTHERSCRIPT="/etc/sysconfig/network-scripts/ifup-${DEVICETYPE}" + * */ +#define IFUP_PATH_PREFIX "/etc/sysconfig/network-scripts/ifup-" + i = strlen(device); + p_path = g_malloc(NM_STRLEN(IFUP_PATH_PREFIX) + i + 1); + p_device = &p_path[NM_STRLEN(IFUP_PATH_PREFIX)]; + memcpy(p_device, device, i + 1); + + /* strip trailing numbers */ + while (i >= 1) { + i--; + if (p_device[i] < '0' || p_device[i] > '9') + break; + p_device[i] = '\0'; + } + + if (nm_streq(p_device, "eth")) + type = g_strdup(TYPE_ETHERNET); + else if (nm_streq(p_device, "wireless")) + type = g_strdup(TYPE_WIRELESS); + else if (p_device[0]) { + memcpy(p_path, IFUP_PATH_PREFIX, NM_STRLEN(IFUP_PATH_PREFIX)); + if (access(p_path, X_OK) == 0) { + /* for all other types, this is not something we want to handle. */ + NM_SET_OUT(out_ignore_error, TRUE); + g_set_error( + error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Ignore script for unknown device type which has a matching %s script", + p_path); + return NULL; + } + } + + if (!type) + type = g_strdup(TYPE_ETHERNET); + } + } else { + /* For the unit tests, there won't necessarily be any + * adapters of the connection's type in the system so the + * type can't be tested with ioctls. + */ + type = g_strdup(test_type); + } + + g_free(device); + } else { + /* Check for IBM s390 CTC devices and call them Ethernet */ + if (g_strcmp0(type, "CTC") == 0) { + g_free(type); + type = g_strdup(TYPE_ETHERNET); + } + } + + if (nm_streq0(type, TYPE_ETHERNET)) { + gs_free char *bond_options = NULL; + + if (svGetValueStr(main_ifcfg, "BONDING_OPTS", &bond_options)) { + /* initscripts consider these as bond masters */ + g_free(type); + type = g_strdup(TYPE_BOND); + } + } + + if (svGetValueBoolean(main_ifcfg, "BONDING_MASTER", FALSE) + && g_ascii_strcasecmp(type, TYPE_BOND)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "BONDING_MASTER=yes key only allowed in TYPE=bond connections"); + return NULL; + } + + /* Construct the connection */ + if (!g_ascii_strcasecmp(type, TYPE_ETHERNET)) + connection = wired_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_WIRELESS)) + connection = wireless_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_INFINIBAND)) + connection = infiniband_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_BOND)) + connection = bond_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_TEAM)) + connection = team_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_VLAN)) + connection = vlan_connection_from_ifcfg(filename, main_ifcfg, error); + else if (!g_ascii_strcasecmp(type, TYPE_BRIDGE)) + connection = bridge_connection_from_ifcfg(filename, main_ifcfg, error); + else { + connection = + create_unhandled_connection(filename, main_ifcfg, "unrecognized", out_unhandled); + if (!connection) { + PARSE_WARNING("connection type was unrecognized but device was not uniquely " + "identified; device may be managed"); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Failed to read unrecognized connection"); + } + return g_steal_pointer(&connection); + } + + if (!connection) + return NULL; + + parse_ethtool_options(main_ifcfg, connection); + + has_complex_routes_v4 = utils_has_complex_routes(filename, AF_INET); + has_complex_routes_v6 = utils_has_complex_routes(filename, AF_INET6); + + if (has_complex_routes_v4 || has_complex_routes_v6) { + if (has_complex_routes_v4 && !has_complex_routes_v6) + PARSE_WARNING("'rule-' file is present; you will need to use a dispatcher script to " + "apply these routes"); + else if (has_complex_routes_v6 && !has_complex_routes_v4) + PARSE_WARNING("'rule6-' file is present; you will need to use a dispatcher script to " + "apply these routes"); + else + PARSE_WARNING("'rule-' and 'rule6-' files are present; you will need to use a " + "dispatcher script to apply these routes"); + } + + s_ip6 = make_ip6_setting(main_ifcfg, + network_ifcfg, + !has_complex_routes_v4 && !has_complex_routes_v6, + error); + if (!s_ip6) + return NULL; + nm_connection_add_setting(connection, s_ip6); + + s_ip4 = make_ip4_setting(main_ifcfg, + network_ifcfg, + !has_complex_routes_v4 && !has_complex_routes_v6, + &has_ip4_defroute, + error); + if (!s_ip4) + return NULL; + read_aliases(NM_SETTING_IP_CONFIG(s_ip4), + !has_ip4_defroute + && !nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(s_ip4)), + filename); + nm_connection_add_setting(connection, s_ip4); + + read_routing_rules(main_ifcfg, + !has_complex_routes_v4 && !has_complex_routes_v6, + NM_SETTING_IP_CONFIG(s_ip4), + NM_SETTING_IP_CONFIG(s_ip6)); + + setting = make_sriov_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_tc_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + /* For backwards compatibility, if IPv4 is disabled or the + * config fails for some reason, we read DOMAIN and put the + * values into IPv6 config instead of IPv4. + */ + check_dns_search_domains(main_ifcfg, s_ip4, s_ip6); + + setting = make_proxy_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_hostname_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_user_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_match_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_bridge_port_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + setting = make_team_port_setting(main_ifcfg); + if (setting) + nm_connection_add_setting(connection, setting); + + if (!make_dcb_setting(main_ifcfg, &setting, error)) + return NULL; + if (setting) + nm_connection_add_setting(connection, setting); + + if (!nm_connection_normalize(connection, NULL, NULL, error)) + return NULL; + + return g_steal_pointer(&connection); +} + +NMConnection * +connection_from_file(const char *filename, + char ** out_unhandled, + GError ** error, + gboolean * out_ignore_error) +{ + return connection_from_file_full(filename, NULL, NULL, out_unhandled, error, out_ignore_error); +} + +NMConnection * +nmtst_connection_from_file(const char *filename, + const char *network_file, + const char *test_type, + char ** out_unhandled, + GError ** error) +{ + return connection_from_file_full(filename, network_file, test_type, out_unhandled, error, NULL); +} diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h new file mode 100644 index 0000000..c7dc224 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef __NMS_IFCFG_RH_READER_H__ +#define __NMS_IFCFG_RH_READER_H__ + +#include "nm-connection.h" + +NMConnection *connection_from_file(const char *filename, + char ** out_unhandled, + GError ** error, + gboolean * out_ignore_error); + +NMConnection *nmtst_connection_from_file(const char *filename, + const char *network_file, + const char *test_type, + char ** out_unhandled, + GError ** error); + +#endif /* __NMS_IFCFG_RH_READER_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c new file mode 100644 index 0000000..3c26828 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifcfg-rh-storage.h" + +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nm-connection.h" +#include "nms-ifcfg-rh-plugin.h" + +/*****************************************************************************/ + +struct _NMSIfcfgRHStorageClass { + NMSettingsStorageClass parent; +}; + +G_DEFINE_TYPE(NMSIfcfgRHStorage, nms_ifcfg_rh_storage, NM_TYPE_SETTINGS_STORAGE) + +/*****************************************************************************/ + +gboolean +nms_ifcfg_rh_storage_equal_type(const NMSIfcfgRHStorage *self_a, const NMSIfcfgRHStorage *self_b) +{ + return (self_a == self_b) + || (self_a && self_b + && nm_streq0(nms_ifcfg_rh_storage_get_uuid_opt(self_a), + nms_ifcfg_rh_storage_get_uuid_opt(self_b)) + && nm_streq0(self_a->unmanaged_spec, self_b->unmanaged_spec) + && nm_streq0(self_a->unrecognized_spec, self_b->unrecognized_spec)); +} + +void +nms_ifcfg_rh_storage_copy_content(NMSIfcfgRHStorage *dst, const NMSIfcfgRHStorage *src) +{ + nm_assert(src != dst); + nm_assert(src && dst); + nm_assert(nms_ifcfg_rh_storage_equal_type(dst, src)); + nm_assert(nms_ifcfg_rh_storage_get_filename(dst) + && nm_streq(nms_ifcfg_rh_storage_get_filename(dst), + nms_ifcfg_rh_storage_get_filename(src))); + + nm_g_object_ref_set(&dst->connection, src->connection); + g_free(dst->unmanaged_spec); + g_free(dst->unrecognized_spec); + dst->unmanaged_spec = g_strdup(src->unmanaged_spec); + dst->unrecognized_spec = g_strdup(src->unrecognized_spec); + dst->stat_mtime = src->stat_mtime; +} + +NMConnection * +nms_ifcfg_rh_storage_steal_connection(NMSIfcfgRHStorage *self) +{ + nm_assert(NMS_IS_IFCFG_RH_STORAGE(self)); + + return g_steal_pointer(&self->connection); +} + +/*****************************************************************************/ + +static int +cmp_fcn(const NMSIfcfgRHStorage *a, const NMSIfcfgRHStorage *b) +{ + nm_assert(NMS_IS_IFCFG_RH_STORAGE(a)); + nm_assert(NMS_IS_IFCFG_RH_STORAGE(b)); + nm_assert(a != b); + + /* newer files are more important. */ + NM_CMP_FIELD(a, b, stat_mtime.tv_sec); + NM_CMP_FIELD(a, b, stat_mtime.tv_nsec); + + NM_CMP_DIRECT_STRCMP(nms_ifcfg_rh_storage_get_filename(a), + nms_ifcfg_rh_storage_get_filename(b)); + + return 0; +} + +/*****************************************************************************/ + +static void +nms_ifcfg_rh_storage_init(NMSIfcfgRHStorage *self) +{} + +static NMSIfcfgRHStorage * +_storage_new(NMSIfcfgRHPlugin *plugin, const char *uuid, const char *filename) +{ + nm_assert(NMS_IS_IFCFG_RH_PLUGIN(plugin)); + nm_assert(!uuid || nm_utils_is_uuid(uuid)); + nm_assert(filename && filename[0] == '/'); + + return g_object_new(NMS_TYPE_IFCFG_RH_STORAGE, + NM_SETTINGS_STORAGE_PLUGIN, + plugin, + NM_SETTINGS_STORAGE_UUID, + uuid, + NM_SETTINGS_STORAGE_FILENAME, + filename, + NULL); +} + +NMSIfcfgRHStorage * +nms_ifcfg_rh_storage_new_connection(NMSIfcfgRHPlugin * plugin, + const char * filename, + NMConnection * connection_take, + const struct timespec *mtime) +{ + NMSIfcfgRHStorage *self; + + nm_assert(NM_IS_CONNECTION(connection_take)); + nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS); + nmtst_connection_assert_unchanging(connection_take); + + self = _storage_new(plugin, nm_connection_get_uuid(connection_take), filename); + self->connection = connection_take; + if (mtime) + self->stat_mtime = *mtime; + return self; +} + +NMSIfcfgRHStorage * +nms_ifcfg_rh_storage_new_unhandled(NMSIfcfgRHPlugin *plugin, + const char * filename, + const char * unmanaged_spec, + const char * unrecognized_spec) +{ + NMSIfcfgRHStorage *self; + + nm_assert(unmanaged_spec || unrecognized_spec); + + self = _storage_new(plugin, NULL, filename); + self->unmanaged_spec = g_strdup(unmanaged_spec); + self->unrecognized_spec = g_strdup(unrecognized_spec); + return self; +} + +static void +_storage_clear(NMSIfcfgRHStorage *self) +{ + c_list_unlink(&self->parent._storage_lst); + c_list_unlink(&self->parent._storage_by_uuid_lst); + nm_clear_g_free(&self->unmanaged_spec); + nm_clear_g_free(&self->unrecognized_spec); + g_clear_object(&self->connection); +} + +static void +dispose(GObject *object) +{ + NMSIfcfgRHStorage *self = NMS_IFCFG_RH_STORAGE(object); + + _storage_clear(self); + + G_OBJECT_CLASS(nms_ifcfg_rh_storage_parent_class)->dispose(object); +} + +void +nms_ifcfg_rh_storage_destroy(NMSIfcfgRHStorage *self) +{ + _storage_clear(self); + g_object_unref(self); +} + +static void +nms_ifcfg_rh_storage_class_init(NMSIfcfgRHStorageClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS(klass); + + object_class->dispose = dispose; + + storage_class->cmp_fcn = (int (*)(NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn; +} diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h new file mode 100644 index 0000000..6fb9838 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NMS_IFCFG_RH_STORAGE_H__ +#define __NMS_IFCFG_RH_STORAGE_H__ + +#include "c-list/src/c-list.h" +#include "settings/nm-settings-storage.h" + +/*****************************************************************************/ + +#define NMS_TYPE_IFCFG_RH_STORAGE (nms_ifcfg_rh_storage_get_type()) +#define NMS_IFCFG_RH_STORAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorage)) +#define NMS_IFCFG_RH_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass)) +#define NMS_IS_IFCFG_RH_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFCFG_RH_STORAGE)) +#define NMS_IS_IFCFG_RH_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFCFG_RH_STORAGE)) +#define NMS_IFCFG_RH_STORAGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass)) + +typedef struct { + NMSettingsStorage parent; + + NMConnection *connection; + + char *unmanaged_spec; + char *unrecognized_spec; + + /* The timestamp (stat's mtime) of the file. Newer files have + * higher priority. */ + struct timespec stat_mtime; + + bool dirty : 1; + +} NMSIfcfgRHStorage; + +typedef struct _NMSIfcfgRHStorageClass NMSIfcfgRHStorageClass; + +GType nms_ifcfg_rh_storage_get_type(void); + +struct _NMSIfcfgRHPlugin; + +NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_connection(struct _NMSIfcfgRHPlugin *plugin, + const char * filename, + NMConnection * connection_take, + const struct timespec * mtime); + +NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_unhandled(struct _NMSIfcfgRHPlugin *plugin, + const char * filename, + const char * unmanaged_spec, + const char * unrecognized_spec); + +void nms_ifcfg_rh_storage_destroy(NMSIfcfgRHStorage *self); + +/*****************************************************************************/ + +gboolean nms_ifcfg_rh_storage_equal_type(const NMSIfcfgRHStorage *self_a, + const NMSIfcfgRHStorage *self_b); + +void nms_ifcfg_rh_storage_copy_content(NMSIfcfgRHStorage *dst, const NMSIfcfgRHStorage *src); + +NMConnection *nms_ifcfg_rh_storage_steal_connection(NMSIfcfgRHStorage *self); + +/*****************************************************************************/ + +static inline const char * +nms_ifcfg_rh_storage_get_uuid_opt(const NMSIfcfgRHStorage *self) +{ + return nm_settings_storage_get_uuid_opt((const NMSettingsStorage *) self); +} + +static inline const char * +nms_ifcfg_rh_storage_get_filename(const NMSIfcfgRHStorage *self) +{ + return nm_settings_storage_get_filename((const NMSettingsStorage *) self); +} + +#endif /* __NMS_IFCFG_RH_STORAGE_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c new file mode 100644 index 0000000..58d3e69 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c @@ -0,0 +1,1106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifcfg-rh-utils.h" + +#include + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +#include "nms-ifcfg-rh-common.h" + +/*****************************************************************************/ + +gboolean +nms_ifcfg_rh_utils_parse_unhandled_spec(const char * unhandled_spec, + const char **out_unmanaged_spec, + const char **out_unrecognized_spec) +{ + if (unhandled_spec) { + if (NM_STR_HAS_PREFIX(unhandled_spec, "unmanaged:")) { + NM_SET_OUT(out_unmanaged_spec, &unhandled_spec[NM_STRLEN("unmanaged:")]); + NM_SET_OUT(out_unrecognized_spec, NULL); + return TRUE; + } + if (NM_STR_HAS_PREFIX(unhandled_spec, "unrecognized:")) { + NM_SET_OUT(out_unmanaged_spec, NULL); + NM_SET_OUT(out_unrecognized_spec, &unhandled_spec[NM_STRLEN("unrecognized:")]); + return TRUE; + } + } + NM_SET_OUT(out_unmanaged_spec, NULL); + NM_SET_OUT(out_unrecognized_spec, NULL); + return FALSE; +} + +/*****************************************************************************/ + +/* + * Check ';[a-fA-F0-9]{8}' file suffix used for temporary files by rpm when + * installing packages. + * + * Implementation taken from upstart. + */ +static gboolean +check_rpm_temp_suffix(const char *path) +{ + const char *ptr; + + g_return_val_if_fail(path != NULL, FALSE); + + /* Matches *;[a-fA-F0-9]{8}; used by rpm */ + ptr = strrchr(path, ';'); + if (ptr && strspn(ptr + 1, "abcdefABCDEF0123456789") == 8 && !ptr[9]) + return TRUE; + return FALSE; +} + +static gboolean +check_suffix(const char *base, const char *tag) +{ + int len, tag_len; + + g_return_val_if_fail(base != NULL, TRUE); + g_return_val_if_fail(tag != NULL, TRUE); + + len = strlen(base); + tag_len = strlen(tag); + if ((len > tag_len) && !g_ascii_strcasecmp(base + len - tag_len, tag)) + return TRUE; + return FALSE; +} + +gboolean +utils_should_ignore_file(const char *filename, gboolean only_ifcfg) +{ + gs_free char *base = NULL; + + g_return_val_if_fail(filename != NULL, TRUE); + + base = g_path_get_basename(filename); + + /* Only handle ifcfg, keys, and routes files */ + if (strncmp(base, IFCFG_TAG, strlen(IFCFG_TAG)) != 0) { + if (only_ifcfg) + return TRUE; + else if (strncmp(base, KEYS_TAG, strlen(KEYS_TAG)) != 0 + && strncmp(base, ROUTE_TAG, strlen(ROUTE_TAG)) != 0 + && strncmp(base, ROUTE6_TAG, strlen(ROUTE6_TAG)) != 0) + return TRUE; + } + + /* But not those that have certain suffixes */ + if (check_suffix(base, BAK_TAG) || check_suffix(base, TILDE_TAG) || check_suffix(base, ORIG_TAG) + || check_suffix(base, REJ_TAG) || check_suffix(base, RPMNEW_TAG) + || check_suffix(base, AUGNEW_TAG) || check_suffix(base, AUGTMP_TAG) + || check_rpm_temp_suffix(base)) + return TRUE; + + return FALSE; +} + +char * +utils_cert_path(const char *parent, const char *suffix, const char *extension) +{ + gs_free char *dir = NULL; + const char * name; + + g_return_val_if_fail(parent, NULL); + g_return_val_if_fail(suffix, NULL); + g_return_val_if_fail(extension, NULL); + + name = utils_get_ifcfg_name(parent, FALSE); + g_return_val_if_fail(name, NULL); + + dir = g_path_get_dirname(parent); + return g_strdup_printf("%s/%s-%s.%s", dir, name, suffix, extension); +} + +const char * +utils_get_ifcfg_name(const char *file, gboolean only_ifcfg) +{ + const char *name; + + g_return_val_if_fail(file != NULL, NULL); + + name = strrchr(file, '/'); + if (!name) + name = file; + else + name++; + if (!*name) + return NULL; + +#define MATCH_TAG_AND_RETURN(name, TAG) \ + G_STMT_START \ + { \ + if (strncmp(name, TAG, NM_STRLEN(TAG)) == 0) { \ + name += NM_STRLEN(TAG); \ + if (name[0] == '\0') \ + return NULL; \ + else \ + return name; \ + } \ + } \ + G_STMT_END + + /* Do not detect alias files and return 'eth0:0' instead of 'eth0'. + * Unfortunately, we cannot be sure that our files don't contain colons, + * so we cannot reject files with colons. + * + * Instead, you must not call utils_get_ifcfg_name() with an alias file + * or files that are ignored. */ + MATCH_TAG_AND_RETURN(name, IFCFG_TAG); + if (!only_ifcfg) { + MATCH_TAG_AND_RETURN(name, KEYS_TAG); + MATCH_TAG_AND_RETURN(name, ROUTE_TAG); + MATCH_TAG_AND_RETURN(name, ROUTE6_TAG); + } + + return NULL; +} + +/* Used to get any ifcfg/extra file path from any other ifcfg/extra path + * in the form . + */ +static char * +utils_get_extra_path(const char *parent, const char *tag) +{ + char * item_path = NULL, *dirname; + const char *name; + + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(tag != NULL, NULL); + + dirname = g_path_get_dirname(parent); + if (!dirname) + g_return_val_if_reached(NULL); + + name = utils_get_ifcfg_name(parent, FALSE); + if (name) { + if (!strcmp(dirname, ".")) + item_path = g_strdup_printf("%s%s", tag, name); + else + item_path = g_strdup_printf("%s/%s%s", dirname, tag, name); + } + g_free(dirname); + + return item_path; +} + +char * +utils_get_ifcfg_path(const char *parent) +{ + return utils_get_extra_path(parent, IFCFG_TAG); +} + +char * +utils_get_keys_path(const char *parent) +{ + return utils_get_extra_path(parent, KEYS_TAG); +} + +char * +utils_get_route_path(const char *parent) +{ + return utils_get_extra_path(parent, ROUTE_TAG); +} + +char * +utils_get_route6_path(const char *parent) +{ + return utils_get_extra_path(parent, ROUTE6_TAG); +} + +shvarFile * +utils_get_extra_ifcfg(const char *parent, const char *tag, gboolean should_create) +{ + shvarFile *ifcfg = NULL; + char * path; + + path = utils_get_extra_path(parent, tag); + if (!path) + return NULL; + + if (should_create && !g_file_test(path, G_FILE_TEST_EXISTS)) + ifcfg = svCreateFile(path); + + if (!ifcfg) + ifcfg = svOpenFile(path, NULL); + + g_free(path); + return ifcfg; +} + +shvarFile * +utils_get_keys_ifcfg(const char *parent, gboolean should_create) +{ + return utils_get_extra_ifcfg(parent, KEYS_TAG, should_create); +} + +shvarFile * +utils_get_route_ifcfg(const char *parent, gboolean should_create) +{ + return utils_get_extra_ifcfg(parent, ROUTE_TAG, should_create); +} + +/* Finds out if route file has new or older format + * Returns TRUE - new syntax (ADDRESS=a.b.c.d ...), error opening file or empty + * FALSE - older syntax, i.e. argument to 'ip route add' (1.2.3.0/24 via 11.22.33.44) + */ +gboolean +utils_has_route_file_new_syntax(const char *filename) +{ + gs_free char *contents_data = NULL; + gsize len; + + g_return_val_if_fail(filename != NULL, TRUE); + + if (!g_file_get_contents(filename, &contents_data, &len, NULL)) + return TRUE; + + return utils_has_route_file_new_syntax_content(contents_data, len); +} + +gboolean +utils_has_route_file_new_syntax_content(const char *contents, gsize len) +{ + if (len <= 0) + return TRUE; + + while (TRUE) { + const char *line = contents; + char * eol; + gboolean found = FALSE; + + /* matches regex "^[[:space:]]*ADDRESS[0-9]+=" */ + + eol = (char *) strchr(contents, '\n'); + if (eol) { + eol[0] = '\0'; + contents = &eol[1]; + } + + line = nm_str_skip_leading_spaces(line); + if (NM_STR_HAS_PREFIX(line, "ADDRESS")) { + line += NM_STRLEN("ADDRESS"); + if (g_ascii_isdigit(line[0])) { + while (g_ascii_isdigit((++line)[0])) { + /* pass */ + } + if (line[0] == '=') + found = TRUE; + } + } + + if (eol) { + /* restore the line ending. We don't want to mangle the content from + * POV of the caller. */ + eol[0] = '\n'; + } + + if (found) + return TRUE; + if (!eol) + return FALSE; + } +} + +gboolean +utils_has_complex_routes(const char *filename, int addr_family) +{ + g_return_val_if_fail(filename, TRUE); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + gs_free char *rules = utils_get_extra_path(filename, RULE_TAG); + + if (g_file_test(rules, G_FILE_TEST_EXISTS)) + return TRUE; + } + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + gs_free char *rules = utils_get_extra_path(filename, RULE6_TAG); + if (g_file_test(rules, G_FILE_TEST_EXISTS)) + return TRUE; + } + + return FALSE; +} + +/* Find out if the 'alias' file name might be an alias file for 'ifcfg' file name, + * or any alias when 'ifcfg' is NULL. Does not check that it's actually a valid + * alias name; that happens in reader.c + */ +gboolean +utils_is_ifcfg_alias_file(const char *alias, const char *ifcfg) +{ + g_return_val_if_fail(alias != NULL, FALSE); + + if (strncmp(alias, IFCFG_TAG, strlen(IFCFG_TAG))) + return FALSE; + + if (ifcfg) { + size_t len = strlen(ifcfg); + + return (strncmp(alias, ifcfg, len) == 0 && alias[len] == ':'); + } else { + return (strchr(alias, ':') != NULL); + } +} + +char * +utils_detect_ifcfg_path(const char *path, gboolean only_ifcfg) +{ + const char *base; + + g_return_val_if_fail(path != NULL, NULL); + + if (utils_should_ignore_file(path, only_ifcfg)) + return NULL; + + base = strrchr(path, '/'); + if (!base) + base = path; + else + base += 1; + + if (NM_STR_HAS_PREFIX(base, IFCFG_TAG)) { + if (base[NM_STRLEN(IFCFG_TAG)] == '\0') + return NULL; + if (utils_is_ifcfg_alias_file(base, NULL)) { + gs_free char *ifcfg = NULL; + char * ptr; + + ifcfg = g_strdup(path); + ptr = strrchr(ifcfg, ':'); + if (ptr && ptr > ifcfg && !strchr(ptr, '/')) { + *ptr = '\0'; + if (g_file_test(ifcfg, G_FILE_TEST_EXISTS)) { + /* the file has a colon, so it is probably an alias. + * To be ~more~ certain that this is an alias file, + * check whether a corresponding base file exists. */ + if (only_ifcfg) + return NULL; + return g_steal_pointer(&ifcfg); + } + } + } + return g_strdup(path); + } + + if (only_ifcfg) + return NULL; + return utils_get_ifcfg_path(path); +} + +void +nms_ifcfg_rh_utils_user_key_encode(const char *key, GString *str_buffer) +{ + gsize i; + + nm_assert(key); + nm_assert(str_buffer); + + for (i = 0; key[i]; i++) { + char ch = key[i]; + + /* we encode the key in only upper case letters, digits, and underscore. + * As we expect lower-case letters to be more common, we encode lower-case + * letters as upper case, and upper-case letters with a leading underscore. */ + + if (ch >= '0' && ch <= '9') { + g_string_append_c(str_buffer, ch); + continue; + } + if (ch >= 'a' && ch <= 'z') { + g_string_append_c(str_buffer, ch - 'a' + 'A'); + continue; + } + if (ch == '.') { + g_string_append(str_buffer, "__"); + continue; + } + if (ch >= 'A' && ch <= 'Z') { + g_string_append_c(str_buffer, '_'); + g_string_append_c(str_buffer, ch); + continue; + } + g_string_append_printf(str_buffer, "_%03o", (unsigned) ch); + } +} + +gboolean +nms_ifcfg_rh_utils_user_key_decode(const char *name, GString *str_buffer) +{ + gsize i; + + nm_assert(name); + nm_assert(str_buffer); + + if (!name[0]) + return FALSE; + + for (i = 0; name[i];) { + char ch = name[i]; + + if (ch >= '0' && ch <= '9') { + g_string_append_c(str_buffer, ch); + i++; + continue; + } + if (ch >= 'A' && ch <= 'Z') { + g_string_append_c(str_buffer, ch - 'A' + 'a'); + i++; + continue; + } + + if (ch == '_') { + ch = name[i + 1]; + if (ch == '_') { + g_string_append_c(str_buffer, '.'); + i += 2; + continue; + } + if (ch >= 'A' && ch <= 'Z') { + g_string_append_c(str_buffer, ch); + i += 2; + continue; + } + if (ch >= '0' && ch <= '7') { + char ch2, ch3; + unsigned v; + + ch2 = name[i + 2]; + if (!(ch2 >= '0' && ch2 <= '7')) + return FALSE; + + ch3 = name[i + 3]; + if (!(ch3 >= '0' && ch3 <= '7')) + return FALSE; + +#define OCTAL_VALUE(ch) ((unsigned) ((ch) - '0')) + v = (OCTAL_VALUE(ch) << 6) + (OCTAL_VALUE(ch2) << 3) + OCTAL_VALUE(ch3); + if (v > 0xFF || v == 0) + return FALSE; + ch = (char) v; + if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == '.') + || (ch >= 'a' && ch <= 'z')) { + /* such characters are not expected to be encoded via + * octal representation. The encoding is invalid. */ + return FALSE; + } + g_string_append_c(str_buffer, ch); + i += 4; + continue; + } + return FALSE; + } + + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +const char *const _nm_ethtool_ifcfg_names[] = { +#define ETHT_NAME(eid, ename) [eid] = "" ename "" + /* indexed by NMEthtoolID */ + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, "adaptive-rx"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX, "adaptive-tx"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES, "rx-frames"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH, "rx-frames-high"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ, "rx-frames-irq"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW, "rx-frames-low"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH, "pkt-rate-high"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW, "pkt-rate-low"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS, "rx-usecs"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH, "rx-usecs-high"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ, "rx-usecs-irq"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW, "rx-usecs-low"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL, "sample-interval"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS, "stats-block-usecs"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES, "tx-frames"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH, "tx-frames-high"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ, "tx-frames-irq"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW, "tx-frames-low"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS, "tx-usecs"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH, "tx-usecs-high"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ, "tx-usecs-irq"), + ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW, "tx-usecs-low"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_GRO, "gro"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_GSO, "gso"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LRO, "lro"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_NTUPLE, "ntuple"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX, "rx"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RXHASH, "rxhash"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rxvlan"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_SG, "sg"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TSO, "tso"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX, "tx"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TXVLAN, "txvlan"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"), + ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"), + ETHT_NAME(NM_ETHTOOL_ID_RING_RX, "rx"), + ETHT_NAME(NM_ETHTOOL_ID_RING_RX_JUMBO, "rx-jumbo"), + ETHT_NAME(NM_ETHTOOL_ID_RING_RX_MINI, "rx-mini"), + ETHT_NAME(NM_ETHTOOL_ID_RING_TX, "tx"), +}; + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _get_ethtoolid_coalesce_by_name, + NMEthtoolID, + { nm_assert(name); }, + { return NM_ETHTOOL_ID_UNKNOWN; }, + {"adaptive-rx", NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX}, + {"adaptive-tx", NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX}, + {"pkt-rate-high", NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH}, + {"pkt-rate-low", NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW}, + {"rx-frames", NM_ETHTOOL_ID_COALESCE_RX_FRAMES}, + {"rx-frames-high", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH}, + {"rx-frames-irq", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ}, + {"rx-frames-low", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW}, + {"rx-usecs", NM_ETHTOOL_ID_COALESCE_RX_USECS}, + {"rx-usecs-high", NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH}, + {"rx-usecs-irq", NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ}, + {"rx-usecs-low", NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW}, + {"sample-interval", NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL}, + {"stats-block-usecs", NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS}, + {"tx-frames", NM_ETHTOOL_ID_COALESCE_TX_FRAMES}, + {"tx-frames-high", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH}, + {"tx-frames-irq", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ}, + {"tx-frames-low", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW}, + {"tx-usecs", NM_ETHTOOL_ID_COALESCE_TX_USECS}, + {"tx-usecs-high", NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH}, + {"tx-usecs-irq", NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ}, + {"tx-usecs-low", NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW}, ); + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _get_ethtoolid_feature_by_name, + NMEthtoolID, + { nm_assert(name); }, + { return NM_ETHTOOL_ID_UNKNOWN; }, + + /* Map the names from kernel/ethtool/ifcfg to NMEthtoolID. Note that ethtool utility has built-in + * features and NetworkManager's API follows the naming of these built-in features, whenever + * they exist. + * For example, NM's "ethtool.feature-ntuple" corresponds to ethtool utility's "ntuple" + * feature. However the underlying kernel feature is called "rx-ntuple-filter" (as reported + * for ETH_SS_FEATURES). + * + * With ethtool utility, whose command line we attempt to parse here, the user can also + * specify the name of the underlying kernel feature directly. So, check whether that is + * the case and if yes, map them to the corresponding NetworkManager's features. + * + * That is why there are duplicate IDs in this list. */ + {"esp-hw-offload", NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD}, + {"esp-tx-csum-hw-offload", NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD}, + {"fcoe-mtu", NM_ETHTOOL_ID_FEATURE_FCOE_MTU}, + {"gro", NM_ETHTOOL_ID_FEATURE_GRO}, + {"gso", NM_ETHTOOL_ID_FEATURE_GSO}, + {"highdma", NM_ETHTOOL_ID_FEATURE_HIGHDMA}, + {"hw-tc-offload", NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD}, + {"l2-fwd-offload", NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD}, + {"loopback", NM_ETHTOOL_ID_FEATURE_LOOPBACK}, + {"lro", NM_ETHTOOL_ID_FEATURE_LRO}, + {"ntuple", NM_ETHTOOL_ID_FEATURE_NTUPLE}, + {"rx", NM_ETHTOOL_ID_FEATURE_RX}, + {"rx-all", NM_ETHTOOL_ID_FEATURE_RX_ALL}, + {"rx-checksum", NM_ETHTOOL_ID_FEATURE_RX}, // kernel-only name + {"rx-fcs", NM_ETHTOOL_ID_FEATURE_RX_FCS}, + {"rx-gro", NM_ETHTOOL_ID_FEATURE_GRO}, // kernel-only name + {"rx-gro-hw", NM_ETHTOOL_ID_FEATURE_RX_GRO_HW}, + {"rx-hashing", NM_ETHTOOL_ID_FEATURE_RXHASH}, // kernel-only name + {"rx-lro", NM_ETHTOOL_ID_FEATURE_LRO}, // kernel-only name + {"rx-ntuple-filter", NM_ETHTOOL_ID_FEATURE_NTUPLE}, // kernel-only name + {"rx-udp_tunnel-port-offload", NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD}, + {"rx-vlan-filter", NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER}, + {"rx-vlan-hw-parse", NM_ETHTOOL_ID_FEATURE_RXVLAN}, // kernel-only name + {"rx-vlan-stag-filter", NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER}, + {"rx-vlan-stag-hw-parse", NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE}, + {"rxhash", NM_ETHTOOL_ID_FEATURE_RXHASH}, + {"rxvlan", NM_ETHTOOL_ID_FEATURE_RXVLAN}, + {"sg", NM_ETHTOOL_ID_FEATURE_SG}, + {"tls-hw-record", NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD}, + {"tls-hw-tx-offload", NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD}, + {"tso", NM_ETHTOOL_ID_FEATURE_TSO}, + {"tx", NM_ETHTOOL_ID_FEATURE_TX}, + {"tx-checksum-fcoe-crc", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC}, + {"tx-checksum-ip-generic", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC}, + {"tx-checksum-ipv4", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4}, + {"tx-checksum-ipv6", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6}, + {"tx-checksum-sctp", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP}, + {"tx-esp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION}, + {"tx-fcoe-segmentation", NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION}, + {"tx-generic-segmentation", NM_ETHTOOL_ID_FEATURE_GSO}, // kernel-only name + {"tx-gre-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION}, + {"tx-gre-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION}, + {"tx-gso-partial", NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL}, + {"tx-gso-robust", NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST}, + {"tx-ipxip4-segmentation", NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION}, + {"tx-ipxip6-segmentation", NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION}, + {"tx-nocache-copy", NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY}, + {"tx-scatter-gather", NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER}, + {"tx-scatter-gather-fraglist", NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST}, + {"tx-sctp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION}, + {"tx-tcp-ecn-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION}, + {"tx-tcp-mangleid-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION}, + {"tx-tcp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION}, + {"tx-tcp6-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION}, + {"tx-udp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION}, + {"tx-udp_tnl-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION}, + {"tx-udp_tnl-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION}, + {"tx-vlan-hw-insert", NM_ETHTOOL_ID_FEATURE_TXVLAN}, // kernel-only name + {"tx-vlan-stag-hw-insert", NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT}, + {"txvlan", NM_ETHTOOL_ID_FEATURE_TXVLAN}, ); + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _get_ethtoolid_ring_by_name, + NMEthtoolID, + { nm_assert(name); }, + { return NM_ETHTOOL_ID_UNKNOWN; }, + {"rx", NM_ETHTOOL_ID_RING_RX}, + {"rx-jumbo", NM_ETHTOOL_ID_RING_RX_JUMBO}, + {"rx-mini", NM_ETHTOOL_ID_RING_RX_MINI}, + {"tx", NM_ETHTOOL_ID_RING_TX}, ); + +const NMEthtoolData * +nms_ifcfg_rh_utils_get_ethtool_by_name(const char *name, NMEthtoolType ethtool_type) +{ + NMEthtoolID id; + + switch (ethtool_type) { + case NM_ETHTOOL_TYPE_COALESCE: + id = _get_ethtoolid_coalesce_by_name(name); + break; + case NM_ETHTOOL_TYPE_FEATURE: + id = _get_ethtoolid_feature_by_name(name); + break; + case NM_ETHTOOL_TYPE_RING: + id = _get_ethtoolid_ring_by_name(name); + break; + default: + nm_assert_not_reached(); + return NULL; + } + + if (id == NM_ETHTOOL_ID_UNKNOWN) + return NULL; + + nm_assert(_NM_INT_NOT_NEGATIVE(id)); + nm_assert(id < G_N_ELEMENTS(nm_ethtool_data)); + nm_assert(nm_ethtool_data[id]); + nm_assert(nm_ethtool_data[id]->id == id); + return nm_ethtool_data[id]; +} + +/*****************************************************************************/ + +gboolean +nms_ifcfg_rh_utils_is_numbered_tag_impl(const char *key, + const char *tag, + gsize tag_len, + gint64 * out_idx) +{ + gint64 idx; + + nm_assert(key); + nm_assert(tag); + nm_assert(tag_len == strlen(tag)); + nm_assert(tag_len > 0); + + if (strncmp(key, tag, tag_len) != 0) + return FALSE; + + key += tag_len; + + if (key[0] == '\0') { + /* The key has no number suffix. We treat this also as a numbered + * tag, and it is for certain tags like "IPADDR", but not so much + * for others like "ROUTING_RULE_". The caller may want to handle + * this case specially. */ + NM_SET_OUT(out_idx, -1); + return TRUE; + } + + if (!NM_STRCHAR_ALL(key, ch, g_ascii_isdigit(ch))) + return FALSE; + + idx = _nm_utils_ascii_str_to_int64(key, 10, 0, G_MAXINT64, -1); + if (idx == -1) + return FALSE; + + NM_SET_OUT(out_idx, idx); + return TRUE; +} + +/*****************************************************************************/ + +#define _KEY_TYPE(key, flags) \ + { \ + .key_name = "" key "", .key_flags = ((NMS_IFCFG_KEY_TYPE_WELL_KNOWN) | (flags)), \ + } + +const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = { + _KEY_TYPE("ACD_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ADDRESS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("AP_ISOLATION", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ARPING_WAIT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("AUTH_RETRIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("AUTOCONNECT_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("AUTOCONNECT_RETRIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("AUTOCONNECT_SLAVES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BAND", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BONDING_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BONDING_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BOOTPROTO", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGE_MACADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGE_PORT_VLANS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGE_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGE_VLANS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BRIDGING_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BROWSER_ONLY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("BSSID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CHANNEL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CIPHER_GROUP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CIPHER_PAIRWISE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CONNECTED_MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CONNECTION_METERED", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("CTCPROT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DCB", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FCOE_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FCOE_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FCOE_MODE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DCB_APP_FCOE_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FCOE_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FIP_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FIP_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DCB_APP_FIP_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_FIP_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_ISCSI_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_ISCSI_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DCB_APP_ISCSI_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_APP_ISCSI_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PFC_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PFC_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PFC_UP, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PFC_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_ID, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_PCT, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_STRICT, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_UP2TC, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_UPPCT, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE(KEY_DCB_PG_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DEFAULTKEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DEFROUTE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DELAY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DEVICE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DEVICETYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DEVTIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6C", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_DUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPV6_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_CLIENT_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_FQDN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_REJECT_SERVERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCP_VENDOR_CLASS_IDENTIFIER", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPv6_DUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DHCPv6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("DNS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("DOMAIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ESSID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ETHTOOL_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ETHTOOL_WAKE_ON_LAN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("FILS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("FILTER", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("GATEWAY", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("GATEWAYDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("GATEWAY_PING_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("GENERATE_MAC_ADDRESS_MASK", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("GVRP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HOSTNAME_FROM_DHCP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HOSTNAME_FROM_DNS_LOOKUP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HOSTNAME_ONLY_FROM_DEFAULT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HOSTNAME_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HWADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("HWADDR_BLACKLIST", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_ALTSUBJECT_MATCHES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_ANON_IDENTITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_AUTH_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CA_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CA_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CA_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CA_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CLIENT_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CLIENT_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_CLIENT_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_DOMAIN_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_DOMAIN_SUFFIX_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_EAP_METHODS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_FAST_PROVISIONING", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_IDENTITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_AUTH_METHODS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CA_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CA_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CA_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_OPTIONAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PAC_FILE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PASSWORD_RAW", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PASSWORD_RAW_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PEAP_FORCE_NEW_LABEL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PEAP_VERSION", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE1_AUTH_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE2_CA_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE2_DOMAIN_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PHASE2_SUBJECT_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PIN_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PRIVATE_KEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PRIVATE_KEY_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_SUBJECT_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IEEE_8021X_SYSTEM_CA_CERTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPADDR", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("IPV4_DHCP_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV4_DNS_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV4_FAILURE_FATAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV4_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV4_ROUTE_TABLE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6ADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6ADDR_SECONDARIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6FORWARDING", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6INIT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6TUNNELIPV4", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_ADDR_GEN_MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_AUTOCONF", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DEFAULTDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DEFAULTGW", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DEFROUTE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DHCP_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DISABLED", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DNS_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_DOMAIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_FAILURE_FATAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_PEERDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_PEERROUTES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_PRIVACY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_PRIVACY_PREFER_PUBLIC_IP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_RA_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_RES_OPTIONS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_ROUTE_TABLE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("IPV6_TOKEN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("KEY", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("KEY_MGMT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("KEY_PASSPHRASE", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("KEY_TYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("LLDP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("LLMNR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MACADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MAC_ADDRESS_RANDOMIZATION", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MASTER_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MATCH_DRIVER", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MATCH_INTERFACE_NAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MATCH_KERNEL_COMMAND_LINE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MATCH_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("METRIC", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MTU", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MUD_URL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MULTI_CONNECT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("MVRP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("NAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("NETMASK", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("NETTYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("NM_CONTROLLED", NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY), + _KEY_TYPE("NM_USER_", NMS_IFCFG_KEY_TYPE_IS_PREFIX), + _KEY_TYPE("ONBOOT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("OPTIONS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("OVS_PORT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("OVS_PORT_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PAC_SCRIPT", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PAC_URL", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PEERDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PEERROUTES", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PHYSDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PKEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PKEY_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PMF", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PORTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("POWERSAVE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("PREFIX", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("PROXY_METHOD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("QDISC", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("REORDER_HDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("RES_OPTIONS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ROUTING_RULE6_", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("ROUTING_RULE_", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("SEARCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SECONDARY_UUIDS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SECURITYMODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SLAVE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SRIOV_AUTOPROBE_DRIVERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SRIOV_TOTAL_VFS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SRIOV_VF", NMS_IFCFG_KEY_TYPE_IS_NUMBERED), + _KEY_TYPE("SSID_HIDDEN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("STABLE_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("STP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("SUBCHANNELS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("TEAM_CONFIG", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("TEAM_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("TEAM_MASTER_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("TEAM_PORT_CONFIG", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("TYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("USERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VLAN", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VLAN_EGRESS_PRIORITY_MAP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VLAN_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VLAN_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VLAN_INGRESS_PRIORITY_MAP", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VRF", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("VRF_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WEP_KEY_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WPA_ALLOW_WPA", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WPA_ALLOW_WPA2", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WPA_PSK", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WPA_PSK_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("WPS_METHOD", NMS_IFCFG_KEY_TYPE_IS_PLAIN), + _KEY_TYPE("ZONE", NMS_IFCFG_KEY_TYPE_IS_PLAIN), +}; + +const NMSIfcfgKeyTypeInfo * +nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx) +{ + gssize idx; + + G_STATIC_ASSERT(G_STRUCT_OFFSET(NMSIfcfgKeyTypeInfo, key_name) == 0); + + idx = nm_utils_array_find_binary_search(nms_ifcfg_well_known_keys, + sizeof(nms_ifcfg_well_known_keys[0]), + G_N_ELEMENTS(nms_ifcfg_well_known_keys), + &key, + nm_strcmp_p_with_data, + NULL); + NM_SET_OUT(out_idx, idx); + if (idx < 0) + return NULL; + return &nms_ifcfg_well_known_keys[idx]; +} + +const NMSIfcfgKeyTypeInfo * +nms_ifcfg_rh_utils_is_well_known_key(const char *key) +{ + const NMSIfcfgKeyTypeInfo *ti; + gssize idx; + + nm_assert(key); + + ti = nms_ifcfg_well_known_key_find_info(key, &idx); + + if (ti) { + if (NM_FLAGS_ANY(ti->key_flags, + NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) { + /* These tags are valid on full match. + * + * Note that numbered tags we also treat as valid if they have no + * suffix. That is correct for "IPADDR", but less so for "ROUTING_RULE_". */ + return ti; + } + nm_assert(NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX)); + /* a prefix tag needs some extra suffix afterwards to be valid. */ + return NULL; + } + + /* Not found. Maybe it's a numbered/prefixed key? With idx we got the index where + * we should insert the key. Since the numbered/prefixed keys share a prefix, we can + * find the possible prefix at the index before the insert position. */ + idx = ~idx; + if (idx == 0) + return NULL; + + ti = &nms_ifcfg_well_known_keys[idx - 1]; + + if (NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) { + if (nms_ifcfg_rh_utils_is_numbered_tag(key, ti->key_name, NULL)) + return ti; + return NULL; + } + + if (NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX)) { + gsize l = strlen(ti->key_name); + + if (strncmp(key, ti->key_name, l) == 0 && key[l] != '\0') + return ti; + return NULL; + } + + return NULL; +} diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h new file mode 100644 index 0000000..36ec922 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include "nm-connection.h" +#include "nm-base/nm-ethtool-base.h" + +#include "shvar.h" + +/*****************************************************************************/ + +typedef enum { + NMS_IFCFG_KEY_TYPE_UNKNOWN = 0, + NMS_IFCFG_KEY_TYPE_WELL_KNOWN = (1u << 0), + + NMS_IFCFG_KEY_TYPE_IS_PLAIN = (1u << 1), + NMS_IFCFG_KEY_TYPE_IS_NUMBERED = (1u << 2), + NMS_IFCFG_KEY_TYPE_IS_PREFIX = (1u << 3), + + /* by default, well knowns keys that are not explicitly set + * by the writer (the unvisited, dirty ones) are removed. + * With this flag, such keys are kept if they are present. */ + NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY = (1u << 4), + +} NMSIfcfgKeyTypeFlags; + +typedef struct { + const char * key_name; + NMSIfcfgKeyTypeFlags key_flags; +} NMSIfcfgKeyTypeInfo; + +extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[247]; + +const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx); + +static inline NMSIfcfgKeyTypeFlags +nms_ifcfg_well_known_key_find_info_flags(const char *key) +{ + const NMSIfcfgKeyTypeInfo *ti; + + ti = nms_ifcfg_well_known_key_find_info(key, NULL); + if (!ti) + return NMS_IFCFG_KEY_TYPE_UNKNOWN; + return ti->key_flags; +} + +/*****************************************************************************/ + +gboolean nms_ifcfg_rh_utils_parse_unhandled_spec(const char * unhandled_spec, + const char **out_unmanaged_spec, + const char **out_unrecognized_spec); + +#define NM_IFCFG_CONNECTION_LOG_PATH(path) ((path) ?: "in-memory") +#define NM_IFCFG_CONNECTION_LOG_FMT "%s (%s,\"%s\")" +#define NM_IFCFG_CONNECTION_LOG_ARG(con) \ + NM_IFCFG_CONNECTION_LOG_PATH( \ + nm_settings_connection_get_filename((NMSettingsConnection *) (con))), \ + nm_settings_connection_get_uuid((NMSettingsConnection *) (con)), \ + nm_settings_connection_get_id((NMSettingsConnection *) (con)) +#define NM_IFCFG_CONNECTION_LOG_FMTD "%s (%s,\"%s\",%p)" +#define NM_IFCFG_CONNECTION_LOG_ARGD(con) \ + NM_IFCFG_CONNECTION_LOG_PATH( \ + nm_settings_connection_get_filename((NMSettingsConnection *) (con))), \ + nm_settings_connection_get_uuid((NMSettingsConnection *) (con)), \ + nm_settings_connection_get_id((NMSettingsConnection *) (con)), (con) + +char *utils_cert_path(const char *parent, const char *suffix, const char *extension); + +const char *utils_get_ifcfg_name(const char *file, gboolean only_ifcfg); + +gboolean utils_should_ignore_file(const char *filename, gboolean only_ifcfg); + +char *utils_get_ifcfg_path(const char *parent); +char *utils_get_keys_path(const char *parent); +char *utils_get_route_path(const char *parent); +char *utils_get_route6_path(const char *parent); + +shvarFile *utils_get_extra_ifcfg(const char *parent, const char *tag, gboolean should_create); +shvarFile *utils_get_keys_ifcfg(const char *parent, gboolean should_create); +shvarFile *utils_get_route_ifcfg(const char *parent, gboolean should_create); + +gboolean utils_has_route_file_new_syntax(const char *filename); +gboolean utils_has_route_file_new_syntax_content(const char *contents, gsize len); +gboolean utils_has_complex_routes(const char *filename, int addr_family); + +gboolean utils_is_ifcfg_alias_file(const char *alias, const char *ifcfg); + +char *utils_detect_ifcfg_path(const char *path, gboolean only_ifcfg); + +void nms_ifcfg_rh_utils_user_key_encode(const char *key, GString *str_buffer); +gboolean nms_ifcfg_rh_utils_user_key_decode(const char *name, GString *str_buffer); + +static inline const char * +_nms_ifcfg_rh_utils_numbered_tag(char *buf, gsize buf_len, const char *tag_name, int which) +{ + gsize l; + +#if NM_MORE_ASSERTS > 5 + nm_assert(NM_FLAGS_ALL(nms_ifcfg_well_known_key_find_info_flags(tag_name), + NMS_IFCFG_KEY_TYPE_WELL_KNOWN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED)); +#endif + + l = g_strlcpy(buf, tag_name, buf_len); + nm_assert(l < buf_len); + if (which != -1) { + buf_len -= l; + l = g_snprintf(&buf[l], buf_len, "%d", which); + nm_assert(l < buf_len); + } + return buf; +} +#define numbered_tag(buf, tag_name, which) \ + ({ \ + _nm_unused char *const _buf = (buf); \ + \ + /* some static assert trying to ensure that the buffer is statically allocated. + * It disallows a buffer size of sizeof(gpointer) to catch that. */ \ + G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) != sizeof(char *) \ + && sizeof(buf) < G_MAXINT); \ + _nms_ifcfg_rh_utils_numbered_tag(buf, sizeof(buf), "" tag_name "", (which)); \ + }) + +gboolean nms_ifcfg_rh_utils_is_numbered_tag_impl(const char *key, + const char *tag, + gsize tag_len, + gint64 * out_idx); + +static inline gboolean +nms_ifcfg_rh_utils_is_numbered_tag(const char *key, const char *tag, gint64 *out_idx) +{ + nm_assert(tag); + + return nms_ifcfg_rh_utils_is_numbered_tag_impl(key, tag, strlen(tag), out_idx); +} + +#define NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, out_idx) \ + nms_ifcfg_rh_utils_is_numbered_tag_impl(key, tag, NM_STRLEN(tag), out_idx) + +/*****************************************************************************/ + +const NMSIfcfgKeyTypeInfo *nms_ifcfg_rh_utils_is_well_known_key(const char *key); + +/*****************************************************************************/ + +extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_NUM]; + +static inline const char * +nms_ifcfg_rh_utils_get_ethtool_name(NMEthtoolID ethtool_id) +{ + nm_assert(ethtool_id >= _NM_ETHTOOL_ID_FIRST && ethtool_id <= _NM_ETHTOOL_ID_LAST); + nm_assert(ethtool_id < G_N_ELEMENTS(_nm_ethtool_ifcfg_names)); + nm_assert(_nm_ethtool_ifcfg_names[ethtool_id]); + + return _nm_ethtool_ifcfg_names[ethtool_id]; +} + +const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name(const char * name, + NMEthtoolType ethtool_type); + +#endif /* _UTILS_H_ */ diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c new file mode 100644 index 0000000..a268c58 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -0,0 +1,3655 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifcfg-rh-writer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-enum-utils.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-manager.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-8021x.h" +#include "nm-setting-proxy.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-vlan.h" +#include "nm-setting-user.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" +#include "nm-meta-setting.h" +#include "nm-base/nm-ethtool-base.h" + +#include "nms-ifcfg-rh-common.h" +#include "nms-ifcfg-rh-reader.h" +#include "nms-ifcfg-rh-utils.h" +#include "shvar.h" + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG_PREFIX_NAME "ifcfg-rh" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +save_secret_flags(shvarFile *ifcfg, const char *key, NMSettingSecretFlags flags) +{ + GString *str; + + g_return_if_fail(ifcfg != NULL); + g_return_if_fail(key != NULL); + + if (flags == NM_SETTING_SECRET_FLAG_NONE) + return; + + /* Convert flags bitfield into string representation */ + str = g_string_sized_new(20); + if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED) + g_string_append(str, SECRET_FLAG_AGENT); + + if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + if (str->len) + g_string_append_c(str, ' '); + g_string_append(str, SECRET_FLAG_NOT_SAVED); + } + + if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + if (str->len) + g_string_append_c(str, ' '); + g_string_append(str, SECRET_FLAG_NOT_REQUIRED); + } + + svSetValueStr(ifcfg, key, str->len ? str->str : NULL); + g_string_free(str, TRUE); +} + +static void +set_secret(shvarFile * ifcfg, + GHashTable * secrets, + const char * key, + const char * value, + const char * flags_key, + NMSettingSecretFlags flags) +{ + /* Save secret flags */ + save_secret_flags(ifcfg, flags_key, flags); + + /* Only write the secret if it's system owned and supposed to be saved */ + if (flags != NM_SETTING_SECRET_FLAG_NONE) + value = NULL; + + g_hash_table_replace(secrets, g_strdup(key), g_strdup(value)); +} + +static gboolean +write_secrets(shvarFile *ifcfg, GHashTable *secrets, GError **error) +{ + nm_auto_shvar_file_close shvarFile *keyfile = NULL; + gs_free NMUtilsNamedValue *secrets_arr_free = NULL; + NMUtilsNamedValue secrets_arr_static[30]; + const NMUtilsNamedValue * secrets_arr; + guint secrets_len; + GError * local = NULL; + gboolean any_secrets = FALSE; + guint i; + + keyfile = utils_get_keys_ifcfg(svFileGetName(ifcfg), TRUE); + if (!keyfile) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Failure to create secrets file for '%s'", + svFileGetName(ifcfg)); + return FALSE; + } + + secrets_arr = nm_utils_named_values_from_strdict(secrets, + &secrets_len, + secrets_arr_static, + &secrets_arr_free); + for (i = 0; i < secrets_len; i++) { + const char *k = secrets_arr[i].name; + const char *v = secrets_arr[i].value_str; + + if (v) { + svSetValueStr(keyfile, k, v); + any_secrets = TRUE; + } + } + + if (!any_secrets) + (void) unlink(svFileGetName(keyfile)); + else if (!svWriteFileWithoutDirtyWellknown(keyfile, 0600, &local)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Failure to write secrets to '%s': %s", + svFileGetName(keyfile), + local->message); + return FALSE; + } + + return TRUE; +} + +typedef struct { + const NMSetting8021xSchemeVtable *vtable; + const char * ifcfg_rh_key; +} Setting8021xSchemeVtable; + +static const Setting8021xSchemeVtable setting_8021x_scheme_vtable[] = { +#define _D(_scheme_type, _ifcfg_rh_key) \ + [(_scheme_type)] = { \ + .vtable = &nm_setting_8021x_scheme_vtable[(_scheme_type)], \ + .ifcfg_rh_key = ""_ifcfg_rh_key \ + "", \ + } + _D(NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, "IEEE_8021X_CA_CERT"), + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, "IEEE_8021X_INNER_CA_CERT"), + _D(NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, "IEEE_8021X_CLIENT_CERT"), + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, "IEEE_8021X_INNER_CLIENT_CERT"), + _D(NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, "IEEE_8021X_PRIVATE_KEY"), + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, "IEEE_8021X_INNER_PRIVATE_KEY"), +#undef _D +}; + +static gboolean +write_object(NMSetting8021x * s_8021x, + shvarFile * ifcfg, + GHashTable * secrets, + GHashTable * blobs, + const Setting8021xSchemeVtable *objtype, + gboolean force_write, + GError ** error) +{ + NMSetting8021xCKScheme scheme; + const char * value = NULL; + GBytes * blob = NULL; + const char * password = NULL; + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + char secret_name[100]; + char secret_flags[sizeof(secret_name) + NM_STRLEN("_FLAGS")]; + const char * extension; + char * standard_file; + + g_return_val_if_fail(ifcfg != NULL, FALSE); + g_return_val_if_fail(objtype != NULL, FALSE); + + scheme = (*(objtype->vtable->scheme_func))(s_8021x); + switch (scheme) { + case NM_SETTING_802_1X_CK_SCHEME_UNKNOWN: + break; + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + blob = (*(objtype->vtable->blob_func))(s_8021x); + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + value = (*(objtype->vtable->path_func))(s_8021x); + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + value = (*(objtype->vtable->uri_func))(s_8021x); + break; + default: + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Unhandled certificate object scheme"); + return FALSE; + } + + /* Set the password for certificate/private key. */ + nm_sprintf_buf(secret_name, "%s_PASSWORD", objtype->ifcfg_rh_key); + nm_sprintf_buf(secret_flags, "%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key); + password = (*(objtype->vtable->passwd_func))(s_8021x); + flags = (*(objtype->vtable->pwflag_func))(s_8021x); + set_secret(ifcfg, secrets, secret_name, password, secret_flags, flags); + + if (!objtype->vtable->format_func) + extension = "der"; + else if (objtype->vtable->format_func(s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + extension = "p12"; + else + extension = "pem"; + + /* If the object path was specified, prefer that over any raw cert data that + * may have been sent. + */ + if (value) { + svSetValueStr(ifcfg, objtype->ifcfg_rh_key, value); + return TRUE; + } + + /* If it's raw certificate data, write the data out to the standard file */ + if (blob) { + char *new_file; + + new_file = utils_cert_path(svFileGetName(ifcfg), objtype->vtable->file_suffix, extension); + g_hash_table_replace(blobs, new_file, g_bytes_ref(blob)); + svSetValueStr(ifcfg, objtype->ifcfg_rh_key, new_file); + return TRUE; + } + + /* If certificate/private key wasn't sent, the connection may no longer be + * 802.1x and thus we clear out the paths and certs. + * + * Since no cert/private key is now being used, delete any standard file + * that was created for this connection, but leave other files alone. + * Thus, for example, + * /etc/sysconfig/network-scripts/ca-cert-Test_Write_Wifi_WPA_EAP-TLS.der + * will be deleted, but /etc/pki/tls/cert.pem will not. + */ + standard_file = utils_cert_path(svFileGetName(ifcfg), objtype->vtable->file_suffix, extension); + g_hash_table_replace(blobs, standard_file, NULL); + svSetValue(ifcfg, objtype->ifcfg_rh_key, force_write ? "" : NULL); + return TRUE; +} + +static gboolean +write_blobs(GHashTable *blobs, GError **error) +{ + GHashTableIter iter; + const char * filename; + GBytes * blob; + + if (!blobs) + return TRUE; + + g_hash_table_iter_init(&iter, blobs); + while (g_hash_table_iter_next(&iter, (gpointer *) &filename, (gpointer *) &blob)) { + GError *write_error = NULL; + + if (!blob) { + (void) unlink(filename); + continue; + } + + /* Write the raw certificate data out to the standard file so that we + * can use paths from now on instead of pushing around the certificate + * data itself. + */ + if (!nm_utils_file_set_contents(filename, + (const char *) g_bytes_get_data(blob, NULL), + g_bytes_get_size(blob), + 0600, + NULL, + &write_error)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Could not write certificate to file \"%s\": %s", + filename, + write_error->message); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +write_8021x_certs(NMSetting8021x *s_8021x, + GHashTable * secrets, + GHashTable * blobs, + gboolean phase2, + shvarFile * ifcfg, + GError ** error) +{ + const Setting8021xSchemeVtable *pk_otype = NULL; + gs_free char * value_to_free = NULL; + + /* CA certificate */ + if (!write_object( + s_8021x, + ifcfg, + secrets, + blobs, + phase2 ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] + : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT], + FALSE, + error)) + return FALSE; + + /* Private key */ + if (phase2) + pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY]; + else + pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY]; + + /* Save the private key */ + if (!write_object(s_8021x, ifcfg, secrets, blobs, pk_otype, FALSE, error)) + return FALSE; + + /* Save the client certificate. + * If there is a private key, always write a property for the + * client certificate even if it is empty, so that the reader + * doesn't have to read the private key file to determine if it + * is a PKCS #12 one which serves also as client certificate. + */ + if (!write_object( + s_8021x, + ifcfg, + secrets, + blobs, + phase2 ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] + : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT], + !!svGetValue(ifcfg, pk_otype->ifcfg_rh_key, &value_to_free), + error)) + return FALSE; + + return TRUE; +} + +static gboolean +write_8021x_setting(NMConnection *connection, + shvarFile * ifcfg, + GHashTable * secrets, + GHashTable * blobs, + gboolean wired, + GError ** error) +{ + NMSetting8021x * s_8021x; + NMSetting8021xAuthFlags auth_flags; + const char * value, *match; + gconstpointer ptr; + GBytes * bytes; + char * tmp = NULL; + GString * phase2_auth; + GString * str; + guint32 i, num; + gsize size; + int vint; + + s_8021x = nm_connection_get_setting_802_1x(connection); + if (!s_8021x) + return TRUE; + + /* If wired, write KEY_MGMT */ + if (wired) + svSetValueStr(ifcfg, "KEY_MGMT", "IEEE8021X"); + + /* EAP method */ + if (nm_setting_802_1x_get_num_eap_methods(s_8021x)) { + value = nm_setting_802_1x_get_eap_method(s_8021x, 0); + if (value) + tmp = g_ascii_strup(value, -1); + } + svSetValueStr(ifcfg, "IEEE_8021X_EAP_METHODS", tmp); + g_free(tmp); + + svSetValueStr(ifcfg, "IEEE_8021X_IDENTITY", nm_setting_802_1x_get_identity(s_8021x)); + + svSetValueStr(ifcfg, + "IEEE_8021X_ANON_IDENTITY", + nm_setting_802_1x_get_anonymous_identity(s_8021x)); + + set_secret(ifcfg, + secrets, + "IEEE_8021X_PASSWORD", + nm_setting_802_1x_get_password(s_8021x), + "IEEE_8021X_PASSWORD_FLAGS", + nm_setting_802_1x_get_password_flags(s_8021x)); + + tmp = NULL; + bytes = nm_setting_802_1x_get_password_raw(s_8021x); + if (bytes) { + ptr = g_bytes_get_data(bytes, &size); + tmp = nm_utils_bin2hexstr(ptr, size, -1); + } + set_secret(ifcfg, + secrets, + "IEEE_8021X_PASSWORD_RAW", + tmp, + "IEEE_8021X_PASSWORD_RAW_FLAGS", + nm_setting_802_1x_get_password_raw_flags(s_8021x)); + g_free(tmp); + + svSetValueBoolean_cond_true(ifcfg, + "IEEE_8021X_SYSTEM_CA_CERTS", + nm_setting_802_1x_get_system_ca_certs(s_8021x)); + + value = nm_setting_802_1x_get_phase1_peapver(s_8021x); + svSetValueStr(ifcfg, "IEEE_8021X_PEAP_VERSION", value); + + svSetValueBoolean_cond_true(ifcfg, + "IEEE_8021X_PEAP_FORCE_NEW_LABEL", + nm_streq0(nm_setting_802_1x_get_phase1_peaplabel(s_8021x), "1")); + + svSetValueStr(ifcfg, "IEEE_8021X_PAC_FILE", nm_setting_802_1x_get_pac_file(s_8021x)); + + /* FAST PAC provisioning */ + value = nm_setting_802_1x_get_phase1_fast_provisioning(s_8021x); + if (value) { + if (strcmp(value, "1") == 0) + value = "allow-unauth"; + else if (strcmp(value, "2") == 0) + value = "allow-auth"; + else if (strcmp(value, "3") == 0) + value = "allow-unauth allow-auth"; + else + value = NULL; + } + svSetValueStr(ifcfg, "IEEE_8021X_FAST_PROVISIONING", value); + + /* Phase2 auth methods */ + phase2_auth = g_string_new(NULL); + + value = nm_setting_802_1x_get_phase2_auth(s_8021x); + if (value) { + tmp = g_ascii_strup(value, -1); + g_string_append(phase2_auth, tmp); + g_free(tmp); + } + + value = nm_setting_802_1x_get_phase2_autheap(s_8021x); + if (value) { + if (phase2_auth->len) + g_string_append_c(phase2_auth, ' '); + + tmp = g_ascii_strup(value, -1); + g_string_append_printf(phase2_auth, "EAP-%s", tmp); + g_free(tmp); + } + + auth_flags = nm_setting_802_1x_get_phase1_auth_flags(s_8021x); + if (auth_flags != NM_SETTING_802_1X_AUTH_FLAGS_NONE) { + svSetValueEnum(ifcfg, + "IEEE_8021X_PHASE1_AUTH_FLAGS", + nm_setting_802_1x_auth_flags_get_type(), + auth_flags); + } + + svSetValueStr(ifcfg, + "IEEE_8021X_INNER_AUTH_METHODS", + phase2_auth->len ? phase2_auth->str : NULL); + + g_string_free(phase2_auth, TRUE); + + svSetValueStr(ifcfg, "IEEE_8021X_SUBJECT_MATCH", nm_setting_802_1x_get_subject_match(s_8021x)); + + svSetValueStr(ifcfg, + "IEEE_8021X_PHASE2_SUBJECT_MATCH", + nm_setting_802_1x_get_phase2_subject_match(s_8021x)); + + str = g_string_new(NULL); + num = nm_setting_802_1x_get_num_altsubject_matches(s_8021x); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c(str, ' '); + match = nm_setting_802_1x_get_altsubject_match(s_8021x, i); + g_string_append(str, match); + } + if (str->len > 0) + svSetValueStr(ifcfg, "IEEE_8021X_ALTSUBJECT_MATCHES", str->str); + g_string_free(str, TRUE); + + str = g_string_new(NULL); + num = nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c(str, ' '); + match = nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, i); + g_string_append(str, match); + } + if (str->len > 0) + svSetValueStr(ifcfg, "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES", str->str); + g_string_free(str, TRUE); + + svSetValueStr(ifcfg, + "IEEE_8021X_DOMAIN_SUFFIX_MATCH", + nm_setting_802_1x_get_domain_suffix_match(s_8021x)); + svSetValueStr(ifcfg, + "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", + nm_setting_802_1x_get_phase2_domain_suffix_match(s_8021x)); + + svSetValueStr(ifcfg, "IEEE_8021X_DOMAIN_MATCH", nm_setting_802_1x_get_domain_match(s_8021x)); + svSetValueStr(ifcfg, + "IEEE_8021X_PHASE2_DOMAIN_MATCH", + nm_setting_802_1x_get_phase2_domain_match(s_8021x)); + + vint = nm_setting_802_1x_get_auth_timeout(s_8021x); + svSetValueInt64_cond(ifcfg, "IEEE_8021X_AUTH_TIMEOUT", vint > 0, vint); + + svSetValueBoolean_cond_true(ifcfg, + "IEEE_8021X_OPTIONAL", + nm_setting_802_1x_get_optional(s_8021x)); + + svSetValue(ifcfg, "IEEE_8021X_CA_PATH", nm_setting_802_1x_get_ca_path(s_8021x)); + svSetValue(ifcfg, "IEEE_8021X_PHASE2_CA_PATH", nm_setting_802_1x_get_phase2_ca_path(s_8021x)); + + set_secret(ifcfg, + secrets, + "IEEE_8021X_PIN", + nm_setting_802_1x_get_pin(s_8021x), + "IEEE_8021X_PIN_FLAGS", + nm_setting_802_1x_get_pin_flags(s_8021x)); + + if (!write_8021x_certs(s_8021x, secrets, blobs, FALSE, ifcfg, error)) + return FALSE; + + /* phase2/inner certs */ + if (!write_8021x_certs(s_8021x, secrets, blobs, TRUE, ifcfg, error)) + return FALSE; + + return TRUE; +} + +static gboolean +write_wireless_security_setting(NMConnection *connection, + shvarFile * ifcfg, + GHashTable * secrets, + gboolean adhoc, + gboolean * no_8021x, + GError ** error) +{ + NMSettingWirelessSecurity * s_wsec; + const char * key_mgmt, *auth_alg, *key, *proto, *cipher; + const char * psk = NULL; + gboolean wep = FALSE, wpa = FALSE, dynamic_wep = FALSE; + NMSettingWirelessSecurityWpsMethod wps_method; + char * tmp; + guint32 i, num; + GString * str; + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (!s_wsec) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + return FALSE; + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + nm_assert(key_mgmt); + + auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); + + if (!strcmp(key_mgmt, "none")) { + wep = TRUE; + *no_8021x = TRUE; + } else if (!strcmp(key_mgmt, "wpa-psk")) { + svSetValueStr(ifcfg, "KEY_MGMT", "WPA-PSK"); + wpa = TRUE; + *no_8021x = TRUE; + } else if (!strcmp(key_mgmt, "sae")) { + svSetValueStr(ifcfg, "KEY_MGMT", "SAE"); + wpa = TRUE; + *no_8021x = TRUE; + } else if (!strcmp(key_mgmt, "owe")) { + svSetValueStr(ifcfg, "KEY_MGMT", "OWE"); + wpa = FALSE; + *no_8021x = TRUE; + } else if (!strcmp(key_mgmt, "ieee8021x")) { + svSetValueStr(ifcfg, "KEY_MGMT", "IEEE8021X"); + dynamic_wep = TRUE; + } else if (!strcmp(key_mgmt, "wpa-eap")) { + svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP"); + wpa = TRUE; + } else if (!strcmp(key_mgmt, "wpa-eap-suite-b-192")) { + svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP-SUITE-B-192"); + wpa = TRUE; + } + + if (auth_alg) { + if (!strcmp(auth_alg, "shared")) + svSetValueStr(ifcfg, "SECURITYMODE", "restricted"); + else if (!strcmp(auth_alg, "open")) + svSetValueStr(ifcfg, "SECURITYMODE", "open"); + else if (!strcmp(auth_alg, "leap")) { + svSetValueStr(ifcfg, "SECURITYMODE", "leap"); + svSetValueStr(ifcfg, + "IEEE_8021X_IDENTITY", + nm_setting_wireless_security_get_leap_username(s_wsec)); + set_secret(ifcfg, + secrets, + "IEEE_8021X_PASSWORD", + nm_setting_wireless_security_get_leap_password(s_wsec), + "IEEE_8021X_PASSWORD_FLAGS", + nm_setting_wireless_security_get_leap_password_flags(s_wsec)); + *no_8021x = TRUE; + } + } + + wps_method = nm_setting_wireless_security_get_wps_method(s_wsec); + if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT) + svSetValueEnum(ifcfg, + "WPS_METHOD", + nm_setting_wireless_security_wps_method_get_type(), + wps_method); + + /* WEP keys */ + + /* Clear any default key */ + set_secret(ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + + /* Clear existing keys */ + for (i = 0; i < 4; i++) { + char tag[64]; + + numbered_tag(tag, "KEY_PASSPHRASE", i + 1); + set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + + numbered_tag(tag, "KEY", i + 1); + set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + } + + /* And write the new ones out */ + if (wep) { + NMWepKeyType key_type; + const char * key_type_str = NULL; + + /* Default WEP TX key index */ + svSetValueInt64(ifcfg, + "DEFAULTKEY", + nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) + 1); + + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + switch (key_type) { + case NM_WEP_KEY_TYPE_KEY: + key_type_str = "key"; + break; + case NM_WEP_KEY_TYPE_PASSPHRASE: + key_type_str = "passphrase"; + break; + case NM_WEP_KEY_TYPE_UNKNOWN: + break; + } + svSetValue(ifcfg, "KEY_TYPE", key_type_str); + + for (i = 0; i < 4; i++) { + key = nm_setting_wireless_security_get_wep_key(s_wsec, i); + if (key) { + gs_free char *ascii_key = NULL; + char tag[64]; + gboolean key_valid = TRUE; + + /* Passphrase needs a different ifcfg key since with WEP, there + * are some passphrases that are indistinguishable from WEP hex + * keys. + */ + if (key_type == NM_WEP_KEY_TYPE_UNKNOWN) { + if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY)) + key_type = NM_WEP_KEY_TYPE_KEY; + else if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE)) + key_type = NM_WEP_KEY_TYPE_PASSPHRASE; + } + + if (key_type == NM_WEP_KEY_TYPE_PASSPHRASE) + numbered_tag(tag, "KEY_PASSPHRASE", i + 1); + else if (key_type == NM_WEP_KEY_TYPE_KEY) { + numbered_tag(tag, "KEY", i + 1); + + /* Add 's:' prefix for ASCII keys */ + if (strlen(key) == 5 || strlen(key) == 13) { + ascii_key = g_strdup_printf("s:%s", key); + key = ascii_key; + } + } else { + g_warn_if_reached(); + key_valid = FALSE; + } + + if (key_valid) { + set_secret(ifcfg, + secrets, + tag, + key, + "WEP_KEY_FLAGS", + nm_setting_wireless_security_get_wep_key_flags(s_wsec)); + } + } + } + } + + /* WPA protos */ + num = nm_setting_wireless_security_get_num_protos(s_wsec); + for (i = 0; i < num; i++) { + proto = nm_setting_wireless_security_get_proto(s_wsec, i); + if (proto && !strcmp(proto, "wpa")) + svSetValueStr(ifcfg, "WPA_ALLOW_WPA", "yes"); + else if (proto && !strcmp(proto, "rsn")) + svSetValueStr(ifcfg, "WPA_ALLOW_WPA2", "yes"); + } + + /* WPA Pairwise ciphers */ + str = g_string_new(NULL); + num = nm_setting_wireless_security_get_num_pairwise(s_wsec); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c(str, ' '); + cipher = nm_setting_wireless_security_get_pairwise(s_wsec, i); + + /* Don't write out WEP40 or WEP104 if for some reason they are set; they + * are not valid pairwise ciphers. + */ + if (strcmp(cipher, "wep40") && strcmp(cipher, "wep104")) { + tmp = g_ascii_strup(cipher, -1); + g_string_append(str, tmp); + g_free(tmp); + } + } + if (strlen(str->str) && (dynamic_wep == FALSE)) + svSetValueStr(ifcfg, "CIPHER_PAIRWISE", str->str); + g_string_free(str, TRUE); + + /* WPA Group ciphers */ + str = g_string_new(NULL); + num = nm_setting_wireless_security_get_num_groups(s_wsec); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c(str, ' '); + cipher = nm_setting_wireless_security_get_group(s_wsec, i); + tmp = g_ascii_strup(cipher, -1); + g_string_append(str, tmp); + g_free(tmp); + } + if (strlen(str->str) && (dynamic_wep == FALSE)) + svSetValueStr(ifcfg, "CIPHER_GROUP", str->str); + g_string_free(str, TRUE); + + if (wpa) + psk = nm_setting_wireless_security_get_psk(s_wsec); + + set_secret(ifcfg, + secrets, + "WPA_PSK", + psk, + "WPA_PSK_FLAGS", + wpa ? nm_setting_wireless_security_get_psk_flags(s_wsec) + : NM_SETTING_SECRET_FLAG_NONE); + + if (nm_setting_wireless_security_get_pmf(s_wsec) != NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) { + svSetValueEnum(ifcfg, + "PMF", + nm_setting_wireless_security_pmf_get_type(), + nm_setting_wireless_security_get_pmf(s_wsec)); + } + + if (nm_setting_wireless_security_get_fils(s_wsec) + != NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) { + svSetValueEnum(ifcfg, + "FILS", + nm_setting_wireless_security_fils_get_type(), + nm_setting_wireless_security_get_fils(s_wsec)); + } + + return TRUE; +} + +static gboolean +write_wireless_setting(NMConnection *connection, + shvarFile * ifcfg, + GHashTable * secrets, + gboolean * no_8021x, + GError ** error) +{ + NMSettingWireless *s_wireless; + GBytes * ssid; + const guint8 * ssid_data; + gsize ssid_len; + const char * mode, *bssid; + const char * device_mac, *cloned_mac; + guint32 mtu, chan, i; + gboolean adhoc = FALSE, hex_ssid = FALSE; + const char *const *macaddr_blacklist; + + s_wireless = nm_connection_get_setting_wireless(connection); + if (!s_wireless) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + device_mac = nm_setting_wireless_get_mac_address(s_wireless); + svSetValueStr(ifcfg, "HWADDR", device_mac); + + cloned_mac = nm_setting_wireless_get_cloned_mac_address(s_wireless); + svSetValueStr(ifcfg, "MACADDR", cloned_mac); + + svSetValueStr(ifcfg, + "GENERATE_MAC_ADDRESS_MASK", + nm_setting_wireless_get_generate_mac_address_mask(s_wireless)); + + macaddr_blacklist = nm_setting_wireless_get_mac_address_blacklist(s_wireless); + if (macaddr_blacklist[0]) { + gs_free char *blacklist_str = NULL; + + blacklist_str = g_strjoinv(" ", (char **) macaddr_blacklist); + svSetValueStr(ifcfg, "HWADDR_BLACKLIST", blacklist_str); + } + + mtu = nm_setting_wireless_get_mtu(s_wireless); + svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + if (!ssid) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing SSID in '%s' setting", + NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + ssid_data = g_bytes_get_data(ssid, &ssid_len); + if (!ssid_len || ssid_len > 32) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Invalid SSID in '%s' setting", + NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + /* If the SSID contains any non-printable characters, we need to use the + * hex notation of the SSID instead. + */ + if (ssid_len > 2 && ssid_data[0] == '0' && ssid_data[1] == 'x') { + hex_ssid = TRUE; + for (i = 2; i < ssid_len; i++) { + if (!g_ascii_isxdigit(ssid_data[i])) { + hex_ssid = FALSE; + break; + } + } + } + if (!hex_ssid) { + for (i = 0; i < ssid_len; i++) { + if (!g_ascii_isprint(ssid_data[i])) { + hex_ssid = TRUE; + break; + } + } + } + + if (hex_ssid) { + GString *str; + + /* Hex SSIDs don't get quoted */ + str = g_string_sized_new(ssid_len * 2 + 3); + g_string_append(str, "0x"); + for (i = 0; i < ssid_len; i++) + g_string_append_printf(str, "%02X", ssid_data[i]); + svSetValueStr(ifcfg, "ESSID", str->str); + g_string_free(str, TRUE); + } else { + char buf[33]; + + nm_assert(ssid_len <= 32); + memcpy(buf, ssid_data, ssid_len); + buf[ssid_len] = '\0'; + svSetValueStr(ifcfg, "ESSID", buf); + } + + mode = nm_setting_wireless_get_mode(s_wireless); + if (!mode) { + /* pass */ + } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_INFRA)) + svSetValueStr(ifcfg, "MODE", "Managed"); + else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { + svSetValueStr(ifcfg, "MODE", "Ad-Hoc"); + adhoc = TRUE; + } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_AP)) + svSetValueStr(ifcfg, "MODE", "Ap"); + else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Invalid mode '%s' in '%s' setting", + mode, + NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + chan = nm_setting_wireless_get_channel(s_wireless); + if (chan) { + svSetValueInt64(ifcfg, "CHANNEL", chan); + } else { + /* Band only set if channel is not, since channel implies band */ + svSetValueStr(ifcfg, "BAND", nm_setting_wireless_get_band(s_wireless)); + } + + bssid = nm_setting_wireless_get_bssid(s_wireless); + svSetValueStr(ifcfg, "BSSID", bssid); + + /* Ensure DEFAULTKEY and SECURITYMODE are cleared unless there's security; + * otherwise there's no way to detect WEP vs. open when WEP keys aren't + * saved. + */ + + if (nm_connection_get_setting_wireless_security(connection)) { + if (!write_wireless_security_setting(connection, ifcfg, secrets, adhoc, no_8021x, error)) + return FALSE; + } else { + /* Clear out wifi security keys */ + set_secret(ifcfg, + secrets, + "IEEE_8021X_PASSWORD", + NULL, + "IEEE_8021X_PASSWORD_FLAGS", + NM_SETTING_SECRET_FLAG_NONE); + + /* Clear existing keys */ + set_secret(ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + for (i = 0; i < 4; i++) { + char tag[64]; + + numbered_tag(tag, "KEY_PASSPHRASE", i + 1); + set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + + numbered_tag(tag, "KEY", i + 1); + set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + } + + set_secret(ifcfg, secrets, "WPA_PSK", NULL, "WPA_PSK_FLAGS", NM_SETTING_SECRET_FLAG_NONE); + } + + svSetValueStr(ifcfg, "SSID_HIDDEN", nm_setting_wireless_get_hidden(s_wireless) ? "yes" : NULL); + + switch (nm_setting_wireless_get_powersave(s_wireless)) { + case NM_SETTING_WIRELESS_POWERSAVE_IGNORE: + svSetValueStr(ifcfg, "POWERSAVE", "ignore"); + break; + case NM_SETTING_WIRELESS_POWERSAVE_DISABLE: + svSetValueStr(ifcfg, "POWERSAVE", "disable"); + break; + case NM_SETTING_WIRELESS_POWERSAVE_ENABLE: + svSetValueStr(ifcfg, "POWERSAVE", "enable"); + break; + default: + case NM_SETTING_WIRELESS_POWERSAVE_DEFAULT: + break; + } + + switch (nm_setting_wireless_get_mac_address_randomization(s_wireless)) { + case NM_SETTING_MAC_RANDOMIZATION_NEVER: + svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "never"); + break; + case NM_SETTING_MAC_RANDOMIZATION_ALWAYS: + svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "always"); + break; + case NM_SETTING_MAC_RANDOMIZATION_DEFAULT: + default: + svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "default"); + break; + } + + svSetValueTernary(ifcfg, "AP_ISOLATION", nm_setting_wireless_get_ap_isolation(s_wireless)); + + svSetValueStr(ifcfg, "TYPE", TYPE_WIRELESS); + + return TRUE; +} + +static gboolean +write_infiniband_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingInfiniband *s_infiniband; + const char * mac, *transport_mode, *parent; + guint32 mtu; + int p_key; + + s_infiniband = nm_connection_get_setting_infiniband(connection); + if (!s_infiniband) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_INFINIBAND_SETTING_NAME); + return FALSE; + } + + mac = nm_setting_infiniband_get_mac_address(s_infiniband); + svSetValueStr(ifcfg, "HWADDR", mac); + + mtu = nm_setting_infiniband_get_mtu(s_infiniband); + svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu); + + transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband); + svSetValueBoolean(ifcfg, "CONNECTED_MODE", nm_streq(transport_mode, "connected")); + + p_key = nm_setting_infiniband_get_p_key(s_infiniband); + if (p_key != -1) { + svSetValueStr(ifcfg, "PKEY", "yes"); + svSetValueInt64(ifcfg, "PKEY_ID", p_key); + + parent = nm_setting_infiniband_get_parent(s_infiniband); + if (parent) + svSetValueStr(ifcfg, "PHYSDEV", parent); + } + + svSetValueStr(ifcfg, "TYPE", TYPE_INFINIBAND); + + return TRUE; +} + +static void +write_hostname_setting(NMConnection *connection, shvarFile *ifcfg) +{ + NMSettingHostname *s_hostname; + NMTernary t; + + s_hostname = _nm_connection_get_setting(connection, NM_TYPE_SETTING_HOSTNAME); + if (!s_hostname) + return; + + svSetValueInt64(ifcfg, "HOSTNAME_PRIORITY", nm_setting_hostname_get_priority(s_hostname)); + + t = nm_setting_hostname_get_from_dhcp(s_hostname); + svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DHCP", t != NM_TERNARY_DEFAULT, t); + + t = nm_setting_hostname_get_from_dns_lookup(s_hostname); + svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP", t != NM_TERNARY_DEFAULT, t); + + t = nm_setting_hostname_get_only_from_default(s_hostname); + svSetValueInt64_cond(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT", t != NM_TERNARY_DEFAULT, t); +} + +static gboolean +write_wired_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingWired * s_wired; + const char *const *s390_subchannels; + guint32 mtu, num_opts, i; + const char *const *macaddr_blacklist; + + s_wired = nm_connection_get_setting_wired(connection); + if (!s_wired) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_WIRED_SETTING_NAME); + return FALSE; + } + + svSetValueStr(ifcfg, "HWADDR", nm_setting_wired_get_mac_address(s_wired)); + + svSetValueStr(ifcfg, "MACADDR", nm_setting_wired_get_cloned_mac_address(s_wired)); + + svSetValueStr(ifcfg, + "GENERATE_MAC_ADDRESS_MASK", + nm_setting_wired_get_generate_mac_address_mask(s_wired)); + + macaddr_blacklist = nm_setting_wired_get_mac_address_blacklist(s_wired); + if (macaddr_blacklist[0]) { + gs_free char *blacklist_str = NULL; + + blacklist_str = g_strjoinv(" ", (char **) macaddr_blacklist); + svSetValueStr(ifcfg, "HWADDR_BLACKLIST", blacklist_str); + } + + mtu = nm_setting_wired_get_mtu(s_wired); + svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu); + + s390_subchannels = nm_setting_wired_get_s390_subchannels(s_wired); + + { + gs_free char *tmp = NULL; + gsize len = NM_PTRARRAY_LEN(s390_subchannels); + + if (len == 2) { + tmp = g_strdup_printf("%s,%s", s390_subchannels[0], s390_subchannels[1]); + } else if (len == 3) { + tmp = g_strdup_printf("%s,%s,%s", + s390_subchannels[0], + s390_subchannels[1], + s390_subchannels[2]); + } + + svSetValueStr(ifcfg, "SUBCHANNELS", tmp); + } + + svSetValueStr(ifcfg, "NETTYPE", nm_setting_wired_get_s390_nettype(s_wired)); + + svSetValueStr(ifcfg, "PORTNAME", nm_setting_wired_get_s390_option_by_key(s_wired, "portname")); + + svSetValueStr(ifcfg, "CTCPROT", nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot")); + + num_opts = nm_setting_wired_get_num_s390_options(s_wired); + if (s390_subchannels && num_opts) { + nm_auto_free_gstring GString *tmp = NULL; + + for (i = 0; i < num_opts; i++) { + const char *s390_key, *s390_val; + + nm_setting_wired_get_s390_option(s_wired, i, &s390_key, &s390_val); + + /* portname is handled separately */ + if (NM_IN_STRSET(s390_key, "portname", "ctcprot")) + continue; + + if (strchr(s390_key, '=')) { + /* this key cannot be expressed. But after all, it's not valid anyway + * and the connection shouldn't even verify. */ + continue; + } + + if (!tmp) + tmp = g_string_sized_new(30); + else + g_string_append_c(tmp, ' '); + nm_utils_escaped_tokens_escape_gstr(s390_key, NM_ASCII_SPACES, tmp); + g_string_append_c(tmp, '='); + nm_utils_escaped_tokens_escape_gstr(s390_val, NM_ASCII_SPACES, tmp); + } + if (tmp) + svSetValueStr(ifcfg, "OPTIONS", tmp->str); + } + + svSetValueStr(ifcfg, "TYPE", TYPE_ETHERNET); + + return TRUE; +} + +static void +_ethtool_gstring_prepare(GString **str, gboolean *is_first, char cmdline_flag, const char *iface) +{ + if (!*is_first) { + nm_assert(*str && (*str)->len > 0); + return; + } + + if (!*str) + *str = g_string_sized_new(30); + else { + nm_assert((*str)->len > 0); + g_string_append(*str, " ; "); + } + g_string_append_printf(*str, "-%c %s", cmdline_flag, iface); + *is_first = FALSE; +} + +static gboolean +write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingWired * s_wired; + NMSettingEthtool * s_ethtool; + const char * duplex; + guint32 speed; + GString * str = NULL; + gboolean auto_negotiate; + NMSettingWiredWakeOnLan wol; + const char * wol_password; + + s_wired = nm_connection_get_setting_wired(connection); + s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(connection, NM_TYPE_SETTING_ETHTOOL)); + + if (!s_wired && !s_ethtool) + return TRUE; + + if (s_wired) { + auto_negotiate = nm_setting_wired_get_auto_negotiate(s_wired); + speed = nm_setting_wired_get_speed(s_wired); + duplex = nm_setting_wired_get_duplex(s_wired); + + /* autoneg off + speed 0 + duplex NULL, means we want NM + * to skip link configuration which is default. So write + * down link config only if we have auto-negotiate true or + * a valid value for one among speed and duplex. + */ + if (auto_negotiate) { + str = g_string_sized_new(64); + g_string_printf(str, "autoneg on"); + } else if (speed || duplex) { + str = g_string_sized_new(64); + g_string_printf(str, "autoneg off"); + } + if (speed) + g_string_append_printf(str, " speed %u", speed); + if (duplex) + g_string_append_printf(str, " duplex %s", duplex); + + wol = nm_setting_wired_get_wake_on_lan(s_wired); + wol_password = nm_setting_wired_get_wake_on_lan_password(s_wired); + + svSetValue(ifcfg, + "ETHTOOL_WAKE_ON_LAN", + wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE ? "ignore" : NULL); + if (!NM_IN_SET(wol, + NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE, + NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT)) { + if (!str) + str = g_string_sized_new(30); + else + g_string_append(str, " "); + + g_string_append(str, "wol "); + + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY)) + g_string_append(str, "p"); + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST)) + g_string_append(str, "u"); + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST)) + g_string_append(str, "m"); + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST)) + g_string_append(str, "b"); + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP)) + g_string_append(str, "a"); + if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) + g_string_append(str, "g"); + + if (!NM_FLAGS_ANY(wol, NM_SETTING_WIRED_WAKE_ON_LAN_ALL)) + g_string_append(str, "d"); + + if (wol_password && NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) + g_string_append_printf(str, "s sopass %s", wol_password); + } + } + + if (s_ethtool) { + NMEthtoolID ethtool_id; + NMSettingConnection *s_con; + const char * iface; + gboolean is_first; + guint32 u32; + gboolean b; + + s_con = nm_connection_get_setting_connection(connection); + if (s_con) { + iface = nm_setting_connection_get_interface_name(s_con); + if (iface + && (!iface[0] + || !NM_STRCHAR_ALL(iface, + ch, + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') || NM_IN_SET(ch, '_')))) + iface = NULL; + } else + iface = NULL; + if (!iface) + iface = "net0"; + + is_first = TRUE; + for (ethtool_id = _NM_ETHTOOL_ID_FEATURE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST; + ethtool_id++) { + nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + if (!nm_setting_option_get_boolean(NM_SETTING(s_ethtool), + nm_ethtool_data[ethtool_id]->optname, + &b)) + continue; + + _ethtool_gstring_prepare(&str, &is_first, 'K', iface); + g_string_append_c(str, ' '); + g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + g_string_append(str, b ? " on" : " off"); + } + + is_first = TRUE; + for (ethtool_id = _NM_ETHTOOL_ID_COALESCE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_COALESCE_LAST; + ethtool_id++) { + nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + if (!nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + nm_ethtool_data[ethtool_id]->optname, + &u32)) + continue; + + _ethtool_gstring_prepare(&str, &is_first, 'C', iface); + g_string_append_c(str, ' '); + g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32); + } + + is_first = TRUE; + for (ethtool_id = _NM_ETHTOOL_ID_RING_FIRST; ethtool_id <= _NM_ETHTOOL_ID_RING_LAST; + ethtool_id++) { + nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + if (!nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + nm_ethtool_data[ethtool_id]->optname, + &u32)) + continue; + + _ethtool_gstring_prepare(&str, &is_first, 'G', iface); + g_string_append_c(str, ' '); + g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); + g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32); + } + } + + if (str) { + svSetValueStr(ifcfg, "ETHTOOL_OPTS", str->str); + g_string_free(str, TRUE); + } + + return TRUE; +} + +static char * +vlan_priority_maplist_to_stringlist(NMSettingVlan *s_vlan, NMVlanPriorityMap map) +{ + char **strlist; + char * value; + + if (map == NM_VLAN_INGRESS_MAP) + g_object_get(G_OBJECT(s_vlan), NM_SETTING_VLAN_INGRESS_PRIORITY_MAP, &strlist, NULL); + else if (map == NM_VLAN_EGRESS_MAP) + g_object_get(G_OBJECT(s_vlan), NM_SETTING_VLAN_EGRESS_PRIORITY_MAP, &strlist, NULL); + else + return NULL; + + if (strlist[0]) + value = g_strjoinv(",", strlist); + else + value = NULL; + g_strfreev(strlist); + + return value; +} + +static gboolean +write_wired_for_virtual(NMConnection *connection, shvarFile *ifcfg) +{ + NMSettingWired *s_wired; + gboolean has_wired = FALSE; + + s_wired = nm_connection_get_setting_wired(connection); + if (s_wired) { + const char *device_mac, *cloned_mac; + guint32 mtu; + + has_wired = TRUE; + + device_mac = nm_setting_wired_get_mac_address(s_wired); + svSetValue(ifcfg, "HWADDR", device_mac ?: ""); + + cloned_mac = nm_setting_wired_get_cloned_mac_address(s_wired); + svSetValueStr(ifcfg, "MACADDR", cloned_mac); + + svSetValueStr(ifcfg, + "GENERATE_MAC_ADDRESS_MASK", + nm_setting_wired_get_generate_mac_address_mask(s_wired)); + + mtu = nm_setting_wired_get_mtu(s_wired); + svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu); + } + return has_wired; +} + +static gboolean +write_vlan_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error) +{ + NMSettingVlan *s_vlan; + char * tmp; + guint32 vlan_flags = 0; + gsize s_buf_len; + char s_buf[50], *s_buf_ptr; + + s_vlan = nm_connection_get_setting_vlan(connection); + if (!s_vlan) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing VLAN setting"); + return FALSE; + } + + svSetValueStr(ifcfg, "VLAN", "yes"); + svSetValueStr(ifcfg, "TYPE", TYPE_VLAN); + svSetValueStr(ifcfg, "PHYSDEV", nm_setting_vlan_get_parent(s_vlan)); + svSetValueInt64(ifcfg, "VLAN_ID", nm_setting_vlan_get_id(s_vlan)); + + vlan_flags = nm_setting_vlan_get_flags(s_vlan); + svSetValueBoolean(ifcfg, "REORDER_HDR", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS)); + svSetValueBoolean(ifcfg, "GVRP", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_GVRP)); + + nm_utils_strbuf_init(s_buf, &s_buf_ptr, &s_buf_len); + + if (NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_LOOSE_BINDING)) + nm_utils_strbuf_append_str(&s_buf_ptr, &s_buf_len, "LOOSE_BINDING"); + if (!NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS)) + nm_utils_strbuf_append(&s_buf_ptr, &s_buf_len, "%sNO_REORDER_HDR", s_buf[0] ? "," : ""); + + svSetValueStr(ifcfg, "VLAN_FLAGS", s_buf); + + svSetValueBoolean(ifcfg, "MVRP", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_MVRP)); + + tmp = vlan_priority_maplist_to_stringlist(s_vlan, NM_VLAN_INGRESS_MAP); + svSetValueStr(ifcfg, "VLAN_INGRESS_PRIORITY_MAP", tmp); + g_free(tmp); + + tmp = vlan_priority_maplist_to_stringlist(s_vlan, NM_VLAN_EGRESS_MAP); + svSetValueStr(ifcfg, "VLAN_EGRESS_PRIORITY_MAP", tmp); + g_free(tmp); + + *wired = write_wired_for_virtual(connection, ifcfg); + + return TRUE; +} + +static gboolean +write_bond_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error) +{ + NMSettingBond *s_bond; + guint32 i, num_opts; + + s_bond = nm_connection_get_setting_bond(connection); + if (!s_bond) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_BOND_SETTING_NAME); + return FALSE; + } + + num_opts = nm_setting_bond_get_num_options(s_bond); + if (num_opts) { + nm_auto_free_gstring GString *str = NULL; + const char * name, *value; + + str = g_string_sized_new(64); + for (i = 0; i < num_opts; i++) { + if (str->len) + g_string_append_c(str, ' '); + nm_setting_bond_get_option(s_bond, i, &name, &value); + g_string_append_printf(str, "%s=%s", name, value); + } + + svSetValueStr(ifcfg, "BONDING_OPTS", str->str); + } + + svSetValueStr(ifcfg, "TYPE", TYPE_BOND); + svSetValueStr(ifcfg, "BONDING_MASTER", "yes"); + + *wired = write_wired_for_virtual(connection, ifcfg); + + return TRUE; +} + +static gboolean +write_team_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error) +{ + NMSettingTeam *s_team; + const char * config; + + s_team = nm_connection_get_setting_team(connection); + if (!s_team) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_TEAM_SETTING_NAME); + return FALSE; + } + + config = nm_setting_team_get_config(s_team); + svSetValueStr(ifcfg, "TEAM_CONFIG", config); + + *wired = write_wired_for_virtual(connection, ifcfg); + + return TRUE; +} + +#define get_setting_default_checked_boolean(dflt, setting, prop) \ + ({ \ + const gboolean _dflt = (dflt); \ + \ + nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN( \ + g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \ + == _dflt); \ + _dflt; \ + }) + +#define get_setting_default_checked_uint(dflt, setting, prop) \ + ({ \ + const guint _dflt = (dflt); \ + \ + nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_UINT( \ + g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \ + == _dflt); \ + _dflt; \ + }) + +#define get_setting_default_checked_uint64(dflt, setting, prop) \ + ({ \ + const guint64 _dflt = (dflt); \ + \ + nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_UINT64( \ + g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \ + == _dflt); \ + _dflt; \ + }) + +static gboolean +write_bridge_vlans(NMSetting * setting, + const char *property_name, + shvarFile * ifcfg, + const char *key, + GError ** error) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + NMBridgeVlan * vlan; + GString * string; + guint i; + + g_object_get(setting, property_name, &vlans, NULL); + + if (!vlans || !vlans->len) + return TRUE; + + string = g_string_new(""); + for (i = 0; i < vlans->len; i++) { + gs_free char *vlan_str = NULL; + + vlan = vlans->pdata[i]; + vlan_str = nm_bridge_vlan_to_str(vlan, error); + if (!vlan_str) + return FALSE; + if (string->len > 0) + g_string_append(string, ","); + nm_utils_escaped_tokens_escape_gstr_assert(vlan_str, ",", string); + } + + svSetValueStr(ifcfg, key, string->str); + g_string_free(string, TRUE); + return TRUE; +} + +static gboolean +write_bridge_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error) +{ + NMSettingBridge *s_bridge; + guint32 u32; + guint64 u64; + guint u; + gboolean b; + const char * s; + GString * opts; + + s_bridge = nm_connection_get_setting_bridge(connection); + if (!s_bridge) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Missing '%s' setting", + NM_SETTING_BRIDGE_SETTING_NAME); + return FALSE; + } + + svSetValueBoolean(ifcfg, "STP", FALSE); + + s = nm_setting_bridge_get_mac_address(s_bridge); + svSetValueStr(ifcfg, "BRIDGE_MACADDR", s); + + /* Bridge options */ + opts = g_string_sized_new(32); + + if (nm_setting_bridge_get_stp(s_bridge)) { + svSetValueStr(ifcfg, "STP", "yes"); + + u32 = nm_setting_bridge_get_forward_delay(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_FORWARD_DELAY_DEF, + s_bridge, + NM_SETTING_BRIDGE_FORWARD_DELAY)) + svSetValueInt64(ifcfg, "DELAY", u32); + + g_string_append_printf(opts, "priority=%u", nm_setting_bridge_get_priority(s_bridge)); + + u32 = nm_setting_bridge_get_hello_time(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_HELLO_TIME_DEF, + s_bridge, + NM_SETTING_BRIDGE_HELLO_TIME)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "hello_time=%u", u32); + } + + u32 = nm_setting_bridge_get_max_age(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_MAX_AGE_DEF, + s_bridge, + NM_SETTING_BRIDGE_MAX_AGE)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "max_age=%u", u32); + } + } + + u = nm_setting_bridge_get_ageing_time(s_bridge); + if (u + != get_setting_default_checked_uint(NM_BRIDGE_AGEING_TIME_DEF, + s_bridge, + NM_SETTING_BRIDGE_AGEING_TIME)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "ageing_time=%u", u); + } + + s = nm_setting_bridge_get_group_address(s_bridge); + if (s) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "group_address=%s", s); + } + + u32 = nm_setting_bridge_get_group_forward_mask(s_bridge); + if (u32 + != get_setting_default_checked_uint(0, s_bridge, NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "group_fwd_mask=%u", u32); + } + + u32 = nm_setting_bridge_get_multicast_hash_max(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_HASH_MAX_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_HASH_MAX)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_hash_max=%u", u32); + } + + u32 = nm_setting_bridge_get_multicast_last_member_count(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_last_member_count=%u", u32); + } + + u64 = nm_setting_bridge_get_multicast_last_member_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_last_member_interval=%" G_GUINT64_FORMAT, u64); + } + + u64 = nm_setting_bridge_get_multicast_membership_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_membership_interval=%" G_GUINT64_FORMAT, u64); + } + + b = nm_setting_bridge_get_multicast_querier(s_bridge); + if (b + != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_QUERIER_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_QUERIER)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_querier=%u", (guint) b); + } + + u64 = nm_setting_bridge_get_multicast_querier_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_querier_interval=%" G_GUINT64_FORMAT, u64); + } + + u64 = nm_setting_bridge_get_multicast_query_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_query_interval=%" G_GUINT64_FORMAT, u64); + } + + u64 = nm_setting_bridge_get_multicast_query_response_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64( + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_query_response_interval=%" G_GUINT64_FORMAT, u64); + } + + b = nm_setting_bridge_get_multicast_query_use_ifaddr(s_bridge); + if (b + != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_query_use_ifaddr=%u", (guint) b); + } + + b = nm_setting_bridge_get_multicast_snooping(s_bridge); + if (b + != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_SNOOPING_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_snooping=%u", (guint32) b); + } + + u32 = nm_setting_bridge_get_multicast_startup_query_count(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_startup_query_count=%u", u32); + } + + u64 = nm_setting_bridge_get_multicast_startup_query_interval(s_bridge); + if (u64 + != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF, + s_bridge, + NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_startup_query_interval=%" G_GUINT64_FORMAT, u64); + } + + s = nm_setting_bridge_get_multicast_router(s_bridge); + if (s) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "multicast_router=%s", s); + } + + b = nm_setting_bridge_get_vlan_filtering(s_bridge); + if (b + != get_setting_default_checked_boolean(FALSE, s_bridge, NM_SETTING_BRIDGE_VLAN_FILTERING)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "vlan_filtering=%u", (guint32) b); + } + + u32 = nm_setting_bridge_get_vlan_default_pvid(s_bridge); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_VLAN_DEFAULT_PVID_DEF, + s_bridge, + NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "default_pvid=%u", u32); + } + + s = nm_setting_bridge_get_vlan_protocol(s_bridge); + if (s) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "vlan_protocol=%s", s); + } + + b = nm_setting_bridge_get_vlan_stats_enabled(s_bridge); + if (b + != get_setting_default_checked_boolean(NM_BRIDGE_VLAN_STATS_ENABLED_DEF, + s_bridge, + NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) { + nm_gstring_add_space_delimiter(opts); + g_string_append_printf(opts, "vlan_stats_enabled=%u", (guint) b); + } + + if (opts->len) + svSetValueStr(ifcfg, "BRIDGING_OPTS", opts->str); + g_string_free(opts, TRUE); + + if (!write_bridge_vlans((NMSetting *) s_bridge, + NM_SETTING_BRIDGE_VLANS, + ifcfg, + "BRIDGE_VLANS", + error)) + return FALSE; + + svSetValueStr(ifcfg, "TYPE", TYPE_BRIDGE); + + *wired = write_wired_for_virtual(connection, ifcfg); + + return TRUE; +} + +static gboolean +write_bridge_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingBridgePort *s_port; + guint32 u32; + GString * string; + + s_port = nm_connection_get_setting_bridge_port(connection); + if (!s_port) + return TRUE; + + /* Bridge options */ + string = g_string_sized_new(32); + + u32 = nm_setting_bridge_port_get_priority(s_port); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_PORT_PRIORITY_DEF, + s_port, + NM_SETTING_BRIDGE_PORT_PRIORITY)) + g_string_append_printf(string, "priority=%u", u32); + + u32 = nm_setting_bridge_port_get_path_cost(s_port); + if (u32 + != get_setting_default_checked_uint(NM_BRIDGE_PORT_PATH_COST_DEF, + s_port, + NM_SETTING_BRIDGE_PORT_PATH_COST)) { + if (string->len) + g_string_append_c(string, ' '); + g_string_append_printf(string, "path_cost=%u", u32); + } + + if (nm_setting_bridge_port_get_hairpin_mode(s_port)) { + if (string->len) + g_string_append_c(string, ' '); + g_string_append_printf(string, "hairpin_mode=1"); + } + + if (string->len) + svSetValueStr(ifcfg, "BRIDGING_OPTS", string->str); + g_string_free(string, TRUE); + + if (!write_bridge_vlans((NMSetting *) s_port, + NM_SETTING_BRIDGE_PORT_VLANS, + ifcfg, + "BRIDGE_PORT_VLANS", + error)) + return FALSE; + + return TRUE; +} + +static gboolean +write_team_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingTeamPort *s_port; + const char * config; + + s_port = nm_connection_get_setting_team_port(connection); + if (!s_port) + return TRUE; + + config = nm_setting_team_port_get_config(s_port); + svSetValueStr(ifcfg, "TEAM_PORT_CONFIG", config); + + return TRUE; +} + +static void +write_dcb_flags(shvarFile *ifcfg, const char *tag, NMSettingDcbFlags flags) +{ + char prop[NM_STRLEN("DCB_xxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyy")]; + + nm_sprintf_buf(prop, "DCB_%s_ENABLE", tag); + svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_ENABLE) ? "yes" : NULL); + + nm_sprintf_buf(prop, "DCB_%s_ADVERTISE", tag); + svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_ADVERTISE) ? "yes" : NULL); + + nm_sprintf_buf(prop, "DCB_%s_WILLING", tag); + svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_WILLING) ? "yes" : NULL); +} + +static void +write_dcb_app(shvarFile *ifcfg, const char *tag, NMSettingDcbFlags flags, int priority) +{ + char prop[NM_STRLEN("DCB_xxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyy")]; + + write_dcb_flags(ifcfg, tag, flags); + + if ((flags & NM_SETTING_DCB_FLAG_ENABLE) && (priority >= 0)) { + nm_sprintf_buf(prop, "DCB_%s_PRIORITY", tag); + svSetValueInt64(ifcfg, prop, priority); + } +} + +typedef gboolean (*DcbGetBoolFunc)(NMSettingDcb *, guint); + +static void +write_dcb_bool_array(shvarFile * ifcfg, + const char * key, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + DcbGetBoolFunc get_func) +{ + char str[9]; + guint i; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) + return; + + str[8] = 0; + for (i = 0; i < 8; i++) + str[i] = get_func(s_dcb, i) ? '1' : '0'; + svSetValueStr(ifcfg, key, str); +} + +typedef guint (*DcbGetUintFunc)(NMSettingDcb *, guint); + +static void +write_dcb_uint_array(shvarFile * ifcfg, + const char * key, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + DcbGetUintFunc get_func) +{ + char str[9]; + guint i, num; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) + return; + + str[8] = 0; + for (i = 0; i < 8; i++) { + num = get_func(s_dcb, i); + if (num < 10) + str[i] = '0' + num; + else if (num == 15) + str[i] = 'f'; + else + g_assert_not_reached(); + } + svSetValueStr(ifcfg, key, str); +} + +static void +write_dcb_percent_array(shvarFile * ifcfg, + const char * key, + NMSettingDcb * s_dcb, + NMSettingDcbFlags flags, + DcbGetUintFunc get_func) +{ + GString *str; + guint i; + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) + return; + + str = g_string_sized_new(30); + for (i = 0; i < 8; i++) { + if (str->len) + g_string_append_c(str, ','); + g_string_append_printf(str, "%d", get_func(s_dcb, i)); + } + svSetValueStr(ifcfg, key, str->str); + g_string_free(str, TRUE); +} + +static gboolean +write_dcb_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingDcb * s_dcb; + NMSettingDcbFlags flags; + + s_dcb = nm_connection_get_setting_dcb(connection); + if (!s_dcb) + return TRUE; + + svSetValueStr(ifcfg, "DCB", "yes"); + + write_dcb_app(ifcfg, + "APP_FCOE", + nm_setting_dcb_get_app_fcoe_flags(s_dcb), + nm_setting_dcb_get_app_fcoe_priority(s_dcb)); + if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) + svSetValueStr(ifcfg, KEY_DCB_APP_FCOE_MODE, nm_setting_dcb_get_app_fcoe_mode(s_dcb)); + + write_dcb_app(ifcfg, + "APP_ISCSI", + nm_setting_dcb_get_app_iscsi_flags(s_dcb), + nm_setting_dcb_get_app_iscsi_priority(s_dcb)); + write_dcb_app(ifcfg, + "APP_FIP", + nm_setting_dcb_get_app_fip_flags(s_dcb), + nm_setting_dcb_get_app_fip_priority(s_dcb)); + + write_dcb_flags(ifcfg, "PFC", nm_setting_dcb_get_priority_flow_control_flags(s_dcb)); + write_dcb_bool_array(ifcfg, + KEY_DCB_PFC_UP, + s_dcb, + nm_setting_dcb_get_priority_flow_control_flags(s_dcb), + nm_setting_dcb_get_priority_flow_control); + + flags = nm_setting_dcb_get_priority_group_flags(s_dcb); + write_dcb_flags(ifcfg, "PG", flags); + write_dcb_uint_array(ifcfg, KEY_DCB_PG_ID, s_dcb, flags, nm_setting_dcb_get_priority_group_id); + write_dcb_percent_array(ifcfg, + KEY_DCB_PG_PCT, + s_dcb, + flags, + nm_setting_dcb_get_priority_group_bandwidth); + write_dcb_percent_array(ifcfg, + KEY_DCB_PG_UPPCT, + s_dcb, + flags, + nm_setting_dcb_get_priority_bandwidth); + write_dcb_bool_array(ifcfg, + KEY_DCB_PG_STRICT, + s_dcb, + flags, + nm_setting_dcb_get_priority_strict_bandwidth); + write_dcb_uint_array(ifcfg, + KEY_DCB_PG_UP2TC, + s_dcb, + flags, + nm_setting_dcb_get_priority_traffic_class); + + return TRUE; +} + +static void +write_connection_setting(NMSettingConnection *s_con, shvarFile *ifcfg) +{ + guint32 n, i; + nm_auto_free_gstring GString *str = NULL; + const char * master, *master_iface = NULL, *type; + int vint; + gint32 vint32; + NMSettingConnectionMdns mdns; + NMSettingConnectionLlmnr llmnr; + guint32 vuint32; + const char * tmp, *mud_url; + + svSetValueStr(ifcfg, "NAME", nm_setting_connection_get_id(s_con)); + svSetValueStr(ifcfg, "UUID", nm_setting_connection_get_uuid(s_con)); + svSetValueStr(ifcfg, "STABLE_ID", nm_setting_connection_get_stable_id(s_con)); + svSetValueStr(ifcfg, "DEVICE", nm_setting_connection_get_interface_name(s_con)); + svSetValueBoolean(ifcfg, "ONBOOT", nm_setting_connection_get_autoconnect(s_con)); + + vint = nm_setting_connection_get_autoconnect_priority(s_con); + svSetValueInt64_cond(ifcfg, + "AUTOCONNECT_PRIORITY", + vint != NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT, + vint); + + vint = nm_setting_connection_get_autoconnect_retries(s_con); + svSetValueInt64_cond(ifcfg, "AUTOCONNECT_RETRIES", vint != -1, vint); + + vint = nm_setting_connection_get_multi_connect(s_con); + svSetValueInt64_cond(ifcfg, "MULTI_CONNECT", vint != NM_CONNECTION_MULTI_CONNECT_DEFAULT, vint); + + /* Only save the value for master connections */ + type = nm_setting_connection_get_connection_type(s_con); + if (_nm_connection_type_is_master(type)) { + NMSettingConnectionAutoconnectSlaves autoconnect_slaves; + autoconnect_slaves = nm_setting_connection_get_autoconnect_slaves(s_con); + svSetValueStr(ifcfg, + "AUTOCONNECT_SLAVES", + autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES ? "yes" + : autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO ? "no" + : NULL); + } + switch (nm_setting_connection_get_lldp(s_con)) { + case NM_SETTING_CONNECTION_LLDP_ENABLE_RX: + tmp = "rx"; + break; + case NM_SETTING_CONNECTION_LLDP_DISABLE: + tmp = "no"; + break; + default: + tmp = NULL; + } + svSetValueStr(ifcfg, "LLDP", tmp); + + /* Permissions */ + n = nm_setting_connection_get_num_permissions(s_con); + if (n > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < n; i++) { + const char *ptype = NULL; + const char *puser = NULL; + + if (!nm_setting_connection_get_permission(s_con, i, &ptype, &puser, NULL)) + continue; + if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER)) + continue; + + /* Items separated by space for consistency with eg + * IPV6ADDR_SECONDARIES and DOMAIN. + */ + if (str->len) + g_string_append_c(str, ' '); + + g_string_append(str, puser); + } + svSetValueStr(ifcfg, "USERS", str->str); + } + + svSetValueStr(ifcfg, "ZONE", nm_setting_connection_get_zone(s_con)); + + svSetValueStr(ifcfg, "MASTER_UUID", NULL); + svSetValueStr(ifcfg, "MASTER", NULL); + svSetValueStr(ifcfg, "SLAVE", NULL); + svSetValueStr(ifcfg, "BRIDGE_UUID", NULL); + svSetValueStr(ifcfg, "BRIDGE", NULL); + svSetValueStr(ifcfg, "TEAM_MASTER_UUID", NULL); + svSetValueStr(ifcfg, "TEAM_MASTER", NULL); + + mud_url = nm_setting_connection_get_mud_url(s_con); + svSetValue(ifcfg, "MUD_URL", mud_url); + + master = nm_setting_connection_get_master(s_con); + if (master) { + /* The reader prefers the *_UUID variants, however we still try to resolve + * it into an interface name, so that legacy tooling is not confused. */ + if (!nm_utils_get_testing()) { + /* This is conditional for easier testing. */ + master_iface = nm_manager_iface_for_uuid(NM_MANAGER_GET, master); + } + if (!master_iface) { + master_iface = master; + master = NULL; + } + + if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_BOND_SETTING_NAME)) { + svSetValueStr(ifcfg, "MASTER_UUID", master); + svSetValueStr(ifcfg, "MASTER", master_iface); + svSetValueStr(ifcfg, "SLAVE", "yes"); + } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_BRIDGE_SETTING_NAME)) { + svSetValueStr(ifcfg, "BRIDGE_UUID", master); + svSetValueStr(ifcfg, "BRIDGE", master_iface); + } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_TEAM_SETTING_NAME)) { + svSetValueStr(ifcfg, "TEAM_MASTER_UUID", master); + svSetValueStr(ifcfg, "TEAM_MASTER", master_iface); + if (NM_IN_STRSET(type, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_VLAN_SETTING_NAME)) + svUnsetValue(ifcfg, "TYPE"); + } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_OVS_PORT_SETTING_NAME)) { + svSetValueStr(ifcfg, "OVS_PORT_UUID", master); + svSetValueStr(ifcfg, "OVS_PORT", master_iface); + } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_VRF_SETTING_NAME)) { + svSetValueStr(ifcfg, "VRF_UUID", master); + svSetValueStr(ifcfg, "VRF", master_iface); + } else { + _LOGW("don't know how to set master for a %s slave", + nm_setting_connection_get_slave_type(s_con)); + } + } + + if (nm_streq0(type, NM_SETTING_TEAM_SETTING_NAME)) + svSetValueStr(ifcfg, "DEVICETYPE", TYPE_TEAM); + else if (master_iface + && nm_setting_connection_is_slave_type(s_con, NM_SETTING_TEAM_SETTING_NAME)) + svSetValueStr(ifcfg, "DEVICETYPE", TYPE_TEAM_PORT); + + /* secondary connection UUIDs */ + n = nm_setting_connection_get_num_secondaries(s_con); + if (n > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < n; i++) { + const char *uuid; + + /* Items separated by space for consistency with eg + * IPV6ADDR_SECONDARIES and DOMAIN. + */ + if (!(uuid = nm_setting_connection_get_secondary(s_con, i))) + continue; + + if (str->len) + g_string_append_c(str, ' '); + g_string_append(str, uuid); + } + svSetValueStr(ifcfg, "SECONDARY_UUIDS", str->str); + } + + vuint32 = nm_setting_connection_get_gateway_ping_timeout(s_con); + svSetValueInt64_cond(ifcfg, "GATEWAY_PING_TIMEOUT", vuint32 != 0, vuint32); + + switch (nm_setting_connection_get_metered(s_con)) { + case NM_METERED_YES: + svSetValueStr(ifcfg, "CONNECTION_METERED", "yes"); + break; + case NM_METERED_NO: + svSetValueStr(ifcfg, "CONNECTION_METERED", "no"); + break; + case NM_METERED_UNKNOWN: + case NM_METERED_GUESS_YES: + case NM_METERED_GUESS_NO: + break; + } + + vint = nm_setting_connection_get_auth_retries(s_con); + svSetValueInt64_cond(ifcfg, "AUTH_RETRIES", vint >= 0, vint); + + vint32 = nm_setting_connection_get_wait_device_timeout(s_con); + if (vint32 == -1) { + /* pass */ + } else if ((vint32 % 1000) == 0) + svSetValueInt64(ifcfg, "DEVTIMEOUT", vint32 / 1000); + else { + char b[100]; + + svSetValueStr(ifcfg, "DEVTIMEOUT", nm_sprintf_buf(b, "%.3f", ((double) vint) / 1000.0)); + } + + mdns = nm_setting_connection_get_mdns(s_con); + if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) { + svSetValueEnum(ifcfg, "MDNS", nm_setting_connection_mdns_get_type(), mdns); + } + + llmnr = nm_setting_connection_get_llmnr(s_con); + if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) { + svSetValueEnum(ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type(), llmnr); + } +} + +static char * +get_route_attributes_string(NMIPRoute *route, int family) +{ + gs_free const char **names = NULL; + GVariant * attr, *lock; + GString * str; + guint i, len; + + names = _nm_ip_route_get_attribute_names(route, TRUE, &len); + if (!len) + return NULL; + + str = g_string_new(""); + + attr = nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_TYPE); + if (attr + && nm_ip_route_attribute_validate(NM_IP_ROUTE_ATTRIBUTE_TYPE, attr, family, NULL, NULL)) + g_string_append_printf(str, "%s ", g_variant_get_string(attr, NULL)); + + for (i = 0; i < len; i++) { + if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TYPE)) + continue; + + attr = nm_ip_route_get_attribute(route, names[i]); + + if (!nm_ip_route_attribute_validate(names[i], attr, family, NULL, NULL)) + continue; + + if (NM_IN_STRSET(names[i], + NM_IP_ROUTE_ATTRIBUTE_WINDOW, + NM_IP_ROUTE_ATTRIBUTE_CWND, + NM_IP_ROUTE_ATTRIBUTE_INITCWND, + NM_IP_ROUTE_ATTRIBUTE_INITRWND, + NM_IP_ROUTE_ATTRIBUTE_MTU)) { + char lock_name[256]; + + nm_sprintf_buf(lock_name, "lock-%s", names[i]); + lock = nm_ip_route_get_attribute(route, lock_name); + + g_string_append_printf(str, + "%s %s%u", + names[i], + (lock && g_variant_get_boolean(lock)) ? "lock " : "", + g_variant_get_uint32(attr)); + } else if (strstr(names[i], "lock-")) { + const char *n = &(names[i])[NM_STRLEN("lock-")]; + + attr = nm_ip_route_get_attribute(route, n); + if (!attr) { + g_string_append_printf(str, "%s lock 0", n); + } else { + /* we also have a corresponding attribute with the numeric value. The + * lock setting is handled above. */ + } + } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_SCOPE)) { + g_string_append_printf(str, "%s %u", names[i], (unsigned) g_variant_get_byte(attr)); + } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TOS)) { + g_string_append_printf(str, "%s 0x%02x", names[i], (unsigned) g_variant_get_byte(attr)); + } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TABLE)) { + g_string_append_printf(str, "%s %u", names[i], (unsigned) g_variant_get_uint32(attr)); + } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_ONLINK)) { + if (g_variant_get_boolean(attr)) + g_string_append(str, "onlink"); + } else if (NM_IN_STRSET(names[i], NM_IP_ROUTE_ATTRIBUTE_SRC, NM_IP_ROUTE_ATTRIBUTE_FROM)) { + char *arg = nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_SRC) ? "src" : "from"; + + g_string_append_printf(str, "%s %s", arg, g_variant_get_string(attr, NULL)); + } else { + g_warn_if_reached(); + continue; + } + if (names[i + 1]) + g_string_append_c(str, ' '); + } + + return g_string_free(str, FALSE); +} + +static shvarFile * +write_route_file_svformat(const char *filename, NMSettingIPConfig *s_ip4) +{ + shvarFile *routefile; + guint i, num; + + routefile = utils_get_route_ifcfg(filename, TRUE); + + num = nm_setting_ip_config_get_num_routes(s_ip4); + for (i = 0; i < num; i++) { + char buf[INET_ADDRSTRLEN]; + NMIPRoute * route; + guint32 netmask; + gint64 metric; + char addr_key[64]; + char gw_key[64]; + char netmask_key[64]; + char metric_key[64]; + char options_key[64]; + gs_free char *options = NULL; + + numbered_tag(addr_key, "ADDRESS", i); + numbered_tag(netmask_key, "NETMASK", i); + numbered_tag(gw_key, "GATEWAY", i); + + route = nm_setting_ip_config_get_route(s_ip4, i); + + svSetValueStr(routefile, addr_key, nm_ip_route_get_dest(route)); + + netmask = _nm_utils_ip4_prefix_to_netmask(nm_ip_route_get_prefix(route)); + svSetValueStr(routefile, netmask_key, _nm_utils_inet4_ntop(netmask, buf)); + + svSetValueStr(routefile, gw_key, nm_ip_route_get_next_hop(route)); + + metric = nm_ip_route_get_metric(route); + if (metric != -1) { + svSetValueInt64(routefile, numbered_tag(metric_key, "METRIC", i), metric); + } + + options = get_route_attributes_string(route, AF_INET); + if (options) { + svSetValueStr(routefile, numbered_tag(options_key, "OPTIONS", i), options); + } + } + + return routefile; +} + +static GString * +write_route_file(NMSettingIPConfig *s_ip) +{ + GString * contents; + NMIPRoute *route; + guint32 i, num; + int addr_family; + + addr_family = nm_setting_ip_config_get_addr_family(s_ip); + + num = nm_setting_ip_config_get_num_routes(s_ip); + if (num == 0) + return NULL; + + contents = g_string_new(""); + + for (i = 0; i < num; i++) { + gs_free char *options = NULL; + const char * next_hop; + gint64 metric; + + route = nm_setting_ip_config_get_route(s_ip, i); + next_hop = nm_ip_route_get_next_hop(route); + metric = nm_ip_route_get_metric(route); + options = get_route_attributes_string(route, addr_family); + + g_string_append_printf(contents, + "%s/%u", + nm_ip_route_get_dest(route), + nm_ip_route_get_prefix(route)); + if (next_hop) + g_string_append_printf(contents, " via %s", next_hop); + if (metric >= 0) + g_string_append_printf(contents, " metric %u", (guint) metric); + if (options) { + g_string_append_c(contents, ' '); + g_string_append(contents, options); + } + + g_string_append_c(contents, '\n'); + } + + return contents; +} + +static gboolean +write_proxy_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingProxy * s_proxy; + NMSettingProxyMethod method; + const char * pac_url, *pac_script; + + s_proxy = nm_connection_get_setting_proxy(connection); + if (!s_proxy) + return TRUE; + + method = nm_setting_proxy_get_method(s_proxy); + switch (method) { + case NM_SETTING_PROXY_METHOD_AUTO: + svSetValueStr(ifcfg, "PROXY_METHOD", "auto"); + + pac_url = nm_setting_proxy_get_pac_url(s_proxy); + if (pac_url) + svSetValueStr(ifcfg, "PAC_URL", pac_url); + + pac_script = nm_setting_proxy_get_pac_script(s_proxy); + if (pac_script) + svSetValueStr(ifcfg, "PAC_SCRIPT", pac_script); + + break; + case NM_SETTING_PROXY_METHOD_NONE: + svSetValueStr(ifcfg, "PROXY_METHOD", "none"); + break; + } + + svSetValueBoolean(ifcfg, "BROWSER_ONLY", nm_setting_proxy_get_browser_only(s_proxy)); + + return TRUE; +} + +static gboolean +write_user_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingUser * s_user; + guint i, len; + const char *const *keys; + + s_user = NM_SETTING_USER(nm_connection_get_setting(connection, NM_TYPE_SETTING_USER)); + + if (!s_user) + return TRUE; + + keys = nm_setting_user_get_keys(s_user, &len); + if (len) { + nm_auto_free_gstring GString *str = g_string_sized_new(100); + + for (i = 0; i < len; i++) { + const char *key = keys[i]; + + g_string_set_size(str, 0); + g_string_append(str, "NM_USER_"); + nms_ifcfg_rh_utils_user_key_encode(key, str); + svSetValue(ifcfg, str->str, nm_setting_user_get_data(s_user, key)); + } + } + + return TRUE; +} + +static void +write_sriov_setting(NMConnection *connection, shvarFile *ifcfg) +{ + NMSettingSriov *s_sriov; + guint i, num = 0; + NMTernary b; + NMSriovVF * vf; + char key[32]; + char * str; + + s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(connection, NM_TYPE_SETTING_SRIOV)); + if (!s_sriov) { + return; + } + + svSetValueInt64(ifcfg, "SRIOV_TOTAL_VFS", nm_setting_sriov_get_total_vfs(s_sriov)); + + b = nm_setting_sriov_get_autoprobe_drivers(s_sriov); + if (b != NM_TERNARY_DEFAULT) + svSetValueInt64(ifcfg, "SRIOV_AUTOPROBE_DRIVERS", b); + + num = nm_setting_sriov_get_num_vfs(s_sriov); + for (i = 0; i < num; i++) { + vf = nm_setting_sriov_get_vf(s_sriov, i); + nm_sprintf_buf(key, "SRIOV_VF%u", nm_sriov_vf_get_index(vf)); + str = nm_utils_sriov_vf_to_str(vf, TRUE, NULL); + svSetValueStr(ifcfg, key, str); + g_free(str); + } +} + +static gboolean +write_tc_setting(NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingTCConfig *s_tc; + guint i, num, n; + char tag[64]; + + s_tc = nm_connection_get_setting_tc_config(connection); + if (!s_tc) + return TRUE; + + num = nm_setting_tc_config_get_num_qdiscs(s_tc); + for (n = 1, i = 0; i < num; i++) { + NMTCQdisc * qdisc; + gs_free char *str = NULL; + + qdisc = nm_setting_tc_config_get_qdisc(s_tc, i); + str = nm_utils_tc_qdisc_to_str(qdisc, error); + if (!str) + return FALSE; + + svSetValueStr(ifcfg, numbered_tag(tag, "QDISC", n), str); + n++; + } + + num = nm_setting_tc_config_get_num_tfilters(s_tc); + for (n = 1, i = 0; i < num; i++) { + NMTCTfilter * tfilter; + gs_free char *str = NULL; + + tfilter = nm_setting_tc_config_get_tfilter(s_tc, i); + str = nm_utils_tc_tfilter_to_str(tfilter, error); + if (!str) + return FALSE; + + svSetValueStr(ifcfg, numbered_tag(tag, "FILTER", n), str); + n++; + } + + return TRUE; +} + +static void +write_match_setting(NMConnection *connection, shvarFile *ifcfg) +{ + nm_auto_free_gstring GString *str = NULL; + NMSettingMatch * s_match; + guint i, num; + const char * name; + + s_match = (NMSettingMatch *) nm_connection_get_setting(connection, NM_TYPE_SETTING_MATCH); + if (!s_match) + return; + + num = nm_setting_match_get_num_drivers(s_match); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + name = nm_setting_match_get_driver(s_match, i); + nm_gstring_add_space_delimiter(str); + nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str); + } + svSetValueStr(ifcfg, "MATCH_DRIVER", str->str); + } + + num = nm_setting_match_get_num_interface_names(s_match); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + name = nm_setting_match_get_interface_name(s_match, i); + nm_gstring_add_space_delimiter(str); + nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str); + } + svSetValueStr(ifcfg, "MATCH_INTERFACE_NAME", str->str); + } + + num = nm_setting_match_get_num_kernel_command_lines(s_match); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + name = nm_setting_match_get_kernel_command_line(s_match, i); + nm_gstring_add_space_delimiter(str); + nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str); + } + svSetValueStr(ifcfg, "MATCH_KERNEL_COMMAND_LINE", str->str); + } + + num = nm_setting_match_get_num_paths(s_match); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + name = nm_setting_match_get_path(s_match, i); + nm_gstring_add_space_delimiter(str); + nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str); + } + svSetValueStr(ifcfg, "MATCH_PATH", str->str); + } +} + +static void +write_res_options(shvarFile *ifcfg, NMSettingIPConfig *s_ip, const char *var) +{ + nm_auto_free_gstring GString *value = NULL; + guint i, num_options; + + if (!nm_setting_ip_config_has_dns_options(s_ip)) + return; + + value = g_string_new(NULL); + num_options = nm_setting_ip_config_get_num_dns_options(s_ip); + for (i = 0; i < num_options; i++) { + if (i > 0) + g_string_append_c(value, ' '); + g_string_append(value, nm_setting_ip_config_get_dns_option(s_ip, i)); + } + + svSetValue(ifcfg, var, value->str); +} + +static void +write_dns_setting(shvarFile *ifcfg, NMConnection *connection, int addr_family) +{ + NMSettingIPConfig *s_ip; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6 = NULL; + guint num4; + guint num6 = 0; + guint num; + guint i; + guint offset; + + if (addr_family == AF_INET6) { + s_ip6 = nm_connection_get_setting_ip6_config(connection); + num6 = s_ip6 ? nm_setting_ip_config_get_num_dns(s_ip6) : 0u; + } + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + num4 = s_ip4 ? nm_setting_ip_config_get_num_dns(s_ip4) : 0u; + + if (addr_family == AF_INET6) { + num = num6; + offset = num4; + s_ip = s_ip6; + } else { + num = num4; + offset = 0; + s_ip = s_ip4; + } + + for (i = 0; i < num; i++) { + char tag[64]; + + svSetValueStr(ifcfg, + numbered_tag(tag, "DNS", offset + i + 1u), + nm_setting_ip_config_get_dns(s_ip, i)); + } +} + +static gboolean +write_ip4_setting(NMConnection *connection, + shvarFile * ifcfg, + shvarFile ** out_route_content_svformat, + GString ** out_route_content, + GError ** error) +{ + NMSettingIPConfig * s_ip4; + const char * value; + char * tmp; + char tag[64]; + int j; + guint i, num, n; + gint64 route_metric; + NMIPRouteTableSyncMode route_table; + int priority; + int timeout; + nm_auto_free_gstring GString *str = NULL; + const char * method = NULL; + gboolean has_netmask; + NMDhcpHostnameFlags flags; + const char *const * strv; + + NM_SET_OUT(out_route_content_svformat, NULL); + NM_SET_OUT(out_route_content, NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (!s_ip4) + return TRUE; + + method = nm_setting_ip_config_get_method(s_ip4); + + /* Missing IP4 setting is assumed to be DHCP */ + if (!method) + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + return TRUE; + + num = nm_setting_ip_config_get_num_addresses(s_ip4); + + if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) + svSetValueStr(ifcfg, "BOOTPROTO", "dhcp"); + else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + /* Preserve the archaic form of "static" if there actually + * is static configuration. */ + if (g_strcmp0(svGetValue(ifcfg, "BOOTPROTO", &tmp), "static") || !num) + svSetValueStr(ifcfg, "BOOTPROTO", "none"); + g_free(tmp); + } else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) + svSetValueStr(ifcfg, "BOOTPROTO", "autoip"); + else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + svSetValueStr(ifcfg, "BOOTPROTO", "shared"); + + has_netmask = !!svFindFirstNumberedKey(ifcfg, "NETMASK"); + + /* Write out IPADDR, PREFIX, GATEWAY for current IP addresses + * without labels. Unset obsolete NETMASK. + */ + for (i = n = 0; i < num; i++) { + NMIPAddress *addr; + guint prefix; + + addr = nm_setting_ip_config_get_address(s_ip4, i); + + if (i > 0) { + GVariant *label; + + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (label) + continue; + } + + if (n == 0) { + /* Instead of index 0 use un-numbered variables. + * It's needed for compatibility with ifup that only recognizes 'GATEAWAY' + * See https://bugzilla.redhat.com/show_bug.cgi?id=771673 + * and https://bugzilla.redhat.com/show_bug.cgi?id=1105770 + */ + j = -1; + } else + j = n; + + svSetValueStr(ifcfg, numbered_tag(tag, "IPADDR", j), nm_ip_address_get_address(addr)); + + prefix = nm_ip_address_get_prefix(addr); + svSetValueInt64(ifcfg, numbered_tag(tag, "PREFIX", j), prefix); + + /* If the legacy "NETMASK" is present, keep it. */ + numbered_tag(tag, "NETMASK", j); + if (has_netmask) { + char buf[INET_ADDRSTRLEN]; + + svSetValueStr(ifcfg, + tag, + _nm_utils_inet4_ntop(_nm_utils_ip4_prefix_to_netmask(prefix), buf)); + } + + n++; + } + + svSetValueStr(ifcfg, "GATEWAY", nm_setting_ip_config_get_gateway(s_ip4)); + + write_dns_setting(ifcfg, connection, AF_INET); + + num = nm_setting_ip_config_get_num_dns_searches(s_ip4); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + nm_gstring_add_space_delimiter(str); + g_string_append(str, nm_setting_ip_config_get_dns_search(s_ip4, i)); + } + svSetValueStr(ifcfg, "DOMAIN", str->str); + } + + /* DEFROUTE; remember that it has the opposite meaning from never-default */ + svSetValueBoolean(ifcfg, "DEFROUTE", !nm_setting_ip_config_get_never_default(s_ip4)); + + /* Missing PEERDNS means TRUE, so write it only when is FALSE */ + svSetValueStr(ifcfg, "PEERDNS", nm_setting_ip_config_get_ignore_auto_dns(s_ip4) ? "no" : NULL); + /* Missing PEERROUTES means TRUE, so write it only when is FALSE */ + svSetValueStr(ifcfg, + "PEERROUTES", + nm_setting_ip_config_get_ignore_auto_routes(s_ip4) ? "no" : NULL); + + value = nm_setting_ip_config_get_dhcp_hostname(s_ip4); + svSetValueStr(ifcfg, "DHCP_HOSTNAME", value); + + value = nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)); + svSetValueStr(ifcfg, "DHCP_FQDN", value); + + flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip4); + svSetValueInt64_cond(ifcfg, "DHCP_HOSTNAME_FLAGS", flags != NM_DHCP_HOSTNAME_FLAG_NONE, flags); + + /* Missing DHCP_SEND_HOSTNAME means TRUE, and we prefer not write it explicitly + * in that case, because it is NM-specific variable + */ + svSetValueStr(ifcfg, + "DHCP_SEND_HOSTNAME", + nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) ? NULL : "no"); + + value = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4)); + svSetValueStr(ifcfg, "DHCP_CLIENT_ID", value); + + svSetValue( + ifcfg, + "DHCP_VENDOR_CLASS_IDENTIFIER", + nm_setting_ip4_config_get_dhcp_vendor_class_identifier(NM_SETTING_IP4_CONFIG(s_ip4))); + + value = nm_setting_ip_config_get_dhcp_iaid(s_ip4); + svSetValueStr(ifcfg, "DHCP_IAID", value); + + timeout = nm_setting_ip_config_get_dhcp_timeout(s_ip4); + svSetValueInt64_cond(ifcfg, "IPV4_DHCP_TIMEOUT", timeout != 0, timeout); + + svSetValueBoolean(ifcfg, "IPV4_FAILURE_FATAL", !nm_setting_ip_config_get_may_fail(s_ip4)); + + route_metric = nm_setting_ip_config_get_route_metric(s_ip4); + svSetValueInt64_cond(ifcfg, "IPV4_ROUTE_METRIC", route_metric != -1, route_metric); + + route_table = nm_setting_ip_config_get_route_table(s_ip4); + svSetValueInt64_cond(ifcfg, "IPV4_ROUTE_TABLE", route_table != 0, route_table); + + NM_SET_OUT(out_route_content_svformat, write_route_file_svformat(svFileGetName(ifcfg), s_ip4)); + NM_SET_OUT(out_route_content, write_route_file(s_ip4)); + + timeout = nm_setting_ip_config_get_dad_timeout(s_ip4); + if (timeout < 0) { + /* pass */ + } else if (timeout == 0) { + svSetValueStr(ifcfg, "ACD_TIMEOUT", "0"); + svSetValueStr(ifcfg, "ARPING_WAIT", "0"); + } else { + svSetValueInt64(ifcfg, "ACD_TIMEOUT", timeout); + /* Round the value up to next integer for initscripts */ + svSetValueInt64(ifcfg, "ARPING_WAIT", (timeout - 1) / 1000 + 1); + } + + priority = nm_setting_ip_config_get_dns_priority(s_ip4); + if (priority) + svSetValueInt64(ifcfg, "IPV4_DNS_PRIORITY", priority); + + write_res_options(ifcfg, s_ip4, "RES_OPTIONS"); + + strv = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, &num); + if (num > 0) { + nm_gstring_prepare(&str); + for (i = 0; i < num; i++) { + nm_gstring_add_space_delimiter(str); + nm_utils_escaped_tokens_escape_gstr(strv[i], NM_ASCII_SPACES, str); + } + svSetValueStr(ifcfg, "DHCP_REJECT_SERVERS", str->str); + } + + return TRUE; +} + +static void +write_ip4_aliases(NMConnection *connection, const char *base_ifcfg_path) +{ + NMSettingIPConfig *s_ip4; + gs_free char * base_ifcfg_dir = NULL, *base_ifcfg_name = NULL; + const char * base_name; + int i, num, base_ifcfg_name_len, base_name_len; + GDir * dir; + + base_ifcfg_dir = g_path_get_dirname(base_ifcfg_path); + base_ifcfg_name = g_path_get_basename(base_ifcfg_path); + base_ifcfg_name_len = strlen(base_ifcfg_name); + if (!g_str_has_prefix(base_ifcfg_name, IFCFG_TAG)) + g_return_if_reached(); + base_name = base_ifcfg_name + strlen(IFCFG_TAG); + base_name_len = strlen(base_name); + + /* Remove all existing aliases for this file first */ + dir = g_dir_open(base_ifcfg_dir, 0, NULL); + if (dir) { + const char *item; + + while ((item = g_dir_read_name(dir))) { + char *full_path; + + if (strncmp(item, base_ifcfg_name, base_ifcfg_name_len) != 0 + || item[base_ifcfg_name_len] != ':') + continue; + + full_path = g_build_filename(base_ifcfg_dir, item, NULL); + unlink(full_path); + g_free(full_path); + } + + g_dir_close(dir); + } + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (!s_ip4) { + /* slave-type: no alias files */ + return; + } + + num = nm_setting_ip_config_get_num_addresses(s_ip4); + for (i = 0; i < num; i++) { + GVariant * label_var; + const char * label, *p; + char * path; + NMIPAddress *addr; + shvarFile * ifcfg; + + addr = nm_setting_ip_config_get_address(s_ip4, i); + + label_var = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (!label_var) + continue; + label = g_variant_get_string(label_var, NULL); + if (strncmp(label, base_name, base_name_len) != 0 || label[base_name_len] != ':') + continue; + + for (p = label; *p; p++) { + if (!g_ascii_isalnum(*p) && *p != '_' && *p != ':') + break; + } + if (*p) + continue; + + path = g_strdup_printf("%s%s", base_ifcfg_path, label + base_name_len); + ifcfg = svCreateFile(path); + g_free(path); + + svSetValueStr(ifcfg, "DEVICE", label); + + addr = nm_setting_ip_config_get_address(s_ip4, i); + svSetValueStr(ifcfg, "IPADDR", nm_ip_address_get_address(addr)); + + svSetValueInt64(ifcfg, "PREFIX", nm_ip_address_get_prefix(addr)); + + svWriteFileWithoutDirtyWellknown(ifcfg, 0644, NULL); + svCloseFile(ifcfg); + } +} + +static gboolean +write_ip6_setting(NMConnection *connection, + shvarFile * ifcfg, + GString ** out_route6_content, + GError ** error) +{ + NMSettingIPConfig * s_ip6; + const char * value; + guint i, num; + int priority; + NMIPAddress * addr; + gint64 route_metric; + NMIPRouteTableSyncMode route_table; + GString * ip_str1, *ip_str2, *ip_ptr; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + NMDhcpHostnameFlags flags; + const char * hostname; + int timeout; + + NM_SET_OUT(out_route6_content, NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (!s_ip6) + return TRUE; + + value = nm_setting_ip_config_get_method(s_ip6); + g_assert(value); + if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { + svSetValueStr(ifcfg, "IPV6INIT", "no"); + return TRUE; + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + svSetValueStr(ifcfg, "IPV6_DISABLED", "yes"); + svSetValueStr(ifcfg, "IPV6INIT", "no"); + return TRUE; + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + svSetValueStr(ifcfg, "IPV6INIT", "yes"); + svSetValueStr(ifcfg, "IPV6_AUTOCONF", "yes"); + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) { + svSetValueStr(ifcfg, "IPV6INIT", "yes"); + svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no"); + svSetValueStr(ifcfg, "DHCPV6C", "yes"); + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + svSetValueStr(ifcfg, "IPV6INIT", "yes"); + svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no"); + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) { + svSetValueStr(ifcfg, "IPV6INIT", "yes"); + svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no"); + } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) { + svSetValueStr(ifcfg, "IPV6INIT", "yes"); + svSetValueStr(ifcfg, "IPV6_AUTOCONF", "shared"); + } + + svSetValueStr(ifcfg, + "DHCPV6_DUID", + nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6))); + svSetValueStr(ifcfg, "DHCPV6_IAID", nm_setting_ip_config_get_dhcp_iaid(s_ip6)); + + hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip6); + svSetValueStr(ifcfg, "DHCPV6_HOSTNAME", hostname); + + /* Missing DHCPV6_SEND_HOSTNAME means TRUE, and we prefer not write it + * explicitly in that case, because it is NM-specific variable + */ + if (!nm_setting_ip_config_get_dhcp_send_hostname(s_ip6)) + svSetValueStr(ifcfg, "DHCPV6_SEND_HOSTNAME", "no"); + + timeout = nm_setting_ip6_config_get_ra_timeout(NM_SETTING_IP6_CONFIG(s_ip6)); + svSetValueInt64_cond(ifcfg, "IPV6_RA_TIMEOUT", timeout != 0, timeout); + + timeout = nm_setting_ip_config_get_dhcp_timeout(s_ip6); + svSetValueInt64_cond(ifcfg, "IPV6_DHCP_TIMEOUT", timeout != 0, timeout); + + flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip6); + svSetValueInt64_cond(ifcfg, + "DHCPV6_HOSTNAME_FLAGS", + flags != NM_DHCP_HOSTNAME_FLAG_NONE, + flags); + + /* Write out IP addresses */ + num = nm_setting_ip_config_get_num_addresses(s_ip6); + ip_str1 = g_string_new(NULL); + ip_str2 = g_string_new(NULL); + for (i = 0; i < num; i++) { + if (i == 0) + ip_ptr = ip_str1; + else + ip_ptr = ip_str2; + + addr = nm_setting_ip_config_get_address(s_ip6, i); + + if (i > 1) + g_string_append_c(ip_ptr, ' '); /* separate addresses in IPV6ADDR_SECONDARIES */ + g_string_append_printf(ip_ptr, + "%s/%u", + nm_ip_address_get_address(addr), + nm_ip_address_get_prefix(addr)); + } + svSetValueStr(ifcfg, "IPV6ADDR", ip_str1->str); + svSetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", ip_str2->str); + svSetValueStr(ifcfg, "IPV6_DEFAULTGW", nm_setting_ip_config_get_gateway(s_ip6)); + g_string_free(ip_str1, TRUE); + g_string_free(ip_str2, TRUE); + + write_dns_setting(ifcfg, connection, AF_INET6); + + /* Write out DNS domains */ + num = nm_setting_ip_config_get_num_dns_searches(s_ip6); + if (num > 0) { + nm_auto_free_gstring GString *searches = NULL; + + searches = g_string_new(NULL); + for (i = 0; i < num; i++) { + if (searches->len > 0) + g_string_append_c(searches, ' '); + g_string_append(searches, nm_setting_ip_config_get_dns_search(s_ip6, i)); + } + svSetValueStr(ifcfg, "IPV6_DOMAIN", searches->str); + } + + /* handle IPV6_DEFROUTE */ + /* IPV6_DEFROUTE has the opposite meaning from 'never-default' */ + svSetValueBoolean(ifcfg, "IPV6_DEFROUTE", !nm_setting_ip_config_get_never_default(s_ip6)); + + svSetValueStr(ifcfg, + "IPV6_PEERDNS", + nm_setting_ip_config_get_ignore_auto_dns(s_ip6) ? "no" : NULL); + + svSetValueStr(ifcfg, + "IPV6_PEERROUTES", + nm_setting_ip_config_get_ignore_auto_routes(s_ip6) ? "no" : NULL); + + svSetValueStr(ifcfg, + "IPV6_FAILURE_FATAL", + nm_setting_ip_config_get_may_fail(s_ip6) ? "no" : "yes"); + + route_metric = nm_setting_ip_config_get_route_metric(s_ip6); + svSetValueInt64_cond(ifcfg, "IPV6_ROUTE_METRIC", route_metric != -1, route_metric); + + route_table = nm_setting_ip_config_get_route_table(s_ip6); + svSetValueInt64_cond(ifcfg, "IPV6_ROUTE_TABLE", route_table != 0, route_table); + + /* IPv6 Privacy Extensions */ + switch (nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6))) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + svSetValueStr(ifcfg, "IPV6_PRIVACY", "no"); + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + svSetValueStr(ifcfg, "IPV6_PRIVACY", "rfc3041"); + svSetValueStr(ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", "yes"); + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + svSetValueStr(ifcfg, "IPV6_PRIVACY", "rfc3041"); + break; + default: + break; + } + + /* IPv6 Address generation mode */ + addr_gen_mode = nm_setting_ip6_config_get_addr_gen_mode(NM_SETTING_IP6_CONFIG(s_ip6)); + if (addr_gen_mode != NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) { + svSetValueEnum(ifcfg, + "IPV6_ADDR_GEN_MODE", + nm_setting_ip6_config_addr_gen_mode_get_type(), + addr_gen_mode); + } + + /* IPv6 tokenized interface identifier */ + value = nm_setting_ip6_config_get_token(NM_SETTING_IP6_CONFIG(s_ip6)); + svSetValueStr(ifcfg, "IPV6_TOKEN", value); + + priority = nm_setting_ip_config_get_dns_priority(s_ip6); + if (priority) + svSetValueInt64(ifcfg, "IPV6_DNS_PRIORITY", priority); + + write_res_options(ifcfg, s_ip6, "IPV6_RES_OPTIONS"); + + NM_SET_OUT(out_route6_content, write_route_file(s_ip6)); + + return TRUE; +} + +static void +write_ip_routing_rules(NMConnection *connection, shvarFile *ifcfg, gboolean route_ignore) +{ + gsize idx; + int is_ipv4; + + if (route_ignore) + return; + + idx = 0; + + for (is_ipv4 = 1; is_ipv4 >= 0; is_ipv4--) { + const int addr_family = is_ipv4 ? AF_INET : AF_INET6; + NMSettingIPConfig *s_ip; + guint i, num; + + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (!s_ip) + continue; + + num = nm_setting_ip_config_get_num_routing_rules(s_ip); + for (i = 0; i < num; i++) { + NMIPRoutingRule * rule = nm_setting_ip_config_get_routing_rule(s_ip, i); + gs_free const char *s = NULL; + char key[64]; + + s = nm_ip_routing_rule_to_string(rule, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, + NULL, + NULL); + if (!s) + continue; + + if (is_ipv4) + numbered_tag(key, "ROUTING_RULE_", ++idx); + else + numbered_tag(key, "ROUTING_RULE6_", ++idx); + svSetValueStr(ifcfg, key, s); + } + } +} + +static char * +escape_id(const char *id) +{ + char *escaped = g_strdup(id); + char *p = escaped; + + /* Escape random stuff */ + while (*p) { + if (*p == ' ') + *p = '_'; + else if (strchr("\\][|/=()!:", *p)) + *p = '-'; + p++; + } + + return escaped; +} + +static gboolean +do_write_construct(NMConnection * connection, + const char * ifcfg_dir, + const char * filename, + NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + shvarFile ** out_ifcfg, + GHashTable ** out_blobs, + GHashTable ** out_secrets, + gboolean * out_route_ignore, + shvarFile ** out_route_content_svformat, + GString ** out_route_content, + GString ** out_route6_content, + GError ** error) +{ + NMSettingConnection * s_con; + nm_auto_shvar_file_close shvarFile *ifcfg = NULL; + gs_free char * ifcfg_name = NULL; + gs_free char * route_path = NULL; + gs_free char * route6_path = NULL; + const char * type; + gs_unref_hashtable GHashTable *blobs = NULL; + gs_unref_hashtable GHashTable *secrets = NULL; + gboolean wired; + gboolean no_8021x; + gboolean route_path_is_svformat; + gboolean has_complex_routes_v4; + gboolean has_complex_routes_v6; + gboolean route_ignore; + nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL; + nm_auto_free_gstring GString *route_content = NULL; + nm_auto_free_gstring GString *route6_content = NULL; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS); + + if (!nms_ifcfg_rh_writer_can_write_connection(connection, error)) + return FALSE; + + s_con = nm_connection_get_setting_connection(connection); + + if (filename) { + /* For existing connections, 'filename' should be full path to ifcfg file */ + ifcfg = svOpenFile(filename, error); + if (!ifcfg) + return FALSE; + + ifcfg_name = g_strdup(filename); + } else if (ifcfg_dir) { + gs_free char *escaped = NULL; + int i_path; + + escaped = escape_id(nm_setting_connection_get_id(s_con)); + + for (i_path = 0; i_path < 10000; i_path++) { + gs_free char *path_candidate = NULL; + + if (i_path == 0) + path_candidate = g_strdup_printf("%s/ifcfg-%s", ifcfg_dir, escaped); + else + path_candidate = g_strdup_printf("%s/ifcfg-%s-%d", ifcfg_dir, escaped, i_path); + + if (allow_filename_cb && !allow_filename_cb(path_candidate, allow_filename_user_data)) + continue; + + if (g_file_test(path_candidate, G_FILE_TEST_EXISTS)) + continue; + + ifcfg_name = g_steal_pointer(&path_candidate); + break; + } + + if (!ifcfg_name) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Failed to find usable ifcfg file name"); + return FALSE; + } + + ifcfg = svCreateFile(ifcfg_name); + } else + ifcfg = svCreateFile("/tmp/ifcfg-dummy"); + + route_path = utils_get_route_path(svFileGetName(ifcfg)); + if (!route_path) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Could not get route file path for '%s'", + svFileGetName(ifcfg)); + return FALSE; + } + + route6_path = utils_get_route6_path(svFileGetName(ifcfg)); + if (!route6_path) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Could not get route6 file path for '%s'", + svFileGetName(ifcfg)); + return FALSE; + } + + type = nm_setting_connection_get_connection_type(s_con); + if (!type) { + g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "Missing connection type!"); + return FALSE; + } + + secrets = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + + wired = FALSE; + no_8021x = FALSE; + if (!strcmp(type, NM_SETTING_WIRED_SETTING_NAME)) { + // FIXME: can't write PPPoE at this time + if (nm_connection_get_setting_pppoe(connection)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Can't write connection type '%s'", + NM_SETTING_PPPOE_SETTING_NAME); + return FALSE; + } + + if (!write_wired_setting(connection, ifcfg, error)) + return FALSE; + wired = TRUE; + } else if (!strcmp(type, NM_SETTING_VLAN_SETTING_NAME)) { + if (!write_vlan_setting(connection, ifcfg, &wired, error)) + return FALSE; + } else if (!strcmp(type, NM_SETTING_WIRELESS_SETTING_NAME)) { + if (!write_wireless_setting(connection, ifcfg, secrets, &no_8021x, error)) + return FALSE; + } else if (!strcmp(type, NM_SETTING_INFINIBAND_SETTING_NAME)) { + if (!write_infiniband_setting(connection, ifcfg, error)) + return FALSE; + } else if (!strcmp(type, NM_SETTING_BOND_SETTING_NAME)) { + if (!write_bond_setting(connection, ifcfg, &wired, error)) + return FALSE; + } else if (!strcmp(type, NM_SETTING_TEAM_SETTING_NAME)) { + if (!write_team_setting(connection, ifcfg, &wired, error)) + return FALSE; + } else if (!strcmp(type, NM_SETTING_BRIDGE_SETTING_NAME)) { + if (!write_bridge_setting(connection, ifcfg, &wired, error)) + return FALSE; + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Can't write connection type '%s'", + type); + return FALSE; + } + + if (!no_8021x) { + blobs = + g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref); + if (!write_8021x_setting(connection, ifcfg, secrets, blobs, wired, error)) + return FALSE; + } + + if (!write_bridge_port_setting(connection, ifcfg, error)) + return FALSE; + + if (!write_team_port_setting(connection, ifcfg, error)) + return FALSE; + + if (!write_dcb_setting(connection, ifcfg, error)) + return FALSE; + + if (!write_proxy_setting(connection, ifcfg, error)) + return FALSE; + + if (!write_ethtool_setting(connection, ifcfg, error)) + return FALSE; + + if (!write_user_setting(connection, ifcfg, error)) + return FALSE; + + write_match_setting(connection, ifcfg); + write_hostname_setting(connection, ifcfg); + write_sriov_setting(connection, ifcfg); + + if (!write_tc_setting(connection, ifcfg, error)) + return FALSE; + + route_path_is_svformat = utils_has_route_file_new_syntax(route_path); + + has_complex_routes_v4 = utils_has_complex_routes(ifcfg_name, AF_INET); + has_complex_routes_v6 = utils_has_complex_routes(ifcfg_name, AF_INET6); + + if (has_complex_routes_v4 || has_complex_routes_v6) { + NMSettingIPConfig *s_ip4, *s_ip6; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if ((s_ip4 && nm_setting_ip_config_get_num_routes(s_ip4) > 0) + || (s_ip6 && nm_setting_ip_config_get_num_routes(s_ip6) > 0)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Cannot configure static routes on a connection that has an associated " + "'rule%s-' file", + has_complex_routes_v4 ? "" : "6"); + return FALSE; + } + if ((s_ip4 && nm_setting_ip_config_get_route_table(s_ip4) != 0) + || (s_ip6 && nm_setting_ip_config_get_route_table(s_ip6) != 0)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Cannot configure a route table for policy routing on a connection that " + "has an associated 'rule%s-' file", + has_complex_routes_v4 ? "" : "6"); + return FALSE; + } + if ((s_ip4 && nm_setting_ip_config_get_num_routing_rules(s_ip4) > 0) + || (s_ip6 && nm_setting_ip_config_get_num_routing_rules(s_ip6) > 0)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Cannot configure routing rules on a connection that has an associated " + "'rule%s-' file", + has_complex_routes_v4 ? "" : "6"); + return FALSE; + } + route_ignore = TRUE; + } else + route_ignore = FALSE; + + if (!write_ip4_setting(connection, + ifcfg, + !route_ignore && route_path_is_svformat ? &route_content_svformat : NULL, + !route_ignore && route_path_is_svformat ? NULL : &route_content, + error)) + return FALSE; + + if (!write_ip6_setting(connection, ifcfg, !route_ignore ? &route6_content : NULL, error)) + return FALSE; + + write_ip_routing_rules(connection, ifcfg, route_ignore); + + write_connection_setting(s_con, ifcfg); + + NM_SET_OUT(out_ifcfg, g_steal_pointer(&ifcfg)); + NM_SET_OUT(out_blobs, g_steal_pointer(&blobs)); + NM_SET_OUT(out_secrets, g_steal_pointer(&secrets)); + NM_SET_OUT(out_route_ignore, route_ignore); + NM_SET_OUT(out_route_content_svformat, g_steal_pointer(&route_content_svformat)); + NM_SET_OUT(out_route_content, g_steal_pointer(&route_content)); + NM_SET_OUT(out_route6_content, g_steal_pointer(&route6_content)); + return TRUE; +} + +static gboolean +do_write_to_disk(NMConnection *connection, + shvarFile * ifcfg, + GHashTable * blobs, + GHashTable * secrets, + gboolean route_ignore, + shvarFile * route_content_svformat, + GString * route_content, + GString * route6_content, + GError ** error) +{ + /* From here on, we persist data to disk. Before, it was all in-memory + * only. But we loaded the ifcfg files from disk, and managled our + * new settings (in-memory). */ + + if (!svWriteFileWithoutDirtyWellknown(ifcfg, 0644, error)) + return FALSE; + + write_ip4_aliases(connection, svFileGetName(ifcfg)); + + if (!write_blobs(blobs, error)) + return FALSE; + + if (!write_secrets(ifcfg, secrets, error)) + return FALSE; + + if (!route_ignore) { + gs_free char *route_path = utils_get_route_path(svFileGetName(ifcfg)); + + if (!route_content && !route_content_svformat) + (void) unlink(route_path); + else { + nm_assert(route_content_svformat || route_content); + if (route_content_svformat) { + if (!svWriteFileWithoutDirtyWellknown(route_content_svformat, 0644, error)) + return FALSE; + } else { + if (!g_file_set_contents(route_path, + route_content->str, + route_content->len, + NULL)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Writing route file '%s' failed", + route_path); + return FALSE; + } + } + } + } + + if (!route_ignore) { + gs_free char *route6_path = utils_get_route6_path(svFileGetName(ifcfg)); + + if (!route6_content) + (void) unlink(route6_path); + else { + if (!g_file_set_contents(route6_path, route6_content->str, route6_content->len, NULL)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "Writing route6 file '%s' failed", + route6_path); + return FALSE; + } + } + } + + return TRUE; +} + +gboolean +nms_ifcfg_rh_writer_write_connection(NMConnection * connection, + const char * ifcfg_dir, + const char * filename, + NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + char ** out_filename, + NMConnection ** out_reread, + gboolean * out_reread_same, + GError ** error) +{ + nm_auto_shvar_file_close shvarFile *ifcfg = NULL; + nm_auto_free_gstring GString *route_content = NULL; + gboolean route_ignore = FALSE; + nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL; + nm_auto_free_gstring GString *route6_content = NULL; + gs_unref_hashtable GHashTable *secrets = NULL; + gs_unref_hashtable GHashTable *blobs = NULL; + + nm_assert(!out_reread || !*out_reread); + + if (!do_write_construct(connection, + ifcfg_dir, + filename, + allow_filename_cb, + allow_filename_user_data, + &ifcfg, + &blobs, + &secrets, + &route_ignore, + &route_content_svformat, + &route_content, + &route6_content, + error)) + return FALSE; + + _LOGT("write: write connection %s (%s) to file \"%s\"", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection), + svFileGetName(ifcfg)); + + if (!do_write_to_disk(connection, + ifcfg, + blobs, + secrets, + route_ignore, + route_content_svformat, + route_content, + route6_content, + error)) + return FALSE; + + /* Note that we just wrote the connection to disk, and re-read it from there. + * That is racy if somebody else modifies the connection. + * That race is why we must not tread a failure to re-read the profile + * as an error. + * + * FIXME: a much better solution might be, to re-read the connection only based + * on the in-memory representation of what we collected above. But the reader + * does not yet allow to inject the configuration. */ + if (out_reread || out_reread_same) { + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + gs_free_error GError *local = NULL; + gs_free char * unhandled = NULL; + + reread = connection_from_file(svFileGetName(ifcfg), &unhandled, &local, NULL); + nm_assert((NM_IS_CONNECTION(reread) && !local) || (!reread && local)); + + if (!reread) { + _LOGW("write: failure to re-read connection \"%s\": %s", + svFileGetName(ifcfg), + local->message); + } else if (unhandled) { + g_clear_object(&reread); + _LOGW("write: failure to re-read connection \"%s\": %s", + svFileGetName(ifcfg), + "connection is unhandled"); + } else { + /* ifcfg-rh doesn't support the 'timestamp' property, let's add it here */ + g_object_set(nm_connection_get_setting_connection(reread), + NM_SETTING_CONNECTION_TIMESTAMP, + nm_setting_connection_get_timestamp( + nm_connection_get_setting_connection(connection)), + NULL); + if (out_reread_same) { + reread_same = + nm_connection_compare(reread, connection, NM_SETTING_COMPARE_FLAG_EXACT); + if (!reread_same) { + _LOGD("write: connection %s (%s) was modified by persisting it to \"%s\" ", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection), + svFileGetName(ifcfg)); + } + } + } + + NM_SET_OUT(out_reread, g_steal_pointer(&reread)); + NM_SET_OUT(out_reread_same, reread_same); + } + + /* Only return the filename if this was a newly written ifcfg */ + if (out_filename && !filename) + *out_filename = g_strdup(svFileGetName(ifcfg)); + + return TRUE; +} + +gboolean +nms_ifcfg_rh_writer_can_write_connection(NMConnection *connection, GError **error) +{ + const char *type, *id; + + type = nm_connection_get_connection_type(connection); + if (NM_IN_STRSET(type, + NM_SETTING_VLAN_SETTING_NAME, + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_TEAM_SETTING_NAME, + NM_SETTING_BRIDGE_SETTING_NAME)) + return TRUE; + if (nm_streq0(type, NM_SETTING_WIRED_SETTING_NAME) + && !nm_connection_get_setting_pppoe(connection)) + return TRUE; + + id = nm_connection_get_id(connection); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "The ifcfg-rh plugin cannot write the connection %s%s%s (type %s%s%s)", + NM_PRINT_FMT_QUOTE_STRING(id), + NM_PRINT_FMT_QUOTE_STRING(type)); + return FALSE; +} diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h new file mode 100644 index 0000000..d0d8a4c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef __NMS_IFCFG_RH_WRITER_H__ +#define __NMS_IFCFG_RH_WRITER_H__ + +#include "nm-connection.h" + +typedef gboolean (*NMSIfcfgRHWriterAllowFilenameCb)(const char *check_filename, + gpointer allow_filename_user_data); + +gboolean nms_ifcfg_rh_writer_can_write_connection(NMConnection *connection, GError **error); + +gboolean nms_ifcfg_rh_writer_write_connection(NMConnection * connection, + const char * ifcfg_dir, + const char * filename, + NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + char ** out_filename, + NMConnection **out_reread, + gboolean * out_reread_same, + GError ** error); + +#endif /* __NMS_IFCFG_RH_WRITER_H__ */ diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c new file mode 100644 index 0000000..6c1cd41 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/shvar.c @@ -0,0 +1,1585 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 1999, 2000 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "shvar.h" + +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "nm-core-utils.h" +#include "nm-glib-aux/nm-enum-utils.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "c-list/src/c-list.h" +#include "nms-ifcfg-rh-utils.h" + +/*****************************************************************************/ + +struct _shvarLine { + const char *key; + + CList lst; + + /* We index variables by their key in shvarFile.lst_idx. One shell variable might + * occur multiple times in a file (in which case the last occurrence wins). + * Hence, we need to keep a list of all the same keys. + * + * This is a pointer to the next shadowed line. */ + struct _shvarLine *prev_shadowed; + + /* There are three cases: + * + * 1) the line is not a valid variable assignment (that is, it doesn't + * start with a "FOO=" with possible whitespace prefix). + * In that case, @key and @key_with_prefix are %NULL, and the entire + * original line is in @line. Such entries are ignored for the most part. + * + * 2) if the line can be parsed with a "FOO=" assignment, then @line contains + * the part after '=', @key_with_prefix contains the key "FOO" with possible + * whitespace prefix, and @key points into @key_with_prefix skipping over the + * whitespace. + * + * 3) like 2, but if the value was deleted via svSetValue(), the entry is not removed, + * but only marked for deletion. That is done by clearing @line but preserving + * @key/@key_with_prefix. + * */ + char *line; + char *key_with_prefix; + + /* svSetValue() will clear the dirty flag. */ + bool dirty : 1; +}; + +typedef struct _shvarLine shvarLine; + +struct _shvarFile { + char * fileName; + CList lst_head; + GHashTable *lst_idx; + int fd; + bool modified : 1; +}; + +/*****************************************************************************/ + +static void _line_link_parse(shvarFile *s, const char *value, gsize len); + +/*****************************************************************************/ + +#define ASSERT_key_is_well_known(key) \ + nm_assert(({ \ + const char *_key = (key); \ + gboolean _is_wellknown = TRUE; \ + \ + if (!nms_ifcfg_rh_utils_is_well_known_key(_key)) { \ + _is_wellknown = FALSE; \ + g_critical("ifcfg-rh key \"%s\" is not well-known", _key); \ + } \ + \ + _is_wellknown; \ + })) + +/** + * svParseBoolean: + * @value: the input string + * @fallback: the fallback value + * + * Parses a string and returns the boolean value it contains or, + * in case no valid value is found, the fallback value. Valid values + * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0". + * + * Returns: the parsed boolean value or @fallback. + */ +int +svParseBoolean(const char *value, int fallback) +{ + if (!value) + return fallback; + + if (!g_ascii_strcasecmp("yes", value) || !g_ascii_strcasecmp("true", value) + || !g_ascii_strcasecmp("t", value) || !g_ascii_strcasecmp("y", value) + || !g_ascii_strcasecmp("1", value)) + return TRUE; + else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value) + || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value) + || !g_ascii_strcasecmp("0", value)) + return FALSE; + + return fallback; +} + +/*****************************************************************************/ + +static gboolean +_shell_is_name(const char *key, gssize len) +{ + gssize i; + + /* whether @key is a valid identifier (name). */ + if (!key || len == 0) + return FALSE; + if (!g_ascii_isalpha(key[0]) && key[0] != '_') + return FALSE; + for (i = 1; TRUE; i++) { + if (len < 0) { + if (!key[i]) + return TRUE; + } else { + if (i >= len) + return TRUE; + } + if (!g_ascii_isalnum(key[i]) && key[i] != '_') + return FALSE; + } +} + +/*****************************************************************************/ + +/* like g_strescape(), except that it also escapes '\''' *sigh*. + * + * While at it, add $''. */ +static char * +_escape_ansic(const char *source) +{ + const char *p; + char * dest; + char * q; + + nm_assert(source); + + p = (const char *) source; + /* Each source byte needs maximally four destination chars (\777) */ + q = dest = g_malloc(strlen(source) * 4 + 1 + 3); + + *q++ = '$'; + *q++ = '\''; + + while (*p) { + switch (*p) { + case '\b': + *q++ = '\\'; + *q++ = 'b'; + break; + case '\f': + *q++ = '\\'; + *q++ = 'f'; + break; + case '\n': + *q++ = '\\'; + *q++ = 'n'; + break; + case '\r': + *q++ = '\\'; + *q++ = 'r'; + break; + case '\t': + *q++ = '\\'; + *q++ = 't'; + break; + case '\v': + *q++ = '\\'; + *q++ = 'v'; + break; + case '\\': + case '"': + case '\'': + *q++ = '\\'; + *q++ = *p; + break; + default: + if ((*p < ' ') || (*p >= 0177)) { + *q++ = '\\'; + *q++ = '0' + (((*p) >> 6) & 07); + *q++ = '0' + (((*p) >> 3) & 07); + *q++ = '0' + ((*p) & 07); + } else + *q++ = *p; + break; + } + p++; + } + *q++ = '\''; + *q++ = '\0'; + + nm_assert(q - dest <= strlen(source) * 4 + 1 + 3); + + return dest; +} + +/*****************************************************************************/ + +#define _char_req_escape(ch) NM_IN_SET(ch, '"', '\\', '$', '`') +#define _char_req_escape_old(ch) NM_IN_SET(ch, '"', '\\', '\'', '$', '`', '~') +#define _char_req_quotes(ch) NM_IN_SET(ch, ' ', '\'', '~', '\t', '|', '&', ';', '(', ')', '<', '>') + +const char * +svEscape(const char *s, char **to_free) +{ + char *new; + gsize mangle = 0; + gboolean requires_quotes = FALSE; + int newlen; + size_t i, j, slen; + + for (slen = 0; s[slen]; slen++) { + if (_char_req_escape(s[slen])) + mangle++; + else if (_char_req_quotes(s[slen])) + requires_quotes = TRUE; + else if (s[slen] < ' ') { + /* if the string contains newline we can only express it using ANSI C quotation + * (as we don't support line continuation). + * Additionally, ANSI control characters look odd with regular quotation, so handle + * them too. */ + return (*to_free = _escape_ansic(s)); + } + } + if (!mangle && !requires_quotes) { + *to_free = NULL; + return s; + } + + newlen = slen + mangle + 3; /* 3 is extra ""\0 */ + new = g_malloc(newlen); + + j = 0; + new[j++] = '"'; + for (i = 0; i < slen; i++) { + if (_char_req_escape(s[i])) + new[j++] = '\\'; + new[j++] = s[i]; + } + new[j++] = '"'; + new[j++] = '\0'; + + nm_assert(j == slen + mangle + 3); + + *to_free = new; + return new; +} + +static gboolean +_looks_like_old_svescaped(const char *value) +{ + gsize k; + + if (value[0] != '"') + return FALSE; + + for (k = 1;; k++) { + if (value[k] == '\0') + return FALSE; + if (!_char_req_escape_old(value[k])) + continue; + + if (value[k] == '"') + return (value[k + 1] == '\0'); + else if (value[k] == '\\') { + k++; + if (!_char_req_escape_old(value[k])) + return FALSE; + } else + return FALSE; + } +} + +static gboolean +_ch_octal_is(char ch) +{ + return ch >= '0' && ch < '8'; +} + +static guint8 +_ch_octal_get(char ch) +{ + nm_assert(_ch_octal_is(ch)); + return (ch - '0'); +} + +static gboolean +_ch_hex_is(char ch) +{ + return g_ascii_isxdigit(ch); +} + +static guint8 +_ch_hex_get(char ch) +{ + nm_assert(_ch_hex_is(ch)); + return ch <= '9' ? ch - '0' : (ch & 0x4F) - 'A' + 10; +} + +static void +_gstr_init(GString **str, const char *value, gsize i) +{ + nm_assert(str); + nm_assert(value); + + if (!(*str)) { + /* if @str is not yet initialized, it allocates + * a new GString and copies @i characters from + * @value over. + * + * Unescaping usually does not extend the length of a string, + * so we might be tempted to allocate a fixed buffer of length + * (strlen(value)+CONST). + * However, due to $'\Ux' escapes, the maximum length is some + * (FACTOR*strlen(value) + CONST), which is non trivial to get + * right in all cases. Also, we would have to provision for the + * very unlikely extreme case. + * Instead, use a GString buffer which can grow as needed. But for an + * initial guess, strlen(value) is a good start */ + *str = g_string_new_len(NULL, strlen(value) + 3); + if (i) + g_string_append_len(*str, value, i); + } +} + +const char * +svUnescape(const char *value, char **to_free) +{ + gsize i, j; + GString *str = NULL; + int looks_like_old_svescaped = -1; + + /* we handle bash syntax here (note that ifup has #!/bin/bash. + * Thus, see https://www.gnu.org/software/bash/manual/html_node/Quoting.html#Quoting */ + + /* @value shall start with the first character after "FOO=" */ + + nm_assert(value); + nm_assert(to_free); + + /* we don't expect any newlines. They must be filtered out before-hand. + * We also don't support line continuation. */ + nm_assert(!NM_STRCHAR_ANY(value, ch, ch == '\n')); + + i = 0; + while (TRUE) { + if (value[i] == '\0') + goto out_value; + + if (g_ascii_isspace(value[i]) || value[i] == ';') { + gboolean has_semicolon = (value[i] == ';'); + + /* starting with space is only allowed, if the entire + * string consists of spaces (possibly terminated by a comment). + * This disallows for example + * LANG=C ls -1 + * LANG= ls -1 + * but allows + * LANG= #comment + * + * As a special case, we also allow one trailing semicolon, as long + * it is only followed by whitespace or a #-comment. + * FOO=; + * FOO=a; + * FOO=b ; #hallo + */ + j = i + 1; + while (g_ascii_isspace(value[j]) + || (!has_semicolon && (has_semicolon = (value[j] == ';')))) + j++; + if (!NM_IN_SET(value[j], '\0', '#')) + goto out_error; + goto out_value; + } + + if (value[i] == '\\') { + /* backslash escape */ + _gstr_init(&str, value, i); + i++; + if (G_UNLIKELY(value[i] == '\0')) { + /* we don't support line continuation */ + goto out_error; + } + g_string_append_c(str, value[i]); + i++; + goto loop1_next; + } + + if (value[i] == '\'') { + /* single quotes */ + _gstr_init(&str, value, i); + i++; + j = i; + while (TRUE) { + if (value[j] == '\0') { + /* unterminated single quote. We don't support line continuation */ + goto out_error; + } + if (value[j] == '\'') + break; + j++; + } + g_string_append_len(str, &value[i], j - i); + i = j + 1; + goto loop1_next; + } + + if (value[i] == '"') { + /* double quotes */ + _gstr_init(&str, value, i); + i++; + while (TRUE) { + if (value[i] == '"') { + i++; + break; + } + if (value[i] == '\0') { + /* unterminated double quote. We don't support line continuation. */ + goto out_error; + } + if (NM_IN_SET(value[i], '`', '$')) { + /* we don't support shell expansion. */ + goto out_error; + } + if (value[i] == '\\') { + i++; + if (value[i] == '\0') { + /* we don't support line continuation */ + goto out_error; + } + if (NM_IN_SET(value[i], '$', '`', '"', '\\')) { + /* Drop the backslash. */ + } else if (NM_IN_SET(value[i], '\'', '~')) { + /* '\'' and '~' in double quotes are not handled special by shell. + * However, old versions of svEscape() would wrongly use double-quoting + * with backslash escaping for these characters (expecting svUnescape() + * to remove the backslash). + * + * In order to preserve previous behavior, we continue to read such + * strings different then shell does. */ + + /* Actually, we can relax this. Old svEscape() escaped the entire value + * in a particular way with double quotes. + * If the value doesn't exactly look like something as created by svEscape(), + * don't do the compat hack and preserve the backslash. */ + if (looks_like_old_svescaped < 0) + looks_like_old_svescaped = _looks_like_old_svescaped(value); + if (!looks_like_old_svescaped) + g_string_append_c(str, '\\'); + } else + g_string_append_c(str, '\\'); + } + g_string_append_c(str, value[i]); + i++; + } + goto loop1_next; + } + + if (value[i] == '$' && value[i + 1] == '\'') { + /* ANSI-C Quoting */ + _gstr_init(&str, value, i); + i += 2; + while (TRUE) { + char ch; + + if (value[i] == '\'') { + i++; + break; + } + if (value[i] == '\0') { + /* unterminated double quote. We don't support line continuation. */ + goto out_error; + } + if (value[i] == '\\') { + i++; + if (value[i] == '\0') { + /* we don't support line continuation */ + goto out_error; + } + switch (value[i]) { + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'e': + ch = '\e'; + break; + case 'E': + ch = '\E'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + case '?': + ch = '\?'; + break; + case '"': + ch = '"'; + break; + case '\\': + ch = '\\'; + break; + case '\'': + ch = '\''; + break; + default: + if (_ch_octal_is(value[i])) { + guint v; + + v = _ch_octal_get(value[i]); + i++; + if (_ch_octal_is(value[i])) { + v = (v * 8) + _ch_octal_get(value[i]); + i++; + if (_ch_octal_is(value[i])) { + v = (v * 8) + _ch_octal_get(value[i]); + i++; + } + } + /* like bash, we cut too large numbers off. E.g. A=$'\772' becomes 0xfa */ + g_string_append_c(str, (guint8) v); + } else if (NM_IN_SET(value[i], 'x', 'u', 'U')) { + const char escape_type = value[i]; + int max_digits = escape_type == 'x' ? 2 : escape_type == 'u' ? 4 : 8; + guint64 v; + + i++; + if (!_ch_hex_is(value[i])) { + /* missing hex value after "\x" escape. This is treated like no escaping. */ + g_string_append_c(str, '\\'); + g_string_append_c(str, escape_type); + } else { + v = _ch_hex_get(value[i]); + i++; + + while (--max_digits > 0) { + if (!_ch_hex_is(value[i])) + break; + v = v * 16 + _ch_hex_get(value[i]); + i++; + } + if (escape_type == 'x') + g_string_append_c(str, v); + else { + /* we treat the unicode escapes as utf-8 encoded values. */ + g_string_append_unichar(str, v); + } + } + } else { + g_string_append_c(str, '\\'); + g_string_append_c(str, value[i]); + i++; + } + goto loop_ansic_next; + } + } else + ch = value[i]; + g_string_append_c(str, ch); + i++; +loop_ansic_next:; + } + goto loop1_next; + } + + if (NM_IN_SET(value[i], '|', '&', '(', ')', '<', '>')) { + /* shell metacharacters are not supported without quoting. + * Note that ';' is already handled above. */ + goto out_error; + } + + /* an unquoted, regular character. Just consume it directly. */ + if (str) + g_string_append_c(str, value[i]); + i++; + +loop1_next:; + } + + nm_assert_not_reached(); + +out_value: + if (i == 0) { + nm_assert(!str); + *to_free = NULL; + return ""; + } + + if (str) { + if (str->len == 0 || str->str[0] == '\0') { + g_string_free(str, TRUE); + *to_free = NULL; + return ""; + } else { + *to_free = g_string_free(str, FALSE); + return *to_free; + } + } + + if (value[i] != '\0') { + *to_free = g_strndup(value, i); + return *to_free; + } + + *to_free = NULL; + return value; + +out_error: + if (str) + g_string_free(str, TRUE); + *to_free = NULL; + return NULL; +} + +/*****************************************************************************/ + +shvarFile * +svFile_new(const char *name, int fd, const char *content) +{ + shvarFile * s; + const char *p; + const char *q; + + nm_assert(name); + nm_assert(fd >= -1); + + s = g_slice_new(shvarFile); + *s = (shvarFile){ + .fileName = g_strdup(name), + .fd = fd, + .lst_head = C_LIST_INIT(s->lst_head), + .lst_idx = g_hash_table_new(nm_pstr_hash, nm_pstr_equal), + }; + + if (content) { + for (p = content; (q = strchr(p, '\n')) != NULL; p = q + 1) + _line_link_parse(s, p, q - p); + if (p[0]) + _line_link_parse(s, p, strlen(p)); + } + + return s; +} + +const char * +svFileGetName(const shvarFile *s) +{ + nm_assert(s); + + return s->fileName; +} + +void +_nmtst_svFileSetName(shvarFile *s, const char *fileName) +{ + /* changing the file name is not supported for regular + * operation. Only allowed to use in tests, otherwise, + * the filename is immutable. */ + g_free(s->fileName); + s->fileName = g_strdup(fileName); +} + +void +_nmtst_svFileSetModified(shvarFile *s) +{ + /* marking a file as modified is only for testing. */ + s->modified = TRUE; +} + +/*****************************************************************************/ + +static void +ASSERT_shvarLine(const shvarLine *line) +{ +#if NM_MORE_ASSERTS > 5 + const char *s, *s2; + + nm_assert(line); + if (!line->key) { + nm_assert(line->line); + nm_assert(!line->key_with_prefix); + s = nm_str_skip_leading_spaces(line->line); + s2 = strchr(s, '='); + nm_assert(!s2 || !_shell_is_name(s, s2 - s)); + } else { + nm_assert(line->key_with_prefix); + nm_assert(line->key == nm_str_skip_leading_spaces(line->key_with_prefix)); + nm_assert(_shell_is_name(line->key, -1)); + } +#endif +} + +static shvarLine * +line_new_parse(const char *value, gsize len) +{ + shvarLine *line; + gsize k, e; + + nm_assert(value); + + line = g_slice_new(shvarLine); + *line = (shvarLine){ + .lst = C_LIST_INIT(line->lst), + .dirty = TRUE, + }; + + for (k = 0; k < len; k++) { + if (g_ascii_isspace(value[k])) + continue; + + if (g_ascii_isalpha(value[k]) || value[k] == '_') { + for (e = k + 1; e < len; e++) { + if (value[e] == '=') { + nm_assert(_shell_is_name(&value[k], e - k)); + line->line = g_strndup(&value[e + 1], len - e - 1); + line->key_with_prefix = g_strndup(value, e); + line->key = &line->key_with_prefix[k]; + ASSERT_shvarLine(line); + return line; + } + if (!g_ascii_isalnum(value[e]) && value[e] != '_') + break; + } + } + break; + } + line->line = g_strndup(value, len); + ASSERT_shvarLine(line); + return line; +} + +static shvarLine * +line_new_build(const char *key, const char *value) +{ + char * value_escaped = NULL; + shvarLine *line; + char * new_key; + + value = svEscape(value, &value_escaped); + + line = g_slice_new(shvarLine); + new_key = g_strdup(key), *line = (shvarLine){ + .lst = C_LIST_INIT(line->lst), + .line = value_escaped ?: g_strdup(value), + .key_with_prefix = new_key, + .key = new_key, + .dirty = FALSE, + }; + ASSERT_shvarLine(line); + return line; +} + +static gboolean +line_set(shvarLine *line, const char *value) +{ + char * value_escaped = NULL; + gboolean changed = FALSE; + + ASSERT_shvarLine(line); + nm_assert(line->key); + + line->dirty = FALSE; + + if (line->key != line->key_with_prefix) { + memmove(line->key_with_prefix, line->key, strlen(line->key) + 1); + line->key = line->key_with_prefix; + changed = TRUE; + ASSERT_shvarLine(line); + } + + value = svEscape(value, &value_escaped); + + if (line->line) { + if (nm_streq(value, line->line)) { + g_free(value_escaped); + return changed; + } + g_free(line->line); + } + + line->line = value_escaped ?: g_strdup(value); + ASSERT_shvarLine(line); + return TRUE; +} + +static void +line_free(shvarLine *line) +{ + ASSERT_shvarLine(line); + c_list_unlink_stale(&line->lst); + g_free(line->line); + g_free(line->key_with_prefix); + g_slice_free(shvarLine, line); +} + +/*****************************************************************************/ + +static void +_line_link_parse(shvarFile *s, const char *value, gsize len) +{ + shvarLine *line; + + line = line_new_parse(value, len); + if (!line->key) + goto do_link; + + if (G_UNLIKELY(!g_hash_table_insert(s->lst_idx, line, line))) { + shvarLine *existing_key; + shvarLine *existing_val; + + /* Slow-path: we have duplicate keys. Fix the mess we created. + * Unfortunately, g_hash_table_insert() now had to allocate an extra + * array to track the keys/values differently. I wish there was an + * GHashTable API to add a key only if it does not exist yet. */ + + if (!g_hash_table_lookup_extended(s->lst_idx, + line, + (gpointer *) &existing_key, + (gpointer *) &existing_val)) + nm_assert_not_reached(); + + nm_assert(existing_val == line); + nm_assert(existing_key != line); + line->prev_shadowed = existing_key; + g_hash_table_replace(s->lst_idx, line, line); + } + +do_link: + c_list_link_tail(&s->lst_head, &line->lst); +} + +/*****************************************************************************/ + +/* Open the file , returning a shvarFile on success and NULL on failure. + * Add a wrinkle to let the caller specify whether or not to create the file + * (actually, return a structure anyway) if it doesn't exist. + */ +static shvarFile * +svOpenFileInternal(const char *name, gboolean create, GError **error) +{ + gboolean closefd = FALSE; + int errsv = 0; + gs_free char *content = NULL; + gs_free_error GError *local = NULL; + nm_auto_close int fd = -1; + + if (create) + fd = open(name, O_RDWR | O_CLOEXEC); /* NOT O_CREAT */ + if (fd < 0) { + /* try read-only */ + fd = open(name, O_RDONLY | O_CLOEXEC); /* NOT O_CREAT */ + if (fd < 0) + errsv = errno; + else + closefd = TRUE; + } + + if (fd < 0) { + if (create) + return svFile_new(name, -1, NULL); + + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not read file '%s': %s", + name, + nm_strerror_native(errsv)); + return NULL; + } + + if (!nm_utils_fd_get_contents(closefd ? nm_steal_fd(&fd) : fd, + closefd, + 10 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &content, + NULL, + NULL, + &local)) { + if (create) + return svFile_new(name, -1, NULL); + + g_set_error(error, + G_FILE_ERROR, + local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED, + "Could not read file '%s': %s", + name, + local->message); + return NULL; + } + + /* closefd is set if we opened the file read-only, so go ahead and + * close it, because we can't write to it anyway */ + nm_assert(closefd || fd >= 0); + return svFile_new(name, !closefd ? nm_steal_fd(&fd) : -1, content); +} + +/* Open the file , return shvarFile on success, NULL on failure */ +shvarFile * +svOpenFile(const char *name, GError **error) +{ + return svOpenFileInternal(name, FALSE, error); +} + +/* Create a new file structure, returning actual data if the file exists, + * and a suitable starting point if it doesn't. + */ +shvarFile * +svCreateFile(const char *name) +{ + return svOpenFileInternal(name, TRUE, NULL); +} + +/*****************************************************************************/ + +static gboolean +_svKeyMatchesType(const char *key, SvKeyType match_key_type) +{ + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ANY)) + return TRUE; + +#define _IS_NUMBERED(key, tag) \ + ({ \ + gint64 _idx; \ + \ + NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, &_idx) \ + &&_idx >= 0; \ + }) + + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) { + if (_IS_NUMBERED(key, "ADDRESS") || _IS_NUMBERED(key, "NETMASK") + || _IS_NUMBERED(key, "GATEWAY") || _IS_NUMBERED(key, "METRIC") + || _IS_NUMBERED(key, "OPTIONS")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) { + if (_IS_NUMBERED(key, "IPADDR") || _IS_NUMBERED(key, "PREFIX") + || _IS_NUMBERED(key, "NETMASK") || _IS_NUMBERED(key, "GATEWAY")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_USER)) { + if (g_str_has_prefix(key, "NM_USER_")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_TC)) { + if (_IS_NUMBERED(key, "QDISC") || _IS_NUMBERED(key, "FILTER")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_SRIOV_VF)) { + if (_IS_NUMBERED(key, "SRIOV_VF")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) { + if (_IS_NUMBERED(key, "ROUTING_RULE_")) + return TRUE; + } + if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) { + if (_IS_NUMBERED(key, "ROUTING_RULE6_")) + return TRUE; + } + + return FALSE; +} + +gint64 +svNumberedParseKey(const char *key) +{ + gint64 idx; + + if (NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE_", &idx) + || NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE6_", &idx)) + return idx; + return -1; +} + +GHashTable * +svGetKeys(shvarFile *s, SvKeyType match_key_type) +{ + GHashTable * keys = NULL; + CList * current; + const shvarLine *line; + + nm_assert(s); + + c_list_for_each (current, &s->lst_head) { + line = c_list_entry(current, shvarLine, lst); + if (line->key && line->line && _svKeyMatchesType(line->key, match_key_type)) { + /* we don't clone the keys. The keys are only valid + * until @s gets modified. */ + if (!keys) + keys = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL); + g_hash_table_add(keys, (gpointer) line->key); + } + } + return keys; +} + +static int +_get_keys_sorted_cmp(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *k_a = *((const char *const *) a); + const char *k_b = *((const char *const *) b); + gint64 n_a; + gint64 n_b; + + n_a = svNumberedParseKey(k_a); + n_b = svNumberedParseKey(k_b); + NM_CMP_DIRECT(n_a, n_b); + NM_CMP_RETURN(strcmp(k_a, k_b)); + nm_assert_not_reached(); + return 0; +} + +const char ** +svGetKeysSorted(shvarFile *s, SvKeyType match_key_type, guint *out_len) +{ + gs_unref_hashtable GHashTable *keys_hash = NULL; + + keys_hash = svGetKeys(s, match_key_type); + if (!keys_hash) { + NM_SET_OUT(out_len, 0); + return NULL; + } + return ( + const char **) nm_utils_hash_keys_to_array(keys_hash, _get_keys_sorted_cmp, NULL, out_len); +} + +/*****************************************************************************/ + +const char * +svFindFirstNumberedKey(shvarFile *s, const char *key_prefix) +{ + const shvarLine *line; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key_prefix, NULL); + + c_list_for_each_entry (line, &s->lst_head, lst) { + if (line->key && line->line + && nms_ifcfg_rh_utils_is_numbered_tag(line->key, key_prefix, NULL)) + return line->key; + } + + return NULL; +} + +/*****************************************************************************/ + +static const char * +_svGetValue(shvarFile *s, const char *key, char **to_free) +{ + const shvarLine *line; + const char * v; + + nm_assert(s); + nm_assert(_shell_is_name(key, -1)); + nm_assert(to_free); + + ASSERT_key_is_well_known(key); + + line = g_hash_table_lookup(s->lst_idx, &key); + + if (line && line->line) { + v = svUnescape(line->line, to_free); + if (!v) { + /* a wrongly quoted value is treated like the empty string. + * See also svWriteFile(), which handles unparsable values + * that way. */ + nm_assert(!*to_free); + return ""; + } + return v; + } + *to_free = NULL; + return NULL; +} + +/* Returns the value for key. The value is either owned by @s + * or returned as to_free. This aims to avoid cloning the string. + * + * - like svGetValue_cp(), but avoids cloning the value if possible. + * - like svGetValueStr(), but does not ignore empty string values. + */ +const char * +svGetValue(shvarFile *s, const char *key, char **to_free) +{ + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(to_free, NULL); + + return _svGetValue(s, key, to_free); +} + +/* Returns the value for key. The value is either owned by @s + * or returned as to_free. This aims to avoid cloning the string. + * + * - like svGetValue(), but does not return an empty string. + * - like svGetValueStr_cp(), but avoids cloning the value if possible. + */ +const char * +svGetValueStr(shvarFile *s, const char *key, char **to_free) +{ + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(to_free, NULL); + + value = _svGetValue(s, key, to_free); + if (!value || !value[0]) { + nm_assert(!*to_free); + return NULL; + } + return value; +} + +/* Returns the value for key. The returned value must be freed + * by the caller. + * + * - like svGetValue(), but always returns a copy of the value. + * - like svGetValueStr_cp(), but does not ignore an empty string. + */ +char * +svGetValue_cp(shvarFile *s, const char *key) +{ + char * to_free; + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + + value = _svGetValue(s, key, &to_free); + if (!value) { + nm_assert(!to_free); + return NULL; + } + return to_free ?: g_strdup(value); +} + +/* Returns the value for key. The returned value must be freed + * by the caller. + * If the key is unset or the value an empty string, NULL is returned. + * + * - like svGetValueStr(), but always returns a copy of the value. + * - like svGetValue_cp(), but returns NULL instead of an empty string. + */ +char * +svGetValueStr_cp(shvarFile *s, const char *key) +{ + char * to_free; + const char *value; + + g_return_val_if_fail(s, NULL); + g_return_val_if_fail(key, NULL); + + value = _svGetValue(s, key, &to_free); + if (!value || !value[0]) { + nm_assert(!to_free); + return NULL; + } + return to_free ?: g_strdup(value); +} + +/* svGetValueBoolean: + * @s: fhe file + * @key: the name of the key to read + * @fallback: the fallback value in any error case + * + * Reads a value @key and converts it to a boolean using svParseBoolean(). + * + * Returns: the parsed boolean value or @fallback. + */ +int +svGetValueBoolean(shvarFile *s, const char *key, int fallback) +{ + gs_free char *to_free = NULL; + const char * value; + + value = _svGetValue(s, key, &to_free); + return svParseBoolean(value, fallback); +} + +/* svGetValueTernary: + * @s: fhe file + * @key: the name of the key to read + * + * Reads a value @key and converts it to a NMTernary value. + * + * Returns: the parsed NMTernary + */ +NMTernary +svGetValueTernary(shvarFile *s, const char *key) +{ + return svGetValueBoolean(s, key, NM_TERNARY_DEFAULT); +} + +/* svGetValueInt64: + * @s: fhe file + * @key: the name of the key to read + * @base: the numeric base (usually 10). Setting to 0 means "auto". Usually you want 10. + * @min: the minimum for range-check + * @max: the maximum for range-check + * @fallback: the fallback value in any error case + * + * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64(). + * In case of error, @errno will be set and @fallback returned. */ +gint64 +svGetValueInt64(shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback) +{ + char * to_free; + const char *value; + gint64 result; + int errsv; + + value = _svGetValue(s, key, &to_free); + if (!value) { + nm_assert(!to_free); + /* indicate that the key does not exist (or has a syntax error + * and svUnescape() failed). */ + errno = ENOKEY; + return fallback; + } + + result = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback); + if (to_free) { + errsv = errno; + g_free(to_free); + errno = errsv; + } + return result; +} + +gboolean +svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GError **error) +{ + gs_free char *to_free = NULL; + const char * svalue; + gs_free char *err_token = NULL; + int value; + + svalue = _svGetValue(s, key, &to_free); + if (!svalue) { + /* don't touch out_value. The caller is supposed + * to initialize it with the default value. */ + return TRUE; + } + + if (!nm_utils_enum_from_str(gtype, svalue, &value, &err_token)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Invalid token \"%s\" in \"%s\" for %s", + err_token, + svalue, + key); + return FALSE; + } + + NM_SET_OUT(out_value, value); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +svUnsetAll(shvarFile *s, SvKeyType match_key_type) +{ + shvarLine *line; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + + c_list_for_each_entry (line, &s->lst_head, lst) { + ASSERT_shvarLine(line); + if (line->key && _svKeyMatchesType(line->key, match_key_type)) { + if (nm_clear_g_free(&line->line)) { + ASSERT_shvarLine(line); + changed = TRUE; + } + } + } + + if (changed) + s->modified = TRUE; + return changed; +} + +gboolean +svUnsetDirtyWellknown(shvarFile *s, NMTernary new_dirty_value) +{ + shvarLine *line; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + + c_list_for_each_entry (line, &s->lst_head, lst) { + const NMSIfcfgKeyTypeInfo *ti; + + ASSERT_shvarLine(line); + + if (line->dirty && line->key && line->line + && (ti = nms_ifcfg_rh_utils_is_well_known_key(line->key)) + && !NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY)) { + if (nm_clear_g_free(&line->line)) { + ASSERT_shvarLine(line); + changed = TRUE; + } + } + + if (new_dirty_value != NM_TERNARY_DEFAULT) + line->dirty = (new_dirty_value != NM_TERNARY_FALSE); + } + + if (changed) + s->modified = TRUE; + return changed; +} + +/* Same as svSetValueStr() but it preserves empty @value -- contrary to + * svSetValueStr() for which "" effectively means to remove the value. */ +gboolean +svSetValue(shvarFile *s, const char *key, const char *value) +{ + shvarLine *line; + shvarLine *l_shadowed; + gboolean changed = FALSE; + + g_return_val_if_fail(s, FALSE); + g_return_val_if_fail(key, FALSE); + + nm_assert(_shell_is_name(key, -1)); + + ASSERT_key_is_well_known(key); + + line = g_hash_table_lookup(s->lst_idx, &key); + if (line && (l_shadowed = line->prev_shadowed)) { + /* if we find multiple entries for the same key, we can + * delete the shadowed ones. */ + line->prev_shadowed = NULL; + changed = TRUE; + do { + shvarLine *l = l_shadowed; + + l_shadowed = l_shadowed->prev_shadowed; + line_free(l); + } while (l_shadowed); + } + + if (!value) { + if (line) { + /* We only clear the value, but leave the line entry. This way, if we + * happen to re-add the value, we write it to the same line again. */ + if (nm_clear_g_free(&line->line)) { + changed = TRUE; + } + } + } else { + if (!line) { + line = line_new_build(key, value); + if (!g_hash_table_add(s->lst_idx, line)) + nm_assert_not_reached(); + c_list_link_tail(&s->lst_head, &line->lst); + changed = TRUE; + } else { + if (line_set(line, value)) + changed = TRUE; + } + } + + if (changed) + s->modified = TRUE; + return changed; +} + +/* Set the variable equal to the value . + * If does not exist, and the pointer is set, append + * the key=value pair after that line. Otherwise, append the pair + * to the bottom of the file. + */ +gboolean +svSetValueStr(shvarFile *s, const char *key, const char *value) +{ + return svSetValue(s, key, value && value[0] ? value : NULL); +} + +gboolean +svSetValueInt64(shvarFile *s, const char *key, gint64 value) +{ + char buf[NM_DECIMAL_STR_MAX(value)]; + + return svSetValue(s, key, nm_sprintf_buf(buf, "%" G_GINT64_FORMAT, value)); +} + +gboolean +svSetValueInt64_cond(shvarFile *s, const char *key, gboolean do_set, gint64 value) +{ + if (do_set) + return svSetValueInt64(s, key, value); + else + return svUnsetValue(s, key); +} + +gboolean +svSetValueBoolean(shvarFile *s, const char *key, gboolean value) +{ + return svSetValue(s, key, value ? "yes" : "no"); +} + +gboolean +svSetValueTernary(shvarFile *s, const char *key, NMTernary value) +{ + if (NM_IN_SET(value, NM_TERNARY_TRUE, NM_TERNARY_FALSE)) + return svSetValueBoolean(s, key, (gboolean) value); + else + return svUnsetValue(s, key); +} + +gboolean +svSetValueBoolean_cond_true(shvarFile *s, const char *key, gboolean value) +{ + return svSetValue(s, key, value ? "yes" : NULL); +} + +gboolean +svSetValueEnum(shvarFile *s, const char *key, GType gtype, int value) +{ + gs_free char *v = NULL; + + v = _nm_utils_enum_to_str_full(gtype, value, " ", NULL); + return svSetValueStr(s, key, v); +} + +gboolean +svUnsetValue(shvarFile *s, const char *key) +{ + return svSetValue(s, key, NULL); +} + +/*****************************************************************************/ + +/* Write the current contents iff modified. Returns FALSE on error + * and TRUE on success. Do not write if no values have been modified. + * The mode argument is only used if creating the file, not if + * re-writing an existing file, and is passed unchanged to the + * open() syscall. + */ +gboolean +svWriteFile(shvarFile *s, int mode, GError **error) +{ + FILE * f; + int tmpfd; + CList *current; + int errsv; + + if (s->modified) { + if (s->fd == -1) + s->fd = open(s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode); + if (s->fd == -1) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not open file '%s' for writing: %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + if (ftruncate(s->fd, 0) < 0) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Could not overwrite file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + + tmpfd = fcntl(s->fd, F_DUPFD_CLOEXEC, 0); + if (tmpfd == -1) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Internal error writing file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + f = fdopen(tmpfd, "w"); + if (!f) { + errsv = errno; + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "Internal error writing file '%s': %s", + s->fileName, + nm_strerror_native(errsv)); + return FALSE; + } + fseek(f, 0, SEEK_SET); + c_list_for_each (current, &s->lst_head) { + const shvarLine *line = c_list_entry(current, shvarLine, lst); + const char * str; + char * s_tmp; + gboolean valid_value; + + ASSERT_shvarLine(line); + + if (!line->key) { + str = nm_str_skip_leading_spaces(line->line); + if (NM_IN_SET(str[0], '\0', '#')) + fprintf(f, "%s\n", line->line); + else + fprintf(f, "#NM: %s\n", line->line); + continue; + } + + if (!line->line) + continue; + + /* we check that the assignment can be properly unescaped. */ + valid_value = !!svUnescape(line->line, &s_tmp); + g_free(s_tmp); + + if (valid_value) + fprintf(f, "%s=%s\n", line->key_with_prefix, line->line); + else { + fprintf(f, "%s=\n", line->key); + fprintf(f, "#NM: %s=%s\n", line->key_with_prefix, line->line); + } + } + fclose(f); + } + + return TRUE; +} + +void +svCloseFile(shvarFile *s) +{ + shvarLine *line; + + g_return_if_fail(s != NULL); + + if (s->fd >= 0) + nm_close(s->fd); + g_free(s->fileName); + g_hash_table_destroy(s->lst_idx); + while ((line = c_list_first_entry(&s->lst_head, shvarLine, lst))) + line_free(line); + g_slice_free(shvarFile, s); +} diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.h b/src/core/settings/plugins/ifcfg-rh/shvar.h new file mode 100644 index 0000000..6965d87 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/shvar.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 1999 Red Hat, Inc. + */ + +#ifndef _SHVAR_H +#define _SHVAR_H + +typedef struct _shvarFile shvarFile; + +typedef enum { + SV_KEY_TYPE_ANY = (1LL << 0), + SV_KEY_TYPE_ROUTE_SVFORMAT = (1LL << 1), + SV_KEY_TYPE_IP4_ADDRESS = (1LL << 2), + SV_KEY_TYPE_TC = (1LL << 3), + SV_KEY_TYPE_USER = (1LL << 4), + SV_KEY_TYPE_SRIOV_VF = (1LL << 5), + SV_KEY_TYPE_ROUTING_RULE4 = (1LL << 6), + SV_KEY_TYPE_ROUTING_RULE6 = (1LL << 7), +} SvKeyType; + +const char *svFileGetName(const shvarFile *s); + +void _nmtst_svFileSetName(shvarFile *s, const char *fileName); +void _nmtst_svFileSetModified(shvarFile *s); + +/*****************************************************************************/ + +shvarFile *svFile_new(const char *name, int fd, const char *content); + +/* Create the file , return a shvarFile (never fails) */ +shvarFile *svCreateFile(const char *name); + +/* Open the file , return shvarFile on success, NULL on failure */ +shvarFile *svOpenFile(const char *name, GError **error); + +/*****************************************************************************/ + +const char *svFindFirstNumberedKey(shvarFile *s, const char *key_prefix); + +/* Get the value associated with the key, and leave the current pointer + * pointing at the line containing the value. The char* returned MUST + * be freed by the caller. + */ +const char *svGetValue(shvarFile *s, const char *key, char **to_free); +char * svGetValue_cp(shvarFile *s, const char *key); + +const char *svGetValueStr(shvarFile *s, const char *key, char **to_free); +char * svGetValueStr_cp(shvarFile *s, const char *key); + +int svParseBoolean(const char *value, int def); + +gint64 svNumberedParseKey(const char *key); + +GHashTable *svGetKeys(shvarFile *s, SvKeyType match_key_type); + +const char **svGetKeysSorted(shvarFile *s, SvKeyType match_key_type, guint *out_len); + +/* return TRUE if resolves to any truth value (e.g. "yes", "y", "true") + * return FALSE if resolves to any non-truth value (e.g. "no", "n", "false") + * return otherwise + */ +int svGetValueBoolean(shvarFile *s, const char *key, int def); + +NMTernary svGetValueTernary(shvarFile *s, const char *key); + +gint64 +svGetValueInt64(shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback); + +gboolean svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GError **error); + +/* Set the variable equal to the value . + * If does not exist, and the pointer is set, append + * the key=value pair after that line. Otherwise, prepend the pair + * to the top of the file. + */ +gboolean svSetValue(shvarFile *s, const char *key, const char *value); +gboolean svSetValueStr(shvarFile *s, const char *key, const char *value); +gboolean svSetValueBoolean(shvarFile *s, const char *key, gboolean value); +gboolean svSetValueBoolean_cond_true(shvarFile *s, const char *key, gboolean value); +gboolean svSetValueInt64(shvarFile *s, const char *key, gint64 value); +gboolean svSetValueInt64_cond(shvarFile *s, const char *key, gboolean do_set, gint64 value); +gboolean svSetValueEnum(shvarFile *s, const char *key, GType gtype, int value); +gboolean svSetValueTernary(shvarFile *s, const char *key, NMTernary value); + +gboolean svUnsetValue(shvarFile *s, const char *key); +gboolean svUnsetAll(shvarFile *s, SvKeyType match_key_type); +gboolean svUnsetDirtyWellknown(shvarFile *s, NMTernary new_dirty_value); + +/* Write the current contents iff modified. Returns FALSE on error + * and TRUE on success. Do not write if no values have been modified. + * The mode argument is only used if creating the file, not if + * re-writing an existing file, and is passed unchanged to the + * open() syscall. + */ +gboolean svWriteFile(shvarFile *s, int mode, GError **error); + +static inline gboolean +svWriteFileWithoutDirtyWellknown(shvarFile *s, int mode, GError **error) +{ + svUnsetDirtyWellknown(s, NM_TERNARY_FALSE); + return svWriteFile(s, mode, error); +} + +/* Close the file descriptor (if open) and free the shvarFile. */ +void svCloseFile(shvarFile *s); + +const char *svEscape(const char *s, char **to_free); +const char *svUnescape(const char *s, char **to_free); + +static inline void +_nm_auto_shvar_file_close(shvarFile **p_s) +{ + if (*p_s) { + int errsv = errno; + + svCloseFile(*p_s); + errno = errsv; + } +} +#define nm_auto_shvar_file_close nm_auto(_nm_auto_shvar_file_close) + +#endif /* _SHVAR_H */ diff --git a/src/core/settings/plugins/ifcfg-rh/tests/meson.build b/src/core/settings/plugins/ifcfg-rh/tests/meson.build new file mode 100644 index 0000000..077e478 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/meson.build @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +exe = executable( + 'test-ifcfg-rh', + 'test-ifcfg-rh.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + link_with: libnms_ifcfg_rh_core, +) + +test( + 'ifcfg-rh/test-ifcfg-rh', + test_script, + timeout: 90, + args: test_args + [exe.full_path()], +) diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected new file mode 100644 index 0000000..d81d918 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected @@ -0,0 +1,8 @@ +HWADDR=00:22:15:59:62:97 +TYPE=Ethernet +BRIDGING_OPTS="priority=28 hairpin_mode=1" +NAME="System test-bridge-component" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=no +BRIDGE=br0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected new file mode 100644 index 0000000..2e69c5e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected @@ -0,0 +1,16 @@ +HWADDR=00:22:15:59:62:97 +TYPE=Ethernet +NAME="System test-bridge-component" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=no +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected new file mode 100644 index 0000000..59a6f79 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected @@ -0,0 +1,22 @@ +HWADDR=00:11:22:33:44:EE +TYPE=Ethernet +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=PEAP +IEEE_8021X_IDENTITY="Jara Cimrman" +IEEE_8021X_PASSWORD_FLAGS="user ask" +IEEE_8021X_PEAP_VERSION=1 +IEEE_8021X_INNER_AUTH_METHODS=GTC +IEEE_8021X_SUBJECT_MATCH=server1.yourdomain.tld +IEEE_8021X_PHASE2_SUBJECT_MATCH=server2.yourdomain.tld +IEEE_8021X_ALTSUBJECT_MATCHES="a.yourdomain.tld b.yourdomain.tld c.yourdomain.tld" +IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES="x.yourdomain.tld y.yourdomain.tld" +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="System test-wired-802-1X-subj-matches" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected new file mode 100644 index 0000000..a48be78 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected @@ -0,0 +1,34 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +NM_USER__M_Y___053=val=MY.+ +NM_USER__M_Y___055=val=MY.- +NM_USER__M_Y___057=val=MY./ +NM_USER__M_Y__8_053_V=val=MY.8+V +NM_USER__M_Y__8_055_V=val=MY.8-V +NM_USER__M_Y__8_057_V=val=MY.8/V +NM_USER__M_Y__8_075_V=val=MY.8=V +NM_USER__M_Y__8_V=val=MY.8V +NM_USER__M_Y__8_137_V=val=MY.8_V +NM_USER__M_Y___075=val=MY.= +NM_USER__M_Y___A_V=val=MY.AV +NM_USER__M_Y___137=val=MY._ +NM_USER_MY___AV=val=my.Av +NM_USER_MY___137V=val=my._v +NM_USER_MY__KEYS__1=val=my.keys.1 +NM_USER_MY__OTHER___K_E_Y__42=val=my.other.KEY.42 +NM_USER_MY__V_053=val=my.v+ +NM_USER_MY__V_137_137AL3=val=my.v__al3 +NM_USER_MY__VAL1= +NM_USER_MY__VAL2=val=my.val2 +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test User 1" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected new file mode 100644 index 0000000..b288c04 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected @@ -0,0 +1,17 @@ +BONDING_OPTS="mode=balance-rr downdelay=5 miimon=100 updelay=10" +TYPE=Bond +BONDING_MASTER=yes +HWADDR= +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=none +IPADDR=1.1.1.3 +PREFIX=24 +GATEWAY=1.1.1.1 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write Bond Main" +UUID=${UUID} +DEVICE=bond0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected new file mode 100644 index 0000000..a8ff8df --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected @@ -0,0 +1,9 @@ +HWADDR=31:33:33:37:BE:CD +MTU=1492 +TYPE=Ethernet +BRIDGING_OPTS="priority=50 path_cost=33" +BRIDGE_PORT_VLANS="1 untagged,2 pvid,4-4094 untagged" +NAME="Test Write Bridge Component" +UUID=${UUID} +ONBOOT=yes +BRIDGE=br0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected new file mode 100644 index 0000000..80e9692 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected @@ -0,0 +1,11 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write Permissions" +UUID=${UUID} +ONBOOT=yes +USERS="blahblah foobar asdfasdf" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected new file mode 100644 index 0000000..716eedc --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=auto +PAC_URL=https://wpad.neverland.org/wpad.dat +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Proxy Basic" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected new file mode 100644 index 0000000..0c2fa03 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected @@ -0,0 +1,19 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +ROUTING_RULE_1="priority 10 from 0.0.0.0/0 table 1" +ROUTING_RULE_2="priority 10 to 192.167.8.0/24 table 2" +ROUTING_RULE6_3="priority 10 from ::/0 table 10" +ROUTING_RULE6_4="priority 10 to 1:2:3::5/24 table 22" +ROUTING_RULE6_5="priority 10 to 1:3:3::5 table 55" +NAME="Test Write Routing Rules" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected new file mode 100644 index 0000000..2df1fbb --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected @@ -0,0 +1,9 @@ +CONNECTED_MODE=no +TYPE=InfiniBand +TEAM_PORT_CONFIG="{\"inf1\": {\"prio\": -10, \"sticky\": true}}" +NAME="Test Write Team Infiniband Port" +UUID=${UUID} +DEVICE=inf1 +ONBOOT=yes +TEAM_MASTER=team0 +DEVICETYPE=TeamPort diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected new file mode 100644 index 0000000..ff55cef --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected @@ -0,0 +1,6 @@ +TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}" +NAME="Test Write Team Port" +UUID=${UUID} +ONBOOT=yes +TEAM_MASTER=team0 +DEVICETYPE=TeamPort diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected new file mode 100644 index 0000000..9c2a1ff --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected @@ -0,0 +1,21 @@ +VLAN=yes +TYPE=Vlan +PHYSDEV=eth0 +VLAN_ID=444 +REORDER_HDR=yes +GVRP=no +MVRP=no +HWADDR= +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write VLAN reorder_hdr" +UUID=${UUID} +ONBOOT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected new file mode 100644 index 0000000..ccec2b8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected @@ -0,0 +1,19 @@ +ESSID=MySSID +MODE=Ap +CHANNEL=196 +MAC_ADDRESS_RANDOMIZATION=default +AP_ISOLATION=yes +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi AP Mode" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected new file mode 100644 index 0000000..ddbd986 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected @@ -0,0 +1,18 @@ +ESSID="Test SSID" +MODE=Managed +BAND=a +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi Band A" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected new file mode 100644 index 0000000..495a24d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected @@ -0,0 +1,17 @@ +ESSID="Test SSID" +SSID_HIDDEN=yes +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi Hidden" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected new file mode 100644 index 0000000..aec6918 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected @@ -0,0 +1,18 @@ +MACADDR=random +ESSID="Test SSID" +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=always +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi MAC always" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected new file mode 100644 index 0000000..9d47163 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected @@ -0,0 +1,17 @@ +ESSID="Test SSID" +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi MAC default" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected new file mode 100644 index 0000000..43c07dd --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected @@ -0,0 +1,18 @@ +MACADDR=permanent +ESSID="Test SSID" +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=never +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi MAC missing" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected new file mode 100644 index 0000000..21f2e2d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected @@ -0,0 +1,18 @@ +MACADDR=permanent +ESSID="Test SSID" +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=never +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wi-Fi MAC never" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected new file mode 100644 index 0000000..d3db19b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected @@ -0,0 +1,16 @@ +ESSID=blahblah +MODE=Managed +KEY_MGMT=IEEE8021X +SECURITYMODE=leap +IEEE_8021X_IDENTITY="Bill Smith" +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write Wifi LEAP" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected new file mode 100644 index 0000000..32db726 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected @@ -0,0 +1,15 @@ +ESSID=blahblah104 +MODE=Managed +SECURITYMODE=open +DEFAULTKEY=1 +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write Wifi WEP 104 ASCII" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected new file mode 100644 index 0000000..214f5da --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg off speed 10 duplex half" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wired Auto-Negotiate" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected new file mode 100644 index 0000000..cd8fc96 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected @@ -0,0 +1,22 @@ +HWADDR=31:33:33:37:BE:CD +MTU=1492 +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=none +IPADDR=1.1.1.3 +PREFIX=24 +IPADDR1=1.1.1.5 +PREFIX1=24 +GATEWAY=1.1.1.1 +DNS1=4.2.2.1 +DNS2=4.2.2.2 +DOMAIN="foobar.com lab.foobar.com" +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +ACD_TIMEOUT=400 +ARPING_WAIT=1 +IPV6INIT=no +NAME="Test Write Wired Static Routes" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected new file mode 100644 index 0000000..de66dcd --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="wol umgs sopass 00:00:00:11:22:33" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wired Wake-on-LAN" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected new file mode 100644 index 0000000..9499278 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected @@ -0,0 +1,13 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +MATCH_DRIVER="!virtio e1000e" +MATCH_INTERFACE_NAME="ens* eth\\ 1? !veth*" +MATCH_KERNEL_COMMAND_LINE=!ip= +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write Wired with Match setting" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected new file mode 100644 index 0000000..44eb777 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected @@ -0,0 +1,23 @@ +VLAN=yes +TYPE=Vlan +PHYSDEV=eth9 +VLAN_ID=43 +REORDER_HDR=yes +GVRP=yes +VLAN_FLAGS=LOOSE_BINDING +MVRP=no +VLAN_INGRESS_PRIORITY_MAP=0:1,2:5 +VLAN_EGRESS_PRIORITY_MAP=3:1,12:3,14:7 +HWADDR= +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=none +IPADDR=192.168.43.149 +PREFIX=24 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Vlan test-vlan-interface" +UUID=${UUID} +DEVICE=vlan43 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 new file mode 100644 index 0000000..4f9d645 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=aliasem0 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 new file mode 100644 index 0000000..37c0df1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 @@ -0,0 +1,2 @@ +DEVICE=aliasem0:1 +IPADDR=192.168.1.6 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 new file mode 100644 index 0000000..0c3b6a7 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 @@ -0,0 +1,2 @@ +DEVICE=aliasem0:2 +IPADDR=192.168.1.9 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 new file mode 100644 index 0000000..5df3f15 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 @@ -0,0 +1,2 @@ +DEVICE=aliasem0:99 +IPADDR=192.168.1.99 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 new file mode 100644 index 0000000..f6fefc0 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=aliasem1 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 new file mode 100644 index 0000000..2197161 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 @@ -0,0 +1,2 @@ +# bad, no DEVICE +IPADDR=192.168.1.12 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 new file mode 100644 index 0000000..92ed461 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=aliasem2 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 new file mode 100644 index 0000000..ce0f23b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 @@ -0,0 +1,3 @@ +# bad: wrong DEVICE +DEVICE=aliasem0:1 +IPADDR=192.168.1.20 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 new file mode 100644 index 0000000..b7bdd78 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 @@ -0,0 +1,11 @@ +TYPE=Ethernet +DEVICE=aliasem0 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 new file mode 100644 index 0000000..5e15187 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 @@ -0,0 +1,4 @@ +DEVICE=aliasem3:1 +IPADDR=192.168.1.6 +DEFROUTE=yes +GATEWAY=192.168.1.1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected new file mode 100644 index 0000000..56e233c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected @@ -0,0 +1,41 @@ +TYPE=Ethernet +DCB=yes +DCB_APP_FCOE_ENABLE=yes +DCB_APP_FCOE_ADVERTISE=yes +DCB_APP_FCOE_WILLING=yes +DCB_APP_FCOE_PRIORITY=5 +DCB_APP_FCOE_MODE=fabric +DCB_APP_ISCSI_ENABLE=yes +DCB_APP_ISCSI_ADVERTISE=yes +DCB_APP_ISCSI_WILLING=yes +DCB_APP_ISCSI_PRIORITY=1 +DCB_APP_FIP_ENABLE=yes +DCB_APP_FIP_ADVERTISE=yes +DCB_APP_FIP_WILLING=yes +DCB_APP_FIP_PRIORITY=3 +DCB_PFC_ENABLE=yes +DCB_PFC_ADVERTISE=yes +DCB_PFC_WILLING=yes +DCB_PFC_UP=11010110 +DCB_PG_ENABLE=yes +DCB_PG_ADVERTISE=yes +DCB_PG_WILLING=yes +DCB_PG_ID=4f6f173f +DCB_PG_PCT=10,20,15,10,2,3,35,5 +DCB_PG_UPPCT=10,20,30,40,50,10,0,25 +DCB_PG_STRICT=10110001 +DCB_PG_UP2TC=34721056 +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME=dcb-test +UUID=${UUID} +DEVICE=eth0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 new file mode 100644 index 0000000..ecb36c3 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 @@ -0,0 +1,14 @@ +DNS1="192.0.2.1" +IPADDR="102.0.2.2" +GATEWAY="192.0.2.1" +NETMASK="255.254.0.0" +BOOTPROTO="static" +DEVICE="eth1" +ONBOOT="yes" +IPV6INIT="yes" + +#bogus +PREFIX1=25 +NETMASK0=255.255.0.0 + +#end diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected new file mode 100644 index 0000000..87493ac --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected @@ -0,0 +1,22 @@ +DNS1=192.0.2.1 +IPADDR=102.0.2.2 +GATEWAY=192.0.2.1 +NETMASK=255.254.0.0 +DEVICE=eth1 +ONBOOT=yes +IPV6INIT=yes + +#bogus + +#end +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +PREFIX=15 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +NAME="System netmask-1" +UUID=${UUID} diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected new file mode 100644 index 0000000..ead3a04 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected @@ -0,0 +1,13 @@ +ESSID=blahblah +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="random wifi connection" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected new file mode 100644 index 0000000..7bc1ae6 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected @@ -0,0 +1,15 @@ +ESSID=SomeSSID +MODE=Managed +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="random wifi connection 2" +UUID=${UUID} +ONBOOT=yes +USERS=superman +DEFAULTKEY=1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected new file mode 100644 index 0000000..b01372a --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected @@ -0,0 +1,13 @@ +VLAN=yes +PHYSDEV=enp31s0f1 +VLAN_ID=142 +REORDER_HDR=yes +GVRP=no +MVRP=no +HWADDR= +NAME=team-slave-enp31s0f1-142 +UUID=74f435bb-ede4-415a-9d48-f580b60eba04 +DEVICE=enp31s0f1-142 +ONBOOT=no +TEAM_MASTER=team142 +DEVICETYPE=TeamPort diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type new file mode 100644 index 0000000..8d295c7 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type @@ -0,0 +1,7 @@ +DEVICE=bond0 +NM_CONTROLLED=yes +TYPE=Ethernet +BONDING_OPTS="miimon=213 mode=4 lacp_rate=1" +BONDING_MASTER=yes +ONBOOT=yes +BOOTPROTO=none diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main new file mode 100644 index 0000000..4c4834d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main @@ -0,0 +1,5 @@ +DEVICE=bond0 +ONBOOT=no +TYPE=Bond +BOOTPROTO=dhcp +BONDING_OPTS="miimon=100" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric new file mode 100644 index 0000000..9989f58 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric @@ -0,0 +1,5 @@ +DEVICE=bond0 +ONBOOT=no +TYPE=Bond +BOOTPROTO=dhcp +BONDING_OPTS="mode=4 miimon=100" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave new file mode 100644 index 0000000..c0de645 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave @@ -0,0 +1,6 @@ +DEVICE=eth0 +HWADDR=00:22:15:59:62:97 +ONBOOT=no +MASTER=bond0 +# This should be ignored +BOOTPROTO=dhcp diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib new file mode 100644 index 0000000..5bd9a3a --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib @@ -0,0 +1,7 @@ +TYPE=InfiniBand +DEVICE=ib0 +HWADDR=80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22 +ONBOOT=no +MASTER=bond0 +# This should be ignored +BOOTPROTO=dhcp diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component new file mode 100644 index 0000000..24b5122 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component @@ -0,0 +1,5 @@ +DEVICE=eth0 +HWADDR=00:22:15:59:62:97 +ONBOOT=no +BRIDGE=br0 +BRIDGING_OPTS="priority=28 hairpin_mode=1 path_cost=100" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main new file mode 100644 index 0000000..1788efe --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main @@ -0,0 +1,8 @@ +DEVICE=br0 +ONBOOT=no +TYPE=Bridge +BOOTPROTO=dhcp +STP=on +DELAY=2 +BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24 vlan_filtering=1 default_pvid=99" +MACADDR=00:16:41:11:22:33 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp new file mode 100644 index 0000000..7135cd4 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp @@ -0,0 +1,5 @@ +DEVICE=br0 +ONBOOT=no +TYPE=Bridge +BOOTPROTO=dhcp + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb new file mode 100644 index 0000000..6e6589d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb @@ -0,0 +1,43 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +# dcb global switch +# use DCB=no to turn it off +DCB=yes + +# application settings +DCB_APP_FCOE_PRIORITY=7 +DCB_APP_FCOE_ENABLE=yes +DCB_APP_FCOE_ADVERTISE=yes +DCB_APP_FCOE_WILLING=yes + +DCB_APP_ISCSI_PRIORITY=6 +DCB_APP_ISCSI_ENABLE=yes +DCB_APP_ISCSI_ADVERTISE=yes +DCB_APP_ISCSI_WILLING=yes + +DCB_APP_FIP_PRIORITY=2 +DCB_APP_FIP_ENABLE=yes +DCB_APP_FIP_ADVERTISE=yes +DCB_APP_FIP_WILLING=yes + +# priority group settings +DCB_PG_UP2TC=76543210 +DCB_PG_PCT=25,0,0,75,0,0,0,0 +DCB_PG_ID=0000111f +DCB_PG_UPPCT=5,10,30,25,10,50,5,0 +DCB_PG_STRICT=00110101 +DCB_PG_ENABLE=yes +DCB_PG_ADVERTISE=yes +DCB_PG_WILLING=yes + +# priority flow control settings +DCB_PFC_UP=10011010 +DCB_PFC_ENABLE=yes +DCB_PFC_ADVERTISE=yes +DCB_PFC_WILLING=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans new file mode 100644 index 0000000..2f46a08 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_STRICT=02030101 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent new file mode 100644 index 0000000..0c5d7b8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_PCT=25,0,0,75,0,0,110,0 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints new file mode 100644 index 0000000..8334999 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_UP2TC=96543210 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities new file mode 100644 index 0000000..5bdcb63 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities @@ -0,0 +1,7 @@ +TYPE=Ethernet +DEVICE=eth0 +DCB=yes +DCB_APP_FCOE_ENABLE=yes +DCB_APP_ISCSI_ENABLE=yes +DCB_APP_FIP_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 new file mode 100644 index 0000000..95eb7c5 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_PCT=25,0,0,3,75,0,25,0 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans new file mode 100644 index 0000000..fbe1cbb --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_STRICT=0111010 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent new file mode 100644 index 0000000..07ba6b6 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_PCT=25,0,0,75,0,0,0 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints new file mode 100644 index 0000000..05ae39c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints @@ -0,0 +1,12 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +GATEWAY=1.1.1.1 + +DCB=yes +DCB_PG_UP2TC=7654321 +DCB_PG_ENABLE=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options new file mode 100644 index 0000000..cea2471 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options @@ -0,0 +1,16 @@ +TYPE=Ethernet +DEVICE=eth2 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DNS1=10.2.0.4 +DOMAIN="lorem.com ipsum.org dolor.edu" +RES_OPTIONS="ndots:3 single-request-reopen" +IPV6_RES_OPTIONS="inet6" +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR="1001:abba::1234/56" +IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric new file mode 100644 index 0000000..cde1c28 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric @@ -0,0 +1,6 @@ +TYPE=Ethernet +DEVICE=eth0 +DCB=yes +DCB_APP_FCOE_ENABLE=yes +DCB_APP_FCOE_MODE=fabric + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn new file mode 100644 index 0000000..20c0845 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn @@ -0,0 +1,6 @@ +TYPE=Ethernet +DEVICE=eth0 +DCB=yes +DCB_APP_FCOE_ENABLE=yes +DCB_APP_FCOE_MODE=vn2vn + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft new file mode 100644 index 0000000..d3e4edc --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft @@ -0,0 +1,10 @@ +# Generated by dracut initrd +DEVICE="ibft0" +ONBOOT=yes +NETBOOT=yes +UUID="2aa8c19d-9744-4ca4-b74f-c37e73f2918e" +BOOTPROTO=ibft +HWADDR="00:33:21:98:b9:f0" +TYPE=Ethernet +NAME="ibft0" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband new file mode 100644 index 0000000..6c36f1d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband @@ -0,0 +1,8 @@ +TYPE=InfiniBand +DEVICE=ib0 +HWADDR=80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22 +CONNECTED_MODE=yes +MTU=65520 +IPADDR=192.168.2.2 +NETMASK=255.255.255.0 +GATEWAY=192.168.2.1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected new file mode 100644 index 0000000..cae51b8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected @@ -0,0 +1,11 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6_DISABLED=yes +IPV6INIT=no +NAME="Test Write Wired Disabled IP6" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal new file mode 100644 index 0000000..63bcc84 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal @@ -0,0 +1,4 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +DEVICE=eth0 +HWADDR=00:16:41:11:22:33 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables new file mode 100644 index 0000000..1b10cdd --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables @@ -0,0 +1,4 @@ +DEVICE=eth0 +HWADDR_BLACKLIST="00:16:41:11:22:88 00:16:41:11:22:99 XX:aa:invalid 6A:5D:5A:FA:DD:F0" +AUTOCONNECT_RETRIES=100 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled new file mode 100644 index 0000000..a38f1f5 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled @@ -0,0 +1,9 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:f8:9f +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=no + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized new file mode 100644 index 0000000..58263ec --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized @@ -0,0 +1,6 @@ +TYPE=IPoAC +NAME=PigeonNet +DEVICE=ipoac0 +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip new file mode 100644 index 0000000..b5028da --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip @@ -0,0 +1,3 @@ +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no new file mode 100644 index 0000000..b949171 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no @@ -0,0 +1,5 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +ONBOOT=no + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions new file mode 100644 index 0000000..5b413aa --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions @@ -0,0 +1,8 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERS="dcbw ssmith johnny5" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic new file mode 100644 index 0000000..e2cf6c2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=no + +# proxy configuration +PROXY_METHOD=auto +PAC_URL=http://wpad.mycompany.com/wpad.dat +BROWSER_ONLY=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore new file mode 100644 index 0000000..a2581db --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore @@ -0,0 +1,12 @@ +# this ifcfg-file represents a "sit" type without explicit TYPE. +# Such connection types are not supported by NetworkManager and +# the connection should be ignored based on the presence of +# IPV6TUNNELIPV4. + +DEVICE=sit1 +BOOTPROTO=none +ONBOOT=yes +IPV6INIT=yes +IPV6TUNNELIPV4=5.4.3.6 +IPV6TUNNELIPV4LOCAL=172.17.1.9 +IPV6ADDR=2001:470:2:3:4::2/64 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov new file mode 100644 index 0000000..142f56e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov @@ -0,0 +1,19 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=no +NAME=ethernet-sriov +UUID=acc703b8-e751-44ce-b456-1550bdf2057e +SRIOV_TOTAL_VFS=16 +SRIOV_AUTOPROBE_DRIVERS=0 +SRIOV_VF15="max-tx-rate=200 mac=01:23:45:67:89:ab vlans=2" +SRIOV_VF12="trust=false min-tx-rate=100 vlans=1.200.ad" +SRIOV_VF3="mac=55:44:33:22:11:00 spoof-check=true" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected new file mode 100644 index 0000000..c882c47 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected @@ -0,0 +1,18 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +SRIOV_TOTAL_VFS=64 +SRIOV_AUTOPROBE_DRIVERS=1 +SRIOV_VF2="mac=55:55:55:55:55:55 vlans=3.10.ad;10" +SRIOV_VF19=spoof-check=true +BOOTPROTO=none +IPADDR=1.1.1.3 +PREFIX=24 +GATEWAY=1.1.1.1 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write SR-IOV config" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy new file mode 100644 index 0000000..2173729 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy @@ -0,0 +1,12 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +DEVICE=eth0 +HWADDR=00:16:41:11:22:33 +NAME="test-static-routes-legacy" +TYPE=Ethernet +BOOTPROTO=dhcp +DEFROUTE=yes +UUID=ba60d05a-7898-820d-c2db-427a88f8f2a5 +ONBOOT=yes +IPV6INIT=no +PEERDNS=yes +PEERROUTES=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected new file mode 100644 index 0000000..a28c5c1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected @@ -0,0 +1,12 @@ +HWADDR=00:16:41:11:22:33 +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME=test-static-routes-legacy +UUID=ba60d05a-7898-820d-c2db-427a88f8f2a5 +DEVICE=eth0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc new file mode 100644 index 0000000..d0a3c25 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc @@ -0,0 +1,16 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:55 +BOOTPROTO=none +ONBOOT=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=no +QDISC1="root fq_codel" +FILTER1="parent 1234: matchall action simple sdata Hello" +NAME=ethernet-tc +UUID=a42c8d4e-11a2-4144-92d2-5cbce8c6b2c4 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected new file mode 100644 index 0000000..a67ca59 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected @@ -0,0 +1,16 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +QDISC1="parent 2468:2 pfifo_fast" +FILTER1="parent 1234: matchall action simple sdata Hello" +BOOTPROTO=none +IPADDR=1.1.1.3 +PREFIX=24 +GATEWAY=1.1.1.1 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write TC config" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 new file mode 100644 index 0000000..209447b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 @@ -0,0 +1,6 @@ +DEVICE=team0 +ONBOOT=no +DEVICETYPE=Team +BOOTPROTO=dhcp +TEAM_CONFIG="{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 new file mode 100644 index 0000000..26e448c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 @@ -0,0 +1,5 @@ +DEVICE=team0 +ONBOOT=no +BOOTPROTO=dhcp +TEAM_CONFIG="{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid new file mode 100644 index 0000000..41c0f25 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid @@ -0,0 +1,4 @@ +DEVICE=team0 +ONBOOT=no +BOOTPROTO=dhcp +TEAM_CONFIG="{ foobar }x" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 new file mode 100644 index 0000000..80355c2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 @@ -0,0 +1,5 @@ +TYPE=Ethernet +TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}" +DEVICE=p4p1 +TEAM_MASTER=team0 +DEVICETYPE=TeamPort diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 new file mode 100644 index 0000000..4284737 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 @@ -0,0 +1,4 @@ +TYPE=Ethernet +TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}" +DEVICE=p4p1 +TEAM_MASTER=team0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config new file mode 100644 index 0000000..04df693 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config @@ -0,0 +1,4 @@ +TYPE=Ethernet +DEVICE=p4p1 +TEAM_MASTER=team0 +DEVICETYPE=TeamPort diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized new file mode 100644 index 0000000..18da66c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized @@ -0,0 +1,7 @@ +TYPE=PPP-over-InfiniBand +NAME=U Can't Touch This +DEVICE=pppoipoib0 +HWADDR=00:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 new file mode 100644 index 0000000..af62dcf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 @@ -0,0 +1,8 @@ +DEVICE=eth0 +HWADDR=00:16:41:11:22:33 + +# Variable with only single character (double quote) +NAME=" + +# Variable with only single character (quote) +ZONE=' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 new file mode 100644 index 0000000..5ff8789 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 @@ -0,0 +1,14 @@ +VLAN=yes +TYPE=Vlan +DEVICE=super-vlan +VLAN_ID=44 +PHYSDEV=eth9 +REORDER_HDR=yes +GVRP=no +VLAN_FLAGS="GVRP LOOSE_BINDING" +VLAN_INGRESS_PRIORITY_MAP=0:1,2:5 +VLAN_EGRESS_PRIORITY_MAP=12:3,14:7,3:1 +ONBOOT=yes +BOOTPROTO=static +IPADDR=192.168.43.149 +NETMASK=255.255.255.0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 new file mode 100644 index 0000000..2c01be7 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 @@ -0,0 +1,10 @@ +VLAN=yes +TYPE=Vlan +DEVICE=super-vlan +VLAN_ID=44 +PHYSDEV=eth9 +VLAN_FLAGS="GVRP LOOSE_BINDING" +ONBOOT=yes +BOOTPROTO=static +IPADDR=192.168.43.149 +NETMASK=255.255.255.0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface new file mode 100644 index 0000000..d8d9193 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface @@ -0,0 +1,11 @@ +VLAN=yes +TYPE=Vlan +DEVICE=vlan43 +PHYSDEV=eth9 +VLAN_FLAGS=GVRP,LOOSE_BINDING +VLAN_INGRESS_PRIORITY_MAP=0:1,2:5 +VLAN_EGRESS_PRIORITY_MAP=12:3,14:7,3:1 +ONBOOT=yes +BOOTPROTO=static +IPADDR=192.168.43.149 +NETMASK=255.255.255.0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device new file mode 100644 index 0000000..4ba06f2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device @@ -0,0 +1,4 @@ +VLAN=yes +TYPE=Vlan +DEVICE=eth0.9 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid new file mode 100644 index 0000000..622d41e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid @@ -0,0 +1,6 @@ +VLAN=yes +TYPE=Vlan +PHYSDEV=eth9 +VLAN_ID=43 +ONBOOT=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev new file mode 100644 index 0000000..446c2a6 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev @@ -0,0 +1,6 @@ +VLAN=yes +TYPE=Vlan +DEVICE=vlan0.3 +PHYSDEV=eth0 +VLAN_ID=3 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 new file mode 100644 index 0000000..0dc5397 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 @@ -0,0 +1,6 @@ +VLAN=yes +TYPE=Vlan +DEVICE=vlan0.3 +PHYSDEV=eth0 +VLAN_ID=3 +REORDER_HDR=0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 new file mode 100644 index 0000000..d98a9d3 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 @@ -0,0 +1,6 @@ +VLAN=yes +TYPE=Vlan +DEVICE=vlan0.3 +PHYSDEV=eth0 +VLAN_ID=3 +VLAN_FLAGS="LOOSE_BINDING,NO_REORDER_HDR" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces new file mode 100644 index 0000000..4a31e47 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces @@ -0,0 +1,11 @@ +DEVICE="vlan201" +ONBOOT=yes +NETBOOT=yes +BOOTPROTO=none +IPADDR="10.130.70.7" +NETMASK="255.255.0.0" +TYPE=Vlan +NAME="vlan201" +VLAN=yes +PHYSDEV="enccw0.0.fb00" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a new file mode 100644 index 0000000..4328611 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +BAND=a +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch new file mode 100644 index 0000000..8905ba0 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch @@ -0,0 +1,9 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=9 +BAND=a +MODE=Managed + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch new file mode 100644 index 0000000..e02e21a --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch @@ -0,0 +1,9 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=183 +BAND=bg +MODE=Managed + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap new file mode 100644 index 0000000..d9c95cc --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap @@ -0,0 +1,17 @@ +ESSID="sdasdsdg" +MODE=Managed +KEY_MGMT=IEEE8021X +CIPHER_GROUP="WEP40 WEP104" +TYPE=Wireless +IEEE_8021X_EAP_METHODS=LEAP +IEEE_8021X_IDENTITY="bill smith" +BOOTPROTO=dhcp +DEFROUTE=yes +PEERDNS=yes +PEERROUTES=yes +IPV4_FAILURE_FATAL=yes +IPV6INIT=no +NAME="Test Dynamic WEP LEAP" +UUID=aca7a23c-d934-49a3-8bfb-ad66f846c57b +ONBOOT=yes +USERS=dcbw diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden new file mode 100644 index 0000000..08b9a73 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden @@ -0,0 +1,10 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes +ESSID=blahblah +MODE=Managed +SSID_HIDDEN=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap new file mode 100644 index 0000000..50f4fd3 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap @@ -0,0 +1,17 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +KEY_MGMT=IEEE8021X +SECURITYMODE=LEAP +IEEE_8021X_IDENTITY="Bill Smith" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent new file mode 100644 index 0000000..991ba67 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent @@ -0,0 +1,17 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +KEY_MGMT=IEEE8021X +SECURITYMODE=LEAP +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_PASSWORD_FLAGS=user diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask new file mode 100644 index 0000000..5bb02b5 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask @@ -0,0 +1,17 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +KEY_MGMT=IEEE8021X +SECURITYMODE=LEAP +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_PASSWORD_FLAGS="user ask" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always new file mode 100644 index 0000000..9dcb5bf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always @@ -0,0 +1,7 @@ +TYPE=Wireless +DEVICE=eth2 +BOOTPROTO=dhcp +ESSID=blahblah +ONBOOT=yes +MAC_ADDRESS_RANDOMIZATION=always + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default new file mode 100644 index 0000000..3709c49 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default @@ -0,0 +1,6 @@ +TYPE=Wireless +DEVICE=eth2 +BOOTPROTO=dhcp +ESSID=blahblah +ONBOOT=yes +MAC_ADDRESS_RANDOMIZATION=default diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing new file mode 100644 index 0000000..f896eb0 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing @@ -0,0 +1,6 @@ +TYPE=Wireless +DEVICE=eth2 +BOOTPROTO=dhcp +ESSID=blahblah +ONBOOT=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never new file mode 100644 index 0000000..aa12d74 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never @@ -0,0 +1,6 @@ +TYPE=Wireless +DEVICE=eth2 +BOOTPROTO=dhcp +ESSID=blahblah +ONBOOT=yes +MAC_ADDRESS_RANDOMIZATION=never diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open new file mode 100644 index 0000000..b089103 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open @@ -0,0 +1,16 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +AUTOCONNECT_PRIORITY=-1 +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +IPV4_ROUTE_METRIC=104 +IPV6_ROUTE_METRIC=106 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto new file mode 100644 index 0000000..42ee200 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Auto +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex new file mode 100644 index 0000000..32d6334 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=0x626cxx +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex new file mode 100644 index 0000000..dcb46da --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=0x626c6168626c6168 +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex new file mode 100644 index 0000000..37bb085 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=0x626c6168626c6168626c6168626c6168626c6168626c6168626c6168626c6168AA +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted new file mode 100644 index 0000000..0b46acd --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID="foo\"bar\\foo\"bar\\foo\"bar\\foo\"bar\\1" +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted new file mode 100644 index 0000000..08496bb --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID="foo\"bar\\" +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe new file mode 100644 index 0000000..354046a --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe @@ -0,0 +1,5 @@ +TYPE=Wireless +DEVICE=wlan1 +ESSID=blahblah_owe +MODE=Managed +KEY_MGMT=OWE diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae new file mode 100644 index 0000000..68afbe9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae @@ -0,0 +1,5 @@ +TYPE=Wireless +DEVICE=wlan1 +ESSID=blahblah +MODE=Managed +KEY_MGMT=SAE diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep new file mode 100644 index 0000000..e1ce20d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep @@ -0,0 +1,14 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +SECURITYMODE=restricted diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii new file mode 100644 index 0000000..250efa1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii @@ -0,0 +1,14 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +SECURITYMODE=open diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii new file mode 100644 index 0000000..250efa1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii @@ -0,0 +1,14 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +SECURITYMODE=open diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc new file mode 100644 index 0000000..9d0bacf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc @@ -0,0 +1,15 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=11 +MODE=Ad-Hoc +RATE=auto +ONBOOT=no +USERCTL=yes +PEERDNS=no +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys new file mode 100644 index 0000000..2bc16b6 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys @@ -0,0 +1,18 @@ +ESSID="foobar" +MODE=Managed +TYPE=Wireless +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +UUID=9c4637bd-7600-40cc-9c24-13819c5bf5dd +ONBOOT=yes +HWADDR=00:16:BB:AA:CC:DD +WEP_KEY_FLAGS=user +PEERDNS=yes +PEERROUTES=yes +IPV6_PEERDNS=yes +IPV6_PEERROUTES=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap new file mode 100644 index 0000000..bdea6aa --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap @@ -0,0 +1,20 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=yes +ESSID=blahblah +MODE=Managed +RATE=auto +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TTLS +IEEE_8021X_IDENTITY="David Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_INNER_AUTH_METHODS=CHAP + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys new file mode 100644 index 0000000..cb4da43 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys @@ -0,0 +1,18 @@ +ESSID="foobar" +MODE=Managed +TYPE=Wireless +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +UUID=9c4637bd-7600-40cc-9c24-13819c5bf5dd +ONBOOT=yes +HWADDR=00:16:BB:AA:CC:DD +DEFAULTKEY=1 +PEERDNS=yes +PEERROUTES=yes +IPV6_PEERDNS=yes +IPV6_PEERROUTES=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase new file mode 100644 index 0000000..250efa1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase @@ -0,0 +1,14 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +SECURITYMODE=open diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls new file mode 100644 index 0000000..9a74bb4 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls @@ -0,0 +1,22 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=yes +ESSID=blahblah +MODE=Managed +RATE=auto +KEY_MGMT=WPA-EAP-SUITE-B-192 +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes +IEEE_8021X_EAP_METHODS=TLS +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls new file mode 100644 index 0000000..92aaeea --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls @@ -0,0 +1,25 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=yes +ESSID=blahblah +MODE=Managed +RATE=auto +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-EAP +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes +IEEE_8021X_EAP_METHODS=TLS +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls new file mode 100644 index 0000000..42ed1d6 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls @@ -0,0 +1,28 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ONBOOT=yes +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=yes +ESSID=blahblah +MODE=Managed +RATE=auto +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-EAP +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes +IEEE_8021X_EAP_METHODS=TTLS +IEEE_8021X_IDENTITY="Chuck Shumer" +IEEE_8021X_ANON_IDENTITY="anonymous" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_INNER_AUTH_METHODS=EAP-TLS +IEEE_8021X_INNER_CA_CERT=test_ca_cert.pem +IEEE_8021X_INNER_CLIENT_CERT=test1_key_and_cert.pem +IEEE_8021X_INNER_PRIVATE_KEY=test1_key_and_cert.pem + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk new file mode 100644 index 0000000..4ab45ae --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk @@ -0,0 +1,19 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-PSK +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes +CTCPROT=no-newline \ No newline at end of file diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 new file mode 100644 index 0000000..038b656 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 @@ -0,0 +1,19 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=ipsum +CHANNEL=6 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-PSK +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc new file mode 100644 index 0000000..c3cadbb --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc @@ -0,0 +1,17 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Ad-Hoc +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_GROUP=CCMP +CIPHER_PAIRWISE=CCMP +KEY_MGMT=WPA-PSK + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex new file mode 100644 index 0000000..2119ba8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex @@ -0,0 +1,19 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-PSK +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted new file mode 100644 index 0000000..2119ba8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted @@ -0,0 +1,19 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-PSK +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 new file mode 100644 index 0000000..2119ba8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 @@ -0,0 +1,19 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=1 +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no +CIPHER_PAIRWISE="TKIP CCMP" +CIPHER_GROUP="TKIP CCMP WEP40 WEP104" +KEY_MGMT=WPA-PSK +WPA_ALLOW_WPA=yes +WPA_ALLOW_WPA2=yes + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches new file mode 100644 index 0000000..70d69bf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches @@ -0,0 +1,17 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=PEAP +IEEE_8021X_IDENTITY="Jara Cimrman" +IEEE_8021X_PEAP_VERSION=1 +IEEE_8021X_INNER_AUTH_METHODS=GTC +IEEE_8021X_PASSWORD_FLAGS="user ask" +IEEE_8021X_SUBJECT_MATCH=server1.yourdomain.tld +IEEE_8021X_ALTSUBJECT_MATCHES="a.yourdomain.tld b.yourdomain.tld c.yourdomain.tld" +IEEE_8021X_PHASE2_SUBJECT_MATCH=server2.yourdomain.tld +IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES="x.yourdomain.tld y.yourdomain.tld" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw new file mode 100644 index 0000000..a543443 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TTLS +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_INNER_AUTH_METHODS=EAP-GTC +IEEE_8021X_PASSWORD_RAW=0408151623420001 +#IEEE_8021X_PIN=hallo1 +IEEE_8021X_PIN_FLAGS=0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc new file mode 100644 index 0000000..fad9277 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc @@ -0,0 +1,13 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TTLS +IEEE_8021X_IDENTITY="Bill Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_INNER_AUTH_METHODS=EAP-GTC +IEEE_8021X_PASSWORD_FLAGS=ask diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 new file mode 100644 index 0000000..27bcbbf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=PEAP +IEEE_8021X_IDENTITY="David Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_PEAP_VERSION=1 +IEEE_8021X_PEAP_FORCE_NEW_LABEL=yes +IEEE_8021X_INNER_AUTH_METHODS=MSCHAPV2 +IEEE_8021X_ANON_IDENTITY=somebody diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent new file mode 100644 index 0000000..052ab42 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TLS +IEEE_8021X_IDENTITY="David Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS=user diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always new file mode 100644 index 0000000..5deee06 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TLS +IEEE_8021X_IDENTITY="David Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem +IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS="user ask" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert new file mode 100644 index 0000000..2439747 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert @@ -0,0 +1,13 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +KEY_MGMT=IEEE8021X +IEEE_8021X_EAP_METHODS=TLS +IEEE_8021X_IDENTITY="David Smith" +IEEE_8021X_CA_CERT=test_ca_cert.pem +IEEE_8021X_PRIVATE_KEY=test_client.p12 +IEEE_8021X_PRIVATE_KEY_PASSWORD="test1" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on new file mode 100644 index 0000000..d0b5077 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on @@ -0,0 +1,22 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR=dead:beaf::1 +IPV6ADDR_SECONDARIES="dead:beaf::2/56" +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b +RES_OPTIONS= +ETHTOOL_OPTS="wol apgs sopass 00:11:22:33:44:55 autoneg on speed 100" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip new file mode 100644 index 0000000..e683db3 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip @@ -0,0 +1,6 @@ +TYPE=Ethernet +DEVICE=eth0 +BOOTPROTO=autoip +IPV4_FAILURE_FATAL=yes +PEERDNS=no +DEVTIMEOUT=2.6 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static new file mode 100644 index 0000000..61f8f42 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static @@ -0,0 +1,12 @@ +# IBM CTC +DEVICE=ctc0 +TYPE=CTC +BOOTPROTO=static +IPADDR=192.168.70.87 +GATEWAY=192.168.70.136 +NETMASK=255.255.255.0 +ONBOOT=yes +SUBCHANNELS=0.0.1b00,0.0.1b01 +NETTYPE=ctc +CTCPROT=0 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no new file mode 100644 index 0000000..fe8b15b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth4 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DEFROUTE=no + +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=no + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes new file mode 100644 index 0000000..3cf4323 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DEFROUTE=no + +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=no + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp new file mode 100644 index 0000000..5d36675 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp @@ -0,0 +1,19 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +PEERDNS=no +DHCP_FQDN=foo.bar +DHCP_HOSTNAME_FLAGS=6 +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPV6_AUTOCONF=no +IPV6INIT=yes +DHCPV6C=yes +DHCPV6_HOSTNAME_FLAGS=8 +DHCPV6_HOSTNAME=foo.bar diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip new file mode 100644 index 0000000..af254f9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip @@ -0,0 +1,26 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=no +DNS1=4.2.2.1 +DNS2=4.2.2.2 +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b + +# additional IPs +IPADDR=1.2.3.4 +PREFIX=24 +IPADDR1=9.8.7.6 +PREFIX1=16 +GATEWAY=1.1.1.1 + +IPV6ADDR="1001:abba::1234/56" +IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname new file mode 100644 index 0000000..cba380d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname @@ -0,0 +1,12 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=no +DHCP_HOSTNAME="svata-pulec" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only new file mode 100644 index 0000000..de03e04 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only @@ -0,0 +1,11 @@ +DEVICE="eth0" +ONBOOT=no +TYPE=Ethernet +DEFROUTE=yes +PEERDNS=yes +PEERROUTES=yes +IPV6INIT=yes +IPV6_AUTOCONF=no +DHCPV6C=yes +HWADDR=00:13:20:F5:F5:E4 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback new file mode 100644 index 0000000..d38e762 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback @@ -0,0 +1,12 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=no +DHCP_HOSTNAME="fully.qualified.domain" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway new file mode 100644 index 0000000..98d9105 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +PREFIX=24 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore new file mode 100644 index 0000000..bb81399 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore @@ -0,0 +1,8 @@ +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +IPV6INIT=no + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 new file mode 100644 index 0000000..db09afd --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR0=1.2.3.4 +PREFIX0=24 +IPADDR1=9.8.7.6 +PREFIX1=16 +IPADDR2=3.3.3.3 +PREFIX2=8 +GATEWAY=1.1.1.1 +ACD_TIMEOUT=2000 +ARPING_WAIT=1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 new file mode 100644 index 0000000..6972e27 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 @@ -0,0 +1,13 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +IPADDR2=9.8.7.6 +PREFIX2=16 +IPADDR3=3.3.3.3 +PREFIX3=8 +ACD_TIMEOUT=2000 +ARPING_WAIT=1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 new file mode 100644 index 0000000..d906599 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 @@ -0,0 +1,13 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR2=1.2.3.4 +PREFIX2=24 +IPADDR3=9.8.7.6 +PREFIX3=16 +IPADDR4=3.3.3.3 +PREFIX4=8 +ACD_TIMEOUT=2000 +ARPING_WAIT=1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 new file mode 100644 index 0000000..935267f --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 @@ -0,0 +1,13 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +IPADDR=1.2.3.4 +PREFIX=24 +IPADDR1=9.8.7.6 +PREFIX1=16 +IPADDR2=3.3.3.3 +PREFIX2=8 +ACD_TIMEOUT=2000 +ARPING_WAIT=1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual new file mode 100644 index 0000000..45db0e4 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual @@ -0,0 +1,19 @@ +# Intel Corporation 82567LM Gigabit Network Connection +TYPE=Ethernet +DEVICE=eth2 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DNS1=10.2.0.4 +DNS2=10.2.0.5 +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b +DOMAIN="lorem.com ipsum.org dolor.edu" +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR="1001:abba::1234/56" +IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96" +IPV6_FAILURE_FATAL=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only new file mode 100644 index 0000000..9418906 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only @@ -0,0 +1,14 @@ +# Intel Corporation 82567LM Gigabit Network Connection +TYPE=Ethernet +DEVICE=eth2 +HWADDR=00:11:22:33:44:ee +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DNS1=1:2:3:4::a +IPV6_DOMAIN="lorem.com ipsum.org dolor.edu" +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR="1001:abba::1234/56" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 new file mode 100644 index 0000000..95f8a8e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 @@ -0,0 +1,16 @@ +IPADDR= +GATEWAY= +NETMASK= +BOOTPROTO=static +TYPE=Ethernet +DEVICE=eth2 +HWADDR=00:11:22:33:44:ee +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +DNS1=1:2:3:4::a +DOMAIN="lorem.com ipsum.org dolor.edu" +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR="1001:abba::1234/56" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default new file mode 100644 index 0000000..12d5b5e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default @@ -0,0 +1,11 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth4 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=dhcp +ONBOOT=yes +USERCTL=yes +NM_CONTROLLED=yes +PEERDNS=yes +IPV6INIT=yes +IPV6_AUTOCONF=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n new file mode 100644 index 0000000..2190a99 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n @@ -0,0 +1,13 @@ +# GATEWAY is obsolete, but we read it for backwards compatibility + +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes + +# manual IP +IPADDR1=1.2.3.4 +PREFIX1=24 +GATEWAY1=1.1.1.1 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static new file mode 100644 index 0000000..4719de2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static @@ -0,0 +1,13 @@ +# IBM QETH +DEVICE=eth1 +BOOTPROTO=static +IPADDR=192.168.70.87 +NETMASK=255.255.255.0 +ONBOOT=yes +NETTYPE=qeth +SUBCHANNELS=0.0.0600,0.0.0601,0.0.0602 +TYPE=Ethernet +PORTNAME=OSAPORT +OPTIONS='layer2=1 portno=0' +MACADDR=02:00:00:23:65:1a + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip new file mode 100644 index 0000000..5098aef --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip @@ -0,0 +1,19 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=shared +ONBOOT=no +IPV6INIT=yes + +# additional IPs +IPADDR=10.20.30.5 +PREFIX=24 +GATEWAY=1.1.1.1 + +# these are ignored for shared method +IPADDR1=6.7.8.9 +PREFIX1=16 +IPADDR2=3.3.3.3 +PREFIX2=24 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static new file mode 100644 index 0000000..34acf9f --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static @@ -0,0 +1,22 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR=dead:beaf::1 +IPV6ADDR_SECONDARIES="dead:beaf::2/56" +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b +RES_OPTIONS= +IPV6_RES_OPTIONS= diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto new file mode 100644 index 0000000..a01f655 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto @@ -0,0 +1,16 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=static +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +RES_OPTIONS= diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 new file mode 100644 index 0000000..0799903 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=172.16.3.4 +GATEWAY=172.16.3.1 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 new file mode 100644 index 0000000..688143c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +GATEWAY=192.168.1.1 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 new file mode 100644 index 0000000..0433c62 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 @@ -0,0 +1,14 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=10.11.12.13 +GATEWAY=10.0.0.1 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes new file mode 100644 index 0000000..7faf49b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy new file mode 100644 index 0000000..7faf49b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy @@ -0,0 +1,15 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +IPV6INIT=no +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt new file mode 100644 index 0000000..861dd41 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt @@ -0,0 +1,22 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR=dead:beaf::1 +IPV6ADDR_SECONDARIES="dead:beaf::2/56" +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b +RES_OPTIONS= +ETHTOOL_OPTS="unknown1 wol apgs sopass 00:11:22:33:44:55 unkwnown2 opt2 unknown3" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan new file mode 100644 index 0000000..1dfc9a4 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan @@ -0,0 +1,22 @@ +# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile) +TYPE=Ethernet +DEVICE=eth0 +HWADDR=00:11:22:33:44:ee +BOOTPROTO=none +ONBOOT=yes +USERCTL=yes +MTU=1492 +NM_CONTROLLED=yes +DNS1=4.2.2.1 +DNS2=4.2.2.2 +IPADDR=192.168.1.5 +NETMASK=255.255.255.0 +GATEWAY=192.168.1.1 +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR=dead:beaf::1 +IPV6ADDR_SECONDARIES="dead:beaf::2/56" +DNS3=1:2:3:4::a +DNS4=1:2:3:4::b +RES_OPTIONS= +ETHTOOL_OPTS="speed 100 duplex full wol apgs sopass 00:11:22:33:44:55 autoneg off" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 new file mode 100644 index 0000000..2af1daf --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 @@ -0,0 +1,8 @@ +FOO='val + bar=3' +wrong line + F2=b + F3='b +XXA=adf' + XXA2=val2 +' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected new file mode 100644 index 0000000..7f15407 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected @@ -0,0 +1,12 @@ +FOO= +#NM: FOO='val +bar= +#NM: bar=3' +#NM: wrong line + F2=b +F3= +#NM: F3='b +XXA= +#NM: XXA=adf' + XXA2=val2 +#NM: ' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 new file mode 100644 index 0000000..850149b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 @@ -0,0 +1,3 @@ +FOO=' +BAR=a +' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected new file mode 100644 index 0000000..6ec53c1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected @@ -0,0 +1,4 @@ +FOO= +#NM: FOO=' +BAR=a +#NM: ' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 new file mode 100644 index 0000000..3d198e0 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 @@ -0,0 +1,3 @@ +FOO=' +BAR=" +' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected new file mode 100644 index 0000000..d2b27eb --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected @@ -0,0 +1,5 @@ +FOO= +#NM: FOO=' +BAR= +#NM: BAR=" +#NM: ' diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 new file mode 100644 index 0000000..f834464 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 @@ -0,0 +1,36 @@ +# test what happens with multiple defines of a name. +# +# Note that svGetValue() will return "l4x", which +# isn't correct in terms of shell-parsing. But we +# don't consider only line-by-line, thus this is +# expected. +# +# Also note that setting IPADDR will replace the last +# occurrence, and delete all previous once. + +#L1 +IPADDR=l2 + +#L2 +IPADDR=l3 + +METRIC1='' +METRIC2=$'\U0x' +METRIC3=$'x\U0' + +#L4 +IPADDR=' +IPADDR=l4x +' + +#Lx-1 +IPADDR2=not-visible +#Lx-2 +IPADDR2='invalid +#Lx-3 + +#Ly-1 +IPADDR3='invalid +#Ly-2 +IPADDR3=name3-value +#Ly-3 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected new file mode 100644 index 0000000..040ddc9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected @@ -0,0 +1,31 @@ +# test what happens with multiple defines of a name. +# +# Note that svGetValue() will return "l4x", which +# isn't correct in terms of shell-parsing. But we +# don't consider only line-by-line, thus this is +# expected. +# +# Also note that setting IPADDR will replace the last +# occurrence, and delete all previous once. + +#L1 + +#L2 + +METRIC1='' +METRIC2=$'\U0x' +METRIC3=$'x\U0' + +#L4 +IPADDR=set-by-test1 +#NM: ' + +#Lx-1 +#Lx-2 +IPADDR2=set-by-test2 +#Lx-3 + +#Ly-1 +#Ly-2 +IPADDR3=set-by-test3 +#Ly-3 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected new file mode 100644 index 0000000..4260857 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg on ; -K net0 rxvlan off tx on" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +NAME="Test Write Wired Auto-Negotiate" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap new file mode 100644 index 0000000..6936f2e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap @@ -0,0 +1,2 @@ +IEEE_8021X_PASSWORD="foobar baz" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap new file mode 100644 index 0000000..fe78177 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap @@ -0,0 +1 @@ +IEEE_8021X_PASSWORD="foobarblah" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae new file mode 100644 index 0000000..5a9569e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae @@ -0,0 +1 @@ +WPA_PSK="The king is dead." diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep new file mode 100644 index 0000000..0bd766c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep @@ -0,0 +1 @@ +KEY=0123456789abcdef0123456789 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii new file mode 100644 index 0000000..f5d532b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii @@ -0,0 +1 @@ +KEY1=s:LoremIpsumSit diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii new file mode 100644 index 0000000..b0d1470 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii @@ -0,0 +1 @@ +KEY1=s:Lorem diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc new file mode 100644 index 0000000..0bd766c --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc @@ -0,0 +1 @@ +KEY=0123456789abcdef0123456789 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap new file mode 100644 index 0000000..6936f2e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap @@ -0,0 +1,2 @@ +IEEE_8021X_PASSWORD="foobar baz" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase new file mode 100644 index 0000000..d45c0ea --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase @@ -0,0 +1 @@ +KEY_PASSPHRASE1="foobar222blahblah" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls new file mode 100644 index 0000000..0c3bc1e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls @@ -0,0 +1,2 @@ +IEEE_8021X_PRIVATE_KEY_PASSWORD="test1" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls new file mode 100644 index 0000000..b6256b9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls @@ -0,0 +1,2 @@ +IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD="test1" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk new file mode 100644 index 0000000..d7813b2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk @@ -0,0 +1,2 @@ +WPA_PSK="I wonder what the king is doing tonight?" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 new file mode 100644 index 0000000..995721d --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 @@ -0,0 +1,2 @@ +WPA_PSK="They're really saying I love you. >>\`<< '" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc new file mode 100644 index 0000000..d7813b2 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc @@ -0,0 +1,2 @@ +WPA_PSK="I wonder what the king is doing tonight?" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex new file mode 100644 index 0000000..d0576a8 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex @@ -0,0 +1,2 @@ +WPA_PSK=1da190379817bc360dda52e85c388c439a21ea5c7bf819c64e9da051807deae6 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted new file mode 100644 index 0000000..9a47196 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted @@ -0,0 +1,2 @@ +WPA_PSK=54336845e2f3f321c4c7 + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 new file mode 100644 index 0000000..347bb85 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 @@ -0,0 +1,2 @@ +WPA_PSK="a5d4d45e78e1455d8e6124e81ea137f9a5d4d45e78e1455d8e6124e81ea137f9" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw new file mode 100644 index 0000000..d29a428 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw @@ -0,0 +1 @@ +IEEE_8021X_PIN=hallo2 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 new file mode 100644 index 0000000..6936f2e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 @@ -0,0 +1,2 @@ +IEEE_8021X_PASSWORD="foobar baz" + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes new file mode 100644 index 0000000..0d6a302 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes @@ -0,0 +1,2 @@ +GATEWAYDEV=eth0 +IPV6_DEFAULTDEV=eth0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway new file mode 100644 index 0000000..7987d10 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway @@ -0,0 +1 @@ +GATEWAY=192.168.1.2 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore new file mode 100644 index 0000000..7987d10 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore @@ -0,0 +1 @@ +GATEWAY=192.168.1.2 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default new file mode 100644 index 0000000..4347405 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default @@ -0,0 +1,4 @@ +GATEWAYDEV=eth0 +# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is preferred +IPV6_DEFAULTDEV=eth4 +IPV6_DEFAULTGW=2001::1234%eth0 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy new file mode 100644 index 0000000..c9e422e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy @@ -0,0 +1,4 @@ +1.2.3.0/24 via 222.173.190.239 metric 0 +3.2.1.0/24 via 202.254.171.190 metric 77 +7.7.7.7/32 via 10.0.2.2 metric 11 +8.8.8.8/32 metric 12 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes new file mode 100644 index 0000000..9c05417 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes @@ -0,0 +1,19 @@ +ADDRESS0=11.22.33.0 +NETMASK0=255.255.255.0 +GATEWAY0=192.168.1.5 + +ADDRESS1=44.55.66.77 +NETMASK1=255.255.255.255 +GATEWAY1=192.168.1.7 +METRIC1=3 +OPTIONS1="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 window 30000 scope 10 initcwnd lock 13 initrwnd 14" + +ADDRESS2=44.55.66.78 +NETMASK2=255.255.255.255 +GATEWAY2=192.168.1.8 +METRIC2=3 +OPTIONS2="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 onlink window 30000 initcwnd lock 13 initrwnd 14 scope link" + +ADDRESS3=1.2.3.4 +NETMASK3=255.255.255.255 +OPTIONS3="local scope host" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy new file mode 100644 index 0000000..1fef7e9 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy @@ -0,0 +1,9 @@ +# Test route file in legacy format; i.e. lines passed as argumet to "ip route add" + + +21.31.41.0/24 via 9.9.9.9 metric 1 + via 8.8.8.8 to 32.42.52.62 + 43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2 cwnd 14 mtu lock 9000 initrwnd 20 window lock 10000 initcwnd lock 42 src 1.2.3.4 + +7.7.7.8/32 via (null) metric 18 +local 1.2.3.4 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual new file mode 100644 index 0000000..8bdf0ac --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual @@ -0,0 +1,9 @@ +9876::1234/96 via 9876::7777 metric 2 + +# default route is ignored by ifcfg-rh reader, because NM handles it internally +default via dead::beaf + +# routes without "via" are valid +abbe::cafe/64 metric 777 + +aaaa::cccc/64 from 1111::2222/48 via 3333::4444 src 5555::6666 mtu lock 1450 cwnd 13 diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem new file mode 100644 index 0000000..dec9aa1 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem @@ -0,0 +1,118 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,4DE0615F23D82107 + +QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz +L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7 +zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU +h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW +ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv +QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na +eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo +cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m +RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8 +xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr +T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI +ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo +RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ +EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt +lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5 +r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4 +aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W +GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e +DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE +lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO +8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3 +VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt +G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ +tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW +QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1 +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com + Validity + Not Before: Mar 10 15:13:16 2009 GMT + Not After : Mar 8 15:13:16 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a: + 82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48: + ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49: + c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8: + 03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14: + ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9: + c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c: + 60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d: + 44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b: + f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d: + c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75: + d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2: + 06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38: + ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba: + ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36: + 2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11: + a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f: + a3:43 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C + X509v3 Authority Key Identifier: + keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10 + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com + serial:EB:E7:64:FB:79:F7:22:19 + + Signature Algorithm: md5WithRSAEncryption + 7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82: + f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89: + b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b: + 12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76: + 1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48: + 32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02: + 85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1: + fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c: + cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43: + 8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71: + 1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19: + 07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69: + 7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62: + 8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7: + 3b:65:fe:f9 +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx +EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO +TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx +HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN +MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl +MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG +A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR +XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA +vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9 +jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF +TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX +gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj +ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy +YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww +gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ +BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx +FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD +VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3 +IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/ +DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2 +8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr +vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD +T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa +PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk= +-----END CERTIFICATE----- diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem new file mode 100644 index 0000000..ef1be20 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 new file mode 100644 index 0000000..edc2af7 Binary files /dev/null and b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 differ diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c new file mode 100644 index 0000000..10d498e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -0,0 +1,11858 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-aux/nm-json-aux.h" +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-user.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-8021x.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-ppp.h" +#include "nm-setting-vpn.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-gsm.h" +#include "nm-setting-cdma.h" +#include "nm-setting-serial.h" +#include "nm-setting-vlan.h" +#include "nm-setting-dcb.h" +#include "nm-core-internal.h" +#include "nm-base/nm-ethtool-base.h" +#include "nm-base/nm-ethtool-utils-base.h" + +#include "NetworkManagerUtils.h" + +#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h" +#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h" +#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h" +#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h" + +#include "nm-test-utils-core.h" + +#define TEST_IFCFG_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts" +#define TEST_SCRATCH_DIR \ + NM_BUILD_BUILDDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts" +#define TEST_SCRATCH_DIR_TMP TEST_SCRATCH_DIR "/tmp" + +#define TEST_SCRATCH_ALIAS_BASE TEST_SCRATCH_DIR "/ifcfg-alias0" + +/*****************************************************************************/ + +#define _svOpenFile(testfile) \ + ({ \ + shvarFile * _f; \ + GError * _error = NULL; \ + const char *_testfile = (testfile); \ + \ + g_assert(_testfile); \ + _f = svOpenFile(_testfile, &_error); \ + nmtst_assert_success(_f, _error); \ + _f; \ + }) + +#define _svGetValue_check(f, key, expected_value) \ + G_STMT_START \ + { \ + const char * _val; \ + gs_free char * _to_free = NULL; \ + gs_free char * _val_string = NULL; \ + shvarFile *const _f = (f); \ + const char *const _key = (key); \ + \ + _val_string = svGetValueStr_cp(_f, _key); \ + _val = svGetValue(_f, _key, &_to_free); \ + g_assert_cmpstr(_val, ==, (expected_value)); \ + g_assert((!_val_string && (!_val || !_val[0])) \ + || (_val_string && nm_streq0(_val, _val_string))); \ + } \ + G_STMT_END + +static void +_assert_reread_same(NMConnection *connection, NMConnection *reread) +{ + nmtst_assert_connection_verifies_without_normalization(reread); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +_assert_reread_same_FIXME(NMConnection *connection, NMConnection *reread) +{ + gs_unref_object NMConnection *connection_normalized = NULL; + gs_unref_hashtable GHashTable *settings = NULL; + + /* FIXME: these assertion failures should not happen as we expect + * that re-reading a connection after write yields the same result. + * + * Needs investigation and fixing. */ + nmtst_assert_connection_verifies_without_normalization(reread); + + connection_normalized = nmtst_connection_duplicate_and_normalize(connection); + + g_assert(!nm_connection_compare(connection_normalized, reread, NM_SETTING_COMPARE_FLAG_EXACT)); + g_assert(!nm_connection_diff(connection_normalized, + reread, + NM_SETTING_COMPARE_FLAG_EXACT, + &settings)); +} + +/* dummy path for an "expected" file, meaning: don't check for expected + * written ifcfg file. */ +static const char NO_EXPECTED[1]; + +static void +_assert_expected_content(NMConnection *connection, const char *filename, const char *expected) +{ + gs_free char *content_expectd = NULL; + gs_free char *content_written = NULL; + GError * error = NULL; + gsize len_expectd = 0; + gsize len_written = 0; + gboolean success; + const char * uuid = NULL; + + g_assert(NM_IS_CONNECTION(connection)); + g_assert(filename); + g_assert(g_file_test(filename, G_FILE_TEST_EXISTS)); + + g_assert(expected); + if (expected == NO_EXPECTED) + return; + + success = g_file_get_contents(filename, &content_written, &len_written, &error); + nmtst_assert_success(success, error); + + success = g_file_get_contents(expected, &content_expectd, &len_expectd, &error); + nmtst_assert_success(success, error); + + { + gsize i, j; + + for (i = 0; i < len_expectd;) { + if (content_expectd[i] != '$') { + i++; + continue; + } + if (g_str_has_prefix(&content_expectd[i], "${UUID}")) { + GString *str; + + if (!uuid) { + uuid = nm_connection_get_uuid(connection); + g_assert(uuid); + } + + j = strlen(uuid); + + str = g_string_new_len(content_expectd, len_expectd); + g_string_erase(str, i, NM_STRLEN("${UUID}")); + g_string_insert_len(str, i, uuid, j); + + g_free(content_expectd); + len_expectd = str->len; + content_expectd = g_string_free(str, FALSE); + i += j; + continue; + } + + /* other '$' is not supported. If need be, support escaping of + * '$' via '$$'. */ + g_assert_not_reached(); + } + } + + if (len_expectd != len_written || memcmp(content_expectd, content_written, len_expectd) != 0) { + if (g_getenv("NMTST_IFCFG_RH_UPDATE_EXPECTED") + || nm_streq0(g_getenv("NM_TEST_REGENERATE"), "1")) { + if (uuid) { + gs_free char *search = g_strdup_printf("UUID=%s\n", uuid); + const char * s; + gsize i; + GString * str; + + s = content_written; + while (TRUE) { + s = strstr(s, search); + g_assert(s); + if (s == content_written || s[-1] == '\n') + break; + s += strlen(search); + } + + i = s - content_written; + + str = g_string_new_len(content_written, len_written); + g_string_erase(str, i, strlen(search)); + g_string_insert(str, i, "UUID=${UUID}\n"); + + len_written = str->len; + content_written = g_string_free(str, FALSE); + } + success = g_file_set_contents(expected, content_written, len_written, &error); + nmtst_assert_success(success, error); + } else { + g_error("The content of \"%s\" (%zu) differs from \"%s\" (%zu). Set " + "NMTST_IFCFG_RH_UPDATE_EXPECTED=yes to update the files " + "inplace\n\n>>>%s<<<\n\n>>>%s<<<\n", + filename, + len_written, + expected, + len_expectd, + content_written, + content_expectd); + } + } +} + +#define _writer_update_connection_reread(connection, \ + ifcfg_dir, \ + filename, \ + expected, \ + out_reread, \ + out_reread_same) \ + G_STMT_START \ + { \ + gs_unref_object NMConnection *_connection = \ + nmtst_connection_duplicate_and_normalize(connection); \ + NMConnection **_out_reread = (out_reread); \ + gboolean * _out_reread_same = (out_reread_same); \ + const char * _ifcfg_dir = (ifcfg_dir); \ + const char * _filename = (filename); \ + const char * _expected = (expected); \ + GError * _error = NULL; \ + gboolean _success; \ + \ + g_assert(_ifcfg_dir &&_ifcfg_dir[0]); \ + g_assert(_filename &&_filename[0]); \ + \ + _success = nms_ifcfg_rh_writer_write_connection(_connection, \ + _ifcfg_dir, \ + _filename, \ + NULL, \ + NULL, \ + NULL, \ + _out_reread, \ + _out_reread_same, \ + &_error); \ + nmtst_assert_success(_success, _error); \ + _assert_expected_content(_connection, _filename, _expected); \ + } \ + G_STMT_END + +#define _writer_update_connection(connection, ifcfg_dir, filename, expected) \ + G_STMT_START \ + { \ + gs_unref_object NMConnection *_reread = NULL; \ + NMConnection * _c = (connection); \ + gboolean _reread_same = FALSE; \ + \ + _writer_update_connection_reread(_c, \ + ifcfg_dir, \ + filename, \ + expected, \ + &_reread, \ + &_reread_same); \ + _assert_reread_same(_c, _reread); \ + g_assert(_reread_same); \ + } \ + G_STMT_END + +static NMConnection * +_connection_from_file(const char *filename, + const char *network_file, + const char *test_type, + char ** out_unhandled) +{ + NMConnection *connection; + GError * error = NULL; + char * unhandled_fallback = NULL; + + g_assert(!out_unhandled || !*out_unhandled); + + connection = nmtst_connection_from_file(filename, + network_file, + test_type, + out_unhandled ?: &unhandled_fallback, + &error); + g_assert_no_error(error); + g_assert(!unhandled_fallback); + + if (out_unhandled && *out_unhandled) + nmtst_assert_connection_verifies(connection); + else + nmtst_assert_connection_verifies_without_normalization(connection); + return connection; +} + +static void +_connection_from_file_fail(const char *filename, + const char *network_file, + const char *test_type, + GError ** error) +{ + NMConnection *connection; + GError * local = NULL; + char * unhandled = NULL; + + connection = nmtst_connection_from_file(filename, network_file, test_type, &unhandled, &local); + + g_assert(!connection); + g_assert(local); + g_assert(!unhandled); + g_propagate_error(error, local); +} + +static void +_writer_new_connection_reread(NMConnection * connection, + const char * ifcfg_dir, + char ** out_filename, + const char * expected, + NMConnection **out_reread, + gboolean * out_reread_same) +{ + gboolean success; + GError * error = NULL; + char * filename = NULL; + gs_unref_object NMConnection *con_verified = NULL; + gs_unref_object NMConnection *reread_copy = NULL; + NMConnection **reread = out_reread ?: ((nmtst_get_rand_uint32() % 2) ? &reread_copy : NULL); + + g_assert(NM_IS_CONNECTION(connection)); + g_assert(ifcfg_dir); + + con_verified = nmtst_connection_duplicate_and_normalize(connection); + + success = nms_ifcfg_rh_writer_write_connection(con_verified, + ifcfg_dir, + NULL, + NULL, + NULL, + &filename, + reread, + out_reread_same, + &error); + nmtst_assert_success(success, error); + g_assert(filename && filename[0]); + + if (reread) + nmtst_assert_connection_verifies_without_normalization(*reread); + + _assert_expected_content(con_verified, filename, expected); + + if (out_filename) + *out_filename = filename; + else + g_free(filename); +} + +static void +_writer_new_connec_exp(NMConnection *connection, + const char * ifcfg_dir, + const char * expected, + char ** out_filename) +{ + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + + _writer_new_connection_reread(connection, + ifcfg_dir, + out_filename, + expected, + &reread, + &reread_same); + _assert_reread_same(connection, reread); + g_assert(reread_same); +} + +static void +_writer_new_connection(NMConnection *connection, const char *ifcfg_dir, char **out_filename) +{ + _writer_new_connec_exp(connection, ifcfg_dir, NO_EXPECTED, out_filename); +} + +static void +_writer_new_connection_FIXME(NMConnection *connection, const char *ifcfg_dir, char **out_filename) +{ + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + + /* FIXME: this should not happen. Fix it to use _writer_new_connection() instead. */ + + _writer_new_connection_reread(connection, + ifcfg_dir, + out_filename, + NO_EXPECTED, + &reread, + &reread_same); + _assert_reread_same_FIXME(connection, reread); + g_assert(!reread_same); +} + +static void +_writer_new_connection_fail(NMConnection *connection, const char *ifcfg_dir, GError **error) +{ + gs_unref_object NMConnection *connection_normalized = NULL; + gs_unref_object NMConnection *reread = NULL; + gboolean success; + GError * local = NULL; + char * filename = NULL; + + g_assert(NM_IS_CONNECTION(connection)); + g_assert(ifcfg_dir); + + connection_normalized = nmtst_connection_duplicate_and_normalize(connection); + + success = nms_ifcfg_rh_writer_write_connection(connection_normalized, + ifcfg_dir, + NULL, + NULL, + NULL, + &filename, + &reread, + NULL, + &local); + nmtst_assert_no_success(success, local); + g_assert(!filename); + g_assert(!reread); + + g_propagate_error(error, local); +} + +/*****************************************************************************/ + +static void +test_read_netmask_1(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_free char * content = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMIPAddress * ip4_addr; + const char * FILENAME = TEST_IFCFG_DIR "/ifcfg-netmask-1"; + + connection = _connection_from_file(FILENAME, NULL, TYPE_ETHERNET, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System netmask-1"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "102.0.2.2"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 15); + + nmtst_assert_connection_verifies_without_normalization(connection); + + content = nmtst_file_get_contents(FILENAME); + + testfile = g_strdup(TEST_SCRATCH_DIR "/ifcfg-netmask-1.copy"); + + nmtst_file_set_contents(testfile, content); + + _writer_update_connection(connection, + TEST_SCRATCH_DIR, + testfile, + TEST_IFCFG_DIR "/ifcfg-netmask-1.cexpected"); +} + +/*****************************************************************************/ + +static gboolean +verify_cert_or_key(NMSetting8021x *s_compare, + const char * file, + const char * privkey_password, + const char * property) +{ + NMSetting8021x * s_8021x; + GError * error = NULL; + gboolean success = FALSE; + const char * expected = NULL, *setting = NULL; + gboolean phase2 = FALSE; + NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + + if (strstr(property, "phase2")) + phase2 = TRUE; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + + /* Load the certificate into an empty setting */ + if (strstr(property, "ca-cert")) { + if (phase2) + success = nm_setting_802_1x_set_phase2_ca_cert(s_8021x, + file, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + else + success = nm_setting_802_1x_set_ca_cert(s_8021x, + file, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + } else if (strstr(property, "client-cert")) { + if (phase2) + success = nm_setting_802_1x_set_phase2_client_cert(s_8021x, + file, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + else + success = nm_setting_802_1x_set_client_cert(s_8021x, + file, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + } else if (strstr(property, "private-key")) { + if (phase2) + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + file, + privkey_password, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + else + success = nm_setting_802_1x_set_private_key(s_8021x, + file, + privkey_password, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + } + g_assert_no_error(error); + g_assert_cmpint(success, ==, TRUE); + + /* Ensure it was loaded using the PATH scheme */ + if (strstr(property, "ca-cert")) { + if (phase2) + scheme = nm_setting_802_1x_get_phase2_ca_cert_scheme(s_8021x); + else + scheme = nm_setting_802_1x_get_ca_cert_scheme(s_8021x); + } else if (strstr(property, "client-cert")) { + if (phase2) + scheme = nm_setting_802_1x_get_phase2_client_cert_scheme(s_8021x); + else + scheme = nm_setting_802_1x_get_client_cert_scheme(s_8021x); + } else if (strstr(property, "private-key")) { + if (phase2) + scheme = nm_setting_802_1x_get_phase2_private_key_scheme(s_8021x); + else + scheme = nm_setting_802_1x_get_private_key_scheme(s_8021x); + } + g_assert_cmpint(scheme, ==, NM_SETTING_802_1X_CK_SCHEME_PATH); + + /* Grab the path back out */ + if (strstr(property, "ca-cert")) { + if (phase2) + expected = nm_setting_802_1x_get_phase2_ca_cert_path(s_8021x); + else + expected = nm_setting_802_1x_get_ca_cert_path(s_8021x); + } else if (strstr(property, "client-cert")) { + if (phase2) + expected = nm_setting_802_1x_get_phase2_client_cert_path(s_8021x); + else + expected = nm_setting_802_1x_get_client_cert_path(s_8021x); + } else if (strstr(property, "private-key")) { + if (phase2) + expected = nm_setting_802_1x_get_phase2_private_key_path(s_8021x); + else + expected = nm_setting_802_1x_get_private_key_path(s_8021x); + } + g_assert_cmpstr(expected, ==, file); + + /* Compare the path with the expected path from the real setting */ + if (strstr(property, "ca-cert")) { + if (phase2) + setting = nm_setting_802_1x_get_phase2_ca_cert_path(s_compare); + else + setting = nm_setting_802_1x_get_ca_cert_path(s_compare); + } else if (strstr(property, "client-cert")) { + if (phase2) + setting = nm_setting_802_1x_get_phase2_client_cert_path(s_compare); + else + setting = nm_setting_802_1x_get_client_cert_path(s_compare); + } else if (strstr(property, "private-key")) { + if (phase2) + setting = nm_setting_802_1x_get_phase2_private_key_path(s_compare); + else + setting = nm_setting_802_1x_get_private_key_path(s_compare); + } + g_assert_cmpstr(setting, ==, expected); + + g_object_unref(s_8021x); + return TRUE; +} + +static void +test_read_basic(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-minimal", NULL, TYPE_ETHERNET, NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-minimal"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + g_assert_cmpint(nm_setting_connection_get_autoconnect_retries(s_con), ==, -1); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + /* MAC address */ + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN)); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE); + + g_object_unref(connection); +} + +static void +test_read_miscellaneous_variables(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * expected_mac_blacklist[3] = {"00:16:41:11:22:88", + "00:16:41:11:22:99", + "6a:5d:5a:fa:dd:f0"}; + int mac_blacklist_num, i; + guint64 expected_timestamp = 0; + + NMTST_EXPECT_NM_WARN("*invalid MAC in HWADDR_BLACKLIST 'XX:aa:invalid'*"); + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-misc-variables", + NULL, + TYPE_ETHERNET, + NULL); + g_test_assert_expected_messages(); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + g_assert_cmpint(nm_setting_connection_get_autoconnect_retries(s_con), ==, 100); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + /* MAC blacklist */ + mac_blacklist_num = nm_setting_wired_get_num_mac_blacklist_items(s_wired); + g_assert_cmpint(mac_blacklist_num, ==, 3); + for (i = 0; i < mac_blacklist_num; i++) + g_assert(nm_utils_hwaddr_matches(nm_setting_wired_get_mac_blacklist_item(s_wired, i), + -1, + expected_mac_blacklist[i], + -1)); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE); + + g_object_unref(connection); +} + +static void +test_read_variables_corner_cases(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-variables-corner-cases-1", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System test-variables-corner-cases-1"); + g_assert_cmpstr(nm_setting_connection_get_zone(s_con), ==, NULL); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0); + + /* MAC address */ + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN)); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE); + + g_object_unref(connection); +} + +static void +test_read_unmanaged(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + char * unhandled_spec = NULL; + guint64 expected_timestamp = 0; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled", + NULL, + TYPE_ETHERNET, + &unhandled_spec); + g_assert_cmpstr(unhandled_spec, ==, "unmanaged:mac:00:11:22:33:f8:9f"); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-nm-controlled"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + g_free(unhandled_spec); + g_object_unref(connection); +} + +static void +test_read_unmanaged_unrecognized(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + gs_free char * unhandled_spec = NULL; + guint64 expected_timestamp = 0; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled-unrecognized", + NULL, + NULL, + &unhandled_spec); + g_assert_cmpstr(unhandled_spec, ==, "unmanaged:interface-name:=ipoac0"); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "PigeonNet"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp); + + g_object_unref(connection); +} + +static void +test_read_unrecognized(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + gs_free char * unhandled_spec = NULL; + guint64 expected_timestamp = 0; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-unrecognized", + NULL, + NULL, + &unhandled_spec); + g_assert_cmpstr(unhandled_spec, ==, "unrecognized:mac:00:11:22:33"); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-unrecognized"); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp); + + g_object_unref(connection); +} + +static void +test_read_wired_static(gconstpointer test_data) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee}; + NMIPAddress * ip4_addr; + NMIPAddress * ip6_addr; + const char * file, *expected_id; + gpointer expect_ip6_p; + + nmtst_test_data_unpack(test_data, &file, &expected_id, &expect_ip6_p); + + g_assert(expected_id); + + connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged); + g_assert_cmpstr(unmanaged, ==, NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1492); + + /* MAC address */ + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN)); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert(nm_setting_ip_config_get_may_fail(s_ip4)); + + g_assert(nm_setting_ip_config_has_dns_options(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 0); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2"); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5"); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1"); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + if (GPOINTER_TO_INT(expect_ip6_p)) { + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); + + g_assert(nm_setting_ip_config_has_dns_options(s_ip6)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip6), ==, 0); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b"); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 2); + + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip6_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "dead:beaf::1"); + + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1); + g_assert(ip6_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "dead:beaf::2"); + } else { + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + g_assert(!nm_setting_ip_config_has_dns_options(s_ip6)); + } + + g_object_unref(connection); +} + +static void +test_read_wired_static_no_prefix(gconstpointer user_data) +{ + guint32 expected_prefix = GPOINTER_TO_UINT(user_data); + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMIPAddress * ip4_addr; + char * file, *expected_id; + + file = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wired-static-no-prefix-%u", expected_prefix); + expected_id = g_strdup_printf("System test-wired-static-no-prefix-%u", expected_prefix); + + NMTST_EXPECT_NM_WARN("*missing PREFIX, assuming*"); + connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL); + g_test_assert_expected_messages(); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert(!nm_setting_ip_config_has_dns_options(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 0); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, expected_prefix); + + g_free(file); + g_free(expected_id); + g_object_unref(connection); +} + +static void +test_read_wired_dhcp(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee}; + const char * mac; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(unmanaged == NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp"); + g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* MAC address */ + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert_cmpstr(nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)), + ==, + "foo.bar"); + g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2"); + g_assert_cmpuint(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip4), + ==, + NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "foo.bar"); + g_assert_cmpuint(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip6), + ==, + NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS); + + g_object_unref(connection); +} + +static void +test_read_wired_dhcp_plus_ip(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + NMIPAddress * ip4_addr; + NMIPAddress * ip6_addr; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-plus-ip", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_may_fail(s_ip4)); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2"); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 2); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4"); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1"); + + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "9.8.7.6"); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b"); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 3); + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip6_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234"); + + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1); + g_assert(ip6_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "2001:abba::2234"); + + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 2); + g_assert(ip6_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 96); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "3001:abba::3234"); + + g_object_unref(connection); +} + +static void +test_read_wired_shared_plus_ip(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMIPAddress * ip4_addr; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-shared-plus-ip", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_SHARED); + g_assert(nm_setting_ip_config_get_may_fail(s_ip4)); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.20.30.5"); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1"); + + g_object_unref(connection); +} + +static void +test_read_wired_global_gateway(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMIPAddress * ip4_addr; + char * unmanaged = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-global-gateway", + TEST_IFCFG_DIR "/network-test-wired-global-gateway", + TYPE_ETHERNET, + &unmanaged); + g_assert(unmanaged == NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-global-gateway"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + /* Address #1 */ + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5"); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.2"); + + g_object_unref(connection); +} + +/* Ignore GATEWAY from /etc/sysconfig/network for automatic connections */ +static void +test_read_wired_global_gateway_ignore(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * unmanaged = NULL; + + NMTST_EXPECT_NM_WARN("*ignoring GATEWAY (/etc/sysconfig/network) for * because the connection " + "has no static addresses"); + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-global-gateway-ignore", + TEST_IFCFG_DIR "/network-test-wired-global-gateway-ignore", + TYPE_ETHERNET, + &unmanaged); + g_test_assert_expected_messages(); + g_assert(unmanaged == NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System test-wired-global-gateway-ignore"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, NULL); + + g_object_unref(connection); +} + +static void +test_read_wired_obsolete_gateway_n(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMIPAddress * ip4_addr; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-obsolete-gateway-n", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4"); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1"); + + g_object_unref(connection); +} + +static void +test_user_1(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingUser * s_user; + + connection = + nmtst_create_minimal_connection("Test User 1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_user = NM_SETTING_USER(nm_setting_user_new()); + +#define _USER_SET_DATA(s_user, key, val) \ + G_STMT_START \ + { \ + GError * _error = NULL; \ + gboolean _success; \ + \ + _success = nm_setting_user_set_data((s_user), (key), (val), &_error); \ + nmtst_assert_success(_success, _error); \ + } \ + G_STMT_END + +#define _USER_SET_DATA_X(s_user, key) _USER_SET_DATA(s_user, key, "val=" key "") + + _USER_SET_DATA(s_user, "my.val1", ""); + _USER_SET_DATA_X(s_user, "my.val2"); + _USER_SET_DATA_X(s_user, "my.v__al3"); + _USER_SET_DATA_X(s_user, "my._v"); + _USER_SET_DATA_X(s_user, "my.v+"); + _USER_SET_DATA_X(s_user, "my.Av"); + _USER_SET_DATA_X(s_user, "MY.AV"); + _USER_SET_DATA_X(s_user, "MY.8V"); + _USER_SET_DATA_X(s_user, "MY.8-V"); + _USER_SET_DATA_X(s_user, "MY.8_V"); + _USER_SET_DATA_X(s_user, "MY.8+V"); + _USER_SET_DATA_X(s_user, "MY.8/V"); + _USER_SET_DATA_X(s_user, "MY.8=V"); + _USER_SET_DATA_X(s_user, "MY.-"); + _USER_SET_DATA_X(s_user, "MY._"); + _USER_SET_DATA_X(s_user, "MY.+"); + _USER_SET_DATA_X(s_user, "MY./"); + _USER_SET_DATA_X(s_user, "MY.="); + _USER_SET_DATA_X(s_user, "my.keys.1"); + _USER_SET_DATA_X(s_user, "my.other.KEY.42"); + + nm_connection_add_setting(connection, NM_SETTING(s_user)); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_User_1.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_wired_never_default(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-never-default", + TEST_IFCFG_DIR "/network-test-wired-never-default", + TYPE_ETHERNET, + NULL); + + /* ===== WIRED SETTING ===== */ + g_assert(nm_connection_get_setting_wired(connection)); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip6)); + + g_object_unref(connection); +} + +static void +test_read_wired_defroute_no(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(unmanaged == NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-defroute-no"); + + g_assert(nm_connection_get_setting_wired(connection)); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip4)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip6)); + + g_object_unref(connection); +} + +static void +test_read_wired_defroute_no_gatewaydev_yes(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no-gatewaydev-yes", + TEST_IFCFG_DIR "/network-test-wired-defroute-no-gatewaydev-yes", + TYPE_ETHERNET, + &unmanaged); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System test-wired-defroute-no-gatewaydev-yes"); + + g_assert(nm_connection_get_setting_wired(connection)); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); + g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE); + + g_object_unref(connection); +} + +static void +test_read_wired_static_routes(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMIPRoute * ip4_route; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-static-routes"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + /* Routes */ + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 4); + + ip4_route = nm_setting_ip_config_get_route(s_ip4, 0); + g_assert(ip4_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "11.22.33.0"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 24); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.5"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, -1); + + ip4_route = nm_setting_ip_config_get_route(s_ip4, 1); + g_assert(ip4_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "44.55.66.77"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.7"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3); + nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TOS, 0x28); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 30000); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 12); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 13); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 14); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, TRUE); + nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.1.1.1"); + nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 10); + + ip4_route = nm_setting_ip_config_get_route(s_ip4, 2); + g_assert(ip4_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "44.55.66.78"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.8"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3); + nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TOS, 0x28); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 30000); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 12); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 13); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 14); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, TRUE); + nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.1.1.1"); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, TRUE); + nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 253); + + ip4_route = nm_setting_ip_config_get_route(s_ip4, 3); + g_assert(ip4_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "1.2.3.4"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32); + nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local"); + nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 254); + + g_object_unref(connection); +} + +static void +test_read_wired_static_routes_legacy(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * unmanaged = NULL; + NMIPRoute * ip4_route; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes-legacy", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System test-wired-static-routes-legacy"); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + /* Routes */ + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 5); + + /* Route #1 */ + ip4_route = nm_setting_ip_config_get_route(s_ip4, 0); + g_assert(ip4_route != NULL); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "21.31.41.0"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 24); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "9.9.9.9"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 1); + + /* Route #2 */ + ip4_route = nm_setting_ip_config_get_route(s_ip4, 1); + g_assert(ip4_route != NULL); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "32.42.52.62"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "8.8.8.8"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, -1); + + /* Route #3 */ + ip4_route = nm_setting_ip_config_get_route(s_ip4, 2); + g_assert(ip4_route != NULL); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "43.53.0.0"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 16); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "7.7.7.7"); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 10000); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 14); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 42); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 20); + nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, TRUE); + nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE); + nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.2.3.4"); + + ip4_route = nm_setting_ip_config_get_route(s_ip4, 3); + g_assert(ip4_route != NULL); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "7.7.7.8"); + g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, NULL); + g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 18); + + /* Route #5 */ + ip4_route = nm_setting_ip_config_get_route(s_ip4, 4); + g_assert(ip4_route != NULL); + g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "1.2.3.4"); + nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local"); + + g_object_unref(connection); +} + +static void +test_read_wired_ipv4_manual(gconstpointer data) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * unmanaged = NULL; + NMIPAddress * ip4_addr; + const char * file, *expected_id; + + nmtst_test_data_unpack(data, &file, &expected_id); + + g_assert(expected_id); + + connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert_cmpint(nm_setting_ip_config_get_dad_timeout(s_ip4), ==, 2000); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 3); + + /* Address #1 */ + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + + /* Address #2 */ + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "9.8.7.6"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16); + + /* Address #3 */ + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 2); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "3.3.3.3"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8); + + g_object_unref(connection); +} + +static void +test_read_wired_ipv6_manual(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + NMIPAddress * ip6_addr; + NMIPRoute * ip6_route; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-manual", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ipv6-manual"); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + + /* DNS search domains */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip4), ==, 3); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 0), ==, "lorem.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "ipsum.org"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 2), ==, "dolor.edu"); + + /* ===== IPv6 SETTING ===== */ + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert(!nm_setting_ip_config_get_never_default(s_ip6)); + g_assert(nm_setting_ip_config_get_may_fail(s_ip6)); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 3); + + /* Address #1 */ + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip6_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234"); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56); + + /* Address #2 */ + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1); + g_assert(ip6_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "2001:abba::2234"); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64); + + /* Address #3 */ + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 2); + g_assert(ip6_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "3001:abba::3234"); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 96); + + /* Routes */ + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 4); + /* Route #1 */ + ip6_route = nm_setting_ip_config_get_route(s_ip6, 0); + g_assert(ip6_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "9876::1234"); + g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 96); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "9876::7777"); + g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, 2); + /* Route #2 */ + ip6_route = nm_setting_ip_config_get_route(s_ip6, 1); + g_assert(ip6_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "::"); + g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 0); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "dead::beaf"); + g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, -1); + /* Route #3 */ + ip6_route = nm_setting_ip_config_get_route(s_ip6, 2); + g_assert(ip6_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "abbe::cafe"); + g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 64); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, NULL); + g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, 777); + /* Route #4 */ + ip6_route = nm_setting_ip_config_get_route(s_ip6, 3); + g_assert(ip6_route); + g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "aaaa::cccc"); + g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 64); + g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "3333::4444"); + nmtst_assert_route_attribute_uint32(ip6_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 13); + nmtst_assert_route_attribute_uint32(ip6_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1450); + nmtst_assert_route_attribute_boolean(ip6_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE); + nmtst_assert_route_attribute_string(ip6_route, NM_IP_ROUTE_ATTRIBUTE_FROM, "1111::2222/48"); + nmtst_assert_route_attribute_string(ip6_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "5555::6666"); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b"); + + /* DNS domains - none as domains are stuffed to 'ipv4' setting */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 0); + + g_object_unref(connection); +} + +static void +test_read_wired_ipv6_only(gconstpointer test_data) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + NMIPAddress * ip6_addr; + const char * method; + const char * file, *expected_id; + + nmtst_test_data_unpack(test_data, &file, &expected_id); + + g_assert(expected_id); + + connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + + method = nm_setting_ip_config_get_method(s_ip4); + g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + /* ===== IPv6 SETTING ===== */ + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + + /* IP addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + + /* Address #1 */ + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip6_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234"); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a"); + + /* DNS domains should be in IPv6, because IPv4 is disabled */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 3); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "lorem.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "ipsum.org"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "dolor.edu"); + + g_object_unref(connection); +} + +static void +test_read_wired_dhcp6_only(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char * unmanaged = NULL; + const char * method; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp6-only", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp6-only"); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + + method = nm_setting_ip_config_get_method(s_ip4); + g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + /* ===== IPv6 SETTING ===== */ + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP); + + g_object_unref(connection); +} + +static void +test_read_wired_autoip(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + char * unmanaged = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-autoip", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(unmanaged == NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL); + g_assert(!nm_setting_ip_config_get_may_fail(s_ip4)); + g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + + s_con = nm_connection_get_setting_connection(connection); + g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, 2600); +} + +static void +test_read_onboot_no(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + char * unmanaged = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-onboot-no", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + g_assert(!nm_setting_connection_get_autoconnect(s_con)); + + g_object_unref(connection); +} + +static void +test_read_noip(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-noip", NULL, TYPE_ETHERNET, NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE); + + g_object_unref(connection); +} + +#define TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT TEST_IFCFG_DIR "/test_ca_cert.pem" + +static void +test_read_wired_8021x_peap_mschapv2(void) +{ + NMConnection * connection; + NMSettingWired * s_wired; + NMSettingIPConfig *s_ip4; + NMSetting8021x * s_8021x; + NMSetting8021x * tmp_8021x; + char * unmanaged = NULL; + GError * error = NULL; + gboolean success = FALSE; + const char * expected_ca_cert_path; + const char * read_ca_cert_path; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-peap-mschapv2", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith"); + g_assert_cmpstr(nm_setting_802_1x_get_anonymous_identity(s_8021x), ==, "somebody"); + g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz"); + g_assert_cmpstr(nm_setting_802_1x_get_phase1_peapver(s_8021x), ==, "1"); + g_assert_cmpstr(nm_setting_802_1x_get_phase1_peaplabel(s_8021x), ==, "1"); + + /* CA Cert */ + tmp_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + + success = nm_setting_802_1x_set_ca_cert(tmp_8021x, + TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + g_assert_no_error(error); + g_assert(success == TRUE); + + expected_ca_cert_path = nm_setting_802_1x_get_ca_cert_path(tmp_8021x); + g_assert(expected_ca_cert_path); + + read_ca_cert_path = nm_setting_802_1x_get_ca_cert_path(s_8021x); + g_assert(read_ca_cert_path); + + g_assert_cmpstr(read_ca_cert_path, ==, expected_ca_cert_path); + + g_object_unref(tmp_8021x); + + g_object_unref(connection); +} + +static void +test_read_wired_8021x_tls_secret_flags(gconstpointer test_data) +{ + NMConnection * connection; + NMSettingWired *s_wired; + NMSetting8021x *s_8021x; + char * dirname, *tmp; + const char * ifcfg; + gpointer expected_flags_p; + + nmtst_test_data_unpack(test_data, &ifcfg, &expected_flags_p); + + connection = _connection_from_file(ifcfg, NULL, TYPE_ETHERNET, NULL); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith"); + g_assert_cmpint(nm_setting_802_1x_get_private_key_password_flags(s_8021x), + ==, + GPOINTER_TO_INT(expected_flags_p)); + + dirname = g_path_get_dirname(ifcfg); + tmp = g_build_path("/", dirname, "test_ca_cert.pem", NULL); + g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, tmp); + g_free(tmp); + + tmp = g_build_path("/", dirname, "test1_key_and_cert.pem", NULL); + g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), ==, tmp); + g_assert_cmpstr(nm_setting_802_1x_get_private_key_path(s_8021x), ==, tmp); + g_free(tmp); + + g_free(dirname); + + g_object_unref(connection); +} + +static void +test_read_write_802_1X_subj_matches(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSetting8021x * s_8021x; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1X-subj-matches", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman"); + g_assert_cmpstr(nm_setting_802_1x_get_subject_match(s_8021x), ==, "server1.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_subject_match(s_8021x), + ==, + "server2.yourdomain.tld"); + g_assert_cmpint(nm_setting_802_1x_get_num_altsubject_matches(s_8021x), ==, 3); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 0), ==, "a.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 1), ==, "b.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 2), ==, "c.yourdomain.tld"); + g_assert_cmpint(nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x), ==, 2); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 0), + ==, + "x.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 1), + ==, + "y.yourdomain.tld"); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-System_test-wired-802-1X-subj-matches.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + /* Check 802.1X stuff of the re-read connection. */ + s_8021x = nm_connection_get_setting_802_1x(reread); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman"); + g_assert_cmpstr(nm_setting_802_1x_get_subject_match(s_8021x), ==, "server1.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_subject_match(s_8021x), + ==, + "server2.yourdomain.tld"); + g_assert_cmpint(nm_setting_802_1x_get_num_altsubject_matches(s_8021x), ==, 3); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 0), ==, "a.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 1), ==, "b.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 2), ==, "c.yourdomain.tld"); + g_assert_cmpint(nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x), ==, 2); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 0), + ==, + "x.yourdomain.tld"); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 1), + ==, + "y.yourdomain.tld"); +} + +static void +test_read_802_1x_ttls_eapgtc(void) +{ + NMConnection * connection; + NMSetting8021x *s_8021x; + + /* Test that EAP-* inner methods are correctly read into the + * NMSetting8021x::autheap property. + */ + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1x-ttls-eapgtc", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + /* EAP methods */ + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls"); + + /* Auth methods */ + g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, NULL); + g_assert_cmpstr(nm_setting_802_1x_get_phase2_autheap(s_8021x), ==, "gtc"); + + g_object_unref(connection); +} + +static void +test_read_802_1x_tls_p12_no_client_cert(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSetting8021x * s_8021x; + const char * path; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-p12-no-client-cert", + NULL, + TYPE_ETHERNET, + NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + g_assert_cmpint(nm_setting_802_1x_get_private_key_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + path = nm_setting_802_1x_get_private_key_path(s_8021x); + g_assert(path); + + g_assert_cmpint(nm_setting_802_1x_get_client_cert_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr(path, ==, nm_setting_802_1x_get_client_cert_path(s_8021x)); +} + +static void +test_read_write_802_1x_password_raw(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSetting8021x * s_8021x; + GBytes * bytes; + gconstpointer data; + gsize size; + + /* Test that the 802-1x.password-raw is correctly read and written. */ + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1x-password-raw", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + bytes = nm_setting_802_1x_get_password_raw(s_8021x); + g_assert(bytes); + data = g_bytes_get_data(bytes, &size); + g_assert_cmpmem(data, size, "\x04\x08\x15\x16\x23\x42\x00\x01", 8); + + g_assert_cmpint(nm_setting_802_1x_get_password_raw_flags(s_8021x), + ==, + NM_SETTING_SECRET_FLAG_NONE); + + g_assert_cmpstr(nm_setting_802_1x_get_pin(s_8021x), ==, "hallo2"); + g_assert_cmpint(nm_setting_802_1x_get_pin_flags(s_8021x), ==, NM_SETTING_SECRET_FLAG_NONE); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + keyfile = utils_get_keys_path(testfile); + g_assert(g_file_test(keyfile, G_FILE_TEST_EXISTS)); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_wired_aliases_good(gconstpointer test_data) +{ + const int N = GPOINTER_TO_INT(test_data); + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + int expected_num_addresses; + const char * expected_address_0[] = {"192.168.1.5", + "192.168.1.6", + "192.168.1.9", + "192.168.1.99", + NULL}; + const char * expected_address_3[] = {"192.168.1.5", "192.168.1.6", NULL}; + const char * expected_label_0[] = { + NULL, + "aliasem0:1", + "aliasem0:2", + "aliasem0:99", + NULL, + }; + const char *expected_label_3[] = { + NULL, + "aliasem3:1", + NULL, + }; + const char **expected_address; + const char **expected_label; + int i, j; + char path[256]; + + expected_address = N == 0 ? expected_address_0 : expected_address_3; + expected_label = N == 0 ? expected_label_0 : expected_label_3; + expected_num_addresses = g_strv_length((char **) expected_address); + + nm_sprintf_buf(path, TEST_IFCFG_DIR "/ifcfg-aliasem%d", N); + + connection = _connection_from_file(path, NULL, TYPE_ETHERNET, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + if (N == 0) + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem0"); + else + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem3"); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, expected_num_addresses); + + /* Addresses */ + for (i = 0; i < expected_num_addresses; i++) { + NMIPAddress *ip4_addr; + const char * addr; + GVariant * label; + + ip4_addr = nm_setting_ip_config_get_address(s_ip4, i); + g_assert(ip4_addr != NULL); + + addr = nm_ip_address_get_address(ip4_addr); + g_assert(nm_utils_ipaddr_is_valid(AF_INET, addr)); + + for (j = 0; j < expected_num_addresses; j++) { + if (!g_strcmp0(addr, expected_address[j])) + break; + } + g_assert(j < expected_num_addresses); + + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + label = nm_ip_address_get_attribute(ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (expected_label[j]) + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, expected_label[j]); + else + g_assert(label == NULL); + + expected_address[j] = NULL; + expected_label[j] = NULL; + } + + /* Gateway */ + g_assert(!nm_setting_ip_config_get_never_default(s_ip4)); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1"); + + for (i = 0; i < expected_num_addresses; i++) + g_assert(!expected_address[i]); + + g_object_unref(connection); +} + +static void +test_read_wired_aliases_bad(const char *base, const char *expected_id) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMIPAddress * ip4_addr; + + g_assert(expected_id); + + connection = _connection_from_file(base, NULL, TYPE_ETHERNET, NULL); + g_test_assert_expected_messages(); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + + /* Addresses */ + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24); + g_assert(nm_ip_address_get_attribute(ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1"); + + g_object_unref(connection); +} + +static void +test_read_wired_aliases_bad_1(void) +{ + NMTST_EXPECT_NM_WARN("*aliasem1:1*has no DEVICE*"); + test_read_wired_aliases_bad(TEST_IFCFG_DIR "/ifcfg-aliasem1", "System aliasem1"); +} + +static void +test_read_wired_aliases_bad_2(void) +{ + NMTST_EXPECT_NM_WARN("*aliasem2:1*has invalid DEVICE*"); + test_read_wired_aliases_bad(TEST_IFCFG_DIR "/ifcfg-aliasem2", "System aliasem2"); +} + +static void +test_read_dns_options(void) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip4, *s_ip6; + char * unmanaged = NULL; + const char * option; + const char * options4[] = {"ndots:3", "single-request-reopen"}; + const char * options6[] = {"inet6"}; + guint32 i, num; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dns-options", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert_cmpstr(unmanaged, ==, NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + + num = nm_setting_ip_config_get_num_dns_options(s_ip4); + g_assert_cmpint(num, ==, G_N_ELEMENTS(options4)); + + for (i = 0; i < num; i++) { + option = nm_setting_ip_config_get_dns_option(s_ip4, i); + g_assert_cmpstr(options4[i], ==, option); + } + + num = nm_setting_ip_config_get_num_dns_options(s_ip6); + g_assert_cmpint(num, ==, G_N_ELEMENTS(options6)); + + for (i = 0; i < num; i++) { + option = nm_setting_ip_config_get_dns_option(s_ip6, i); + g_assert_cmpstr(options6[i], ==, option); + } + + g_object_unref(connection); +} + +static void +test_clear_master(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_free char * keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + char * unmanaged = NULL; + shvarFile * f; + + /* 1. load the bridge slave connection from disk */ + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-component", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert_cmpstr(unmanaged, ==, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, "bridge"); + + /* 2. write the connection to a new file */ + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-System_test-bridge-component-a.cexpected", + &testfile); + + /* 3. clear master and slave-type */ + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + NULL, + NM_SETTING_CONNECTION_SLAVE_TYPE, + NULL, + NULL); + + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, NULL); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NULL); + + nmtst_assert_connection_verifies_after_normalization(connection, 0, 0); + + /* 4. update the connection on disk */ + _writer_update_connection(connection, + TEST_SCRATCH_DIR, + testfile, + TEST_IFCFG_DIR "/ifcfg-System_test-bridge-component-b.cexpected"); + keyfile = utils_get_keys_path(testfile); + g_assert(!g_file_test(keyfile, G_FILE_TEST_EXISTS)); + + /* 5. check that BRIDGE variable has been removed */ + f = _svOpenFile(testfile); + _svGetValue_check(f, "BRIDGE", NULL); + svCloseFile(f); +} + +static void +test_write_dns_options(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + static const char * mac = "31:33:33:37:be:cd"; + guint32 mtu = 1492; + NMIPAddress * addr; + NMIPAddress * addr6; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test DNS options", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 204, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nm_setting_ip_config_add_dns_option(s_ip4, "debug"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 206, + NULL); + + /* Add addresses */ + addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + nm_setting_ip_config_add_dns_option(s_ip6, "timeout:3"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_wifi_open(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4, *s_ip6; + GBytes * ssid; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + const char *expected_ssid = "blahblah"; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open", NULL, TYPE_WIRELESS, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-open)"); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + g_assert_cmpint(nm_setting_connection_get_autoconnect_priority(s_con), ==, -1); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* MAC address */ + mac = nm_setting_wireless_get_mac_address(s_wireless); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + + g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1); + + /* ===== Wi-Fi SECURITY SETTING ===== */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec == NULL); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip4), ==, 104); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip6), ==, 106); + + g_object_unref(connection); +} + +static void +test_read_wifi_open_auto(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wireless; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-auto", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-open-auto)"); + + /* ===== WIRELESS SETTING ===== */ + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + + g_object_unref(connection); +} + +static void +test_read_wifi_open_ssid_hex(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wireless; + GBytes * ssid; + const char * expected_ssid = "blahblah"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-hex", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-open-ssid-hex)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_object_unref(connection); +} + +static void +test_read_wifi_open_ssid_hex_bad(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + GBytes * ssid; + const char * expected_ssid = "0x626cxx"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-bad-hex", + NULL, + TYPE_WIRELESS, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System 0x626cxx (test-wifi-open-ssid-bad-hex)"); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); +} + +static void +test_read_wifi_open_ssid_bad(gconstpointer data) +{ + _connection_from_file_fail((const char *) data, NULL, TYPE_WIRELESS, NULL); +} + +static void +test_read_wifi_open_ssid_quoted(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wireless; + GBytes * ssid; + const char * expected_ssid = "foo\"bar\\"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-quoted", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System foo\"bar\\ (test-wifi-open-ssid-quoted)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + GBytes * ssid; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + const char * expected_ssid = "blahblah"; + NMWepKeyType key_type; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep", NULL, TYPE_WIRELESS, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wep)"); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* MAC address */ + mac = nm_setting_wireless_get_mac_address(s_wireless); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + + /* MTU */ + g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "shared"); + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY); + + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), + ==, + "0123456789abcdef0123456789"); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3)); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_adhoc(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + GBytes * ssid; + const char * expected_ssid = "blahblah"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-adhoc", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-wep-adhoc)"); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + g_assert(!nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc"); + g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 11); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec)); + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), + ==, + "0123456789abcdef0123456789"); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3)); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* Ignore auto DNS */ + g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4)); + + /* DNS Addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_passphrase(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-passphrase", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + g_assert_cmpint(nm_setting_wireless_security_get_wep_key_type(s_wsec), + ==, + NM_WEP_KEY_TYPE_UNKNOWN); + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "foobar222blahblah"); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3)); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_40_ascii(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMWepKeyType key_type; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-40-ascii", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY); + + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "Lorem"); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3)); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_104_ascii(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMWepKeyType key_type; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-104-ascii", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY); + + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "LoremIpsumSit"); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2)); + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3)); + + g_object_unref(connection); +} + +static void +test_read_wifi_leap(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-leap", NULL, TYPE_WIRELESS, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-leap)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x"); + g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "leap"); + g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, "Bill Smith"); + g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, "foobarblah"); + + g_object_unref(connection); +} + +static void +test_read_wifi_leap_secret_flags(gconstpointer test_data) +{ + NMConnection * connection; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + const char * file; + gpointer expected_flags_p; + + nmtst_test_data_unpack(test_data, &file, &expected_flags_p); + + connection = _connection_from_file(file, NULL, TYPE_WIRELESS, NULL); + + /* ===== WIRELESS SETTING ===== */ + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + /* ===== WIRELESS SECURITY SETTING ===== */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + + g_assert(g_strcmp0(nm_setting_wireless_security_get_key_mgmt(s_wsec), "ieee8021x") == 0); + g_assert(g_strcmp0(nm_setting_wireless_security_get_auth_alg(s_wsec), "leap") == 0); + g_assert(g_strcmp0(nm_setting_wireless_security_get_leap_username(s_wsec), "Bill Smith") == 0); + /* password blank as it's not system-owned */ + g_assert(nm_setting_wireless_security_get_leap_password_flags(s_wsec) + == GPOINTER_TO_INT(expected_flags_p)); + g_assert(nm_setting_wireless_security_get_leap_password(s_wsec) == NULL); + + g_object_unref(connection); +} + +static void +test_ifcfg_no_trailing_newline(void) +{ + shvarFile *sv; + + sv = _svOpenFile(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk"); + _svGetValue_check(sv, "CTCPROT", "no-newline"); + svCloseFile(sv); +} + +static void +test_read_wifi_wpa_psk(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + GBytes * ssid; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + const char *expected_ssid = "blahblah"; + guint32 n, i; + gboolean found_pair_tkip = FALSE; + gboolean found_pair_ccmp = FALSE; + gboolean found_group_tkip = FALSE; + gboolean found_group_ccmp = FALSE; + gboolean found_group_wep40 = FALSE; + gboolean found_group_wep104 = FALSE; + gboolean found_proto_wpa = FALSE; + gboolean found_proto_rsn = FALSE; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk", NULL, TYPE_WIRELESS, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wpa-psk)"); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + mac = nm_setting_wireless_get_mac_address(s_wireless); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + + g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), + ==, + "I wonder what the king is doing tonight?"); + g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec)); + + /* Pairwise ciphers */ + n = nm_setting_wireless_security_get_num_pairwise(s_wsec); + g_assert_cmpint(n, ==, 2); + for (i = 0; i < n; i++) { + const char *tmp = nm_setting_wireless_security_get_pairwise(s_wsec, i); + g_assert(tmp); + if (strcmp(tmp, "tkip") == 0) + found_pair_tkip = TRUE; + else if (strcmp(tmp, "ccmp") == 0) + found_pair_ccmp = TRUE; + } + g_assert(found_pair_tkip); + g_assert(found_pair_ccmp); + + /* Group ciphers */ + n = nm_setting_wireless_security_get_num_groups(s_wsec); + g_assert_cmpint(n, ==, 4); + for (i = 0; i < n; i++) { + const char *tmp = nm_setting_wireless_security_get_group(s_wsec, i); + g_assert(tmp); + if (strcmp(tmp, "tkip") == 0) + found_group_tkip = TRUE; + else if (strcmp(tmp, "ccmp") == 0) + found_group_ccmp = TRUE; + else if (strcmp(tmp, "wep40") == 0) + found_group_wep40 = TRUE; + else if (strcmp(tmp, "wep104") == 0) + found_group_wep104 = TRUE; + } + g_assert(found_group_tkip); + g_assert(found_group_ccmp); + g_assert(found_group_wep40); + g_assert(found_group_wep104); + + /* Protocols */ + n = nm_setting_wireless_security_get_num_protos(s_wsec); + g_assert_cmpint(n, ==, 2); + for (i = 0; i < n; i++) { + const char *tmp = nm_setting_wireless_security_get_proto(s_wsec, i); + g_assert(tmp); + if (strcmp(tmp, "wpa") == 0) + found_proto_wpa = TRUE; + else if (strcmp(tmp, "rsn") == 0) + found_proto_rsn = TRUE; + } + g_assert(found_proto_wpa); + g_assert(found_proto_rsn); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + g_object_unref(connection); +} + +static void +test_read_wifi_sae(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity * s_wsec; + GBytes * ssid; + const char * expected_ssid = "blahblah"; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-sae", NULL, TYPE_WIRELESS, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-sae)"); + + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "sae"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "The king is dead."); + g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec)); +} + +static void +test_read_wifi_owe(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity * s_wsec; + GBytes * ssid; + const char * expected_ssid = "blahblah_owe"; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-owe", NULL, TYPE_WIRELESS, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah_owe (test-wifi-owe)"); + + g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert_cmpmem(g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + expected_ssid, + strlen(expected_ssid)); + + g_assert(!nm_setting_wireless_get_bssid(s_wireless)); + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure"); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "owe"); + g_assert(!nm_setting_wireless_security_get_psk(s_wsec)); + g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec)); +} + +static void +test_read_wifi_wpa_psk_2(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-2", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System ipsum (test-wifi-wpa-psk-2)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), + ==, + "They're really saying I love you. >>`<< '"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wpa_psk_unquoted(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-unquoted", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-wpa-psk-unquoted)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "54336845e2f3f321c4c7"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wpa_psk_unquoted2(void) +{ + gs_unref_object NMConnection *connection = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-unquoted2", + NULL, + TYPE_WIRELESS, + NULL); +} + +static void +test_read_wifi_wpa_psk_adhoc(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-adhoc", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-wpa-psk-adhoc)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc"); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), + ==, + "I wonder what the king is doing tonight?"); + + g_assert_cmpint(nm_setting_wireless_security_get_num_pairwise(s_wsec), ==, 1); + g_assert_cmpstr(nm_setting_wireless_security_get_pairwise(s_wsec, 0), ==, "ccmp"); + + g_assert_cmpint(nm_setting_wireless_security_get_num_groups(s_wsec), ==, 1); + g_assert_cmpstr(nm_setting_wireless_security_get_group(s_wsec, 0), ==, "ccmp"); + + g_assert_cmpint(nm_setting_wireless_security_get_num_protos(s_wsec), ==, 1); + g_assert_cmpstr(nm_setting_wireless_security_get_proto(s_wsec, 0), ==, "rsn"); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + g_object_unref(connection); +} + +static void +test_read_wifi_wpa_psk_hex(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + GBytes * ssid; + const char * expected_ssid = "blahblah"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-hex", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System blahblah (test-wifi-wpa-psk-hex)"); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid))); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), + ==, + "1da190379817bc360dda52e85c388c439a21ea5c7bf819c64e9da051807deae6"); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + g_object_unref(connection); +} + +#define TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT TEST_IFCFG_DIR "/test_ca_cert.pem" +#define TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT TEST_IFCFG_DIR "/test1_key_and_cert.pem" +#define TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY TEST_IFCFG_DIR "/test1_key_and_cert.pem" + +static void +test_read_wifi_wpa_eap_tls(void) +{ + NMConnection * connection; + NMSettingWireless *s_wireless; + NMSettingIPConfig *s_ip4; + NMSetting8021x * s_8021x; + char * unmanaged = NULL; + const char * expected_privkey_password = "test1"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-tls", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Bill Smith"); + + /* CA Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NULL, + NM_SETTING_802_1X_CA_CERT); + + /* Client Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT, + NULL, + NM_SETTING_802_1X_CLIENT_CERT); + + /* Private Key Password */ + g_assert_cmpstr(nm_setting_802_1x_get_private_key_password(s_8021x), + ==, + expected_privkey_password); + + /* Private key */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY, + expected_privkey_password, + NM_SETTING_802_1X_PRIVATE_KEY); + + g_object_unref(connection); +} + +/* Also use TLS defines from the previous test */ + +static void +test_read_wifi_wpa_eap_ttls_tls(void) +{ + NMConnection * connection; + NMSettingWireless *s_wireless; + NMSettingIPConfig *s_ip4; + NMSetting8021x * s_8021x; + char * unmanaged = NULL; + const char * expected_privkey_password = "test1"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-ttls-tls", + NULL, + TYPE_WIRELESS, + &unmanaged); + g_assert(!unmanaged); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Chuck Shumer"); + + /* CA Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_DIR "/test_ca_cert.pem", + NULL, + NM_SETTING_802_1X_CA_CERT); + + /* Inner auth method */ + g_assert_cmpstr(nm_setting_802_1x_get_phase2_autheap(s_8021x), ==, "tls"); + + /* Inner CA Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NULL, + NM_SETTING_802_1X_PHASE2_CA_CERT); + + /* Inner Client Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT, + NULL, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + + /* Inner Private Key Password */ + g_assert_cmpstr(nm_setting_802_1x_get_phase2_private_key_password(s_8021x), + ==, + expected_privkey_password); + + /* Inner private key */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY, + expected_privkey_password, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY); + + g_object_unref(connection); +} + +static void +test_read_wifi_wpa_eap_suite_b_192_tls(void) +{ + NMConnection * connection; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-suite-b-192-tls", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wireless_sec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wireless_sec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec), + ==, + "wpa-eap-suite-b-192"); + + g_object_unref(connection); +} + +static void +test_read_wifi_dynamic_wep_leap(void) +{ + NMConnection * connection; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x * s_8021x; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-dynamic-wep-leap", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== WIRELESS SETTING ===== */ + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + /* ===== Wi-Fi SECURITY SETTING ===== */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + + /* Key management */ + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x"); + + /* Auth alg should be NULL (open) for dynamic WEP with LEAP as the EAP method; + * only "old-school" LEAP uses 'leap' for the auth alg. + */ + g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, NULL); + + /* Expect no old-school LEAP username/password, that'll be in the 802.1x setting */ + g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, NULL); + g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, NULL); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + /* EAP method should be "leap" */ + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "leap"); + + /* username & password */ + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "bill smith"); + g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_eap_ttls_chap(void) +{ + NMConnection * connection; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig * s_ip4; + NMSetting8021x * s_8021x; + char * unmanaged = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-eap-ttls-chap", + NULL, + TYPE_WIRELESS, + &unmanaged); + g_assert(!unmanaged); + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + /* ===== 802.1x SETTING ===== */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x"); + + /* ===== 802.1x SETTING ===== */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + /* EAP methods */ + g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1); + g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls"); + + /* CA Cert */ + verify_cert_or_key(s_8021x, + TEST_IFCFG_DIR "/test_ca_cert.pem", + NULL, + NM_SETTING_802_1X_CA_CERT); + + g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, "chap"); + g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith"); + g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz"); + + g_object_unref(connection); +} + +static void +test_read_wired_wake_on_lan(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-wake-on-lan", + NULL, + TYPE_WIRELESS, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_wake_on_lan(s_wired), + ==, + NM_SETTING_WIRED_WAKE_ON_LAN_ARP | NM_SETTING_WIRED_WAKE_ON_LAN_PHY + | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC); + + g_assert_cmpstr(nm_setting_wired_get_wake_on_lan_password(s_wired), ==, "00:11:22:33:44:55"); + + g_object_unref(connection); +} + +static void +test_read_wired_auto_negotiate_off(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-wake-on-lan", + NULL, + TYPE_ETHERNET, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + g_assert(!nm_setting_wired_get_auto_negotiate(s_wired)); + g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 100); + g_assert_cmpstr(nm_setting_wired_get_duplex(s_wired), ==, "full"); +} + +static void +test_read_wired_auto_negotiate_on(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-auto-negotiate-on", + NULL, + TYPE_ETHERNET, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + g_assert(nm_setting_wired_get_auto_negotiate(s_wired)); + g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 0); + g_assert_cmpstr(nm_setting_wired_get_duplex(s_wired), ==, NULL); +} + +static void +test_read_wired_unknown_ethtool_opt(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-unknown-ethtool-opt", + NULL, + TYPE_ETHERNET, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + g_assert(!nm_setting_wired_get_auto_negotiate(s_wired)); + g_assert(!nm_setting_wired_get_speed(s_wired)); + g_assert(!nm_setting_wired_get_duplex(s_wired)); + + g_assert_cmpint(nm_setting_wired_get_wake_on_lan(s_wired), + ==, + NM_SETTING_WIRED_WAKE_ON_LAN_ARP | NM_SETTING_WIRED_WAKE_ON_LAN_PHY + | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC); + g_assert_cmpstr(nm_setting_wired_get_wake_on_lan_password(s_wired), ==, "00:11:22:33:44:55"); +} + +static void +test_read_wifi_hidden(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wifi; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-hidden", NULL, TYPE_WIRELESS, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRELESS_SETTING_NAME); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + g_assert(nm_setting_wireless_get_hidden(s_wifi) == TRUE); + + g_object_unref(connection); +} + +static void +test_write_wifi_hidden(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + shvarFile * f; + GBytes * ssid; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wi-Fi Hidden", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); + + g_bytes_unref(ssid); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Hidden.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "SSID_HIDDEN", "yes"); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_wifi_mac_random(gconstpointer user_data) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free char * path = NULL; + NMSettingWireless * s_wifi; + const char * name; + gpointer value_p; + NMSettingMacRandomization value; + + nmtst_test_data_unpack(user_data, &name, &value_p); + value = GPOINTER_TO_INT(value_p); + + path = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wifi-mac-random-%s", name); + connection = _connection_from_file(path, NULL, TYPE_WIRELESS, NULL); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + g_assert_cmpint(nm_setting_wireless_get_mac_address_randomization(s_wifi), ==, value); +} + +static void +test_write_wifi_mac_random(gconstpointer user_data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + char * val; + shvarFile * f; + GBytes * ssid; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + const char * name, *write_expected; + gpointer value_p; + NMSettingMacRandomization value; + char cexpected[NM_STRLEN(TEST_IFCFG_DIR) + 100]; + + nmtst_test_data_unpack(user_data, &name, &value_p, &write_expected); + value = GPOINTER_TO_INT(value_p); + + g_assert(write_expected); + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + val = g_strdup_printf("Test Write Wi-Fi MAC %s", name); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + val, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free(val); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION, + value, + NULL); + g_bytes_unref(ssid); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp( + connection, + TEST_SCRATCH_DIR, + nm_sprintf_buf(cexpected, TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_MAC_%s.cexpected", name), + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "MAC_ADDRESS_RANDOMIZATION", write_expected); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_wake_on_lan(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingWiredWakeOnLan wol; + char * val; + shvarFile * f; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired Wake-on-LAN", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + wol = NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST | NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST + | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC; + + g_object_set(s_wired, + NM_SETTING_WIRED_WAKE_ON_LAN, + wol, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, + "00:00:00:11:22:33", + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected", + &testfile); + + f = _svOpenFile(testfile); + val = svGetValueStr_cp(f, "ETHTOOL_OPTS"); + g_assert(val); + g_assert(strstr(val, "wol")); + g_assert(strstr(val, "sopass 00:00:00:11:22:33")); + g_free(val); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_auto_negotiate_off(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingWired * s_wired; + char * val; + shvarFile * f; + + connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nm_connection_get_setting_wired(connection); + g_object_set(s_wired, + NM_SETTING_WIRED_AUTO_NEGOTIATE, + FALSE, + NM_SETTING_WIRED_DUPLEX, + "half", + NM_SETTING_WIRED_SPEED, + 10, + NULL); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected", + &testfile); + + f = _svOpenFile(testfile); + val = svGetValueStr_cp(f, "ETHTOOL_OPTS"); + g_assert(val); + g_assert(strstr(val, "autoneg off")); + g_assert(strstr(val, "speed 10")); + g_assert(strstr(val, "duplex half")); + g_free(val); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_auto_negotiate_on(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingWired * s_wired; + NMSettingEthtool * s_ethtool; + char * val; + shvarFile * f; + + connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nm_connection_get_setting_wired(connection); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL); + + s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX, NM_TERNARY_TRUE); + nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN, NM_TERNARY_FALSE); + nm_connection_add_setting(connection, NM_SETTING(s_ethtool)); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test_write_wired_auto_negotiate_on.cexpected", + &testfile); + + f = _svOpenFile(testfile); + val = svGetValueStr_cp(f, "ETHTOOL_OPTS"); + g_assert(val); + g_assert(strstr(val, "autoneg on")); + g_assert(!strstr(val, "speed")); + g_assert(!strstr(val, "duplex")); + g_free(val); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_verifies_without_normalization(reread); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(reread, NM_TYPE_SETTING_ETHTOOL)); + g_assert(s_ethtool); + g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN), + ==, + NM_TERNARY_DEFAULT); +} + +static void +test_read_wifi_band_a(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wifi; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-a", NULL, TYPE_WIRELESS, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRELESS_SETTING_NAME); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + g_assert_cmpstr(nm_setting_wireless_get_band(s_wifi), ==, "a"); + + g_object_unref(connection); +} + +static void +test_write_wifi_band_a(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + shvarFile * f; + GBytes * ssid; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wi-Fi Band A", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NM_SETTING_WIRELESS_BAND, + "a", + NULL); + + g_bytes_unref(ssid); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Band_A.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "BAND", "a"); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_ap_mode(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + gs_unref_bytes GBytes *ssid = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wi-Fi AP Mode", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new("MySSID", NM_STRLEN("MySSID")); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "ap", + NM_SETTING_WIRELESS_BAND, + "a", + NM_SETTING_WIRELESS_CHANNEL, + (guint) 196, + NM_SETTING_WIRELESS_AP_ISOLATION, + NM_TERNARY_TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_AP_Mode.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_wifi_band_a_channel_mismatch(void) +{ + gs_free_error GError *error = NULL; + + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-a-channel-mismatch", + NULL, + TYPE_WIRELESS, + &error); + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); +} + +static void +test_read_wifi_band_bg_channel_mismatch(void) +{ + gs_free_error GError *error = NULL; + + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-bg-channel-mismatch", + NULL, + TYPE_WIRELESS, + &error); + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); +} + +static void +test_read_wired_qeth_static(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * unmanaged = NULL; + const char *const * subchannels; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-qeth-static", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(!unmanaged); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-qeth-static"); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + g_assert(!nm_setting_wired_get_mac_address(s_wired)); + + /* Subchannels */ + subchannels = nm_setting_wired_get_s390_subchannels(s_wired); + g_assert(subchannels); + g_assert(subchannels[0] && subchannels[1] && subchannels[2] && !subchannels[3]); + + g_assert_cmpstr(subchannels[0], ==, "0.0.0600"); + g_assert_cmpstr(subchannels[1], ==, "0.0.0601"); + g_assert_cmpstr(subchannels[2], ==, "0.0.0602"); + + g_assert_cmpstr(nm_setting_wired_get_s390_nettype(s_wired), ==, "qeth"); + g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "portname"), ==, "OSAPORT"); + g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "portno"), ==, "0"); + g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "layer2"), ==, "1"); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_object_unref(connection); +} + +static void +test_read_wired_ctc_static(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + char * unmanaged = NULL; + const char *const * subchannels; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ctc-static", + NULL, + TYPE_ETHERNET, + &unmanaged); + g_assert(unmanaged == NULL); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con != NULL); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ctc-static"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired != NULL); + + g_assert(nm_setting_wired_get_mac_address(s_wired) == NULL); + + /* Subchannels */ + subchannels = nm_setting_wired_get_s390_subchannels(s_wired); + g_assert(subchannels != NULL); + g_assert(subchannels[0] && subchannels[1] && !subchannels[2]); + + g_assert_cmpstr(subchannels[0], ==, "0.0.1b00"); + g_assert_cmpstr(subchannels[1], ==, "0.0.1b01"); + + g_assert_cmpstr(nm_setting_wired_get_s390_nettype(s_wired), ==, "ctc"); + g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot"), ==, "0"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_no_keys(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingWirelessSecurity *s_wsec; + NMWepKeyType key_type; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-no-keys", + NULL, + TYPE_WIRELESS, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), + ==, + "System foobar (test-wifi-wep-no-keys)"); + + /* UUID can't be tested if the ifcfg does not contain the UUID key, because + * the UUID is generated on the full path of the ifcfg file, which can change + * depending on where the tests are run. + */ + + /* ===== WIRELESS SETTING ===== */ + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + /* ===== WIRELESS SECURITY SETTING ===== */ + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + + /* Key management */ + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none"); + + /* WEP key index */ + g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0); + + /* WEP key type */ + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY); + + /* WEP key index 0; we don't expect it to be filled */ + g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 0)); + + g_object_unref(connection); +} + +static void +test_read_permissions(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + gboolean success; + guint32 num; + const char * tmp; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-permissions", NULL, TYPE_ETHERNET, NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + num = nm_setting_connection_get_num_permissions(s_con); + g_assert_cmpint(num, ==, 3); + + /* verify each permission */ + tmp = NULL; + success = nm_setting_connection_get_permission(s_con, 0, NULL, &tmp, NULL); + g_assert(success); + g_assert_cmpstr(tmp, ==, "dcbw"); + + tmp = NULL; + success = nm_setting_connection_get_permission(s_con, 1, NULL, &tmp, NULL); + g_assert(success); + g_assert_cmpstr(tmp, ==, "ssmith"); + + tmp = NULL; + success = nm_setting_connection_get_permission(s_con, 2, NULL, &tmp, NULL); + g_assert(success); + g_assert_cmpstr(tmp, ==, "johnny5"); + + g_object_unref(connection); +} + +static void +test_read_wifi_wep_agent_keys(void) +{ + NMConnection * connection; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMWepKeyType key_type; + NMSettingSecretFlags flags; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-agent-keys", + NULL, + TYPE_WIRELESS, + NULL); + + /* Ensure the connection is still marked for wifi security even though + * we don't have any WEP keys because they are agent owned. + */ + + /* ===== WIRELESS SETTING ===== */ + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + /* ===== WIRELESS SECURITY SETTING ===== */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + + g_assert(strcmp(nm_setting_wireless_security_get_key_mgmt(s_wsec), "none") == 0); + g_assert(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) == 0); + + key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec); + g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY); + + /* We don't expect WEP key0 to be filled */ + g_assert(nm_setting_wireless_security_get_wep_key(s_wsec, 0) == NULL); + + flags = nm_setting_wireless_security_get_wep_key_flags(s_wsec); + g_assert(flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED); + + g_object_unref(connection); +} + +static void +test_write_wired_static(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *route6file = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4, *reread_s_ip4; + NMSettingIPConfig * s_ip6, *reread_s_ip6; + NMIPAddress * addr; + NMIPAddress * addr6; + NMIPRoute * route6; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired Static", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, + 1, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, + NM_SETTING_WIRED_MAC_ADDRESS, + "31:33:33:37:be:cd", + NM_SETTING_WIRED_MTU, + (guint32) 1492, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 204, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1"); + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2"); + + nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com"); + nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 206, + NULL); + + /* Add addresses */ + addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + addr6 = nm_ip_address_new(AF_INET6, "2003:1234:abcd::2", 22, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + addr6 = nm_ip_address_new(AF_INET6, "3003:1234:abcd::3", 33, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + /* Add routes */ + route6 = nm_ip_route_new(AF_INET6, + "2222:aaaa:bbbb:cccc::", + 64, + "2222:aaaa:bbbb:cccc:dddd:eeee:5555:6666", + 99, + &error); + g_assert_no_error(error); + nm_setting_ip_config_add_route(s_ip6, route6); + nm_ip_route_unref(route6); + + route6 = nm_ip_route_new(AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error); + g_assert_no_error(error); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(100)); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1280)); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean(TRUE)); + nm_ip_route_set_attribute(route6, + NM_IP_ROUTE_ATTRIBUTE_FROM, + g_variant_new_string("2222::bbbb/32")); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("::42")); + nm_setting_ip_config_add_route(s_ip6, route6); + nm_ip_route_unref(route6); + + /* DNS servers */ + nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face"); + nm_setting_ip_config_add_dns(s_ip6, "cafe:ffff:eeee:dddd:cccc:bbbb:aaaa:feed"); + + /* DNS domains */ + nm_setting_ip_config_add_dns_search(s_ip6, "foobar6.com"); + nm_setting_ip_config_add_dns_search(s_ip6, "lab6.foobar.com"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + route6file = utils_get_route6_path(testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + reread_s_ip4 = nm_connection_get_setting_ip4_config(reread); + reread_s_ip6 = nm_connection_get_setting_ip6_config(reread); + + g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204); + g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_equals(connection, FALSE, reread, FALSE); +} + +static void +test_write_wired_static_with_generic(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *route6file = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4, *reread_s_ip4; + NMSettingIPConfig * s_ip6, *reread_s_ip6; + NMIPAddress * addr; + NMIPAddress * addr6; + NMIPRoute * route6; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired Static", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, + 1, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, + NM_SETTING_WIRED_MAC_ADDRESS, + "31:33:33:37:be:cd", + NM_SETTING_WIRED_MTU, + (guint32) 1492, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 204, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1"); + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2"); + + nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com"); + nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_ROUTE_METRIC, + (gint64) 206, + NULL); + + /* Add addresses */ + addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + addr6 = nm_ip_address_new(AF_INET6, "2003:1234:abcd::2", 22, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + addr6 = nm_ip_address_new(AF_INET6, "3003:1234:abcd::3", 33, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + /* Add routes */ + route6 = nm_ip_route_new(AF_INET6, + "2222:aaaa:bbbb:cccc::", + 64, + "2222:aaaa:bbbb:cccc:dddd:eeee:5555:6666", + 99, + &error); + g_assert_no_error(error); + nm_setting_ip_config_add_route(s_ip6, route6); + nm_ip_route_unref(route6); + + route6 = nm_ip_route_new(AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error); + g_assert_no_error(error); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(100)); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1280)); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean(TRUE)); + nm_ip_route_set_attribute(route6, + NM_IP_ROUTE_ATTRIBUTE_FROM, + g_variant_new_string("2222::bbbb/32")); + nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("::42")); + nm_setting_ip_config_add_route(s_ip6, route6); + nm_ip_route_unref(route6); + + /* DNS servers */ + nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face"); + nm_setting_ip_config_add_dns(s_ip6, "cafe:ffff:eeee:dddd:cccc:bbbb:aaaa:feed"); + + /* DNS domains */ + nm_setting_ip_config_add_dns_search(s_ip6, "foobar6.com"); + nm_setting_ip_config_add_dns_search(s_ip6, "lab6.foobar.com"); + + nm_connection_add_setting(connection, nm_setting_generic_new()); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile); + route6file = utils_get_route6_path(testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + reread_s_ip4 = nm_connection_get_setting_ip4_config(reread); + reread_s_ip6 = nm_connection_get_setting_ip6_config(reread); + + g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204); + g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + { + gs_unref_hashtable GHashTable *diffs = NULL; + + g_assert(!nm_connection_diff(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs)); + g_assert(diffs); + g_assert(g_hash_table_size(diffs) == 1); + g_assert(g_hash_table_lookup(diffs, "generic")); + g_assert(!nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT)); + } + g_assert(!nm_connection_get_setting(reread, NM_TYPE_SETTING_GENERIC)); + nm_connection_add_setting(reread, nm_setting_generic_new()); + { + gs_unref_hashtable GHashTable *diffs = NULL; + + g_assert(nm_connection_diff(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs)); + g_assert(!diffs); + g_assert(nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT)); + } +} + +static void +test_write_wired_dhcp(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired DHCP", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, + "random-client-id-00:22:33", + NM_SETTING_IP4_CONFIG_DHCP_FQDN, + "awesome.hostname", + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, + (guint) NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, + TRUE, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + TRUE, + NM_SETTING_IP_CONFIG_DHCP_IAID, + "2864434397", + NULL); + + nmtst_assert_connection_verifies(connection); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + "awesome.hostname", + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, + (guint) NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE, + NULL); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static NMIPRoutingRule * +_ip_routing_rule_new(int addr_family, const char *str) +{ + NMIPRoutingRuleAsStringFlags flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE; + gs_free_error GError *local = NULL; + NMIPRoutingRule * rule; + + if (addr_family != AF_UNSPEC) { + if (addr_family == AF_INET) + flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET; + else { + g_assert(addr_family == AF_INET6); + flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6; + } + } + + rule = nm_ip_routing_rule_from_string(str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE | flags, + NULL, + nmtst_get_rand_bool() ? &local : NULL); + nmtst_assert_success(rule, local); + + if (addr_family != AF_UNSPEC) + g_assert_cmpint(nm_ip_routing_rule_get_addr_family(rule), ==, addr_family); + return rule; +} + +static void +_ip_routing_rule_add_to_setting(NMSettingIPConfig *s_ip, const char *str) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + + rule = _ip_routing_rule_new(nm_setting_ip_config_get_addr_family(s_ip), str); + nm_setting_ip_config_add_routing_rule(s_ip, rule); +} + +static void +test_write_routing_rules(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Routing Rules", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + _ip_routing_rule_add_to_setting(s_ip4, "pref 10 from 0.0.0.0/0 table 1"); + _ip_routing_rule_add_to_setting(s_ip4, "priority 10 to 192.167.8.0/24 table 2"); + _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 table 10"); + _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 to 1:2:3::5/24 table 22"); + _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 to 1:3:3::5/128 table 55"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Routing_Rules.cexpected", + &testfile); + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_match(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingMatch * s_match; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired with Match setting", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + /* Match setting */ + s_match = (NMSettingMatch *) nm_setting_match_new(); + nm_setting_match_add_interface_name(s_match, "ens*"); + nm_setting_match_add_interface_name(s_match, "eth 1?"); + nm_setting_match_add_interface_name(s_match, "!veth*"); + nm_setting_match_add_driver(s_match, "!virtio"); + nm_setting_match_add_driver(s_match, "e1000e"); + nm_setting_match_add_kernel_command_line(s_match, "!ip="); + nm_connection_add_setting(connection, NM_SETTING(s_match)); + + nmtst_assert_connection_verifies(connection); + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_match.cexpected", + &testfile); + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_dhcp_plus_ip(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-plus-ip", + NULL, + TYPE_ETHERNET, + NULL); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_write_wired_dhcp_send_hostname(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * dhcp_hostname = "kamil-patka"; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-send-hostname", + NULL, + TYPE_ETHERNET, + NULL); + + /* Check dhcp-hostname and dhcp-send-hostname */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip4); + g_assert(s_ip6); + g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == TRUE); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "svata-pulec"); + g_assert(!nm_setting_ip_config_get_dhcp_hostname(s_ip6)); + + /* Set dhcp-send-hostname=false dhcp-hostname="kamil-patka" and write the connection. */ + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, FALSE, NULL); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, dhcp_hostname, NULL); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, dhcp_hostname, NULL); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + /* Check dhcp-hostname and dhcp-send-hostname from the re-read connection. */ + s_ip4 = nm_connection_get_setting_ip4_config(reread); + s_ip6 = nm_connection_get_setting_ip6_config(reread); + g_assert(s_ip4); + g_assert(s_ip6); + g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == FALSE); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, dhcp_hostname); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, dhcp_hostname); +} + +static void +test_read_wired_dhcpv6_hostname_fallback(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingIPConfig * s_ip6; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcpv6-hostname-fallback", + NULL, + TYPE_ETHERNET, + NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip6) == TRUE); + g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "fully.qualified.domain"); +} + +static void +test_write_wired_static_ip6_only(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * addr6; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired Static IP6 Only", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); + + /* Add addresses */ + addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + /* DNS server */ + nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_ip6_disabled(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nmtst_create_minimal_connection("Test Write Wired Disabled IP6", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR_TMP, + TEST_IFCFG_DIR "/ifcfg-test-ip6-disabled.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +/* Test writing an IPv6 config with varying gateway address. + * For missing gateway (::), we expect no IPV6_DEFAULTGW to be written + * to ifcfg-rh. + * + * As user_data pass the IPv6 address of the gateway as string. NULL means + * not to explicitly set the gateway in the configuration before writing it. + * That way, the gateway actually defaults to "::". + */ +static void +test_write_wired_static_ip6_only_gw(gconstpointer user_data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * addr6; + GError * error = NULL; + char * id = NULL; + gs_free char * written_ifcfg_gateway = NULL; + const char * gateway6 = user_data; + shvarFile * ifcfg; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + id = g_strdup_printf("Test Write Wired Static IP6 Only With Gateway %s", gateway6 ?: "NULL"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + g_free(id); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + gateway6, + NULL); + + /* Add addresses */ + addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr6); + nm_ip_address_unref(addr6); + + /* DNS server */ + nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + ifcfg = _svOpenFile(testfile); + written_ifcfg_gateway = svGetValueStr_cp(ifcfg, "IPV6_DEFAULTGW"); + svCloseFile(ifcfg); + + /* access the gateway from the loaded connection. */ + s_ip6 = nm_connection_get_setting_ip6_config(reread); + g_assert(s_ip6 && nm_setting_ip_config_get_num_addresses(s_ip6) == 1); + addr6 = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(addr6); + + /* assert that the gateway was written and reloaded as expected */ + if (!gateway6 || !strcmp(gateway6, "::")) { + g_assert(nm_setting_ip_config_get_gateway(s_ip6) == NULL); + g_assert(written_ifcfg_gateway == NULL); + } else { + g_assert(nm_setting_ip_config_get_gateway(s_ip6) != NULL); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, gateway6); + g_assert_cmpstr(written_ifcfg_gateway, ==, gateway6); + } +} + +static void +test_read_write_static_routes_legacy(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *routefile = NULL; + gs_free char * route6file = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + const char * tmp; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-static-routes-legacy", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== CONNECTION SETTING ===== */ + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + /* ID */ + tmp = nm_setting_connection_get_id(s_con); + g_assert(tmp); + + /* Autoconnect */ + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + /* ===== WIRED SETTING ===== */ + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + g_assert(!nm_setting_ip_config_get_never_default(s_ip4)); + + /* Save the ifcfg; use a special different scratch dir to ensure that + * we can clean up after the written connection in both the original + * source tree and for 'make distcheck'. + */ + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR_TMP, + TEST_IFCFG_DIR "/ifcfg-test-static-routes-legacy.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + routefile = utils_get_route_path(testfile); + route6file = utils_get_route6_path(testfile); + g_assert(!g_file_test(route6file, G_FILE_TEST_EXISTS)); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_static_routes(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *routefile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPAddress * addr; + NMIPRoute * route; + GError * error = NULL; + gboolean reread_same = FALSE; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired Static Routes", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, + NM_SETTING_WIRED_MAC_ADDRESS, + "31:33:33:37:be:cd", + NM_SETTING_WIRED_MTU, + (guint32) 1492, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_DAD_TIMEOUT, + 400, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* Write out routes */ + route = nm_ip_route_new(AF_INET, "1.2.3.0", 24, "222.173.190.239", 0, &error); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, g_variant_new_uint32(3455)); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE)); + g_assert_no_error(error); + nm_setting_ip_config_add_route(s_ip4, route); + nm_ip_route_unref(route); + + route = nm_ip_route_new(AF_INET, "3.2.1.0", 24, "202.254.186.190", 77, &error); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, g_variant_new_uint32(30000)); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(FALSE)); + g_assert_no_error(error); + nm_setting_ip_config_add_route(s_ip4, route); + nm_ip_route_unref(route); + + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1"); + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2"); + + nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com"); + nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_reread(connection, + TEST_SCRATCH_DIR, + &testfile, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Static_Routes.cexpected", + &reread, + &reread_same); + /* ifcfg does not support setting onlink=0. It gets lost during write+re-read. + * Assert that it's missing, and patch it to check whether the rest of the + * connection equals. */ + g_assert(!reread_same); + nmtst_assert_connection_verifies_without_normalization(reread); + s_ip4 = nm_connection_get_setting_ip4_config(reread); + g_assert(s_ip4); + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 2); + route = nm_setting_ip_config_get_route(s_ip4, 1); + g_assert(route); + g_assert(!nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK)); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(FALSE)); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + routefile = utils_get_route_path(testfile); +} + +static void +test_write_wired_dhcp_8021x_peap_mschapv2(void) +{ + nmtst_auto_unlinkfile char *keyfile = NULL; + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSetting8021x * s_8021x; + gboolean success; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired DHCP 802.1x PEAP MSCHAPv2", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + /* 802.1x setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + "Bob Saget", + NM_SETTING_802_1X_ANONYMOUS_IDENTITY, + "barney", + NM_SETTING_802_1X_PASSWORD, + "Kids, it was back in October 2008...", + NM_SETTING_802_1X_PHASE1_PEAPVER, + "1", + NM_SETTING_802_1X_PHASE1_PEAPLABEL, + "1", + NM_SETTING_802_1X_PHASE2_AUTH, + "mschapv2", + NULL); + + nm_setting_802_1x_add_eap_method(s_8021x, "peap"); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + keyfile = utils_get_keys_path(testfile); +} + +static void +test_write_wired_8021x_tls(gconstpointer test_data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_free char * keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSetting8021x * s_8021x; + gboolean success; + GError * error = NULL; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + const char * pw; + char * tmp; + gpointer scheme_p, flags_p; + NMSetting8021xCKScheme scheme; + NMSettingSecretFlags flags; + + nmtst_test_data_unpack(test_data, &scheme_p, &flags_p); + scheme = GPOINTER_TO_INT(scheme_p); + flags = GPOINTER_TO_INT(flags_p); + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired 802.1x TLS Blobs", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + /* 802.1x setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + + /* CA cert */ + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_IFCFG_DIR "/test_ca_cert.pem", + scheme, + &format, + &error); + g_assert_no_error(error); + g_assert(success); + g_assert(format == NM_SETTING_802_1X_CK_FORMAT_X509); + + /* Client cert */ + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + success = nm_setting_802_1x_set_client_cert(s_8021x, + TEST_IFCFG_DIR "/test1_key_and_cert.pem", + scheme, + &format, + &error); + g_assert_no_error(error); + g_assert(success); + g_assert(format == NM_SETTING_802_1X_CK_FORMAT_X509); + + /* Private key */ + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + success = nm_setting_802_1x_set_private_key(s_8021x, + TEST_IFCFG_DIR "/test1_key_and_cert.pem", + "test1", + scheme, + &format, + &error); + g_assert_no_error(error); + g_assert(success); + g_assert(format == NM_SETTING_802_1X_CK_FORMAT_RAW_KEY); + + /* Set secret flags */ + g_object_set(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, flags, NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + nmtst_file_unlink_if_exists(keyfile); + + /* Ensure the reread connection's certificates and private key are paths; no + * matter what scheme was used in the original connection they will be read + * back in as paths. + */ + s_8021x = nm_connection_get_setting_802_1x(reread); + g_assert(s_8021x); + g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpint(nm_setting_802_1x_get_client_cert_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpint(nm_setting_802_1x_get_private_key_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + + g_assert_cmpint(nm_setting_802_1x_get_private_key_password_flags(s_8021x), ==, flags); + pw = nm_setting_802_1x_get_private_key_password(s_8021x); + if (flags == NM_SETTING_SECRET_FLAG_NONE) { + /* Ensure the private key password is still set */ + g_assert(pw != NULL); + g_assert_cmpstr(pw, ==, "test1"); + } else { + /* If the secret isn't owned by system settings, make sure its no longer there */ + g_assert(pw == NULL); + } + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + /* Do a direct compare if using the path scheme since then the + * certificate and key properties should be the same. If using blob + * scheme the original connection cert/key properties will be blobs + * but the re-read connection is always path scheme, so we wouldn't + * expect it to compare successfully. + */ + if (flags != NM_SETTING_SECRET_FLAG_NONE) { + /* Clear original connection's private key password because flags + * say it's not system-owned, and therefore it should not show up + * in the re-read connection. + */ + s_8021x = nm_connection_get_setting_802_1x(connection); + g_object_set(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, NULL, NULL); + } + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + } + + /* Clean up created certs and keys */ + tmp = utils_cert_path(testfile, "ca-cert", "der"); + nmtst_file_unlink_if_exists(tmp); + g_free(tmp); + + tmp = utils_cert_path(testfile, "client-cert", "der"); + nmtst_file_unlink_if_exists(tmp); + g_free(tmp); + + tmp = utils_cert_path(testfile, "private-key", "pem"); + nmtst_file_unlink_if_exists(tmp); + g_free(tmp); +} + +static void +test_write_wired_aliases(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + int num_addresses = 4; + const char * ip[] = {"1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4"}; + const char * label[] = {NULL, "alias0:2", NULL, "alias0:3"}; + NMIPAddress * addr; + GError * error = NULL; + shvarFile * ifcfg; + int i, j; + + nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":2"); + nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":3"); + nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":5"); + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "alias0", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + for (i = 0; i < num_addresses; i++) { + addr = nm_ip_address_new(AF_INET, ip[i], 24, &error); + g_assert_no_error(error); + if (label[i]) + nm_ip_address_set_attribute(addr, + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string(label[i])); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + } + + nmtst_assert_connection_verifies(connection); + + /* Create some pre-existing alias files, to make sure they get overwritten / deleted. */ + ifcfg = svCreateFile(TEST_SCRATCH_ALIAS_BASE ":2"); + svSetValueStr(ifcfg, "DEVICE", "alias0:2"); + svSetValueStr(ifcfg, "IPADDR", "192.168.1.2"); + svWriteFile(ifcfg, 0644, NULL); + svCloseFile(ifcfg); + g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":2", G_FILE_TEST_EXISTS)); + + ifcfg = svCreateFile(TEST_SCRATCH_ALIAS_BASE ":5"); + svSetValueStr(ifcfg, "DEVICE", "alias0:5"); + svSetValueStr(ifcfg, "IPADDR", "192.168.1.5"); + svWriteFile(ifcfg, 0644, NULL); + svCloseFile(ifcfg); + g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":5", G_FILE_TEST_EXISTS)); + + _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile); + + /* Re-check the alias files */ + g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":2", G_FILE_TEST_EXISTS)); + g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":3", G_FILE_TEST_EXISTS)); + g_assert(!g_file_test(TEST_SCRATCH_ALIAS_BASE ":5", G_FILE_TEST_EXISTS)); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + nmtst_file_unlink(TEST_SCRATCH_ALIAS_BASE ":2"); + nmtst_file_unlink(TEST_SCRATCH_ALIAS_BASE ":3"); + + /* nm_connection_compare() is not guaranteed to succeed, because the + * aliases get read back in essentially random order. So just + * verify the aliases manually. + */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(nm_setting_ip_config_get_num_addresses(s_ip4) == num_addresses); + + /* Addresses */ + for (i = 0; i < num_addresses; i++) { + const char *addrstr; + + addr = nm_setting_ip_config_get_address(s_ip4, i); + g_assert(addr != NULL); + + addrstr = nm_ip_address_get_address(addr); + for (j = 0; j < num_addresses; j++) { + if (!g_strcmp0(addrstr, ip[j])) + break; + } + if (j >= num_addresses) + g_assert_not_reached(); + else { + g_assert_cmpint(nm_ip_address_get_prefix(addr), ==, 24); + if (label[j]) + g_assert_cmpstr( + g_variant_get_string( + nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL), + NULL), + ==, + label[j]); + else + g_assert(nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL); + ip[j] = NULL; + } + } + + for (i = 0; i < num_addresses; i++) + g_assert(!ip[i]); + + /* Gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1"); +} + +static void +test_write_gateway(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + GError * error = NULL; + shvarFile * f; + NMIPAddress * addr; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Static Addresses Gateway", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.254", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + addr = nm_ip_address_new(AF_INET, "2.2.2.5", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "IPADDR", "1.1.1.3"); + _svGetValue_check(f, "IPADDR1", "2.2.2.5"); + _svGetValue_check(f, "IPADDR0", NULL); + _svGetValue_check(f, "PREFIX", "24"); + _svGetValue_check(f, "PREFIX1", "24"); + _svGetValue_check(f, "PREFIX0", NULL); + _svGetValue_check(f, "GATEWAY", "1.1.1.254"); + _svGetValue_check(f, "GATEWAY0", NULL); + _svGetValue_check(f, "GATEWAY1", NULL); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_open(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + shvarFile * ifcfg; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi Open", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_BSSID, + "11:22:33:44:55:66", + NM_SETTING_WIRELESS_MAC_ADDRESS, + "aa:bb:cc:dd:ee:ff", + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NM_SETTING_WIRELESS_BAND, + "bg", + NM_SETTING_WIRELESS_CHANNEL, + (guint32) 9, + NM_SETTING_WIRELESS_MTU, + (guint32) 1345, + NULL); + + g_bytes_unref(ssid); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + ifcfg = _svOpenFile(testfile); + _svGetValue_check(ifcfg, "ESSID", "Test SSID"); + svCloseFile(ifcfg); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_open_hex_ssid(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const unsigned char ssid_data[] = + {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd}; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi Open Hex SSID", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + struct stat statbuf; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + 2, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "shared", + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "0123456789abcdef0123456789"); + nm_setting_wireless_security_set_wep_key(s_wsec, 1, "11111111111111111111111111"); + nm_setting_wireless_security_set_wep_key(s_wsec, 2, "aaaaaaaaaaaaaaaaaaaaaaaaaa"); + nm_setting_wireless_security_set_wep_key(s_wsec, 3, "BBBBBBBBBBBBBBBBBBBBBBBBBB"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep_adhoc(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GError * error = NULL; + GBytes * ssid; + const char * ssid_data = "blahblah"; + struct stat statbuf; + NMIPAddress * addr; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP AdHoc", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_MODE, "adhoc", NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "0123456789abcdef0123456789"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NULL); + + /* IP Address */ + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep_passphrase(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + struct stat statbuf; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP Passphrase", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + 0, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "shared", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_PASSPHRASE, + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "asdfdjaslfjasd;flasjdfl;aksdf"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep_40_ascii(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah40"; + struct stat statbuf; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP 40 ASCII", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + 2, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_KEY, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "shared", + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "lorem"); + nm_setting_wireless_security_set_wep_key(s_wsec, 1, "ipsum"); + nm_setting_wireless_security_set_wep_key(s_wsec, 2, "dolor"); + nm_setting_wireless_security_set_wep_key(s_wsec, 3, "donec"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep_104_ascii(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah104"; + struct stat statbuf; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP 104 ASCII", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + 0, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_UNKNOWN, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "LoremIpsumSit"); + nm_setting_wireless_security_set_wep_key(s_wsec, 1, "AlfaBetaGamma"); + nm_setting_wireless_security_set_wep_key(s_wsec, 2, "WEP-104 ASCII"); + nm_setting_wireless_security_set_wep_key(s_wsec, 3, "thisismyascii"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_leap(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + struct stat statbuf; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi LEAP", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "leap", + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, + "Bill Smith", + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + "foobar22", + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Wifi_LEAP.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + g_assert_cmpint(stat(keyfile, &statbuf), ==, 0); + g_assert(S_ISREG(statbuf.st_mode)); + g_assert_cmpint((statbuf.st_mode & 0077), ==, 0); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_leap_secret_flags(gconstpointer data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_free char * keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + NMSettingSecretFlags flags = GPOINTER_TO_UINT(data); + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi LEAP Secret Flags", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "leap", + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, + "Bill Smith", + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + "foobar22", + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, + flags, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + /* No key should be written out since the secret is not system owned */ + keyfile = utils_get_keys_path(testfile); + g_assert(g_file_test(keyfile, G_FILE_TEST_EXISTS) == FALSE); + + /* Remove the LEAP password from the original connection since it wont' be + * in the reread connection, as the password is not system owned. + */ + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, NULL, NULL); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_psk(gconstpointer test_data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + struct { + const char *name, *psk; + gpointer wep_group_p, wpa_p, wpa2_p; + } args; + + nmtst_test_data_unpack(test_data, + &args.name, + &args.wep_group_p, + &args.wpa_p, + &args.wpa2_p, + &args.psk); + + g_assert(args.psk); + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + args.name, + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + args.psk, + NM_SETTING_WIRELESS_SECURITY_PMF, + (int) NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED, + NULL); + + if (GPOINTER_TO_INT(args.wep_group_p)) { + nm_setting_wireless_security_add_group(s_wsec, "wep40"); + nm_setting_wireless_security_add_group(s_wsec, "wep104"); + } + if (GPOINTER_TO_INT(args.wpa_p)) { + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + } + if (GPOINTER_TO_INT(args.wpa2_p)) { + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + } + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_psk_adhoc(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GError * error = NULL; + GBytes * ssid; + const char * ssid_data = "blahblah"; + NMIPAddress * addr; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WPA PSK", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "adhoc", + NM_SETTING_WIRELESS_CHANNEL, + 11, + NM_SETTING_WIRELESS_BAND, + "bg", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + "7d308b11df1b4243b0f78e5f3fc68cdbb9a264ed0edf4c188edf329ff5b467f0", + NULL); + + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NULL); + + /* IP Address */ + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 25, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1"); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_eap_tls(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSetting8021x * s_8021x; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + gboolean success; + GError * error = NULL; + GBytes * ssid; + const char * ssid_data = "blahblah"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WPA EAP-TLS", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-eap", + NM_SETTING_WIRELESS_SECURITY_FILS, + (int) NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED, + NULL); + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + g_object_set(s_8021x, + NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, + (guint)(NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE + | NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE), + NULL); + + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_client_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_private_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY, + "test1", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_eap_ttls_tls(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSetting8021x * s_8021x; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + gboolean success; + GError * error = NULL; + GBytes * ssid; + const char * ssid_data = "blahblah"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WPA EAP-TTLS (TLS)", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + nm_setting_802_1x_add_eap_method(s_8021x, "ttls"); + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + "Bill Smith", + NM_SETTING_802_1X_ANONYMOUS_IDENTITY, + "foobar22", + NM_SETTING_802_1X_PHASE2_AUTHEAP, + "tls", + NULL); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* Phase 2 TLS stuff */ + + /* phase2 CA cert */ + success = nm_setting_802_1x_set_phase2_ca_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* phase2 client cert */ + success = nm_setting_802_1x_set_phase2_client_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* phase2 private key */ + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY, + "test1", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_eap_ttls_mschapv2(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSetting8021x * s_8021x; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + gboolean success; + GError * error = NULL; + GBytes * ssid; + const char * ssid_data = "blahblah"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WPA EAP-TTLS (MSCHAPv2)", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL); + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + nm_setting_802_1x_add_eap_method(s_8021x, "ttls"); + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + "Bill Smith", + NM_SETTING_802_1X_PASSWORD, + ";alkdfja;dslkfjsad;lkfjsadf", + NM_SETTING_802_1X_ANONYMOUS_IDENTITY, + "foobar22", + NM_SETTING_802_1X_PHASE2_AUTHEAP, + "mschapv2", + NULL); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + &error); + nmtst_assert_success(success, error); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + keyfile = utils_get_keys_path(testfile); +} + +static void +test_write_wifi_wpa_then_open(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_free char * keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + + /* Test that writing out a WPA config then changing that to an open + * config doesn't leave various WPA-related keys lying around in the ifcfg. + */ + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "random wifi connection", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + "some cool PSK", + NULL); + + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + g_object_unref(reread); + + nmtst_connection_normalize(connection); + + /* Now change the connection to open and recheck */ + nm_connection_remove_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY); + + /* Write it back out */ + _writer_update_connection(connection, + TEST_SCRATCH_DIR, + testfile, + TEST_IFCFG_DIR "/ifcfg-random_wifi_connection.cexpected"); + keyfile = utils_get_keys_path(testfile); + g_assert(!g_file_test(keyfile, G_FILE_TEST_EXISTS)); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wpa_then_wep_with_perms(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + gboolean success; + GBytes * ssid; + char ** perms; + const char * ssid_data = "SomeSSID"; + + /* Test that writing out a WPA config then changing that to a WEP + * config works and doesn't cause infinite loop or other issues. + */ + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + perms = g_strsplit("user:superman:", ",", -1); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "random wifi connection 2", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_PERMISSIONS, + perms, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_strfreev(perms); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + "My cool PSK", + NULL); + + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + g_object_unref(reread); + + nmtst_connection_normalize(connection); + + /* Now change the connection to WEP and recheck */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "abraka dabra"); + + /* Write it back out */ + _writer_update_connection(connection, + TEST_SCRATCH_DIR, + testfile, + TEST_IFCFG_DIR "/ifcfg-random_wifi_connection_2.cexpected"); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_connection_normalize(connection); + success = nm_connection_compare(connection, + reread, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS + | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS); + g_assert(success); + + keyfile = utils_get_keys_path(testfile); +} + +static void +test_write_wifi_dynamic_wep_leap(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + nmtst_auto_unlinkfile char *keyfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSetting8021x * s_8021x; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + GBytes * ssid; + const char * ssid_data = "blahblah"; + shvarFile * ifcfg; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi Dynamic WEP LEAP", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + + g_bytes_unref(ssid); + + /* Wireless security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NULL); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + nm_setting_802_1x_add_eap_method(s_8021x, "leap"); + + g_object_set(s_8021x, + NM_SETTING_802_1X_IDENTITY, + "Bill Smith", + NM_SETTING_802_1X_PASSWORD, + ";alkdfja;dslkfjsad;lkfjsadf", + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + keyfile = utils_get_keys_path(testfile); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); + + /* Check and make sure that an "old-school" LEAP (Network EAP) connection + * did not get written. Check first that the auth alg is not set to "LEAP" + * and next that the only IEEE 802.1x EAP method is "LEAP". + */ + ifcfg = _svOpenFile(testfile); + _svGetValue_check(ifcfg, "SECURITYMODE", NULL); + _svGetValue_check(ifcfg, "IEEE_8021X_EAP_METHODS", "LEAP"); + svCloseFile(ifcfg); +} + +static void +test_write_wired_qeth_dhcp(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char ** subchans; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired qeth Static", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + subchans = g_strsplit("0.0.600,0.0.601,0.0.602", ",", -1); + g_object_set(s_wired, + NM_SETTING_WIRED_S390_SUBCHANNELS, + subchans, + NM_SETTING_WIRED_S390_NETTYPE, + "qeth", + NULL); + g_strfreev(subchans); + + nm_setting_wired_add_s390_option(s_wired, "portname", "FOOBAR"); + nm_setting_wired_add_s390_option(s_wired, "portno", "1"); + nm_setting_wired_add_s390_option(s_wired, "layer2", "0"); + nm_setting_wired_add_s390_option(s_wired, "protocol", "blahbalh"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_ctc_dhcp(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + char ** subchans; + shvarFile * ifcfg; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired ctc Static", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + subchans = g_strsplit("0.0.600,0.0.601", ",", -1); + g_object_set(s_wired, + NM_SETTING_WIRED_S390_SUBCHANNELS, + subchans, + NM_SETTING_WIRED_S390_NETTYPE, + "ctc", + NULL); + g_strfreev(subchans); + nm_setting_wired_add_s390_option(s_wired, "ctcprot", "0"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + /* Ensure the CTCPROT item gets written out as its own option */ + ifcfg = _svOpenFile(testfile); + + _svGetValue_check(ifcfg, "CTCPROT", "0"); + + /* And that it's not in the generic OPTIONS string */ + _svGetValue_check(ifcfg, "OPTIONS", NULL); + + svCloseFile(ifcfg); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_permissions(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Permissions", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + nm_setting_connection_add_permission(s_con, "user", "blahblah", NULL); + nm_setting_connection_add_permission(s_con, "user", "foobar", NULL); + nm_setting_connection_add_permission(s_con, "user", "asdfasdf", NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Permissions.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wifi_wep_agent_keys(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * str_ssid = "foobarbaz"; + GBytes * ssid; + + connection = nm_simple_connection_new(); + g_assert(connection != NULL); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wifi WEP Agent Owned", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(str_ssid, strlen(str_ssid)); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + g_bytes_unref(ssid); + + /* Wifi security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_PASSPHRASE, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, "asdfdjaslfjasd;flasjdfl;aksdf"); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + /* Remove the WEP key from the original, because it should not have been + * written out to disk as it was agent-owned. The new connection should + * not have any WEP keys set. + * Also the new connection should not have WEP key type set. + */ + nm_setting_wireless_security_set_wep_key(s_wsec, 0, NULL); + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_wired_pppoe(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingPppoe * s_pppoe; + NMSettingPpp * s_ppp; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired PPPoE", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* PPPoE setting */ + s_pppoe = (NMSettingPppoe *) nm_setting_pppoe_new(); + nm_connection_add_setting(connection, NM_SETTING(s_pppoe)); + + g_object_set(G_OBJECT(s_pppoe), + NM_SETTING_PPPOE_SERVICE, + "stupid-service", + NM_SETTING_PPPOE_USERNAME, + "Bill Smith", + NM_SETTING_PPPOE_PASSWORD, + "test1", + NULL); + + /* PPP setting */ + s_ppp = (NMSettingPpp *) nm_setting_ppp_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ppp)); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL); + + g_object_unref(connection); + g_clear_error(&error); +} + +static void +test_write_vpn(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingVpn * s_vpn; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write VPN", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_VPN_SETTING_NAME, + NULL); + + /* VPN setting */ + s_vpn = (NMSettingVpn *) nm_setting_vpn_new(); + nm_connection_add_setting(connection, NM_SETTING(s_vpn)); + + g_object_set(s_vpn, + NM_SETTING_VPN_SERVICE_TYPE, + "awesomevpn", + NM_SETTING_VPN_USER_NAME, + "Bill Smith", + NULL); + + nm_setting_vpn_add_data_item(s_vpn, "server", "vpn.somewhere.com"); + nm_setting_vpn_add_secret(s_vpn, "password", "sup3rs3cr3t"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL); + + g_object_unref(connection); + g_clear_error(&error); +} + +static void +test_write_mobile_broadband(gconstpointer data) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSettingGsm * s_gsm; + NMSettingCdma * s_cdma; + NMSettingPpp * s_ppp; + NMSettingSerial * s_serial; + GError * error = NULL; + gboolean gsm = GPOINTER_TO_UINT(data); + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + gsm ? "Test Write GSM" : "Test Write CDMA", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + gsm ? NM_SETTING_GSM_SETTING_NAME : NM_SETTING_CDMA_SETTING_NAME, + NULL); + + if (gsm) { + /* GSM setting */ + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + nm_connection_add_setting(connection, NM_SETTING(s_gsm)); + } else { + /* CDMA setting */ + s_cdma = (NMSettingCdma *) nm_setting_cdma_new(); + nm_connection_add_setting(connection, NM_SETTING(s_cdma)); + + g_object_set(s_cdma, NM_SETTING_CDMA_NUMBER, "#777", NULL); + } + + /* Serial setting */ + s_serial = (NMSettingSerial *) nm_setting_serial_new(); + nm_connection_add_setting(connection, NM_SETTING(s_serial)); + + g_object_set(s_serial, + NM_SETTING_SERIAL_BAUD, + 115200, + NM_SETTING_SERIAL_BITS, + 8, + NM_SETTING_SERIAL_PARITY, + NM_SETTING_SERIAL_PARITY_NONE, + NM_SETTING_SERIAL_STOPBITS, + 1, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* PPP setting */ + s_ppp = (NMSettingPpp *) nm_setting_ppp_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ppp)); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL); + + g_object_unref(connection); + g_clear_error(&error); +} + +static void +test_read_bridge_main(void) +{ + NMConnection * connection; + NMSettingBridge *s_bridge; + NMSettingWired * s_wired; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33}; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-main", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0"); + + /* ===== Bridging SETTING ===== */ + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2); + g_assert(nm_setting_bridge_get_stp(s_bridge)); + g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744); + g_assert_cmpuint(nm_setting_bridge_get_hello_time(s_bridge), ==, 7); + g_assert_cmpuint(nm_setting_bridge_get_max_age(s_bridge), ==, 39); + g_assert_cmpuint(nm_setting_bridge_get_ageing_time(s_bridge), ==, 235352); + g_assert_cmpuint(nm_setting_bridge_get_group_forward_mask(s_bridge), ==, 24); + g_assert(!nm_setting_bridge_get_multicast_snooping(s_bridge)); + g_assert_cmpint(nm_setting_bridge_get_vlan_filtering(s_bridge), ==, TRUE); + g_assert_cmpint(nm_setting_bridge_get_vlan_default_pvid(s_bridge), ==, 99); + + /* MAC address */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac = nm_setting_wired_get_cloned_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN)); + + g_object_unref(connection); +} + +static void +test_write_bridge_main(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingBridge * s_bridge; + NMSettingIPConfig * s_ip4, *s_ip6; + NMSettingWired * s_wired; + NMIPAddress * addr; + static const char * mac = "31:33:33:37:be:cd"; + GError * error = NULL; + gs_unref_ptrarray GPtrArray *vlans = NULL; + NMBridgeVlan * vlan; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bridge Main", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "br0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + /* bridge setting */ + s_bridge = (NMSettingBridge *) nm_setting_bridge_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bridge)); + + vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + vlan = nm_bridge_vlan_new(10, 16); + nm_bridge_vlan_set_untagged(vlan, TRUE); + g_ptr_array_add(vlans, vlan); + vlan = nm_bridge_vlan_new(22, 22); + nm_bridge_vlan_set_pvid(vlan, TRUE); + nm_bridge_vlan_set_untagged(vlan, TRUE); + g_ptr_array_add(vlans, vlan); + vlan = nm_bridge_vlan_new(44, 0); + g_ptr_array_add(vlans, vlan); + + g_object_set(s_bridge, + NM_SETTING_BRIDGE_MAC_ADDRESS, + mac, + NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, + 19008, + NM_SETTING_BRIDGE_VLAN_FILTERING, + TRUE, + NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, + 4000, + NM_SETTING_BRIDGE_VLANS, + vlans, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_BRIDGE, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_bridge_component(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingBridgePort *s_port; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-component", + NULL, + TYPE_ETHERNET, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); + + s_port = nm_connection_get_setting_bridge_port(connection); + g_assert(s_port); + g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port)); + g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28); + g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100); + + g_object_unref(connection); +} + +static void +test_write_bridge_component(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSetting * s_port; + static const char * mac = "31:33:33:37:be:cd"; + guint32 mtu = 1492; + gs_unref_ptrarray GPtrArray *vlans = NULL; + NMBridgeVlan * vlan; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bridge Component", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "br0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL); + + /* Bridge port */ + vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + vlan = nm_bridge_vlan_new(1, 0); + nm_bridge_vlan_set_untagged(vlan, TRUE); + g_ptr_array_add(vlans, vlan); + vlan = nm_bridge_vlan_new(4, 4094); + nm_bridge_vlan_set_untagged(vlan, TRUE); + g_ptr_array_add(vlans, vlan); + vlan = nm_bridge_vlan_new(2, 2); + nm_bridge_vlan_set_pvid(vlan, TRUE); + g_ptr_array_add(vlans, vlan); + + s_port = nm_setting_bridge_port_new(); + nm_connection_add_setting(connection, s_port); + g_object_set(s_port, + NM_SETTING_BRIDGE_PORT_PRIORITY, + 50, + NM_SETTING_BRIDGE_PORT_PATH_COST, + 33, + NM_SETTING_BRIDGE_PORT_VLANS, + vlans, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Bridge_Component.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_bridge_missing_stp(void) +{ + NMConnection * connection; + NMSettingBridge *s_bridge; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-missing-stp", + NULL, + TYPE_BRIDGE, + NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0"); + + /* ===== Bridging SETTING ===== */ + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + g_assert(nm_setting_bridge_get_stp(s_bridge) == FALSE); + + g_object_unref(connection); +} + +#define TEST_IFCFG_VLAN_INTERFACE TEST_IFCFG_DIR "/ifcfg-test-vlan-interface" + +static void +test_read_vlan_interface(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + guint32 from = 0, to = 0; + + connection = _connection_from_file(TEST_IFCFG_VLAN_INTERFACE, NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan43"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), + ==, + NM_VLAN_FLAG_GVRP | NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS); + + /* Ingress map */ + g_assert_cmpint(nm_setting_vlan_get_num_priorities(s_vlan, NM_VLAN_INGRESS_MAP), ==, 2); + + g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_INGRESS_MAP, 0, &from, &to)); + g_assert_cmpint(from, ==, 0); + g_assert_cmpint(to, ==, 1); + + g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_INGRESS_MAP, 1, &from, &to)); + g_assert_cmpint(from, ==, 2); + g_assert_cmpint(to, ==, 5); + + /* Egress map */ + g_assert_cmpint(nm_setting_vlan_get_num_priorities(s_vlan, NM_VLAN_EGRESS_MAP), ==, 3); + + g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 0, &from, &to)); + g_assert_cmpint(from, ==, 3); + g_assert_cmpint(to, ==, 1); + + g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 1, &from, &to)); + g_assert_cmpint(from, ==, 12); + g_assert_cmpint(to, ==, 3); + + g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 2, &from, &to)); + g_assert_cmpint(from, ==, 14); + g_assert_cmpint(to, ==, 7); + + g_object_unref(connection); +} + +#define TEST_IFCFG_VLAN_ONLY_VLANID TEST_IFCFG_DIR "/ifcfg-test-vlan-only-vlanid" + +static void +test_read_vlan_only_vlan_id(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = _connection_from_file(TEST_IFCFG_VLAN_ONLY_VLANID, NULL, TYPE_ETHERNET, NULL); + + g_assert(nm_connection_get_interface_name(connection) == NULL); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS); + + g_object_unref(connection); +} + +static void +test_read_vlan_only_device(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-only-device", + NULL, + TYPE_ETHERNET, + NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0.9"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 9); + + g_object_unref(connection); +} + +static void +test_read_vlan_physdev(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-physdev", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3); + + g_object_unref(connection); +} + +static void +test_read_vlan_reorder_hdr_1(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + NMTST_EXPECT_NM_WARN("*REORDER_HDR key is deprecated, use VLAN_FLAGS*"); + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-1", + NULL, + TYPE_ETHERNET, + NULL); + g_test_assert_expected_messages(); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3); + /* Check that REORDER_HDR=0 is ignored */ + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS); + + g_object_unref(connection); +} + +static void +test_read_vlan_reorder_hdr_2(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-2", + NULL, + TYPE_ETHERNET, + NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3); + /* Check that VLAN_FLAGS=NO_REORDER_HDR works */ + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_LOOSE_BINDING); + + g_object_unref(connection); +} + +static void +test_read_vlan_flags_1(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-1", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), + ==, + NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS); + + g_object_unref(connection); +} + +static void +test_read_vlan_flags_2(void) +{ + NMConnection * connection; + NMSettingVlan *s_vlan; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-2", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), + ==, + NM_VLAN_FLAG_GVRP | NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS); + + g_object_unref(connection); +} + +static void +test_write_vlan(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + + connection = _connection_from_file(TEST_IFCFG_VLAN_INTERFACE, NULL, TYPE_VLAN, NULL); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Vlan_test-vlan-interface.cexpected", + &testfile); +} + +static void +test_write_vlan_flags(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-2", NULL, TYPE_VLAN, NULL); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_vlan_only_vlanid(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + + connection = _connection_from_file(TEST_IFCFG_VLAN_ONLY_VLANID, NULL, TYPE_VLAN, NULL); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_vlan_reorder_hdr(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingVlan * s_vlan; + NMSettingWired * s_wired; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write VLAN reorder_hdr", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_VLAN_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* VLAN setting */ + s_vlan = (NMSettingVlan *) nm_setting_vlan_new(); + nm_connection_add_setting(connection, NM_SETTING(s_vlan)); + + g_object_set(s_vlan, + NM_SETTING_VLAN_PARENT, + "eth0", + NM_SETTING_VLAN_ID, + 444, + NM_SETTING_VLAN_FLAGS, + 1, + NULL); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_ethernet_missing_ipv6(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Ethernet Without IPv6 Setting", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, + "random-client-id-00:22:33", + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, + TRUE, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + TRUE, + NULL); + + /* IP6 setting */ + /* + * We intentionally don't add IPv6 setting here. ifcfg-rh plugin should regard + * missing IPv6 as IPv6 with NM_SETTING_IP6_CONFIG_METHOD_AUTO method. + */ + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_ibft_ignored(void) +{ + gs_free_error GError *error = NULL; + + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-ibft", NULL, TYPE_ETHERNET, &error); + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); +} + +static void +test_read_bond_main(void) +{ + NMConnection * connection; + NMSettingBond *s_bond; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-main", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0"); + + /* ===== Bonding SETTING ===== */ + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON), + ==, + "100"); + + g_object_unref(connection); +} + +static void +test_read_bond_eth_type(void) +{ + NMConnection * connection; + NMSettingBond *s_bond; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-eth-type", + NULL, + TYPE_ETHERNET, + NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0"); + + /* ===== Bonding SETTING ===== */ + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON), + ==, + "213"); + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_LACP_RATE), + ==, + "1"); + + g_object_unref(connection); +} + +static void +test_write_bond_main(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingBond * s_bond; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMIPAddress * addr; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bond Main", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "bond0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BOND_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* bond setting */ + s_bond = (NMSettingBond *) nm_setting_bond_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bond)); + + nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY, "5"); + nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_UPDELAY, "10"); + nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_MIIMON, "100"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Bond_Main.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_BOND, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_bond_slave(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave", NULL, TYPE_ETHERNET, NULL); + g_test_assert_expected_messages(); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0"); + + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); + + g_object_unref(connection); +} + +static void +test_write_bond_slave(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + static const char * mac = "31:33:33:37:be:cd"; + guint32 mtu = 1492; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bond Slave", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "bond0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BOND_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_infiniband(void) +{ + NMConnection * connection; + NMSettingInfiniband *s_infiniband; + char * unmanaged = NULL; + const char * mac; + char expected_mac_address[INFINIBAND_ALEN] = {0x80, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22}; + const char *transport_mode; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-infiniband", + NULL, + TYPE_INFINIBAND, + &unmanaged); + g_assert(!unmanaged); + + /* ===== INFINIBAND SETTING ===== */ + + s_infiniband = nm_connection_get_setting_infiniband(connection); + g_assert(s_infiniband); + + /* MAC address */ + mac = nm_setting_infiniband_get_mac_address(s_infiniband); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + + /* Transport mode */ + transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband); + g_assert(transport_mode); + g_assert_cmpstr(transport_mode, ==, "connected"); + + g_object_unref(connection); +} + +static void +test_write_infiniband(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingInfiniband * s_infiniband; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * mac = "80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22"; + guint32 mtu = 65520; + NMIPAddress *addr; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write InfiniBand", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_INFINIBAND_SETTING_NAME, + NULL); + + /* InfiniBand setting */ + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new(); + nm_connection_add_setting(connection, NM_SETTING(s_infiniband)); + + g_object_set(s_infiniband, + NM_SETTING_INFINIBAND_MAC_ADDRESS, + mac, + NM_SETTING_INFINIBAND_MTU, + mtu, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "connected", + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_INFINIBAND, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_bond_slave_ib(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave-ib", NULL, NULL, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0"); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME); +} + +static void +test_write_bond_slave_ib(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingInfiniband * s_infiniband; + static const char *mac = "80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bond Slave InfiniBand", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "bond0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BOND_SETTING_NAME, + NULL); + + /* InfiniBand setting */ + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new(); + nm_connection_add_setting(connection, NM_SETTING(s_infiniband)); + + g_object_set(s_infiniband, + NM_SETTING_INFINIBAND_MAC_ADDRESS, + mac, + NM_SETTING_INFINIBAND_MTU, + 2044, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "datagram", + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + reread = _connection_from_file(testfile, NULL, NULL, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_bond_opts_mode_numeric(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingBond * s_bond; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-mode-numeric", + NULL, + TYPE_ETHERNET, + NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_BOND_SETTING_NAME); + + s_bond = nm_connection_get_setting_bond(connection); + g_assert(s_bond); + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE), + ==, + "802.3ad"); + + g_object_unref(connection); +} + +#define DCB_ALL_FLAGS \ + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + +static void +test_read_dcb_basic(void) +{ + NMConnection *connection; + NMSettingDcb *s_dcb; + guint i; + guint expected_group_ids[8] = {0, 0, 0, 0, 1, 1, 1, 0xF}; + guint expected_group_bandwidths[8] = {25, 0, 0, 75, 0, 0, 0, 0}; + guint expected_bandwidths[8] = {5, 10, 30, 25, 10, 50, 5, 0}; + gboolean expected_strict[8] = {FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE}; + guint expected_traffic_classes[8] = {7, 6, 5, 4, 3, 2, 1, 0}; + gboolean expected_pfcs[8] = {TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE}; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dcb", NULL, TYPE_ETHERNET, NULL); + + s_dcb = nm_connection_get_setting_dcb(connection); + g_assert(s_dcb); + + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, DCB_ALL_FLAGS); + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, 7); + + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, DCB_ALL_FLAGS); + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_priority(s_dcb), ==, 6); + + g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, DCB_ALL_FLAGS); + g_assert_cmpint(nm_setting_dcb_get_app_fip_priority(s_dcb), ==, 2); + + g_assert_cmpint(nm_setting_dcb_get_priority_flow_control_flags(s_dcb), + ==, + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE)); + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_flow_control(s_dcb, i), ==, expected_pfcs[i]); + + g_assert_cmpint(nm_setting_dcb_get_priority_group_flags(s_dcb), ==, DCB_ALL_FLAGS); + + /* Group IDs */ + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_group_id(s_dcb, i), ==, expected_group_ids[i]); + + /* Group bandwidth */ + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_group_bandwidth(s_dcb, i), + ==, + expected_group_bandwidths[i]); + + /* User priority bandwidth */ + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_bandwidth(s_dcb, i), + ==, + expected_bandwidths[i]); + + /* Strict bandwidth */ + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_strict_bandwidth(s_dcb, i), + ==, + expected_strict[i]); + + /* Traffic class */ + for (i = 0; i < 8; i++) + g_assert_cmpint(nm_setting_dcb_get_priority_traffic_class(s_dcb, i), + ==, + expected_traffic_classes[i]); + + g_object_unref(connection); +} + +static void +test_write_dcb_basic(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingDcb * s_dcb; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + guint i; + const guint group_ids[8] = {4, 0xF, 6, 0xF, 1, 7, 3, 0xF}; + const guint group_bandwidths[8] = {10, 20, 15, 10, 2, 3, 35, 5}; + const guint bandwidths[8] = {10, 20, 30, 40, 50, 10, 0, 25}; + const gboolean strict[8] = {TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE}; + const guint traffic_classes[8] = {3, 4, 7, 2, 1, 0, 5, 6}; + const gboolean pfcs[8] = {TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, FALSE}; + + connection = nm_simple_connection_new(); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_ID, + "dcb-test", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP stuff */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + /* DCB */ + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + nm_connection_add_setting(connection, NM_SETTING(s_dcb)); + + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FCOE_FLAGS, + DCB_ALL_FLAGS, + NM_SETTING_DCB_APP_FCOE_PRIORITY, + 5, + NM_SETTING_DCB_APP_ISCSI_FLAGS, + DCB_ALL_FLAGS, + NM_SETTING_DCB_APP_ISCSI_PRIORITY, + 1, + NM_SETTING_DCB_APP_FIP_FLAGS, + DCB_ALL_FLAGS, + NM_SETTING_DCB_APP_FIP_PRIORITY, + 3, + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, + DCB_ALL_FLAGS, + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, + DCB_ALL_FLAGS, + NULL); + + for (i = 0; i < 8; i++) { + nm_setting_dcb_set_priority_flow_control(s_dcb, i, pfcs[i]); + nm_setting_dcb_set_priority_group_id(s_dcb, i, group_ids[i]); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, i, group_bandwidths[i]); + nm_setting_dcb_set_priority_bandwidth(s_dcb, i, bandwidths[i]); + nm_setting_dcb_set_priority_strict_bandwidth(s_dcb, i, strict[i]); + nm_setting_dcb_set_priority_traffic_class(s_dcb, i, traffic_classes[i]); + } + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "//ifcfg-dcb-test.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_dcb_default_app_priorities(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingDcb * s_dcb; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dcb-default-app-priorities", + NULL, + TYPE_ETHERNET, + NULL); + + s_dcb = nm_connection_get_setting_dcb(connection); + g_assert(s_dcb); + + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE); + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, -1); + + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE); + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_priority(s_dcb), ==, -1); + + g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE); + g_assert_cmpint(nm_setting_dcb_get_app_fip_priority(s_dcb), ==, -1); +} + +static void +test_read_dcb_bad_booleans(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*invalid DCB_PG_STRICT value*not all 0s and 1s*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-booleans", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "invalid boolean digit")); +} + +static void +test_read_dcb_short_booleans(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*DCB_PG_STRICT value*8 characters*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-booleans", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "boolean array must be 8 characters")); +} + +static void +test_read_dcb_bad_uints(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*invalid DCB_PG_UP2TC value*not 0 - 7*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-uints", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "invalid uint digit")); +} + +static void +test_read_dcb_short_uints(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*DCB_PG_UP2TC value*8 characters*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-uints", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "uint array must be 8 characters")); +} + +static void +test_read_dcb_bad_percent(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*invalid DCB_PG_PCT percentage value*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-percent", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "invalid percent element")); +} + +static void +test_read_dcb_short_percent(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*invalid DCB_PG_PCT percentage list value*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-percent", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "percent array must be 8 elements")); +} + +static void +test_read_dcb_pgpct_not_100(void) +{ + gs_free_error GError *error = NULL; + + NMTST_EXPECT_NM_WARN("*DCB_PG_PCT percentages do not equal 100*"); + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-pgpct-not-100", + NULL, + TYPE_ETHERNET, + &error); + g_test_assert_expected_messages(); + + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); + g_assert(strstr(error->message, "invalid percentage sum")); +} + +static void +test_read_fcoe_mode(gconstpointer user_data) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free char * file = NULL; + const char * expected_mode = user_data; + NMSettingDcb * s_dcb; + + file = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-fcoe-%s", expected_mode); + connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL); + + s_dcb = nm_connection_get_setting_dcb(connection); + g_assert(s_dcb); + + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE); + g_assert_cmpstr(nm_setting_dcb_get_app_fcoe_mode(s_dcb), ==, expected_mode); +} + +static void +test_write_fcoe_mode(gconstpointer user_data) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + const char * expected_mode = user_data; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingDcb * s_dcb; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + shvarFile * ifcfg; + + connection = nm_simple_connection_new(); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_ID, + "fcoe-test", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP stuff */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + /* DCB */ + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + nm_connection_add_setting(connection, NM_SETTING(s_dcb)); + + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FCOE_FLAGS, + NM_SETTING_DCB_FLAG_ENABLE, + NM_SETTING_DCB_APP_FCOE_MODE, + expected_mode, + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + ifcfg = _svOpenFile(testfile); + _svGetValue_check(ifcfg, "DCB_APP_FCOE_MODE", expected_mode); + svCloseFile(ifcfg); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_team_master(gconstpointer user_data) +{ + const char *const PATH_NAME = user_data; + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingTeam * s_team; + const char * expected_config = + "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"; + + connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "team0"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_TEAM_SETTING_NAME); + + s_team = nm_connection_get_setting_team(connection); + g_assert(s_team); + g_assert_cmpstr(nm_setting_team_get_config(s_team), ==, expected_config); + + g_object_unref(connection); +} + +static void +test_read_team_master_invalid(gconstpointer user_data) +{ + const char *const PATH_NAME = user_data; + gs_free_error GError *error = NULL; + gs_unref_object NMConnection *connection = NULL; + + if (nm_json_vt()) { + _connection_from_file_fail(PATH_NAME, NULL, TYPE_ETHERNET, &error); + + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(strstr(error->message, _("invalid json"))); + } else + connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL); +} + +static void +test_write_team_master(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingTeam * s_team; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * expected_config = + "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"; + shvarFile *f; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Team Master", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_INTERFACE_NAME, + "team0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_TEAM_SETTING_NAME, + NULL); + + /* Team setting */ + s_team = (NMSettingTeam *) nm_setting_team_new(); + nm_connection_add_setting(connection, NM_SETTING(s_team)); + + g_object_set(s_team, NM_SETTING_TEAM_CONFIG, expected_config, NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(connection); + + _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "DEVICETYPE", "Team"); + _svGetValue_check(f, "TEAM_CONFIG", expected_config); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_team_port(gconstpointer user_data) +{ + const char *const PATH_NAME = user_data; + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingTeamPort * s_team_port; + const char * expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}"; + + connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0"); + + s_team_port = nm_connection_get_setting_team_port(connection); + g_assert(s_team_port); + g_assert_cmpstr(nm_setting_team_port_get_config(s_team_port), ==, expected_config); + + g_object_unref(connection); +} + +static void +test_write_team_port(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingTeamPort * s_team_port; + NMSettingWired * s_wired; + const char * expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}"; + shvarFile * f; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Team Port", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "team0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_TEAM_SETTING_NAME, + NULL); + + /* Team setting */ + s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new(); + nm_connection_add_setting(connection, NM_SETTING(s_team_port)); + g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Team_Port.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "TYPE", NULL); + _svGetValue_check(f, "DEVICETYPE", "TeamPort"); + _svGetValue_check(f, "TEAM_PORT_CONFIG", expected_config); + _svGetValue_check(f, "TEAM_MASTER", "team0"); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_write_team_infiniband_port(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingTeamPort * s_team_port; + NMSettingInfiniband * s_inf; + const char * expected_config = "{\"inf1\": {\"prio\": -10, \"sticky\": true}}"; + shvarFile * f; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Team Infiniband Port", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "team0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_TEAM_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "inf1", + NULL); + + /* Team setting */ + s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new(); + nm_connection_add_setting(connection, NM_SETTING(s_team_port)); + g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL); + + /* Infiniband setting */ + s_inf = (NMSettingInfiniband *) nm_setting_infiniband_new(); + nm_connection_add_setting(connection, NM_SETTING(s_inf)); + g_object_set(s_inf, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Team_Infiniband_Port.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "TYPE", "InfiniBand"); + _svGetValue_check(f, "DEVICETYPE", "TeamPort"); + _svGetValue_check(f, "TEAM_PORT_CONFIG", expected_config); + _svGetValue_check(f, "TEAM_MASTER", "team0"); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +test_read_team_port_empty_config(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-team-port-empty-config", + NULL, + TYPE_ETHERNET, + NULL); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRED_SETTING_NAME); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0"); + + /* Normalization adds a team-port setting */ + g_assert(nm_connection_get_setting_team_port(connection)); + + /* empty/missing config */ + g_assert(!nm_setting_team_port_get_config(nm_connection_get_setting_team_port(connection))); + + g_object_unref(connection); +} + +static void +test_team_reread_slave(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection_1 = NULL; + gs_unref_object NMConnection *connection_2 = NULL; + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + NMSettingConnection * s_con; + + connection_1 = + nmtst_create_connection_from_keyfile("[connection]\n" + "id=team-slave-enp31s0f1-142\n" + "uuid=74f435bb-ede4-415a-9d48-f580b60eba04\n" + "type=vlan\n" + "autoconnect=false\n" + "interface-name=enp31s0f1-142\n" + "master=team142\n" + "permissions=\n" + "slave-type=team\n" + "\n" + "[vlan]\n" + "egress-priority-map=\n" + "flags=1\n" + "id=142\n" + "ingress-priority-map=\n" + "parent=enp31s0f1\n", + "/test_team_reread_slave"); + + /* to double-check keyfile syntax, re-create the connection by hand. */ + connection_2 = nmtst_create_minimal_connection("team-slave-enp31s0f1-142", + "74f435bb-ede4-415a-9d48-f580b60eba04", + NM_SETTING_VLAN_SETTING_NAME, + &s_con); + g_object_set(s_con, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "enp31s0f1-142", + NM_SETTING_CONNECTION_MASTER, + "team142", + NM_SETTING_CONNECTION_SLAVE_TYPE, + "team", + NULL); + g_object_set(nm_connection_get_setting_vlan(connection_2), + NM_SETTING_VLAN_FLAGS, + 1, + NM_SETTING_VLAN_ID, + 142, + NM_SETTING_VLAN_PARENT, + "enp31s0f1", + NULL); + nm_connection_add_setting(connection_2, nm_setting_team_port_new()); + nmtst_connection_normalize(connection_2); + + nmtst_assert_connection_equals(connection_1, FALSE, connection_2, FALSE); + + _writer_new_connection_reread((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2, + TEST_SCRATCH_DIR, + &testfile, + TEST_IFCFG_DIR "/ifcfg-team-slave-enp31s0f1-142.cexpected", + &reread, + &reread_same); + _assert_reread_same((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2, reread); + g_assert(reread_same); + g_clear_object(&reread); + + reread = _connection_from_file(testfile, NULL, TYPE_VLAN, NULL); + nmtst_assert_connection_equals((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2, + FALSE, + reread, + FALSE); +} + +static void +test_read_proxy_basic(void) +{ + NMConnection * connection; + NMSettingProxy *s_proxy; + + /* Test basic proxy configuration */ + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-read-proxy-basic", + NULL, + TYPE_ETHERNET, + NULL); + + /* ===== Proxy setting ===== */ + s_proxy = nm_connection_get_setting_proxy(connection); + g_assert(s_proxy); + + /* Proxy method */ + g_assert_cmpint(nm_setting_proxy_get_method(s_proxy), ==, NM_SETTING_PROXY_METHOD_AUTO); + g_assert(nm_setting_proxy_get_browser_only(s_proxy)); + g_assert_cmpstr(nm_setting_proxy_get_pac_url(s_proxy), + ==, + "http://wpad.mycompany.com/wpad.dat"); + + g_object_unref(connection); +} + +static void +test_write_proxy_basic(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingProxy * s_proxy; + const char * expected_url = "https://wpad.neverland.org/wpad.dat"; + shvarFile * f; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Proxy Basic", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Proxy setting */ + s_proxy = (NMSettingProxy *) nm_setting_proxy_new(); + nm_connection_add_setting(connection, NM_SETTING(s_proxy)); + g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, NM_SETTING_PROXY_METHOD_AUTO, NULL); + g_object_set(s_proxy, NM_SETTING_PROXY_PAC_URL, expected_url, NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_Proxy_Basic.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "TYPE", "Ethernet"); + _svGetValue_check(f, "PROXY_METHOD", "auto"); + _svGetValue_check(f, "PAC_URL", expected_url); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +/*****************************************************************************/ + +static const char * +_svUnescape(const char *str, char **to_free) +{ + const char * s; + gs_free char *str_free = NULL; + + g_assert(str); + g_assert(to_free); + + if (str[0] == '\0') { + /* avoid static string "" */ + str = (str_free = g_strdup(str)); + } + + s = svUnescape(str, to_free); + if (*to_free) { + g_assert(s == *to_free); + g_assert(s[0]); + } else { + g_assert(s == NULL || (!s[0] && (s < str || s > strchr(str, '\0'))) + || (s[0] && s >= str && s <= strchr(str, '\0'))); + } + return s; +} + +typedef struct { + const char *val; + const char *exp; + bool can_concat : 1; + bool needs_ascii_separator : 1; +} UnescapeTestData; + +static void +do_svUnescape_assert(const char *str, const char *expected) +{ + gs_free char *to_free = NULL; + const char * s; + + s = _svUnescape(str, &to_free); + g_assert_cmpstr(s, ==, expected); + + /* check we can make a round-trip */ + if (expected) { + gs_free char *s1_free = NULL; + gs_free char *s2_free = NULL; + const char * s1, *s2; + + s1 = svEscape(expected, &s1_free); + g_assert(s1); + + s2 = _svUnescape(s1, &s2_free); + g_assert(s2); + + g_assert_cmpstr(s2, ==, expected); + } +} + +static void +do_svUnescape_combine_ansi_append(GString * str_val, + GString * str_exp, + const UnescapeTestData *data, + gboolean honor_needs_ascii_separator) +{ + g_string_append(str_val, data->val); + g_string_append(str_exp, data->exp); + if (honor_needs_ascii_separator && data->needs_ascii_separator) { + /* the string has an open escape sequence. We must ensure that when + * combining it with another sequence, that they don't merge into + * something different. for example "\xa" + "a" must not result in + * "\xaa". Instead, we add a space in between to get "\xa a". */ + g_string_append(str_val, " "); + g_string_append(str_exp, " "); + } +} + +static void +do_svUnescape_combine_ansi(GString * str_val, + GString * str_exp, + const UnescapeTestData *data_ansi, + gsize data_len, + gssize idx) +{ + gsize i, j; + + g_string_set_size(str_val, 0); + g_string_set_size(str_exp, 0); + g_string_append(str_val, "$'"); + if (idx < 0) { + for (i = -idx; i > 0; i--) { + j = nmtst_get_rand_uint32() % data_len; + if (!data_ansi[j].can_concat) { + i++; + continue; + } + do_svUnescape_combine_ansi_append(str_val, str_exp, &data_ansi[j], i > 1); + } + } else { + g_assert_cmpint(idx, <, data_len); + do_svUnescape_combine_ansi_append(str_val, str_exp, &data_ansi[idx], FALSE); + } + g_string_append(str_val, "'"); +} + +static void +test_svUnescape(void) +{ +#define V0(v_value, v_expected) \ + { \ + .val = "" v_value "", .exp = v_expected, .can_concat = FALSE, \ + } +#define V1(v_value, v_expected) \ + { \ + .val = "" v_value "", .exp = v_expected, .can_concat = !!v_expected, \ + } +#define V2(v_value, v_expected) \ + { \ + .val = "" v_value "", .exp = v_expected, .can_concat = TRUE, \ + .needs_ascii_separator = TRUE, \ + } + const UnescapeTestData data_full[] = { + V1("", ""), + V0("'", NULL), + V1("'x'", "x"), + V1("' '", " "), + V1("'x'", "x"), + V0("\"", NULL), + V0("\\", NULL), + V0(" ", ""), + V0(" ", ""), + V0("a; #", "a"), + V0(" ; #", ""), + V0("; ", ""), + V0("; ;", NULL), + V0(" ; a #", NULL), + V0(" ; a;; #", NULL), + V0("a; ; #", NULL), + V0("\t # ", ""), + V0("\t #a", ""), + V0("\t #a\r", ""), + V0("\r", ""), + V1("\\\"", "\""), + V1("\\`", "`"), + V1("\\$", "$"), + V1("\\\\", "\\"), + V1("\\a", "a"), + V1("\\b", "b"), + V1("\\'", "'"), + V1("\\~", "~"), + V1("\\\t", "\t"), + V1("\"\\\"\"", "\""), + V1("\"\\`\"", "`"), + V1("\"\\$\"", "$"), + V1("\"\\\\\"", "\\"), + V1("\"\\a\"", "\\a"), + V1("\"\\b\"", "\\b"), + V1("\"\\\t\"", "\\\t"), + V0("ab\r", "ab"), + V0("a'b'\r ", "ab"), + V0("a'b' \r", "ab"), + V0("a#b", "a#b"), + V0("#b", "#b"), + V1("\'some string\'", "some string"), + V0("Bob outside LAN", NULL), + V1("x", "x"), + V1("'{ \"device\": \"team0\", \"link_watch\": { \"name\": \"ethtool\" } }'", + "{ \"device\": \"team0\", \"link_watch\": { \"name\": \"ethtool\" } }"), + V1("'{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}'", + "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"), + V1("x\"\"b", "xb"), + V1("x\"c\"b", "xcb"), + V1("\"c\"b", "cb"), + V1("\"c\"\\'b", "c'b"), + V1("$''", ""), + V1("$'\\n'", "\n"), + V0("$'\\'", NULL), + V1("$'\\x'", "\\x"), + V1("$'\\xa'", "\xa"), + V0("$'\\x0'", ""), + V1("$'\\x12'", "\x12"), + V1("$'\\x12A'", + "\x12" + "A"), + V1("$'\\x12t'", "\x12t"), + V1("\"aa\\\"\"", "aa\""), + V1("\"aa\\\"b\"c", "aa\"bc"), + V1("\"aa\\\"\"b", "aa\"b"), + + /* the following is not shell behavior, but kept for backward compatibility + * with old svEscape(). */ + V0("\"\\'\"", "'"), + V0("\"\\~\"", "~"), + V0("\"b\\~b\"", "b~b"), + V0("\"\\~\\~\"", "~~"), + V0("\"\\~\\'\"", "~'"), + + /* the following is shell-behavior, because it doesn't look like written + * by old svEscape(). */ + V1("\"\\~~\"", "\\~~"), + V1("\"\\a\\'\"", "\\a\\'"), + V1("x\"\\~\"", "x\\~"), + V1("\"\\'\"''", "\\'"), + V0("\"b\\~b\" ", "b\\~b"), + V1("\"b\\~b\"x", "b\\~bx"), + }; + const UnescapeTestData data_ansi[] = { + /* strings inside $''. They cannot be compared directly, but must + * be wrapped by do_svUnescape_combine_ansi(). */ + V1("", ""), + V1("a", "a"), + V1("b", "b"), + V1("x", "x"), + V1(" ", " "), + V1("\\a", "\a"), + V1("\\b", "\b"), + V1("\\e", "\e"), + V1("\\E", "\E"), + V1("\\f", "\f"), + V1("\\n", "\n"), + V1("\\r", "\r"), + V1("\\t", "\t"), + V1("\\v", "\v"), + V1("\\\\", "\\"), + V1("\\'", "'"), + V1("\\\"", "\""), + V1("\\?", "\?"), + V1("\\?", "?"), + V2("\\8", "\\8"), + V2("\\1", "\1"), + V1("\\1A", "\1A"), + V1("\\18", "\18"), + V2("\\01", "\1"), + V1("\\001", "\1"), + V0("\\008", ""), + V1("\\018", "\0018"), + V0("\\08", ""), + V1("\\18", "\0018"), + V1("\\x", "\\x"), + V2("\\xa", "\xa"), + V1("\\x12", "\x12"), + V1("\\x12A", + "\x12" + "A"), + V1("\\x12a", + "\x12" + "a"), + V1("\\x12t", "\x12t"), + V1("\\x1a", "\x1a"), + V1("\\x1A", "\x1A"), + V1("\\ut", "\\ut"), + V2("\\ua", "\xa"), + V1("\\uat", "\xat"), + V2("\\uab", "\xc2\xab"), + V1("\\uabt", "\xc2\xabt"), + V2("\\uabc", "\xe0\xaa\xbc"), + V1("\\uabct", "\xe0\xaa\xbct"), + V2("\\uabcd", "\xea\xaf\x8d"), + V1("\\uabcdt", "\xea\xaf\x8dt"), + V2("\\uabcde", + "\xea\xaf\x8d" + "e"), + V1("\\uabcdet", + "\xea\xaf\x8d" + "et"), + V1("\\Ut", "\\Ut"), + V2("\\Ua", "\xa"), + V1("\\Uat", "\xat"), + V2("\\Uab", "\xc2\xab"), + V1("\\Uabt", "\xc2\xabt"), + V2("\\Uabc", "\xe0\xaa\xbc"), + V1("\\Uabct", "\xe0\xaa\xbct"), + V2("\\Uabcd", "\xea\xaf\x8d"), + V1("\\Uabcdt", "\xea\xaf\x8dt"), + V2("\\Uabcde", "\xf2\xab\xb3\x9e"), + V1("\\Uabcdet", "\xf2\xab\xb3\x9et"), + V2("\\Uabcde0", "\xf8\xaa\xbc\xb7\xa0"), + V1("\\Uabcde0t", "\xf8\xaa\xbc\xb7\xa0t"), + V2("\\Uabcde01", "\xfc\x8a\xaf\x8d\xb8\x81"), + V1("\\Uabcde01t", "\xfc\x8a\xaf\x8d\xb8\x81t"), + V2("\\U0abcde01", "\xfc\x8a\xaf\x8d\xb8\x81"), + V1("\\U0abcde01t", "\xfc\x8a\xaf\x8d\xb8\x81t"), + V1("\\U00abcde01", + "\xf8\xaa\xbc\xb7\xa0" + "1"), + V1("\\U00abcde01t", + "\xf8\xaa\xbc\xb7\xa0" + "1t"), + + /* control-x sequence is not supported */ + V1("\\c", "\\c"), + V1("\\c1", "\\c1"), + }; +#undef V0 +#undef V1 +#undef V2 + gsize i; + nm_auto_free_gstring GString *str_val = g_string_new(NULL); + nm_auto_free_gstring GString *str_val2 = g_string_new(NULL); + nm_auto_free_gstring GString *str_exp = g_string_new(NULL); + nm_auto_free_gstring GString *str_exp2 = g_string_new(NULL); + + do_svUnescape_assert("' '' '", " "); + + for (i = 0; i < G_N_ELEMENTS(data_full); i++) + do_svUnescape_assert(data_full[i].val, data_full[i].exp); + + for (i = 0; i < G_N_ELEMENTS(data_ansi); i++) { + do_svUnescape_combine_ansi(str_val, str_exp, data_ansi, G_N_ELEMENTS(data_ansi), i); + do_svUnescape_assert(str_val->str, str_exp->str); + } + + /* different values can be just concatenated... */ + for (i = 0; i < 200; i++) { + gsize num_concat = (nmtst_get_rand_uint32() % 5) + 2; + + g_string_set_size(str_val, 0); + g_string_set_size(str_exp, 0); + + while (num_concat > 0) { + gsize idx; + + if ((nmtst_get_rand_uint32() % 3 == 0)) { + do_svUnescape_combine_ansi(str_val2, + str_exp2, + data_ansi, + G_N_ELEMENTS(data_ansi), + -((int) ((nmtst_get_rand_uint32() % 5) + 1))); + continue; + } + + idx = nmtst_get_rand_uint32() % G_N_ELEMENTS(data_full); + if (!data_full[idx].can_concat) + continue; + g_string_append(str_val, data_full[idx].val); + g_string_append(str_exp, data_full[idx].exp); + num_concat--; + } + + switch (nmtst_get_rand_uint32() % 3) { + case 0: + g_string_append(str_val, " "); + break; + case 1: + g_string_append(str_val, " "); + break; + } + switch (nmtst_get_rand_uint32() % 3) { + case 0: + g_string_append(str_val, " #"); + break; + case 1: + g_string_append(str_val, " #foo"); + break; + } + do_svUnescape_assert(str_val->str, str_exp->str); + } +} + +/*****************************************************************************/ + +static void +test_write_unknown(gconstpointer test_data) +{ + nmtst_auto_unlinkfile char *filename_tmp_1 = g_strdup(TEST_SCRATCH_DIR_TMP "/tmp-1"); + const char * testfile = test_data; + gs_free char * testfile_expected = g_strconcat(testfile, ".expected", NULL); + shvarFile * sv; + gs_free_error GError *error = NULL; + gboolean success; + gs_free char * file_contents_out = NULL; + gs_free char * file_contents_exp = NULL; + + sv = _svOpenFile(testfile); + + _nmtst_svFileSetName(sv, filename_tmp_1); + _nmtst_svFileSetModified(sv); + + if (g_str_has_suffix(testfile, "ifcfg-test-write-unknown-4")) { + _svGetValue_check(sv, "IPADDR", "l4x"); + _svGetValue_check(sv, "IPADDR2", ""); + _svGetValue_check(sv, "IPADDR3", "name3-value"); + + svSetValue(sv, "IPADDR", "set-by-test1"); + svSetValue(sv, "IPADDR2", NULL); + svSetValue(sv, "IPADDR2", "set-by-test2"); + svSetValue(sv, "IPADDR3", "set-by-test3"); + + _svGetValue_check(sv, "METRIC", NULL); + _svGetValue_check(sv, "METRIC1", ""); + _svGetValue_check(sv, "METRIC2", ""); + _svGetValue_check(sv, "METRIC3", "x"); + + _svGetValue_check(sv, "IPADDR", "set-by-test1"); + _svGetValue_check(sv, "IPADDR2", "set-by-test2"); + _svGetValue_check(sv, "IPADDR3", "set-by-test3"); + } + + success = svWriteFile(sv, 0644, &error); + nmtst_assert_success(success, error); + + file_contents_out = nmtst_file_get_contents(filename_tmp_1); + file_contents_exp = nmtst_file_get_contents(testfile_expected); + + g_assert_cmpstr(file_contents_out, ==, file_contents_exp); + + svCloseFile(sv); +} + +/*****************************************************************************/ + +static void +test_read_vlan_trailing_spaces(void) +{ + const char * testfile = TEST_IFCFG_DIR "/ifcfg-test-vlan-trailing-spaces"; + NMConnection * connection; + gboolean success; + GError * error = NULL; + NMSettingVlan *s_vlan; + char * contents = NULL; + + /* Ensure there is whitespace at the end of the VLAN interface name, + * to prevent the whitespace getting stripped off and committed mistakenly + * by something in the future. + */ + success = g_file_get_contents(testfile, &contents, NULL, &error); + g_assert_no_error(error); + g_assert(success); + g_assert(contents && contents[0]); + g_assert(strstr(contents, "DEVICE=\"vlan201\" \n")); + g_free(contents); + + connection = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan201"); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "enccw0.0.fb00"); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 201); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS); + + g_object_unref(connection); +} + +/*****************************************************************************/ + +static void +test_sit_read_ignore(void) +{ + gs_free_error GError *error = NULL; + + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-sit-ignore", + NULL, + TYPE_ETHERNET, + &error); + nmtst_assert_error(error, 0, 0, "*Ignoring unsupported connection due to IPV6TUNNELIPV4*"); +} + +/*****************************************************************************/ + +static void +do_test_utils_name(const char *desc, const char *path, gboolean only_ifcfg, const char *expected) +{ + const char *result; + + result = utils_get_ifcfg_name(path, only_ifcfg); + g_assert_cmpstr(result, ==, expected); +} + +static void +test_utils_name(void) +{ + do_test_utils_name("get-ifcfg-name-bad", "/foo/bar/adfasdfadf", FALSE, NULL); + do_test_utils_name("get-ifcfg-name-good", "/foo/bar/ifcfg-FooBar", FALSE, "FooBar"); + do_test_utils_name("get-ifcfg-name-keys", "/foo/bar/keys-BlahLbah", FALSE, "BlahLbah"); + do_test_utils_name("get-ifcfg-name-route", "/foo/bar/route-Lalalala", FALSE, "Lalalala"); + do_test_utils_name("get-ifcfg-name-only-ifcfg-route", "/foo/bar/route-Lalalala", TRUE, NULL); + do_test_utils_name("get-ifcfg-name-only-ifcfg-keys", "/foo/bar/keys-Lalalala", TRUE, NULL); + do_test_utils_name("get-ifcfg-name-no-path-ifcfg", "ifcfg-Lalalala", FALSE, "Lalalala"); + do_test_utils_name("get-ifcfg-name-no-path-keys", "keys-Lalalala", FALSE, "Lalalala"); + do_test_utils_name("get-ifcfg-name-no-path-route", "route-Lalalala", FALSE, "Lalalala"); + + do_test_utils_name("get-ifcfg-name-bad2-ifcfg", "/foo/bar/asdfasifcfg-Foobar", FALSE, NULL); + do_test_utils_name("get-ifcfg-name-bad2-keys", "/foo/bar/asdfaskeys-Foobar", FALSE, NULL); + do_test_utils_name("get-ifcfg-name-bad2-route", "/foo/bar/asdfasroute-Foobar", FALSE, NULL); +} + +/*****************************************************************************/ + +static void +do_test_utils_path_ifcfg(const char *desc, const char *path, const char *expected) +{ + char *result; + + result = utils_get_ifcfg_path(path); + g_assert_cmpstr(result, ==, expected); + g_free(result); +} + +static void +do_test_utils_path_keys(const char *desc, const char *path, const char *expected) +{ + char *result; + + result = utils_get_keys_path(path); + g_assert_cmpstr(result, ==, expected); + g_free(result); +} + +static void +do_test_utils_path_route(const char *desc, const char *path, const char *expected) +{ + char *result; + + result = utils_get_route_path(path); + g_assert_cmpstr(result, ==, expected); + g_free(result); +} + +static void +test_utils_path(void) +{ + do_test_utils_path_ifcfg("ifcfg-path-bad", "/foo/bar/adfasdfasdf", NULL); + do_test_utils_path_ifcfg("ifcfg-path-from-keys-no-path", "keys-BlahBlah", "ifcfg-BlahBlah"); + do_test_utils_path_ifcfg("ifcfg-path-from-keys", + "/foo/bar/keys-BlahBlah", + "/foo/bar/ifcfg-BlahBlah"); + do_test_utils_path_ifcfg("ifcfg-path-from-route", + "/foo/bar/route-BlahBlah", + "/foo/bar/ifcfg-BlahBlah"); + + do_test_utils_path_keys("keys-path-bad", "/foo/bar/asdfasdfasdfasdf", NULL); + do_test_utils_path_keys("keys-path-from-ifcfg-no-path", "ifcfg-FooBar", "keys-FooBar"); + do_test_utils_path_keys("keys-path-from-ifcfg", + "/foo/bar/ifcfg-FooBar", + "/foo/bar/keys-FooBar"); + do_test_utils_path_keys("keys-path-from-route", + "/foo/bar/route-FooBar", + "/foo/bar/keys-FooBar"); + + do_test_utils_path_route("route-path-bad", "/foo/bar/asdfasdfasdfasdf", NULL); + do_test_utils_path_route("route-path-from-ifcfg-no-path", "ifcfg-FooBar", "route-FooBar"); + do_test_utils_path_route("route-path-from-ifcfg", + "/foo/bar/ifcfg-FooBar", + "/foo/bar/route-FooBar"); + do_test_utils_path_route("route-path-from-keys", + "/foo/bar/keys-FooBar", + "/foo/bar/route-FooBar"); +} + +/*****************************************************************************/ + +static void +do_test_utils_ignored(const char *desc, const char *path, gboolean expected_ignored) +{ + gboolean result; + + result = utils_should_ignore_file(path, FALSE); + g_assert(result == expected_ignored); +} + +static void +test_utils_ignore(void) +{ + do_test_utils_ignored("ignored-ifcfg", "ifcfg-FooBar", FALSE); + do_test_utils_ignored("ignored-keys", "keys-FooBar", FALSE); + do_test_utils_ignored("ignored-route", "route-FooBar", FALSE); + do_test_utils_ignored("ignored-bak", "ifcfg-FooBar" BAK_TAG, TRUE); + do_test_utils_ignored("ignored-tilde", "ifcfg-FooBar" TILDE_TAG, TRUE); + do_test_utils_ignored("ignored-orig", "ifcfg-FooBar" ORIG_TAG, TRUE); + do_test_utils_ignored("ignored-rej", "ifcfg-FooBar" REJ_TAG, TRUE); + do_test_utils_ignored("ignored-rpmnew", "ifcfg-FooBar" RPMNEW_TAG, TRUE); + do_test_utils_ignored("ignored-augnew", "ifcfg-FooBar" AUGNEW_TAG, TRUE); + do_test_utils_ignored("ignored-augtmp", "ifcfg-FooBar" AUGTMP_TAG, TRUE); +} + +/*****************************************************************************/ + +static void +test_sriov_read(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingSriov * s_sriov; + NMSriovVF * vf; + GVariant * variant; + GError * error = NULL; + char * str; + + connection = + _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-sriov", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0"); + + s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(connection, NM_TYPE_SETTING_SRIOV)); + g_assert(s_sriov); + + g_assert_cmpint(nm_setting_sriov_get_total_vfs(s_sriov), ==, 16); + g_assert_cmpint(nm_setting_sriov_get_num_vfs(s_sriov), ==, 3); + g_assert_cmpint(nm_setting_sriov_get_autoprobe_drivers(s_sriov), ==, NM_TERNARY_FALSE); + + /* VF 3 */ + vf = nm_setting_sriov_get_vf(s_sriov, 0); + g_assert(vf); + g_assert_cmpint(nm_sriov_vf_get_index(vf), ==, 3); + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAC); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(variant, NULL), ==, "55:44:33:22:11:00"); + + variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)); + g_assert_cmpint(g_variant_get_boolean(variant), ==, TRUE); + + /* VF 12 */ + vf = nm_setting_sriov_get_vf(s_sriov, 1); + str = nm_utils_sriov_vf_to_str(vf, FALSE, &error); + g_assert_no_error(error); + g_assert_cmpstr(str, ==, "12 min-tx-rate=100 trust=false vlans=1.200.ad"); + g_free(str); + + /* VF 15 */ + vf = nm_setting_sriov_get_vf(s_sriov, 2); + str = nm_utils_sriov_vf_to_str(vf, FALSE, &error); + g_assert_no_error(error); + g_assert_cmpstr(str, ==, "15 mac=01:23:45:67:89:ab max-tx-rate=200 vlans=2"); + g_free(str); +} + +static void +test_sriov_write(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMSettingSriov * s_sriov; + NMSriovVF * vf; + gs_unref_ptrarray GPtrArray *vfs = NULL; + NMIPAddress * addr; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write SR-IOV config", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + /* SRIOV setting */ + s_sriov = (NMSettingSriov *) nm_setting_sriov_new(); + nm_connection_add_setting(connection, NM_SETTING(s_sriov)); + + vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); + + vf = nm_utils_sriov_vf_from_str("2 mac=55:55:55:55:55:55 vlans=3.10.ad;10", &error); + nmtst_assert_success(vf, error); + g_ptr_array_add(vfs, vf); + + vf = nm_utils_sriov_vf_from_str("19 spoof-check=true", &error); + nmtst_assert_success(vf, error); + g_ptr_array_add(vfs, vf); + + g_object_set(s_sriov, + NM_SETTING_SRIOV_TOTAL_VFS, + 64, + NM_SETTING_SRIOV_VFS, + vfs, + NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, + NM_TERNARY_TRUE, + NULL); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test-sriov-write.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +/*****************************************************************************/ + +static void +test_tc_read(void) +{ + NMConnection * connection; + NMSettingTCConfig *s_tc; + NMTCQdisc * qdisc; + NMTCTfilter * filter; + char * str; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-tc", NULL, TYPE_ETHERNET, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0"); + + s_tc = nm_connection_get_setting_tc_config(connection); + g_assert(s_tc); + + g_assert_cmpint(nm_setting_tc_config_get_num_qdiscs(s_tc), ==, 1); + qdisc = nm_setting_tc_config_get_qdisc(s_tc, 0); + g_assert(qdisc); + g_assert_cmpint(nm_tc_qdisc_get_parent(qdisc), ==, TC_H_ROOT); + g_assert_cmpint(nm_tc_qdisc_get_handle(qdisc), ==, TC_H_UNSPEC); + g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc), ==, "fq_codel"); + + g_assert_cmpint(nm_setting_tc_config_get_num_tfilters(s_tc), ==, 1); + filter = nm_setting_tc_config_get_tfilter(s_tc, 0); + g_assert(filter); + str = nm_utils_tc_tfilter_to_str(filter, NULL); + g_assert_cmpstr(str, ==, "parent 1234: matchall action simple sdata Hello"); + g_free(str); + + g_object_unref(connection); +} + +static void +test_tc_write(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMSettingTCConfig * s_tc; + NMTCQdisc * qdisc; + NMTCTfilter * tfilter; + NMIPAddress * addr; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write TC config", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + /* TC setting */ + s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_tc)); + + qdisc = nm_tc_qdisc_new("pfifo_fast", TC_H_MAKE(0x2468 << 16, 0x2), &error); + g_assert_no_error(error); + nm_setting_tc_config_add_qdisc(s_tc, qdisc); + nm_tc_qdisc_unref(qdisc); + + tfilter = + nm_utils_tc_tfilter_from_str("parent 1234: matchall action simple sdata Hello", &error); + g_assert_no_error(error); + nm_setting_tc_config_add_tfilter(s_tc, tfilter); + nm_tc_tfilter_unref(tfilter); + + nm_connection_add_setting(connection, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test-tc-write.cexpected", + &testfile); + + reread = _connection_from_file(testfile, NULL, TYPE_BOND, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +/*****************************************************************************/ + +static void +test_well_known_keys(void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) { + const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i]; + + g_assert(ti->key_name); + g_assert(ti->key_name[0]); + g_assert(NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_WELL_KNOWN)); + g_assert( + nm_utils_is_power_of_two(ti->key_flags + & (NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED + | NMS_IFCFG_KEY_TYPE_IS_PREFIX))); + } + + for (i = 1; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) { + const NMSIfcfgKeyTypeInfo *ti_prev = &nms_ifcfg_well_known_keys[i - 1]; + const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i]; + + g_assert_cmpstr(ti_prev->key_name, <, ti->key_name); + } + + for (i = 0; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) { + const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i]; + gs_free char * key_name = NULL; + gssize idx; + + g_assert(ti == nms_ifcfg_well_known_key_find_info(ti->key_name, &idx)); + g_assert_cmpint(i, ==, idx); + + key_name = g_strdup(ti->key_name); + g_assert(ti == nms_ifcfg_well_known_key_find_info(key_name, &idx)); + g_assert_cmpint(i, ==, idx); + } + +#define _test_well_known(key, expected) \ + G_STMT_START \ + { \ + const NMSIfcfgKeyTypeInfo *_ti; \ + const char * _expected = (expected); \ + \ + _ti = nms_ifcfg_rh_utils_is_well_known_key("" key ""); \ + if (!_expected) { \ + g_assert(!_ti); \ + } else { \ + g_assert(_ti); \ + g_assert_cmpstr(_ti->key_name, ==, _expected); \ + } \ + } \ + G_STMT_END + +#define _test_well_known_plain(key) _test_well_known("" key "", "" key "") + + _test_well_known_plain("ONBOOT"); + _test_well_known("NM_USER_", NULL); + _test_well_known("NM_USER_x", "NM_USER_"); + _test_well_known("IPADDR", "IPADDR"); + _test_well_known("IPADDR1", "IPADDR"); + _test_well_known("IPADDRx", NULL); +} + +/*****************************************************************************/ + +static void +_do_utils_has_route_file_new_syntax_size(gboolean has_new_syntax, + const char *content, + gssize content_len) +{ + nmtst_auto_unlinkfile char *testfile = + g_strdup(TEST_SCRATCH_DIR "/utils-has-route-file-new-syntax-test.txt"); + gboolean val; + + nmtst_file_set_contents_size(testfile, content, content_len); + + val = utils_has_route_file_new_syntax(testfile); + + g_assert_cmpint(val, ==, has_new_syntax); +} +#define _do_utils_has_route_file_new_syntax(has_new_syntax, content) \ + _do_utils_has_route_file_new_syntax_size(has_new_syntax, (content), NM_STRLEN(content)) + +static void +test_utils_has_route_file_new_syntax(void) +{ + _do_utils_has_route_file_new_syntax(TRUE, ""); + _do_utils_has_route_file_new_syntax(FALSE, "\0"); + _do_utils_has_route_file_new_syntax(FALSE, "\n"); + _do_utils_has_route_file_new_syntax(FALSE, "ADDRESS=bogus"); + _do_utils_has_route_file_new_syntax(FALSE, "ADDRESS=bogus\0"); + _do_utils_has_route_file_new_syntax(TRUE, "ADDRESS1=b\0ogus\0"); + _do_utils_has_route_file_new_syntax(TRUE, "ADDRESS1=bogus\0"); + _do_utils_has_route_file_new_syntax(TRUE, "\n\n\tADDRESS1=bogus\0"); + _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tADDRESS=bogus\n"); + _do_utils_has_route_file_new_syntax(TRUE, "\n\n\tADDRESS=bogus\n ADDRESS000=\n"); + _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tROUTE1=bogus\n ADDRES=\n"); + _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tADDRESS=bogus\n ADDRESS\000000=\n"); +} + +/*****************************************************************************/ + +static void +test_ethtool_names(void) +{ + static const struct { + NMEthtoolID ethtool_id; + const char *kernel_name; + } kernel_names[] = { + {NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"}, + {NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"}, + {NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"}, + {NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"}, + {NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"}, + {NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"}, + {NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"}, + {NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"}, + }; + const struct { + guint nm_ethtool_id_first; + guint nm_ethtool_id_last; + } s_idxs[] = { + {_NM_ETHTOOL_ID_FEATURE_FIRST, _NM_ETHTOOL_ID_FEATURE_LAST}, + {_NM_ETHTOOL_ID_COALESCE_FIRST, _NM_ETHTOOL_ID_COALESCE_LAST}, + {_NM_ETHTOOL_ID_RING_FIRST, _NM_ETHTOOL_ID_RING_LAST}, + }; + const NMEthtoolData *data; + NMEthtoolID id; + guint i, k; + + for (k = 0; k < sizeof(s_idxs) / sizeof(*s_idxs); ++k) { + for (id = s_idxs[k].nm_ethtool_id_first; id <= s_idxs[k].nm_ethtool_id_last; id++) { + const char *ifcfg_rh_name; + + g_assert(id >= 0); + g_assert(id < G_N_ELEMENTS(_nm_ethtool_ifcfg_names)); + ifcfg_rh_name = _nm_ethtool_ifcfg_names[id]; + g_assert(ifcfg_rh_name && ifcfg_rh_name[0]); + + for (i = s_idxs[k].nm_ethtool_id_first; i < s_idxs[k].nm_ethtool_id_last; i++) { + if (i != id) + g_assert_cmpstr(ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]); + } + + g_assert_cmpstr(nms_ifcfg_rh_utils_get_ethtool_name(id), ==, ifcfg_rh_name); + + data = nms_ifcfg_rh_utils_get_ethtool_by_name(ifcfg_rh_name, nm_ethtool_id_to_type(id)); + + g_assert(data); + g_assert(data->id == id); + } + } + + for (i = 0; i < G_N_ELEMENTS(kernel_names); i++) { + const char *name = kernel_names[i].kernel_name; + + id = kernel_names[i].ethtool_id; + + data = nms_ifcfg_rh_utils_get_ethtool_by_name(name, nm_ethtool_id_to_type(id)); + + g_assert(data); + g_assert(data->id == id); + g_assert_cmpstr(nms_ifcfg_rh_utils_get_ethtool_name(id), !=, name); + } +} + +/*****************************************************************************/ + +#define TPATH "/settings/plugins/ifcfg-rh/" + +#define TEST_IFCFG_WIFI_OPEN_SSID_LONG_QUOTED \ + TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-long-quoted" +#define TEST_IFCFG_WIFI_OPEN_SSID_LONG_HEX TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-long-hex" + +#define DEFAULT_HEX_PSK "7d308b11df1b4243b0f78e5f3fc68cdbb9a264ed0edf4c188edf329ff5b467f0" + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + int errsv; + + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + if (g_mkdir_with_parents(TEST_SCRATCH_DIR_TMP, 0755) != 0) { + errsv = errno; + g_error("failure to create test directory \"%s\": %s", + TEST_SCRATCH_DIR_TMP, + nm_strerror_native(errsv)); + } + + g_test_add_func(TPATH "svUnescape", test_svUnescape); + + g_test_add_data_func(TPATH "write-unknown/1", + TEST_IFCFG_DIR "/ifcfg-test-write-unknown-1", + test_write_unknown); + g_test_add_data_func(TPATH "write-unknown/2", + TEST_IFCFG_DIR "/ifcfg-test-write-unknown-2", + test_write_unknown); + g_test_add_data_func(TPATH "write-unknown/3", + TEST_IFCFG_DIR "/ifcfg-test-write-unknown-3", + test_write_unknown); + g_test_add_data_func(TPATH "write-unknown/4", + TEST_IFCFG_DIR "/ifcfg-test-write-unknown-4", + test_write_unknown); + + g_test_add_func(TPATH "vlan-trailing-spaces", test_read_vlan_trailing_spaces); + + g_test_add_func(TPATH "unmanaged", test_read_unmanaged); + g_test_add_func(TPATH "unmanaged-unrecognized", test_read_unmanaged_unrecognized); + g_test_add_func(TPATH "unrecognized", test_read_unrecognized); + g_test_add_func(TPATH "basic", test_read_basic); + g_test_add_func(TPATH "miscellaneous-variables", test_read_miscellaneous_variables); + g_test_add_func(TPATH "variables-corner-cases", test_read_variables_corner_cases); + g_test_add_data_func(TPATH "no-prefix/8", + GUINT_TO_POINTER(8), + test_read_wired_static_no_prefix); + g_test_add_data_func(TPATH "no-prefix/16", + GUINT_TO_POINTER(16), + test_read_wired_static_no_prefix); + g_test_add_data_func(TPATH "no-prefix/24", + GUINT_TO_POINTER(24), + test_read_wired_static_no_prefix); + g_test_add_data_func(TPATH "static-ip6-only-gw/_NULL_", + NULL, + test_write_wired_static_ip6_only_gw); + g_test_add_data_func(TPATH "static-ip6-only-gw/::", "::", test_write_wired_static_ip6_only_gw); + g_test_add_data_func(TPATH "static-ip6-only-gw/2001:db8:8:4::2", + "2001:db8:8:4::2", + test_write_wired_static_ip6_only_gw); + g_test_add_data_func(TPATH "static-ip6-only-gw/::ffff:255.255.255.255", + "::ffff:255.255.255.255", + test_write_wired_static_ip6_only_gw); + g_test_add_func(TPATH "ip6/disabled", test_write_ip6_disabled); + g_test_add_func(TPATH "read-dns-options", test_read_dns_options); + g_test_add_func(TPATH "clear-master", test_clear_master); + + nmtst_add_test_func(TPATH "read-static", + test_read_wired_static, + TEST_IFCFG_DIR "/ifcfg-test-wired-static", + "System test-wired-static", + GINT_TO_POINTER(TRUE)); + nmtst_add_test_func(TPATH "read-static-bootproto", + test_read_wired_static, + TEST_IFCFG_DIR "/ifcfg-test-wired-static-bootproto", + "System test-wired-static-bootproto", + GINT_TO_POINTER(FALSE)); + + g_test_add_func(TPATH "read-netmask-1", test_read_netmask_1); + + g_test_add_func(TPATH "read-dhcp", test_read_wired_dhcp); + g_test_add_func(TPATH "read-dhcp-plus-ip", test_read_wired_dhcp_plus_ip); + g_test_add_func(TPATH "read-shared-plus-ip", test_read_wired_shared_plus_ip); + g_test_add_func(TPATH "read-dhcp-send-hostname", test_read_write_wired_dhcp_send_hostname); + g_test_add_func(TPATH "read-dhcpv6-hostname-fallback", + test_read_wired_dhcpv6_hostname_fallback); + g_test_add_func(TPATH "read-global-gateway", test_read_wired_global_gateway); + g_test_add_func(TPATH "read-global-gateway-ignore", test_read_wired_global_gateway_ignore); + g_test_add_func(TPATH "read-obsolete-gateway-n", test_read_wired_obsolete_gateway_n); + g_test_add_func(TPATH "read-never-default", test_read_wired_never_default); + g_test_add_func(TPATH "read-defroute-no", test_read_wired_defroute_no); + g_test_add_func(TPATH "read-defroute-no-gatewaydev-yes", + test_read_wired_defroute_no_gatewaydev_yes); + g_test_add_func(TPATH "routes/read-static", test_read_wired_static_routes); + g_test_add_func(TPATH "routes/read-static-legacy", test_read_wired_static_routes_legacy); + + nmtst_add_test_func(TPATH "wired/read/manual/1", + test_read_wired_ipv4_manual, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-1", + "System test-wired-ipv4-manual-1"); + nmtst_add_test_func(TPATH "wired/read/manual/2", + test_read_wired_ipv4_manual, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-2", + "System test-wired-ipv4-manual-2"); + nmtst_add_test_func(TPATH "wired/read/manual/3", + test_read_wired_ipv4_manual, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-3", + "System test-wired-ipv4-manual-3"); + nmtst_add_test_func(TPATH "wired/read/manual/4", + test_read_wired_ipv4_manual, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-4", + "System test-wired-ipv4-manual-4"); + + g_test_add_func(TPATH "user/1", test_user_1); + + g_test_add_func(TPATH "wired/ipv6-manual", test_read_wired_ipv6_manual); + + nmtst_add_test_func(TPATH "wired-ipv6-only/0", + test_read_wired_ipv6_only, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-only", + "System test-wired-ipv6-only"); + nmtst_add_test_func(TPATH "wired-ipv6-only/1", + test_read_wired_ipv6_only, + TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-only-1", + "System test-wired-ipv6-only-1"); + + g_test_add_func(TPATH "wired/dhcpv6-only", test_read_wired_dhcp6_only); + g_test_add_func(TPATH "wired/autoip", test_read_wired_autoip); + g_test_add_func(TPATH "wired/onboot/no", test_read_onboot_no); + g_test_add_func(TPATH "wired/no-ip", test_read_noip); + g_test_add_func(TPATH "802-1x/peap/mschapv2", test_read_wired_8021x_peap_mschapv2); + + nmtst_add_test_func(TPATH "test-wired-8021x-tls/agent", + test_read_wired_8021x_tls_secret_flags, + TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-agent", + GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED)); + nmtst_add_test_func( + TPATH "test-wired-8021x-tls/always", + test_read_wired_8021x_tls_secret_flags, + TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-always", + GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED)); + + g_test_add_func(TPATH "802-1x/subj-matches", test_read_write_802_1X_subj_matches); + g_test_add_func(TPATH "802-1x/ttls-eapgtc", test_read_802_1x_ttls_eapgtc); + g_test_add_func(TPATH "802-1x/password_raw", test_read_write_802_1x_password_raw); + g_test_add_func(TPATH "802-1x/tls-p12-no-client-cert", test_read_802_1x_tls_p12_no_client_cert); + + g_test_add_data_func(TPATH "wired/read/aliases/good/0", + GINT_TO_POINTER(0), + test_read_wired_aliases_good); + g_test_add_data_func(TPATH "wired/read/aliases/good/3", + GINT_TO_POINTER(3), + test_read_wired_aliases_good); + g_test_add_func(TPATH "wired/read/aliases/bad1", test_read_wired_aliases_bad_1); + g_test_add_func(TPATH "wired/read/aliases/bad2", test_read_wired_aliases_bad_2); + g_test_add_func(TPATH "wifi/read/open", test_read_wifi_open); + g_test_add_func(TPATH "wifi/read/open/auto", test_read_wifi_open_auto); + g_test_add_func(TPATH "wifi/read/open/hex-ssid", test_read_wifi_open_ssid_hex); + g_test_add_func(TPATH "wifi/read/open-ssid/bad-hex", test_read_wifi_open_ssid_hex_bad); + g_test_add_data_func(TPATH "wifi/read/open-ssid/long-hex", + TEST_IFCFG_WIFI_OPEN_SSID_LONG_HEX, + test_read_wifi_open_ssid_bad); + g_test_add_data_func(TPATH "wifi/read/open-ssid/long-quoted", + TEST_IFCFG_WIFI_OPEN_SSID_LONG_QUOTED, + test_read_wifi_open_ssid_bad); + g_test_add_func(TPATH "wifi/read/open/quoted-ssid", test_read_wifi_open_ssid_quoted); + g_test_add_func(TPATH "wifi/read/wep", test_read_wifi_wep); + g_test_add_func(TPATH "wifi/read/wep/adhoc", test_read_wifi_wep_adhoc); + g_test_add_func(TPATH "wifi/read/wep/passphrase", test_read_wifi_wep_passphrase); + g_test_add_func(TPATH "wifi/read/wep/40-ascii", test_read_wifi_wep_40_ascii); + g_test_add_func(TPATH "wifi/read/wep/104-ascii", test_read_wifi_wep_104_ascii); + g_test_add_func(TPATH "wifi/read/leap", test_read_wifi_leap); + + nmtst_add_test_func(TPATH "wifi-leap-secret-flags/agent", + test_read_wifi_leap_secret_flags, + TEST_IFCFG_DIR "/ifcfg-test-wifi-leap-agent", + GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED)); + nmtst_add_test_func( + TPATH "wifi-leap-secret-flags/ask", + test_read_wifi_leap_secret_flags, + TEST_IFCFG_DIR "/ifcfg-test-wifi-leap-always-ask", + GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED)); + + g_test_add_func(TPATH "wifi/read/wpa-psk", test_read_wifi_wpa_psk); + g_test_add_func(TPATH "wifi/read/wpa-psk/2", test_read_wifi_wpa_psk_2); + g_test_add_func(TPATH "wifi/read/wpa-psk/unquoted", test_read_wifi_wpa_psk_unquoted); + g_test_add_func(TPATH "wifi/read/wpa-psk/unquoted2", test_read_wifi_wpa_psk_unquoted2); + g_test_add_func(TPATH "wifi/read/wpa-psk/adhoc", test_read_wifi_wpa_psk_adhoc); + g_test_add_func(TPATH "wifi/read/wpa-psk/hex", test_read_wifi_wpa_psk_hex); + g_test_add_func(TPATH "wifi/read/sae", test_read_wifi_sae); + g_test_add_func(TPATH "wifi/read/owe", test_read_wifi_owe); + g_test_add_func(TPATH "wifi/read/dynamic-wep/leap", test_read_wifi_dynamic_wep_leap); + g_test_add_func(TPATH "wifi/read/wpa/eap/tls", test_read_wifi_wpa_eap_tls); + g_test_add_func(TPATH "wifi/read/wpa/eap/ttls/tls", test_read_wifi_wpa_eap_ttls_tls); + g_test_add_func(TPATH "wifi/read/wpa/eap-suite-b-192/tls", + test_read_wifi_wpa_eap_suite_b_192_tls); + g_test_add_func(TPATH "wifi/read/dynamic-wep/eap/ttls/chap", test_read_wifi_wep_eap_ttls_chap); + g_test_add_func(TPATH "wifi/read-band-a", test_read_wifi_band_a); + g_test_add_func(TPATH "wifi/read-band-a-channel-mismatch", + test_read_wifi_band_a_channel_mismatch); + g_test_add_func(TPATH "wifi/read-band-bg-channel-mismatch", + test_read_wifi_band_bg_channel_mismatch); + g_test_add_func(TPATH "wifi/read-hidden", test_read_wifi_hidden); + + nmtst_add_test_func(TPATH "wifi/read-mac-random-always", + test_read_wifi_mac_random, + "always", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_ALWAYS)); + nmtst_add_test_func(TPATH "wifi/read-mac-random-never", + test_read_wifi_mac_random, + "never", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER)); + nmtst_add_test_func(TPATH "wifi/read-mac-random-default", + test_read_wifi_mac_random, + "default", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT)); + nmtst_add_test_func(TPATH "wifi/read-mac-random-missing", + test_read_wifi_mac_random, + "missing", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT)); + + nmtst_add_test_func(TPATH "wifi/write-mac-random-always", + test_write_wifi_mac_random, + "always", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_ALWAYS), + "always"); + nmtst_add_test_func(TPATH "wifi/write-mac-random-never", + test_write_wifi_mac_random, + "never", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER), + "never"); + nmtst_add_test_func(TPATH "wifi/write-mac-random-default", + test_write_wifi_mac_random, + "default", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT), + "default"); + nmtst_add_test_func(TPATH "wifi/write-mac-random-missing", + test_write_wifi_mac_random, + "missing", + GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER), + "never"); + + g_test_add_func(TPATH "wifi/read/wep-no-keys", test_read_wifi_wep_no_keys); + g_test_add_func(TPATH "wifi/read/wep-agent-keys", test_read_wifi_wep_agent_keys); + g_test_add_func(TPATH "infiniband/read", test_read_infiniband); + g_test_add_func(TPATH "vlan/read", test_read_vlan_interface); + g_test_add_func(TPATH "vlan/read-flags-1", test_read_vlan_flags_1); + g_test_add_func(TPATH "vlan/read-flags-2", test_read_vlan_flags_2); + g_test_add_func(TPATH "vlan/read/only-vlanid", test_read_vlan_only_vlan_id); + g_test_add_func(TPATH "vlan/read/only-device", test_read_vlan_only_device); + g_test_add_func(TPATH "vlan/read/physdev", test_read_vlan_physdev); + g_test_add_func(TPATH "vlan/read/reorder-hdr-1", test_read_vlan_reorder_hdr_1); + g_test_add_func(TPATH "vlan/read/reorder-hdr-2", test_read_vlan_reorder_hdr_2); + g_test_add_func(TPATH "wired/read/read-wake-on-lan", test_read_wired_wake_on_lan); + g_test_add_func(TPATH "wired/read/read-auto-negotiate-off", test_read_wired_auto_negotiate_off); + g_test_add_func(TPATH "wired/read/read-auto-negotiate-on", test_read_wired_auto_negotiate_on); + g_test_add_func(TPATH "wired/read/unkwnown-ethtool-opt", test_read_wired_unknown_ethtool_opt); + + g_test_add_func(TPATH "wired/write/static", test_write_wired_static); + g_test_add_func(TPATH "wired/write/static-with-generic", test_write_wired_static_with_generic); + g_test_add_func(TPATH "wired/write/static-ip6-only", test_write_wired_static_ip6_only); + g_test_add_func(TPATH "wired/write-static-routes", test_write_wired_static_routes); + g_test_add_func(TPATH "wired/read-write-static-routes-legacy", + test_read_write_static_routes_legacy); + g_test_add_func(TPATH "wired/write/dhcp", test_write_wired_dhcp); + g_test_add_func(TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip); + g_test_add_func(TPATH "wired/write/dhcp-8021x-peap-mschapv2", + test_write_wired_dhcp_8021x_peap_mschapv2); + g_test_add_func(TPATH "wired/write/match", test_write_wired_match); + g_test_add_func(TPATH "wired/write/routing-rules", test_write_routing_rules); + +#define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \ + nmtst_add_test_func(testpath, \ + test_write_wired_8021x_tls, \ + GINT_TO_POINTER(scheme), \ + GINT_TO_POINTER(flags)) + _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/1", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NM_SETTING_SECRET_FLAG_AGENT_OWNED); + _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/2", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NM_SETTING_SECRET_FLAG_NOT_SAVED); + _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/3", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NM_SETTING_SECRET_FLAG_AGENT_OWNED + | NM_SETTING_SECRET_FLAG_NOT_SAVED); + _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/4", + NM_SETTING_802_1X_CK_SCHEME_BLOB, + NM_SETTING_SECRET_FLAG_NONE); + + g_test_add_func(TPATH "wired/write-aliases", test_write_wired_aliases); + g_test_add_func(TPATH "ipv4/write-static-addresses-GATEWAY", test_write_gateway); + g_test_add_func(TPATH "wired/write-wake-on-lan", test_write_wired_wake_on_lan); + g_test_add_func(TPATH "wired/write-auto-negotiate-off", test_write_wired_auto_negotiate_off); + g_test_add_func(TPATH "wired/write-auto-negotiate-on", test_write_wired_auto_negotiate_on); + g_test_add_func(TPATH "wifi/write/open", test_write_wifi_open); + g_test_add_func(TPATH "wifi/write/open/hex-ssid", test_write_wifi_open_hex_ssid); + g_test_add_func(TPATH "wifi/write/wep", test_write_wifi_wep); + g_test_add_func(TPATH "wifi/write/wep/adhoc", test_write_wifi_wep_adhoc); + g_test_add_func(TPATH "wifi/write/wep/passphrase", test_write_wifi_wep_passphrase); + g_test_add_func(TPATH "wifi/write/wep/40-ascii", test_write_wifi_wep_40_ascii); + g_test_add_func(TPATH "wifi/write/wep/104-ascii", test_write_wifi_wep_104_ascii); + g_test_add_func(TPATH "wifi/write/leap", test_write_wifi_leap); + g_test_add_data_func(TPATH "wifi/write/leap/flags/agent", + GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED), + test_write_wifi_leap_secret_flags); + g_test_add_data_func(TPATH "wifi/write/leap/flags/not-saved", + GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_NOT_SAVED), + test_write_wifi_leap_secret_flags); + g_test_add_data_func( + TPATH "wifi/write/leap/flags/agent-and-not-saved", + GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED), + test_write_wifi_leap_secret_flags); + +#define _add_test_write_wifi_wpa_psk(testpath, name, wep_group, wpa, wpa2, psk) \ + nmtst_add_test_func(testpath, \ + test_write_wifi_wpa_psk, \ + name, \ + GPOINTER_TO_INT(wep_group), \ + GPOINTER_TO_INT(wpa), \ + GPOINTER_TO_INT(wpa2), \ + psk) + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-psk-write", + "Test Write Wifi WPA PSK", + FALSE, + TRUE, + FALSE, + DEFAULT_HEX_PSK); + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa2-psk-write", + "Test Write Wifi WPA2 PSK", + FALSE, + FALSE, + TRUE, + DEFAULT_HEX_PSK); + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-write", + "Test Write Wifi WPA WPA2 PSK", + FALSE, + TRUE, + TRUE, + DEFAULT_HEX_PSK); + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wep-wpa-wpa2-psk-write", + "Test Write Wifi WEP WPA WPA2 PSK", + TRUE, + TRUE, + TRUE, + DEFAULT_HEX_PSK); + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-passphrase-write", + "Test Write Wifi WPA WPA2 PSK Passphrase", + FALSE, + TRUE, + TRUE, + "really insecure passphrase04!"); + _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-passphrase-write-spec-chars", + "Test Write Wifi WPA WPA2 PSK Passphrase Special Chars", + FALSE, + TRUE, + TRUE, + "blah`oops\"grr'$*@~!%\\"); + + g_test_add_func(TPATH "wifi/write/wpa/psk/adhoc", test_write_wifi_wpa_psk_adhoc); + g_test_add_func(TPATH "wifi/write/wpa/eap/tls", test_write_wifi_wpa_eap_tls); + g_test_add_func(TPATH "wifi/write/wpa/eap/ttls/tls", test_write_wifi_wpa_eap_ttls_tls); + g_test_add_func(TPATH "wifi/write/wpa/eap/ttls/mschapv2", + test_write_wifi_wpa_eap_ttls_mschapv2); + g_test_add_func(TPATH "wifi/write/dynamic-wep/leap", test_write_wifi_dynamic_wep_leap); + g_test_add_func(TPATH "wifi/write-wpa-then-open", test_write_wifi_wpa_then_open); + g_test_add_func(TPATH "wifi/write-wpa-then-wep-with-perms", + test_write_wifi_wpa_then_wep_with_perms); + g_test_add_func(TPATH "wifi/write-hidden", test_write_wifi_hidden); + g_test_add_func(TPATH "wifi/write-band-a", test_write_wifi_band_a); + g_test_add_func(TPATH "wifi/write-ap-mode", test_write_wifi_ap_mode); + + g_test_add_func(TPATH "s390/read-qeth-static", test_read_wired_qeth_static); + g_test_add_func(TPATH "s390/write-qeth-dhcp", test_write_wired_qeth_dhcp); + g_test_add_func(TPATH "s390/read-ctc-static", test_read_wired_ctc_static); + g_test_add_func(TPATH "s390/write-ctc-dhcp", test_write_wired_ctc_dhcp); + + g_test_add_func(TPATH "permissions/read", test_read_permissions); + g_test_add_func(TPATH "permissions/write", test_write_permissions); + g_test_add_func(TPATH "wifi/write-wep-agent-keys", test_write_wifi_wep_agent_keys); + g_test_add_func(TPATH "infiniband/write", test_write_infiniband); + g_test_add_func(TPATH "vlan/write", test_write_vlan); + g_test_add_func(TPATH "vlan/write-flags", test_write_vlan_flags); + g_test_add_func(TPATH "vlan/write-only-vlanid", test_write_vlan_only_vlanid); + g_test_add_func(TPATH "vlan/write-vlan-reorder-hdr", test_write_vlan_reorder_hdr); + g_test_add_func(TPATH "wired/write-missing-ipv6", test_write_ethernet_missing_ipv6); + g_test_add_func(TPATH "write-dns-options", test_write_dns_options); + + g_test_add_func(TPATH "ibft/ignored", test_read_ibft_ignored); + + g_test_add_func(TPATH "dcb/read-basic", test_read_dcb_basic); + g_test_add_func(TPATH "dcb/write-basic", test_write_dcb_basic); + g_test_add_func(TPATH "dcb/default-app-priorities", test_read_dcb_default_app_priorities); + g_test_add_func(TPATH "dcb/bad-booleans", test_read_dcb_bad_booleans); + g_test_add_func(TPATH "dcb/short-booleans", test_read_dcb_short_booleans); + g_test_add_func(TPATH "dcb/bad-uints", test_read_dcb_bad_uints); + g_test_add_func(TPATH "dcb/short-uints", test_read_dcb_short_uints); + g_test_add_func(TPATH "dcb/bad-percent", test_read_dcb_bad_percent); + g_test_add_func(TPATH "dcb/short-percent", test_read_dcb_short_percent); + g_test_add_func(TPATH "dcb/pgpct-not-100", test_read_dcb_pgpct_not_100); + g_test_add_data_func(TPATH "fcoe/fabric", + (gpointer) NM_SETTING_DCB_FCOE_MODE_FABRIC, + test_read_fcoe_mode); + g_test_add_data_func(TPATH "fcoe/vn2vn", + (gpointer) NM_SETTING_DCB_FCOE_MODE_VN2VN, + test_read_fcoe_mode); + g_test_add_data_func(TPATH "fcoe/write-fabric", + (gpointer) NM_SETTING_DCB_FCOE_MODE_FABRIC, + test_write_fcoe_mode); + g_test_add_data_func(TPATH "fcoe/write-vn2vn", + (gpointer) NM_SETTING_DCB_FCOE_MODE_VN2VN, + test_write_fcoe_mode); + + g_test_add_func(TPATH "bond/read-master", test_read_bond_main); + g_test_add_func(TPATH "bond/read-master-eth-type", test_read_bond_eth_type); + g_test_add_func(TPATH "bond/read-slave", test_read_bond_slave); + g_test_add_func(TPATH "bond/read-slave-ib", test_read_bond_slave_ib); + g_test_add_func(TPATH "bond/write-master", test_write_bond_main); + g_test_add_func(TPATH "bond/write-slave", test_write_bond_slave); + g_test_add_func(TPATH "bond/write-slave-ib", test_write_bond_slave_ib); + g_test_add_func(TPATH "bond/bonding-opts-numeric-mode", test_read_bond_opts_mode_numeric); + + g_test_add_func(TPATH "bridge/read-master", test_read_bridge_main); + g_test_add_func(TPATH "bridge/write-master", test_write_bridge_main); + g_test_add_func(TPATH "bridge/read-component", test_read_bridge_component); + g_test_add_func(TPATH "bridge/write-component", test_write_bridge_component); + g_test_add_func(TPATH "bridge/read-missing-stp", test_read_bridge_missing_stp); + + g_test_add_data_func(TPATH "team/read-master-1", + TEST_IFCFG_DIR "/ifcfg-test-team-master-1", + test_read_team_master); + g_test_add_data_func(TPATH "team/read-master-2", + TEST_IFCFG_DIR "/ifcfg-test-team-master-2", + test_read_team_master); + g_test_add_data_func(TPATH "team/read-master-invalid", + TEST_IFCFG_DIR "/ifcfg-test-team-master-invalid", + test_read_team_master_invalid); + g_test_add_func(TPATH "team/write-master", test_write_team_master); + g_test_add_data_func(TPATH "team/read-port-1", + TEST_IFCFG_DIR "/ifcfg-test-team-port-1", + test_read_team_port); + g_test_add_data_func(TPATH "team/read-port-2", + TEST_IFCFG_DIR "/ifcfg-test-team-port-2", + test_read_team_port); + g_test_add_func(TPATH "team/write-port", test_write_team_port); + g_test_add_func(TPATH "team/write-infiniband-port", test_write_team_infiniband_port); + g_test_add_func(TPATH "team/read-port-empty-config", test_read_team_port_empty_config); + g_test_add_func(TPATH "team/reread-slave", test_team_reread_slave); + + g_test_add_func(TPATH "proxy/read-proxy-basic", test_read_proxy_basic); + g_test_add_func(TPATH "proxy/write-proxy-basic", test_write_proxy_basic); + + g_test_add_func(TPATH "sit/read/ignore", test_sit_read_ignore); + + /* Stuff we expect to fail for now */ + g_test_add_func(TPATH "pppoe/write-wired", test_write_wired_pppoe); + g_test_add_func(TPATH "vpn/write", test_write_vpn); + g_test_add_data_func(TPATH "wwan/write-gsm", + GUINT_TO_POINTER(TRUE), + test_write_mobile_broadband); + g_test_add_data_func(TPATH "wwan/write-cdma", + GUINT_TO_POINTER(FALSE), + test_write_mobile_broadband); + + g_test_add_func(TPATH "no-trailing-newline", test_ifcfg_no_trailing_newline); + + g_test_add_func(TPATH "utils/name", test_utils_name); + g_test_add_func(TPATH "utils/path", test_utils_path); + g_test_add_func(TPATH "utils/ignore", test_utils_ignore); + + g_test_add_func(TPATH "sriov/read", test_sriov_read); + g_test_add_func(TPATH "sriov/write", test_sriov_write); + + g_test_add_func(TPATH "tc/read", test_tc_read); + g_test_add_func(TPATH "tc/write", test_tc_write); + g_test_add_func(TPATH "utils/test_well_known_keys", test_well_known_keys); + g_test_add_func(TPATH "utils/test_utils_has_route_file_new_syntax", + test_utils_has_route_file_new_syntax); + + g_test_add_func(TPATH "utils/test_ethtool_names", test_ethtool_names); + + return g_test_run(); +} diff --git a/src/core/settings/plugins/ifupdown/meson.build b/src/core/settings/plugins/ifupdown/meson.build new file mode 100644 index 0000000..dd25278 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/meson.build @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnms_ifupdown_core = static_library( + 'nms-ifupdown-core', + sources: files( + 'nms-ifupdown-interface-parser.c', + 'nms-ifupdown-parser.c', + ), + dependencies: core_default_dep, + c_args: daemon_c_flags, +) + +libnm_settings_plugin_ifupdown = shared_module( + 'nm-settings-plugin-ifupdown', + sources: 'nms-ifupdown-plugin.c', + dependencies: core_plugin_dep, + c_args: daemon_c_flags, + link_with: libnms_ifupdown_core, + link_args: ldflags_linker_script_settings, + link_depends: linker_script_settings, + install: true, + install_dir: nm_plugindir, +) + +core_plugins += libnm_settings_plugin_ifupdown + +if enable_tests + subdir('tests') +endif diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c new file mode 100644 index 0000000..c766a35 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Tom Parker + * Copyright (C) 2004 Tom Parker + */ + +#include "nm-default.h" + +#include "nms-ifupdown-interface-parser.h" + +#include +#include +#include +#include + +#include "nm-utils.h" + +/*****************************************************************************/ + +static void +_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "ifupdown" +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static void +add_block(if_parser *parser, const char *type, const char *name) +{ + if_block *ifb; + gsize l_type, l_name; + + l_type = strlen(type) + 1; + l_name = strlen(name) + 1; + + ifb = g_malloc(sizeof(if_block) + l_type + l_name); + memcpy((char *) ifb->name, name, l_name); + ifb->type = &ifb->name[l_name]; + memcpy((char *) ifb->type, type, l_type); + c_list_init(&ifb->data_lst_head); + c_list_link_tail(&parser->block_lst_head, &ifb->block_lst); +} + +static void +add_data(if_parser *parser, const char *key, const char *data) +{ + if_block *last_block; + if_data * ifd; + char * idx; + gsize l_key, l_data; + + last_block = c_list_last_entry(&parser->block_lst_head, if_block, block_lst); + + /* Check if there is a block where we can attach our data */ + if (!last_block) + return; + + l_key = strlen(key) + 1; + l_data = strlen(data) + 1; + + ifd = g_malloc(sizeof(if_data) + l_key + l_data); + memcpy((char *) ifd->key, key, l_key); + ifd->data = &ifd->key[l_key]; + memcpy((char *) ifd->data, data, l_data); + + /* Normalize keys. Convert '_' to '-', as ifupdown accepts both variants. + * When querying keys via ifparser_getkey(), use '-'. */ + idx = (char *) ifd->key; + while ((idx = strchr(idx, '_'))) + *(idx++) = '-'; + + c_list_link_tail(&last_block->data_lst_head, &ifd->data_lst); +} + +/* join values in src with spaces into dst; dst needs to be large enough */ +static char * +join_values_with_spaces(char *dst, char **src) +{ + if (dst != NULL) { + *dst = '\0'; + if (src != NULL && *src != NULL) { + strcat(dst, *src); + + for (src++; *src != NULL; src++) { + strcat(dst, " "); + strcat(dst, *src); + } + } + } + return (dst); +} + +static void +_recursive_ifparser(if_parser *parser, const char *eni_file, int quiet) +{ + FILE *inp; + char line[255]; + int skip_to_block = 1; + int skip_long_line = 0; + int offs = 0; + + /* Check if interfaces file exists and open it */ + if (!g_file_test(eni_file, G_FILE_TEST_EXISTS)) { + if (!quiet) + _LOGW("interfaces file %s doesn't exist", eni_file); + return; + } + inp = fopen(eni_file, "re"); + if (inp == NULL) { + if (!quiet) + _LOGW("Can't open %s", eni_file); + return; + } + if (!quiet) + _LOGI(" interface-parser: parsing file %s", eni_file); + + while (!feof(inp)) { + char *token[128]; /* 255 chars can only be split into 127 tokens */ + char value[255]; /* large enough to join previously split tokens */ + char *safeptr; + int toknum; + int len = 0; + + char *ptr = fgets(line + offs, 255 - offs, inp); + if (ptr == NULL) + break; + + len = strlen(line); + /* skip over-long lines */ + if (!feof(inp) && len > 0 && line[len - 1] != '\n') { + if (!skip_long_line) { + if (!quiet) + _LOGW("Skipping over-long-line '%s...'", line); + } + skip_long_line = 1; + continue; + } + + /* trailing '\n' found: remove it & reset offset to 0 */ + if (len > 0 && line[len - 1] == '\n') { + line[--len] = '\0'; + offs = 0; + } + + /* if we're in long_line_skip mode, terminate it for real next line */ + if (skip_long_line) { + if (len == 0 || line[len - 1] != '\\') + skip_long_line = 0; + continue; + } + + /* unwrap wrapped lines */ + if (len > 0 && line[len - 1] == '\\') { + offs = len - 1; + continue; + } + +#define SPACES " \t" + /* tokenize input; */ + for (toknum = 0, token[toknum] = strtok_r(line, SPACES, &safeptr); token[toknum] != NULL; + toknum++, token[toknum] = strtok_r(NULL, SPACES, &safeptr)) + ; + + /* ignore comments and empty lines */ + if (toknum == 0 || *token[0] == '#') + continue; + + if (toknum < 2) { + if (!quiet) { + _LOGW("Can't parse interface line '%s'", join_values_with_spaces(value, token)); + } + skip_to_block = 1; + continue; + } + + /* There are six different stanzas: + * iface, mapping, auto, allow-*, source, and source-directory. + * Create a block for each of them except source and source-directory. */ + + /* iface stanza takes at least 3 parameters */ + if (nm_streq(token[0], "iface")) { + if (toknum < 4) { + if (!quiet) { + _LOGW("Can't parse iface line '%s'", join_values_with_spaces(value, token)); + } + continue; + } + add_block(parser, token[0], token[1]); + skip_to_block = 0; + add_data(parser, token[2], join_values_with_spaces(value, token + 3)); + } + /* auto and allow-auto stanzas are equivalent, + * both can take multiple interfaces as parameters: add one block for each */ + else if (NM_IN_STRSET(token[0], "auto", "allow-auto")) { + int i; + + for (i = 1; i < toknum; i++) + add_block(parser, "auto", token[i]); + skip_to_block = 0; + } else if (nm_streq(token[0], "mapping")) { + add_block(parser, token[0], join_values_with_spaces(value, token + 1)); + skip_to_block = 0; + } + /* allow-* can take multiple interfaces as parameters: add one block for each */ + else if (g_str_has_prefix(token[0], "allow-")) { + int i; + for (i = 1; i < toknum; i++) + add_block(parser, token[0], token[i]); + skip_to_block = 0; + } + /* source and source-directory stanzas take one or more paths as parameters */ + else if (NM_IN_STRSET(token[0], "source", "source-directory")) { + int i; + char *en_dir; + + skip_to_block = 0; + en_dir = g_path_get_dirname(eni_file); + for (i = 1; i < toknum; ++i) { + if (nm_streq(token[0], "source-directory")) + _ifparser_source(parser, token[i], en_dir, quiet, TRUE); + else + _ifparser_source(parser, token[i], en_dir, quiet, FALSE); + } + g_free(en_dir); + } else { + if (skip_to_block) { + if (!quiet) { + _LOGW("ignoring out-of-block data '%s'", join_values_with_spaces(value, token)); + } + } else + add_data(parser, token[0], join_values_with_spaces(value, token + 1)); + } + } + fclose(inp); + + if (!quiet) + _LOGI(" interface-parser: finished parsing file %s", eni_file); +} + +static void +_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir) +{ + char * abs_path; + const char *item; + wordexp_t we; + GDir * source_dir; + GError * error = NULL; + uint i; + + if (g_path_is_absolute(path)) + abs_path = g_strdup(path); + else + abs_path = g_build_filename(en_dir, path, NULL); + + if (!quiet) + _LOGI(" interface-parser: source line includes interfaces file(s) %s", abs_path); + + /* ifupdown uses WRDE_NOCMD for wordexp. */ + if (wordexp(abs_path, &we, WRDE_NOCMD)) { + if (!quiet) + _LOGW("word expansion for %s failed", abs_path); + } else { + for (i = 0; i < we.we_wordc; i++) { + if (dir) { + source_dir = g_dir_open(we.we_wordv[i], 0, &error); + if (!source_dir) { + if (!quiet) { + _LOGW("Failed to open directory %s: %s", we.we_wordv[i], error->message); + } + g_clear_error(&error); + } else { + while ((item = g_dir_read_name(source_dir))) + _ifparser_source(parser, item, we.we_wordv[i], quiet, FALSE); + g_dir_close(source_dir); + } + } else + _recursive_ifparser(parser, we.we_wordv[i], quiet); + } + wordfree(&we); + } + g_free(abs_path); +} + +if_parser * +ifparser_parse(const char *eni_file, int quiet) +{ + if_parser *parser; + + parser = g_slice_new(if_parser); + c_list_init(&parser->block_lst_head); + _recursive_ifparser(parser, eni_file, quiet); + return parser; +} + +static void +_destroy_data(if_data *ifd) +{ + c_list_unlink_stale(&ifd->data_lst); + g_free(ifd); +} + +static void +_destroy_block(if_block *ifb) +{ + if_data *ifd; + + while ((ifd = c_list_first_entry(&ifb->data_lst_head, if_data, data_lst))) + _destroy_data(ifd); + c_list_unlink_stale(&ifb->block_lst); + g_free(ifb); +} + +void +ifparser_destroy(if_parser *parser) +{ + if_block *ifb; + + while ((ifb = c_list_first_entry(&parser->block_lst_head, if_block, block_lst))) + _destroy_block(ifb); + g_slice_free(if_parser, parser); +} + +if_block * +ifparser_getfirst(if_parser *parser) +{ + return c_list_first_entry(&parser->block_lst_head, if_block, block_lst); +} + +guint +ifparser_get_num_blocks(if_parser *parser) +{ + return c_list_length(&parser->block_lst_head); +} + +if_block * +ifparser_getif(if_parser *parser, const char *iface) +{ + if_block *ifb; + + c_list_for_each_entry (ifb, &parser->block_lst_head, block_lst) { + if (nm_streq(ifb->type, "iface") && nm_streq(ifb->name, iface)) + return ifb; + } + return NULL; +} + +static if_data * +ifparser_findkey(if_block *iface, const char *key) +{ + if_data *ifd; + + c_list_for_each_entry (ifd, &iface->data_lst_head, data_lst) { + if (nm_streq(ifd->key, key)) + return ifd; + } + return NULL; +} + +const char * +ifparser_getkey(if_block *iface, const char *key) +{ + if_data *ifd; + + ifd = ifparser_findkey(iface, key); + return ifd ? ifd->data : NULL; +} + +gboolean +ifparser_haskey(if_block *iface, const char *key) +{ + return !!ifparser_findkey(iface, key); +} + +guint +ifparser_get_num_info(if_block *iface) +{ + return c_list_length(&iface->data_lst_head); +} diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h new file mode 100644 index 0000000..dd0e15f --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Tom Parker + * Copyright (C) 2004 Tom Parker + */ + +#ifndef _INTERFACE_PARSER_H +#define _INTERFACE_PARSER_H + +#include "c-list/src/c-list.h" + +typedef struct { + CList data_lst; + const char *data; + const char key[]; +} if_data; + +typedef struct { + CList block_lst; + CList data_lst_head; + const char *type; + const char name[]; +} if_block; + +typedef struct { + CList block_lst_head; +} if_parser; + +if_parser *ifparser_parse(const char *eni_file, int quiet); + +void ifparser_destroy(if_parser *parser); +NM_AUTO_DEFINE_FCN0(if_parser *, _nm_auto_ifparser, ifparser_destroy); +#define nm_auto_ifparser nm_auto(_nm_auto_ifparser) + +if_block * ifparser_getif(if_parser *parser, const char *iface); +if_block * ifparser_getfirst(if_parser *parser); +const char *ifparser_getkey(if_block *iface, const char *key); +gboolean ifparser_haskey(if_block *iface, const char *key); + +guint ifparser_get_num_blocks(if_parser *parser); +guint ifparser_get_num_info(if_block *iface); + +#endif diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c new file mode 100644 index 0000000..767e0da --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c @@ -0,0 +1,686 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Alexander Sack + * Copyright (C) 2008 Canonical Ltd. + */ + +#include "nm-default.h" + +#include "nms-ifupdown-parser.h" + +#include +#include +#include + +#include "nm-core-internal.h" +#include "settings/nm-settings-plugin.h" + +#include "nms-ifupdown-plugin.h" +#include "nms-ifupdown-parser.h" + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "ifupdown" +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +#define _str_has_prefix(val, prefix, require_suffix) \ + ({ \ + const char *_val = (val); \ + \ + (strncmp(_val, "" prefix "", NM_STRLEN(prefix)) == 0) \ + && (!(require_suffix) || _val[NM_STRLEN(prefix)] != '\0'); \ + }) + +static const char * +_ifupdownplugin_guess_connection_type(if_block *block) +{ + const char *ret_type = NULL; + + if (nm_streq0(ifparser_getkey(block, "inet"), "ppp")) + ret_type = NM_SETTING_PPP_SETTING_NAME; + else { + if_data *ifb; + + c_list_for_each_entry (ifb, &block->data_lst_head, data_lst) { + if (_str_has_prefix(ifb->key, "wireless-", FALSE) + || _str_has_prefix(ifb->key, "wpa-", FALSE)) { + ret_type = NM_SETTING_WIRELESS_SETTING_NAME; + break; + } + } + if (!ret_type) + ret_type = NM_SETTING_WIRED_SETTING_NAME; + } + + _LOGI("guessed connection type (%s) = %s", block->name, ret_type); + return ret_type; +} + +struct _Mapping { + const char * domain; + const gpointer target; +}; + +static gpointer +map_by_mapping(struct _Mapping *mapping, const char *key) +{ + struct _Mapping *curr = mapping; + + while (curr->domain) { + if (nm_streq(curr->domain, key)) + return curr->target; + curr++; + } + return NULL; +} + +static void +update_wireless_setting_from_if_block(NMConnection *connection, if_block *block) +{ + if_data * curr; + const char * value = ifparser_getkey(block, "inet"); + struct _Mapping mapping[] = {{"ssid", "ssid"}, + {"essid", "ssid"}, + {"mode", "mode"}, + {NULL, NULL}}; + + NMSettingWireless *wireless_setting = NULL; + + if (nm_streq0(value, "ppp")) + return; + + _LOGI("update wireless settings (%s).", block->name); + wireless_setting = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + + c_list_for_each_entry (curr, &block->data_lst_head, data_lst) { + if (_str_has_prefix(curr->key, "wireless-", TRUE)) { + const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wireless-")); + + _LOGI("wireless setting key: %s='%s'", newkey, curr->data); + if (nm_streq0(newkey, "ssid")) { + GBytes *ssid; + int len = strlen(curr->data); + + ssid = g_bytes_new(curr->data, len); + g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + _LOGI("setting wireless ssid = %d", len); + } else if (nm_streq0(newkey, "mode")) { + if (!g_ascii_strcasecmp(curr->data, "Managed") + || !g_ascii_strcasecmp(curr->data, "Auto")) + g_object_set(wireless_setting, + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_INFRA, + NULL); + else if (!g_ascii_strcasecmp(curr->data, "Ad-Hoc")) + g_object_set(wireless_setting, + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_ADHOC, + NULL); + else if (!g_ascii_strcasecmp(curr->data, "Master")) + g_object_set(wireless_setting, + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_AP, + NULL); + else + _LOGW("Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')", + curr->data); + } else { + g_object_set(wireless_setting, newkey, curr->data, NULL); + } + } else if (_str_has_prefix(curr->key, "wpa-", TRUE)) { + const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wpa-")); + + if (nm_streq0(newkey, "ssid")) { + GBytes *ssid; + int len = strlen(curr->data); + + ssid = g_bytes_new(curr->data, len); + g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + _LOGI("setting wpa ssid = %d", len); + } else if (newkey) { + g_object_set(wireless_setting, newkey, curr->data, NULL); + _LOGI("setting wpa newkey(%s)=data(%s)", newkey, curr->data); + } + } + } + nm_connection_add_setting(connection, (NMSetting *) wireless_setting); +} + +typedef char *(*IfupdownStrDupeFunc)(gconstpointer value, gpointer data); +typedef gpointer (*IfupdownStrToTypeFunc)(const char *value); + +static char * +normalize_dupe_wireless_key(gpointer value, gpointer data) +{ + char *valuec = value; + char *endc = valuec + strlen(valuec); + char *delim = valuec; + char *next = delim; + char *result = malloc(strlen(valuec) + 1); + char *result_cur = result; + + while (*delim && (next = strchr(delim, '-')) != NULL) { + if (next == delim) { + delim++; + continue; + } + strncpy(result_cur, delim, next - delim); + result_cur += next - delim; + delim = next + 1; + } + if (*delim && strlen(valuec) > GPOINTER_TO_UINT(delim - valuec)) { + strncpy(result_cur, delim, endc - delim); + result_cur += endc - delim; + } + *result_cur = '\0'; + return result; +} + +static char * +normalize_dupe(gpointer value, gpointer data) +{ + return g_strdup(value); +} + +static char * +normalize_tolower(gpointer value, gpointer data) +{ + return g_ascii_strdown(value, -1); +} + +static char * +normalize_psk(gpointer value, gpointer data) +{ + if (strlen(value) >= 8 && strlen(value) <= 64) + return g_strdup(value); + return NULL; +} + +static gpointer +string_to_gpointerint(const char *data) +{ + int result = (int) strtol(data, NULL, 10); + return GINT_TO_POINTER(result); +} + +static gpointer +string_to_glist_of_strings(const char *data) +{ + GSList *ret = NULL; + char * string = (char *) data; + while (string) { + char *next = NULL; + if ((next = strchr(string, ' ')) || (next = strchr(string, '\t')) + || (next = strchr(string, '\0'))) { + char *part = g_strndup(string, (next - string)); + ret = g_slist_append(ret, part); + if (*next) + string = next + 1; + else + string = NULL; + } else { + string = NULL; + } + } + return ret; +} + +static void +slist_free_all(gpointer slist) +{ + g_slist_free_full((GSList *) slist, g_free); +} + +static void +update_wireless_security_setting_from_if_block(NMConnection *connection, if_block *block) +{ + if_data * curr; + const char * value = ifparser_getkey(block, "inet"); + struct _Mapping mapping[] = {{"psk", "psk"}, + {"identity", "leap-username"}, + {"password", "leap-password"}, + {"key", "wep-key0"}, + {"key-mgmt", "key-mgmt"}, + {"group", "group"}, + {"pairwise", "pairwise"}, + {"proto", "proto"}, + {"pin", "pin"}, + {"wep-key0", "wep-key0"}, + {"wep-key1", "wep-key1"}, + {"wep-key2", "wep-key2"}, + {"wep-key3", "wep-key3"}, + {"wep-tx-keyidx", "wep-tx-keyidx"}, + {NULL, NULL}}; + + struct _Mapping dupe_mapping[] = {{"psk", normalize_psk}, + {"identity", normalize_dupe}, + {"password", normalize_dupe}, + {"key", normalize_dupe_wireless_key}, + {"key-mgmt", normalize_tolower}, + {"group", normalize_tolower}, + {"pairwise", normalize_tolower}, + {"proto", normalize_tolower}, + {"pin", normalize_dupe}, + {"wep-key0", normalize_dupe_wireless_key}, + {"wep-key1", normalize_dupe_wireless_key}, + {"wep-key2", normalize_dupe_wireless_key}, + {"wep-key3", normalize_dupe_wireless_key}, + {"wep-tx-keyidx", normalize_dupe}, + {NULL, NULL}}; + + struct _Mapping type_mapping[] = {{"group", string_to_glist_of_strings}, + {"pairwise", string_to_glist_of_strings}, + {"proto", string_to_glist_of_strings}, + {"wep-tx-keyidx", string_to_gpointerint}, + {NULL, NULL}}; + + struct _Mapping free_type_mapping[] = {{"group", slist_free_all}, + {"pairwise", slist_free_all}, + {"proto", slist_free_all}, + {NULL, NULL}}; + + NMSettingWirelessSecurity *wireless_security_setting; + NMSettingWireless * s_wireless; + gboolean security = FALSE; + + if (nm_streq0(value, "ppp")) + return; + + s_wireless = nm_connection_get_setting_wireless(connection); + g_return_if_fail(s_wireless); + + _LOGI("update wireless security settings (%s).", block->name); + wireless_security_setting = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new()); + + c_list_for_each_entry (curr, &block->data_lst_head, data_lst) { + if (_str_has_prefix(curr->key, "wireless-", TRUE)) { + const char * key = curr->key + NM_STRLEN("wireless-"); + char * property_value = NULL; + gpointer typed_property_value = NULL; + const char * newkey = map_by_mapping(mapping, key); + IfupdownStrDupeFunc dupe_func = map_by_mapping(dupe_mapping, key); + IfupdownStrToTypeFunc type_map_func = map_by_mapping(type_mapping, key); + GFreeFunc free_func = map_by_mapping(free_type_mapping, key); + if (!newkey || !dupe_func) + goto next; + + property_value = (*dupe_func)(curr->data, connection); + _LOGI("setting wireless security key: %s=%s", newkey, property_value); + + if (type_map_func) { + errno = 0; + typed_property_value = (*type_map_func)(property_value); + if (errno) + goto wireless_next; + } + + g_object_set(wireless_security_setting, + newkey, + typed_property_value ?: property_value, + NULL); + security = TRUE; + +wireless_next: + g_free(property_value); + if (typed_property_value && free_func) + (*free_func)(typed_property_value); + + } else if (_str_has_prefix(curr->key, "wpa-", TRUE)) { + const char * key = curr->key + NM_STRLEN("wpa-"); + char * property_value = NULL; + gpointer typed_property_value = NULL; + const char * newkey = map_by_mapping(mapping, key); + IfupdownStrDupeFunc dupe_func = map_by_mapping(dupe_mapping, key); + IfupdownStrToTypeFunc type_map_func = map_by_mapping(type_mapping, key); + GFreeFunc free_func = map_by_mapping(free_type_mapping, key); + if (!newkey || !dupe_func) + goto next; + + property_value = (*dupe_func)(curr->data, connection); + _LOGI("setting wpa security key: %s=%s", + newkey, + NM_IN_STRSET(newkey, + "key", + "leap-password", + "pin", + "psk", + "wep-key0", + "wep-key1", + "wep-key2", + "wep-key3") + ? "" + : property_value); + + if (type_map_func) { + errno = 0; + typed_property_value = (*type_map_func)(property_value); + if (errno) + goto wpa_next; + } + + g_object_set(wireless_security_setting, + newkey, + typed_property_value ?: property_value, + NULL); + security = TRUE; + +wpa_next: + g_free(property_value); + if (free_func && typed_property_value) + (*free_func)(typed_property_value); + } +next:; + } + + if (security) + nm_connection_add_setting(connection, NM_SETTING(wireless_security_setting)); +} + +static void +update_wired_setting_from_if_block(NMConnection *connection, if_block *block) +{ + NMSettingWired *s_wired = NULL; + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); +} + +static void +ifupdown_ip4_add_dns(NMSettingIPConfig *s_ip4, const char *dns) +{ + gs_free const char **list = NULL; + const char ** iter; + guint32 addr; + + if (dns == NULL) + return; + + list = nm_utils_strsplit_set(dns, " \t"); + for (iter = list; iter && *iter; iter++) { + if (!inet_pton(AF_INET, *iter, &addr)) { + _LOGW(" ignoring invalid nameserver '%s'", *iter); + continue; + } + + if (!nm_setting_ip_config_add_dns(s_ip4, *iter)) + _LOGW(" duplicate DNS domain '%s'", *iter); + } +} + +static gboolean +update_ip4_setting_from_if_block(NMConnection *connection, if_block *block, GError **error) +{ + gs_unref_object NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + const char * type = ifparser_getkey(block, "inet"); + + if (!nm_streq0(type, "static")) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + } else { + guint32 tmp_mask; + NMIPAddress *addr; + const char * address_v; + const char * netmask_v; + const char * gateway_v; + const char * nameserver_v; + const char * nameservers_v; + const char * search_v; + guint32 netmask_int = 32; + + /* Address */ + address_v = ifparser_getkey(block, "address"); + if (!address_v) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing IPv4 address"); + return FALSE; + } + + /* mask/prefix */ + netmask_v = ifparser_getkey(block, "netmask"); + if (netmask_v) { + if (strlen(netmask_v) < 7) { + netmask_int = atoi(netmask_v); + } else if (!inet_pton(AF_INET, netmask_v, &tmp_mask)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IPv4 netmask '%s'", + netmask_v); + return FALSE; + } else { + netmask_int = nm_utils_ip4_netmask_to_prefix(tmp_mask); + } + } + + /* Add the new address to the setting */ + addr = nm_ip_address_new(AF_INET, address_v, netmask_int, error); + if (!addr) + return FALSE; + + if (nm_setting_ip_config_add_address(s_ip4, addr)) { + _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip4)); + } else { + _LOGI("ignoring duplicate IP4 address"); + } + nm_ip_address_unref(addr); + + /* gateway */ + gateway_v = ifparser_getkey(block, "gateway"); + if (gateway_v) { + if (!nm_utils_ipaddr_is_valid(AF_INET, gateway_v)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IPv4 gateway '%s'", + gateway_v); + return FALSE; + } + if (!nm_setting_ip_config_get_gateway(s_ip4)) + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL); + } + + nameserver_v = ifparser_getkey(block, "dns-nameserver"); + ifupdown_ip4_add_dns(s_ip4, nameserver_v); + + nameservers_v = ifparser_getkey(block, "dns-nameservers"); + ifupdown_ip4_add_dns(s_ip4, nameservers_v); + + if (!nm_setting_ip_config_get_num_dns(s_ip4)) + _LOGI("No dns-nameserver configured in /etc/network/interfaces"); + + /* DNS searches */ + search_v = ifparser_getkey(block, "dns-search"); + if (search_v) { + gs_free const char **list = NULL; + const char ** iter; + + list = nm_utils_strsplit_set(search_v, " \t"); + for (iter = list; iter && *iter; iter++) { + if (!nm_setting_ip_config_add_dns_search(s_ip4, *iter)) + _LOGW(" duplicate DNS domain '%s'", *iter); + } + } + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + } + + nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip4))); + return TRUE; +} + +static void +ifupdown_ip6_add_dns(NMSettingIPConfig *s_ip6, const char *dns) +{ + gs_free const char **list = NULL; + const char ** iter; + struct in6_addr addr; + + if (dns == NULL) + return; + + list = nm_utils_strsplit_set(dns, " \t"); + for (iter = list; iter && *iter; iter++) { + if (!inet_pton(AF_INET6, *iter, &addr)) { + _LOGW(" ignoring invalid nameserver '%s'", *iter); + continue; + } + + if (!nm_setting_ip_config_add_dns(s_ip6, *iter)) + _LOGW(" duplicate DNS domain '%s'", *iter); + } +} + +static gboolean +update_ip6_setting_from_if_block(NMConnection *connection, if_block *block, GError **error) +{ + gs_unref_object NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + const char * type = ifparser_getkey(block, "inet6"); + + if (!NM_IN_STRSET(type, "static", "v4tunnel")) { + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + } else { + NMIPAddress *addr; + const char * address_v; + const char * prefix_v; + const char * gateway_v; + const char * nameserver_v; + const char * nameservers_v; + const char * search_v; + guint prefix_int; + + address_v = ifparser_getkey(block, "address"); + if (!address_v) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Missing IPv6 address"); + return FALSE; + } + + prefix_v = ifparser_getkey(block, "netmask"); + if (prefix_v) + prefix_int = _nm_utils_ascii_str_to_int64(prefix_v, 10, 0, 128, G_MAXINT); + else + prefix_int = 128; + + addr = nm_ip_address_new(AF_INET6, address_v, prefix_int, error); + if (!addr) + return FALSE; + + if (nm_setting_ip_config_add_address(s_ip6, addr)) { + _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip6)); + } else { + _LOGI("ignoring duplicate IP6 address"); + } + nm_ip_address_unref(addr); + + gateway_v = ifparser_getkey(block, "gateway"); + if (gateway_v) { + if (!nm_utils_ipaddr_is_valid(AF_INET6, gateway_v)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid IPv6 gateway '%s'", + gateway_v); + return FALSE; + } + if (!nm_setting_ip_config_get_gateway(s_ip6)) + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL); + } + + nameserver_v = ifparser_getkey(block, "dns-nameserver"); + ifupdown_ip6_add_dns(s_ip6, nameserver_v); + + nameservers_v = ifparser_getkey(block, "dns-nameservers"); + ifupdown_ip6_add_dns(s_ip6, nameservers_v); + + if (!nm_setting_ip_config_get_num_dns(s_ip6)) + _LOGI("No dns-nameserver configured in /etc/network/interfaces"); + + search_v = ifparser_getkey(block, "dns-search"); + if (search_v) { + gs_free const char **list = NULL; + const char ** iter; + + list = nm_utils_strsplit_set(search_v, " \t"); + for (iter = list; iter && *iter; iter++) { + if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter)) + _LOGW(" duplicate DNS domain '%s'", *iter); + } + } + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); + } + + nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip6))); + return TRUE; +} + +NMConnection * +ifupdown_new_connection_from_if_block(if_block *block, gboolean autoconnect, GError **error) +{ + gs_unref_object NMConnection *connection = NULL; + const char * type; + gs_free char * idstr = NULL; + gs_free char * uuid = NULL; + NMSettingConnection * s_con; + + connection = nm_simple_connection_new(); + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + type = _ifupdownplugin_guess_connection_type(block); + idstr = g_strconcat("Ifupdown (", block->name, ")", NULL); + + uuid = nm_utils_uuid_generate_from_string(idstr, -1, NM_UTILS_UUID_TYPE_LEGACY, NULL); + g_object_set(s_con, + NM_SETTING_CONNECTION_TYPE, + type, + NM_SETTING_CONNECTION_INTERFACE_NAME, + block->name, + NM_SETTING_CONNECTION_ID, + idstr, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + (gboolean)(!!autoconnect), + NULL); + + _LOGD("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s", + block->name, + type, + idstr, + nm_setting_connection_get_uuid(s_con)); + + if (nm_streq(type, NM_SETTING_WIRED_SETTING_NAME)) + update_wired_setting_from_if_block(connection, block); + else if (nm_streq(type, NM_SETTING_WIRELESS_SETTING_NAME)) { + update_wireless_setting_from_if_block(connection, block); + update_wireless_security_setting_from_if_block(connection, block); + } + + if (ifparser_haskey(block, "inet6")) { + if (!update_ip6_setting_from_if_block(connection, block, error)) + return FALSE; + } else { + if (!update_ip4_setting_from_if_block(connection, block, error)) + return FALSE; + } + + if (!nm_connection_normalize(connection, NULL, NULL, error)) + return NULL; + + return g_steal_pointer(&connection); +} diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h new file mode 100644 index 0000000..ced98db --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Alexander Sack + * Copyright (C) 2008 Canonical Ltd. + */ + +#ifndef __NMS_IFUPDOWN_PARSER_H__ +#define __NMS_IFUPDOWN_PARSER_H__ + +#include "nm-connection.h" +#include "nms-ifupdown-interface-parser.h" + +NMConnection * +ifupdown_new_connection_from_if_block(if_block *block, gboolean autoconnect, GError **error); + +#endif /* __NMS_IFUPDOWN_PARSER_H__ */ diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c new file mode 100644 index 0000000..8c22e3b --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Alexander Sack + * Copyright (C) 2007, 2008 Canonical Ltd. + * Copyright (C) 2009 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-ifupdown-plugin.h" + +#include "nm-core-internal.h" +#include "nm-core-utils.h" +#include "nm-config.h" +#include "settings/nm-settings-plugin.h" +#include "settings/nm-settings-storage.h" + +#include "nms-ifupdown-interface-parser.h" +#include "nms-ifupdown-parser.h" + +#define ENI_INTERFACES_FILE "/etc/network/interfaces" + +#define IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT TRUE + +/*****************************************************************************/ + +typedef struct { + NMConnection * connection; + NMSettingsStorage *storage; +} StorageData; + +typedef struct { + /* Stores an entry for blocks/interfaces read from /e/n/i and (if exists) + * the StorageData associated with the block. + */ + GHashTable *eni_ifaces; + + bool ifupdown_managed : 1; + + bool initialized : 1; + + bool already_reloaded : 1; +} NMSIfupdownPluginPrivate; + +struct _NMSIfupdownPlugin { + NMSettingsPlugin parent; + NMSIfupdownPluginPrivate _priv; +}; + +struct _NMSIfupdownPluginClass { + NMSettingsPluginClass parent; +}; + +G_DEFINE_TYPE(NMSIfupdownPlugin, nms_ifupdown_plugin, NM_TYPE_SETTINGS_PLUGIN) + +#define NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSIfupdownPlugin, NMS_IS_IFUPDOWN_PLUGIN) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "ifupdown" +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static GHashTable *load_eni_ifaces(NMSIfupdownPlugin *self); + +/*****************************************************************************/ + +static void +_storage_data_destroy(StorageData *sd) +{ + if (!sd) + return; + nm_g_object_unref(sd->connection); + nm_g_object_unref(sd->storage); + g_slice_free(StorageData, sd); +} + +/*****************************************************************************/ + +static void +initialize(NMSIfupdownPlugin *self) +{ + NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self); + gboolean ifupdown_managed; + + nm_assert(!priv->initialized); + + priv->initialized = TRUE; + + ifupdown_managed = nm_config_data_get_value_boolean(NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_IFUPDOWN, + NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED, + !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT); + _LOGI("management mode: %s", ifupdown_managed ? "managed" : "unmanaged"); + priv->ifupdown_managed = ifupdown_managed; + + priv->eni_ifaces = load_eni_ifaces(self); +} + +static void +reload_connections(NMSettingsPlugin * plugin, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSIfupdownPlugin * self = NMS_IFUPDOWN_PLUGIN(plugin); + NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *eni_ifaces_old = NULL; + GHashTableIter iter; + StorageData * sd; + StorageData * sd2; + const char * block_name; + + if (!priv->initialized) + initialize(self); + else if (!priv->already_reloaded) { + /* This is the first call to reload, but we are already initialized. + * + * This happens because during start NMSettings first queries unmanaged-specs, + * and then issues a reload call right away. + * + * On future reloads, we really want to load /e/n/i again. */ + priv->already_reloaded = TRUE; + } else { + eni_ifaces_old = priv->eni_ifaces; + priv->eni_ifaces = load_eni_ifaces(self); + + g_hash_table_iter_init(&iter, eni_ifaces_old); + while (g_hash_table_iter_next(&iter, (gpointer *) &block_name, (gpointer *) &sd)) { + if (!sd) + continue; + + sd2 = g_hash_table_lookup(priv->eni_ifaces, block_name); + if (!sd2) + continue; + + nm_assert(nm_streq(nm_settings_storage_get_uuid(sd->storage), + nm_settings_storage_get_uuid(sd2->storage))); + nm_g_object_ref_set(&sd2->storage, sd->storage); + g_hash_table_iter_remove(&iter); + } + } + + if (!priv->ifupdown_managed) + _LOGD("load: no connections due to managed=false"); + + g_hash_table_iter_init(&iter, priv->eni_ifaces); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &sd)) { + gs_unref_object NMConnection *connection = NULL; + + if (!sd) + continue; + + connection = g_steal_pointer(&sd->connection); + + if (!priv->ifupdown_managed) + continue; + + _LOGD("load: %s (%s)", + nm_settings_storage_get_uuid(sd->storage), + nm_connection_get_id(connection)); + callback(plugin, sd->storage, connection, user_data); + } + if (eni_ifaces_old && priv->ifupdown_managed) { + g_hash_table_iter_init(&iter, eni_ifaces_old); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &sd)) { + if (!sd) + continue; + _LOGD("unload: %s", nm_settings_storage_get_uuid(sd->storage)); + callback(plugin, sd->storage, NULL, user_data); + } + } +} + +/*****************************************************************************/ + +static GSList * +_unmanaged_specs(GHashTable *eni_ifaces) +{ + gs_free const char **keys = NULL; + GSList * specs = NULL; + guint i, len; + + keys = nm_utils_strdict_get_keys(eni_ifaces, TRUE, &len); + for (i = len; i > 0;) { + i--; + specs = g_slist_prepend(specs, + g_strdup_printf(NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", keys[i])); + } + return specs; +} + +static GSList * +get_unmanaged_specs(NMSettingsPlugin *plugin) +{ + NMSIfupdownPlugin * self = NMS_IFUPDOWN_PLUGIN(plugin); + NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->initialized)) + initialize(self); + + if (priv->ifupdown_managed) + return NULL; + + _LOGD("unmanaged-specs: unmanaged devices count %u", g_hash_table_size(priv->eni_ifaces)); + + return _unmanaged_specs(priv->eni_ifaces); +} + +/*****************************************************************************/ + +static GHashTable * +load_eni_ifaces(NMSIfupdownPlugin *self) +{ + gs_unref_hashtable GHashTable *eni_ifaces = NULL; + gs_unref_hashtable GHashTable *auto_ifaces = NULL; + nm_auto_ifparser if_parser *parser = NULL; + if_block * block; + StorageData * sd; + + eni_ifaces = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) _storage_data_destroy); + + parser = ifparser_parse(ENI_INTERFACES_FILE, 0); + + c_list_for_each_entry (block, &parser->block_lst_head, block_lst) { + if (NM_IN_STRSET(block->type, "auto", "allow-hotplug")) { + if (!auto_ifaces) + auto_ifaces = g_hash_table_new(nm_str_hash, g_str_equal); + g_hash_table_add(auto_ifaces, (char *) block->name); + } + } + + c_list_for_each_entry (block, &parser->block_lst_head, block_lst) { + if (NM_IN_STRSET(block->type, "auto", "allow-hotplug")) + continue; + + if (nm_streq(block->type, "iface")) { + gs_free_error GError *local = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMSettingsStorage *storage = NULL; + const char * uuid = NULL; + StorageData * sd_repl; + + /* Bridge configuration */ + if (g_str_has_prefix(block->name, "br")) { + /* Try to find bridge ports */ + const char *ports = ifparser_getkey(block, "bridge-ports"); + + if (ports) { + int state = 0; + gs_free const char **port_ifaces = NULL; + gsize i; + + _LOGD("parse: found bridge ports %s for %s", ports, block->name); + + port_ifaces = nm_utils_strsplit_set(ports, " \t"); + for (i = 0; port_ifaces && port_ifaces[i]; i++) { + const char *token = port_ifaces[i]; + + /* Skip crazy stuff like regex or all */ + if (nm_streq(token, "all")) + continue; + + /* Small SM to skip everything inside regex */ + if (nm_streq(token, "regex")) { + state++; + continue; + } + if (nm_streq(token, "noregex")) { + state--; + continue; + } + if (nm_streq(token, "none")) + continue; + if (state == 0) { + sd = g_hash_table_lookup(eni_ifaces, block->name); + if (!sd) { + _LOGD("parse: adding bridge port \"%s\"", token); + g_hash_table_insert(eni_ifaces, g_strdup(token), NULL); + } else { + _LOGD("parse: adding bridge port \"%s\" (have connection %s)", + token, + nm_settings_storage_get_uuid(sd->storage)); + } + } + } + } + continue; + } + + /* Skip loopback configuration */ + if (nm_streq(block->name, "lo")) + continue; + + sd_repl = g_hash_table_lookup(eni_ifaces, block->name); + if (sd_repl) { + _LOGD("parse: replace connection \"%s\" (%s)", + block->name, + nm_settings_storage_get_uuid(sd_repl->storage)); + storage = g_steal_pointer(&sd_repl->storage); + g_hash_table_remove(eni_ifaces, block->name); + } + + connection = ifupdown_new_connection_from_if_block( + block, + auto_ifaces && g_hash_table_contains(auto_ifaces, block->name), + &local); + + if (!connection) { + _LOGD("parse: adding place holder for \"%s\"%s%s%s", + block->name, + NM_PRINT_FMT_QUOTED(local, " (", local->message, ")", "")); + sd = NULL; + } else { + nmtst_connection_assert_unchanging(connection); + uuid = nm_connection_get_uuid(connection); + + if (!storage) + storage = nm_settings_storage_new(NM_SETTINGS_PLUGIN(self), uuid, NULL); + + sd = g_slice_new(StorageData); + *sd = (StorageData){ + .connection = g_steal_pointer(&connection), + .storage = g_steal_pointer(&storage), + }; + _LOGD("parse: adding connection \"%s\" (%s)", block->name, uuid); + } + + g_hash_table_replace(eni_ifaces, g_strdup(block->name), sd); + continue; + } + + if (nm_streq(block->type, "mapping")) { + sd = g_hash_table_lookup(eni_ifaces, block->name); + if (!sd) { + _LOGD("parse: adding mapping \"%s\"", block->name); + g_hash_table_insert(eni_ifaces, g_strdup(block->name), NULL); + } else { + _LOGD("parse: adding mapping \"%s\" (have connection %s)", + block->name, + nm_settings_storage_get_uuid(sd->storage)); + } + continue; + } + } + + nm_clear_pointer(&auto_ifaces, g_hash_table_destroy); + + return g_steal_pointer(&eni_ifaces); +} + +/*****************************************************************************/ + +static void +nms_ifupdown_plugin_init(NMSIfupdownPlugin *self) +{} + +static void +dispose(GObject *object) +{ + NMSIfupdownPlugin * plugin = NMS_IFUPDOWN_PLUGIN(object); + NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(plugin); + + nm_clear_pointer(&priv->eni_ifaces, g_hash_table_destroy); + + G_OBJECT_CLASS(nms_ifupdown_plugin_parent_class)->dispose(object); +} + +static void +nms_ifupdown_plugin_class_init(NMSIfupdownPluginClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass); + + object_class->dispose = dispose; + + plugin_class->plugin_name = "ifupdown"; + plugin_class->reload_connections = reload_connections; + plugin_class->get_unmanaged_specs = get_unmanaged_specs; +} + +/*****************************************************************************/ + +G_MODULE_EXPORT NMSettingsPlugin * + nm_settings_plugin_factory(void) +{ + return g_object_new(NMS_TYPE_IFUPDOWN_PLUGIN, NULL); +} diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h new file mode 100644 index 0000000..edf1b31 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Alexander Sack + * Copyright (C) 2008 Canonical Ltd. + */ + +#ifndef __NMS_IFUPDOWN_PLUGIN_H__ +#define __NMS_IFUPDOWN_PLUGIN_H__ + +#define PLUGIN_NAME "ifupdown" + +#define NMS_TYPE_IFUPDOWN_PLUGIN (nms_ifupdown_plugin_get_type()) +#define NMS_IFUPDOWN_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPlugin)) +#define NMS_IFUPDOWN_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass)) +#define NMS_IS_IFUPDOWN_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFUPDOWN_PLUGIN)) +#define NMS_IS_IFUPDOWN_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFUPDOWN_PLUGIN)) +#define NMS_IFUPDOWN_PLUGIN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass)) + +typedef struct _NMSIfupdownPlugin NMSIfupdownPlugin; +typedef struct _NMSIfupdownPluginClass NMSIfupdownPluginClass; + +GType nms_ifupdown_plugin_get_type(void); + +#endif /* __NMS_IFUPDOWN_PLUGIN_H__ */ diff --git a/src/core/settings/plugins/ifupdown/tests/meson.build b/src/core/settings/plugins/ifupdown/tests/meson.build new file mode 100644 index 0000000..882287c --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +exe = executable( + 'test-ifupdown', + 'test-ifupdown.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + link_with: libnms_ifupdown_core, +) + +test( + 'ifupdown/test-ifupdown', + test_script, + args: test_args + [exe.full_path()], +) diff --git a/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c new file mode 100644 index 0000000..c3c5bb5 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c @@ -0,0 +1,648 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-core-internal.h" + +#include "settings/plugins/ifupdown/nms-ifupdown-interface-parser.h" +#include "settings/plugins/ifupdown/nms-ifupdown-parser.h" + +#include "nm-test-utils-core.h" + +#define TEST_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifupdown/tests" + +/*****************************************************************************/ + +#define _connection_from_if_block(block) \ + ({ \ + NMConnection *_con; \ + if_block * _block = (block); \ + GError * _local = NULL; \ + \ + g_assert(_block); \ + _con = ifupdown_new_connection_from_if_block(_block, FALSE, &_local); \ + nmtst_assert_success(NM_IS_CONNECTION(_con), _local); \ + nmtst_assert_connection_verifies_without_normalization(_con); \ + _con; \ + }) + +#define _connection_first_from_parser(parser) \ + ({ \ + if_parser *_parser = (parser); \ + \ + g_assert(_parser); \ + _connection_from_if_block(ifparser_getfirst(_parser)); \ + }) + +/*****************************************************************************/ + +typedef struct { + char *key; + char *data; +} ExpectedKey; + +typedef struct { + char * type; + char * name; + GSList *keys; +} ExpectedBlock; + +typedef struct { + GSList *blocks; +} Expected; + +static ExpectedKey * +expected_key_new(const char *key, const char *data) +{ + ExpectedKey *k; + + k = g_malloc0(sizeof(ExpectedKey)); + k->key = g_strdup(key); + k->data = g_strdup(data); + return k; +} + +static void +expected_key_free(gpointer ptr) +{ + ExpectedKey *k = ptr; + + g_assert(k); + g_free(k->key); + g_free(k->data); + memset(k, 0, sizeof(ExpectedKey)); + g_free(k); +} + +static ExpectedBlock * +expected_block_new(const char *type, const char *name) +{ + ExpectedBlock *b; + + g_assert(type); + g_assert(name); + b = g_malloc0(sizeof(ExpectedBlock)); + g_assert(b); + b->type = g_strdup(type); + b->name = g_strdup(name); + return b; +} + +static void +expected_block_free(gpointer ptr) +{ + ExpectedBlock *b = ptr; + + g_assert(b); + g_slist_free_full(b->keys, expected_key_free); + g_free(b->type); + g_free(b->name); + memset(b, 0, sizeof(ExpectedBlock)); + g_free(b); +} + +static void +expected_block_add_key(ExpectedBlock *b, ExpectedKey *k) +{ + g_assert(b); + g_assert(k); + b->keys = g_slist_append(b->keys, k); +} + +static Expected * +expected_new(void) +{ + return g_malloc0(sizeof(Expected)); +} + +static void +expected_add_block(Expected *e, ExpectedBlock *b) +{ + g_assert(e); + g_assert(b); + e->blocks = g_slist_append(e->blocks, b); +} + +static void +expected_free(Expected *e) +{ + g_assert(e); + g_slist_free_full(e->blocks, expected_block_free); + memset(e, 0, sizeof(Expected)); + g_free(e); +} + +NM_AUTO_DEFINE_FCN_VOID0(Expected *, _nm_auto_free_expected, expected_free); +#define nm_auto_free_expected nm_auto(_nm_auto_free_expected) + +static void +compare_expected_to_ifparser(if_parser *parser, Expected *e) +{ + if_block *n; + GSList * biter, *kiter; + + g_assert_cmpint(g_slist_length(e->blocks), ==, ifparser_get_num_blocks(parser)); + + biter = e->blocks; + c_list_for_each_entry (n, &parser->block_lst_head, block_lst) { + if_data * m; + ExpectedBlock *b = biter->data; + + g_assert(b->type && n->type); + g_assert_cmpstr(b->type, ==, n->type); + g_assert(b->name); + g_assert_cmpstr(b->name, ==, n->name); + + g_assert_cmpint(g_slist_length(b->keys), ==, ifparser_get_num_info(n)); + + kiter = b->keys; + c_list_for_each_entry (m, &n->data_lst_head, data_lst) { + ExpectedKey *k = kiter->data; + + g_assert(k->key); + g_assert_cmpstr(k->key, ==, m->key); + g_assert(k->data && m->data); + g_assert_cmpstr(k->data, ==, m->data); + + kiter = g_slist_next(kiter); + } + g_assert(!kiter); + + biter = g_slist_next(biter); + } + g_assert(!biter); +} + +static void +dump_blocks(if_parser *parser) +{ + if_block *n; + + g_message("\n***************************************************"); + c_list_for_each_entry (n, &parser->block_lst_head, block_lst) { + if_data *m; + + // each block start with its type & name + // (single quotes used to show typ & name baoundaries) + g_print("'%s' '%s'\n", n->type, n->name); + + // each key-value pair within a block is indented & separated by a tab + // (single quotes used to show type & name boundaries) + c_list_for_each_entry (m, &n->data_lst_head, data_lst) + g_print("\t'%s'\t'%s'\n", m->key, m->data); + + // blocks are separated by an empty line + g_print("\n"); + } + g_message("##################################################\n"); +} + +static if_parser * +init_ifparser_with_file(const char *file) +{ + if_parser * parser; + gs_free char *tmp = NULL; + + tmp = g_strdup_printf("%s/%s", TEST_DIR, file); + parser = ifparser_parse(tmp, 1); + g_assert(parser); + return parser; +} + +static void +test1_ignore_line_before_first_block(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test1"); + + e = expected_new(); + b = expected_block_new("auto", "eth0"); + expected_add_block(e, b); + b = expected_block_new("iface", "eth0"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test2_wrapped_line(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test2"); + + e = expected_new(); + b = expected_block_new("auto", "lo"); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test3_wrapped_multiline_multiarg(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test3"); + + e = expected_new(); + b = expected_block_new("allow-hotplug", "eth0"); + expected_add_block(e, b); + b = expected_block_new("allow-hotplug", "wlan0"); + expected_add_block(e, b); + b = expected_block_new("allow-hotplug", "bnep0"); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test4_allow_auto_is_auto(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test4"); + + e = expected_new(); + b = expected_block_new("auto", "eth0"); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test5_allow_auto_multiarg(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test5"); + + e = expected_new(); + b = expected_block_new("allow-hotplug", "eth0"); + expected_add_block(e, b); + b = expected_block_new("allow-hotplug", "wlan0"); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test6_mixed_whitespace(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test6"); + + e = expected_new(); + b = expected_block_new("iface", "lo"); + expected_block_add_key(b, expected_key_new("inet", "loopback")); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test7_long_line(void) +{ + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test7"); + + g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0); +} + +static void +test8_long_line_wrapped(void) +{ + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test8"); + + g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0); +} + +static void +test9_wrapped_lines_in_block(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test9"); + + e = expected_new(); + b = expected_block_new("iface", "eth0"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "static")); + expected_block_add_key(b, expected_key_new("address", "10.250.2.3")); + expected_block_add_key(b, expected_key_new("netmask", "255.255.255.192")); + expected_block_add_key(b, expected_key_new("broadcast", "10.250.2.63")); + expected_block_add_key(b, expected_key_new("gateway", "10.250.2.50")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test11_complex_wrap(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test11"); + + e = expected_new(); + b = expected_block_new("iface", "pppoe"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "manual")); + expected_block_add_key(b, expected_key_new("pre-up", "/sbin/ifconfig eth0 up")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test12_complex_wrap_split_word(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test12"); + + e = expected_new(); + b = expected_block_new("iface", "pppoe"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "manual")); + expected_block_add_key(b, expected_key_new("up", "ifup ppp0=dsl")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test13_more_mixed_whitespace(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test13"); + + e = expected_new(); + b = expected_block_new("iface", "dsl"); + expected_block_add_key(b, expected_key_new("inet", "ppp")); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test14_mixed_whitespace_block_start(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test14"); + + e = expected_new(); + b = expected_block_new("iface", "wlan0"); + expected_block_add_key(b, expected_key_new("inet", "manual")); + expected_add_block(e, b); + b = expected_block_new("iface", "wlan-adpm"); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + expected_add_block(e, b); + b = expected_block_new("iface", "wlan-default"); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test15_trailing_space(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test15"); + + e = expected_new(); + b = expected_block_new("iface", "bnep0"); + expected_block_add_key(b, expected_key_new("inet", "static")); + expected_add_block(e, b); + + compare_expected_to_ifparser(parser, e); +} + +static void +test16_missing_newline(void) +{ + nm_auto_free_expected Expected *e = NULL; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test16"); + + e = expected_new(); + expected_add_block(e, expected_block_new("mapping", "eth0")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test17_read_static_ipv4(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingWired * s_wired; + NMIPAddress * ip4_addr; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test17-wired-static-verify-ip4"); + + connection = _connection_first_from_parser(parser); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (eth0)"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "10.0.0.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "10.0.0.2"); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 0), ==, "example.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "foo.example.com"); +} + +static void +test18_read_static_ipv6(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip6; + NMSettingWired * s_wired; + NMIPAddress * ip6_addr; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test18-wired-static-verify-ip6"); + + connection = _connection_first_from_parser(parser); + + /* ===== CONNECTION SETTING ===== */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (myip6tunnel)"); + + /* ===== WIRED SETTING ===== */ + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + /* ===== IPv6 SETTING ===== */ + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0); + g_assert(ip6_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "fc00::1"); + g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "fc00::2"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "fc00::3"); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "example.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "foo.example.com"); +} + +static void +test19_read_static_ipv4_plen(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingIPConfig * s_ip4; + NMIPAddress * ip4_addr; + nm_auto_ifparser if_parser *parser = + init_ifparser_with_file("test19-wired-static-verify-ip4-plen"); + + connection = _connection_first_from_parser(parser); + + /* ===== IPv4 SETTING ===== */ + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert(ip4_addr != NULL); + g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3"); + g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8); +} + +static void +test20_source_stanza(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test20-source-stanza"); + + e = expected_new(); + + b = expected_block_new("auto", "eth0"); + expected_add_block(e, b); + b = expected_block_new("iface", "eth0"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + + b = expected_block_new("auto", "eth1"); + expected_add_block(e, b); + b = expected_block_new("iface", "eth1"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test21_source_dir_stanza(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test21-source-dir-stanza"); + + e = expected_new(); + + b = expected_block_new("auto", "eth0"); + expected_add_block(e, b); + b = expected_block_new("iface", "eth0"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "dhcp")); + + compare_expected_to_ifparser(parser, e); +} + +static void +test22_duplicate_stanzas(void) +{ + nm_auto_free_expected Expected *e = NULL; + ExpectedBlock * b; + nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test22-duplicate-stanzas"); + + e = expected_new(); + + b = expected_block_new("iface", "br10"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "manual")); + expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15")); + expected_block_add_key(b, expected_key_new("bridge-stp", "off")); + expected_block_add_key(b, expected_key_new("bridge-maxwait", "0")); + expected_block_add_key(b, expected_key_new("bridge-fd", "0")); + b = expected_block_new("iface", "br10"); + expected_add_block(e, b); + expected_block_add_key(b, expected_key_new("inet", "auto")); + expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15")); + + compare_expected_to_ifparser(parser, e); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT"); + + (void) dump_blocks; + + g_test_add_func("/ifupdate/ignore_line_before_first_block", + test1_ignore_line_before_first_block); + g_test_add_func("/ifupdate/wrapped_line", test2_wrapped_line); + g_test_add_func("/ifupdate/wrapped_multiline_multiarg", test3_wrapped_multiline_multiarg); + g_test_add_func("/ifupdate/allow_auto_is_auto", test4_allow_auto_is_auto); + g_test_add_func("/ifupdate/allow_auto_multiarg", test5_allow_auto_multiarg); + g_test_add_func("/ifupdate/mixed_whitespace", test6_mixed_whitespace); + g_test_add_func("/ifupdate/long_line", test7_long_line); + g_test_add_func("/ifupdate/long_line_wrapped", test8_long_line_wrapped); + g_test_add_func("/ifupdate/wrapped_lines_in_block", test9_wrapped_lines_in_block); + g_test_add_func("/ifupdate/complex_wrap", test11_complex_wrap); + g_test_add_func("/ifupdate/complex_wrap_split_word", test12_complex_wrap_split_word); + g_test_add_func("/ifupdate/more_mixed_whitespace", test13_more_mixed_whitespace); + g_test_add_func("/ifupdate/mixed_whitespace_block_start", test14_mixed_whitespace_block_start); + g_test_add_func("/ifupdate/trailing_space", test15_trailing_space); + g_test_add_func("/ifupdate/missing_newline", test16_missing_newline); + g_test_add_func("/ifupdate/read_static_ipv4", test17_read_static_ipv4); + g_test_add_func("/ifupdate/read_static_ipv6", test18_read_static_ipv6); + g_test_add_func("/ifupdate/read_static_ipv4_plen", test19_read_static_ipv4_plen); + g_test_add_func("/ifupdate/source_stanza", test20_source_stanza); + g_test_add_func("/ifupdate/source_dir_stanza", test21_source_dir_stanza); + g_test_add_func("/ifupdate/test22-duplicate-stanzas", test22_duplicate_stanzas); + + return g_test_run(); +} diff --git a/src/core/settings/plugins/ifupdown/tests/test1 b/src/core/settings/plugins/ifupdown/tests/test1 new file mode 100644 index 0000000..74c23b4 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test1 @@ -0,0 +1,6 @@ +# case 1: line before 1st block (must be ignored) +address 10.250.2.3 + +auto eth0 +iface eth0 inet dhcp + diff --git a/src/core/settings/plugins/ifupdown/tests/test11 b/src/core/settings/plugins/ifupdown/tests/test11 new file mode 100644 index 0000000..89561dd --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test11 @@ -0,0 +1,5 @@ +iface pppoe inet manual +# case 11: wrapped line (without leading space on the wrapped part, wrap within a multi-word value) + pre-up /sbin/ifconfig \ +eth0 up + diff --git a/src/core/settings/plugins/ifupdown/tests/test12 b/src/core/settings/plugins/ifupdown/tests/test12 new file mode 100644 index 0000000..6096842 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test12 @@ -0,0 +1,5 @@ +iface pppoe inet manual +# case 12: wrapped line, splitting a word (must be joined again) + up ifup ppp0\ +=dsl + diff --git a/src/core/settings/plugins/ifupdown/tests/test13 b/src/core/settings/plugins/ifupdown/tests/test13 new file mode 100644 index 0000000..c001f7e --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test13 @@ -0,0 +1,3 @@ +# case 13: variations of tabs & spaces +iface dsl inet ppp + diff --git a/src/core/settings/plugins/ifupdown/tests/test14 b/src/core/settings/plugins/ifupdown/tests/test14 new file mode 100644 index 0000000..4a153ab --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test14 @@ -0,0 +1,5 @@ +# case 14: variations of tabs and spaces (all must be recognized as lines starting an iface block) +iface wlan0 inet manual + iface wlan-adpm inet dhcp +iface wlan-default inet dhcp + diff --git a/src/core/settings/plugins/ifupdown/tests/test15 b/src/core/settings/plugins/ifupdown/tests/test15 new file mode 100644 index 0000000..c3ceca2 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test15 @@ -0,0 +1,3 @@ +# case 15: trailing space (must be ignored) +iface bnep0 inet static + diff --git a/src/core/settings/plugins/ifupdown/tests/test16 b/src/core/settings/plugins/ifupdown/tests/test16 new file mode 100644 index 0000000..f4f74fb --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test16 @@ -0,0 +1,2 @@ +# case 16: last line that is not followed by LF (added with 'echo -n "mapping eth0" >> /e/n/i') +mapping eth0 \ No newline at end of file diff --git a/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 new file mode 100644 index 0000000..9e5243a --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 @@ -0,0 +1,5 @@ +iface eth0 inet static + address 10.0.0.3 + netmask 255.0.0.0 + dns-search example.com foo.example.com + dns-nameservers 10.0.0.1 10.0.0.2 diff --git a/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 new file mode 100644 index 0000000..29546a6 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 @@ -0,0 +1,6 @@ +iface myip6tunnel inet6 v4tunnel + address fc00::1 + netmask 64 + endpoint 78.35.24.124 + dns-nameservers fc00::2 fc00::3 + dns-search example.com foo.example.com diff --git a/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen new file mode 100644 index 0000000..441106d --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen @@ -0,0 +1,3 @@ +iface eth0 inet static + address 10.0.0.3 + netmask 8 diff --git a/src/core/settings/plugins/ifupdown/tests/test2 b/src/core/settings/plugins/ifupdown/tests/test2 new file mode 100644 index 0000000..7462b35 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test2 @@ -0,0 +1,4 @@ +# case 2: wrapped line +auto \ +lo + diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza new file mode 100644 index 0000000..5cfe173 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza @@ -0,0 +1 @@ +source test20-source-stanza.eth* diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 new file mode 100644 index 0000000..81922ce --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 @@ -0,0 +1,2 @@ +auto eth0 +iface eth0 inet dhcp diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 new file mode 100644 index 0000000..b8a783f --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 @@ -0,0 +1,2 @@ +auto eth1 +iface eth1 inet dhcp diff --git a/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza new file mode 100644 index 0000000..d0604dd --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza @@ -0,0 +1 @@ +source-directory test21-source-dir-stanza.d diff --git a/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 new file mode 100644 index 0000000..81922ce --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 @@ -0,0 +1,2 @@ +auto eth0 +iface eth0 inet dhcp diff --git a/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas new file mode 100644 index 0000000..c13c2e7 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas @@ -0,0 +1,8 @@ +iface br10 inet manual + bridge_ports enp6s0.15 + bridge_stp off + bridge_maxwait 0 + bridge_fd 0 + +iface br10 inet auto + bridge_ports enp6s0.15 diff --git a/src/core/settings/plugins/ifupdown/tests/test3 b/src/core/settings/plugins/ifupdown/tests/test3 new file mode 100644 index 0000000..f6293bb --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test3 @@ -0,0 +1,5 @@ +# case 3: line wrapped over multiple lines & multi-argument allow-* +allow-hotplug eth0 \ + wlan0 \ + bnep0 + diff --git a/src/core/settings/plugins/ifupdown/tests/test4 b/src/core/settings/plugins/ifupdown/tests/test4 new file mode 100644 index 0000000..46a40bc --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test4 @@ -0,0 +1,3 @@ +# case 4: 'allow-auto' is synonymous to 'auto' +allow-auto eth0 + diff --git a/src/core/settings/plugins/ifupdown/tests/test5 b/src/core/settings/plugins/ifupdown/tests/test5 new file mode 100644 index 0000000..b69fc42 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test5 @@ -0,0 +1,3 @@ +# case 5: multi-argument allow-* (even worse: trailing space) +allow-hotplug eth0 wlan0 + diff --git a/src/core/settings/plugins/ifupdown/tests/test6 b/src/core/settings/plugins/ifupdown/tests/test6 new file mode 100644 index 0000000..50ac69b --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test6 @@ -0,0 +1,3 @@ +# case 6: mix between tabs and spaces + iface lo inet loopback + diff --git a/src/core/settings/plugins/ifupdown/tests/test7 b/src/core/settings/plugins/ifupdown/tests/test7 new file mode 100644 index 0000000..03cb131 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test7 @@ -0,0 +1,3 @@ +# case 7: over-long line (must be ignored completely) +123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 + diff --git a/src/core/settings/plugins/ifupdown/tests/test8 b/src/core/settings/plugins/ifupdown/tests/test8 new file mode 100644 index 0000000..311f7e1 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test8 @@ -0,0 +1,5 @@ +# case 8: over-long line that wraps to consecutive lines (must be ignored completely) +123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 \ +allow-test eth0 \ +eth0 + diff --git a/src/core/settings/plugins/ifupdown/tests/test9 b/src/core/settings/plugins/ifupdown/tests/test9 new file mode 100644 index 0000000..7d94563 --- /dev/null +++ b/src/core/settings/plugins/ifupdown/tests/test9 @@ -0,0 +1,10 @@ +iface eth0 inet static +# case 9: wrapped lines inside a block (to be on the safe side) + address \ + 10.250.2.3 + netmask \ + 255.255.255.192 + + broadcast 10.250.2.63 + gateway 10.250.2.50 + diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c new file mode 100644 index 0000000..400c175 --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c @@ -0,0 +1,1348 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-plugin.h" + +#include +#include +#include +#include + +#include "nm-std-aux/c-list-util.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-io-utils.h" + +#include "nm-connection.h" +#include "nm-setting.h" +#include "nm-setting-connection.h" +#include "nm-utils.h" +#include "nm-config.h" +#include "nm-core-internal.h" +#include "nm-keyfile/nm-keyfile-internal.h" + +#include "systemd/nm-sd-utils-shared.h" + +#include "settings/nm-settings-plugin.h" +#include "settings/nm-settings-storage.h" +#include "settings/nm-settings-utils.h" + +#include "nms-keyfile-storage.h" +#include "nms-keyfile-writer.h" +#include "nms-keyfile-reader.h" +#include "nms-keyfile-utils.h" + +/*****************************************************************************/ + +typedef struct { + NMConfig *config; + + /* there can/could be multiple read-only directories. For example, one + * could set dirname_libs to + * - /usr/lib/NetworkManager/profiles/ + * - /etc/NetworkManager/system-connections + * and leave dirname_etc unset. In this case, there would be multiple + * read-only directories. + * + * Directories that come later have higher priority and shadow profiles + * from earlier directories. + * + * Currently, this is only an array with zero or one elements. It could be + * easily extended to support multiple read-only directories. + */ + char *dirname_libs[2]; + char *dirname_etc; + char *dirname_run; + + NMSettUtilStorages storages; + +} NMSKeyfilePluginPrivate; + +struct _NMSKeyfilePlugin { + NMSettingsPlugin parent; + NMSKeyfilePluginPrivate _priv; +}; + +struct _NMSKeyfilePluginClass { + NMSettingsPluginClass parent; +}; + +G_DEFINE_TYPE(NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN) + +#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "keyfile" +#define _NMLOG_DOMAIN LOGD_SETTINGS +#define _NMLOG(level, ...) \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static const char * +_extra_flags_to_string(char * str, + gsize str_len, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external) +{ + const char *str0 = str; + + if (!is_nm_generated && !is_volatile && !is_external) + nm_utils_strbuf_append_str(&str, &str_len, ""); + else { + char ch = '('; + + nm_utils_strbuf_append_c(&str, &str_len, ' '); + if (is_nm_generated) { + nm_utils_strbuf_append_c(&str, &str_len, ch); + nm_utils_strbuf_append_str(&str, &str_len, "nm-generated"); + ch = ','; + } + if (is_volatile) { + nm_utils_strbuf_append_c(&str, &str_len, ch); + nm_utils_strbuf_append_str(&str, &str_len, "volatile"); + ch = ','; + } + if (is_external) { + nm_utils_strbuf_append_c(&str, &str_len, ch); + nm_utils_strbuf_append_str(&str, &str_len, "external"); + ch = ','; + } + nm_utils_strbuf_append_c(&str, &str_len, ')'); + } + + return str0; +} + +static gboolean +_ignore_filename(NMSKeyfileStorageType storage_type, const char *filename) +{ + /* for backward-compatibility, we don't require an extension for + * files under "/etc/...". */ + return nm_keyfile_utils_ignore_filename(filename, + (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC)); +} + +static const char * +_get_plugin_dir(NMSKeyfilePluginPrivate *priv) +{ + /* the plugin dir is only needed to generate connection.uuid value via + * nm_keyfile_read_ensure_uuid(). This is either the configured /etc + * directory, of the compile-time default (in case the /etc directory + * is disabled). */ + return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT; +} + +static gboolean +_path_detect_storage_type(const char * full_filename, + const char *const * dirname_libs, + const char * dirname_etc, + const char * dirname_run, + NMSKeyfileStorageType *out_storage_type, + const char ** out_dirname, + const char ** out_filename, + gboolean * out_is_nmmeta_file, + gboolean * out_failed_due_to_invalid_filename) +{ + NMSKeyfileStorageType storage_type; + const char * filename = NULL; + const char * dirname = NULL; + guint i; + gboolean is_nmmeta_file = FALSE; + + NM_SET_OUT(out_failed_due_to_invalid_filename, FALSE); + + if (full_filename[0] != '/') + return FALSE; + + if (dirname_run && (filename = nm_utils_file_is_in_path(full_filename, dirname_run))) { + storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN; + dirname = dirname_run; + } else if (dirname_etc && (filename = nm_utils_file_is_in_path(full_filename, dirname_etc))) { + storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC; + dirname = dirname_etc; + } else { + for (i = 0; dirname_libs && dirname_libs[i]; i++) { + if ((filename = nm_utils_file_is_in_path(full_filename, dirname_libs[i]))) { + storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB(i); + dirname = dirname_libs[i]; + break; + } + } + if (!dirname) + return FALSE; + } + + if (_ignore_filename(storage_type, filename)) { + /* we accept nmmeta files, but only in /etc and /run directories. */ + + if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC) + || !nms_keyfile_nmmeta_check_filename(filename, NULL)) { + NM_SET_OUT(out_failed_due_to_invalid_filename, TRUE); + return FALSE; + } + + is_nmmeta_file = TRUE; + } + + NM_SET_OUT(out_storage_type, storage_type); + NM_SET_OUT(out_dirname, dirname); + NM_SET_OUT(out_filename, filename); + NM_SET_OUT(out_is_nmmeta_file, is_nmmeta_file); + return TRUE; +} + +/*****************************************************************************/ + +static NMConnection * +_read_from_file(const char * full_filename, + const char * plugin_dir, + struct stat *out_stat, + NMTernary * out_is_nm_generated, + NMTernary * out_is_volatile, + NMTernary * out_is_external, + char ** out_shadowed_storage, + NMTernary * out_shadowed_owned, + GError ** error) +{ + NMConnection *connection; + + nm_assert(full_filename && full_filename[0] == '/'); + + connection = nms_keyfile_reader_from_file(full_filename, + plugin_dir, + out_stat, + out_is_nm_generated, + out_is_volatile, + out_is_external, + out_shadowed_storage, + out_shadowed_owned, + error); + + nm_assert(!connection + || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS)); + nm_assert(!connection || nm_utils_is_uuid(nm_connection_get_uuid(connection))); + + return connection; +} + +/*****************************************************************************/ + +static void +_nm_assert_storage(gpointer plugin /* NMSKeyfilePlugin */, + gpointer storage /* NMSKeyfileStorage */, + gboolean tracked) +{ +#if NM_MORE_ASSERTS + NMSettUtilStorageByUuidHead *sbuh; + const char * uuid; + + nm_assert(!plugin || NMS_IS_KEYFILE_PLUGIN(plugin)); + nm_assert(NMS_IS_KEYFILE_STORAGE(storage)); + nm_assert(!plugin || plugin == nm_settings_storage_get_plugin(storage)); + + nm_assert(({ + const char *f = nms_keyfile_storage_get_filename(storage); + f && f[0] == '/'; + })); + + uuid = nms_keyfile_storage_get_uuid(storage); + + nm_assert(nm_utils_is_uuid(uuid)); + + nm_assert(((NMSKeyfileStorage *) storage)->is_meta_data + || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection) + || (NM_IS_CONNECTION((((NMSKeyfileStorage *) storage)->u.conn_data.connection)) + && nm_streq0(uuid, + nm_connection_get_uuid( + (((NMSKeyfileStorage *) storage)->u.conn_data.connection))))); + + nm_assert( + !tracked || !plugin + || c_list_contains(&NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages._storage_lst_head, + &NMS_KEYFILE_STORAGE(storage)->parent._storage_lst)); + + nm_assert(!tracked || !plugin + || storage + == g_hash_table_lookup( + NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_filename, + nms_keyfile_storage_get_filename(storage))); + + if (tracked && plugin) { + sbuh = g_hash_table_lookup(NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_uuid, + &uuid); + nm_assert(sbuh); + nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head, + &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst)); + } +#endif +} + +/*****************************************************************************/ + +static NMSKeyfileStorage * +_load_file(NMSKeyfilePlugin * self, + const char * dirname, + const char * filename, + NMSKeyfileStorageType storage_type, + GError ** error) +{ + NMSKeyfilePluginPrivate *priv; + gs_unref_object NMConnection *connection = NULL; + NMTernary is_nm_generated_opt; + NMTernary is_volatile_opt; + NMTernary is_external_opt; + NMTernary shadowed_owned_opt; + gs_free char * shadowed_storage = NULL; + gs_free_error GError *local = NULL; + gs_free char * full_filename = NULL; + struct stat st; + + if (_ignore_filename(storage_type, filename)) { + gs_free char *nmmeta = NULL; + gs_free char *loaded_path = NULL; + gs_free char *shadowed_storage_filename = NULL; + + if (!nms_keyfile_nmmeta_check_filename(filename, NULL)) { + if (error) + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip due to invalid filename"); + else + _LOGT("load: \"%s/%s\": skip file due to invalid filename", dirname, filename); + return NULL; + } + if (!nms_keyfile_nmmeta_read(dirname, + filename, + &full_filename, + &nmmeta, + &loaded_path, + &shadowed_storage_filename, + NULL)) { + if (error) + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file"); + else + _LOGT("load: \"%s/%s\": skip unreadable nmmeta file", dirname, filename); + return NULL; + } + nm_assert(loaded_path); + if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)) { + if (error) + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "skip nmmeta file from read-only directory"); + else + _LOGT("load: \"%s/%s\": skip nmmeta file from read-only directory", + dirname, + filename); + return NULL; + } + if (!nm_streq(loaded_path, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL)) { + if (error) + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "skip nmmeta file not symlinking %s", + NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL); + else + _LOGT("load: \"%s/%s\": skip nmmeta file not symlinking to %s", + dirname, + filename, + NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL); + return NULL; + } + + return nms_keyfile_storage_new_tombstone(self, + nmmeta, + full_filename, + storage_type, + shadowed_storage_filename); + } + + full_filename = g_build_filename(dirname, filename, NULL); + + priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + + connection = _read_from_file(full_filename, + _get_plugin_dir(priv), + &st, + &is_nm_generated_opt, + &is_volatile_opt, + &is_external_opt, + &shadowed_storage, + &shadowed_owned_opt, + &local); + if (!connection) { + if (error) + g_propagate_error(error, g_steal_pointer(&local)); + else + _LOGW("load: \"%s\": failed to load connection: %s", full_filename, local->message); + return NULL; + } + + return nms_keyfile_storage_new_connection(self, + g_steal_pointer(&connection), + full_filename, + storage_type, + is_nm_generated_opt, + is_volatile_opt, + is_external_opt, + shadowed_storage, + shadowed_owned_opt, + &st.st_mtim); +} + +static NMSKeyfileStorage * +_load_file_from_path(NMSKeyfilePlugin * self, + const char * full_filename, + NMSKeyfileStorageType storage_type, + GError ** error) +{ + gs_free char *f_dirname_free = NULL; + const char * f_filename; + const char * f_dirname; + + nm_assert(full_filename && full_filename[0] == '/'); + + f_filename = strrchr(full_filename, '/'); + f_dirname = nm_strndup_a(300, full_filename, f_filename - full_filename, &f_dirname_free); + f_filename++; + return _load_file(self, f_dirname, f_filename, storage_type, error); +} + +static void +_load_dir(NMSKeyfilePlugin * self, + NMSKeyfileStorageType storage_type, + const char * dirname, + NMSettUtilStorages * storages) +{ + const char * filename; + GDir * dir; + gs_unref_hashtable GHashTable *dupl_filenames = NULL; + + dir = g_dir_open(dirname, 0, NULL); + if (!dir) + return; + + dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); + + while ((filename = g_dir_read_name(dir))) { + gs_unref_object NMSKeyfileStorage *storage = NULL; + + filename = g_strdup(filename); + if (!g_hash_table_add(dupl_filenames, (char *) filename)) + continue; + + storage = _load_file(self, dirname, filename, storage_type, NULL); + if (!storage) + continue; + + nm_sett_util_storages_add_take(storages, g_steal_pointer(&storage)); + } + + g_dir_close(dir); + +#if NM_MORE_ASSERTS + { + NMSKeyfileStorage *storage; + + c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) + nm_assert(NMS_IS_KEYFILE_STORAGE(storage)); + } +#endif +} + +/*****************************************************************************/ + +static void +_storages_consolidate(NMSKeyfilePlugin * self, + NMSettUtilStorages * storages_new, + gboolean replace_all, + GHashTable * storages_replaced, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + CList lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted); + gs_unref_ptrarray GPtrArray *storages_modified = NULL; + CList storages_deleted; + NMSKeyfileStorage * storage_safe; + NMSKeyfileStorage * storage_new; + NMSKeyfileStorage * storage_old; + NMSKeyfileStorage * storage; + guint i; + + storages_modified = g_ptr_array_new_with_free_func(g_object_unref); + c_list_init(&storages_deleted); + + c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst) + storage_old->is_dirty = TRUE; + + c_list_for_each_entry_safe (storage_new, + storage_safe, + &storages_new->_storage_lst_head, + parent._storage_lst) { + storage_old = + nm_sett_util_storages_lookup_by_filename(&priv->storages, + nms_keyfile_storage_get_filename(storage_new)); + + nm_sett_util_storages_steal(storages_new, storage_new); + + if (!storage_old + || !nm_streq(nms_keyfile_storage_get_uuid(storage_new), + nms_keyfile_storage_get_uuid(storage_old))) { + if (storage_old) { + nm_sett_util_storages_steal(&priv->storages, storage_old); + c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst); + } + storage_new->is_dirty = FALSE; + nm_sett_util_storages_add_take(&priv->storages, storage_new); + g_ptr_array_add(storages_modified, g_object_ref(storage_new)); + continue; + } + + storage_old->is_dirty = FALSE; + nms_keyfile_storage_copy_content(storage_old, storage_new); + nms_keyfile_storage_destroy(storage_new); + g_ptr_array_add(storages_modified, g_object_ref(storage_old)); + } + + c_list_for_each_entry_safe (storage_old, + storage_safe, + &priv->storages._storage_lst_head, + parent._storage_lst) { + if (!storage_old->is_dirty) + continue; + if (replace_all + || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) { + nm_sett_util_storages_steal(&priv->storages, storage_old); + c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst); + } + } + + /* raise events. */ + + for (i = 0; i < storages_modified->len; i++) { + storage = storages_modified->pdata[i]; + storage->is_dirty = TRUE; + } + + for (i = 0; i < storages_modified->len; i++) { + gs_unref_object NMConnection *connection = NULL; + + storage = storages_modified->pdata[i]; + + if (!storage->is_dirty) { + /* the entry is no longer is_dirty. In the meantime we already emitted + * another signal for it. */ + continue; + } + storage->is_dirty = FALSE; + + if (c_list_is_empty(&storage->parent._storage_lst)) { + /* hm? The profile was deleted in the meantime? That is only possible + * if the signal handler called again into the plugin. In any case, the event + * was already emitted. Skip. */ + continue; + } + + nm_assert( + storage + == nm_sett_util_storages_lookup_by_filename(&priv->storages, + nms_keyfile_storage_get_filename(storage))); + + connection = nms_keyfile_storage_steal_connection(storage); + + callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data); + } + + while ((storage = c_list_first_entry(&storages_deleted, + NMSKeyfileStorage, + parent._storage_by_uuid_lst))) { + c_list_unlink(&storage->parent._storage_by_uuid_lst); + callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data); + nms_keyfile_storage_destroy(storage); + } +} + +static void +reload_connections(NMSettingsPlugin * plugin, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSKeyfilePlugin * self = NMS_KEYFILE_PLUGIN(plugin); + NMSKeyfilePluginPrivate * priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = + NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy); + int i; + + _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, &storages_new); + if (priv->dirname_etc) + _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, &storages_new); + for (i = 0; priv->dirname_libs[i]; i++) + _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_LIB(i), priv->dirname_libs[i], &storages_new); + + _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data); +} + +static void +load_connections(NMSettingsPlugin * plugin, + NMSettingsPluginConnectionLoadEntry * entries, + gsize n_entries, + NMSettingsPluginConnectionLoadCallback callback, + gpointer user_data) +{ + NMSKeyfilePlugin * self = NMS_KEYFILE_PLUGIN(plugin); + NMSKeyfilePluginPrivate * priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = + NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy); + gs_unref_hashtable GHashTable *dupl_filenames = NULL; + gs_unref_hashtable GHashTable *storages_replaced = NULL; + gs_unref_hashtable GHashTable *loaded_uuids = NULL; + const char * loaded_uuid; + GHashTableIter h_iter; + gsize i; + + if (n_entries == 0) + return; + + dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + + loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal); + + storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); + + for (i = 0; i < n_entries; i++) { + NMSettingsPluginConnectionLoadEntry *const entry = &entries[i]; + NMSKeyfileStorageType storage_type; + gs_free_error GError * local = NULL; + const char * f_filename; + const char * f_dirname; + const char * full_filename; + gs_free char * full_filename_keep = NULL; + gboolean is_nmmeta_file; + NMSettingsPluginConnectionLoadEntry *dupl_content_entry; + gboolean failed_due_to_invalid_filename; + gs_unref_object NMSKeyfileStorage *storage = NULL; + + if (entry->handled) + continue; + + if (!_path_detect_storage_type(entry->filename, + (const char *const *) priv->dirname_libs, + priv->dirname_etc, + priv->dirname_run, + &storage_type, + &f_dirname, + &f_filename, + &is_nmmeta_file, + &failed_due_to_invalid_filename)) { + if (failed_due_to_invalid_filename) { + entry->handled = TRUE; + nm_utils_error_set(&entry->error, + NM_UTILS_ERROR_UNKNOWN, + "filename is not valid for a keyfile"); + } + continue; + } + + full_filename_keep = g_build_filename(f_dirname, f_filename, NULL); + + if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) { + /* we already visited this file. */ + entry->handled = dupl_content_entry->handled; + if (dupl_content_entry->error) { + g_set_error_literal(&entry->error, + dupl_content_entry->error->domain, + dupl_content_entry->error->code, + dupl_content_entry->error->message); + } + continue; + } + + entry->handled = TRUE; + + full_filename = full_filename_keep; + if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry)) + nm_assert_not_reached(); + + storage = _load_file(self, f_dirname, f_filename, storage_type, &local); + if (!storage) { + if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) { + NMSKeyfileStorage *storage2; + + /* the file does not exist. We take that as indication to unload the file + * that was previously loaded... */ + storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename); + if (storage2) + g_hash_table_add(storages_replaced, g_object_ref(storage2)); + continue; + } + g_propagate_error(&entry->error, g_steal_pointer(&local)); + continue; + } + + g_hash_table_add(loaded_uuids, (char *) nms_keyfile_storage_get_uuid(storage)); + + nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage)); + } + + /* now we visit all UUIDs that are about to change... */ + g_hash_table_iter_init(&h_iter, loaded_uuids); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) { + NMSKeyfileStorage * storage; + NMSettUtilStorageByUuidHead *sbuh; + + sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid); + if (!sbuh) + continue; + + c_list_for_each_entry (storage, + &sbuh->_storage_by_uuid_lst_head, + parent._storage_by_uuid_lst) { + const char * full_filename = nms_keyfile_storage_get_filename(storage); + gs_unref_object NMSKeyfileStorage *storage_new = NULL; + gs_free_error GError *local = NULL; + + if (g_hash_table_contains(dupl_filenames, full_filename)) { + /* already re-loaded. */ + continue; + } + + /* @storage has a UUID that was just loaded from disk, but we have an entry in cache. + * Reload that file too despite not being told to do so. The reason is to get + * the latest file timestamp so that we get the priorities right. */ + + storage_new = _load_file_from_path(self, full_filename, storage->storage_type, &local); + if (storage_new && !nm_streq(loaded_uuid, nms_keyfile_storage_get_uuid(storage_new))) { + /* the file now references a different UUID. We are not told to reload + * that file, so this means the existing storage (with the previous + * filename and UUID tuple) is no longer valid. */ + g_clear_object(&storage_new); + } + + g_hash_table_add(storages_replaced, g_object_ref(storage)); + if (storage_new) + nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new)); + } + } + + nm_clear_pointer(&loaded_uuids, g_hash_table_destroy); + nm_clear_pointer(&dupl_filenames, g_hash_table_destroy); + + _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data); +} + +gboolean +nms_keyfile_plugin_add_connection(NMSKeyfilePlugin * self, + NMConnection * connection, + gboolean in_memory, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + gs_unref_object NMConnection *reread = NULL; + gs_free char * full_filename = NULL; + NMSKeyfileStorageType storage_type; + gs_unref_object NMSKeyfileStorage *storage = NULL; + GError * local = NULL; + const char * uuid; + gboolean reread_same; + struct timespec mtime; + char strbuf[100]; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(out_storage && !*out_storage); + nm_assert(out_connection && !*out_connection); + + nm_assert(in_memory + || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage + && !shadowed_owned)); + + uuid = nm_connection_get_uuid(connection); + + /* Note that even if the caller requests persistent storage, we may switch to in-memory, if + * no /etc directory is configured. */ + storage_type = !in_memory && priv->dirname_etc ? NMS_KEYFILE_STORAGE_TYPE_ETC + : NMS_KEYFILE_STORAGE_TYPE_RUN; + + if (!nms_keyfile_writer_connection( + connection, + is_nm_generated, + is_volatile, + is_external, + shadowed_storage, + shadowed_owned, + storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc : priv->dirname_run, + _get_plugin_dir(priv), + NULL, + FALSE, + FALSE, + nm_sett_util_allow_filename_cb, + NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL), + &full_filename, + &reread, + &reread_same, + &local)) { + _LOGT("commit: %s (%s) failed to add: %s", + nm_connection_get_uuid(connection), + nm_connection_get_id(connection), + local->message); + g_propagate_error(error, local); + return FALSE; + } + + if (!reread || reread_same) + nm_g_object_ref_set(&reread, connection); + + nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert(nm_streq0(nm_connection_get_uuid(connection), nm_connection_get_uuid(reread))); + + nm_assert(full_filename && full_filename[0] == '/'); + nm_assert(!nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename)); + + _LOGT("commit: %s (%s) added as \"%s\"%s%s%s%s", + uuid, + nm_connection_get_id(connection), + full_filename, + _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external), + NM_PRINT_FMT_QUOTED(shadowed_storage, + " (shadows \"", + shadowed_storage, + shadowed_owned ? "\", owned)" : "\")", + "")); + + storage = + nms_keyfile_storage_new_connection(self, + g_steal_pointer(&reread), + full_filename, + storage_type, + is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, + is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, + is_external ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, + shadowed_storage, + shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, + nm_sett_util_stat_mtime(full_filename, FALSE, &mtime)); + + nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage)); + + *out_connection = nms_keyfile_storage_steal_connection(storage); + *out_storage = NM_SETTINGS_STORAGE(g_steal_pointer(&storage)); + + return TRUE; +} + +static gboolean +add_connection(NMSettingsPlugin * plugin, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + return nms_keyfile_plugin_add_connection(NMS_KEYFILE_PLUGIN(plugin), + connection, + FALSE, + FALSE, + FALSE, + FALSE, + NULL, + FALSE, + out_storage, + out_connection, + error); +} + +gboolean +nms_keyfile_plugin_update_connection(NMSKeyfilePlugin * self, + NMSettingsStorage * storage_x, + NMConnection * connection, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + gboolean force_rename, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + NMSKeyfileStorage * storage = NMS_KEYFILE_STORAGE(storage_x); + gs_unref_object NMConnection *connection_clone = NULL; + gs_unref_object NMConnection *reread = NULL; + gs_free char * full_filename = NULL; + gs_free_error GError *local = NULL; + struct timespec mtime; + const char * previous_filename; + gboolean reread_same; + const char * uuid; + char strbuf[100]; + + _nm_assert_storage(self, storage, TRUE); + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert(nm_streq(nms_keyfile_storage_get_uuid(storage), nm_connection_get_uuid(connection))); + nm_assert(!error || !*error); + nm_assert(NM_IN_SET(storage->storage_type, + NMS_KEYFILE_STORAGE_TYPE_ETC, + NMS_KEYFILE_STORAGE_TYPE_RUN)); + nm_assert(!storage->is_meta_data); + nm_assert(storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN + || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage + && !shadowed_owned)); + nm_assert(!shadowed_owned || shadowed_storage); + nm_assert(priv->dirname_etc || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC); + + previous_filename = nms_keyfile_storage_get_filename(storage); + uuid = nms_keyfile_storage_get_uuid(storage); + + if (!nms_keyfile_writer_connection( + connection, + is_nm_generated, + is_volatile, + is_external, + shadowed_storage, + shadowed_owned, + storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc + : priv->dirname_run, + _get_plugin_dir(priv), + previous_filename, + FALSE, + FALSE, + nm_sett_util_allow_filename_cb, + NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename), + &full_filename, + &reread, + &reread_same, + &local)) { + _LOGW("commit: failure to write %s (%s) to \"%s\": %s", + uuid, + nm_connection_get_id(connection_clone), + previous_filename, + local->message); + g_propagate_error(error, g_steal_pointer(&local)); + return FALSE; + } + + nm_assert(full_filename && nm_streq(full_filename, previous_filename)); + + if (!reread || reread_same) + nm_g_object_ref_set(&reread, connection); + + nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert(nm_streq(nm_connection_get_uuid(reread), uuid)); + + _LOGT("commit: \"%s\": profile %s (%s) written%s%s%s%s", + full_filename, + uuid, + nm_connection_get_id(connection), + _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external), + NM_PRINT_FMT_QUOTED(shadowed_storage, + shadowed_owned ? " (owns \"" : " (shadows \"", + shadowed_storage, + "\")", + "")); + + storage->u.conn_data.is_nm_generated = is_nm_generated; + storage->u.conn_data.is_volatile = is_volatile; + storage->u.conn_data.is_external = is_external; + storage->u.conn_data.stat_mtime = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime); + storage->u.conn_data.shadowed_owned = shadowed_owned; + + *out_storage = g_object_ref(NM_SETTINGS_STORAGE(storage)); + *out_connection = g_steal_pointer(&reread); + return TRUE; +} + +static gboolean +update_connection(NMSettingsPlugin * plugin, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error) +{ + return nms_keyfile_plugin_update_connection(NMS_KEYFILE_PLUGIN(plugin), + storage, + connection, + FALSE, + FALSE, + FALSE, + NULL, + FALSE, + FALSE, + out_storage, + out_connection, + error); +} + +static gboolean +delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error) +{ + NMSKeyfilePlugin * self = NMS_KEYFILE_PLUGIN(plugin); + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + gs_unref_object NMSKeyfileStorage *storage = g_object_ref(NMS_KEYFILE_STORAGE(storage_x)); + const char * remove_from_disk_errmsg = NULL; + const char * operation_message; + const char * previous_filename; + const char * uuid; + gboolean success = TRUE; + + _nm_assert_storage(self, storage, TRUE); + nm_assert(!error || !*error); + + previous_filename = nms_keyfile_storage_get_filename(storage); + uuid = nms_keyfile_storage_get_uuid(storage); + + if (!NM_IN_SET(storage->storage_type, + NMS_KEYFILE_STORAGE_TYPE_ETC, + NMS_KEYFILE_STORAGE_TYPE_RUN)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "profile in read-only storage cannot be deleted"); + success = FALSE; + operation_message = "dropped readonly file from memory"; + } else if (unlink(previous_filename) != 0) { + int errsv; + + errsv = errno; + if (errsv != ENOENT) { + remove_from_disk_errmsg = nm_strerror_native(errsv); + operation_message = "failed to delete from disk"; + success = FALSE; + nm_utils_error_set_errno(error, + errsv, + "failure to delete \"%s\": %s", + previous_filename); + } else + operation_message = "does not exist on disk"; + } else + operation_message = "deleted from disk"; + + _LOGT("commit: deleted \"%s\", %s %s (%s%s%s%s)", + previous_filename, + storage->is_meta_data ? "meta-data" : "profile", + uuid, + operation_message, + NM_PRINT_FMT_QUOTED(remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", "")); + + if (success) { + nm_sett_util_storages_steal(&priv->storages, storage); + nms_keyfile_storage_destroy(storage); + } + + return success; +} + +/** + * nms_keyfile_plugin_set_nmmeta_tombstone: + * @self: the #NMSKeyfilePlugin instance + * @simulate: if %TRUE, don't do anything on the filename but just pretend + * that the loaded UUID file gets tracked/untracked. In this mode, the function + * cannot fail (except on hard-failure, see below). + * The idea is that you first try without simulate to write to disk. + * If that fails, you might still want to forcefully pretend (in-memory + * only) that this uuid is marked as tombstone (or not), as desired. + * So you repeate the call with @simulate %TRUE. + * @uuid: the UUID for which to write/delete the nmmeta file + * @in_memory: the storage type, either /etc or /run. Note that if @self + * has no /etc directory configured, this results in a hard failure. + * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE, + * delete the nmmeta file (if it exists). + * @shadowed_storage: a tombstone can also shadow an existing storage. + * In combination with @set and @in_memory, this is allowed to store + * the shadowed storage filename. + * @out_storage: (transfer full) (allow-none): the storage element that changes, or + * NULL if nothing changed. Note that the file on disk is already as + * we want to write it, then this still counts as a change. No change only + * means if we try to delete a storage (@set %FALSE) that did not + * exist previously. + * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure. + * + * The function writes or deletes nmmeta files to/from filesystem. In this case, + * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones). + * + * A hard failure can only happen if @self has no /etc directory configured + * and @in_memory is FALSE. In such case even @simulate call fails (which + * otherwise would always succeed). + * Also, if you get a hard-failure (with @simulate %FALSE) there is no point + * in retrying with @simulate %TRUE (contrary to all other cases!). + * + * Returns: %TRUE on success. + */ +gboolean +nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin * self, + gboolean simulate, + const char * uuid, + gboolean in_memory, + gboolean set, + const char * shadowed_storage, + NMSettingsStorage **out_storage, + gboolean * out_hard_failure) +{ + NMSKeyfilePluginPrivate *priv; + gboolean hard_failure = FALSE; + NMSKeyfileStorage * storage; + gs_unref_object NMSKeyfileStorage *storage_result = NULL; + gboolean nmmeta_errno; + gs_free char * nmmeta_filename = NULL; + NMSKeyfileStorageType storage_type; + const char * loaded_path; + const char * dirname; + + nm_assert(NMS_IS_KEYFILE_PLUGIN(self)); + nm_assert(nm_utils_is_uuid(uuid)); + nm_assert(!out_storage || !*out_storage); + nm_assert(!shadowed_storage || (set && in_memory)); + + priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + + loaded_path = set ? NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL : NULL; + + if (in_memory) { + storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN; + dirname = priv->dirname_run; + } else { + if (!priv->dirname_etc) { + _LOGT("commit: cannot %s%s nmmeta file for %s as there is no /etc directory", + simulate ? "simulate " : "", + loaded_path ? "write" : "delete", + uuid); + nmmeta_errno = 0; + hard_failure = TRUE; + goto out; + } + storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC; + dirname = priv->dirname_etc; + } + + if (simulate) { + nmmeta_errno = 0; + nmmeta_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE); + } else { + nmmeta_errno = nms_keyfile_nmmeta_write(dirname, + uuid, + loaded_path, + FALSE, + shadowed_storage, + &nmmeta_filename); + } + + _LOGT("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s%s%s%s", + loaded_path ? "writing" : "deleting", + nmmeta_filename, + NM_PRINT_FMT_QUOTED(loaded_path, " (pointing to \"", loaded_path, "\")", ""), + NM_PRINT_FMT_QUOTED(shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""), + simulate ? "simulated" : (nmmeta_errno < 0 ? "failed" : "succeeded"), + NM_PRINT_FMT_QUOTED(nmmeta_errno < 0, + " (", + nm_strerror_native(nm_errno_native(nmmeta_errno)), + ")", + "")); + + if (nmmeta_errno < 0) + goto out; + + storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, nmmeta_filename); + + nm_assert(!storage + || (storage->is_meta_data && storage->storage_type == storage_type + && nm_streq(nms_keyfile_storage_get_uuid(storage), uuid))); + + if (loaded_path) { + if (!storage) { + storage = nms_keyfile_storage_new_tombstone(self, + uuid, + nmmeta_filename, + storage_type, + shadowed_storage); + nm_sett_util_storages_add_take(&priv->storages, storage); + } else { + g_free(storage->u.meta_data.shadowed_storage); + storage->u.meta_data.shadowed_storage = g_strdup(shadowed_storage); + } + + storage_result = g_object_ref(storage); + } else { + if (storage) + storage_result = nm_sett_util_storages_steal(&priv->storages, storage); + } + +out: + nm_assert(nmmeta_errno <= 0); + nm_assert(nmmeta_errno < 0 || !hard_failure); + nm_assert(nmmeta_errno == 0 || !storage_result); + + NM_SET_OUT(out_hard_failure, hard_failure); + NM_SET_OUT(out_storage, (NMSettingsStorage *) g_steal_pointer(&storage_result)); + return nmmeta_errno >= 0; +} + +/*****************************************************************************/ + +static void +config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMSKeyfilePlugin * self) +{ + gs_free char *old_value = NULL; + gs_free char *new_value = NULL; + + old_value = nm_config_data_get_value(old_data, + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, + NM_CONFIG_GET_VALUE_TYPE_SPEC); + new_value = nm_config_data_get_value(config_data, + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, + NM_CONFIG_GET_VALUE_TYPE_SPEC); + + if (!nm_streq0(old_value, new_value)) + _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self)); +} + +static GSList * +get_unmanaged_specs(NMSettingsPlugin *config) +{ + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(config); + gs_free char * value = NULL; + + value = nm_config_data_get_value(nm_config_get_data(priv->config), + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, + NM_CONFIG_GET_VALUE_TYPE_SPEC); + return nm_match_spec_split(value); +} + +/*****************************************************************************/ + +static void +nms_keyfile_plugin_init(NMSKeyfilePlugin *plugin) +{ + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin); + + priv->config = g_object_ref(nm_config_get()); + + priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages, + nms_keyfile_storage_destroy); + + /* dirname_libs are a set of read-only directories with lower priority than /etc or /run. + * There is nothing complicated about having multiple of such directories, so dirname_libs + * is a list (which currently only has at most one directory). */ + priv->dirname_libs[0] = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_LIB), FALSE); + priv->dirname_libs[1] = NULL; + priv->dirname_run = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_RUN), FALSE); + priv->dirname_etc = nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH, + NM_CONFIG_GET_VALUE_STRIP); + if (priv->dirname_etc && priv->dirname_etc[0] == '\0') { + /* special case: configure an empty keyfile path so that NM has no writable keyfile + * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning + * it cannot persist profile to non-volatile memory. */ + nm_clear_g_free(&priv->dirname_etc); + } else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') { + /* either invalid path or unspecified. Use the default. */ + g_free(priv->dirname_etc); + priv->dirname_etc = + nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE); + } else + nm_sd_utils_path_simplify(priv->dirname_etc, FALSE); + + /* no duplicates */ + if (NM_IN_STRSET(priv->dirname_libs[0], priv->dirname_etc, priv->dirname_run)) + nm_clear_g_free(&priv->dirname_libs[0]); + if (NM_IN_STRSET(priv->dirname_etc, priv->dirname_run)) + nm_clear_g_free(&priv->dirname_etc); + + nm_assert(!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/'); + nm_assert(!priv->dirname_etc || priv->dirname_etc[0] == '/'); + nm_assert(priv->dirname_run && priv->dirname_run[0] == '/'); +} + +static void +constructed(GObject *object) +{ + NMSKeyfilePlugin * self = NMS_KEYFILE_PLUGIN(object); + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + + G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->constructed(object); + + if (nm_config_data_has_value(nm_config_get_data_orig(priv->config), + NM_CONFIG_KEYFILE_GROUP_KEYFILE, + NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME, + NM_CONFIG_GET_VALUE_RAW)) + _LOGW("'hostname' option is deprecated and has no effect"); + + if (nm_config_data_has_value(nm_config_get_data_orig(priv->config), + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES, + NM_CONFIG_GET_VALUE_RAW)) + _LOGW("'monitor-connection-files' option is deprecated and has no effect"); + + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed_cb), + self); +} + +NMSKeyfilePlugin * +nms_keyfile_plugin_new(void) +{ + return g_object_new(NMS_TYPE_KEYFILE_PLUGIN, NULL); +} + +static void +dispose(GObject *object) +{ + NMSKeyfilePlugin * self = NMS_KEYFILE_PLUGIN(object); + NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self); + + if (priv->config) + g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, object); + + nm_sett_util_storages_clear(&priv->storages); + + nm_clear_g_free(&priv->dirname_libs[0]); + nm_clear_g_free(&priv->dirname_etc); + nm_clear_g_free(&priv->dirname_run); + + g_clear_object(&priv->config); + + G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->dispose(object); +} + +static void +nms_keyfile_plugin_class_init(NMSKeyfilePluginClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + plugin_class->plugin_name = "keyfile"; + plugin_class->get_unmanaged_specs = get_unmanaged_specs; + plugin_class->reload_connections = reload_connections; + plugin_class->load_connections = load_connections; + plugin_class->add_connection = add_connection; + plugin_class->update_connection = update_connection; + plugin_class->delete_connection = delete_connection; +} diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h new file mode 100644 index 0000000..5d9f2da --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#ifndef __NMS_KEYFILE_PLUGIN_H__ +#define __NMS_KEYFILE_PLUGIN_H__ + +#include "settings/nm-settings-plugin.h" +#include "settings/nm-settings-storage.h" + +#include "nms-keyfile-utils.h" + +#define NMS_TYPE_KEYFILE_PLUGIN (nms_keyfile_plugin_get_type()) +#define NMS_KEYFILE_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePlugin)) +#define NMS_KEYFILE_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass)) +#define NMS_IS_KEYFILE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_PLUGIN)) +#define NMS_IS_KEYFILE_PLUGIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_PLUGIN)) +#define NMS_KEYFILE_PLUGIN_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass)) + +typedef struct _NMSKeyfilePlugin NMSKeyfilePlugin; +typedef struct _NMSKeyfilePluginClass NMSKeyfilePluginClass; + +GType nms_keyfile_plugin_get_type(void); + +NMSKeyfilePlugin *nms_keyfile_plugin_new(void); + +gboolean nms_keyfile_plugin_add_connection(NMSKeyfilePlugin * self, + NMConnection * connection, + gboolean in_memory, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + +gboolean nms_keyfile_plugin_update_connection(NMSKeyfilePlugin * self, + NMSettingsStorage * storage, + NMConnection * connection, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + gboolean force_rename, + NMSettingsStorage **out_storage, + NMConnection ** out_connection, + GError ** error); + +gboolean nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin * self, + gboolean simulate, + const char * uuid, + gboolean in_memory, + gboolean set, + const char * shadowed_storage, + NMSettingsStorage **out_storage, + gboolean * out_hard_failure); + +#endif /* __NMS_KEYFILE_PLUGIN_H__ */ diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.c b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c new file mode 100644 index 0000000..6978c10 --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-reader.h" + +#include + +#include "nm-keyfile/nm-keyfile-internal.h" + +#include "NetworkManagerUtils.h" +#include "nms-keyfile-utils.h" + +/*****************************************************************************/ + +static const char * +_fmt_warn(const NMKeyfileHandlerData *handler_data, char **out_message) +{ + const char *group = handler_data->kf_group_name; + const char *message = _nm_keyfile_handler_data_warn_get_message(handler_data); + + if (group) { + NMSetting * setting = handler_data->cur_setting; + const char *property_name = handler_data->cur_property; + const char *setting_name = setting ? nm_setting_get_name(setting) : NULL; + char * res; + + if (setting_name) { + if (property_name && !strcmp(group, setting_name)) + res = g_strdup_printf("%s.%s: %s", group, property_name, message); + else if (property_name) + res = g_strdup_printf("%s/%s.%s: %s", group, setting_name, property_name, message); + else if (!strcmp(group, setting_name)) + res = g_strdup_printf("%s: %s", group, message); + else + res = g_strdup_printf("%s/%s: %s", group, setting_name, message); + } else + res = g_strdup_printf("%s: %s", group, message); + *out_message = res; + return res; + } + + return message; +} + +typedef struct { + bool verbose; +} ReadInfo; + +static gboolean +_handler_read(GKeyFile * keyfile, + NMConnection * connection, + NMKeyfileHandlerType handler_type, + NMKeyfileHandlerData *handler_data, + void * user_data) +{ + const ReadInfo *read_info = user_data; + + if (handler_type == NM_KEYFILE_HANDLER_TYPE_WARN) { + const NMKeyfileHandlerDataWarn *warn_data = &handler_data->warn; + NMLogLevel level; + char * message_free = NULL; + + if (!read_info->verbose) + return TRUE; + + if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN) + level = LOGL_ERR; + else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN) + level = LOGL_WARN; + else if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE) + level = LOGL_WARN; + else + level = LOGL_INFO; + + nm_log(level, + LOGD_SETTINGS, + NULL, + nm_connection_get_uuid(connection), + "keyfile: %s", + _fmt_warn(handler_data, &message_free)); + g_free(message_free); + return TRUE; + } + + return FALSE; +} + +NMConnection * +nms_keyfile_reader_from_keyfile(GKeyFile * key_file, + const char *filename, + const char *base_dir, + const char *profile_dir, + gboolean verbose, + GError ** error) +{ + NMConnection *connection; + ReadInfo read_info = { + .verbose = verbose, + }; + gs_free char *base_dir_free = NULL; + gs_free char *profile_filename_free = NULL; + gs_free char *filename_id = NULL; + const char * profile_filename = NULL; + + nm_assert(filename && filename[0]); + nm_assert(!base_dir || base_dir[0] == '/'); + nm_assert(!profile_dir || profile_dir[0] == '/'); + + if (base_dir) + nm_assert(!strchr(filename, '/')); + else { + const char *s; + + nm_assert(filename[0] == '/'); + + /* @base_dir may be NULL, in which case @filename must be an absolute path, + * and the directory is taken as the @base_dir. */ + s = strrchr(filename, '/'); + base_dir = nm_strndup_a(255, filename, s - filename, &base_dir_free); + if (!profile_dir || nm_streq(base_dir, profile_dir)) + profile_filename = filename; + filename = &s[1]; + } + + connection = nm_keyfile_read(key_file, + base_dir, + NM_KEYFILE_HANDLER_FLAGS_NONE, + _handler_read, + &read_info, + error); + if (!connection) + return NULL; + + if (g_str_has_suffix(filename, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) { + gsize l = strlen(filename); + + if (l > NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) + filename_id = g_strndup(filename, l - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)); + } + + nm_keyfile_read_ensure_id(connection, filename_id ?: filename); + + if (!profile_filename) { + profile_filename_free = g_build_filename(profile_dir ?: base_dir, filename, NULL); + profile_filename = profile_filename_free; + } + nm_keyfile_read_ensure_uuid(connection, profile_filename); + + return connection; +} + +NMConnection * +nms_keyfile_reader_from_file(const char * full_filename, + const char * profile_dir, + struct stat *out_stat, + NMTernary * out_is_nm_generated, + NMTernary * out_is_volatile, + NMTernary * out_is_external, + char ** out_shadowed_storage, + NMTernary * out_shadowed_owned, + GError ** error) +{ + nm_auto_unref_keyfile GKeyFile *key_file = NULL; + NMConnection * connection = NULL; + GError * verify_error = NULL; + + nm_assert(full_filename && full_filename[0] == '/'); + nm_assert(!profile_dir || profile_dir[0] == '/'); + + NM_SET_OUT(out_is_nm_generated, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_is_volatile, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_is_external, NM_TERNARY_DEFAULT); + NM_SET_OUT(out_shadowed_owned, NM_TERNARY_DEFAULT); + + if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_KEYFILE, + full_filename, + out_stat, + error)) + return NULL; + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, full_filename, G_KEY_FILE_NONE, error)) + return NULL; + + connection = + nms_keyfile_reader_from_keyfile(key_file, full_filename, NULL, profile_dir, TRUE, error); + if (!connection) + return NULL; + + /* Normalize and verify the connection */ + if (!nm_connection_normalize(connection, NULL, NULL, &verify_error)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid connection: %s", + verify_error->message); + g_clear_error(&verify_error); + g_object_unref(connection); + connection = NULL; + } + + NM_SET_OUT(out_is_nm_generated, + nm_key_file_get_boolean(key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_NM_GENERATED, + NM_TERNARY_DEFAULT)); + + NM_SET_OUT(out_is_volatile, + nm_key_file_get_boolean(key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_VOLATILE, + NM_TERNARY_DEFAULT)); + + NM_SET_OUT(out_is_external, + nm_key_file_get_boolean(key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_EXTERNAL, + NM_TERNARY_DEFAULT)); + + NM_SET_OUT(out_shadowed_storage, + g_key_file_get_string(key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE, + NULL)); + + NM_SET_OUT(out_shadowed_owned, + nm_key_file_get_boolean(key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED, + NM_TERNARY_DEFAULT)); + + return connection; +} diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.h b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h new file mode 100644 index 0000000..247457d --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef __NMS_KEYFILE_READER_H__ +#define __NMS_KEYFILE_READER_H__ + +#include "nm-connection.h" + +NMConnection *nms_keyfile_reader_from_keyfile(GKeyFile * key_file, + const char *filename, + const char *base_dir, + const char *profile_dir, + gboolean verbose, + GError ** error); + +struct stat; + +NMConnection *nms_keyfile_reader_from_file(const char * full_filename, + const char * profile_dir, + struct stat *out_stat, + NMTernary * out_is_nm_generated, + NMTernary * out_is_volatile, + NMTernary * out_is_external, + char ** out_shadowed_storage, + NMTernary * out_shadowed_owned, + GError ** error); + +#endif /* __NMS_KEYFILE_READER_H__ */ diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c new file mode 100644 index 0000000..8fb9861 --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-storage.h" + +#include "nm-utils.h" +#include "nm-core-internal.h" +#include "nms-keyfile-plugin.h" + +/*****************************************************************************/ + +struct _NMSKeyfileStorageClass { + NMSettingsStorageClass parent; +}; + +G_DEFINE_TYPE(NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE) + +/*****************************************************************************/ + +void +nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src) +{ + nm_assert(src != dst); + nm_assert(nm_streq(nms_keyfile_storage_get_uuid(dst), nms_keyfile_storage_get_uuid(src))); + nm_assert( + nms_keyfile_storage_get_filename(dst) + && nm_streq(nms_keyfile_storage_get_filename(dst), nms_keyfile_storage_get_filename(src))); + nm_assert(dst->storage_type == src->storage_type); + nm_assert(dst->is_meta_data == src->is_meta_data); + + if (dst->is_meta_data) { + gs_free char *shadowed_storage_to_free = NULL; + + shadowed_storage_to_free = g_steal_pointer(&dst->u.meta_data.shadowed_storage); + dst->u.meta_data = src->u.meta_data; + dst->u.meta_data.shadowed_storage = g_strdup(dst->u.meta_data.shadowed_storage); + } else { + gs_unref_object NMConnection *connection_to_free = NULL; + gs_free char * shadowed_storage_to_free = NULL; + + connection_to_free = g_steal_pointer(&dst->u.conn_data.connection); + shadowed_storage_to_free = g_steal_pointer(&dst->u.conn_data.shadowed_storage); + dst->u.conn_data = src->u.conn_data; + nm_g_object_ref(dst->u.conn_data.connection); + dst->u.conn_data.shadowed_storage = g_strdup(dst->u.conn_data.shadowed_storage); + } +} + +NMConnection * +nms_keyfile_storage_steal_connection(NMSKeyfileStorage *self) +{ + nm_assert(NMS_IS_KEYFILE_STORAGE(self)); + nm_assert(self->is_meta_data || NM_IS_CONNECTION(self->u.conn_data.connection)); + + return self->is_meta_data ? NULL : g_steal_pointer(&self->u.conn_data.connection); +} + +/*****************************************************************************/ + +static int +cmp_fcn(const NMSKeyfileStorage *a, const NMSKeyfileStorage *b) +{ + nm_assert(NMS_IS_KEYFILE_STORAGE(a)); + nm_assert(NMS_IS_KEYFILE_STORAGE(b)); + nm_assert(a != b); + + /* sort by storage-type, which also has a numeric value according to their + * (inverse) priority. */ + NM_CMP_FIELD_UNSAFE(b, a, storage_type); + + /* meta-data is more important. */ + NM_CMP_FIELD_UNSAFE(a, b, is_meta_data); + + if (a->is_meta_data) { + nm_assert( + nm_streq(nms_keyfile_storage_get_filename(a), nms_keyfile_storage_get_filename(b))); + NM_CMP_FIELD_UNSAFE(a, b, u.meta_data.is_tombstone); + } else { + /* newer files are more important. */ + NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_sec); + NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_nsec); + + NM_CMP_DIRECT_STRCMP(nms_keyfile_storage_get_filename(a), + nms_keyfile_storage_get_filename(b)); + } + + return 0; +} + +/*****************************************************************************/ + +static void +nms_keyfile_storage_init(NMSKeyfileStorage *self) +{} + +static NMSKeyfileStorage * +_storage_new(NMSKeyfilePlugin * plugin, + const char * uuid, + const char * filename, + gboolean is_meta_data, + NMSKeyfileStorageType storage_type) + +{ + NMSKeyfileStorage *self; + + nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin)); + nm_assert(nm_utils_is_uuid(uuid)); + nm_assert(filename && filename[0] == '/'); + + self = g_object_new(NMS_TYPE_KEYFILE_STORAGE, + NM_SETTINGS_STORAGE_PLUGIN, + plugin, + NM_SETTINGS_STORAGE_UUID, + uuid, + NM_SETTINGS_STORAGE_FILENAME, + filename, + NULL); + + *((bool *) &self->is_meta_data) = is_meta_data; + *((NMSKeyfileStorageType *) &self->storage_type) = storage_type; + + return self; +} + +NMSKeyfileStorage * +nms_keyfile_storage_new_tombstone(NMSKeyfilePlugin * plugin, + const char * uuid, + const char * filename, + NMSKeyfileStorageType storage_type, + const char * shadowed_storage) +{ + NMSKeyfileStorage *self; + + nm_assert(nm_utils_is_uuid(uuid)); + nm_assert(filename && filename[0] == '/'); + nm_assert(nms_keyfile_nmmeta_check_filename(filename, NULL)); + nm_assert(NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC, NMS_KEYFILE_STORAGE_TYPE_RUN)); + + self = _storage_new(plugin, uuid, filename, TRUE, storage_type); + self->u.meta_data.is_tombstone = TRUE; + if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) + self->u.meta_data.shadowed_storage = g_strdup(shadowed_storage); + return self; +} + +NMSKeyfileStorage * +nms_keyfile_storage_new_connection(NMSKeyfilePlugin * plugin, + NMConnection * connection_take /* pass reference */, + const char * filename, + NMSKeyfileStorageType storage_type, + NMTernary is_nm_generated_opt, + NMTernary is_volatile_opt, + NMTernary is_external_opt, + const char * shadowed_storage, + NMTernary shadowed_owned_opt, + const struct timespec *stat_mtime) +{ + NMSKeyfileStorage *self; + + nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin)); + nm_assert(NM_IS_CONNECTION(connection_take)); + nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert(filename && filename[0] == '/'); + nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN + && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST); + nmtst_connection_assert_unchanging(connection_take); + + self = _storage_new(plugin, + nm_connection_get_uuid(connection_take), + filename, + FALSE, + storage_type); + + self->u.conn_data.connection = connection_take; /* take reference. */ + + self->u.conn_data.shadowed_storage = g_strdup(shadowed_storage); + + if (stat_mtime) + self->u.conn_data.stat_mtime = *stat_mtime; + + if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) { + self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE); + self->u.conn_data.is_volatile = (is_volatile_opt == NM_TERNARY_TRUE); + self->u.conn_data.is_external = (is_external_opt == NM_TERNARY_TRUE); + self->u.conn_data.shadowed_owned = + shadowed_storage && (shadowed_owned_opt == NM_TERNARY_TRUE); + } + + return self; +} + +static void +_storage_clear(NMSKeyfileStorage *self) +{ + c_list_unlink(&self->parent._storage_lst); + c_list_unlink(&self->parent._storage_by_uuid_lst); + if (self->is_meta_data) + nm_clear_g_free(&self->u.meta_data.shadowed_storage); + else { + g_clear_object(&self->u.conn_data.connection); + nm_clear_g_free(&self->u.conn_data.shadowed_storage); + self->u.conn_data.shadowed_owned = FALSE; + } +} + +static void +dispose(GObject *object) +{ + NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE(object); + + _storage_clear(self); + + G_OBJECT_CLASS(nms_keyfile_storage_parent_class)->dispose(object); +} + +void +nms_keyfile_storage_destroy(NMSKeyfileStorage *self) +{ + _storage_clear(self); + g_object_unref(self); +} + +static void +nms_keyfile_storage_class_init(NMSKeyfileStorageClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS(klass); + + object_class->dispose = dispose; + + storage_class->cmp_fcn = (int (*)(NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn; +} + +/*****************************************************************************/ + +#include "settings/nm-settings-connection.h" + +void +nm_settings_storage_load_sett_flags(NMSettingsStorage * self, + NMSettingsConnectionIntFlags *sett_flags, + NMSettingsConnectionIntFlags *sett_mask) +{ + NMSKeyfileStorage *s; + + *sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE; + *sett_mask = NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL; + + if (!NMS_IS_KEYFILE_STORAGE(self)) + return; + + s = NMS_KEYFILE_STORAGE(self); + + if (s->is_meta_data) + return; + if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN) + return; + + if (s->u.conn_data.is_nm_generated) + *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED; + + if (s->u.conn_data.is_volatile) + *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE; + + if (s->u.conn_data.is_external) + *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL; +} diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.h b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h new file mode 100644 index 0000000..008070d --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NMS_KEYFILE_STORAGE_H__ +#define __NMS_KEYFILE_STORAGE_H__ + +#include "c-list/src/c-list.h" +#include "settings/nm-settings-storage.h" +#include "nms-keyfile-utils.h" + +/*****************************************************************************/ + +#define NMS_TYPE_KEYFILE_STORAGE (nms_keyfile_storage_get_type()) +#define NMS_KEYFILE_STORAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage)) +#define NMS_KEYFILE_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass)) +#define NMS_IS_KEYFILE_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_STORAGE)) +#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_STORAGE)) +#define NMS_KEYFILE_STORAGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass)) + +typedef struct { + /* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */ + char *shadowed_storage; + bool is_tombstone : 1; +} NMSettingsMetaData; + +typedef struct { + NMSettingsStorage parent; + + /* The connection. Note that there are tombstones (loaded-uuid files to /dev/null) + * that don't have a connection. + * + * Also, we don't actually remember the loaded connection after returning it + * to NMSettings. So, also for regular storages (non-tombstones) this field + * is often cleared. */ + union { + struct { + NMConnection *connection; + + /* when we move a profile from permanent storage to unsaved (/run), then + * we may leave the profile on disk (depending on options for Update2()). + * + * Later, when we save the profile again to disk, we want to re-use that filename. + * Likewise, we delete the (now in-memory) profile, we may want to also delete + * the original filename. + * + * This is the original filename, and we store it inside [.nmmeta] in the + * keyfile in /run. Note that we don't store this in the .nmmeta file, because + * the information is tied to the particular keyfile in /run, not to all UUIDs + * in general. */ + char *shadowed_storage; + + /* the timestamp (stat's mtime) of the keyfile. For meta-data this + * is irrelevant. The purpose is that if the same storage type (directory) has + * multiple files with the same UUID, then the newer file gets preferred. */ + struct timespec stat_mtime; + + /* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN + * (and non-metadata). This is to persist and reload these settings flags to + * /run. + * + * Note that these flags are not stored in as meta-data. The reason is that meta-data + * is per UUID. But these flags are only relevant for a particular keyfile on disk. + * That is, it must be tied to the actual keyfile, and not to the UUID. */ + bool is_nm_generated : 1; + bool is_volatile : 1; + bool is_external : 1; + + /* if shadowed_storage is set, then this flag indicates whether the file + * is owned. The difference comes into play when deleting the in-memory, + * shadowing profile: a owned profile will also be deleted. */ + bool shadowed_owned : 1; + + } conn_data; + + /* the content from the .nmmeta file. Note that the nmmeta file has the UUID + * in the filename, that means there can be only two variants of this file: + * in /etc and in /run. As such, this is really meta-data about the entire profile + * (the UUID), and not about the individual keyfile. */ + NMSettingsMetaData meta_data; + + } u; + + /* The storage type. This is directly related to the filename. Since + * the filename cannot change, this value is unchanging. */ + const NMSKeyfileStorageType storage_type; + + /* whether union "u" has meta_data or conn_data. Since the type of the storage + * depends on the (immutable) filename, this is also const. */ + const bool is_meta_data; + + /* this flag is only used during reload to mark and prune old entries. */ + bool is_dirty : 1; + +} NMSKeyfileStorage; + +typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass; + +GType nms_keyfile_storage_get_type(void); + +struct _NMSKeyfilePlugin; + +NMSKeyfileStorage *nms_keyfile_storage_new_tombstone(struct _NMSKeyfilePlugin *self, + const char * uuid, + const char * filename, + NMSKeyfileStorageType storage_type, + const char * shadowed_storage); + +NMSKeyfileStorage * +nms_keyfile_storage_new_connection(struct _NMSKeyfilePlugin *self, + NMConnection * connection_take /* pass reference */, + const char * filename, + NMSKeyfileStorageType storage_type, + NMTernary is_nm_generated_opt, + NMTernary is_volatile_opt, + NMTernary is_external_opt, + const char * shadowed_storage, + NMTernary shadowed_owned_opt, + const struct timespec * stat_mtime); + +void nms_keyfile_storage_destroy(NMSKeyfileStorage *storage); + +/*****************************************************************************/ + +void nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src); + +NMConnection *nms_keyfile_storage_steal_connection(NMSKeyfileStorage *storage); + +/*****************************************************************************/ + +static inline const char * +nms_keyfile_storage_get_uuid(const NMSKeyfileStorage *self) +{ + return nm_settings_storage_get_uuid((const NMSettingsStorage *) self); +} + +static inline const char * +nms_keyfile_storage_get_filename(const NMSKeyfileStorage *self) +{ + return nm_settings_storage_get_filename((const NMSettingsStorage *) self); +} + +/*****************************************************************************/ + +static inline gboolean +nm_settings_storage_is_keyfile_run(const NMSettingsStorage *self) +{ + return NMS_IS_KEYFILE_STORAGE(self) + && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN); +} + +static inline gboolean +nm_settings_storage_is_keyfile_lib(const NMSettingsStorage *self) +{ + return NMS_IS_KEYFILE_STORAGE(self) + && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE); +} + +static inline const NMSettingsMetaData * +nm_settings_storage_is_meta_data(const NMSettingsStorage *storage) +{ + const NMSKeyfileStorage *self; + + if (!NMS_IS_KEYFILE_STORAGE(storage)) + return NULL; + + self = (NMSKeyfileStorage *) storage; + + if (!self->is_meta_data) + return NULL; + + return &self->u.meta_data; +} + +static inline const NMSettingsMetaData * +nm_settings_storage_is_meta_data_alive(const NMSettingsStorage *storage) +{ + const NMSettingsMetaData *meta_data; + + meta_data = nm_settings_storage_is_meta_data(storage); + + if (!meta_data) + return NULL; + + /* Regular (all other) storages are alive as long as they report a NMConnection, and + * they will be dropped, once they have no more connection. + * + * Meta-data storages are special: they never report a NMConnection. + * So, a meta-data storage is alive as long as it is tracked by the + * settings plugin. + * + * This function is used to ckeck for that. */ + + if (c_list_is_empty(&storage->_storage_lst)) + return NULL; + + return meta_data; +} + +static inline const char * +nm_settings_storage_get_shadowed_storage(const NMSettingsStorage *storage, + gboolean * out_shadowed_owned) +{ + if (NMS_IS_KEYFILE_STORAGE(storage)) { + const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage; + + if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) { + if (!self->is_meta_data) { + if (self->u.conn_data.shadowed_storage) { + NM_SET_OUT(out_shadowed_owned, self->u.conn_data.shadowed_owned); + return self->u.conn_data.shadowed_storage; + } + } else { + NM_SET_OUT(out_shadowed_owned, FALSE); + return self->u.meta_data.shadowed_storage; + } + } + } + + NM_SET_OUT(out_shadowed_owned, FALSE); + return NULL; +} + +static inline const char * +nm_settings_storage_get_filename_for_shadowed_storage(const NMSettingsStorage *storage) +{ + g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), NULL); + + if (!storage->_filename) + return NULL; + + if (NMS_IS_KEYFILE_STORAGE(storage)) { + const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage; + + if (self->is_meta_data || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC) + return NULL; + } + + return storage->_filename; +} + +/*****************************************************************************/ + +enum _NMSettingsConnectionIntFlags; + +void nm_settings_storage_load_sett_flags(NMSettingsStorage * self, + enum _NMSettingsConnectionIntFlags *sett_flags, + enum _NMSettingsConnectionIntFlags *sett_mask); + +#endif /* __NMS_KEYFILE_STORAGE_H__ */ diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c new file mode 100644 index 0000000..a62e0ae --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-utils.h" + +#include +#include + +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-keyfile/nm-keyfile-internal.h" +#include "nm-utils.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-config.h" + +/*****************************************************************************/ + +#define NMMETA_KF_GROUP_NAME_NMMETA "nmmeta" +#define NMMETA_KF_KEY_NAME_NMMETA_UUID "uuid" +#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH "loaded-path" +#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE "shadowed-storage" + +/*****************************************************************************/ + +const char * +nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len) +{ + const char *uuid; + const char *s; + gsize len; + + s = strrchr(filename, '/'); + if (s) + filename = &s[1]; + + len = strlen(filename); + if (len <= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA) + || memcmp(&filename[len - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)], + NM_KEYFILE_PATH_SUFFIX_NMMETA, + NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)) + != 0) { + /* the filename does not have the right suffix. */ + return NULL; + } + + len -= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA); + + if (!NM_IN_SET(len, 36, 40)) { + /* the remaining part of the filename has not the right length to + * contain a UUID (according to nm_utils_is_uuid()). */ + return NULL; + } + + uuid = nm_strndup_a(100, filename, len, NULL); + if (!nm_utils_is_uuid(uuid)) + return NULL; + + NM_SET_OUT(out_uuid_len, len); + return filename; +} + +char * +nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary) +{ + char filename[250]; + char *s; + + nm_assert(dirname && dirname[0] == '/'); + nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/')); + + if (g_snprintf(filename, + sizeof(filename), + "%s%s%s", + uuid, + NM_KEYFILE_PATH_SUFFIX_NMMETA, + temporary ? "~" : "") + >= sizeof(filename)) { + /* valid uuids are limited in length (nm_utils_is_uuid). The buffer should always + * be large enough. */ + nm_assert_not_reached(); + } + + s = g_build_filename(dirname, filename, NULL); + + nm_assert(nm_keyfile_utils_ignore_filename(s, FALSE)); + + return s; +} + +gboolean +nms_keyfile_nmmeta_read(const char * dirname, + const char * filename, + char ** out_full_filename, + char ** out_uuid, + char ** out_loaded_path, + char ** out_shadowed_storage, + struct stat *out_st) +{ + const char * uuid; + guint uuid_len; + gs_free char *full_filename = NULL; + gs_free char *loaded_path = NULL; + gs_free char *shadowed_storage = NULL; + struct stat st_stack; + struct stat * st = out_st ?: &st_stack; + + nm_assert(dirname && dirname[0] == '/'); + nm_assert(filename && filename[0] && !strchr(filename, '/')); + + uuid = nms_keyfile_nmmeta_check_filename(filename, &uuid_len); + if (!uuid) + return FALSE; + + full_filename = g_build_filename(dirname, filename, NULL); + + if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_NMMETA, + full_filename, + st, + NULL)) + return FALSE; + + if (S_ISREG(st->st_mode)) { + nm_auto_unref_keyfile GKeyFile *kf = NULL; + gs_free char * v_uuid = NULL; + + kf = g_key_file_new(); + + if (!g_key_file_load_from_file(kf, full_filename, G_KEY_FILE_NONE, NULL)) + return FALSE; + + v_uuid = g_key_file_get_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_UUID, + NULL); + if (!nm_streq0(v_uuid, uuid)) + return FALSE; + + loaded_path = g_key_file_get_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, + NULL); + shadowed_storage = g_key_file_get_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, + NULL); + + if (!loaded_path && !shadowed_storage) { + /* if there is no useful information in the file, it is the same as if + * the file is not present. Signal failure. */ + return FALSE; + } + + } else { + loaded_path = nm_utils_read_link_absolute(full_filename, NULL); + if (!loaded_path) + return FALSE; + } + + NM_SET_OUT(out_uuid, g_strndup(uuid, uuid_len)); + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename)); + NM_SET_OUT(out_loaded_path, g_steal_pointer(&loaded_path)); + NM_SET_OUT(out_shadowed_storage, g_steal_pointer(&shadowed_storage)); + return TRUE; +} + +gboolean +nms_keyfile_nmmeta_read_from_file(const char *full_filename, + char ** out_dirname, + char ** out_filename, + char ** out_uuid, + char ** out_loaded_path, + char ** out_shadowed_storage) +{ + gs_free char *dirname = NULL; + gs_free char *filename = NULL; + + nm_assert(full_filename && full_filename[0] == '/'); + + filename = g_path_get_basename(full_filename); + dirname = g_path_get_dirname(full_filename); + + if (!nms_keyfile_nmmeta_read(dirname, + filename, + NULL, + out_uuid, + out_loaded_path, + out_shadowed_storage, + NULL)) + return FALSE; + + NM_SET_OUT(out_dirname, g_steal_pointer(&dirname)); + NM_SET_OUT(out_filename, g_steal_pointer(&filename)); + return TRUE; +} + +int +nms_keyfile_nmmeta_write(const char *dirname, + const char *uuid, + const char *loaded_path, + gboolean loaded_path_allow_relative, + const char *shadowed_storage, + char ** out_full_filename) +{ + gs_free char *full_filename_tmp = NULL; + gs_free char *full_filename = NULL; + int errsv; + + nm_assert(dirname && dirname[0] == '/'); + nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/')); + nm_assert(!loaded_path || loaded_path[0] == '/'); + nm_assert(!shadowed_storage || loaded_path); + + full_filename_tmp = nms_keyfile_nmmeta_filename(dirname, uuid, TRUE); + + nm_assert(g_str_has_suffix(full_filename_tmp, "~")); + nm_assert(nm_utils_file_is_in_path(full_filename_tmp, dirname)); + + (void) unlink(full_filename_tmp); + + if (!loaded_path) { + full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0'; + errsv = 0; + if (unlink(full_filename_tmp) != 0) { + errsv = -NM_ERRNO_NATIVE(errno); + if (errsv == -ENOENT) + errsv = 0; + } + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp)); + return errsv; + } + + if (loaded_path_allow_relative) { + const char *f; + + f = nm_utils_file_is_in_path(loaded_path, dirname); + if (f) { + /* @loaded_path points to a file directly in @dirname. + * Don't use absolute paths. */ + loaded_path = f; + } + } + + full_filename = g_strndup(full_filename_tmp, strlen(full_filename_tmp) - 1); + + if (shadowed_storage) { + nm_auto_unref_keyfile GKeyFile *kf = NULL; + gs_free char * contents = NULL; + gsize length; + + kf = g_key_file_new(); + + g_key_file_set_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_UUID, + uuid); + g_key_file_set_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, + loaded_path); + g_key_file_set_string(kf, + NMMETA_KF_GROUP_NAME_NMMETA, + NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, + shadowed_storage); + + contents = g_key_file_to_data(kf, &length, NULL); + + if (!nm_utils_file_set_contents(full_filename, contents, length, 0600, &errsv, NULL)) { + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp)); + return -NM_ERRNO_NATIVE(errsv); + } + } else { + /* we only have the "loaded_path" to store. That is commonly used for the tombstones to + * link to /dev/null. A symlink is sufficient to store that amount of information. + * No need to bother with a keyfile. */ + if (symlink(loaded_path, full_filename_tmp) != 0) { + errsv = -NM_ERRNO_NATIVE(errno); + full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0'; + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp)); + return errsv; + } + + if (rename(full_filename_tmp, full_filename) != 0) { + errsv = -NM_ERRNO_NATIVE(errno); + (void) unlink(full_filename_tmp); + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename)); + return errsv; + } + } + + NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename)); + return 0; +} + +/*****************************************************************************/ + +gboolean +nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype, + const struct stat *st, + GError ** error) +{ + g_return_val_if_fail(st, FALSE); + + if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) { + if (!S_ISREG(st->st_mode)) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "file is not a regular file"); + return FALSE; + } + } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) { + if (!S_ISLNK(st->st_mode) && !S_ISREG(st->st_mode)) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "file is neither a symlink nor a regular file"); + return FALSE; + } + } else + g_return_val_if_reached(FALSE); + + if (!NM_FLAGS_HAS(nm_utils_get_testing(), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) { + if (st->st_uid != 0) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "File owner (%lld) is insecure", + (long long) st->st_uid); + return FALSE; + } + + if (S_ISREG(st->st_mode) && (st->st_mode & 0077)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "File permissions (%03o) are insecure", + st->st_mode); + return FALSE; + } + } + + return TRUE; +} + +gboolean +nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype, + const char * filename, + struct stat * out_st, + GError ** error) +{ + struct stat st; + int errsv; + + g_return_val_if_fail(filename && filename[0] == '/', FALSE); + + if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) { + if (stat(filename, &st) != 0) { + errsv = errno; + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "cannot access file: %s", + nm_strerror_native(errsv)); + return FALSE; + } + } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) { + if (lstat(filename, &st) != 0) { + errsv = errno; + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "cannot access file: %s", + nm_strerror_native(errsv)); + return FALSE; + } + } else + g_return_val_if_reached(FALSE); + + if (!nms_keyfile_utils_check_file_permissions_stat(filetype, &st, error)) + return FALSE; + + NM_SET_OUT(out_st, st); + return TRUE; +} diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.h b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h new file mode 100644 index 0000000..419a17e --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2018 Red Hat, Inc. + */ + +#ifndef __NMS_KEYFILE_UTILS_H__ +#define __NMS_KEYFILE_UTILS_H__ + +#include "NetworkManagerUtils.h" + +typedef enum { + NMS_KEYFILE_FILETYPE_KEYFILE, + NMS_KEYFILE_FILETYPE_NMMETA, +} NMSKeyfileFiletype; + +typedef enum { + NMS_KEYFILE_STORAGE_TYPE_RUN = 1, /* read-write, runtime only, e.g. /run */ + NMS_KEYFILE_STORAGE_TYPE_ETC = 2, /* read-write, persistent, e.g. /etc */ + NMS_KEYFILE_STORAGE_TYPE_LIB_BASE = 3, /* read-only, e.g. /usr/lib */ + + _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST = 1000, +} NMSKeyfileStorageType; + +static inline NMSKeyfileStorageType +NMS_KEYFILE_STORAGE_TYPE_LIB(guint run_idx) +{ + nm_assert(run_idx <= (_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST - NMS_KEYFILE_STORAGE_TYPE_LIB_BASE)); + return NMS_KEYFILE_STORAGE_TYPE_LIB_BASE + run_idx; +} + +/*****************************************************************************/ + +const char *nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len); + +char *nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary); + +gboolean nms_keyfile_nmmeta_read(const char * dirname, + const char * filename, + char ** out_full_filename, + char ** out_uuid, + char ** out_loaded_path, + char ** out_shadowed_storage, + struct stat *out_st); + +gboolean nms_keyfile_nmmeta_read_from_file(const char *full_filename, + char ** out_dirname, + char ** out_filename, + char ** out_uuid, + char ** out_loaded_path, + char ** out_shadowed_storage); + +int nms_keyfile_nmmeta_write(const char *dirname, + const char *uuid, + const char *loaded_path, + gboolean loaded_path_allow_relative, + const char *shadowed_storage, + char ** out_full_filename); + +/*****************************************************************************/ + +struct stat; +gboolean nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype, + const struct stat *st, + GError ** error); + +gboolean nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype, + const char * filename, + struct stat * out_st, + GError ** error); + +#endif /* __NMS_KEYFILE_UTILS_H__ */ diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c new file mode 100644 index 0000000..be1bf15 --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-writer.h" + +#include +#include +#include + +#include "nm-keyfile/nm-keyfile-internal.h" + +#include "nms-keyfile-utils.h" +#include "nms-keyfile-reader.h" + +#include "nm-glib-aux/nm-io-utils.h" + +/*****************************************************************************/ + +typedef struct { + const char *keyfile_dir; +} WriteInfo; + +static void +cert_writer(NMConnection * connection, + GKeyFile * file, + NMSetting8021x * setting, + const NMSetting8021xSchemeVtable *vtable, + WriteInfo * info, + GError ** error) +{ + const char * setting_name = nm_setting_get_name(NM_SETTING(setting)); + NMSetting8021xCKScheme scheme; + NMSetting8021xCKFormat format; + const char * path = NULL, *ext = "pem"; + + scheme = vtable->scheme_func(setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + char * tmp = NULL; + const char *accepted_path = NULL; + + path = vtable->path_func(setting); + g_assert(path); + + if (g_str_has_prefix(path, info->keyfile_dir)) { + const char *p = path + strlen(info->keyfile_dir); + + /* If the path is rooted in the keyfile directory, just use a + * relative path instead of an absolute one. + */ + if (*p == '/') { + while (*p == '/') + p++; + if (p[0]) { + /* If @p looks like an integer list, the following detection will fail too and + * we will file:// qualify the path below. We thus avoid writing a path string + * that would be interpreted as legacy binary format by reader. */ + tmp = nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir, + p, + -1, + FALSE, + NULL); + if (tmp) { + nm_clear_g_free(&tmp); + accepted_path = p; + } + } + } + } + if (!accepted_path) { + /* What we are about to write, must also be understood by the reader. + * Otherwise, add a file:// prefix */ + tmp = + nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir, path, -1, FALSE, NULL); + if (tmp) { + nm_clear_g_free(&tmp); + accepted_path = path; + } + } + + if (!accepted_path) + accepted_path = tmp = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, accepted_path); + g_free(tmp); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + nm_keyfile_plugin_kf_set_string(file, + setting_name, + vtable->setting_key, + vtable->uri_func(setting)); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes * blob; + const guint8 *blob_data; + gsize blob_len; + gboolean success; + GError * local = NULL; + char * new_path; + + blob = vtable->blob_func(setting); + g_assert(blob); + blob_data = g_bytes_get_data(blob, &blob_len); + + if (vtable->format_func) { + /* Get the extension for a private key */ + format = vtable->format_func(setting); + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + ext = "p12"; + } else { + /* DER or PEM format certificate? */ + if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82) + ext = "der"; + } + + /* Write the raw data out to the standard file so that we can use paths + * from now on instead of pushing around the certificate data. + */ + new_path = g_strdup_printf("%s/%s-%s.%s", + info->keyfile_dir, + nm_connection_get_uuid(connection), + vtable->file_suffix, + ext); + + /* FIXME(keyfile-parse-in-memory): writer must not access/write to the file system before + * being sure that the entire profile can be written and all circumstances are good to + * proceed. That means, while writing we must only collect the blogs in-memory, and write + * them all in the end together (or not at all). */ + success = nm_utils_file_set_contents(new_path, + (const char *) blob_data, + blob_len, + 0600, + NULL, + &local); + if (success) { + /* Write the path value to the keyfile. + * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64," */ + nm_keyfile_plugin_kf_set_string(file, + setting_name, + vtable->setting_key, + strrchr(new_path, '/') + 1); + } else { + nm_log_warn(LOGD_SETTINGS, + "keyfile: %s.%s: failed to write certificate to file %s: %s", + setting_name, + vtable->setting_key, + new_path, + local->message); + g_error_free(local); + } + g_free(new_path); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached(); + } +} + +static gboolean +_handler_write(NMConnection * connection, + GKeyFile * keyfile, + NMKeyfileHandlerType type, + NMKeyfileHandlerData *type_data, + void * user_data) +{ + if (type == NM_KEYFILE_HANDLER_TYPE_WRITE_CERT) { + cert_writer(connection, + keyfile, + NM_SETTING_802_1X(type_data->cur_setting), + type_data->write_cert.vtable, + user_data, + type_data->p_error); + return TRUE; + } + return FALSE; +} + +static gboolean +_internal_write_connection(NMConnection * connection, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + const char * keyfile_dir, + const char * profile_dir, + gboolean with_extension, + uid_t owner_uid, + pid_t owner_grp, + const char * existing_path, + gboolean existing_path_read_only, + gboolean force_rename, + NMSKeyfileWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + char ** out_path, + NMConnection ** out_reread, + gboolean * out_reread_same, + GError ** error) +{ + nm_auto_unref_keyfile GKeyFile *kf_file = NULL; + gs_free char * kf_content_buf = NULL; + gsize kf_content_len; + gs_free char * path = NULL; + const char * id; + WriteInfo info = {0}; + gs_free_error GError *local_err = NULL; + int errsv; + gboolean rename; + int i_path; + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + + g_return_val_if_fail(!out_path || !*out_path, FALSE); + g_return_val_if_fail(keyfile_dir && keyfile_dir[0] == '/', FALSE); + + nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS); + + nm_assert(!shadowed_owned || shadowed_storage); + + rename = force_rename || existing_path_read_only + || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir)); + + id = nm_connection_get_id(connection); + nm_assert(id && *id); + + info.keyfile_dir = keyfile_dir; + + kf_file = + nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_write, &info, error); + if (!kf_file) + return FALSE; + + if (is_nm_generated) { + g_key_file_set_boolean(kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_NM_GENERATED, + TRUE); + } + + if (is_volatile) { + g_key_file_set_boolean(kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_VOLATILE, + TRUE); + } + + if (is_external) { + g_key_file_set_boolean(kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_EXTERNAL, + TRUE); + } + + if (shadowed_storage) { + g_key_file_set_string(kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE, + shadowed_storage); + } + + if (shadowed_owned) { + g_key_file_set_boolean(kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED, + TRUE); + } + + kf_content_buf = g_key_file_to_data(kf_file, &kf_content_len, error); + if (!kf_content_buf) + return FALSE; + + if (!g_file_test(keyfile_dir, G_FILE_TEST_IS_DIR)) + (void) g_mkdir_with_parents(keyfile_dir, 0755); + + for (i_path = -2; i_path < 10000; i_path++) { + gs_free char *path_candidate = NULL; + gboolean is_existing_path; + + if (i_path == -2) { + if (!existing_path || rename) + continue; + path_candidate = g_strdup(existing_path); + } else if (i_path == -1) { + gs_free char *filename_escaped = NULL; + + filename_escaped = nm_keyfile_utils_create_filename(id, with_extension); + path_candidate = g_build_filename(keyfile_dir, filename_escaped, NULL); + } else { + gs_free char *filename_escaped = NULL; + gs_free char *filename = NULL; + + if (i_path == 0) + filename = g_strdup_printf("%s-%s", id, nm_connection_get_uuid(connection)); + else + filename = + g_strdup_printf("%s-%s-%d", id, nm_connection_get_uuid(connection), i_path); + + filename_escaped = nm_keyfile_utils_create_filename(filename, with_extension); + + path_candidate = g_strdup_printf("%s/%s", keyfile_dir, filename_escaped); + } + + is_existing_path = existing_path && nm_streq(existing_path, path_candidate); + + if (is_existing_path && rename) + continue; + + if (allow_filename_cb && !allow_filename_cb(path_candidate, allow_filename_user_data)) + continue; + + if (!is_existing_path) { + if (g_file_test(path_candidate, G_FILE_TEST_EXISTS)) + continue; + } + + path = g_steal_pointer(&path_candidate); + break; + } + + if (!path) { + gs_free char *ss = NULL; + + /* this really should not happen, we tried hard to find an unused name... bail out. */ + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "could not find suitable keyfile file name (%s already used)", + ss = ({ + gs_free char *filename_escaped = NULL; + + filename_escaped = nm_keyfile_utils_create_filename(id, with_extension); + g_build_filename(keyfile_dir, filename_escaped, NULL); + })); + + return FALSE; + } + + if (out_reread || out_reread_same) { + gs_free_error GError *reread_error = NULL; + + reread = + nms_keyfile_reader_from_keyfile(kf_file, path, NULL, profile_dir, FALSE, &reread_error); + + if (!reread || !nm_connection_normalize(reread, NULL, NULL, &reread_error)) { + nm_log_err( + LOGD_SETTINGS, + "BUG: the profile cannot be stored in keyfile format without becoming unusable: %s", + reread_error->message); + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "keyfile writer produces an invalid connection: %s", + reread_error->message); + nm_assert_not_reached(); + return FALSE; + } + + if (out_reread_same) { + reread_same = + !!nm_connection_compare(reread, connection, NM_SETTING_COMPARE_FLAG_EXACT); + + nm_assert(reread_same + == nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT)); + nm_assert(reread_same == ({ + gs_unref_hashtable GHashTable *_settings = NULL; + + (nm_connection_diff(reread, + connection, + NM_SETTING_COMPARE_FLAG_EXACT, + &_settings) + && !_settings); + })); + } + } + + nm_utils_file_set_contents(path, kf_content_buf, kf_content_len, 0600, NULL, &local_err); + if (local_err) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "error writing to file '%s': %s", + path, + local_err->message); + return FALSE; + } + + if (chown(path, owner_uid, owner_grp) < 0) { + errsv = errno; + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "error chowning '%s': %s (%d)", + path, + nm_strerror_native(errsv), + errsv); + unlink(path); + return FALSE; + } + + /* In case of updating the connection and changing the file path, + * we need to remove the old one, not to end up with two connections. + */ + if (existing_path && !existing_path_read_only && !nm_streq(path, existing_path)) + unlink(existing_path); + + NM_SET_OUT(out_reread, g_steal_pointer(&reread)); + NM_SET_OUT(out_reread_same, reread_same); + NM_SET_OUT(out_path, g_steal_pointer(&path)); + + return TRUE; +} + +gboolean +nms_keyfile_writer_connection(NMConnection * connection, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + const char * keyfile_dir, + const char * profile_dir, + const char * existing_path, + gboolean existing_path_read_only, + gboolean force_rename, + NMSKeyfileWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + char ** out_path, + NMConnection ** out_reread, + gboolean * out_reread_same, + GError ** error) +{ + return _internal_write_connection(connection, + is_nm_generated, + is_volatile, + is_external, + shadowed_storage, + shadowed_owned, + keyfile_dir, + profile_dir, + TRUE, + 0, + 0, + existing_path, + existing_path_read_only, + force_rename, + allow_filename_cb, + allow_filename_user_data, + out_path, + out_reread, + out_reread_same, + error); +} + +gboolean +nms_keyfile_writer_test_connection(NMConnection * connection, + const char * keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char ** out_path, + NMConnection **out_reread, + gboolean * out_reread_same, + GError ** error) +{ + return _internal_write_connection(connection, + FALSE, + FALSE, + FALSE, + NULL, + FALSE, + keyfile_dir, + keyfile_dir, + FALSE, + owner_uid, + owner_grp, + NULL, + FALSE, + FALSE, + NULL, + NULL, + out_path, + out_reread, + out_reread_same, + error); +} diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h new file mode 100644 index 0000000..dffd3b0 --- /dev/null +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#ifndef __NMS_KEYFILE_WRITER_H__ +#define __NMS_KEYFILE_WRITER_H__ + +#include "nm-connection.h" + +typedef gboolean (*NMSKeyfileWriterAllowFilenameCb)(const char *check_filename, + gpointer allow_filename_user_data); + +gboolean nms_keyfile_writer_connection(NMConnection * connection, + gboolean is_nm_generated, + gboolean is_volatile, + gboolean is_external, + const char * shadowed_storage, + gboolean shadowed_owned, + const char * keyfile_dir, + const char * profile_dir, + const char * existing_path, + gboolean existing_path_read_only, + gboolean force_rename, + NMSKeyfileWriterAllowFilenameCb allow_filename_cb, + gpointer allow_filename_user_data, + char ** out_path, + NMConnection ** out_reread, + gboolean * out_reread_same, + GError ** error); + +gboolean nms_keyfile_writer_test_connection(NMConnection * connection, + const char * keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char ** out_path, + NMConnection **out_reread, + gboolean * out_reread_same, + GError ** error); + +#endif /* __NMS_KEYFILE_WRITER_H__ */ diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT new file mode 100644 index 0000000..162bf72 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT @@ -0,0 +1,24 @@ + +[connection] +id=AT&T Data Connect BT +uuid=089130ab-ce28-46e4-ad77-d44869b03d19 +type=bluetooth +autoconnect=false + +[ipv4] +method=auto + +[gsm] +number=*99# +username=ISP@CINGULARGPRS.COM +password=CINGULAR1 +apn=ISP.CINGULAR + +[serial] +baud=115200 +parity=o + +[bluetooth] +bdaddr=00:11:22:33:44:55 +type=dun + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain new file mode 100644 index 0000000..11e1ec6 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain @@ -0,0 +1,24 @@ + +[connection] +id=AT&T Data Connect +uuid=15d742f1-2b5a-421e-9f27-fcb1fc26d72c +type=gsm +autoconnect=false + +[ipv4] +method=auto + +[gsm] +number=*99# +username=ISP@CINGULARGPRS.COM +password=CINGULAR1 +apn=ISP.CINGULAR +network-id=24005 +pin=2345 +device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 +sim-id=89148000000060671234 +sim-operator-id=310260 + +[serial] +baud=115200 +parity=111 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component new file mode 100644 index 0000000..f5463ed --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component @@ -0,0 +1,15 @@ +[connection] +id=Test Bridge Component +uuid=d7b4f96c-c45e-4298-bef8-f48574f8c1c0 +type=802-3-ethernet +master=br0 +slave-type=bridge + +[802-3-ethernet] +mac-address=00:22:15:59:62:97 + +[bridge-port] +priority=28 +hairpin-mode=true +path-cost=100 + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main new file mode 100644 index 0000000..fc9fd8f --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main @@ -0,0 +1,18 @@ +[connection] +id=Test Bridge Main +uuid=8f061643-fe41-4d4c-a8d9-097d26e2ad3a +type=bridge + +[bridge] +interface-name=br0 +stp=true +forward-delay=2 +priority=32744 +hello-time=7 +max-age=39 +ageing-time=235352 +multicast-snooping=false + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property new file mode 100644 index 0000000..52b395b --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property @@ -0,0 +1,8 @@ +[connection] +id=Test Wired Connection IP6 +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=802-3-ethernet + +[ipv6] +method=auto +ip6-privacy=2 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property new file mode 100644 index 0000000..3a46611 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property @@ -0,0 +1,11 @@ +[connection] +id=Test Flags Property +uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f +type=gsm + +[gsm] +number=*99# +username=username +password-flags=5 +apn=my.apn + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection new file mode 100644 index 0000000..3d58fee --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection @@ -0,0 +1,41 @@ + +[serial] +baud=115200 +bits=8 +parity=110 +stopbits=1 +send-delay=0 + +[connection] +id=Test GSM Connection +uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f +type=gsm +autoconnect=false +timestamp=0 + +[gsm] +number=*99# +username=username +apn=my.apn +network-type=0 +band=0 + +[ppp] +noauth=false +refuse-eap=false +refuse-pap=false +refuse-chap=false +refuse-mschap=false +refuse-mschapv2=false +nobsdcomp=false +nodeflate=false +no-vj-comp=false +require-mppe=false +require-mppe-128=false +mppe-stateful=false +crtscts=false +baud=0 +mru=0 +mtu=0 +lcp-echo-failure=5 +lcp-echo-interval=30 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection new file mode 100644 index 0000000..3a984b7 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection @@ -0,0 +1,13 @@ +[connection] +id=Test InfiniBand Connection +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=infiniband + +[infiniband] +mac-address=00:11:22:33:44:55:66:77:88:99:01:12:23:34:45:56:67:78:89:90 +transport-mode=datagram +mtu=1400 + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID new file mode 100644 index 0000000..2bacb72 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID @@ -0,0 +1,11 @@ +[connection] +id=Test +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=802-11-wireless + +[802-11-wireless] +ssid=101 + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 new file mode 100644 index 0000000..2024025 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 @@ -0,0 +1,11 @@ +[connection] +id=Test SSID - escaping semicolon in string +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=802-11-wireless + +[802-11-wireless] +ssid=11\\;12\\;13\\; + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID new file mode 100644 index 0000000..6d2bc0f --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID @@ -0,0 +1,11 @@ +[connection] +id=Test +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=802-11-wireless + +[802-11-wireless] +ssid=98;108;97;104;49;50;51;52; + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format new file mode 100644 index 0000000..b2bf915 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format @@ -0,0 +1,13 @@ +[connection] +id=Test InfiniBand Connection +uuid=5680a56d-c99f-45ad-a6dd-b44d5c398c12 +type=infiniband + +[infiniband] +mac-address=0;17;34;51;68;85;102;119;136;153;1;18;35;52;69;86;103;120;137;144; +transport-mode=datagram +mtu=1400 + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format new file mode 100644 index 0000000..9427b16 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format @@ -0,0 +1,10 @@ +[connection] +id=Test MAC Old Format +uuid=8980a26d-c99f-4aad-a6bd-b439bc348ca4 +type=802-3-ethernet + +[802-3-ethernet] +mac-address=00:11:aa:BB:CC:55 +cloned-mac-address=00;22;170;187;204;254; +mtu=1400 + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID new file mode 100644 index 0000000..138380d --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID @@ -0,0 +1,9 @@ +# id and uuid keys are missing +# They will be auto-genetrated by NetworkManager + +[connection] +type=ethernet +autoconnect=true + +[802-3-ethernet] +mac-address=00:11:22:33:44:55 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags new file mode 100644 index 0000000..330adda --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags @@ -0,0 +1,15 @@ +# VLAN setting with missing 'flags' key +# vlan.flags will be set to 0 (even if the default 'flags' property value is 1) + +[connection] +id=Test Missing Vlan Flags +uuid=803ebe47-8c31-401d-b47b-03fc0d34eb11 +type=vlan +autoconnect=true + +[802-3-ethernet] +mac-address=00:11:22:33:44:55 + +[vlan] +id=444 +parent=em1 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting new file mode 100644 index 0000000..95d7b9a --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting @@ -0,0 +1,11 @@ +# Settings with all default values are not written, including +# VLAN settings with a VLAN ID of 0, which is the default value. + +[connection] +id=Test Missing Vlan Setting +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=vlan +autoconnect=true + +[802-3-ethernet] +mac-address=00:11:22:33:44:55 diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name new file mode 100644 index 0000000..f27cd4a --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name @@ -0,0 +1,13 @@ + +[connection] +id=Test Wired Connection +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=ethernet + +[ethernet] +mac-address=00:11:22:33:44:55 +mtu=1400 + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names new file mode 100644 index 0000000..d9ac586 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names @@ -0,0 +1,16 @@ +[connection] +id=Test New Wireless Group Names +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=wifi + +[wifi] +ssid=foobar +mode=infrastructure + +[wifi-security] +key-mgmt=wpa-psk +psk=s3cu4e passphrase + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID new file mode 100644 index 0000000..4a3b56d --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID @@ -0,0 +1,11 @@ +[connection] +id=Test +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=802-11-wireless + +[802-11-wireless] +ssid=blah blah ssid 1234 + +[ipv4] +method=auto + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config new file mode 100644 index 0000000..14280ee --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config @@ -0,0 +1,11 @@ + +[connection] +id=Test TC Config +uuid=32a51666-95ea-1337-b371-dcf5e54b1001 +type=802-3-ethernet + +[tc] +qdisc.root=handle 1234 fq_codel +qdisc.ffff:fff1=ingress +tfilter.1234:=matchall action drop +tfilter.ffff:=matchall action simple sdata Hello diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection new file mode 100644 index 0000000..f9ccc00 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection @@ -0,0 +1,72 @@ + +[connection] +id=Test Wired Connection +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=802-3-ethernet +autoconnect=true +timestamp=6654332 + +[802-3-ethernet] +mac-address=00:11:22:33:44:55 +speed=0 +duplex=full +auto-negotiate=true +mtu=1400 + +[ipv4] +method=manual +dns=4.2.2.1;bogus;4.2.2.2; +addresses1=192.168.0.5;24;192.168.0.1; +addresses2=1.2.3.4;16;1.2.1.1; +address=2.3.4.5/24,2.3.4.6 +address3=3.4.5.6/16 +address4=4.5.6.7/1.2.3.4 +address5=5.6.7.8 +routes1=1.2.3.0/24,2.3.4.8,99 +route=5.6.7.8/32 +routes2=1.1.1.2/12, +routes3=1.1.1.3/13,, +routes7=1.1.1.7/17,0.0.0.0 +routes4=1.1.1.4/14,2.2.2.4 +address30=1.2.3.130/24 +routes5=1.1.1.5/15,2.2.2.5, +routes6=1.1.1.6/16,2.2.2.6,0 +routes8=1.1.1.8/18,0.0.0.0, +routes9=1.1.1.9/19,0.0.0.0,0 +route10=1.1.1.10/21,,0 +routes10=1.1.1.10/20,,0 +routes11=1.1.1.11/21,,21 +routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7,type=unicast +routes12=1.2.3.4/32 +routes12_options=type=local +address30=1.2.3.30/24 +addresses30=1.2.3.30/25 +addresses31=1.2.3.31/25 +address31=1.2.3.31/24 +ignore-auto-routes=false +ignore-auto-dns=false + +[ipv6] +method=manual +dns=1111:dddd::aaaa;1::cafe; +dns-search=super-domain.com;redhat.com;gnu.org; +addresses1=abcd:1234:ffff::cdde/64 +addresses2=1:2:3:4:5:6:7:8/96 +address=2:3:4:5:6:7:8:9/64,2:3:4:5:1:2:3:4 +address3=3:4:5:6:7:8:9:0/128 +address4=3:4:5:6:7:8:9:14 +address5=3:4:5:6:7:8:9:15, +address6=3:4:5:6:7:8:9:16,66 +address7=3:4:5:6:7:8:9:17,67, +address8=3:4:5:6:7:8:9:18,68,:: +address9=3:4:5:6:7:8:9:19,69,1::09 +routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99; +route=d:e:f:0:1:2:3:4/64,f:e:d:c:1:2:3:4 +route2=8:7:6:5:4:3:2:1/128 +route3=6:7:8:9:0:1:2:3/126,,1 +route4=7:8:9:0:1:2:3:4/125/::,5 +route5=8:9:0:1:2:3:4:5/124,6 +route6=8:9:0:1:2:3:4:6/123,, +route6_options=from=abce::/63 +ignore-auto-routes=false +ignore-auto-dns=false diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 new file mode 100644 index 0000000..a42dd5d --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 @@ -0,0 +1,20 @@ + +[connection] +id=Test Wired Connection IP6 +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=802-3-ethernet +autoconnect=true +timestamp=6654332 + +[802-3-ethernet] +auto-negotiate=true +mtu=1400 + +[ipv4] +method=disabled + +[ipv6] +method=manual +addresses1=abcd:1234:ffff::cdde/64,abcd:1234:ffff::cdd1 +dns=1111:dddd::aaaa;1::cafe; + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case new file mode 100644 index 0000000..29aef49 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case @@ -0,0 +1,32 @@ + +[connection] +id=Test Wired Connection MAC Case +uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57 +type=802-3-ethernet +autoconnect=true +timestamp=6654332 + +[802-3-ethernet] +mac-address=00:11:aa:BB:CC:55 +speed=0 +duplex=full +auto-negotiate=true +mtu=1400 + +[ipv4] +method=manual +dns=4.2.2.1;4.2.2.2; +addresses1=192.168.0.5;24;192.168.0.1; +addresses2=1.2.3.4;16;1.2.1.1; +ignore-auto-routes=false +ignore-auto-dns=false + +[ipv6] +method=manual +dns=1111:dddd::aaaa;1::cafe; +dns-search=super-domain.com;redhat.com;gnu.org; +addresses1=abcd:1234:ffff::cdde/64 +addresses2=1:2:3:4:5:6:7:8/96 +routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99; +ignore-auto-routes=false +ignore-auto-dns=false diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob new file mode 100644 index 0000000..62e6ae3 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob @@ -0,0 +1,22 @@ + +[connection] +id=Wired TLS +uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488 +type=802-3-ethernet + +[802-1x] +eap=tls; +identity=Bill Smith +ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80; +client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key-password=12345testing + +[ipv4] +method=auto + +[802-3-ethernet] +duplex=full + +[ipv6] +method=ignore diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New new file mode 100644 index 0000000..4cd8668 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New @@ -0,0 +1,22 @@ + +[connection] +id=Wired TLS +uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488 +type=802-3-ethernet + +[802-1x] +eap=tls; +identity=Bill Smith +ca-cert=test-ca-cert.pem +client-cert=test-key-and-cert.pem +private-key=test-key-and-cert.pem +private-key-password=12345testing + +[ipv4] +method=auto + +[802-3-ethernet] +duplex=full + +[ipv6] +method=ignore diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old new file mode 100644 index 0000000..d3da598 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old @@ -0,0 +1,22 @@ + +[connection] +id=Wired TLS +uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488 +type=802-3-ethernet + +[802-1x] +eap=tls; +identity=Bill Smith +ca-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;67;65;47;101;97;112;116;101;115;116;95;99;97;95;99;101;114;116;46;112;101;109;0; +client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key-password=12345testing + +[ipv4] +method=auto + +[802-3-ethernet] +duplex=full + +[ipv6] +method=ignore diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing new file mode 100644 index 0000000..2b39538 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing @@ -0,0 +1,22 @@ + +[connection] +id=Wired TLS +uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488 +type=802-3-ethernet + +[802-1x] +eap=tls; +identity=Bill Smith +ca-cert=/some/random/cert/path.pem +client-cert=test-key-and-cert.pem +private-key=test-key-and-cert.pem +private-key-password=12345testing + +[ipv4] +method=auto + +[802-3-ethernet] +duplex=full + +[ipv6] +method=ignore diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection new file mode 100644 index 0000000..b1949d0 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection @@ -0,0 +1,22 @@ +[connection] +id=Test Wireless Connection +uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa +type=802-11-wireless +autoconnect=false +timestamp=1226604314 + +[802-11-wireless] +ssid=110;109;45;116;101;115;116;45;97;112; +mode=infrastructure +channel=0 +rate=0 +tx-power=0 +mtu=0 +bssid=00:1a:33:44:99:82 + +[ipv4] +method=auto +ignore-auto-routes=false +ignore-auto-dns=false +never-default=false + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection new file mode 100644 index 0000000..16d4b45 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection @@ -0,0 +1,33 @@ +[connection] +id=dcb connection 1 +uuid=ac3c251e-260f-49b6-8ceb-12d37ea00751 +type=ethernet +autoconnect=false +permissions= +secondaries= + +[ethernet] +mac-address-blacklist= + +[dcb] +app-fcoe-flags=1 +app-fip-flags=1 +app-iscsi-flags=1 +priority-bandwidth=0;0;0;0;0;0;0;0; +priority-flow-control=0;0;0;0;0;0;0;0; +priority-flow-control-flags=1 +priority-group-bandwidth=100;0;0;0;0;0;0;0; +priority-group-flags=1 +priority-group-id=0;0;0;0;0;0;0;0; +priority-strict-bandwidth=0;0;0;0;0;0;0;0; +priority-traffic-class=0;0;0;0;0;0;0;0; + +[ipv4] +dns-search= +method=auto + +[ipv6] +addr-gen-mode=stable-privacy +dns-search= +ip6-privacy=0 +method=auto diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 new file mode 100644 index 0000000..cac135a --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 @@ -0,0 +1,2 @@ +[connection] +type=802-3-ethernet diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 new file mode 100644 index 0000000..bbf2d8d --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 @@ -0,0 +1 @@ +[802-3-ethernet] diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 new file mode 100644 index 0000000..d3122d5 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 @@ -0,0 +1,4 @@ +[connection] +type=802-3-ethernet +master=br0 +slave-type=bridge diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 new file mode 100644 index 0000000..eb1cdac --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 @@ -0,0 +1,7 @@ +[connection] +master=br0 + +[802-3-ethernet] + +[bridge-port] + diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 new file mode 100644 index 0000000..7419e97 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 @@ -0,0 +1,4 @@ +[connection] +master=br0 +slave-type=bridge +[802-3-ethernet] diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 new file mode 100644 index 0000000..626b6f6 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 @@ -0,0 +1,4 @@ +[connection] +type=802-3-ethernet +master=br0 +[bridge-port] diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem new file mode 100644 index 0000000..ef1be20 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem new file mode 100644 index 0000000..dec9aa1 --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem @@ -0,0 +1,118 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,4DE0615F23D82107 + +QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz +L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7 +zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU +h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW +ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv +QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na +eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo +cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m +RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8 +xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr +T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI +ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo +RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ +EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt +lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5 +r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4 +aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W +GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e +DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE +lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO +8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3 +VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt +G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ +tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW +QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1 +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com + Validity + Not Before: Mar 10 15:13:16 2009 GMT + Not After : Mar 8 15:13:16 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a: + 82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48: + ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49: + c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8: + 03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14: + ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9: + c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c: + 60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d: + 44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b: + f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d: + c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75: + d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2: + 06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38: + ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba: + ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36: + 2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11: + a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f: + a3:43 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C + X509v3 Authority Key Identifier: + keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10 + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com + serial:EB:E7:64:FB:79:F7:22:19 + + Signature Algorithm: md5WithRSAEncryption + 7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82: + f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89: + b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b: + 12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76: + 1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48: + 32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02: + 85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1: + fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c: + cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43: + 8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71: + 1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19: + 07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69: + 7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62: + 8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7: + 3b:65:fe:f9 +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx +EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO +TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx +HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN +MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl +MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG +A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR +XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA +vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9 +jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF +TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX +gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj +ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy +YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww +gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ +BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx +FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD +VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3 +IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/ +DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2 +8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr +vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD +T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa +PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk= +-----END CERTIFICATE----- diff --git a/src/core/settings/plugins/keyfile/tests/meson.build b/src/core/settings/plugins/keyfile/tests/meson.build new file mode 100644 index 0000000..3c89bcf --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +exe = executable( + 'test-keyfile-settings', + 'test-keyfile-settings.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) + +test( + 'test-keyfile-settings', + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c new file mode 100644 index 0000000..4d9c86c --- /dev/null +++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c @@ -0,0 +1,2923 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" + +#include "settings/plugins/keyfile/nms-keyfile-reader.h" +#include "settings/plugins/keyfile/nms-keyfile-writer.h" +#include "settings/plugins/keyfile/nms-keyfile-utils.h" + +#include "nm-test-utils-core.h" + +#define TEST_KEYFILES_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/keyfile/tests/keyfiles" +#define TEST_SCRATCH_DIR NM_BUILD_BUILDDIR "/src/core/settings/plugins/keyfile/tests/keyfiles" + +/*****************************************************************************/ + +static void +check_ip_address(NMSettingIPConfig *config, int idx, const char *address, int plen) +{ + NMIPAddress *ip4 = nm_setting_ip_config_get_address(config, idx); + + g_assert(ip4); + g_assert_cmpstr(nm_ip_address_get_address(ip4), ==, address); + g_assert_cmpint(nm_ip_address_get_prefix(ip4), ==, plen); +} + +static void +check_ip_route(NMSettingIPConfig *config, + int idx, + const char * destination, + int plen, + const char * next_hop, + gint64 metric) +{ + NMIPRoute *route = nm_setting_ip_config_get_route(config, idx); + + g_assert(route); + g_assert_cmpstr(nm_ip_route_get_dest(route), ==, destination); + g_assert_cmpint(nm_ip_route_get_prefix(route), ==, plen); + g_assert_cmpstr(nm_ip_route_get_next_hop(route), ==, next_hop); + g_assert_cmpint(nm_ip_route_get_metric(route), ==, metric); +} + +#define keyfile_read_connection_from_file(full_filename) \ + ({ \ + gs_free_error GError *_error = NULL; \ + NMConnection * _connection; \ + \ + g_assert(full_filename &&full_filename[0] == '/'); \ + \ + _connection = \ + nms_keyfile_reader_from_file(full_filename, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + (nmtst_get_rand_uint32() % 2) ? &_error : NULL); \ + nmtst_assert_success(_connection, _error); \ + nmtst_assert_connection_verifies_without_normalization(_connection); \ + \ + _connection; \ + }) + +static void +assert_reread(NMConnection *connection, gboolean normalize_connection, const char *testfile) +{ + gs_unref_object NMConnection *reread = NULL; + gs_unref_object NMConnection *connection_clone = NULL; + NMSettingConnection * s_con; + + g_assert(NM_IS_CONNECTION(connection)); + + reread = keyfile_read_connection_from_file(testfile); + + if (!normalize_connection && (s_con = nm_connection_get_setting_connection(connection)) + && !nm_setting_connection_get_master(s_con) + && !nm_connection_get_setting_proxy(connection)) { + connection_clone = nmtst_clone_connection(connection); + connection = connection_clone; + nm_connection_add_setting(connection, nm_setting_proxy_new()); + } + + nmtst_assert_connection_equals(connection, normalize_connection, reread, FALSE); +} + +static void +assert_reread_and_unlink(NMConnection *connection, + gboolean normalize_connection, + const char * testfile) +{ + assert_reread(connection, normalize_connection, testfile); + unlink(testfile); +} + +static void +assert_reread_same(NMConnection *connection, NMConnection *reread) +{ + nmtst_assert_connection_verifies_without_normalization(reread); + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void +write_test_connection_reread(NMConnection * connection, + char ** testfile, + NMConnection **out_reread, + gboolean * out_reread_same) +{ + uid_t owner_uid; + gid_t owner_grp; + gboolean success; + GError * error = NULL; + GError ** p_error = (nmtst_get_rand_uint32() % 2) ? &error : NULL; + gs_unref_object NMConnection *connection_normalized = NULL; + + g_assert(NM_IS_CONNECTION(connection)); + g_assert(testfile && !*testfile); + + owner_uid = geteuid(); + owner_grp = getegid(); + + connection_normalized = nmtst_connection_duplicate_and_normalize(connection); + + success = nms_keyfile_writer_test_connection(connection_normalized, + TEST_SCRATCH_DIR, + owner_uid, + owner_grp, + testfile, + out_reread, + out_reread_same, + p_error); + g_assert_no_error(error); + g_assert(success); + g_assert(*testfile && (*testfile)[0]); +} + +static void +write_test_connection(NMConnection *connection, char **testfile) +{ + gs_unref_object NMConnection *reread = NULL; + gboolean reread_same = FALSE; + + write_test_connection_reread(connection, testfile, &reread, &reread_same); + assert_reread_same(connection, reread); + g_assert(reread_same); +} + +static void +write_test_connection_and_reread(NMConnection *connection, gboolean normalize_connection) +{ + gs_free char *testfile = NULL; + + g_assert(NM_IS_CONNECTION(connection)); + + write_test_connection(connection, &testfile); + assert_reread_and_unlink(connection, normalize_connection, testfile); +} + +static GKeyFile * +keyfile_load_from_file(const char *testfile) +{ + GKeyFile *keyfile; + GError * error = NULL; + gboolean success; + + g_assert(testfile && *testfile); + + keyfile = g_key_file_new(); + success = g_key_file_load_from_file(keyfile, testfile, G_KEY_FILE_NONE, &error); + g_assert_no_error(error); + g_assert(success); + + return keyfile; +} + +static void +_setting_copy_property_gbytes(NMConnection *src, + NMConnection *dst, + const char * setting_name, + const char * property_name) +{ + gs_unref_bytes GBytes *blob = NULL; + NMSetting * s_src; + NMSetting * s_dst; + + g_assert(NM_IS_CONNECTION(src)); + g_assert(NM_IS_CONNECTION(dst)); + g_assert(setting_name); + g_assert(property_name); + + s_src = nm_connection_get_setting_by_name(src, setting_name); + g_assert(NM_IS_SETTING(s_src)); + s_dst = nm_connection_get_setting_by_name(dst, setting_name); + g_assert(NM_IS_SETTING(s_dst)); + + g_object_get(s_src, property_name, &blob, NULL); + g_object_set(s_dst, property_name, blob, NULL); +} + +/*****************************************************************************/ + +static void +test_read_valid_wired_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPRoute * route; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + + NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses1*"); + NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses2*"); + NMTST_EXPECT_NM_WARN("*missing prefix length*address4*"); + NMTST_EXPECT_NM_WARN("*missing prefix length*address5*"); + NMTST_EXPECT_NM_WARN("*ipv4.dns: ignoring invalid DNS server IPv4 address 'bogus'*"); + NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes2*"); + NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes3*"); + NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes5*"); + NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes8*"); + NMTST_EXPECT_NM_WARN("*missing prefix length*address4*"); + NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address5*"); + NMTST_EXPECT_NM_WARN("*missing prefix length*address5*"); + NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address7*"); + NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*"); + NMTST_EXPECT_NM_INFO("*ipv6.route*semicolon at the end*route6*"); + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection"); + g_test_assert_expected_messages(); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection"); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), + ==, + "4e80a56d-c99f-4aad-a6dd-b449bc398c57"); + g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 6654332); + g_assert(nm_setting_connection_get_autoconnect(s_con)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2"); + + /* IPv4 addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 10); + check_ip_address(s_ip4, 0, "2.3.4.5", 24); + check_ip_address(s_ip4, 1, "192.168.0.5", 24); + check_ip_address(s_ip4, 2, "1.2.3.4", 16); + check_ip_address(s_ip4, 3, "3.4.5.6", 16); + check_ip_address(s_ip4, 4, "4.5.6.7", 24); + check_ip_address(s_ip4, 5, "5.6.7.8", 24); + check_ip_address(s_ip4, 6, "1.2.3.30", 24); + check_ip_address(s_ip4, 7, "1.2.3.30", 25); + check_ip_address(s_ip4, 8, "1.2.3.31", 24); + check_ip_address(s_ip4, 9, "1.2.3.31", 25); + + /* IPv4 gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "2.3.4.6"); + + /* IPv4 routes */ + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 14); + check_ip_route(s_ip4, 0, "5.6.7.8", 32, NULL, -1); + check_ip_route(s_ip4, 1, "1.2.3.0", 24, "2.3.4.8", 99); + check_ip_route(s_ip4, 2, "1.1.1.2", 12, NULL, -1); + check_ip_route(s_ip4, 3, "1.1.1.3", 13, NULL, -1); + check_ip_route(s_ip4, 4, "1.1.1.4", 14, "2.2.2.4", -1); + check_ip_route(s_ip4, 5, "1.1.1.5", 15, "2.2.2.5", -1); + check_ip_route(s_ip4, 6, "1.1.1.6", 16, "2.2.2.6", 0); + check_ip_route(s_ip4, 7, "1.1.1.7", 17, NULL, -1); + check_ip_route(s_ip4, 8, "1.1.1.8", 18, NULL, -1); + check_ip_route(s_ip4, 9, "1.1.1.9", 19, NULL, 0); + check_ip_route(s_ip4, 10, "1.1.1.10", 21, NULL, 0); + check_ip_route(s_ip4, 11, "1.1.1.10", 20, NULL, 0); + check_ip_route(s_ip4, 12, "1.1.1.11", 21, NULL, 21); + check_ip_route(s_ip4, 13, "1.2.3.4", 32, NULL, -1); + + /* Route attributes */ + route = nm_setting_ip_config_get_route(s_ip4, 12); + g_assert(route); + + nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_CWND, 10); + nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1430); + nmtst_assert_route_attribute_boolean(route, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, TRUE); + nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_SRC, "7.7.7.7"); + nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "unicast"); + + route = nm_setting_ip_config_get_route(s_ip4, 13); + g_assert(route); + + nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + + g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1111:dddd::aaaa"); + g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1::cafe"); + g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 3); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "super-domain.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "redhat.com"); + g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "gnu.org"); + + /* IPv6 addresses */ + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 10); + check_ip_address(s_ip6, 0, "2:3:4:5:6:7:8:9", 64); + check_ip_address(s_ip6, 1, "abcd:1234:ffff::cdde", 64); + check_ip_address(s_ip6, 2, "1:2:3:4:5:6:7:8", 96); + check_ip_address(s_ip6, 3, "3:4:5:6:7:8:9:0", 128); + check_ip_address(s_ip6, 4, "3:4:5:6:7:8:9:14", 64); + check_ip_address(s_ip6, 5, "3:4:5:6:7:8:9:15", 64); + check_ip_address(s_ip6, 6, "3:4:5:6:7:8:9:16", 66); + check_ip_address(s_ip6, 7, "3:4:5:6:7:8:9:17", 67); + check_ip_address(s_ip6, 8, "3:4:5:6:7:8:9:18", 68); + check_ip_address(s_ip6, 9, "3:4:5:6:7:8:9:19", 69); + + /* IPv6 gateway */ + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2:3:4:5:1:2:3:4"); + + /* Routes */ + g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 7); + check_ip_route(s_ip6, 0, "d:e:f:0:1:2:3:4", 64, "f:e:d:c:1:2:3:4", -1); + check_ip_route(s_ip6, 1, "a:b:c:d::", 64, "f:e:d:c:1:2:3:4", 99); + check_ip_route(s_ip6, 2, "8:7:6:5:4:3:2:1", 128, NULL, -1); + check_ip_route(s_ip6, 3, "6:7:8:9:0:1:2:3", 126, NULL, 1); + check_ip_route(s_ip6, 4, "7:8:9:0:1:2:3:4", 125, NULL, 5); + check_ip_route(s_ip6, 5, "8:9:0:1:2:3:4:5", 124, NULL, 6); + check_ip_route(s_ip6, 6, "8:9:0:1:2:3:4:6", 123, NULL, -1); + + /* Route attributes */ + route = nm_setting_ip_config_get_route(s_ip6, 6); + g_assert(route); + nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_FROM, "abce::/63"); +} + +static void +add_one_ip_address(NMSettingIPConfig *s_ip, const char *addr, guint32 prefix) +{ + NMIPAddress * ip_addr; + gs_free_error GError *error = NULL; + + ip_addr = nm_ip_address_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6, + addr, + prefix, + &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip, ip_addr); + nm_ip_address_unref(ip_addr); +} + +static void +add_one_ip_route(NMSettingIPConfig *s_ip, + const char * dest, + const char * nh, + guint32 prefix, + gint64 metric) +{ + NMIPRoute * route; + gs_free_error GError *error = NULL; + + g_assert(prefix > 0); + route = nm_ip_route_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6, + dest, + prefix, + nh, + metric, + &error); + g_assert_no_error(error); + nm_setting_ip_config_add_route(s_ip, route); + nm_ip_route_unref(route); +} + +static void +test_write_wired_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMIPRoute * rt; + const char * mac = "99:88:77:66:55:44"; + const char * dns1 = "4.2.2.1"; + const char * dns2 = "4.2.2.2"; + const char * address1 = "192.168.0.5"; + const char * address2 = "1.2.3.4"; + const char * gw = "192.168.0.1"; + const char * route1 = "10.10.10.2"; + const char * route1_nh = "10.10.10.1"; + const char * route2 = "1.1.1.1"; + const char * route2_nh = "1.2.1.1"; + const char * route3 = "2.2.2.2"; + const char * route3_nh = NULL; + const char * route4 = "3.3.3.3"; + const char * route4_nh = NULL; + const char * dns6_1 = "1::cafe"; + const char * dns6_2 = "2::cafe"; + const char * address6_1 = "abcd::beef"; + const char * address6_2 = "dcba::beef"; + const char * route6_1 = "1:2:3:4:5:6:7:8"; + const char * route6_1_nh = "8:7:6:5:4:3:2:1"; + const char * route6_2 = "2001::1000"; + const char * route6_2_nh = "2001::1111"; + const char * route6_3 = "4:5:6:7:8:9:0:1"; + const char * route6_3_nh = "::"; + const char * route6_4 = "5:6:7:8:9:0:1:2"; + const char * route6_4_nh = "::"; + guint64 timestamp = 0x12345678L; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Work Wired", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_TIMESTAMP, + timestamp, + NULL); + + /* Wired setting */ + + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 900, NULL); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + gw, + NULL); + + /* Addresses */ + add_one_ip_address(s_ip4, address1, 24); + add_one_ip_address(s_ip4, address2, 8); + + /* Routes */ + add_one_ip_route(s_ip4, route1, route1_nh, 24, 3); + add_one_ip_route(s_ip4, route2, route2_nh, 8, 1); + add_one_ip_route(s_ip4, route3, route3_nh, 7, -1); + + rt = nm_ip_route_new(AF_INET, route4, 6, route4_nh, 4, &error); + g_assert_no_error(error); + nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(10)); + nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1492)); + nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("1.2.3.4")); + g_assert(nm_setting_ip_config_add_route(s_ip4, rt)); + nm_ip_route_unref(rt); + + /* DNS servers */ + nm_setting_ip_config_add_dns(s_ip4, dns1); + nm_setting_ip_config_add_dns(s_ip4, dns2); + + /* IP6 setting */ + + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); + + /* Addresses */ + add_one_ip_address(s_ip6, address6_1, 64); + add_one_ip_address(s_ip6, address6_2, 56); + + /* Routes */ + add_one_ip_route(s_ip6, route6_1, route6_1_nh, 64, 3); + add_one_ip_route(s_ip6, route6_2, route6_2_nh, 56, 1); + add_one_ip_route(s_ip6, route6_3, route6_3_nh, 63, 5); + add_one_ip_route(s_ip6, route6_4, route6_4_nh, 62, -1); + + /* DNS servers */ + nm_setting_ip_config_add_dns(s_ip6, dns6_1); + nm_setting_ip_config_add_dns(s_ip6, dns6_2); + + /* DNS searches */ + nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com"); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_ip6_wired_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_IP6"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection IP6"); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), + ==, + "4e80a56d-c99f-4aad-a6dd-b449bc398c57"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), + ==, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1); + check_ip_address(s_ip6, 0, "abcd:1234:ffff::cdde", 64); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "abcd:1234:ffff::cdd1"); +} + +static void +test_write_ip6_wired_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * dns = "1::cafe"; + const char * address = "abcd::beef"; + const char * gw = "dcba::beef"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Work Wired IP6", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL); + + /* IP6 setting */ + + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + gw, + NULL); + + /* Addresses */ + add_one_ip_address(s_ip6, address, 64); + + /* DNS servers */ + nm_setting_ip_config_add_dns(s_ip6, dns); + + /* DNS searches */ + nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com"); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_wired_mac_case(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + const char * mac; + char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55}; + + NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses1*"); + NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses2*"); + NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*"); + connection = + keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_MAC_Case"); + g_test_assert_expected_messages(); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection MAC Case"); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), + ==, + "4e80a56d-c99f-4aad-a6dd-b449bc398c57"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address))); +} + +static void +test_read_mac_old_format(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + const char * mac; + char expected_mac[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55}; + char expected_cloned_mac[ETH_ALEN] = {0x00, 0x16, 0xaa, 0xbb, 0xcc, 0xfe}; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_Old_Format"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, ETH_ALEN)); + + mac = nm_setting_wired_get_cloned_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_cloned_mac, ETH_ALEN)); +} + +static void +test_read_mac_ib_old_format(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingInfiniband * s_ib; + const char * mac; + guint8 expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34, + 0x45, 0x56, 0x67, 0x78, 0x89, 0x90}; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_IB_Old_Format"); + + s_ib = nm_connection_get_setting_infiniband(connection); + g_assert(s_ib); + + /* MAC address */ + mac = nm_setting_infiniband_get_mac_address(s_ib); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac))); +} + +static void +test_read_valid_wireless_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingIPConfig * s_ip4; + const char * bssid; + const guint8 expected_bssid[ETH_ALEN] = {0x00, 0x1a, 0x33, 0x44, 0x99, 0x82}; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wireless_Connection"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wireless Connection"); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), + ==, + "2f962388-e5f3-45af-a62c-ac220b8f7baa"); + g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 1226604314); + g_assert(nm_setting_connection_get_autoconnect(s_con) == FALSE); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + bssid = nm_setting_wireless_get_bssid(s_wireless); + g_assert(bssid); + g_assert(nm_utils_hwaddr_matches(bssid, -1, expected_bssid, sizeof(expected_bssid))); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); +} + +static void +test_write_wireless_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char * bssid = "aa:b9:a1:74:55:44"; + GBytes * ssid; + unsigned char tmpssid[] = {0x31, 0x33, 0x33, 0x37}; + guint64 timestamp = 0x12344433L; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Work Wireless", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_CONNECTION_TIMESTAMP, + timestamp, + NULL); + + /* Wireless setting */ + + s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wireless)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + + g_object_set(s_wireless, + NM_SETTING_WIRELESS_BSSID, + bssid, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRED_MTU, + 1000, + NULL); + + g_bytes_unref(ssid); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_string_ssid(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWireless * s_wireless; + GBytes * ssid; + const guint8 * ssid_data; + gsize ssid_len; + const char * expected_ssid = "blah blah ssid 1234"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_String_SSID"); + + s_wireless = nm_connection_get_setting_wireless(connection); + g_assert(s_wireless); + + ssid = nm_setting_wireless_get_ssid(s_wireless); + g_assert(ssid); + + ssid_data = g_bytes_get_data(ssid, &ssid_len); + g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid)); +} + +static void +test_write_string_ssid(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wireless; + NMSettingIPConfig * s_ip4; + char * tmp; + gs_free char * testfile = NULL; + GBytes * ssid; + unsigned char tmpssid[] = {65, 49, 50, 51, 32, 46, 92, 46, 36, 37, 126, 93}; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "String SSID Test", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wireless setting */ + + s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wireless)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + write_test_connection(connection, &testfile); + + /* Ensure the SSID was written out as a string */ + keyfile = keyfile_load_from_file(testfile); + tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, NULL); + g_assert(tmp); + g_assert_cmpmem(tmp, strlen(tmp), tmpssid, sizeof(tmpssid)); + g_free(tmp); + + assert_reread_and_unlink(connection, TRUE, testfile); +} + +static void +test_read_intlist_ssid(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWireless * s_wifi; + GBytes * ssid; + const guint8 * ssid_data; + gsize ssid_len; + const char * expected_ssid = "blah1234"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlist_SSID"); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + ssid = nm_setting_wireless_get_ssid(s_wifi); + g_assert(ssid != NULL); + ssid_data = g_bytes_get_data(ssid, &ssid_len); + g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid)); +} + +static void +test_write_intlist_ssid(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + gs_free char * testfile = NULL; + GBytes * ssid; + unsigned char tmpssid[] = {65, 49, 50, 51, 0, 50, 50}; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + int * intlist; + gsize len = 0, i; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Intlist SSID Test", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wireless setting */ + s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + g_assert(s_wifi); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + + /* IP4 setting */ + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + g_assert(s_ip4); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + write_test_connection(connection, &testfile); + + /* Ensure the SSID was written out as an int list */ + keyfile = keyfile_load_from_file(testfile); + + intlist = g_key_file_get_integer_list(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &len, &error); + g_assert_no_error(error); + g_assert(intlist); + g_assert_cmpint(len, ==, sizeof(tmpssid)); + + for (i = 0; i < len; i++) + g_assert_cmpint(intlist[i], ==, tmpssid[i]); + g_free(intlist); + + assert_reread_and_unlink(connection, TRUE, testfile); +} + +static void +test_read_intlike_ssid(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWireless * s_wifi; + GBytes * ssid; + const char * expected_ssid = "101"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID"); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + ssid = nm_setting_wireless_get_ssid(s_wifi); + g_assert(ssid); + g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid))); +} + +static void +test_read_intlike_ssid_2(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWireless * s_wifi; + GBytes * ssid; + const char * expected_ssid = "11;12;13;"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID_2"); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + ssid = nm_setting_wireless_get_ssid(s_wifi); + g_assert(ssid); + g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid))); +} + +static void +test_write_intlike_ssid(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + gs_free char * testfile = NULL; + GBytes * ssid; + unsigned char tmpssid[] = {49, 48, 49}; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + char * tmp; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Intlike SSID Test", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wireless setting */ + s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + g_assert(s_wifi); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + + /* IP4 setting */ + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + g_assert(s_ip4); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + write_test_connection(connection, &testfile); + + /* Ensure the SSID was written out as a plain "101" */ + keyfile = keyfile_load_from_file(testfile); + + tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error); + g_assert_no_error(error); + g_assert(tmp); + g_assert_cmpstr(tmp, ==, "101"); + g_free(tmp); + + assert_reread_and_unlink(connection, TRUE, testfile); +} + +static void +test_write_intlike_ssid_2(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + gs_free char * testfile = NULL; + GBytes * ssid; + unsigned char tmpssid[] = {49, 49, 59, 49, 50, 59, 49, 51, 59}; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + char * tmp; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Intlike SSID Test 2", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wireless setting */ + s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new()); + g_assert(s_wifi); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + + /* IP4 setting */ + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + g_assert(s_ip4); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + write_test_connection(connection, &testfile); + + /* Ensure the SSID was written out as a plain "11;12;13;" */ + keyfile = keyfile_load_from_file(testfile); + + tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error); + g_assert_no_error(error); + g_assert(tmp); + g_assert_cmpstr(tmp, ==, "11\\;12\\;13\\;"); + g_free(tmp); + + assert_reread_and_unlink(connection, TRUE, testfile); +} + +static void +test_read_bt_dun_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingBluetooth * s_bluetooth; + NMSettingSerial * s_serial; + NMSettingGsm * s_gsm; + const char * bdaddr; + const guint8 expected_bdaddr[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_BT"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect BT"); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), + ==, + "089130ab-ce28-46e4-ad77-d44869b03d19"); + + s_bluetooth = nm_connection_get_setting_bluetooth(connection); + g_assert(s_bluetooth); + bdaddr = nm_setting_bluetooth_get_bdaddr(s_bluetooth); + g_assert(bdaddr); + g_assert(nm_utils_hwaddr_matches(bdaddr, -1, expected_bdaddr, sizeof(expected_bdaddr))); + g_assert_cmpstr(nm_setting_bluetooth_get_connection_type(s_bluetooth), + ==, + NM_SETTING_BLUETOOTH_TYPE_DUN); + + s_gsm = nm_connection_get_setting_gsm(connection); + g_assert(s_gsm); + g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR"); + g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM"); + g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1"); + + s_serial = nm_connection_get_setting_serial(connection); + g_assert(s_serial); + g_assert(nm_setting_serial_get_parity(s_serial) == NM_SETTING_SERIAL_PARITY_ODD); +} + +static void +test_write_bt_dun_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingBluetooth * s_bt; + NMSettingIPConfig * s_ip4; + NMSettingGsm * s_gsm; + const char * bdaddr = "aa:b9:a1:74:55:44"; + guint64 timestamp = 0x12344433L; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "T-Mobile Funkadelic", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_CONNECTION_TIMESTAMP, + timestamp, + NULL); + + /* Bluetooth setting */ + + s_bt = NM_SETTING_BLUETOOTH(nm_setting_bluetooth_new()); + nm_connection_add_setting(connection, NM_SETTING(s_bt)); + + g_object_set(s_bt, + NM_SETTING_BLUETOOTH_BDADDR, + bdaddr, + NM_SETTING_BLUETOOTH_TYPE, + NM_SETTING_BLUETOOTH_TYPE_DUN, + NULL); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* GSM setting */ + s_gsm = NM_SETTING_GSM(nm_setting_gsm_new()); + nm_connection_add_setting(connection, NM_SETTING(s_gsm)); + + g_object_set(s_gsm, + NM_SETTING_GSM_APN, + "internet2.voicestream.com", + NM_SETTING_GSM_USERNAME, + "george.clinton", + NM_SETTING_GSM_PASSWORD, + "parliament", + NULL); + + write_test_connection_and_reread(connection, TRUE); +} + +static void +test_read_gsm_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingSerial * s_serial; + NMSettingGsm * s_gsm; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_Plain"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect"); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_GSM_SETTING_NAME); + + /* Plain GSM, so no BT setting expected */ + g_assert(nm_connection_get_setting_bluetooth(connection) == NULL); + + s_gsm = nm_connection_get_setting_gsm(connection); + g_assert(s_gsm); + g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR"); + g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM"); + g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1"); + g_assert_cmpstr(nm_setting_gsm_get_network_id(s_gsm), ==, "24005"); + g_assert_cmpstr(nm_setting_gsm_get_pin(s_gsm), ==, "2345"); + g_assert_cmpstr(nm_setting_gsm_get_device_id(s_gsm), + ==, + "da812de91eec16620b06cd0ca5cbc7ea25245222"); + g_assert_cmpstr(nm_setting_gsm_get_sim_id(s_gsm), ==, "89148000000060671234"); + g_assert_cmpstr(nm_setting_gsm_get_sim_operator_id(s_gsm), ==, "310260"); + + s_serial = nm_connection_get_setting_serial(connection); + g_assert(s_serial); + g_assert_cmpint(nm_setting_serial_get_parity(s_serial), ==, NM_SETTING_SERIAL_PARITY_ODD); +} + +static void +test_write_gsm_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingGsm * s_gsm; + guint64 timestamp = 0x12344433L; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "T-Mobile Funkadelic 2", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CONNECTION_TIMESTAMP, + timestamp, + NULL); + + /* IP4 setting */ + + s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* GSM setting */ + s_gsm = NM_SETTING_GSM(nm_setting_gsm_new()); + nm_connection_add_setting(connection, NM_SETTING(s_gsm)); + + g_object_set(s_gsm, + NM_SETTING_GSM_APN, + "internet2.voicestream.com", + NM_SETTING_GSM_USERNAME, + "george.clinton.again", + NM_SETTING_GSM_PASSWORD, + "parliament2", + NM_SETTING_GSM_PIN, + "123456", + NM_SETTING_GSM_NETWORK_ID, + "254098", + NM_SETTING_GSM_HOME_ONLY, + TRUE, + NM_SETTING_GSM_DEVICE_ID, + "da812de91eec16620b06cd0ca5cbc7ea25245222", + NM_SETTING_GSM_SIM_ID, + "89148000000060671234", + NM_SETTING_GSM_SIM_OPERATOR_ID, + "310260", + NULL); + + write_test_connection_and_reread(connection, TRUE); +} + +static void +test_read_wired_8021x_tls_blob_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSetting8021x * s_8021x; + const char * tmp; + GBytes * blob; + + NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file " + "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file " + "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Blob"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired != NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x != NULL); + + g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1); + tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0); + g_assert(g_strcmp0(tmp, "tls") == 0); + + tmp = nm_setting_802_1x_get_identity(s_8021x); + g_assert(g_strcmp0(tmp, "Bill Smith") == 0); + + tmp = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(g_strcmp0(tmp, "12345testing") == 0); + + g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_BLOB); + + /* Make sure it's not a path, since it's a blob */ + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)); + tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x); + g_test_assert_expected_messages(); + g_assert(tmp == NULL); + + /* Validate the path */ + blob = nm_setting_802_1x_get_ca_cert_blob(s_8021x); + g_assert(blob != NULL); + g_assert_cmpint(g_bytes_get_size(blob), ==, 568); + + tmp = nm_setting_802_1x_get_client_cert_path(s_8021x); + g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem"); + + tmp = nm_setting_802_1x_get_private_key_path(s_8021x); + g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem"); +} + +static void +test_read_wired_8021x_tls_bad_path_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSetting8021x * s_8021x; + const char * tmp; + char * tmp2; + + NMTST_EXPECT_NM_WARN("*does not exist*"); + connection = + keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Path_Missing"); + g_test_assert_expected_messages(); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired != NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x != NULL); + + g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1); + tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0); + g_assert(g_strcmp0(tmp, "tls") == 0); + + tmp = nm_setting_802_1x_get_identity(s_8021x); + g_assert(g_strcmp0(tmp, "Bill Smith") == 0); + + tmp = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(g_strcmp0(tmp, "12345testing") == 0); + + g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x), + ==, + NM_SETTING_802_1X_CK_SCHEME_PATH); + + tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x); + g_assert_cmpstr(tmp, ==, "/some/random/cert/path.pem"); + + tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem"); + + tmp = nm_setting_802_1x_get_client_cert_path(s_8021x); + g_assert_cmpstr(tmp, ==, tmp2); + + tmp = nm_setting_802_1x_get_private_key_path(s_8021x); + g_assert_cmpstr(tmp, ==, tmp2); + + g_free(tmp2); +} + +static void +test_read_wired_8021x_tls_old_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSetting8021x * s_8021x; + const char * tmp; + + NMTST_EXPECT_NM_WARN("keyfile: 802-1x.ca-cert: certificate or key file " + "'/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*"); + NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file " + "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file " + "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Old"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired != NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x != NULL); + + g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1); + tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0); + g_assert(g_strcmp0(tmp, "tls") == 0); + + tmp = nm_setting_802_1x_get_identity(s_8021x); + g_assert(g_strcmp0(tmp, "Bill Smith") == 0); + + tmp = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(g_strcmp0(tmp, "12345testing") == 0); + + tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x); + g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem") == 0); + + tmp = nm_setting_802_1x_get_client_cert_path(s_8021x); + g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0); + + tmp = nm_setting_802_1x_get_private_key_path(s_8021x); + g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0); +} + +static void +test_read_wired_8021x_tls_new_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + NMSetting8021x * s_8021x; + const char * tmp; + char * tmp2; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_New"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired != NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x != NULL); + + g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1); + tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0); + g_assert(g_strcmp0(tmp, "tls") == 0); + + tmp = nm_setting_802_1x_get_identity(s_8021x); + g_assert(g_strcmp0(tmp, "Bill Smith") == 0); + + tmp = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(g_strcmp0(tmp, "12345testing") == 0); + + tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-ca-cert.pem"); + tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x); + g_assert_cmpstr(tmp, ==, tmp2); + g_free(tmp2); + + tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem"); + + tmp = nm_setting_802_1x_get_client_cert_path(s_8021x); + g_assert_cmpstr(tmp, ==, tmp2); + + tmp = nm_setting_802_1x_get_private_key_path(s_8021x); + g_assert_cmpstr(tmp, ==, tmp2); + + g_free(tmp2); +} + +#define TEST_WIRED_TLS_CA_CERT TEST_KEYFILES_DIR "/test-ca-cert.pem" +#define TEST_WIRED_TLS_CLIENT_CERT TEST_KEYFILES_DIR "/test-key-and-cert.pem" +#define TEST_WIRED_TLS_PRIVKEY TEST_KEYFILES_DIR "/test-key-and-cert.pem" + +static NMConnection * +create_wired_tls_connection(NMSetting8021xCKScheme scheme) +{ + NMTST_UUID_INIT(uuid); + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip4; + NMSetting * s_wired; + NMSetting8021x * s_8021x; + gboolean success; + gs_free_error GError *error = NULL; + + connection = nm_simple_connection_new(); + g_assert(connection != NULL); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Wired Really Secure TLS", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_assert(s_ip4); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + /* Wired setting */ + s_wired = nm_setting_wired_new(); + g_assert(s_wired); + nm_connection_add_setting(connection, s_wired); + + /* 802.1x setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, TEST_WIRED_TLS_CA_CERT, scheme, NULL, &error); + g_assert_no_error(error); + g_assert(success); + + success = nm_setting_802_1x_set_client_cert(s_8021x, + TEST_WIRED_TLS_CLIENT_CERT, + scheme, + NULL, + &error); + g_assert_no_error(error); + g_assert(success); + + success = nm_setting_802_1x_set_private_key(s_8021x, + TEST_WIRED_TLS_PRIVKEY, + "test1", + scheme, + NULL, + &error); + g_assert_no_error(error); + g_assert(success); + + return connection; +} + +static char * +get_path(const char *file, gboolean relative) +{ + return relative ? g_path_get_basename(file) : g_strdup(file); +} + +static void +test_write_wired_8021x_tls_connection_path(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + char * tmp, *tmp2; + gboolean success; + gs_free char * testfile = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gboolean relative = FALSE; + gboolean reread_same = FALSE; + + connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert(connection != NULL); + + write_test_connection_reread(connection, &testfile, &reread, &reread_same); + nmtst_assert_connection_verifies_without_normalization(reread); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CA_CERT); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY); + assert_reread_same(connection, reread); + g_clear_object(&reread); + + /* Read the connection back in and compare it to the one we just wrote out */ + reread = keyfile_read_connection_from_file(testfile); + + success = nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT); + if (!reread) { + g_warning("Written and re-read connection weren't the same"); + g_assert(success); + } + + /* Ensure the cert and key values are properly written out */ + keyfile = keyfile_load_from_file(testfile); + + /* Depending on whether this test is being run from 'make check' or + * 'make distcheck' we might be using relative paths (check) or + * absolute ones (distcheck). + */ + tmp2 = g_path_get_dirname(testfile); + if (g_strcmp0(tmp2, TEST_KEYFILES_DIR) == 0) + relative = TRUE; + g_free(tmp2); + + /* CA cert */ + tmp = g_key_file_get_string(keyfile, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CA_CERT, + NULL); + tmp2 = get_path(TEST_WIRED_TLS_CA_CERT, relative); + g_assert_cmpstr(tmp, ==, tmp2); + g_free(tmp2); + g_free(tmp); + + /* Client cert */ + tmp = g_key_file_get_string(keyfile, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT, + NULL); + tmp2 = get_path(TEST_WIRED_TLS_CLIENT_CERT, relative); + g_assert_cmpstr(tmp, ==, tmp2); + g_free(tmp2); + g_free(tmp); + + /* Private key */ + tmp = g_key_file_get_string(keyfile, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY, + NULL); + tmp2 = get_path(TEST_WIRED_TLS_PRIVKEY, relative); + g_assert_cmpstr(tmp, ==, tmp2); + g_free(tmp2); + g_free(tmp); + + unlink(testfile); +} + +static void +test_write_wired_8021x_tls_connection_blob(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection * s_con; + NMSetting8021x * s_8021x; + gs_free char * testfile = NULL; + char * new_ca_cert; + char * new_client_cert; + char * new_priv_key; + const char * uuid; + gboolean reread_same = FALSE; + GBytes * password_raw; + +#define PASSWORD_RAW "password-raw\0test" + + connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_BLOB); + g_assert(connection != NULL); + + s_8021x = nm_connection_get_setting_802_1x(connection); + g_assert(s_8021x); + + password_raw = g_bytes_new(PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW)); + g_object_set(s_8021x, NM_SETTING_802_1X_PASSWORD_RAW, password_raw, NULL); + g_bytes_unref(password_raw); + + write_test_connection_reread(connection, &testfile, &reread, &reread_same); + nmtst_assert_connection_verifies_without_normalization(reread); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CA_CERT); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT); + _setting_copy_property_gbytes(connection, + reread, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY); + assert_reread_same(connection, reread); + g_clear_object(&reread); + + /* Check that the new certs got written out */ + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + uuid = nm_setting_connection_get_uuid(s_con); + g_assert(uuid); + + new_ca_cert = g_strdup_printf("%s/%s-ca-cert.pem", TEST_SCRATCH_DIR, uuid); + g_assert(new_ca_cert); + g_assert(g_file_test(new_ca_cert, G_FILE_TEST_EXISTS)); + + new_client_cert = g_strdup_printf("%s/%s-client-cert.pem", TEST_SCRATCH_DIR, uuid); + g_assert(new_client_cert); + g_assert(g_file_test(new_client_cert, G_FILE_TEST_EXISTS)); + + new_priv_key = g_strdup_printf("%s/%s-private-key.pem", TEST_SCRATCH_DIR, uuid); + g_assert(new_priv_key); + g_assert(g_file_test(new_priv_key, G_FILE_TEST_EXISTS)); + + /* Read the connection back in and compare it to the one we just wrote out */ + reread = keyfile_read_connection_from_file(testfile); + + /* Ensure the re-read connection's certificates use the path scheme */ + s_8021x = nm_connection_get_setting_802_1x(reread); + g_assert(s_8021x); + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + + password_raw = nm_setting_802_1x_get_password_raw(s_8021x); + g_assert(password_raw); + g_assert(nm_utils_gbytes_equal_mem(password_raw, PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW))); + + unlink(testfile); + + /* Clean up written certs */ + unlink(new_ca_cert); + g_free(new_ca_cert); + + unlink(new_client_cert); + g_free(new_client_cert); + + unlink(new_priv_key); + g_free(new_priv_key); +} + +static void +test_read_dcb_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_dcb_connection"); +} + +static void +test_read_infiniband_connection(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingInfiniband * s_ib; + const char * mac; + guint8 expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34, + 0x45, 0x56, 0x67, 0x78, 0x89, 0x90}; + const char *expected_id = "Test InfiniBand Connection"; + const char *expected_uuid = "4e80a56d-c99f-4aad-a6dd-b449bc398c57"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_InfiniBand_Connection"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid); + + s_ib = nm_connection_get_setting_infiniband(connection); + g_assert(s_ib); + + mac = nm_setting_infiniband_get_mac_address(s_ib); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac))); +} + +static void +test_write_infiniband_connection(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingInfiniband * s_ib; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + const char *mac = "99:88:77:66:55:44:ab:bc:cd:de:ef:f0:0a:1b:2c:3d:4e:5f:6f:ba"; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Work InfiniBand", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_INFINIBAND_SETTING_NAME, + NULL); + + /* InfiniBand setting */ + s_ib = (NMSettingInfiniband *) nm_setting_infiniband_new(); + g_assert(s_ib); + nm_connection_add_setting(connection, NM_SETTING(s_ib)); + + g_object_set(s_ib, + NM_SETTING_INFINIBAND_MAC_ADDRESS, + mac, + NM_SETTING_INFINIBAND_MTU, + 900, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "datagram", + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_assert(s_ip4); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_assert(s_ip6); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_bridge_main(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingBridge * s_bridge; + const char * expected_id = "Test Bridge Main"; + const char * expected_uuid = "8f061643-fe41-4d4c-a8d9-097d26e2ad3a"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Main"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "br0"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + g_assert(s_ip4); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); + + s_bridge = nm_connection_get_setting_bridge(connection); + g_assert(s_bridge); + g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2); + g_assert_cmpuint(nm_setting_bridge_get_stp(s_bridge), ==, TRUE); + g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744); + g_assert_cmpuint(nm_setting_bridge_get_hello_time(s_bridge), ==, 7); + g_assert_cmpuint(nm_setting_bridge_get_max_age(s_bridge), ==, 39); + g_assert_cmpuint(nm_setting_bridge_get_ageing_time(s_bridge), ==, 235352); + g_assert_cmpuint(nm_setting_bridge_get_multicast_snooping(s_bridge), ==, FALSE); +} + +static void +test_write_bridge_main(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingBridge * s_bridge; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bridge Main", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "br0", + NULL); + + /* Bridge setting */ + s_bridge = (NMSettingBridge *) nm_setting_bridge_new(); + g_assert(s_bridge); + nm_connection_add_setting(connection, NM_SETTING(s_bridge)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_assert(s_ip4); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NULL); + + add_one_ip_address(s_ip4, "1.2.3.4", 24); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_assert(s_ip6); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_bridge_component(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingBridgePort * s_port; + NMSettingWired * s_wired; + const char * mac; + guint8 expected_mac[ETH_ALEN] = {0x00, 0x22, 0x15, 0x59, 0x62, 0x97}; + const char * expected_id = "Test Bridge Component"; + const char * expected_uuid = "d7b4f96c-c45e-4298-bef8-f48574f8c1c0"; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Component"); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid); + g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0"); + g_assert(nm_setting_connection_is_slave_type(s_con, NM_SETTING_BRIDGE_SETTING_NAME)); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac))); + + s_port = nm_connection_get_setting_bridge_port(connection); + g_assert(s_port); + g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port)); + g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28); + g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100); +} + +static void +test_write_bridge_component(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingBridgePort * s_port; + NMSettingWired * s_wired; + const char * mac = "99:88:77:66:55:44"; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Bridge Component", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, + "br0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + g_assert(s_wired); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 1300, NULL); + + /* BridgePort setting */ + s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new(); + g_assert(s_port); + nm_connection_add_setting(connection, NM_SETTING(s_port)); + + g_object_set(s_port, + NM_SETTING_BRIDGE_PORT_PRIORITY, + 3, + NM_SETTING_BRIDGE_PORT_PATH_COST, + 99, + NULL); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_new_wired_group_name(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWired * s_wired; + const char * mac; + guint8 expected_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wired_Group_Name"); + + s_wired = nm_connection_get_setting_wired(connection); + g_assert(s_wired); + g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400); + + mac = nm_setting_wired_get_mac_address(s_wired); + g_assert(mac); + g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac))); +} + +static void +test_write_new_wired_group_name(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + nm_auto_unref_keyfile GKeyFile *kf = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + gs_free char * testfile = NULL; + gs_free_error GError *error = NULL; + char * s; + int mtu; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wired New Group Name", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + g_assert(s_wired); + g_object_set(s_wired, NM_SETTING_WIRED_MTU, 1400, NULL); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + write_test_connection(connection, &testfile); + + assert_reread(connection, TRUE, testfile); + + /* Look at the keyfile itself to ensure we wrote out the new group names and type */ + kf = keyfile_load_from_file(testfile); + + s = g_key_file_get_string(kf, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE, + &error); + g_assert_no_error(error); + g_assert_cmpstr(s, ==, "ethernet"); + g_free(s); + + mtu = g_key_file_get_integer(kf, "ethernet", NM_SETTING_WIRED_MTU, &error); + g_assert_no_error(error); + g_assert_cmpint(mtu, ==, 1400); + + unlink(testfile); +} + +static void +test_read_new_wireless_group_names(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + GBytes * ssid; + const char * expected_ssid = "foobar"; + + connection = + keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wireless_Group_Names"); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + ssid = nm_setting_wireless_get_ssid(s_wifi); + g_assert(ssid); + g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid))); + + g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, NM_SETTING_WIRELESS_MODE_INFRA); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "s3cu4e passphrase"); +} + +static void +test_write_new_wireless_group_names(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + nm_auto_unref_keyfile GKeyFile *kf = NULL; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity * s_wsec; + GBytes * ssid; + unsigned char tmpssid[] = {0x31, 0x33, 0x33, 0x37}; + const char * expected_psk = "asdfasdfasdfa12315"; + gs_free char * testfile = NULL; + gs_free_error GError *error = NULL; + char * s; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write New Wireless Group Names", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wi-Fi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_INFRA, + NULL); + g_bytes_unref(ssid); + + /* Wi-Fi security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + expected_psk, + NULL); + + write_test_connection(connection, &testfile); + + assert_reread(connection, TRUE, testfile); + + /* Look at the keyfile itself to ensure we wrote out the new group names and type */ + kf = keyfile_load_from_file(testfile); + + s = g_key_file_get_string(kf, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE, + &error); + g_assert_no_error(error); + g_assert_cmpstr(s, ==, "wifi"); + g_free(s); + + s = g_key_file_get_string(kf, "wifi", NM_SETTING_WIRELESS_MODE, &error); + g_assert_no_error(error); + g_assert_cmpstr(s, ==, NM_SETTING_WIRELESS_MODE_INFRA); + g_free(s); + + s = g_key_file_get_string(kf, "wifi-security", NM_SETTING_WIRELESS_SECURITY_PSK, &error); + g_assert_no_error(error); + g_assert_cmpstr(s, ==, expected_psk); + g_free(s); + + unlink(testfile); +} + +static void +test_read_missing_vlan_setting(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingVlan * s_vlan; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Setting"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 0); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS); +} + +static void +test_read_missing_vlan_flags(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingVlan * s_vlan; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Flags"); + + s_vlan = nm_connection_get_setting_vlan(connection); + g_assert(s_vlan); + + g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 444); + g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "em1"); + g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS); +} + +static void +test_read_missing_id_uuid(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free char * expected_uuid = NULL; + const char * FILENAME = TEST_KEYFILES_DIR "/Test_Missing_ID_UUID"; + + expected_uuid = _nm_utils_uuid_generate_from_strings("keyfile", FILENAME, NULL); + + connection = keyfile_read_connection_from_file(FILENAME); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "Test_Missing_ID_UUID"); + g_assert_cmpstr(nm_connection_get_uuid(connection), ==, expected_uuid); +} + +static void +test_read_minimal(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *con_archetype = NULL; + NMSettingConnection * s_con; + + con_archetype = nmtst_create_minimal_connection("Test_minimal_x", + "a15bd68f-c32b-40b8-8d27-49e472a85919", + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + nmtst_connection_normalize(con_archetype); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_1"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_2"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); +} + +static void +test_read_minimal_slave(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *con_archetype = NULL; + NMSettingConnection * s_con; + + con_archetype = nmtst_create_minimal_connection("Test_minimal_slave_x", + "a56b4ca5-7075-43d4-82c7-5d0cb15f7654", + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "br0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + "bridge", + NULL); + nmtst_connection_normalize(con_archetype); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_1"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_2"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_3"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_4"); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + nm_connection_get_id(connection), + NM_SETTING_CONNECTION_UUID, + nm_connection_get_uuid(connection), + NULL); + nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE); + g_clear_object(&connection); +} + +static void +test_read_enum_property(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingIPConfig * s_ip6; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Enum_Property"); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpint(nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)), + ==, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR); +} + +static void +test_write_enum_property(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip6; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Enum Property", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* IP6 setting */ + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NULL); + + nmtst_connection_normalize(connection); + + write_test_connection_and_reread(connection, FALSE); +} + +static void +test_read_flags_property(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingGsm * s_gsm; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Flags_Property"); + + s_gsm = nm_connection_get_setting_gsm(connection); + g_assert(s_gsm); + g_assert_cmpint(nm_setting_gsm_get_password_flags(s_gsm), + ==, + NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED); +} + +static void +test_write_flags_property(void) +{ + NMTST_UUID_INIT(uuid); + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSetting * s_gsm; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Flags Property", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_GSM_SETTING_NAME, + NULL); + + /* GSM setting */ + s_gsm = nm_setting_gsm_new(); + nm_connection_add_setting(connection, s_gsm); + g_object_set(s_gsm, + NM_SETTING_GSM_APN, + "myapn", + NM_SETTING_GSM_USERNAME, + "adfasdfasdf", + NM_SETTING_GSM_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + NULL); + + nmtst_connection_normalize(connection); + + write_test_connection_and_reread(connection, FALSE); +} + +/*****************************************************************************/ + +static void +test_read_tc_config(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingTCConfig * s_tc; + NMTCQdisc * qdisc1, *qdisc2; + NMTCAction * action1, *action2; + NMTCTfilter * tfilter1, *tfilter2; + + connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_TC_Config"); + + s_tc = nm_connection_get_setting_tc_config(connection); + g_assert(s_tc); + + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 2); + + qdisc1 = nm_setting_tc_config_get_qdisc(s_tc, 0); + g_assert(qdisc1); + g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc1), "fq_codel") == 0); + g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1234 << 16, 0x0000)); + g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT); + + qdisc2 = nm_setting_tc_config_get_qdisc(s_tc, 1); + g_assert(qdisc2); + g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc2), "ingress") == 0); + g_assert(nm_tc_qdisc_get_handle(qdisc2) == TC_H_MAKE(TC_H_INGRESS, 0)); + g_assert(nm_tc_qdisc_get_parent(qdisc2) == TC_H_INGRESS); + + g_assert(nm_setting_tc_config_get_num_tfilters(s_tc) == 2); + + tfilter1 = nm_setting_tc_config_get_tfilter(s_tc, 0); + g_assert(tfilter1); + g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter1), "matchall") == 0); + g_assert(nm_tc_tfilter_get_handle(tfilter1) == TC_H_UNSPEC); + g_assert(nm_tc_tfilter_get_parent(tfilter1) == TC_H_MAKE(0x1234 << 16, 0x0000)); + + action1 = nm_tc_tfilter_get_action(tfilter1); + g_assert(action1); + g_assert(g_strcmp0(nm_tc_action_get_kind(action1), "drop") == 0); + + tfilter2 = nm_setting_tc_config_get_tfilter(s_tc, 1); + g_assert(tfilter2); + g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter2), "matchall") == 0); + g_assert(nm_tc_tfilter_get_handle(tfilter2) == TC_H_UNSPEC); + g_assert(nm_tc_tfilter_get_parent(tfilter2) == TC_H_MAKE(TC_H_INGRESS, 0)); + + action2 = nm_tc_tfilter_get_action(tfilter2); + g_assert(action2); + g_assert(g_strcmp0(nm_tc_action_get_kind(action2), "simple") == 0); + g_assert( + g_strcmp0(g_variant_get_bytestring(nm_tc_action_get_attribute(action2, "sdata")), "Hello") + == 0); +} + +static void +test_write_tc_config(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSetting * s_tc; + NMTCQdisc * qdisc1, *qdisc2; + NMTCTfilter * tfilter1, *tfilter2; + NMTCAction * action; + GError * error = NULL; + + connection = + nmtst_create_minimal_connection("Test TC", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_tc = nm_setting_tc_config_new(); + + qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error); + nmtst_assert_success(qdisc1, error); + nm_tc_qdisc_set_handle(qdisc1, TC_H_MAKE(0x1234 << 16, 0x0000)); + nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc1); + + qdisc2 = nm_tc_qdisc_new("ingress", TC_H_INGRESS, &error); + nmtst_assert_success(qdisc2, error); + nm_tc_qdisc_set_handle(qdisc2, TC_H_MAKE(TC_H_INGRESS, 0)); + nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc2); + + tfilter1 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234 << 16, 0x0000), &error); + nmtst_assert_success(tfilter1, error); + action = nm_tc_action_new("drop", &error); + nmtst_assert_success(action, error); + nm_tc_tfilter_set_action(tfilter1, action); + nm_tc_action_unref(action); + nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter1); + nm_tc_tfilter_unref(tfilter1); + + tfilter2 = nm_tc_tfilter_new("matchall", TC_H_MAKE(TC_H_INGRESS, 0), &error); + nmtst_assert_success(tfilter2, error); + action = nm_tc_action_new("simple", &error); + nmtst_assert_success(action, error); + nm_tc_action_set_attribute(action, "sdata", g_variant_new_bytestring("Hello")); + nm_tc_tfilter_set_action(tfilter2, action); + nm_tc_action_unref(action); + nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter2); + nm_tc_tfilter_unref(tfilter2); + + nm_connection_add_setting(connection, s_tc); + + nmtst_connection_normalize(connection); + write_test_connection_and_reread(connection, FALSE); + + nm_tc_qdisc_unref(qdisc1); + nm_tc_qdisc_unref(qdisc2); +} + +/*****************************************************************************/ + +static void +_escape_filename(gboolean with_extension, const char *filename, gboolean would_be_ignored) +{ + gs_free char *esc = NULL; + + g_assert(filename && filename[0]); + + if (!!would_be_ignored != !!nm_keyfile_utils_ignore_filename(filename, with_extension)) { + if (would_be_ignored) + g_error("We expect filename \"%s\" to be ignored, but it isn't", filename); + else + g_error("We expect filename \"%s\" not to be ignored, but it is", filename); + } + + esc = nm_keyfile_utils_create_filename(filename, with_extension); + g_assert(esc && esc[0]); + g_assert(!strchr(esc, '/')); + + if (nm_keyfile_utils_ignore_filename(esc, with_extension)) + g_error("Escaping filename \"%s\" yielded \"%s\", but this is ignored", filename, esc); +} + +static void +test_nm_keyfile_plugin_utils_escape_filename(void) +{ + _escape_filename(FALSE, "ab", FALSE); + _escape_filename(FALSE, ".vim-file.swp", TRUE); + _escape_filename(FALSE, ".vim-file.Swp", TRUE); + _escape_filename(FALSE, ".vim-file.SWP", TRUE); + _escape_filename(FALSE, ".vim-file.swpx", TRUE); + _escape_filename(FALSE, ".vim-file.Swpx", TRUE); + _escape_filename(FALSE, ".vim-file.SWPX", TRUE); + _escape_filename(FALSE, ".pem-file.pem", TRUE); + _escape_filename(FALSE, ".pem-file.Pem", TRUE); + _escape_filename(FALSE, ".pem-file.PEM", TRUE); + _escape_filename(FALSE, ".pem-file.der", TRUE); + _escape_filename(FALSE, ".pem-file.Der", TRUE); + _escape_filename(FALSE, ".mkstemp.ABCEDF", TRUE); + _escape_filename(FALSE, ".mkstemp.abcdef", TRUE); + _escape_filename(FALSE, ".mkstemp.123456", TRUE); + _escape_filename(FALSE, ".mkstemp.A23456", TRUE); + _escape_filename(FALSE, ".#emacs-locking", TRUE); + _escape_filename(FALSE, "file-with-tilde~", TRUE); + _escape_filename(FALSE, ".file-with-dot", TRUE); + _escape_filename(FALSE, "/some/path/with/trailing/slash/", TRUE); + _escape_filename(FALSE, "/some/path/without/trailing/slash", FALSE); + + _escape_filename(TRUE, "lala", TRUE); +} + +/*****************************************************************************/ + +static void +_assert_keyfile_nmmeta(const char *dirname, + const char *uuid, + const char *loaded_path, + gboolean allow_relative, + const char *exp_full_filename, + const char *exp_uuid, + const char *exp_symlink_target, + const char *exp_loaded_path) +{ + gs_free char *full_filename = NULL; + gs_free char *symlink_target = NULL; + gs_free char *uuid2 = NULL; + gs_free char *loaded_path2 = NULL; + gs_free char *dirname3 = NULL; + gs_free char *filename3 = NULL; + gs_free char *uuid3 = NULL; + gs_free char *loaded_path3 = NULL; + gboolean success; + gs_free char *filename = NULL; + + g_assert(dirname && dirname[0] == '/'); + g_assert(exp_full_filename && exp_full_filename[0]); + g_assert(!exp_loaded_path || exp_loaded_path[0] == '/'); + + filename = g_path_get_basename(exp_full_filename); + + full_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE); + g_assert_cmpstr(full_filename, ==, full_filename); + nm_clear_g_free(&full_filename); + + g_assert_cmpint( + nms_keyfile_nmmeta_write(dirname, uuid, loaded_path, allow_relative, NULL, &full_filename), + ==, + 0); + g_assert_cmpstr(full_filename, ==, exp_full_filename); + nm_clear_g_free(&full_filename); + + if (exp_symlink_target) + g_assert(g_file_test(exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK)); + else + g_assert(!g_file_test(exp_full_filename, G_FILE_TEST_EXISTS)); + symlink_target = g_file_read_link(exp_full_filename, NULL); + g_assert_cmpstr(symlink_target, ==, exp_symlink_target); + + success = nms_keyfile_nmmeta_read(dirname, + filename, + &full_filename, + &uuid2, + &loaded_path2, + NULL, + NULL); + g_assert_cmpint(!!exp_uuid, ==, success); + if (success) + g_assert_cmpstr(full_filename, ==, exp_full_filename); + else + g_assert_cmpstr(full_filename, ==, NULL); + nm_clear_g_free(&full_filename); + g_assert_cmpstr(uuid2, ==, exp_uuid); + g_assert_cmpstr(loaded_path2, ==, exp_loaded_path); + + success = nms_keyfile_nmmeta_read_from_file(exp_full_filename, + &dirname3, + &filename3, + &uuid3, + &loaded_path3, + NULL); + g_assert_cmpint(!!exp_uuid, ==, success); + if (success) { + g_assert_cmpstr(dirname3, ==, dirname); + g_assert_cmpstr(filename3, ==, filename); + } else { + g_assert_cmpstr(dirname3, ==, NULL); + g_assert_cmpstr(filename3, ==, NULL); + } + g_assert_cmpstr(uuid3, ==, exp_uuid); + g_assert_cmpstr(loaded_path3, ==, exp_loaded_path); +} + +static void +test_nmmeta(void) +{ + const char * uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959"; + gs_free char *filename = g_strdup_printf("%s%s", uuid, NM_KEYFILE_PATH_SUFFIX_NMMETA); + gs_free char *full_filename = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename); + const char * loaded_path0 = NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL; + const char * loaded_path1 = "/some/where/but/not/scratch/dir"; + const char * filename2 = "foo1"; + gs_free char *loaded_path2 = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename2); + + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, FALSE, full_filename, NULL, NULL, NULL); + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, TRUE, full_filename, NULL, NULL, NULL); + + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path0, + FALSE, + full_filename, + uuid, + loaded_path0, + loaded_path0); + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path0, + TRUE, + full_filename, + uuid, + loaded_path0, + loaded_path0); + + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path1, + FALSE, + full_filename, + uuid, + loaded_path1, + loaded_path1); + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path1, + TRUE, + full_filename, + uuid, + loaded_path1, + loaded_path1); + + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path2, + FALSE, + full_filename, + uuid, + loaded_path2, + loaded_path2); + _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, + uuid, + loaded_path2, + TRUE, + full_filename, + uuid, + filename2, + loaded_path2); + + (void) unlink(full_filename); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + int errsv; + + _nm_utils_set_testing(NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK); + + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + if (g_mkdir_with_parents(TEST_SCRATCH_DIR, 0755) != 0) { + errsv = errno; + g_error("failure to create test directory \"%s\": %s", + TEST_SCRATCH_DIR, + nm_strerror_native(errsv)); + } + + /* The tests */ + g_test_add_func("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection); + g_test_add_func("/keyfile/test_write_wired_connection", test_write_wired_connection); + + g_test_add_func("/keyfile/test_read_ip6_wired_connection", test_read_ip6_wired_connection); + g_test_add_func("/keyfile/test_write_ip6_wired_connection", test_write_ip6_wired_connection); + + g_test_add_func("/keyfile/test_read_wired_mac_case", test_read_wired_mac_case); + g_test_add_func("/keyfile/test_read_mac_old_format", test_read_mac_old_format); + g_test_add_func("/keyfile/test_read_mac_ib_old_format", test_read_mac_ib_old_format); + + g_test_add_func("/keyfile/test_read_valid_wireless_connection", + test_read_valid_wireless_connection); + g_test_add_func("/keyfile/test_write_wireless_connection", test_write_wireless_connection); + + g_test_add_func("/keyfile/test_read_string_ssid", test_read_string_ssid); + g_test_add_func("/keyfile/test_write_string_ssid", test_write_string_ssid); + + g_test_add_func("/keyfile/test_read_intlist_ssid", test_read_intlist_ssid); + g_test_add_func("/keyfile/test_write_intlist_ssid", test_write_intlist_ssid); + + g_test_add_func("/keyfile/test_read_intlike_ssid", test_read_intlike_ssid); + g_test_add_func("/keyfile/test_write_intlike_ssid", test_write_intlike_ssid); + + g_test_add_func("/keyfile/test_read_intlike_ssid_2", test_read_intlike_ssid_2); + g_test_add_func("/keyfile/test_write_intlike_ssid_2", test_write_intlike_ssid_2); + + g_test_add_func("/keyfile/test_read_bt_dun_connection", test_read_bt_dun_connection); + g_test_add_func("/keyfile/test_write_bt_dun_connection", test_write_bt_dun_connection); + + g_test_add_func("/keyfile/test_read_gsm_connection", test_read_gsm_connection); + g_test_add_func("/keyfile/test_write_gsm_connection", test_write_gsm_connection); + + g_test_add_func("/keyfile/test_read_wired_8021x_tls_blob_connection", + test_read_wired_8021x_tls_blob_connection); + g_test_add_func("/keyfile/test_read_wired_8021x_tls_bad_path_connection", + test_read_wired_8021x_tls_bad_path_connection); + + g_test_add_func("/keyfile/test_read_wired_8021x_tls_old_connection", + test_read_wired_8021x_tls_old_connection); + g_test_add_func("/keyfile/test_read_wired_8021x_tls_new_connection", + test_read_wired_8021x_tls_new_connection); + g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_path", + test_write_wired_8021x_tls_connection_path); + g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_blob", + test_write_wired_8021x_tls_connection_blob); + + g_test_add_func("/keyfile/test_read_dcb_connection", test_read_dcb_connection); + + g_test_add_func("/keyfile/test_read_infiniband_connection", test_read_infiniband_connection); + g_test_add_func("/keyfile/test_write_infiniband_connection", test_write_infiniband_connection); + + g_test_add_func("/keyfile/test_read_bridge_main", test_read_bridge_main); + g_test_add_func("/keyfile/test_write_bridge_main", test_write_bridge_main); + g_test_add_func("/keyfile/test_read_bridge_component", test_read_bridge_component); + g_test_add_func("/keyfile/test_write_bridge_component", test_write_bridge_component); + + g_test_add_func("/keyfile/test_read_new_wired_group_name", test_read_new_wired_group_name); + g_test_add_func("/keyfile/test_write_new_wired_group_name", test_write_new_wired_group_name); + g_test_add_func("/keyfile/test_read_new_wireless_group_names", + test_read_new_wireless_group_names); + g_test_add_func("/keyfile/test_write_new_wireless_group_names", + test_write_new_wireless_group_names); + + g_test_add_func("/keyfile/test_read_missing_vlan_setting", test_read_missing_vlan_setting); + g_test_add_func("/keyfile/test_read_missing_vlan_flags", test_read_missing_vlan_flags); + g_test_add_func("/keyfile/test_read_missing_id_uuid", test_read_missing_id_uuid); + + g_test_add_func("/keyfile/test_read_minimal", test_read_minimal); + g_test_add_func("/keyfile/test_read_minimal_slave", test_read_minimal_slave); + + g_test_add_func("/keyfile/test_read_enum_property", test_read_enum_property); + g_test_add_func("/keyfile/test_write_enum_property", test_write_enum_property); + g_test_add_func("/keyfile/test_read_flags_property", test_read_flags_property); + g_test_add_func("/keyfile/test_write_flags_property", test_write_flags_property); + + g_test_add_func("/keyfile/test_read_tc_config", test_read_tc_config); + g_test_add_func("/keyfile/test_write_tc_config", test_write_tc_config); + + g_test_add_func("/keyfile/test_nm_keyfile_plugin_utils_escape_filename", + test_nm_keyfile_plugin_utils_escape_filename); + + g_test_add_func("/keyfile/test_nmmeta", test_nmmeta); + + return g_test_run(); +} diff --git a/src/core/settings/plugins/meson.build b/src/core/settings/plugins/meson.build new file mode 100644 index 0000000..d4338a3 --- /dev/null +++ b/src/core/settings/plugins/meson.build @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if enable_ifcfg_rh + subdir('ifcfg-rh') +endif + +if enable_ifupdown + subdir('ifupdown') +endif + +if enable_tests + subdir('keyfile/tests') +endif diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c new file mode 100644 index 0000000..1754bff --- /dev/null +++ b/src/core/supplicant/nm-supplicant-config.c @@ -0,0 +1,1698 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-config.h" + +#include + +#include "nm-glib-aux/nm-str-buf.h" +#include "nm-core-internal.h" +#include "nm-supplicant-settings-verify.h" +#include "nm-setting.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "NetworkManagerUtils.h" +#include "nm-utils.h" +#include "nm-setting-ip4-config.h" + +typedef struct { + char * value; + guint32 len; + NMSupplOptType type; +} ConfigOption; + +/*****************************************************************************/ + +typedef struct { + GHashTable * config; + GHashTable * blobs; + NMSupplCapMask capabilities; + guint32 ap_scan; + bool fast_required : 1; + bool dispose_has_run : 1; + bool ap_isolation : 1; +} NMSupplicantConfigPrivate; + +struct _NMSupplicantConfig { + GObject parent; + NMSupplicantConfigPrivate _priv; +}; + +struct _NMSupplicantConfigClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_CONFIG_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSupplicantConfig, NM_IS_SUPPLICANT_CONFIG) + +/*****************************************************************************/ + +static gboolean +_get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type) +{ + return NM_SUPPL_CAP_MASK_GET(priv->capabilities, type) == NM_TERNARY_TRUE; +} + +NMSupplicantConfig * +nm_supplicant_config_new(NMSupplCapMask capabilities) +{ + NMSupplicantConfigPrivate *priv; + NMSupplicantConfig * self; + + self = g_object_new(NM_TYPE_SUPPLICANT_CONFIG, NULL); + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + priv->capabilities = capabilities; + + return self; +} + +static void +config_option_free(ConfigOption *opt) +{ + g_free(opt->value); + g_slice_free(ConfigOption, opt); +} + +static void +nm_supplicant_config_init(NMSupplicantConfig *self) +{ + NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + priv->config = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) config_option_free); + + priv->ap_scan = 1; + priv->dispose_has_run = FALSE; +} + +static gboolean +nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self, + const char * key, + const char * value, + gint32 len, + NMSupplOptType opt_type, + const char * display_value, + GError ** error) +{ + NMSupplicantConfigPrivate *priv; + ConfigOption * old_opt; + ConfigOption * opt; + NMSupplOptType type; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(value != NULL, FALSE); + nm_assert(!error || !*error); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + if (len < 0) + len = strlen(value); + + if (opt_type != NM_SUPPL_OPT_TYPE_INVALID) + type = opt_type; + else { + type = nm_supplicant_settings_verify_setting(key, value, len); + if (type == NM_SUPPL_OPT_TYPE_INVALID) { + gs_free char *str_free = NULL; + const char * str; + + str = nm_utils_buf_utf8safe_escape(value, + len, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, + &str_free); + + str = nm_strquote_a(255, str); + + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' and/or value %s invalid", + key, + display_value ?: str); + return FALSE; + } + } + + old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key); + if (old_opt) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' already configured", + key); + return FALSE; + } + + opt = g_slice_new0(ConfigOption); + opt->value = g_malloc(len + 1); + memcpy(opt->value, value, len); + opt->value[len] = '\0'; + + opt->len = len; + opt->type = type; + + { + char buf[255]; + memset(&buf[0], 0, sizeof(buf)); + memcpy(&buf[0], opt->value, opt->len > 254 ? 254 : opt->len); + nm_log_info(LOGD_SUPPLICANT, + "Config: added '%s' value '%s'", + key, + display_value ?: &buf[0]); + } + + g_hash_table_insert(priv->config, g_strdup(key), opt); + + return TRUE; +} + +static gboolean +nm_supplicant_config_add_option(NMSupplicantConfig *self, + const char * key, + const char * value, + gint32 len, + const char * display_value, + GError ** error) +{ + return nm_supplicant_config_add_option_with_type(self, + key, + value, + len, + NM_SUPPL_OPT_TYPE_INVALID, + display_value, + error); +} + +static gboolean +nm_supplicant_config_add_blob(NMSupplicantConfig *self, + const char * key, + GBytes * value, + const char * blobid, + GError ** error) +{ + NMSupplicantConfigPrivate *priv; + ConfigOption * old_opt; + ConfigOption * opt; + NMSupplOptType type; + const guint8 * data; + gsize data_len; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(value != NULL, FALSE); + g_return_val_if_fail(blobid != NULL, FALSE); + + data = g_bytes_get_data(value, &data_len); + g_return_val_if_fail(data_len > 0, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + type = nm_supplicant_settings_verify_setting(key, (const char *) data, data_len); + if (type == NM_SUPPL_OPT_TYPE_INVALID) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' and/or its contained value is invalid", + key); + return FALSE; + } + + old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key); + if (old_opt) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' already configured", + key); + return FALSE; + } + + opt = g_slice_new0(ConfigOption); + opt->value = g_strdup_printf("blob://%s", blobid); + opt->len = strlen(opt->value); + opt->type = type; + + nm_log_info(LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value); + + g_hash_table_insert(priv->config, g_strdup(key), opt); + if (!priv->blobs) { + priv->blobs = + g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref); + } + g_hash_table_insert(priv->blobs, g_strdup(blobid), g_bytes_ref(value)); + + return TRUE; +} + +static gboolean +nm_supplicant_config_add_blob_for_connection(NMSupplicantConfig *self, + GBytes * field, + const char * name, + const char * con_uid, + GError ** error) +{ + if (field && g_bytes_get_size(field)) { + gs_free char *uid = NULL; + char * p; + + uid = g_strdup_printf("%s-%s", con_uid, name); + for (p = uid; *p; p++) { + if (*p == '/') + *p = '-'; + } + if (!nm_supplicant_config_add_blob(self, name, field, uid, error)) + return FALSE; + } + return TRUE; +} + +static void +nm_supplicant_config_finalize(GObject *object) +{ + NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(object); + + g_hash_table_destroy(priv->config); + nm_clear_pointer(&priv->blobs, g_hash_table_destroy); + + G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object); +} + +static void +nm_supplicant_config_class_init(NMSupplicantConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = nm_supplicant_config_finalize; +} + +guint32 +nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), 1); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->ap_scan; +} + +gboolean +nm_supplicant_config_fast_required(NMSupplicantConfig *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->fast_required; +} + +GVariant * +nm_supplicant_config_to_variant(NMSupplicantConfig *self) +{ + NMSupplicantConfigPrivate *priv; + GVariantBuilder builder; + GHashTableIter iter; + ConfigOption * option; + const char * key; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_hash_table_iter_init(&iter, priv->config); + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &option)) { + switch (option->type) { + case NM_SUPPL_OPT_TYPE_INT: + g_variant_builder_add(&builder, "{sv}", key, g_variant_new_int32(atoi(option->value))); + break; + case NM_SUPPL_OPT_TYPE_BYTES: + case NM_SUPPL_OPT_TYPE_UTF8: + g_variant_builder_add( + &builder, + "{sv}", + key, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, option->value, option->len, 1)); + break; + case NM_SUPPL_OPT_TYPE_KEYWORD: + case NM_SUPPL_OPT_TYPE_STRING: + g_variant_builder_add(&builder, "{sv}", key, g_variant_new_string(option->value)); + break; + default: + break; + } + } + + return g_variant_builder_end(&builder); +} + +GHashTable * +nm_supplicant_config_get_blobs(NMSupplicantConfig *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->blobs; +} + +static const char * +wifi_freqs_to_string(gboolean bg_band) +{ + static const char *str_2ghz = NULL; + static const char *str_5ghz = NULL; + const char ** f_p; + const char * f; + + f_p = bg_band ? &str_2ghz : &str_5ghz; + +again: + f = g_atomic_pointer_get(f_p); + + if (G_UNLIKELY(!f)) { + nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(400, FALSE); + const guint * freqs; + int i; + + freqs = bg_band ? nm_utils_wifi_2ghz_freqs() : nm_utils_wifi_5ghz_freqs(); + for (i = 0; freqs[i]; i++) { + if (i > 0) + nm_str_buf_append_c(&strbuf, ' '); + nm_str_buf_append_printf(&strbuf, "%u", freqs[i]); + } + + f = g_strdup(nm_str_buf_get_str(&strbuf)); + + if (!g_atomic_pointer_compare_and_exchange(f_p, NULL, f)) { + g_free((char *) f); + goto again; + } + } + + return f; +} + +gboolean +nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self, + NMSettingMacsec * setting, + GError ** error) +{ + const char *value; + char buf[32]; + int port; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(setting != NULL, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (!nm_supplicant_config_add_option(self, "macsec_policy", "1", -1, NULL, error)) + return FALSE; + + value = nm_setting_macsec_get_encrypt(setting) ? "0" : "1"; + if (!nm_supplicant_config_add_option(self, "macsec_integ_only", value, -1, NULL, error)) + return FALSE; + + port = nm_setting_macsec_get_port(setting); + if (port > 0 && port < 65534) { + snprintf(buf, sizeof(buf), "%d", port); + if (!nm_supplicant_config_add_option(self, "macsec_port", buf, -1, NULL, error)) + return FALSE; + } + + if (nm_setting_macsec_get_mode(setting) == NM_SETTING_MACSEC_MODE_PSK) { + guint8 buffer_cak[NM_SETTING_MACSEC_MKA_CAK_LENGTH / 2]; + guint8 buffer_ckn[NM_SETTING_MACSEC_MKA_CKN_LENGTH / 2]; + + if (!nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error)) + return FALSE; + + value = nm_setting_macsec_get_mka_cak(setting); + if (!value || !nm_utils_hexstr2bin_buf(value, FALSE, FALSE, NULL, buffer_cak)) { + g_set_error_literal(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + value ? "invalid MKA CAK" : "missing MKA CAK"); + return FALSE; + } + if (!nm_supplicant_config_add_option(self, + "mka_cak", + (char *) buffer_cak, + sizeof(buffer_cak), + "", + error)) + return FALSE; + + value = nm_setting_macsec_get_mka_ckn(setting); + if (!value || !nm_utils_hexstr2bin_buf(value, FALSE, FALSE, NULL, buffer_ckn)) { + g_set_error_literal(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + value ? "invalid MKA CKN" : "missing MKA CKN"); + return FALSE; + } + if (!nm_supplicant_config_add_option(self, + "mka_ckn", + (char *) buffer_ckn, + sizeof(buffer_ckn), + value, + error)) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self, + NMSettingWireless * setting, + guint32 fixed_freq, + GError ** error) +{ + NMSupplicantConfigPrivate *priv; + gboolean is_adhoc, is_ap, is_mesh; + const char * mode, *band; + guint32 channel; + GBytes * ssid; + const char * bssid; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(setting != NULL, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + mode = nm_setting_wireless_get_mode(setting); + is_adhoc = nm_streq0(mode, "adhoc"); + is_ap = nm_streq0(mode, "ap"); + is_mesh = nm_streq0(mode, "mesh"); + if (is_adhoc || is_ap) + priv->ap_scan = 2; + else + priv->ap_scan = 1; + + ssid = nm_setting_wireless_get_ssid(setting); + if (!nm_supplicant_config_add_option(self, + "ssid", + (char *) g_bytes_get_data(ssid, NULL), + g_bytes_get_size(ssid), + NULL, + error)) + return FALSE; + + if (is_adhoc) { + if (!nm_supplicant_config_add_option(self, "mode", "1", -1, NULL, error)) + return FALSE; + } + + if (is_ap) { + if (!nm_supplicant_config_add_option(self, "mode", "2", -1, NULL, error)) + return FALSE; + + if (nm_setting_wireless_get_hidden(setting) + && !nm_supplicant_config_add_option(self, + "ignore_broadcast_ssid", + "1", + -1, + NULL, + error)) + return FALSE; + } + + if (is_mesh) { + if (!nm_supplicant_config_add_option(self, "mode", "5", -1, NULL, error)) + return FALSE; + } + + if ((is_adhoc || is_ap || is_mesh) && fixed_freq) { + gs_free char *str_freq = NULL; + + str_freq = g_strdup_printf("%u", fixed_freq); + if (!nm_supplicant_config_add_option(self, "frequency", str_freq, -1, NULL, error)) + return FALSE; + } + + /* Except for Ad-Hoc, Hotspot and Mesh, request that the driver probe for the + * specific SSID we want to associate with. + */ + if (!(is_adhoc || is_ap || is_mesh)) { + if (!nm_supplicant_config_add_option(self, "scan_ssid", "1", -1, NULL, error)) + return FALSE; + } + + bssid = nm_setting_wireless_get_bssid(setting); + if (bssid) { + if (!nm_supplicant_config_add_option(self, "bssid", bssid, strlen(bssid), NULL, error)) + return FALSE; + } + + band = nm_setting_wireless_get_band(setting); + channel = nm_setting_wireless_get_channel(setting); + if (band) { + if (channel) { + guint32 freq; + gs_free char *str_freq = NULL; + + freq = nm_utils_wifi_channel_to_freq(channel, band); + str_freq = g_strdup_printf("%u", freq); + if (!nm_supplicant_config_add_option(self, "freq_list", str_freq, -1, NULL, error)) + return FALSE; + } else { + const char *freqs = NULL; + + if (nm_streq(band, "a")) + freqs = wifi_freqs_to_string(FALSE); + else if (nm_streq(band, "bg")) + freqs = wifi_freqs_to_string(TRUE); + + if (freqs + && !nm_supplicant_config_add_option(self, + "freq_list", + freqs, + strlen(freqs), + NULL, + error)) + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connection, GError **error) +{ + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + const char * bgscan; + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + + /* Don't scan when a shared connection (either AP or Ad-Hoc) is active; + * it will disrupt connected clients. + */ + if (NM_IN_STRSET(nm_setting_wireless_get_mode(s_wifi), + NM_SETTING_WIRELESS_MODE_AP, + NM_SETTING_WIRELESS_MODE_ADHOC)) + return TRUE; + + /* Don't scan when the connection is locked to a specific AP, since + * intra-ESS roaming (which requires periodic scanning) isn't being + * used due to the specific AP lock. (bgo #513820) + */ + if (nm_setting_wireless_get_bssid(s_wifi)) + return TRUE; + + /* Default to a very long bgscan interval when signal is OK on the assumption + * that either (a) there aren't multiple APs and we don't need roaming, or + * (b) since EAP/802.1x isn't used and thus there are fewer steps to fail + * during a roam, we can wait longer before scanning for roam candidates. + */ + bgscan = "simple:30:-70:86400"; + + /* If using WPA Enterprise, Dynamic WEP or we have seen more than one AP use + * a shorter bgscan interval on the assumption that this is a multi-AP ESS + * in which we want more reliable roaming between APs. Thus trigger scans + * when the signal is still somewhat OK so we have an up-to-date roam + * candidate list when the signal gets bad. + */ + if (nm_setting_wireless_get_num_seen_bssids(s_wifi) > 1 + || ((s_wsec = nm_connection_get_setting_wireless_security(connection)) + && NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec), + "ieee8021x", + "wpa-eap", + "wpa-eap-suite-b-192"))) + bgscan = "simple:30:-65:300"; + + return nm_supplicant_config_add_option(self, "bgscan", bgscan, -1, FALSE, error); +} + +static gboolean +add_string_val(NMSupplicantConfig *self, + const char * field, + const char * name, + gboolean ucase, + const char * display_value, + GError ** error) +{ + if (field) { + gs_free char *value = NULL; + + if (ucase) { + value = g_ascii_strup(field, -1); + field = value; + } + return nm_supplicant_config_add_option(self, + name, + field, + strlen(field), + display_value, + error); + } + return TRUE; +} + +#define ADD_STRING_LIST_VAL(self, \ + setting, \ + setting_name, \ + field, \ + field_plural, \ + name, \ + separator, \ + ucase, \ + display_value, \ + error) \ + ({ \ + typeof(*(setting)) *_setting = (setting); \ + gboolean _success = TRUE; \ + \ + if (nm_setting_##setting_name##_get_num_##field_plural(_setting)) { \ + const char _separator = (separator); \ + GString * _str = g_string_new(NULL); \ + guint _k, _n; \ + \ + _n = nm_setting_##setting_name##_get_num_##field_plural(_setting); \ + for (_k = 0; _k < _n; _k++) { \ + const char *item = nm_setting_##setting_name##_get_##field(_setting, _k); \ + \ + if (!_str->len) { \ + g_string_append(_str, item); \ + } else { \ + g_string_append_c(_str, _separator); \ + g_string_append(_str, item); \ + } \ + } \ + if ((ucase)) \ + g_string_ascii_up(_str); \ + if (_str->len) { \ + if (!nm_supplicant_config_add_option((self), \ + (name), \ + _str->str, \ + -1, \ + (display_value), \ + (error))) \ + _success = FALSE; \ + } \ + g_string_free(_str, TRUE); \ + } \ + _success; \ + }) + +static void +wep128_passphrase_hash(const char *input, gsize input_len, guint8 *digest /* 13 bytes */) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5]; + guint8 data[64]; + int i; + + nm_assert(input); + nm_assert(input_len); + nm_assert(digest); + + /* Get at least 64 bytes by repeating the passphrase into the buffer */ + for (i = 0; i < sizeof(data); i++) + data[i] = input[i % input_len]; + + sum = g_checksum_new(G_CHECKSUM_MD5); + g_checksum_update(sum, data, sizeof(data)); + nm_utils_checksum_get_digest(sum, md5); + + /* WEP104 keys are 13 bytes in length (26 hex characters) */ + memcpy(digest, md5, 13); +} + +static gboolean +add_wep_key(NMSupplicantConfig *self, + const char * key, + const char * name, + NMWepKeyType wep_type, + GError ** error) +{ + gsize key_len; + + if (!key || (key_len = strlen(key)) == 0) + return TRUE; + + if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) { + if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY)) + wep_type = NM_WEP_KEY_TYPE_KEY; + else if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE)) + wep_type = NM_WEP_KEY_TYPE_PASSPHRASE; + } + + if ((wep_type == NM_WEP_KEY_TYPE_UNKNOWN) || (wep_type == NM_WEP_KEY_TYPE_KEY)) { + if ((key_len == 10) || (key_len == 26)) { + guint8 buffer[26 / 2]; + + if (!nm_utils_hexstr2bin_full(key, + FALSE, + FALSE, + FALSE, + NULL, + key_len / 2, + buffer, + sizeof(buffer), + NULL)) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "cannot add wep-key %s to supplicant config because key is not hex", + name); + return FALSE; + } + if (!nm_supplicant_config_add_option(self, + name, + (char *) buffer, + key_len / 2, + "", + error)) + return FALSE; + } else if ((key_len == 5) || (key_len == 13)) { + if (!nm_supplicant_config_add_option(self, name, key, key_len, "", error)) + return FALSE; + } else { + g_set_error( + error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add wep-key %s to supplicant config because key-length %u is invalid", + name, + (guint) key_len); + return FALSE; + } + } else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) { + guint8 digest[13]; + + wep128_passphrase_hash(key, key_len, digest); + if (!nm_supplicant_config_add_option(self, + name, + (const char *) digest, + sizeof(digest), + "", + error)) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig * self, + NMSettingWirelessSecurity * setting, + NMSetting8021x * setting_8021x, + const char * con_uuid, + guint32 mtu, + NMSettingWirelessSecurityPmf pmf, + NMSettingWirelessSecurityFils fils, + GError ** error) +{ + NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + nm_auto_free_gstring GString *key_mgmt_conf = NULL; + const char * key_mgmt, *auth_alg; + const char * psk; + gboolean set_pmf; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(setting != NULL, FALSE); + g_return_val_if_fail(con_uuid != NULL, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* Check if we actually support FILS */ + if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_FILS)) { + if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) { + g_set_error_literal(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Supplicant does not support FILS"); + return FALSE; + } else if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL) + fils = NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE; + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(setting); + key_mgmt_conf = g_string_new(key_mgmt); + if (nm_streq(key_mgmt, "wpa-psk")) { + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) + g_string_append(key_mgmt_conf, " wpa-psk-sha256"); + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) + g_string_append(key_mgmt_conf, " ft-psk"); + } else if (nm_streq(key_mgmt, "wpa-eap")) { + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) { + g_string_append(key_mgmt_conf, " wpa-eap-sha256"); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SUITEB192) + && pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) + g_string_append(key_mgmt_conf, " wpa-eap-suite-b-192"); + } + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) + g_string_append(key_mgmt_conf, " ft-eap"); + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT) + && _get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384)) + g_string_append(key_mgmt_conf, " ft-eap-sha384"); + switch (fils) { + case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED: + g_string_truncate(key_mgmt_conf, 0); + if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) + g_string_assign(key_mgmt_conf, "fils-sha256 fils-sha384"); + /* fall-through */ + case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL: + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) + g_string_append(key_mgmt_conf, " fils-sha256 fils-sha384"); + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF) + && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) + g_string_append(key_mgmt_conf, " ft-fils-sha256"); + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF) + && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT) + && _get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384)) + g_string_append(key_mgmt_conf, " ft-fils-sha384"); + break; + default: + break; + } + } else if (nm_streq(key_mgmt, "sae")) { + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) + g_string_append(key_mgmt_conf, " ft-sae"); + } else if (nm_streq(key_mgmt, "wpa-eap-suite-b-192")) { + pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED; + if (!nm_supplicant_config_add_option(self, "pairwise", "GCMP-256", -1, NULL, error) + || !nm_supplicant_config_add_option(self, "group", "GCMP-256", -1, NULL, error)) + return FALSE; + } + + if (!add_string_val(self, key_mgmt_conf->str, "key_mgmt", TRUE, NULL, error)) + return FALSE; + + auth_alg = nm_setting_wireless_security_get_auth_alg(setting); + if (!add_string_val(self, auth_alg, "auth_alg", TRUE, NULL, error)) + return FALSE; + + psk = nm_setting_wireless_security_get_psk(setting); + if (psk) { + size_t psk_len = strlen(psk); + + if (psk_len >= 8 && psk_len <= 63) { + /* Use NM_SUPPL_OPT_TYPE_STRING here so that it gets pushed to the + * supplicant as a string, and therefore gets quoted, + * and therefore the supplicant will interpret it as a + * passphrase and not a hex key. + */ + if (!nm_supplicant_config_add_option_with_type(self, + "psk", + psk, + -1, + NM_SUPPL_OPT_TYPE_STRING, + "", + error)) + return FALSE; + } else if (nm_streq(key_mgmt, "sae")) { + /* If the SAE password doesn't comply with WPA-PSK limitation, + * we need to call it "sae_password" instead of "psk". + */ + if (!nm_supplicant_config_add_option_with_type(self, + "sae_password", + psk, + -1, + NM_SUPPL_OPT_TYPE_STRING, + "", + error)) + return FALSE; + } else if (psk_len == 64) { + guint8 buffer[32]; + + /* Hex PSK */ + if (!nm_utils_hexstr2bin_buf(psk, FALSE, FALSE, NULL, buffer)) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add psk to supplicant config due to invalid hex"); + return FALSE; + } + if (!nm_supplicant_config_add_option(self, + "psk", + (char *) buffer, + sizeof(buffer), + "", + error)) + return FALSE; + } else { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add psk to supplicant config due to invalid PSK length %u (not " + "between 8 and 63 characters)", + (guint) psk_len); + return FALSE; + } + } + + /* Don't try to enable PMF on non-WPA/SAE/OWE networks */ + if (!NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192", "wpa-psk", "sae", "owe")) + pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE; + + /* Check if we actually support PMF */ + set_pmf = TRUE; + if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) { + if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) { + g_set_error_literal(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Supplicant does not support PMF"); + return FALSE; + } + set_pmf = FALSE; + } + + /* Only WPA-specific things when using WPA */ + if (NM_IN_STRSET(key_mgmt, "wpa-psk", "wpa-eap", "sae", "owe")) { + if (!ADD_STRING_LIST_VAL(self, + setting, + wireless_security, + proto, + protos, + "proto", + ' ', + TRUE, + NULL, + error)) + return FALSE; + if (!ADD_STRING_LIST_VAL(self, + setting, + wireless_security, + pairwise, + pairwise, + "pairwise", + ' ', + TRUE, + NULL, + error)) + return FALSE; + if (!ADD_STRING_LIST_VAL(self, + setting, + wireless_security, + group, + groups, + "group", + ' ', + TRUE, + NULL, + error)) + return FALSE; + + if (set_pmf + && NM_IN_SET(pmf, + NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE, + NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) { + if (!nm_supplicant_config_add_option( + self, + "ieee80211w", + pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE ? "0" : "2", + -1, + NULL, + error)) + return FALSE; + } + } + + /* WEP keys if required */ + if (nm_streq(key_mgmt, "none")) { + NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type(setting); + const char * wep0 = nm_setting_wireless_security_get_wep_key(setting, 0); + const char * wep1 = nm_setting_wireless_security_get_wep_key(setting, 1); + const char * wep2 = nm_setting_wireless_security_get_wep_key(setting, 2); + const char * wep3 = nm_setting_wireless_security_get_wep_key(setting, 3); + + if (!add_wep_key(self, wep0, "wep_key0", wep_type, error)) + return FALSE; + if (!add_wep_key(self, wep1, "wep_key1", wep_type, error)) + return FALSE; + if (!add_wep_key(self, wep2, "wep_key2", wep_type, error)) + return FALSE; + if (!add_wep_key(self, wep3, "wep_key3", wep_type, error)) + return FALSE; + + if (wep0 || wep1 || wep2 || wep3) { + gs_free char *value = NULL; + + value = g_strdup_printf("%d", nm_setting_wireless_security_get_wep_tx_keyidx(setting)); + if (!nm_supplicant_config_add_option(self, "wep_tx_keyidx", value, -1, NULL, error)) + return FALSE; + } + } + + if (nm_streq0(auth_alg, "leap")) { + /* LEAP */ + if (nm_streq(key_mgmt, "ieee8021x")) { + const char *tmp; + + tmp = nm_setting_wireless_security_get_leap_username(setting); + if (!add_string_val(self, tmp, "identity", FALSE, NULL, error)) + return FALSE; + + tmp = nm_setting_wireless_security_get_leap_password(setting); + if (!add_string_val(self, tmp, "password", FALSE, "", error)) + return FALSE; + + if (!add_string_val(self, "leap", "eap", TRUE, NULL, error)) + return FALSE; + } else { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Invalid key-mgmt \"%s\" for leap", + key_mgmt); + return FALSE; + } + } else { + /* 802.1x for Dynamic WEP and WPA-Enterprise */ + if (NM_IN_STRSET(key_mgmt, "ieee8021x", "wpa-eap", "wpa-eap-suite-b-192")) { + if (!setting_8021x) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Cannot set key-mgmt %s with missing 8021x setting", + key_mgmt); + return FALSE; + } + if (!nm_supplicant_config_add_setting_8021x(self, + setting_8021x, + con_uuid, + mtu, + FALSE, + error)) + return FALSE; + } + + if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) { + /* When using WPA-Enterprise, we want to use Proactive Key Caching (also + * called Opportunistic Key Caching) to avoid full EAP exchanges when + * roaming between access points in the same mobility group. + */ + if (!nm_supplicant_config_add_option(self, + "proactive_key_caching", + "1", + -1, + NULL, + error)) + return FALSE; + } + } + + return TRUE; +} + +static gboolean +add_pkcs11_uri_with_pin(NMSupplicantConfig * self, + const char * name, + const char * uri, + const char * pin, + const NMSettingSecretFlags pin_flags, + GError ** error) +{ + gs_strfreev char **split = NULL; + gs_free char * tmp = NULL; + gs_free char * tmp_log = NULL; + gs_free char * pin_qattr = NULL; + char * escaped = NULL; + + if (uri == NULL) + return TRUE; + + /* We ignore the attributes -- RFC 7512 suggests that some of them + * might be unsafe and we want to be on the safe side. Also, we're + * installing our attributes, so this makes things a bit easier for us. */ + split = g_strsplit(uri, "&", 2); + if (split[1]) + nm_log_info(LOGD_SUPPLICANT, "URI attributes ignored"); + + /* Fill in the PIN if required. */ + if (pin) { + escaped = g_uri_escape_string(pin, NULL, TRUE); + pin_qattr = g_strdup_printf("pin-value=%s", escaped); + g_free(escaped); + } else if (!(pin_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + /* Include an empty PIN to indicate the login is still needed. + * Probably a token that has a PIN path and the actual PIN will + * be entered using a protected path. */ + pin_qattr = g_strdup("pin-value="); + } + + tmp = g_strdup_printf("%s%s%s", split[0], (pin_qattr ? "?" : ""), (pin_qattr ?: "")); + + tmp_log = g_strdup_printf("%s%s%s", + split[0], + (pin_qattr ? "?" : ""), + (pin_qattr ? "pin-value=" : "")); + + return add_string_val(self, tmp, name, FALSE, tmp_log, error); +} + +gboolean +nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self, + NMSetting8021x * setting, + const char * con_uuid, + guint32 mtu, + gboolean wired, + GError ** error) +{ + NMSupplicantConfigPrivate *priv; + char * tmp; + const char * peapver, *value, *path; + gboolean added; + GString * phase1, *phase2; + GBytes * bytes; + gboolean fast = FALSE; + guint32 i, num_eap; + gboolean fast_provisoning_allowed = FALSE; + const char * ca_path_override = NULL, *ca_cert_override = NULL; + guint32 frag, hdrs; + gs_free char * frag_str = NULL; + NMSetting8021xAuthFlags phase1_auth_flags; + nm_auto_free_gstring GString *eap_str = NULL; + + g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE); + g_return_val_if_fail(setting != NULL, FALSE); + g_return_val_if_fail(con_uuid != NULL, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); + + value = nm_setting_802_1x_get_password(setting); + if (value) { + if (!add_string_val(self, value, "password", FALSE, "", error)) + return FALSE; + } else { + bytes = nm_setting_802_1x_get_password_raw(setting); + if (bytes) { + if (!nm_supplicant_config_add_option(self, + "password", + (const char *) g_bytes_get_data(bytes, NULL), + g_bytes_get_size(bytes), + "", + error)) + return FALSE; + } + } + value = nm_setting_802_1x_get_pin(setting); + if (!add_string_val(self, value, "pin", FALSE, "", error)) + return FALSE; + + if (wired) { + if (!add_string_val(self, "IEEE8021X", "key_mgmt", FALSE, NULL, error)) + return FALSE; + /* Wired 802.1x must always use eapol_flags=0 */ + if (!add_string_val(self, "0", "eapol_flags", FALSE, NULL, error)) + return FALSE; + priv->ap_scan = 0; + } + + /* Build the "eap" option string while we check for EAP methods needing + * special handling: PEAP + GTC, FAST, external */ + eap_str = g_string_new(NULL); + num_eap = nm_setting_802_1x_get_num_eap_methods(setting); + for (i = 0; i < num_eap; i++) { + const char *method = nm_setting_802_1x_get_eap_method(setting, i); + + if (nm_streq(method, "fast")) { + fast = TRUE; + priv->fast_required = TRUE; + } + + if (nm_streq(method, "external")) { + if (num_eap == 1) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "Connection settings managed externally to NM, connection" + " cannot be used with wpa_supplicant"); + return FALSE; + } + continue; + } + + if (eap_str->len) + g_string_append_c(eap_str, ' '); + g_string_append(eap_str, method); + } + + g_string_ascii_up(eap_str); + if (eap_str->len + && !nm_supplicant_config_add_option(self, "eap", eap_str->str, -1, NULL, error)) + return FALSE; + + /* Adjust the fragment size according to MTU, but do not set it higher than 1280-14 + * for better compatibility */ + hdrs = 14; /* EAPOL + EAP-TLS */ + frag = 1280 - hdrs; + if (mtu > hdrs) + frag = CLAMP(mtu - hdrs, 100, frag); + frag_str = g_strdup_printf("%u", frag); + + if (!nm_supplicant_config_add_option(self, "fragment_size", frag_str, -1, NULL, error)) + return FALSE; + + phase1 = g_string_new(NULL); + peapver = nm_setting_802_1x_get_phase1_peapver(setting); + if (peapver) { + if (nm_streq(peapver, "0")) + g_string_append(phase1, "peapver=0"); + else if (nm_streq(peapver, "1")) + g_string_append(phase1, "peapver=1"); + } + + if (nm_setting_802_1x_get_phase1_peaplabel(setting)) { + if (phase1->len) + g_string_append_c(phase1, ' '); + g_string_append_printf(phase1, + "peaplabel=%s", + nm_setting_802_1x_get_phase1_peaplabel(setting)); + } + + value = nm_setting_802_1x_get_phase1_fast_provisioning(setting); + if (value) { + if (phase1->len) + g_string_append_c(phase1, ' '); + g_string_append_printf(phase1, "fast_provisioning=%s", value); + + if (!nm_streq(value, "0")) + fast_provisoning_allowed = TRUE; + } + + phase1_auth_flags = nm_setting_802_1x_get_phase1_auth_flags(setting); + if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE)) + g_string_append_printf(phase1, "%stls_disable_tlsv1_0=1", (phase1->len ? " " : "")); + if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE)) + g_string_append_printf(phase1, "%stls_disable_tlsv1_1=1", (phase1->len ? " " : "")); + if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_DISABLE)) + g_string_append_printf(phase1, "%stls_disable_tlsv1_2=1", (phase1->len ? " " : "")); + + if (phase1->len) { + if (!add_string_val(self, phase1->str, "phase1", FALSE, NULL, error)) { + g_string_free(phase1, TRUE); + return FALSE; + } + } + g_string_free(phase1, TRUE); + + phase2 = g_string_new(NULL); + if (nm_setting_802_1x_get_phase2_auth(setting) && !fast_provisoning_allowed) { + tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_auth(setting), -1); + g_string_append_printf(phase2, "auth=%s", tmp); + g_free(tmp); + } + + if (nm_setting_802_1x_get_phase2_autheap(setting)) { + if (phase2->len) + g_string_append_c(phase2, ' '); + tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_autheap(setting), -1); + g_string_append_printf(phase2, "autheap=%s", tmp); + g_free(tmp); + } + + if (phase2->len) { + if (!add_string_val(self, phase2->str, "phase2", FALSE, NULL, error)) { + g_string_free(phase2, TRUE); + return FALSE; + } + } + g_string_free(phase2, TRUE); + + /* PAC file */ + path = nm_setting_802_1x_get_pac_file(setting); + if (path) { + if (!add_string_val(self, path, "pac_file", FALSE, NULL, error)) + return FALSE; + } else { + /* PAC file is not specified. + * If provisioning is allowed, use an blob format. + */ + if (fast_provisoning_allowed) { + gs_free char *blob_name = NULL; + + blob_name = g_strdup_printf("blob://pac-blob-%s", con_uuid); + if (!add_string_val(self, blob_name, "pac_file", FALSE, NULL, error)) + return FALSE; + } else { + /* This is only error for EAP-FAST; don't disturb other methods. */ + if (fast) { + g_set_error(error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "EAP-FAST error: no PAC file provided and " + "automatic PAC provisioning is disabled"); + return FALSE; + } + } + } + + /* If user wants to use system CA certs, either populate ca_path (if the path + * is a directory) or ca_cert (the path is a file name) */ + if (nm_setting_802_1x_get_system_ca_certs(setting)) { + if (g_file_test(SYSTEM_CA_PATH, G_FILE_TEST_IS_DIR)) + ca_path_override = SYSTEM_CA_PATH; + else + ca_cert_override = SYSTEM_CA_PATH; + } + + /* CA path */ + path = nm_setting_802_1x_get_ca_path(setting); + path = ca_path_override ?: path; + if (path) { + if (!add_string_val(self, path, "ca_path", FALSE, NULL, error)) + return FALSE; + } + + /* Phase2 CA path */ + path = nm_setting_802_1x_get_phase2_ca_path(setting); + path = ca_path_override ?: path; + if (path) { + if (!add_string_val(self, path, "ca_path2", FALSE, NULL, error)) + return FALSE; + } + + /* CA certificate */ + if (ca_cert_override) { + if (!add_string_val(self, ca_cert_override, "ca_cert", FALSE, NULL, error)) + return FALSE; + } else { + switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_ca_cert_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "ca_cert", + con_uuid, + error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_ca_cert_path(setting); + if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin(self, + "ca_cert", + nm_setting_802_1x_get_ca_cert_uri(setting), + nm_setting_802_1x_get_ca_cert_password(setting), + nm_setting_802_1x_get_ca_cert_password_flags(setting), + error)) { + return FALSE; + } + break; + default: + break; + } + } + + /* Phase 2 CA certificate */ + if (ca_cert_override) { + if (!add_string_val(self, ca_cert_override, "ca_cert2", FALSE, NULL, error)) + return FALSE; + } else { + switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "ca_cert2", + con_uuid, + error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_ca_cert_path(setting); + if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin( + self, + "ca_cert2", + nm_setting_802_1x_get_phase2_ca_cert_uri(setting), + nm_setting_802_1x_get_phase2_ca_cert_password(setting), + nm_setting_802_1x_get_phase2_ca_cert_password_flags(setting), + error)) { + return FALSE; + } + break; + default: + break; + } + } + + /* Subject match */ + value = nm_setting_802_1x_get_subject_match(setting); + if (!add_string_val(self, value, "subject_match", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_phase2_subject_match(setting); + if (!add_string_val(self, value, "subject_match2", FALSE, NULL, error)) + return FALSE; + + /* altSubjectName match */ + if (!ADD_STRING_LIST_VAL(self, + setting, + 802_1x, + altsubject_match, + altsubject_matches, + "altsubject_match", + ';', + FALSE, + NULL, + error)) + return FALSE; + if (!ADD_STRING_LIST_VAL(self, + setting, + 802_1x, + phase2_altsubject_match, + phase2_altsubject_matches, + "altsubject_match2", + ';', + FALSE, + NULL, + error)) + return FALSE; + + /* Domain suffix match */ + value = nm_setting_802_1x_get_domain_suffix_match(setting); + if (!add_string_val(self, value, "domain_suffix_match", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_phase2_domain_suffix_match(setting); + if (!add_string_val(self, value, "domain_suffix_match2", FALSE, NULL, error)) + return FALSE; + + /* domain match */ + value = nm_setting_802_1x_get_domain_match(setting); + if (!add_string_val(self, value, "domain_match", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_phase2_domain_match(setting); + if (!add_string_val(self, value, "domain_match2", FALSE, NULL, error)) + return FALSE; + + /* Private key */ + added = FALSE; + switch (nm_setting_802_1x_get_private_key_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_private_key_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "private_key", + con_uuid, + error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_private_key_path(setting); + if (!add_string_val(self, path, "private_key", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin(self, + "private_key", + nm_setting_802_1x_get_private_key_uri(setting), + nm_setting_802_1x_get_private_key_password(setting), + nm_setting_802_1x_get_private_key_password_flags(setting), + error)) { + return FALSE; + } + added = TRUE; + break; + default: + break; + } + + if (added) { + NMSetting8021xCKFormat format; + NMSetting8021xCKScheme scheme; + + format = nm_setting_802_1x_get_private_key_format(setting); + scheme = nm_setting_802_1x_get_private_key_scheme(setting); + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH + || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the private key password for PKCS#12 blobs and + * all path schemes, since in both of these cases the private key + * isn't decrypted at all. + */ + value = nm_setting_802_1x_get_private_key_password(setting); + if (!add_string_val(self, value, "private_key_passwd", FALSE, "", error)) + return FALSE; + } + + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the client cert if the private key is not PKCS#12, as + * wpa_supplicant configuration directs us to do. + */ + switch (nm_setting_802_1x_get_client_cert_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_client_cert_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "client_cert", + con_uuid, + error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_client_cert_path(setting); + if (!add_string_val(self, path, "client_cert", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin( + self, + "client_cert", + nm_setting_802_1x_get_client_cert_uri(setting), + nm_setting_802_1x_get_client_cert_password(setting), + nm_setting_802_1x_get_client_cert_password_flags(setting), + error)) { + return FALSE; + } + break; + default: + break; + } + } + } + + /* Phase 2 private key */ + added = FALSE; + switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "private_key2", + con_uuid, + error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_private_key_path(setting); + if (!add_string_val(self, path, "private_key2", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin( + self, + "private_key2", + nm_setting_802_1x_get_phase2_private_key_uri(setting), + nm_setting_802_1x_get_phase2_private_key_password(setting), + nm_setting_802_1x_get_phase2_private_key_password_flags(setting), + error)) { + return FALSE; + } + added = TRUE; + break; + default: + break; + } + + if (added) { + NMSetting8021xCKFormat format; + NMSetting8021xCKScheme scheme; + + format = nm_setting_802_1x_get_phase2_private_key_format(setting); + scheme = nm_setting_802_1x_get_phase2_private_key_scheme(setting); + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH + || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the private key password for PKCS#12 blobs and + * all path schemes, since in both of these cases the private key + * isn't decrypted at all. + */ + value = nm_setting_802_1x_get_phase2_private_key_password(setting); + if (!add_string_val(self, value, "private_key2_passwd", FALSE, "", error)) + return FALSE; + } + + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the client cert if the private key is not PKCS#12, as + * wpa_supplicant configuration directs us to do. + */ + switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting); + if (!nm_supplicant_config_add_blob_for_connection(self, + bytes, + "client_cert2", + con_uuid, + error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_client_cert_path(setting); + if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (!add_pkcs11_uri_with_pin( + self, + "client_cert2", + nm_setting_802_1x_get_phase2_client_cert_uri(setting), + nm_setting_802_1x_get_phase2_client_cert_password(setting), + nm_setting_802_1x_get_phase2_client_cert_password_flags(setting), + error)) { + return FALSE; + } + break; + default: + break; + } + } + } + + value = nm_setting_802_1x_get_identity(setting); + if (!add_string_val(self, value, "identity", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_anonymous_identity(setting); + if (!add_string_val(self, value, "anonymous_identity", FALSE, NULL, error)) + return FALSE; + + return TRUE; +} + +gboolean +nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error) +{ + return nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error); +} + +gboolean +nm_supplicant_config_get_ap_isolation(NMSupplicantConfig *self) +{ + return self->_priv.ap_isolation; +} + +void +nm_supplicant_config_set_ap_isolation(NMSupplicantConfig *self, gboolean ap_isolation) +{ + self->_priv.ap_isolation = ap_isolation; +} diff --git a/src/core/supplicant/nm-supplicant-config.h b/src/core/supplicant/nm-supplicant-config.h new file mode 100644 index 0000000..b561936 --- /dev/null +++ b/src/core/supplicant/nm-supplicant-config.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ +#define __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ + +#include "nm-setting-macsec.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" + +#include "nm-supplicant-types.h" + +#define NM_TYPE_SUPPLICANT_CONFIG (nm_supplicant_config_get_type()) +#define NM_SUPPLICANT_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfig)) +#define NM_SUPPLICANT_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass)) +#define NM_IS_SUPPLICANT_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SUPPLICANT_CONFIG)) +#define NM_IS_SUPPLICANT_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SUPPLICANT_CONFIG)) +#define NM_SUPPLICANT_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass)) + +typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass; + +GType nm_supplicant_config_get_type(void); + +NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities); + +guint32 nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self); + +gboolean nm_supplicant_config_fast_required(NMSupplicantConfig *self); + +GVariant *nm_supplicant_config_to_variant(NMSupplicantConfig *self); + +GHashTable *nm_supplicant_config_get_blobs(NMSupplicantConfig *self); + +gboolean nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self, + NMSettingWireless * setting, + guint32 fixed_freq, + GError ** error); + +gboolean +nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connection, GError **error); + +gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig * self, + NMSettingWirelessSecurity *setting, + NMSetting8021x *setting_8021x, + const char * con_uuid, + guint32 mtu, + NMSettingWirelessSecurityPmf pmf, + NMSettingWirelessSecurityFils fils, + GError ** error); + +gboolean nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error); + +gboolean nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self, + NMSetting8021x * setting, + const char * con_uuid, + guint32 mtu, + gboolean wired, + GError ** error); + +gboolean nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self, + NMSettingMacsec * setting, + GError ** error); + +gboolean nm_supplicant_config_enable_pmf_akm(NMSupplicantConfig *self, GError **error); + +void nm_supplicant_config_set_ap_isolation(NMSupplicantConfig *self, gboolean ap_isolation); +gboolean nm_supplicant_config_get_ap_isolation(NMSupplicantConfig *self); + +#endif /* __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ */ diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c new file mode 100644 index 0000000..ec278c3 --- /dev/null +++ b/src/core/supplicant/nm-supplicant-interface.c @@ -0,0 +1,3522 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-interface.h" + +#include +#include + +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-supplicant-config.h" +#include "nm-supplicant-manager.h" +#include "shared/nm-glib-aux/nm-dbus-aux.h" + +#define DBUS_TIMEOUT_MSEC 20000 + +/*****************************************************************************/ + +typedef struct { + NMSupplicantInterface *self; + char * type; + char * bssid; + char * pin; + guint signal_id; + GCancellable * cancellable; + bool needs_cancelling : 1; + bool is_cancelling : 1; +} WpsData; + +struct _AddNetworkData; + +typedef struct { + NMSupplicantInterface * self; + NMSupplicantConfig * cfg; + GCancellable * cancellable; + NMSupplicantInterfaceAssocCb callback; + gpointer user_data; + guint fail_on_idle_id; + guint blobs_left; + guint calls_left; + struct _AddNetworkData * add_network_data; +} AssocData; + +typedef struct _AddNetworkData { + /* the assoc_data at the time when doing the call. */ + AssocData * assoc_data; + NMRefString *name_owner; + NMRefString *object_path; + GObject * shutdown_wait_obj; +} AddNetworkData; + +enum { + STATE, /* change in the interface's state */ + BSS_CHANGED, /* a new BSS appeared, was updated, or was removed. */ + PEER_CHANGED, /* a new Peer appeared, was updated, or was removed */ + WPS_CREDENTIALS, /* WPS credentials received */ + GROUP_STARTED, /* a new Group (interface) was created */ + GROUP_FINISHED, /* a Group (interface) has been finished */ + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMSupplicantInterface, + PROP_SUPPLICANT_MANAGER, + PROP_DBUS_OBJECT_PATH, + PROP_IFINDEX, + PROP_P2P_GROUP_JOINED, + PROP_P2P_GROUP_PATH, + PROP_P2P_GROUP_OWNER, + PROP_SCANNING, + PROP_CURRENT_BSS, + PROP_DRIVER, + PROP_P2P_AVAILABLE, + PROP_AUTH_STATE, ); + +typedef struct _NMSupplicantInterfacePrivate { + NMSupplicantManager *supplicant_manager; + + GDBusConnection *dbus_connection; + NMRefString * name_owner; + NMRefString * object_path; + + char *ifname; + + GCancellable *main_cancellable; + + NMRefString *p2p_group_path; + + GCancellable *p2p_group_properties_cancellable; + + WpsData *wps_data; + + AssocData *assoc_data; + + char *net_path; + + char *driver; + + GHashTable *bss_idx; + CList bss_lst_head; + CList bss_initializing_lst_head; + + NMRefString *current_bss; + + GHashTable *peer_idx; + CList peer_lst_head; + CList peer_initializing_lst_head; + + gint64 last_scan_msec; + + NMSupplicantAuthState auth_state; + + NMSupplicantDriver requested_driver; + NMSupplCapMask global_capabilities; + NMSupplCapMask iface_capabilities; + + guint properties_changed_id; + guint signal_id; + guint bss_properties_changed_id; + guint peer_properties_changed_id; + guint p2p_group_properties_changed_id; + + int ifindex; + + int starting_pending_count; + + guint32 max_scan_ssids; + + gint32 disconnect_reason; + + NMSupplicantInterfaceState state; + NMSupplicantInterfaceState supp_state; + + bool scanning_property : 1; + bool scanning_cached : 1; + + bool p2p_capable_property : 1; + bool p2p_capable_cached : 1; + + bool p2p_group_owner_property : 1; + bool p2p_group_owner_cached : 1; + + bool p2p_group_joined_cached : 1; + + bool is_ready_main : 1; + bool is_ready_p2p_device : 1; + + bool prop_scan_active : 1; + bool prop_scan_ssid : 1; + + bool ap_isolate_supported : 1; + bool ap_isolate_needs_reset : 1; +} NMSupplicantInterfacePrivate; + +struct _NMSupplicantInterfaceClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE) + +/*****************************************************************************/ + +static const char * +_log_pretty_object_path(NMSupplicantInterfacePrivate *priv) +{ + const char *s; + + nm_assert(priv); + nm_assert(NM_IS_REF_STRING(priv->object_path)); + + s = priv->object_path->str; + if (NM_STR_HAS_PREFIX(s, "/fi/w1/wpa_supplicant1/Interfaces/")) { + s += NM_STRLEN("/fi/w1/wpa_supplicant1/Interfaces/"); + if (s[0] && s[0] != '/') + return s; + } + return priv->object_path->str; +} + +#define _NMLOG_DOMAIN LOGD_SUPPLICANT +#define _NMLOG_PREFIX_NAME "sup-iface" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + NMSupplicantInterface * _self = (self); \ + NMSupplicantInterfacePrivate *_priv = \ + _self ? NM_SUPPLICANT_INTERFACE_GET_PRIVATE(_self) : NULL; \ + char _sbuf[255]; \ + const char *_ifname = _priv ? _priv->ifname : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + _ifname, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_self ? nm_sprintf_buf(_sbuf, \ + "[" NM_HASH_OBFUSCATE_PTR_FMT ",%s,%s]", \ + NM_HASH_OBFUSCATE_PTR(_self), \ + _log_pretty_object_path(_priv), \ + _ifname ?: "???") \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _starting_check_ready(NMSupplicantInterface *self); + +static void assoc_return(NMSupplicantInterface *self, GError *error, const char *message); + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE( + nm_supplicant_interface_state_to_string, + NMSupplicantInterfaceState, + NM_UTILS_LOOKUP_DEFAULT_WARN("internal-unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_INVALID, "internal-invalid"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_STARTING, "internal-starting"), + + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, "4way_handshake"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, "associated"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, "associating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING, "authenticating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_COMPLETED, "completed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, "disconnected"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group_handshake"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_INACTIVE, "inactive"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DISABLED, "interface_disabled"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_SCANNING, "scanning"), + + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DOWN, "internal-down"), ); + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + wpas_state_string_to_enum, + NMSupplicantInterfaceState, + { nm_assert(name); }, + { return NM_SUPPLICANT_INTERFACE_STATE_INVALID; }, + {"4way_handshake", NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE}, + {"associated", NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED}, + {"associating", NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING}, + {"authenticating", NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING}, + {"completed", NM_SUPPLICANT_INTERFACE_STATE_COMPLETED}, + {"disconnected", NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED}, + {"group_handshake", NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE}, + {"inactive", NM_SUPPLICANT_INTERFACE_STATE_INACTIVE}, + {"interface_disabled", NM_SUPPLICANT_INTERFACE_STATE_DISABLED}, + {"scanning", NM_SUPPLICANT_INTERFACE_STATE_SCANNING}, ); + +/*****************************************************************************/ + +static NM80211ApSecurityFlags +security_from_vardict(GVariant *security) +{ + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + const char ** array; + const char * tmp; + + nm_assert(g_variant_is_of_type(security, G_VARIANT_TYPE_VARDICT)); + + if (g_variant_lookup(security, "KeyMgmt", "^a&s", &array)) { + if (g_strv_contains(array, "wpa-psk") || g_strv_contains(array, "wpa-ft-psk")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK; + if (g_strv_contains(array, "wpa-eap") || g_strv_contains(array, "wpa-ft-eap") + || g_strv_contains(array, "wpa-fils-sha256") + || g_strv_contains(array, "wpa-fils-sha384")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X; + if (g_strv_contains(array, "sae")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE; + if (g_strv_contains(array, "owe")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE; + if (g_strv_contains(array, "wpa-eap-suite-b-192")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192; + g_free(array); + } + + if (g_variant_lookup(security, "Pairwise", "^a&s", &array)) { + if (g_strv_contains(array, "tkip")) + flags |= NM_802_11_AP_SEC_PAIR_TKIP; + if (g_strv_contains(array, "ccmp")) + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + g_free(array); + } + + if (g_variant_lookup(security, "Group", "&s", &tmp)) { + if (nm_streq(tmp, "wep40")) + flags |= NM_802_11_AP_SEC_GROUP_WEP40; + else if (nm_streq(tmp, "wep104")) + flags |= NM_802_11_AP_SEC_GROUP_WEP104; + else if (nm_streq(tmp, "tkip")) + flags |= NM_802_11_AP_SEC_GROUP_TKIP; + else if (nm_streq(tmp, "ccmp")) + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + } + + return flags; +} + +/*****************************************************************************/ + +/* Various conditions prevent _starting_check_ready() from completing. For example, + * bss_initializing_lst_head, peer_initializing_lst_head and p2p_group_properties_cancellable. + * At some places, these conditions might toggle, and it would seems we would have + * to call _starting_check_ready() at that point, to ensure we don't miss a state + * change that we are ready. However, these places are deep in the call stack and + * not suitable to perform this state change. Instead, the callers *MUST* have + * added their own starting_pending_count to delay _starting_check_ready(). + * + * Assert that is the case. */ +#define nm_assert_starting_has_pending_count(v) nm_assert((v) > 0) + +/*****************************************************************************/ + +static void +_dbus_connection_call(NMSupplicantInterface *self, + const char * interface_name, + const char * method_name, + GVariant * parameters, + const GVariantType * reply_type, + GDBusCallFlags flags, + int timeout_msec, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + interface_name, + method_name, + parameters, + reply_type, + flags, + timeout_msec, + cancellable, + callback, + user_data); +} + +static void +_dbus_connection_call_simple_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * log_reason; + gs_free char * remote_error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &log_reason); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + if (res) { + _LOGT("call-%s: success", log_reason); + return; + } + + remote_error = g_dbus_error_get_remote_error(error); + if (!nm_streq0(remote_error, "fi.w1.wpa_supplicant1.NotConnected")) { + g_dbus_error_strip_remote_error(error); + _LOGW("call-%s: failed with %s", log_reason, error->message); + return; + } + + _LOGT("call-%s: failed with %s", log_reason, error->message); +} + +static void +_dbus_connection_call_simple(NMSupplicantInterface *self, + const char * interface_name, + const char * method_name, + GVariant * parameters, + const GVariantType * reply_type, + const char * log_reason) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _dbus_connection_call(self, + interface_name, + method_name, + parameters, + reply_type, + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->main_cancellable, + _dbus_connection_call_simple_cb, + nm_utils_user_data_pack(self, log_reason)); +} + +/*****************************************************************************/ + +static void +_emit_signal_state(NMSupplicantInterface * self, + NMSupplicantInterfaceState new_state, + NMSupplicantInterfaceState old_state, + gint32 disconnect_reason) +{ + g_signal_emit(self, + signals[STATE], + 0, + (int) new_state, + (int) old_state, + (int) disconnect_reason); +} + +/*****************************************************************************/ + +static void +_remove_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_free char * net_path = NULL; + + if (!priv->net_path) + return; + + net_path = g_steal_pointer(&priv->net_path); + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "RemoveNetwork", + g_variant_new("(o)", net_path), + G_VARIANT_TYPE("()"), + "remove-network"); + + if (priv->ap_isolate_supported && priv->ap_isolate_needs_reset) { + _dbus_connection_call_simple(self, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_new("(ssv)", + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApIsolate", + g_variant_new_string("0")), + G_VARIANT_TYPE("()"), + "reset-ap-isolation"); + } + priv->ap_isolate_needs_reset = FALSE; +} + +/*****************************************************************************/ + +static void +_notify_maybe_scanning(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean scanning; + + scanning = nm_supplicant_interface_state_is_operational(priv->state) && priv->scanning_property; + + if (priv->scanning_cached == scanning) + return; + + if (!scanning && !c_list_is_empty(&priv->bss_initializing_lst_head)) { + /* we would change state to indicate we no longer scan. However, + * we still have BSS instances to be initialized. Delay the + * state change further. */ + return; + } + + _LOGT("scanning: %s", scanning ? "yes" : "no"); + + if (!scanning) + priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec(); + else { + /* while we are scanning, we set the timestamp to -1. */ + priv->last_scan_msec = -1; + } + priv->scanning_cached = scanning; + _notify(self, PROP_SCANNING); +} + +static void +_notify_maybe_p2p_available(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean value; + + value = priv->is_ready_p2p_device && priv->p2p_capable_property; + + if (priv->p2p_capable_cached == value) + return; + + priv->p2p_capable_cached = value; + _notify(self, PROP_P2P_AVAILABLE); +} + +static void +_notify_maybe_p2p_group(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean value_joined; + gboolean value_owner; + gboolean joined_changed; + gboolean owner_changed; + + value_joined = priv->p2p_group_path && !priv->p2p_group_properties_cancellable; + value_owner = value_joined && priv->p2p_group_owner_property; + + if ((joined_changed = (priv->p2p_group_joined_cached != value_joined))) + priv->p2p_group_joined_cached = value_joined; + + if ((owner_changed = (priv->p2p_group_owner_cached != value_owner))) + priv->p2p_group_owner_cached = value_owner; + + if (joined_changed) + _notify(self, PROP_P2P_GROUP_JOINED); + if (owner_changed) + _notify(self, PROP_P2P_GROUP_OWNER); +} + +/*****************************************************************************/ + +static void +_bss_info_destroy(NMSupplicantBssInfo *bss_info) +{ + c_list_unlink_stale(&bss_info->_bss_lst); + nm_clear_g_cancellable(&bss_info->_init_cancellable); + g_bytes_unref(bss_info->ssid); + nm_ref_string_unref(bss_info->bss_path); + nm_g_slice_free(bss_info); +} + +static void +_bss_info_changed_emit(NMSupplicantInterface *self, + NMSupplicantBssInfo * bss_info, + gboolean is_present) +{ + _LOGT("BSS %s %s", bss_info->bss_path->str, is_present ? "updated" : "deleted"); + g_signal_emit(self, signals[BSS_CHANGED], 0, bss_info, is_present); +} + +static void +_bss_info_properties_changed(NMSupplicantInterface *self, + NMSupplicantBssInfo * bss_info, + GVariant * properties, + gboolean initial) +{ + gboolean v_b; + GVariant * v_v; + const char * v_s; + gint16 v_i16; + guint16 v_u16; + guint32 v_u32; + NM80211ApFlags p_ap_flags; + NM80211Mode p_mode; + guint8 p_signal_percent; + const guint8 * arr_data; + gsize arr_len; + guint32 p_max_rate; + gboolean p_max_rate_has; + gint64 now_msec = 0; + + if (nm_g_variant_lookup(properties, "Age", "u", &v_u32)) { + bss_info->last_seen_msec = + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec) - (((gint64) v_u32) * 1000); + } else if (initial) { + /* Unknown Age. Assume we just received it. */ + bss_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + } + + p_ap_flags = bss_info->ap_flags; + if (nm_g_variant_lookup(properties, "Privacy", "b", &v_b)) + p_ap_flags = NM_FLAGS_ASSIGN(p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY, v_b); + else { + nm_assert(!initial || !NM_FLAGS_HAS(p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY)); + } + v_v = nm_g_variant_lookup_value(properties, "WPS", G_VARIANT_TYPE_VARDICT); + if (v_v || initial) { + NM80211ApFlags f = NM_802_11_AP_FLAGS_NONE; + + if (v_v) { + if (g_variant_lookup(v_v, "Type", "&s", &v_s)) { + f = NM_802_11_AP_FLAGS_WPS; + if (nm_streq(v_s, "pcb")) + f |= NM_802_11_AP_FLAGS_WPS_PBC; + else if (nm_streq(v_s, "pin")) + f |= NM_802_11_AP_FLAGS_WPS_PIN; + } + g_variant_unref(v_v); + } + p_ap_flags = NM_FLAGS_ASSIGN_MASK(p_ap_flags, + NM_802_11_AP_FLAGS_WPS | NM_802_11_AP_FLAGS_WPS_PBC + | NM_802_11_AP_FLAGS_WPS_PIN, + f); + } + if (bss_info->ap_flags != p_ap_flags) { + bss_info->ap_flags = p_ap_flags; + nm_assert(bss_info->ap_flags == p_ap_flags); + } + + if (nm_g_variant_lookup(properties, "Mode", "&s", &v_s)) { + if (nm_streq(v_s, "infrastructure")) + p_mode = NM_802_11_MODE_INFRA; + else if (nm_streq(v_s, "ad-hoc")) + p_mode = NM_802_11_MODE_ADHOC; + else if (nm_streq(v_s, "mesh")) + p_mode = NM_802_11_MODE_MESH; + else + p_mode = NM_802_11_MODE_UNKNOWN; + } else if (initial) + p_mode = NM_802_11_MODE_UNKNOWN; + else + p_mode = bss_info->mode; + if (bss_info->mode != p_mode) { + bss_info->mode = p_mode; + nm_assert(bss_info->mode == p_mode); + } + + if (nm_g_variant_lookup(properties, "Signal", "n", &v_i16)) + p_signal_percent = nm_wifi_utils_level_to_quality(v_i16); + else if (initial) + p_signal_percent = 0; + else + p_signal_percent = bss_info->signal_percent; + bss_info->signal_percent = p_signal_percent; + + if (nm_g_variant_lookup(properties, "Frequency", "q", &v_u16)) + bss_info->frequency = v_u16; + + v_v = nm_g_variant_lookup_value(properties, "SSID", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + arr_len = MIN(32, arr_len); + + /* Stupid ieee80211 layer uses */ + if (arr_data && arr_len + && !(NM_IN_SET(arr_len, 8, 9) && memcmp(arr_data, "", arr_len) == 0) + && !nm_utils_is_empty_ssid(arr_data, arr_len)) { + /* good */ + } else + arr_len = 0; + + if (!nm_utils_gbytes_equal_mem(bss_info->ssid, arr_data, arr_len)) { + _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&bss_info->ssid); + + bss_info->ssid = (arr_len == 0) ? NULL : g_bytes_new(arr_data, arr_len); + } + + g_variant_unref(v_v); + } else { + nm_assert(!initial || !bss_info->ssid); + } + + v_v = nm_g_variant_lookup_value(properties, "BSSID", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (arr_len == ETH_ALEN && memcmp(arr_data, &nm_ether_addr_zero, ETH_ALEN) != 0 + && memcmp(arr_data, (char[ETH_ALEN]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, ETH_ALEN) + != 0) { + /* pass */ + } else + arr_len = 0; + + if (arr_len != 0) { + nm_assert(arr_len == sizeof(bss_info->bssid)); + bss_info->bssid_valid = TRUE; + memcpy(&bss_info->bssid, arr_data, sizeof(bss_info->bssid)); + } else if (bss_info->bssid_valid) { + bss_info->bssid_valid = FALSE; + memset(&bss_info->bssid, 0, sizeof(bss_info->bssid)); + } + g_variant_unref(v_v); + } else { + nm_assert(!initial || !bss_info->bssid_valid); + } + nm_assert((!!bss_info->bssid_valid) + == (!nm_utils_memeqzero(&bss_info->bssid, sizeof(bss_info->bssid)))); + + p_max_rate_has = FALSE; + p_max_rate = 0; + v_v = nm_g_variant_lookup_value(properties, "Rates", G_VARIANT_TYPE("au")); + if (v_v) { + const guint32 *rates = g_variant_get_fixed_array(v_v, &arr_len, sizeof(guint32)); + gsize i; + + for (i = 0; i < arr_len; i++) + p_max_rate = NM_MAX(p_max_rate, rates[i]); + p_max_rate_has = TRUE; + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "WPA", G_VARIANT_TYPE_VARDICT); + if (v_v) { + bss_info->wpa_flags = security_from_vardict(v_v); + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "RSN", G_VARIANT_TYPE_VARDICT); + if (v_v) { + bss_info->rsn_flags = security_from_vardict(v_v); + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "IEs", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + gboolean p_owe_transition_mode; + gboolean p_metered; + guint32 rate; + + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + nm_wifi_utils_parse_ies(arr_data, arr_len, &rate, &p_metered, &p_owe_transition_mode); + p_max_rate = NM_MAX(p_max_rate, rate); + p_max_rate_has = TRUE; + g_variant_unref(v_v); + + if (p_owe_transition_mode) + bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE_TM; + else + bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE_TM; + + bss_info->metered = p_metered; + } + + if (p_max_rate_has) + bss_info->max_rate = p_max_rate / 1000u; + + _bss_info_changed_emit(self, bss_info, TRUE); +} + +static void +_bss_info_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantBssInfo * bss_info; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + bss_info = user_data; + self = bss_info->_self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_clear_object(&bss_info->_init_cancellable); + nm_c_list_move_tail(&priv->bss_lst_head, &bss_info->_bss_lst); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _bss_info_properties_changed(self, bss_info, properties, TRUE); + + _starting_check_ready(self); + + _notify_maybe_scanning(self); +} + +static void +_bss_info_add(NMSupplicantInterface *self, const char *object_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *bss_path = NULL; + NMSupplicantBssInfo * bss_info; + + bss_path = nm_ref_string_new(nm_dbus_path_not_empty(object_path)); + if (!bss_path) + return; + + bss_info = g_hash_table_lookup(priv->bss_idx, &bss_path); + if (bss_info) { + bss_info->_bss_dirty = FALSE; + return; + } + + bss_info = g_slice_new(NMSupplicantBssInfo); + *bss_info = (NMSupplicantBssInfo){ + ._self = self, + .bss_path = g_steal_pointer(&bss_path), + ._init_cancellable = g_cancellable_new(), + }; + c_list_link_tail(&priv->bss_initializing_lst_head, &bss_info->_bss_lst); + g_hash_table_add(priv->bss_idx, bss_info); + + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + bss_info->bss_path->str, + NM_WPAS_DBUS_IFACE_BSS, + 5000, + bss_info->_init_cancellable, + _bss_info_get_all_cb, + bss_info); +} + +static gboolean +_bss_info_remove(NMSupplicantInterface *self, NMRefString **p_bss_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantBssInfo * bss_info; + gpointer unused_but_required; + + if (!g_hash_table_steal_extended(priv->bss_idx, + p_bss_path, + (gpointer *) &bss_info, + &unused_but_required)) + return FALSE; + + c_list_unlink(&bss_info->_bss_lst); + if (!bss_info->_init_cancellable) + _bss_info_changed_emit(self, bss_info, FALSE); + _bss_info_destroy(bss_info); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); + + return TRUE; +} + +/*****************************************************************************/ + +static void +_peer_info_destroy(NMSupplicantPeerInfo *peer_info) +{ + c_list_unlink(&peer_info->_peer_lst); + nm_clear_g_cancellable(&peer_info->_init_cancellable); + + g_free(peer_info->device_name); + g_free(peer_info->manufacturer); + g_free(peer_info->model); + g_free(peer_info->model_number); + g_free(peer_info->serial); + g_free(peer_info->groups); + g_bytes_unref(peer_info->ies); + + nm_ref_string_unref(peer_info->peer_path); + + nm_g_slice_free(peer_info); +} + +static void +_peer_info_changed_emit(NMSupplicantInterface *self, + NMSupplicantPeerInfo * peer_info, + gboolean is_present) +{ + g_signal_emit(self, signals[PEER_CHANGED], 0, peer_info, is_present); +} + +static void +_peer_info_properties_changed(NMSupplicantInterface *self, + NMSupplicantPeerInfo * peer_info, + GVariant * properties, + gboolean initial) +{ + GVariant * v_v; + const char * v_s; + const char ** v_strv; + gint32 v_i32; + const guint8 *arr_data; + gsize arr_len; + + peer_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec(); + + if (nm_g_variant_lookup(properties, "level", "i", &v_i32)) + peer_info->signal_percent = nm_wifi_utils_level_to_quality(v_i32); + + if (nm_g_variant_lookup(properties, "DeviceName", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->device_name, v_s); + + if (nm_g_variant_lookup(properties, "Manufacturer", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->manufacturer, v_s); + + if (nm_g_variant_lookup(properties, "Model", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->model, v_s); + + if (nm_g_variant_lookup(properties, "ModelNumber", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->model_number, v_s); + + if (nm_g_variant_lookup(properties, "Serial", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->serial, v_s); + + if (nm_g_variant_lookup(properties, "Groups", "^a&o", &v_strv)) { + g_free(peer_info->groups); + peer_info->groups = nm_utils_strv_dup_packed(v_strv, -1); + + g_free(v_strv); + } + + v_v = nm_g_variant_lookup_value(properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (arr_len == ETH_ALEN && memcmp(arr_data, &nm_ether_addr_zero, ETH_ALEN) != 0 + && memcmp(arr_data, (char[ETH_ALEN]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, ETH_ALEN) + != 0) { + /* pass */ + } else + arr_len = 0; + + if (arr_len != 0) { + nm_assert(arr_len == sizeof(peer_info->address)); + peer_info->address_valid = TRUE; + memcpy(peer_info->address, arr_data, sizeof(peer_info->address)); + } else if (peer_info->address_valid) { + peer_info->address_valid = FALSE; + memset(peer_info->address, 0, sizeof(peer_info->address)); + } + g_variant_unref(v_v); + } else { + nm_assert(!initial || !peer_info->address_valid); + } + nm_assert((peer_info->address_valid + && !nm_utils_memeqzero(peer_info->address, sizeof(peer_info->address))) + || (!peer_info->address_valid + && nm_utils_memeqzero(peer_info->address, sizeof(peer_info->address)))); + + /* The IEs property contains the WFD R1 subelements */ + v_v = nm_g_variant_lookup_value(properties, "IEs", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (!nm_utils_gbytes_equal_mem(peer_info->ies, arr_data, arr_len)) { + _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&peer_info->ies); + + peer_info->ies = g_bytes_new(arr_data, arr_len); + } else if (arr_len == 0 && !peer_info->ies) + peer_info->ies = g_bytes_new(NULL, 0); + g_variant_unref(v_v); + } + + _peer_info_changed_emit(self, peer_info, TRUE); +} + +static void +_peer_info_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantPeerInfo * peer_info; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + peer_info = user_data; + self = peer_info->_self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_clear_object(&peer_info->_init_cancellable); + nm_c_list_move_tail(&priv->peer_lst_head, &peer_info->_peer_lst); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _peer_info_properties_changed(self, peer_info, properties, TRUE); + + _starting_check_ready(self); +} + +static void +_peer_info_add(NMSupplicantInterface *self, const char *object_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *peer_path = NULL; + NMSupplicantPeerInfo * peer_info; + + peer_path = nm_ref_string_new(nm_dbus_path_not_empty(object_path)); + if (!peer_path) + return; + + peer_info = g_hash_table_lookup(priv->peer_idx, &peer_path); + + if (peer_info) { + peer_info->_peer_dirty = FALSE; + return; + } + + peer_info = g_slice_new(NMSupplicantPeerInfo); + *peer_info = (NMSupplicantPeerInfo){ + ._self = self, + .peer_path = g_steal_pointer(&peer_path), + ._init_cancellable = g_cancellable_new(), + }; + c_list_link_tail(&priv->peer_initializing_lst_head, &peer_info->_peer_lst); + g_hash_table_add(priv->peer_idx, peer_info); + + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + peer_info->peer_path->str, + NM_WPAS_DBUS_IFACE_PEER, + 5000, + peer_info->_init_cancellable, + _peer_info_get_all_cb, + peer_info); +} + +static gboolean +_peer_info_remove(NMSupplicantInterface *self, NMRefString **p_peer_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantPeerInfo * peer_info; + gpointer unused_but_required; + + if (!g_hash_table_steal_extended(priv->peer_idx, + p_peer_path, + (gpointer *) &peer_info, + &unused_but_required)) + return FALSE; + + c_list_unlink(&peer_info->_peer_lst); + if (!peer_info->_init_cancellable) + _peer_info_changed_emit(self, peer_info, FALSE); + _peer_info_destroy(peer_info); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); + + return TRUE; +} + +/*****************************************************************************/ + +static void +set_state_down(NMSupplicantInterface *self, + gboolean force_remove_from_supplicant, + const char * reason) +{ + _nm_unused gs_unref_object NMSupplicantInterface *self_keep_alive = g_object_ref(self); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantBssInfo * bss_info; + NMSupplicantPeerInfo * peer_info; + NMSupplicantInterfaceState old_state; + + nm_assert(priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN); + nm_assert(!c_list_is_empty(&self->supp_lst)); + + _LOGD("remove interface \"%s\" on %s (%s)%s", + priv->object_path->str, + priv->name_owner->str, + reason, + force_remove_from_supplicant ? " (remove in wpa_supplicant)" : ""); + + old_state = priv->state; + + priv->state = NM_SUPPLICANT_INTERFACE_STATE_DOWN; + + _nm_supplicant_manager_unregister_interface(priv->supplicant_manager, self); + + nm_assert(c_list_is_empty(&self->supp_lst)); + + if (force_remove_from_supplicant) { + _nm_supplicant_manager_dbus_call_remove_interface(priv->supplicant_manager, + priv->name_owner->str, + priv->object_path->str); + } + + _emit_signal_state(self, priv->state, old_state, 0); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->signal_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->bss_properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->peer_properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, + &priv->p2p_group_properties_changed_id); + + nm_supplicant_interface_cancel_wps(self); + + if (priv->assoc_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, TRUE, "NMSupplicantInterface"); + assoc_return(self, error, "cancelled because supplicant interface is going down"); + } + + while ( + (bss_info = + c_list_first_entry(&priv->bss_initializing_lst_head, NMSupplicantBssInfo, _bss_lst))) { + g_hash_table_remove(priv->bss_idx, bss_info); + _bss_info_destroy(bss_info); + } + while ((bss_info = c_list_first_entry(&priv->bss_lst_head, NMSupplicantBssInfo, _bss_lst))) { + g_hash_table_remove(priv->bss_idx, bss_info); + _bss_info_destroy(bss_info); + } + nm_assert(g_hash_table_size(priv->bss_idx) == 0); + + while ((peer_info = c_list_first_entry(&priv->peer_initializing_lst_head, + NMSupplicantPeerInfo, + _peer_lst))) { + g_hash_table_remove(priv->peer_idx, peer_info); + _peer_info_destroy(peer_info); + } + while ( + (peer_info = c_list_first_entry(&priv->peer_lst_head, NMSupplicantPeerInfo, _peer_lst))) { + g_hash_table_remove(priv->peer_idx, peer_info); + _peer_info_destroy(peer_info); + } + nm_assert(g_hash_table_size(priv->peer_idx) == 0); + + nm_clear_g_cancellable(&priv->main_cancellable); + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + nm_clear_pointer(&priv->p2p_group_path, nm_ref_string_unref); + + _remove_network(self); + + nm_clear_pointer(&priv->current_bss, nm_ref_string_unref); + + _notify_maybe_scanning(self); +} + +static void +set_state(NMSupplicantInterface *self, NMSupplicantInterfaceState new_state) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantInterfaceState old_state = priv->state; + + nm_assert(new_state > NM_SUPPLICANT_INTERFACE_STATE_STARTING); + nm_assert(new_state < NM_SUPPLICANT_INTERFACE_STATE_DOWN); + nm_assert(nm_supplicant_interface_state_is_operational(new_state)); + + nm_assert(priv->state >= NM_SUPPLICANT_INTERFACE_STATE_STARTING); + nm_assert(priv->state < NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + if (new_state == priv->state) + return; + + _LOGT("state: set state \"%s\" (was \"%s\")", + nm_supplicant_interface_state_to_string(new_state), + nm_supplicant_interface_state_to_string(priv->state)); + + priv->state = new_state; + + _emit_signal_state( + self, + priv->state, + old_state, + priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED ? 0u : priv->disconnect_reason); +} + +NMRefString * +nm_supplicant_interface_get_current_bss(NMSupplicantInterface *self) +{ + g_return_val_if_fail(self != NULL, FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->current_bss; +} + +gboolean +nm_supplicant_interface_get_scanning(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->scanning_cached; +} + +gint64 +nm_supplicant_interface_get_last_scan(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->last_scan_msec; +} + +#define MATCH_PROPERTY(p, n, v, t) (!strcmp(p, n) && g_variant_is_of_type(v, t)) + +static void +parse_capabilities(NMSupplicantInterface *self, GVariant *capabilities) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const gboolean old_prop_scan_active = priv->prop_scan_active; + const gboolean old_prop_scan_ssid = priv->prop_scan_ssid; + const guint32 old_max_scan_ssids = priv->max_scan_ssids; + gboolean have_ft = FALSE; + gint32 max_scan_ssids; + const char ** array; + + nm_assert(capabilities && g_variant_is_of_type(capabilities, G_VARIANT_TYPE_VARDICT)); + + if (g_variant_lookup(capabilities, "KeyMgmt", "^a&s", &array)) { + have_ft = g_strv_contains(array, "wpa-ft-psk"); + g_free(array); + } + + priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities, + NM_SUPPL_CAP_TYPE_FT, + have_ft ? NM_TERNARY_TRUE : NM_TERNARY_FALSE); + + if (g_variant_lookup(capabilities, "Modes", "^a&s", &array)) { + /* Setting p2p_capable might toggle _prop_p2p_available_get(). However, + * we don't need to check for a property changed notification, because + * the caller did g_object_freeze_notify() and will perform the check. */ + priv->p2p_capable_property = g_strv_contains(array, "p2p"); + g_free(array); + } + + if (g_variant_lookup(capabilities, "Scan", "^a&s", &array)) { + const char **a; + + priv->prop_scan_active = FALSE; + priv->prop_scan_ssid = FALSE; + for (a = array; *a; a++) { + if (nm_streq(*a, "active")) + priv->prop_scan_active = TRUE; + else if (nm_streq(*a, "ssid")) + priv->prop_scan_ssid = TRUE; + } + g_free(array); + } + + if (g_variant_lookup(capabilities, "MaxScanSSID", "i", &max_scan_ssids)) { + const gint32 WPAS_MAX_SCAN_SSIDS = 16; + + /* Even if supplicant claims that 20 SSIDs are supported, the Scan request + * still only accepts WPAS_MAX_SCAN_SSIDS SSIDs. Otherwise, the D-Bus + * request will be rejected with "fi.w1.wpa_supplicant1.InvalidArgs" + * Body: ('Did not receive correct message arguments.', 'Too many ssids specified. Specify at most four') + * */ + priv->max_scan_ssids = CLAMP(max_scan_ssids, 0, WPAS_MAX_SCAN_SSIDS); + } + + if (old_max_scan_ssids != priv->max_scan_ssids || old_prop_scan_active != priv->prop_scan_active + || old_prop_scan_ssid != priv->prop_scan_ssid) { + _LOGD("supports %u scan SSIDs (scan: %cactive %cssid)", + (guint32) priv->max_scan_ssids, + priv->prop_scan_active ? '+' : '-', + priv->prop_scan_ssid ? '+' : '-'); + } +} + +static void +_starting_check_ready(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_STARTING) + return; + + if (priv->starting_pending_count > 0) + return; + + if (!c_list_is_empty(&priv->bss_initializing_lst_head)) + return; + + if (!c_list_is_empty(&priv->peer_initializing_lst_head)) + return; + + if (priv->p2p_group_properties_cancellable) + return; + + nm_assert(priv->state == NM_SUPPLICANT_INTERFACE_STATE_STARTING); + + if (!nm_supplicant_interface_state_is_operational(priv->supp_state)) { + _LOGW("Supplicant state is unknown during initialization. Destroy the interface"); + set_state_down(self, TRUE, "failure to get valid interface state"); + return; + } + + set_state(self, priv->supp_state); +} + +static NMTernary +_get_capability(NMSupplicantInterfacePrivate *priv, NMSupplCapType type) +{ + NMTernary value; + NMTernary iface_value; + + switch (type) { + case NM_SUPPL_CAP_TYPE_AP: + iface_value = NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type); + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + value = MAX(iface_value, value); + break; + case NM_SUPPL_CAP_TYPE_FT: + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + if (value != NM_TERNARY_FALSE) { + iface_value = NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type); + if (iface_value != NM_TERNARY_DEFAULT) + value = iface_value; + } + break; + default: + nm_assert(NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type) == NM_TERNARY_DEFAULT); + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + break; + } + return value; +} + +NMTernary +nm_supplicant_interface_get_capability(NMSupplicantInterface *self, NMSupplCapType type) +{ + return _get_capability(NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self), type); +} + +NMSupplCapMask +nm_supplicant_interface_get_capabilities(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplCapMask caps; + + caps = priv->global_capabilities; + caps = NM_SUPPL_CAP_MASK_SET(caps, + NM_SUPPL_CAP_TYPE_AP, + _get_capability(priv, NM_SUPPL_CAP_TYPE_AP)); + caps = NM_SUPPL_CAP_MASK_SET(caps, + NM_SUPPL_CAP_TYPE_FT, + _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)); + + nm_assert(!NM_FLAGS_ANY(priv->iface_capabilities, + ~(NM_SUPPL_CAP_MASK_T_AP_MASK | NM_SUPPL_CAP_MASK_T_FT_MASK))); + +#if NM_MORE_ASSERTS > 10 + { + NMSupplCapType type; + + for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++) + nm_assert(NM_SUPPL_CAP_MASK_GET(caps, type) == _get_capability(priv, type)); + } +#endif + + return caps; +} + +static void +set_bridge_cb(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface *self; + NMLogLevel level; + gs_free const char * bridge = NULL; + + nm_utils_user_data_unpack(user_data, &self, &bridge); + + if (nm_utils_error_is_cancelled(error)) + return; + + /* The supplicant supports writing the bridge property since + * version 2.10. Before that version, trying to set the property + * results in a InvalidArgs error. Don't log a warning unless we + * are trying to set a non-NULL bridge. */ + if (!error) + level = LOGL_DEBUG; + else if (bridge == NULL && g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS)) { + level = LOGL_DEBUG; + } else + level = LOGL_WARN; + + _NMLOG(level, + "set bridge %s%s%s result: %s", + NM_PRINT_FMT_QUOTE_STRING(bridge), + error ? error->message : "success"); +} + +void +nm_supplicant_interface_set_bridge(NMSupplicantInterface *self, const char *bridge) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _LOGT("set bridge %s%s%s", NM_PRINT_FMT_QUOTE_STRING(bridge)); + + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BridgeIfname", + g_variant_new_string(bridge ?: ""), + DBUS_TIMEOUT_MSEC, + priv->main_cancellable, + set_bridge_cb, + nm_utils_user_data_pack(self, g_strdup(bridge))); +} + +void +nm_supplicant_interface_set_global_capabilities(NMSupplicantInterface *self, NMSupplCapMask value) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + priv->global_capabilities = value; +} + +NMSupplicantAuthState +nm_supplicant_interface_get_auth_state(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->auth_state; +} + +/*****************************************************************************/ + +static void +_p2p_group_properties_changed(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char * s; + + if (!properties) + priv->p2p_group_owner_property = FALSE; + else if (g_variant_lookup(properties, "Role", "&s", &s)) + priv->p2p_group_owner_property = nm_streq(s, "GO"); + + _notify_maybe_p2p_group(self); +} + +static void +_p2p_group_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_unref_variant GVariant *changed_properties = NULL; + + if (priv->p2p_group_properties_cancellable) + return; + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + + _p2p_group_properties_changed(self, changed_properties); +} + +static void +_p2p_group_properties_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_object_freeze_notify(G_OBJECT(self)); + + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _p2p_group_properties_changed(self, properties); + + _starting_check_ready(self); + + _notify_maybe_p2p_group(self); + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_p2p_group_set_path(NMSupplicantInterface *self, const char *path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *group_path = NULL; + + group_path = nm_ref_string_new(nm_dbus_path_not_empty(path)); + + if (priv->p2p_group_path == group_path) + return; + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, + &priv->p2p_group_properties_changed_id); + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + nm_ref_string_unref(priv->p2p_group_path); + priv->p2p_group_path = g_steal_pointer(&group_path); + + if (priv->p2p_group_path) { + priv->p2p_group_properties_cancellable = g_cancellable_new(); + priv->p2p_group_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + priv->p2p_group_path->str, + NM_WPAS_DBUS_IFACE_GROUP, + _p2p_group_properties_changed_cb, + self, + NULL); + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->p2p_group_path->str, + NM_WPAS_DBUS_IFACE_GROUP, + 5000, + priv->p2p_group_properties_cancellable, + _p2p_group_properties_get_all_cb, + self); + } + + _notify(self, PROP_P2P_GROUP_PATH); + _notify_maybe_p2p_group(self); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); +} + +/*****************************************************************************/ + +static void +_wps_data_free(WpsData *wps_data, GDBusConnection *dbus_connection) +{ + nm_clear_g_dbus_connection_signal(dbus_connection, &wps_data->signal_id); + nm_clear_g_cancellable(&wps_data->cancellable); + g_free(wps_data->type); + g_free(wps_data->pin); + g_free(wps_data->bssid); + nm_g_slice_free(wps_data); +} + +static void +_wps_credentials_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface *self = user_data; + gs_unref_variant GVariant *props = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) + return; + + g_variant_get(parameters, "(@a{sv})", &props); + + _LOGT("wps: new credentials"); + g_signal_emit(self, signals[WPS_CREDENTIALS], 0, props); +} + +static void +_wps_handle_start_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + WpsData * wps_data; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + wps_data = user_data; + self = wps_data->self; + + if (res) + _LOGT("wps: started with success"); + else + _LOGW("wps: start failed with %s", error->message); + + g_clear_object(&wps_data->cancellable); + nm_clear_g_free(&wps_data->type); + nm_clear_g_free(&wps_data->pin); + nm_clear_g_free(&wps_data->bssid); +} + +static void +_wps_handle_set_pc_cb(GVariant *res, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + WpsData * wps_data; + GVariantBuilder start_args; + guint8 bssid_buf[ETH_ALEN]; + + if (nm_utils_error_is_cancelled(error)) + return; + + wps_data = user_data; + self = wps_data->self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (res) + _LOGT("wps: ProcessCredentials successfully set, starting..."); + else + _LOGW("wps: ProcessCredentials failed to set (%s), starting...", error->message); + + wps_data->signal_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Credentials", + priv->object_path->str, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + _wps_credentials_changed_cb, + self, + NULL); + + g_variant_builder_init(&start_args, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&start_args, "{sv}", "Role", g_variant_new_string("enrollee")); + g_variant_builder_add(&start_args, "{sv}", "Type", g_variant_new_string(wps_data->type)); + if (wps_data->pin) + g_variant_builder_add(&start_args, "{sv}", "Pin", g_variant_new_string(wps_data->pin)); + if (wps_data->bssid) { + /* The BSSID is in fact not mandatory. If it is not set the supplicant would + * enroll with any BSS in range. */ + if (!nm_utils_hwaddr_aton(wps_data->bssid, bssid_buf, sizeof(bssid_buf))) + nm_assert_not_reached(); + g_variant_builder_add( + &start_args, + "{sv}", + "Bssid", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, bssid_buf, ETH_ALEN, sizeof(guint8))); + } + + wps_data->needs_cancelling = TRUE; + if (!wps_data->cancellable) + wps_data->cancellable = g_cancellable_new(); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Start", + g_variant_new("(a{sv})", &start_args), + G_VARIANT_TYPE("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + wps_data->cancellable, + _wps_handle_start_cb, + wps_data); +} + +static void +_wps_call_set_pc(NMSupplicantInterface *self, WpsData *wps_data) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (!wps_data->cancellable) + wps_data->cancellable = g_cancellable_new(); + + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "ProcessCredentials", + g_variant_new_boolean(TRUE), + 5000, + wps_data->cancellable, + _wps_handle_set_pc_cb, + wps_data); +} + +static void +_wps_handle_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source); + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + WpsData * wps_data; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(dbus_connection, result, &error); + nm_assert(!nm_utils_error_is_cancelled(error)); + + wps_data = user_data; + self = wps_data->self; + + if (!self) { + _wps_data_free(wps_data, dbus_connection); + if (res) + _LOGT("wps: cancel completed successfully, after supplicant interface is gone"); + else + _LOGW("wps: cancel failed (%s), after supplicant interface is gone", error->message); + return; + } + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + wps_data->is_cancelling = FALSE; + + if (!wps_data->type) { + priv->wps_data = NULL; + _wps_data_free(wps_data, dbus_connection); + if (res) + _LOGT("wps: cancel completed successfully"); + else + _LOGW("wps: cancel failed (%s)", error->message); + return; + } + + if (res) + _LOGT("wps: cancel completed successfully, setting ProcessCredentials now..."); + else + _LOGW("wps: cancel failed (%s), setting ProcessCredentials now...", error->message); + + _wps_call_set_pc(self, wps_data); +} + +static void +_wps_start(NMSupplicantInterface *self, const char *type, const char *bssid, const char *pin) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + WpsData * wps_data; + + if (type) + _LOGI("wps: type %s start...", type); + + wps_data = priv->wps_data; + + if (!wps_data) { + if (!type) + return; + + if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + _LOGD("wps: interface is down. Cannot start with WPS"); + return; + } + + wps_data = g_slice_new(WpsData); + *wps_data = (WpsData){ + .self = self, + .type = g_strdup(type), + .bssid = g_strdup(bssid), + .pin = g_strdup(pin), + }; + priv->wps_data = wps_data; + } else { + g_free(wps_data->type); + g_free(wps_data->bssid); + g_free(wps_data->pin); + wps_data->type = g_strdup(type); + wps_data->bssid = g_strdup(bssid); + wps_data->pin = g_strdup(pin); + } + + if (wps_data->is_cancelling) { + /* we wait for cancellation to complete. */ + return; + } + + if (!type || wps_data->needs_cancelling) { + _LOGT("wps: cancel %senrollment...", wps_data->needs_cancelling ? "previous " : ""); + + wps_data->is_cancelling = TRUE; + wps_data->needs_cancelling = FALSE; + nm_clear_g_cancellable(&wps_data->cancellable); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &wps_data->signal_id); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Cancel", + NULL, + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + NULL, + _wps_handle_cancel_cb, + wps_data); + return; + } + + _LOGT("wps: setting ProcessCredentials..."); + _wps_call_set_pc(self, wps_data); +} + +void +nm_supplicant_interface_enroll_wps(NMSupplicantInterface *self, + const char * type, + const char * bssid, + const char * pin) +{ + _wps_start(self, type, bssid, pin); +} + +void +nm_supplicant_interface_cancel_wps(NMSupplicantInterface *self) +{ + _wps_start(self, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +static void +iface_introspect_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * data; + NMTernary value; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + nm_assert(NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, NM_SUPPL_CAP_TYPE_AP) + == NM_TERNARY_DEFAULT); + + value = NM_TERNARY_DEFAULT; + if (res) { + g_variant_get(res, "(&s)", &data); + + /* The ProbeRequest method only exists if AP mode has been enabled */ + value = strstr(data, "ProbeRequest") ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; + } + + priv->iface_capabilities = + NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities, NM_SUPPL_CAP_TYPE_AP, value); + + priv->starting_pending_count--; + _starting_check_ready(self); +} + +static void +_properties_changed_main(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char ** v_strv; + const char * v_s; + gboolean v_b; + gint32 v_i32; + GVariant * v_v; + gboolean do_log_driver_info = FALSE; + gboolean do_set_state = FALSE; + gboolean do_notify_current_bss = FALSE; + + nm_assert(properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + v_v = g_variant_lookup_value(properties, "Capabilities", G_VARIANT_TYPE_VARDICT); + if (v_v) { + parse_capabilities(self, v_v); + g_variant_unref(v_v); + } + + if (nm_g_variant_lookup(properties, "Scanning", "b", &v_b)) { + if (priv->scanning_property != (!!v_b)) { + _LOGT("scanning: %s (plain property)", v_b ? "yes" : "no"); + priv->scanning_property = v_b; + } + } + + if (nm_g_variant_lookup(properties, "Ifname", "&s", &v_s)) { + if (nm_utils_strdup_reset(&priv->ifname, v_s)) + do_log_driver_info = TRUE; + } + if (nm_g_variant_lookup(properties, "Driver", "&s", &v_s)) { + if (nm_utils_strdup_reset(&priv->driver, v_s)) + do_log_driver_info = TRUE; + } + + if (nm_g_variant_lookup(properties, "DisconnectReason", "i", &v_i32)) { + /* Disconnect reason is currently only given for deauthentication events, + * not disassociation; currently they are IEEE 802.11 "reason codes", + * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22). Any locally caused + * deauthentication will be negative, while authentications caused by the + * AP will be positive. + */ + priv->disconnect_reason = v_i32; + } + + if (nm_g_variant_lookup(properties, "State", "&s", &v_s)) { + NMSupplicantInterfaceState state; + + state = wpas_state_string_to_enum(v_s); + if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID) + _LOGT("state: ignore unknown supplicant state '%s' (is %s, plain property)", + v_s, + nm_supplicant_interface_state_to_string(priv->supp_state)); + else if (priv->supp_state != state) { + _LOGT("state: %s (was %s, plain property)", + nm_supplicant_interface_state_to_string(state), + nm_supplicant_interface_state_to_string(priv->supp_state)); + priv->supp_state = state; + if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + /* Only transition to actual wpa_supplicant interface states (ie, + * anything > STARTING) after the NMSupplicantInterface has had a + * chance to initialize, which is signalled by entering the STARTING + * state. + */ + do_set_state = TRUE; + } + } + } + + if (nm_g_variant_lookup(properties, "CurrentBSS", "&o", &v_s)) { + v_s = nm_dbus_path_not_empty(v_s); + if (!nm_ref_string_equals_str(priv->current_bss, v_s)) { + nm_ref_string_unref(priv->current_bss); + priv->current_bss = nm_ref_string_new(v_s); + do_notify_current_bss = TRUE; + } + } + + if (nm_g_variant_lookup(properties, "ApIsolate", "&s", &v_s)) + priv->ap_isolate_supported = TRUE; + + if (do_log_driver_info) { + _LOGD("supplicant interface for ifindex=%d, ifname=%s%s%s, driver=%s%s%s (requested %s)", + priv->ifindex, + NM_PRINT_FMT_QUOTE_STRING(priv->ifname), + NM_PRINT_FMT_QUOTE_STRING(priv->driver), + nm_supplicant_driver_to_string(priv->requested_driver)); + } + + if (nm_g_variant_lookup(properties, "BSSs", "^a&o", &v_strv)) { + NMSupplicantBssInfo *bss_info; + NMSupplicantBssInfo *bss_info_safe; + const char ** iter; + + c_list_for_each_entry (bss_info, &priv->bss_lst_head, _bss_lst) + bss_info->_bss_dirty = TRUE; + c_list_for_each_entry (bss_info, &priv->bss_initializing_lst_head, _bss_lst) + bss_info->_bss_dirty = TRUE; + + for (iter = v_strv; *iter; iter++) + _bss_info_add(self, *iter); + + g_free(v_strv); + + c_list_for_each_entry_safe (bss_info, + bss_info_safe, + &priv->bss_initializing_lst_head, + _bss_lst) { + if (bss_info->_bss_dirty) + _bss_info_remove(self, &bss_info->bss_path); + } + c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_lst_head, _bss_lst) { + if (bss_info->_bss_dirty) + _bss_info_remove(self, &bss_info->bss_path); + } + } + + if (do_notify_current_bss) + _notify(self, PROP_CURRENT_BSS); + + if (do_set_state) + set_state(self, priv->supp_state); + + _notify_maybe_scanning(self); +} + +static void +_properties_changed_p2p_device(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char ** v_strv; + const char * v_s; + + nm_assert(!properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + if (nm_g_variant_lookup(properties, "Peers", "^a&o", &v_strv)) { + NMSupplicantPeerInfo *peer_info; + NMSupplicantPeerInfo *peer_info_safe; + const char *const * iter; + + c_list_for_each_entry (peer_info, &priv->peer_lst_head, _peer_lst) + peer_info->_peer_dirty = TRUE; + c_list_for_each_entry (peer_info, &priv->peer_initializing_lst_head, _peer_lst) + peer_info->_peer_dirty = TRUE; + + for (iter = v_strv; *iter; iter++) + _peer_info_add(self, *iter); + + g_free(v_strv); + + c_list_for_each_entry_safe (peer_info, + peer_info_safe, + &priv->peer_initializing_lst_head, + _peer_lst) { + if (peer_info->_peer_dirty) + _peer_info_remove(self, &peer_info->peer_path); + } + c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_lst_head, _peer_lst) { + if (peer_info->_peer_dirty) + _peer_info_remove(self, &peer_info->peer_path); + } + } + + if (nm_g_variant_lookup(properties, "Group", "&o", &v_s)) + _p2p_group_set_path(self, v_s); +} + +/*****************************************************************************/ + +static void +assoc_return(NMSupplicantInterface *self, GError *error, const char *message) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + AssocData * assoc_data; + + assoc_data = g_steal_pointer(&priv->assoc_data); + if (!assoc_data) + return; + + if (error) { + g_dbus_error_strip_remote_error(error); + _LOGW("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: %s: %s", + NM_HASH_OBFUSCATE_PTR(assoc_data), + message, + error->message); + } else { + _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: association request successful", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + } + + if (assoc_data->add_network_data) { + /* signal that this request already completed */ + assoc_data->add_network_data->assoc_data = NULL; + } + + nm_clear_g_source(&assoc_data->fail_on_idle_id); + nm_clear_g_cancellable(&assoc_data->cancellable); + + if (assoc_data->callback) + assoc_data->callback(self, error, assoc_data->user_data); + + g_object_unref(assoc_data->cfg); + g_slice_free(AssocData, assoc_data); +} + +void +nm_supplicant_interface_disconnect(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + /* Disconnect from the current AP */ + if ((priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) { + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + "disconnect"); + } + + _remove_network(self); + + /* Cancel any WPS enrollment, if any */ + nm_supplicant_interface_cancel_wps(self); + + /* Cancel all pending calls related to a prior connection attempt */ + if (priv->assoc_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, FALSE, "NMSupplicantInterface"); + assoc_return(self, error, "abort due to disconnect"); + } +} + +static void +disconnect_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_object NMSupplicantInterface *self = NULL; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError * error = NULL; + NMSupplicantInterfaceDisconnectCb callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!res && !strstr(error->message, "fi.w1.wpa_supplicant1.NotConnected")) { + /* an already disconnected interface is not an error*/ + g_clear_error(&error); + } + + callback(self, error, callback_user_data); +} + +void +nm_supplicant_interface_disconnect_async(NMSupplicantInterface * self, + GCancellable * cancellable, + NMSupplicantInterfaceDisconnectCb callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(callback); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + cancellable, + disconnect_cb, + nm_utils_user_data_pack(g_object_ref(self), callback, user_data)); +} + +static void +assoc_select_network_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + if (error) + assoc_return(self, error, "failure to select network config"); + else + assoc_return(self, NULL, NULL); +} + +static void +assoc_call_select_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "SelectNetwork", + g_variant_new("(o)", priv->net_path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->assoc_data->cancellable, + assoc_select_network_cb, + self); +} + +static void +assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set network certificates"); + return; + } + + priv->assoc_data->blobs_left--; + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob added (%u left)", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + priv->assoc_data->blobs_left); + if (priv->assoc_data->blobs_left == 0) + assoc_call_select_network(self); +} + +static void +assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + AddNetworkData * add_network_data = user_data; + AssocData * assoc_data; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + GHashTable * blobs; + GHashTableIter iter; + const char * blob_name; + GBytes * blob_data; + nm_auto_ref_string NMRefString *name_owner = NULL; + nm_auto_ref_string NMRefString *object_path = NULL; + + g_clear_object(&add_network_data->shutdown_wait_obj); + + assoc_data = add_network_data->assoc_data; + if (assoc_data) + assoc_data->add_network_data = NULL; + name_owner = g_steal_pointer(&add_network_data->name_owner); + object_path = g_steal_pointer(&add_network_data->object_path); + nm_g_slice_free(add_network_data); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!assoc_data) { + if (!error) { + const char *net_path; + + /* the assoc-request was already cancelled, but the AddNetwork request succeeded. + * Cleanup the created network. + * + * This cleanup action does not work when NetworkManager is about to exit + * and leaves the mainloop. During program shutdown, we may orphan networks. */ + g_variant_get(res, "(&o)", &net_path); + g_dbus_connection_call(G_DBUS_CONNECTION(source), + name_owner->str, + object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "RemoveNetwork", + g_variant_new("(o)", net_path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + } + return; + } + + self = NM_SUPPLICANT_INTERFACE(assoc_data->self); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to add network"); + return; + } + + nm_assert(!priv->net_path); + g_variant_get(res, "(o)", &priv->net_path); + + /* Send blobs first; otherwise jump to selecting the network */ + blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg); + priv->assoc_data->blobs_left = blobs ? g_hash_table_size(blobs) : 0u; + + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s) (%u blobs left)", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + priv->net_path, + priv->assoc_data->blobs_left); + + if (priv->assoc_data->blobs_left == 0) { + assoc_call_select_network(self); + return; + } + + g_hash_table_iter_init(&iter, blobs); + while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) { + _dbus_connection_call( + self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "AddBlob", + g_variant_new("(s@ay)", blob_name, nm_utils_gbytes_to_variant_ay(blob_data)), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->assoc_data->cancellable, + assoc_add_blob_cb, + self); + } +} + +static void +add_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + AddNetworkData * add_network_data; + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + /* the association does not keep @self alive. We want to be able to remove + * the network again, even if @self is already gone. Hence, track the data + * separately. + * + * For that we also have a shutdown_wait_obj so that on exit we still wait + * to handle the response. */ + add_network_data = g_slice_new(AddNetworkData); + *add_network_data = (AddNetworkData){ + .assoc_data = priv->assoc_data, + .name_owner = nm_ref_string_ref(priv->name_owner), + .object_path = nm_ref_string_ref(priv->object_path), + .shutdown_wait_obj = g_object_new(G_TYPE_OBJECT, NULL), + }; + nm_shutdown_wait_obj_register_object(add_network_data->shutdown_wait_obj, + "supplicant-add-network"); + priv->assoc_data->add_network_data = add_network_data; + + _dbus_connection_call( + self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "AddNetwork", + g_variant_new("(@a{sv})", nm_supplicant_config_to_variant(priv->assoc_data->cfg)), + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + NULL, + assoc_add_network_cb, + add_network_data); +} + +static void +assoc_set_ap_isolation(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gboolean value; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set AP isolation"); + return; + } + + value = nm_supplicant_config_get_ap_isolation(priv->assoc_data->cfg); + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface AP isolation set to %d", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + value); + + priv->ap_isolate_needs_reset = value; + + nm_assert(priv->assoc_data->calls_left > 0); + if (--priv->assoc_data->calls_left == 0) + add_network(self); +} + +static void +assoc_set_ap_scan_cb(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set AP scan mode"); + return; + } + + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface ap_scan set to %d", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + nm_supplicant_config_get_ap_scan(priv->assoc_data->cfg)); + + nm_assert(priv->assoc_data->calls_left > 0); + if (--priv->assoc_data->calls_left == 0) + add_network(self); +} + +static gboolean +assoc_fail_on_idle_cb(gpointer user_data) +{ + NMSupplicantInterface * self = user_data; + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + + priv->assoc_data->fail_on_idle_id = 0; + g_set_error(&error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "EAP-FAST is not supported by the supplicant"); + assoc_return(self, error, "failure due to missing supplicant support"); + return G_SOURCE_REMOVE; +} + +/** + * nm_supplicant_interface_assoc: + * @self: the supplicant interface instance + * @cfg: the configuration with the data for the association + * @callback: callback invoked when the association completes or fails. + * @user_data: data for the callback. + * + * Calls AddNetwork and SelectNetwork to start associating according to @cfg. + * + * The callback is invoked exactly once (always) and always asynchronously. + * The pending association can be aborted via nm_supplicant_interface_disconnect() + * or by destroying @self. In that case, the @callback is invoked synchronously with + * an error reason indicating cancellation/disposing (see nm_utils_error_is_cancelled()). + */ +void +nm_supplicant_interface_assoc(NMSupplicantInterface * self, + NMSupplicantConfig * cfg, + NMSupplicantInterfaceAssocCb callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv; + AssocData * assoc_data; + gboolean ap_isolation; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(NM_IS_SUPPLICANT_CONFIG(cfg)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + nm_supplicant_interface_disconnect(self); + + assoc_data = g_slice_new(AssocData); + *assoc_data = (AssocData){ + .self = self, + .cfg = g_object_ref(cfg), + .callback = callback, + .user_data = user_data, + }; + + priv->assoc_data = assoc_data; + + _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: starting association...", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FAST) == NM_TERNARY_FALSE + && nm_supplicant_config_fast_required(cfg)) { + /* Make sure the supplicant supports EAP-FAST before trying to send + * it an EAP-FAST configuration. + */ + assoc_data->fail_on_idle_id = g_idle_add(assoc_fail_on_idle_cb, self); + return; + } + + assoc_data->cancellable = g_cancellable_new(); + assoc_data->calls_left++; + nm_dbus_connection_call_set( + priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApScan", + g_variant_new_uint32(nm_supplicant_config_get_ap_scan(priv->assoc_data->cfg)), + DBUS_TIMEOUT_MSEC, + assoc_data->cancellable, + assoc_set_ap_scan_cb, + self); + + ap_isolation = nm_supplicant_config_get_ap_isolation(priv->assoc_data->cfg); + if (!priv->ap_isolate_supported) { + if (ap_isolation) { + _LOGW("assoc[" NM_HASH_OBFUSCATE_PTR_FMT + "]: requested AP isolation but the supplicant doesn't support it", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + } + } else { + assoc_data->calls_left++; + /* It would be smarter to change the property only when necessary. + * However, wpa_supplicant doesn't send the PropertiesChanged + * signal for ApIsolate, and so to know the current value we would + * need first a Get call. It seems simpler to just set the value + * we want. */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApIsolate", + g_variant_new_string(ap_isolation ? "1" : "0"), + DBUS_TIMEOUT_MSEC, + assoc_data->cancellable, + assoc_set_ap_isolation, + self); + } +} + +/*****************************************************************************/ + +typedef struct { + NMSupplicantInterface * self; + GCancellable * cancellable; + NMSupplicantInterfaceRequestScanCallback callback; + gpointer user_data; +} ScanRequestData; + +static void +scan_request_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_object NMSupplicantInterface *self_keep_alive = NULL; + NMSupplicantInterface * self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + ScanRequestData * data = user_data; + gboolean cancelled = FALSE; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) { + if (!data->callback) { + /* the self instance was not kept alive. We also must not touch it. Return. */ + nm_g_object_unref(data->cancellable); + nm_g_slice_free(data); + return; + } + cancelled = TRUE; + } + + self = data->self; + if (data->callback) { + /* the self instance was kept alive. Balance the reference count. */ + self_keep_alive = self; + } + + /* we don't propagate the error/success. That is, because either answer is not + * reliable. What is important to us is whether the request completed, and + * the current nm_supplicant_interface_get_scanning() state. */ + if (cancelled) + _LOGD("request-scan: request cancelled"); + else { + if (error) { + if (_nm_dbus_error_has_name(error, "fi.w1.wpa_supplicant1.Interface.ScanError")) + _LOGD("request-scan: could not get scan request result: %s", error->message); + else { + g_dbus_error_strip_remote_error(error); + _LOGW("request-scan: could not get scan request result: %s", error->message); + } + } else + _LOGT("request-scan: request scanning success"); + } + + if (data->callback) + data->callback(self, data->cancellable, data->user_data); + + nm_g_object_unref(data->cancellable); + nm_g_slice_free(data); +} + +void +nm_supplicant_interface_request_scan(NMSupplicantInterface * self, + GBytes *const * ssids, + guint ssids_len, + GCancellable * cancellable, + NMSupplicantInterfaceRequestScanCallback callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv; + GVariantBuilder builder; + ScanRequestData * data; + guint i; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + nm_assert((!cancellable && !callback) || (G_IS_CANCELLABLE(cancellable) && callback)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _LOGT("request-scan: request scanning (%u ssids)...", ssids_len); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active")); + g_variant_builder_add(&builder, "{sv}", "AllowRoam", g_variant_new_boolean(FALSE)); + if (ssids_len > 0) { + GVariantBuilder ssids_builder; + + g_variant_builder_init(&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY); + for (i = 0; i < ssids_len; i++) { + nm_assert(ssids[i]); + g_variant_builder_add(&ssids_builder, "@ay", nm_utils_gbytes_to_variant_ay(ssids[i])); + } + g_variant_builder_add(&builder, "{sv}", "SSIDs", g_variant_builder_end(&ssids_builder)); + } + + data = g_slice_new(ScanRequestData); + *data = (ScanRequestData){ + .self = self, + .callback = callback, + .user_data = user_data, + .cancellable = nm_g_object_ref(cancellable), + }; + + if (callback) { + /* A callback was provided. This keeps @self alive. The caller + * must provide a cancellable as the caller must never leave an asynchronous + * operation pending indefinitely. */ + nm_assert(G_IS_CANCELLABLE(cancellable)); + g_object_ref(self); + } else { + /* We don't keep @self alive, and we don't accept a cancellable either. */ + nm_assert(!cancellable); + cancellable = priv->main_cancellable; + } + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Scan", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + cancellable, + scan_request_cb, + data); +} + +/*****************************************************************************/ + +NMSupplicantInterfaceState +nm_supplicant_interface_get_state(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->state; +} + +void +_nm_supplicant_interface_set_state_down(NMSupplicantInterface *self, + gboolean force_remove_from_supplicant, + const char * reason) +{ + set_state_down(self, force_remove_from_supplicant, reason); +} + +NMRefString * +nm_supplicant_interface_get_name_owner(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->name_owner; +} + +NMRefString * +nm_supplicant_interface_get_object_path(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->object_path; +} + +const char * +nm_supplicant_interface_get_ifname(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->ifname; +} + +guint +nm_supplicant_interface_get_max_scan_ssids(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), 0); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + return priv->prop_scan_active && priv->prop_scan_ssid ? priv->max_scan_ssids : 0u; +} + +/*****************************************************************************/ + +void +nm_supplicant_interface_p2p_start_find(NMSupplicantInterface *self, guint timeout) +{ + GVariantBuilder builder; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(timeout > 0 && timeout <= 600); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Timeout", g_variant_new_int32(timeout)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Find", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + "p2p-find"); +} + +void +nm_supplicant_interface_p2p_stop_find(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "StopFind", + NULL, + G_VARIANT_TYPE("()"), + "p2p-stop-find"); +} + +/*****************************************************************************/ + +void +nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self, + const char * peer, + const char * wps_method, + const char * wps_pin) +{ + GVariantBuilder builder; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add(&builder, "{sv}", "wps_method", g_variant_new_string(wps_method)); + if (wps_pin) + g_variant_builder_add(&builder, "{sv}", "pin", g_variant_new_string(wps_pin)); + g_variant_builder_add(&builder, "{sv}", "peer", g_variant_new_object_path(peer)); + g_variant_builder_add(&builder, "{sv}", "join", g_variant_new_boolean(FALSE)); + g_variant_builder_add(&builder, "{sv}", "persistent", g_variant_new_boolean(FALSE)); + g_variant_builder_add(&builder, "{sv}", "go_intent", g_variant_new_int32(7)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Connect", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("(s)"), + "p2p-connect"); +} + +void +nm_supplicant_interface_p2p_cancel_connect(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Cancel", + NULL, + G_VARIANT_TYPE("()"), + "p2p-cancel"); +} + +void +nm_supplicant_interface_p2p_disconnect(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + "p2p-disconnect"); +} + +/*****************************************************************************/ + +static void +_properties_changed(NMSupplicantInterface *self, + const char * interface_name, + GVariant * properties, + gboolean initial) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean is_main; + + nm_assert(!properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + if (initial) + priv->starting_pending_count--; + + if ((initial || priv->is_ready_main) && nm_streq(interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) + is_main = TRUE; + else if ((initial || priv->is_ready_p2p_device) + && nm_streq(interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) { + nm_assert(_get_capability(priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE); + is_main = FALSE; + } else + return; + + g_object_freeze_notify(G_OBJECT(self)); + + priv->starting_pending_count++; + + if (is_main) { + priv->is_ready_main = TRUE; + _properties_changed_main(self, properties); + } else { + priv->is_ready_p2p_device = TRUE; + _properties_changed_p2p_device(self, properties); + } + + priv->starting_pending_count--; + _starting_check_ready(self); + + _notify_maybe_scanning(self); + _notify_maybe_p2p_available(self); + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE(user_data); + const char * interface_name; + gs_unref_variant GVariant *changed_properties = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", &interface_name, &changed_properties, NULL); + _properties_changed(self, interface_name, changed_properties, FALSE); +} + +static void +_bss_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *bss_path = NULL; + gs_unref_variant GVariant *changed_properties = NULL; + NMSupplicantBssInfo * bss_info; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + bss_path = nm_ref_string_new(object_path); + + bss_info = g_hash_table_lookup(priv->bss_idx, &bss_path); + if (!bss_info) + return; + if (bss_info->_init_cancellable) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + _bss_info_properties_changed(self, bss_info, changed_properties, FALSE); +} + +static void +_peer_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *peer_path = NULL; + gs_unref_variant GVariant *changed_properties = NULL; + NMSupplicantPeerInfo * peer_info; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + peer_path = nm_ref_string_new(object_path); + + peer_info = g_hash_table_lookup(priv->peer_idx, &peer_path); + if (!peer_info) + return; + if (peer_info->_init_cancellable) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + _peer_info_properties_changed(self, peer_info, changed_properties, FALSE); +} + +static void +_get_all_main_cb(GVariant *result, GError *error, gpointer user_data) +{ + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + _properties_changed(user_data, NM_WPAS_DBUS_IFACE_INTERFACE, properties, TRUE); +} + +static void +_get_all_p2p_device_cb(GVariant *result, GError *error, gpointer user_data) +{ + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + _properties_changed(user_data, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, properties, TRUE); +} + +static void +_signal_handle(NMSupplicantInterface *self, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char * path; + + if (nm_streq(signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) { + if (!priv->is_ready_main) + return; + + if (nm_streq(signal_name, "BSSAdded")) { + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sv})"))) + return; + + g_variant_get(parameters, "(&oa{sv})", &path, NULL); + _bss_info_add(self, path); + return; + } + + if (nm_streq(signal_name, "BSSRemoved")) { + nm_auto_ref_string NMRefString *bss_path = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) + return; + + g_variant_get(parameters, "(&o)", &path); + bss_path = nm_ref_string_new(path); + _bss_info_remove(self, &bss_path); + return; + } + + if (nm_streq(signal_name, "EAP")) { + NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN; + const char * status; + const char * parameter; + + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)"))) + return; + + g_variant_get(parameters, "(&s&s)", &status, ¶meter); + + if (nm_streq(status, "started")) + auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED; + else if (nm_streq(status, "completion")) { + if (nm_streq(parameter, "success")) + auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS; + else if (nm_streq(parameter, "failure")) + auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE; + } + + /* the state eventually reaches one of started, success or failure + * so ignore any other intermediate (unknown) state change. */ + if (auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN && auth_state != priv->auth_state) { + priv->auth_state = auth_state; + _notify(self, PROP_AUTH_STATE); + } + return; + } + + return; + } + + if (nm_streq(signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) { + if (!priv->is_ready_p2p_device) + return; + + if (nm_streq(signal_name, "DeviceFound")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) { + g_variant_get(parameters, "(&o)", &path); + _peer_info_add(self, path); + } + return; + } + + if (nm_streq(signal_name, "DeviceLost")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) { + nm_auto_ref_string NMRefString *peer_path = NULL; + + g_variant_get(parameters, "(&o)", &path); + peer_path = nm_ref_string_new(path); + _peer_info_remove(self, &peer_path); + } + return; + } + + if (nm_streq(signal_name, "GroupStarted")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) { + gs_unref_variant GVariant *args = NULL; + gs_unref_object NMSupplicantInterface *iface = NULL; + const char * group_path; + const char * iface_path; + + g_variant_get(parameters, "(@a{sv})", &args); + if (!g_variant_lookup(args, "group_object", "&o", &group_path)) + return; + if (!g_variant_lookup(args, "interface_object", "&o", &iface_path)) + return; + + if (nm_streq(iface_path, priv->object_path->str)) { + _LOGW("P2P: GroupStarted on existing interface"); + iface = g_object_ref(self); + } else { + iface = + nm_supplicant_manager_create_interface_from_path(priv->supplicant_manager, + iface_path); + if (iface == NULL) { + _LOGW("P2P: Group interface already exists in GroupStarted handler, " + "aborting further processing."); + return; + } + } + + /* Signal existence of the (new) interface. */ + g_signal_emit(self, signals[GROUP_STARTED], 0, iface); + } + return; + } + + if (nm_streq(signal_name, "GroupFinished")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) { + gs_unref_variant GVariant *args = NULL; + const char * iface_path; + + g_variant_get(parameters, "(@a{sv})", &args); + + /* TODO: Group finished is called on the management interface! + * This means the signal consumer will currently need to assume which + * interface is finishing or it needs to match the object paths. + */ + if (!g_variant_lookup(args, "interface_object", "&o", &iface_path)) + return; + + _LOGD("P2P: GroupFinished signal on interface %s for interface %s", + priv->object_path->str, + iface_path); + + /* Signal group finish interface (on management interface). */ + g_signal_emit(self, signals[GROUP_FINISHED], 0, iface_path); + } + return; + } + + return; + } +} + +static void +_signal_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = user_data; + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + priv->starting_pending_count++; + _signal_handle(self, signal_interface_name, signal_name, parameters); + priv->starting_pending_count--; + _starting_check_ready(self); + + _notify_maybe_scanning(self); +} + +/*****************************************************************************/ + +gboolean +nm_supplicant_interface_get_p2p_available(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_capable_cached; +} + +gboolean +nm_supplicant_interface_get_p2p_group_joined(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_joined_cached; +} + +const char * +nm_supplicant_interface_get_p2p_group_path(NMSupplicantInterface *self) +{ + return nm_ref_string_get_str(NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_path); +} + +gboolean +nm_supplicant_interface_get_p2p_group_owner(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_owner_cached; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_SCANNING: + g_value_set_boolean(value, nm_supplicant_interface_get_scanning(self)); + break; + case PROP_CURRENT_BSS: + g_value_set_string(value, + nm_ref_string_get_str(nm_supplicant_interface_get_current_bss(self))); + break; + case PROP_P2P_GROUP_JOINED: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_group_joined(self)); + break; + case PROP_P2P_GROUP_PATH: + g_value_set_string(value, nm_supplicant_interface_get_p2p_group_path(self)); + break; + case PROP_P2P_GROUP_OWNER: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_group_owner(self)); + break; + case PROP_P2P_AVAILABLE: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_available(self)); + break; + case PROP_AUTH_STATE: + g_value_set_uint(value, priv->auth_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_SUPPLICANT_MANAGER: + /* construct-only */ + priv->supplicant_manager = g_object_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_SUPPLICANT_MANAGER(priv->supplicant_manager)); + + priv->dbus_connection = + g_object_ref(nm_supplicant_manager_get_dbus_connection(priv->supplicant_manager)); + nm_assert(G_IS_DBUS_CONNECTION(priv->dbus_connection)); + + priv->name_owner = + nm_ref_string_ref(nm_supplicant_manager_get_dbus_name_owner(priv->supplicant_manager)); + nm_assert(NM_IS_REF_STRING(priv->name_owner)); + + priv->global_capabilities = + nm_supplicant_manager_get_global_capabilities(priv->supplicant_manager); + break; + case PROP_DBUS_OBJECT_PATH: + /* construct-only */ + priv->object_path = nm_ref_string_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_REF_STRING(priv->object_path)); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + break; + case PROP_DRIVER: + /* construct-only */ + priv->requested_driver = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_supplicant_interface_init(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, + NM_TYPE_SUPPLICANT_INTERFACE, + NMSupplicantInterfacePrivate); + + self->_priv = priv; + + nm_assert(priv->global_capabilities == NM_SUPPL_CAP_MASK_NONE); + nm_assert(priv->iface_capabilities == NM_SUPPL_CAP_MASK_NONE); + + priv->state = NM_SUPPLICANT_INTERFACE_STATE_STARTING; + priv->supp_state = NM_SUPPLICANT_INTERFACE_STATE_INVALID; + priv->last_scan_msec = -1; + + c_list_init(&self->supp_lst); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSupplicantBssInfo, bss_path) == 0); + priv->bss_idx = g_hash_table_new(nm_pdirect_hash, nm_pdirect_equal); + + c_list_init(&priv->bss_lst_head); + c_list_init(&priv->bss_initializing_lst_head); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSupplicantPeerInfo, peer_path) == 0); + priv->peer_idx = g_hash_table_new(nm_pdirect_hash, nm_pdirect_equal); + + c_list_init(&priv->peer_lst_head); + c_list_init(&priv->peer_initializing_lst_head); + + priv->main_cancellable = g_cancellable_new(); +} + +static void +constructed(GObject *object) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_supplicant_interface_parent_class)->constructed(object); + + _LOGD("new supplicant interface %s on %s", priv->object_path->str, priv->name_owner->str); + + priv->properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NULL, + _properties_changed_cb, + self, + NULL); + + priv->bss_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + NULL, + NM_WPAS_DBUS_IFACE_BSS, + _bss_properties_changed_cb, + self, + NULL); + + priv->signal_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner->str, + NULL, + NULL, + priv->object_path->str, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + _signal_cb, + self, + NULL); + + /* Scan result aging parameters */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireAge", + g_variant_new_uint32(250), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireCount", + g_variant_new_uint32(2), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF) == NM_TERNARY_TRUE) { + /* Initialize global PMF setting to 'optional' */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Pmf", + g_variant_new_string("1"), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + } + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT) { + /* If the global supplicant capabilities property is not present, we can + * fall back to checking whether the ProbeRequest method is supported. If + * neither of these works we have no way of determining if AP mode is + * supported or not. hostap 1.0 and earlier don't support either of these. + */ + priv->starting_pending_count++; + _dbus_connection_call(self, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect", + NULL, + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + priv->main_cancellable, + iface_introspect_cb, + self); + } + + priv->starting_pending_count++; + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + 5000, + priv->main_cancellable, + _get_all_main_cb, + self); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE) { + priv->peer_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + NULL, + NM_WPAS_DBUS_IFACE_PEER, + _peer_properties_changed_cb, + self, + NULL); + + priv->starting_pending_count++; + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + 5000, + priv->main_cancellable, + _get_all_p2p_device_cb, + self); + } +} + +NMSupplicantInterface * +nm_supplicant_interface_new(NMSupplicantManager *supplicant_manager, + NMRefString * object_path, + int ifindex, + NMSupplicantDriver driver) +{ + nm_assert(NM_IS_SUPPLICANT_MANAGER(supplicant_manager)); + + return g_object_new(NM_TYPE_SUPPLICANT_INTERFACE, + NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, + supplicant_manager, + NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, + object_path, + NM_SUPPLICANT_INTERFACE_IFINDEX, + ifindex, + NM_SUPPLICANT_INTERFACE_DRIVER, + (guint) driver, + NULL); +} + +static void +dispose(GObject *object) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN) + set_state_down(self, TRUE, "NMSupplicantInterface is disposing"); + + nm_assert(c_list_is_empty(&self->supp_lst)); + + if (priv->wps_data) { + /* we shut down, but an asynchronous Cancel request is pending. + * We don't want to cancel it, so mark wps-data that @self is gone. + * This way, _wps_handle_cancel_cb() knows it must no longer touch + * @self */ + priv->wps_data->self = NULL; + priv->wps_data = NULL; + } + + nm_assert(!priv->assoc_data); + + nm_clear_pointer(&priv->bss_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->peer_idx, g_hash_table_destroy); + + nm_clear_pointer(&priv->current_bss, nm_ref_string_unref); + + G_OBJECT_CLASS(nm_supplicant_interface_parent_class)->dispose(object); + + nm_clear_pointer(&priv->object_path, nm_ref_string_unref); + nm_clear_pointer(&priv->name_owner, nm_ref_string_unref); + g_clear_object(&priv->supplicant_manager); + g_clear_object(&priv->dbus_connection); + nm_clear_g_free(&priv->ifname); + nm_clear_g_free(&priv->driver); + nm_assert(!priv->net_path); +} + +static void +nm_supplicant_interface_class_init(NMSupplicantInterfaceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMSupplicantInterfacePrivate)); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->set_property = set_property; + object_class->get_property = get_property; + + obj_properties[PROP_SUPPLICANT_MANAGER] = + g_param_spec_pointer(NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DBUS_OBJECT_PATH] = + g_param_spec_pointer(NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_SUPPLICANT_INTERFACE_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DRIVER] = + g_param_spec_uint(NM_SUPPLICANT_INTERFACE_DRIVER, + "", + "", + 0, + G_MAXUINT, + NM_SUPPLICANT_DRIVER_WIRELESS, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_SCANNING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CURRENT_BSS] = + g_param_spec_string(NM_SUPPLICANT_INTERFACE_CURRENT_BSS, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_JOINED] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_PATH] = + g_param_spec_string(NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_OWNER] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_AVAILABLE] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_AUTH_STATE] = g_param_spec_uint(NM_SUPPLICANT_INTERFACE_AUTH_STATE, + "", + "", + NM_SUPPLICANT_AUTH_STATE_UNKNOWN, + _NM_SUPPLICANT_AUTH_STATE_NUM - 1, + NM_SUPPLICANT_AUTH_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[STATE] = g_signal_new(NM_SUPPLICANT_INTERFACE_STATE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT); + + signals[BSS_CHANGED] = g_signal_new(NM_SUPPLICANT_INTERFACE_BSS_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_BOOLEAN); + + signals[PEER_CHANGED] = g_signal_new(NM_SUPPLICANT_INTERFACE_PEER_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_BOOLEAN); + + signals[WPS_CREDENTIALS] = g_signal_new(NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_VARIANT); + + signals[GROUP_STARTED] = g_signal_new(NM_SUPPLICANT_INTERFACE_GROUP_STARTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_SUPPLICANT_INTERFACE); + + signals[GROUP_FINISHED] = g_signal_new(NM_SUPPLICANT_INTERFACE_GROUP_FINISHED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} diff --git a/src/core/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h new file mode 100644 index 0000000..a62eeb6 --- /dev/null +++ b/src/core/supplicant/nm-supplicant-interface.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2017 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NM_SUPPLICANT_INTERFACE_H__ +#define __NM_SUPPLICANT_INTERFACE_H__ + +#include "nm-supplicant-types.h" + +#include "c-list/src/c-list.h" + +/* + * Supplicant interface states + * A mix of wpa_supplicant interface states and internal states. + */ +typedef enum { + NM_SUPPLICANT_INTERFACE_STATE_INVALID = 0, + + NM_SUPPLICANT_INTERFACE_STATE_STARTING = 1, + + NM_SUPPLICANT_INTERFACE_STATE_DISABLED, + NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, + NM_SUPPLICANT_INTERFACE_STATE_INACTIVE, + NM_SUPPLICANT_INTERFACE_STATE_SCANNING, + NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, + NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_COMPLETED, + + NM_SUPPLICANT_INTERFACE_STATE_DOWN, +} NMSupplicantInterfaceState; + +static inline gboolean +nm_supplicant_interface_state_is_operational(NMSupplicantInterfaceState state) +{ + return state > NM_SUPPLICANT_INTERFACE_STATE_STARTING + && state < NM_SUPPLICANT_INTERFACE_STATE_DOWN; +} + +static inline gboolean +nm_supplicant_interface_state_is_associated(NMSupplicantInterfaceState state) +{ + return state >= NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING + && state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED; +} + +typedef enum { + NM_SUPPLICANT_AUTH_STATE_UNKNOWN, + NM_SUPPLICANT_AUTH_STATE_STARTED, + NM_SUPPLICANT_AUTH_STATE_SUCCESS, + NM_SUPPLICANT_AUTH_STATE_FAILURE, + _NM_SUPPLICANT_AUTH_STATE_NUM, +} NMSupplicantAuthState; + +#define NM_TYPE_SUPPLICANT_INTERFACE (nm_supplicant_interface_get_type()) +#define NM_SUPPLICANT_INTERFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterface)) +#define NM_SUPPLICANT_INTERFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass)) +#define NM_IS_SUPPLICANT_INTERFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SUPPLICANT_INTERFACE)) +#define NM_IS_SUPPLICANT_INTERFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SUPPLICANT_INTERFACE)) +#define NM_SUPPLICANT_INTERFACE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass)) + +#define NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER "supplicant-manager" +#define NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH "dbus-object-path" +#define NM_SUPPLICANT_INTERFACE_IFINDEX "ifindex" +#define NM_SUPPLICANT_INTERFACE_SCANNING "scanning" +#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS "current-bss" +#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED "p2p-group-joined" +#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH "p2p-group-path" +#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER "p2p-group-owner" +#define NM_SUPPLICANT_INTERFACE_DRIVER "driver" +#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE "p2p-available" +#define NM_SUPPLICANT_INTERFACE_AUTH_STATE "auth-state" + +#define NM_SUPPLICANT_INTERFACE_STATE "state" +#define NM_SUPPLICANT_INTERFACE_BSS_CHANGED "bss-changed" +#define NM_SUPPLICANT_INTERFACE_PEER_CHANGED "peer-changed" +#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS "wps-credentials" +#define NM_SUPPLICANT_INTERFACE_GROUP_STARTED "group-started" +#define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED "group-finished" + +typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass; + +struct _NMSupplicantInterfacePrivate; + +struct _NMSupplicantInterface { + GObject parent; + CList supp_lst; + struct _NMSupplicantInterfacePrivate *_priv; +}; + +GType nm_supplicant_interface_get_type(void); + +NMSupplicantInterface *nm_supplicant_interface_new(NMSupplicantManager *supplicant_manager, + NMRefString * object_path, + int ifindex, + NMSupplicantDriver driver); + +NMRefString *nm_supplicant_interface_get_name_owner(NMSupplicantInterface *self); +NMRefString *nm_supplicant_interface_get_object_path(NMSupplicantInterface *iface); + +void _nm_supplicant_interface_set_state_down(NMSupplicantInterface *self, + gboolean force_remove_from_supplicant, + const char * reason); + +typedef void (*NMSupplicantInterfaceAssocCb)(NMSupplicantInterface *iface, + GError * error, + gpointer user_data); + +void nm_supplicant_interface_assoc(NMSupplicantInterface * self, + NMSupplicantConfig * cfg, + NMSupplicantInterfaceAssocCb callback, + gpointer user_data); + +void nm_supplicant_interface_disconnect(NMSupplicantInterface *iface); + +typedef void (*NMSupplicantInterfaceDisconnectCb)(NMSupplicantInterface *iface, + GError * error, + gpointer user_data); + +void nm_supplicant_interface_disconnect_async(NMSupplicantInterface * self, + GCancellable * cancellable, + NMSupplicantInterfaceDisconnectCb callback, + gpointer user_data); + +typedef void (*NMSupplicantInterfaceRequestScanCallback)(NMSupplicantInterface *self, + GCancellable * cancellable, + gpointer user_data); + +void nm_supplicant_interface_request_scan(NMSupplicantInterface * self, + GBytes *const * ssids, + guint ssids_len, + GCancellable * cancellable, + NMSupplicantInterfaceRequestScanCallback callback, + gpointer user_data); + +NMSupplicantInterfaceState nm_supplicant_interface_get_state(NMSupplicantInterface *self); + +const char *nm_supplicant_interface_state_to_string(NMSupplicantInterfaceState state); + +gboolean nm_supplicant_interface_get_scanning(NMSupplicantInterface *self); + +NMRefString *nm_supplicant_interface_get_current_bss(NMSupplicantInterface *self); + +gint64 nm_supplicant_interface_get_last_scan(NMSupplicantInterface *self); + +const char *nm_supplicant_interface_get_ifname(NMSupplicantInterface *self); + +guint nm_supplicant_interface_get_max_scan_ssids(NMSupplicantInterface *self); + +gboolean nm_supplicant_interface_get_p2p_available(NMSupplicantInterface *self); + +gboolean nm_supplicant_interface_get_p2p_group_joined(NMSupplicantInterface *self); + +const char *nm_supplicant_interface_get_p2p_group_path(NMSupplicantInterface *self); + +gboolean nm_supplicant_interface_get_p2p_group_owner(NMSupplicantInterface *self); + +void nm_supplicant_interface_p2p_start_find(NMSupplicantInterface *self, guint timeout); +void nm_supplicant_interface_p2p_stop_find(NMSupplicantInterface *self); + +void nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self, + const char * peer, + const char * wps_method, + const char * wps_pin); +void nm_supplicant_interface_p2p_cancel_connect(NMSupplicantInterface *self); +void nm_supplicant_interface_p2p_disconnect(NMSupplicantInterface *self); + +void nm_supplicant_interface_set_global_capabilities(NMSupplicantInterface *self, + NMSupplCapMask value); + +NMTernary nm_supplicant_interface_get_capability(NMSupplicantInterface *self, NMSupplCapType type); + +NMSupplCapMask nm_supplicant_interface_get_capabilities(NMSupplicantInterface *self); + +void nm_supplicant_interface_enroll_wps(NMSupplicantInterface *self, + const char *const type, + const char * bssid, + const char * pin); + +void nm_supplicant_interface_cancel_wps(NMSupplicantInterface *self); + +NMSupplicantAuthState nm_supplicant_interface_get_auth_state(NMSupplicantInterface *self); + +void nm_supplicant_interface_set_bridge(NMSupplicantInterface *self, const char *bridge); + +#endif /* __NM_SUPPLICANT_INTERFACE_H__ */ diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c new file mode 100644 index 0000000..30b15cc --- /dev/null +++ b/src/core/supplicant/nm-supplicant-manager.c @@ -0,0 +1,1358 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2010 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-manager.h" + +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-supplicant-interface.h" +#include "nm-supplicant-types.h" +#include "platform/nm-platform.h" + +/*****************************************************************************/ + +#define CREATE_IFACE_TRY_COUNT_MAX 7u + +struct _NMSupplMgrCreateIfaceHandle { + NMSupplicantManager * self; + CList create_iface_lst; + GCancellable * cancellable; + NMSupplicantManagerCreateInterfaceCb callback; + gpointer callback_user_data; + NMShutdownWaitObjHandle * shutdown_handle; + NMRefString * name_owner; + GError * fail_on_idle_error; + NMSupplicantDriver driver; + int ifindex; + guint fail_on_idle_id; + guint create_iface_try_count : 5; +}; + +enum { + AVAILABLE_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GDBusConnection *dbus_connection; + + NMRefString *name_owner; + + GCancellable *get_name_owner_cancellable; + GCancellable *get_capabilities_cancellable; + GCancellable *poke_name_owner_cancellable; + + GHashTable *supp_ifaces; + CList supp_lst_head; + + CList create_iface_lst_head; + + NMSupplCapMask capabilities; + + guint name_owner_changed_id; + guint interface_removed_id; + guint poke_name_owner_timeout_id; + guint available_reset_id; + + /* see nm_supplicant_manager_get_available(). */ + NMTernary available : 2; + +} NMSupplicantManagerPrivate; + +struct _NMSupplicantManager { + GObject parent; + NMSupplicantManagerPrivate _priv; +}; + +struct _NMSupplicantManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER) + +NM_DEFINE_SINGLETON_GETTER(NMSupplicantManager, + nm_supplicant_manager_get, + NM_TYPE_SUPPLICANT_MANAGER); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SUPPLICANT +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "supplicant", __VA_ARGS__) + +/*****************************************************************************/ + +NM_CACHED_QUARK_FCN("nm-supplicant-error-quark", nm_supplicant_error_quark); + +/*****************************************************************************/ + +static void _create_iface_proceed_all(NMSupplicantManager *self, GError *error); +static void _supp_iface_add(NMSupplicantManager * self, + NMRefString * iface_path, + NMSupplicantInterface *supp_iface); +static void _supp_iface_remove_one(NMSupplicantManager * self, + NMSupplicantInterface *supp_iface, + gboolean force_remove_from_supplicant, + const char * reason); +static void _create_iface_dbus_call_get_interface(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + const char * ifname); +static void _create_iface_dbus_call_create_interface(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + const char * ifname); +static gboolean _create_iface_fail_on_idle_cb(gpointer user_data); + +static gboolean _available_reset_cb(gpointer user_data); + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE(nm_supplicant_driver_to_string, + NMSupplicantDriver, + NM_UTILS_LOOKUP_DEFAULT_WARN(NULL), + NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_UNKNOWN, "???"), + NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRELESS, + NM_WPAS_DEFAULT_WIFI_DRIVER), + NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRED, "wired"), + NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_MACSEC, "macsec_linux"), ); + +/*****************************************************************************/ + +NMTernary +nm_supplicant_manager_is_available(NMSupplicantManager *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_TERNARY_FALSE); + + return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->available; +} + +NMRefString * +nm_supplicant_manager_get_dbus_name_owner(NMSupplicantManager *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL); + + return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->name_owner; +} + +GDBusConnection * +nm_supplicant_manager_get_dbus_connection(NMSupplicantManager *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL); + + return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection; +} + +NMSupplCapMask +nm_supplicant_manager_get_global_capabilities(NMSupplicantManager *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_SUPPL_CAP_MASK_NONE); + + return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->capabilities; +} + +/*****************************************************************************/ + +static void +_caps_set(NMSupplicantManagerPrivate *priv, NMSupplCapType type, NMTernary value) +{ + priv->capabilities = NM_SUPPL_CAP_MASK_SET(priv->capabilities, type, value); +} + +static char +_caps_to_char(NMSupplicantManagerPrivate *priv, NMSupplCapType type) +{ + NMTernary val; + + val = NM_SUPPL_CAP_MASK_GET(priv->capabilities, type); + if (val == NM_TERNARY_TRUE) + return '+'; + if (val == NM_TERNARY_FALSE) + return '-'; + return '?'; +} + +/*****************************************************************************/ + +static void +_dbus_call_remove_interface(GDBusConnection *dbus_connection, + const char * name_owner, + const char * iface_path) +{ + nm_assert(G_IS_DBUS_CONNECTION(dbus_connection)); + nm_assert(name_owner); + nm_assert(iface_path); + + g_dbus_connection_call(dbus_connection, + name_owner, + NM_WPAS_DBUS_PATH, + NM_WPAS_DBUS_INTERFACE, + "RemoveInterface", + g_variant_new("(o)", iface_path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 10000, + NULL, + NULL, + NULL); +} + +void +_nm_supplicant_manager_dbus_call_remove_interface(NMSupplicantManager *self, + const char * name_owner, + const char * iface_path) +{ + _dbus_call_remove_interface(NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection, + name_owner, + iface_path); +} + +/*****************************************************************************/ + +static void +on_supplicant_wfd_ies_set(GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), result, &error); + if (!res) + _LOGD("failed to set WFD IEs on wpa_supplicant: %s", error->message); +} + +/** + * nm_supplicant_manager_set_wfd_ies: + * @self: the #NMSupplicantManager + * @wfd_ies: a #GBytes with the WFD IEs or %NULL + * + * This function sets the global WFD IEs on wpa_supplicant. Note that + * it would make more sense if this was per-device, but wpa_supplicant + * simply does not work that way. + * */ +void +nm_supplicant_manager_set_wfd_ies(NMSupplicantManager *self, GBytes *wfd_ies) +{ + NMSupplicantManagerPrivate *priv; + GVariantBuilder params; + + g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(self)); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + if (!priv->name_owner) + return; + + _LOGD("setting WFD IEs for P2P operation on %s", priv->name_owner->str); + + g_variant_builder_init(¶ms, G_VARIANT_TYPE("(ssv)")); + + g_variant_builder_add(¶ms, "s", NM_WPAS_DBUS_INTERFACE); + g_variant_builder_add(¶ms, "s", "WFDIEs"); + g_variant_builder_add_value(¶ms, + g_variant_new_variant(nm_utils_gbytes_to_variant_ay(wfd_ies))); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_PATH, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_builder_end(¶ms), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + NULL, + on_supplicant_wfd_ies_set, + NULL); +} + +/*****************************************************************************/ + +static gboolean +_poke_name_owner_timeout_cb(gpointer user_data) +{ + NMSupplicantManager * self = user_data; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + gboolean available_changed = FALSE; + + nm_assert(!priv->name_owner); + + priv->poke_name_owner_timeout_id = 0; + nm_clear_g_cancellable(&priv->poke_name_owner_cancellable); + + _LOGT("poke service \"%s\" failed for good with timeout%s", + NM_WPAS_DBUS_SERVICE, + (priv->available == NM_TERNARY_DEFAULT) ? " (set as not available)" : ""); + + if (priv->available == NM_TERNARY_DEFAULT) { + /* the available flag usually only changes together with the name-owner. + * However, if we tries to poke the service but failed to start it (with + * timeout), was also set it as (hard) not available. */ + priv->available = NM_TERNARY_FALSE; + nm_clear_g_source(&priv->available_reset_id); + priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self); + available_changed = TRUE; + } + + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "Failed to D-Bus activate wpa_supplicant service"); + + _create_iface_proceed_all(self, error); + + if (available_changed) { + /* We delay the emitting of the notification after aborting all + * create-iface handles. */ + g_signal_emit(self, signals[AVAILABLE_CHANGED], 0); + } + + return G_SOURCE_REMOVE; +} + +static void +_poke_name_owner_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + if (!res) + _LOGT("poke service \"%s\" failed: %s", NM_WPAS_DBUS_SERVICE, error->message); + else + _LOGT("poke service \"%s\" succeeded", NM_WPAS_DBUS_SERVICE); + + /* in both cases, we react the same: we wait for the name owner to appear + * or hit the timeout. */ +} + +static void +_poke_name_owner(NMSupplicantManager *self) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + if (priv->poke_name_owner_cancellable) + return; + + _LOGT("poke service \"%s\"...", NM_WPAS_DBUS_SERVICE); + + priv->poke_name_owner_cancellable = g_cancellable_new(); + priv->poke_name_owner_timeout_id = g_timeout_add(3000, _poke_name_owner_timeout_cb, self); + nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, + NM_WPAS_DBUS_SERVICE, + 5000, + priv->poke_name_owner_cancellable, + _poke_name_owner_cb, + self); +} + +/*****************************************************************************/ + +static void +_create_iface_complete(NMSupplMgrCreateIfaceHandle *handle, + NMSupplicantInterface * supp_iface, + GError * error) +{ + nm_assert(!supp_iface || NM_IS_SUPPLICANT_INTERFACE(supp_iface)); + nm_assert((!!supp_iface) != (!!error)); + + c_list_unlink(&handle->create_iface_lst); + + nm_clear_g_source(&handle->fail_on_idle_id); + + if (handle->callback) { + NMSupplicantManagerCreateInterfaceCb callback; + + nm_assert(NM_IS_SUPPLICANT_MANAGER(handle->self)); + + callback = handle->callback; + handle->callback = NULL; + callback(handle->self, handle, supp_iface, error, handle->callback_user_data); + } + + g_clear_error(&handle->fail_on_idle_error); + + g_clear_object(&handle->self); + + if (handle->shutdown_handle) { + /* we have a pending CreateInterface request. We keep the handle + * instance alive. This is to remove the device again, once the + * request completes. */ + return; + } + + nm_clear_g_cancellable(&handle->cancellable); + nm_ref_string_unref(handle->name_owner); + + nm_g_slice_free_fcn(handle); +} + +static void +_create_iface_add(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + const char * iface_path_str, + gboolean created_by_us) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *iface_path = NULL; + gs_unref_object NMSupplicantInterface *supp_iface = NULL; + + iface_path = nm_ref_string_new(iface_path_str); + + supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path); + if (supp_iface) { + /* Now this is odd... Reuse the same interface. */ + g_object_ref(supp_iface); + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: interface %s on %s created (already existing)", + NM_HASH_OBFUSCATE_PTR(handle), + iface_path_str, + priv->name_owner->str); + _create_iface_complete(handle, supp_iface, NULL); + return; + } + + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface %s on %s created%s", + NM_HASH_OBFUSCATE_PTR(handle), + iface_path_str, + priv->name_owner->str, + created_by_us ? " (created by us)" : ""); + + supp_iface = nm_supplicant_interface_new(self, iface_path, handle->ifindex, handle->driver); + + _supp_iface_add(self, iface_path, supp_iface); + + _create_iface_complete(handle, supp_iface, NULL); +} + +static void +_create_iface_dbus_call_get_interface_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source); + NMSupplMgrCreateIfaceHandle *handle; + NMSupplicantManager * self; + NMSupplicantManagerPrivate * priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * iface_path_str; + + res = g_dbus_connection_call_finish(dbus_connection, result, &error); + + if (nm_utils_error_is_cancelled(error)) + return; + + handle = user_data; + nm_assert(handle->callback); + + self = handle->self; + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + nm_assert(handle->name_owner == priv->name_owner); + + if (!res) { + char ifname[NMP_IFNAMSIZ]; + + if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX + && _nm_dbus_error_has_name(error, NM_WPAS_ERROR_UNKNOWN_IFACE) + && nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) { + /* Before, supplicant told us the interface existed. Was there a race? + * Try again. */ + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: D-Bus call failed to get interface. Try to create it again (ifname \"%s\")", + NM_HASH_OBFUSCATE_PTR(handle), + ifname); + _create_iface_dbus_call_create_interface(self, handle, ifname); + return; + } + + g_clear_object(&handle->cancellable); + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call to get interface failed: %s", + NM_HASH_OBFUSCATE_PTR(handle), + error->message); + _create_iface_complete(handle, NULL, error); + return; + } + + g_clear_object(&handle->cancellable); + + g_variant_get(res, "(&o)", &iface_path_str); + + _create_iface_add(self, handle, iface_path_str, FALSE); +} + +static void +_create_iface_dbus_call_create_interface_cb(GObject * source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source); + NMSupplMgrCreateIfaceHandle *handle = user_data; + NMSupplicantManager * self; + NMSupplicantManagerPrivate * priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * iface_path_str; + char ifname[NMP_IFNAMSIZ]; + + res = g_dbus_connection_call_finish(dbus_connection, result, &error); + + nm_shutdown_wait_obj_unregister(g_steal_pointer(&handle->shutdown_handle)); + + if (!res) { + if (handle->callback && ({ + nm_assert(handle->self); + TRUE; + }) + && _nm_dbus_error_has_name(error, NM_WPAS_ERROR_EXISTS_ERROR) + && nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) { + self = handle->self; + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: D-Bus call failed to create interface. Try to get existing interface (ifname " + "\"%s\")", + NM_HASH_OBFUSCATE_PTR(handle), + ifname); + _create_iface_dbus_call_get_interface(self, handle, ifname); + return; + } + g_clear_object(&handle->cancellable); + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call failed: %s", + NM_HASH_OBFUSCATE_PTR(handle), + error->message); + _create_iface_complete(handle, NULL, error); + return; + } + + g_clear_object(&handle->cancellable); + + self = handle->self; + priv = self ? NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) : NULL; + + g_variant_get(res, "(&o)", &iface_path_str); + + if (!handle->callback || priv->name_owner != handle->name_owner) { + if (!handle->callback) { + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: request already cancelled but still remove interface %s in %s", + NM_HASH_OBFUSCATE_PTR(handle), + iface_path_str, + handle->name_owner->str); + nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "Request already cancelled"); + } else { + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: name owner changed, still remove interface %s in %s", + NM_HASH_OBFUSCATE_PTR(handle), + iface_path_str, + handle->name_owner->str); + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "The name owner changed since creating the interface"); + } + _dbus_call_remove_interface(dbus_connection, handle->name_owner->str, iface_path_str); + _create_iface_complete(handle, NULL, error); + return; + } + + _create_iface_add(self, handle, iface_path_str, TRUE); +} + +static void +_create_iface_dbus_call_get_interface(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + const char * ifname) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + nm_assert(handle->cancellable); + nm_assert(!handle->shutdown_handle); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_PATH, + NM_WPAS_DBUS_INTERFACE, + "GetInterface", + g_variant_new("(s)", ifname), + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + handle->cancellable, + _create_iface_dbus_call_get_interface_cb, + handle); +} + +static void +_create_iface_dbus_call_create_interface(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + const char * ifname) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + GVariantBuilder builder; + + nm_assert(priv->name_owner == handle->name_owner); + nm_assert(handle->cancellable); + nm_assert(!handle->shutdown_handle); + nm_assert(handle->create_iface_try_count <= CREATE_IFACE_TRY_COUNT_MAX); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, + "{sv}", + "Driver", + g_variant_new_string(nm_supplicant_driver_to_string(handle->driver))); + g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(ifname)); + + handle->shutdown_handle = nm_shutdown_wait_obj_register_cancellable_full( + handle->cancellable, + g_strdup_printf("wpas-create-" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(handle)), + TRUE); + handle->create_iface_try_count++; + g_dbus_connection_call(priv->dbus_connection, + handle->name_owner->str, + NM_WPAS_DBUS_PATH, + NM_WPAS_DBUS_INTERFACE, + "CreateInterface", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + handle->cancellable, + _create_iface_dbus_call_create_interface_cb, + handle); +} + +static void +_create_iface_dbus_start(NMSupplicantManager *self, NMSupplMgrCreateIfaceHandle *handle) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + char ifname[NMP_IFNAMSIZ]; + + nm_assert(priv->name_owner); + nm_assert(!handle->cancellable); + + if (!nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) { + nm_utils_error_set(&handle->fail_on_idle_error, + NM_UTILS_ERROR_UNKNOWN, + "Cannot find interface %d", + handle->ifindex); + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: creating interface fails to find interface name for ifindex %d", + NM_HASH_OBFUSCATE_PTR(handle), + handle->ifindex); + handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle); + return; + } + + /* Our handle keeps @self alive. That means, when NetworkManager shall shut + * down, it's the responsibility of the callers to cancel the handles, + * to initiate coordinated shutdown. + * + * However, we now issue a CreateInterface call. Even if the handle gets cancelled + * (because of shutdown, or because the caller is no longer interested in the + * result), we don't want to cancel this request. Instead, we want to get + * the interface path and remove it right away. + * + * That means, the D-Bus call cannot be cancelled (because we always care about + * the result). Only the @handle can be cancelled, but parts of the handle will + * stick around to complete the task. + * + * See also handle->shutdown_handle. + */ + handle->name_owner = nm_ref_string_ref(priv->name_owner); + handle->cancellable = g_cancellable_new(); + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: creating interface (ifname \"%s\")...", + NM_HASH_OBFUSCATE_PTR(handle), + ifname); + _create_iface_dbus_call_create_interface(self, handle, ifname); +} + +static gboolean +_create_iface_fail_on_idle_cb(gpointer user_data) +{ + NMSupplMgrCreateIfaceHandle *handle = user_data; + + handle->fail_on_idle_id = 0; + + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: fail with internal error: %s", + NM_HASH_OBFUSCATE_PTR(handle), + handle->fail_on_idle_error->message); + + _create_iface_complete(handle, NULL, handle->fail_on_idle_error); + return G_SOURCE_REMOVE; +} + +NMSupplMgrCreateIfaceHandle * +nm_supplicant_manager_create_interface(NMSupplicantManager * self, + int ifindex, + NMSupplicantDriver driver, + NMSupplicantManagerCreateInterfaceCb callback, + gpointer user_data) +{ + NMSupplicantManagerPrivate * priv; + NMSupplMgrCreateIfaceHandle *handle; + + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(callback, NULL); + nm_assert(nm_supplicant_driver_to_string(driver)); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + handle = g_slice_new(NMSupplMgrCreateIfaceHandle); + *handle = (NMSupplMgrCreateIfaceHandle){ + .self = g_object_ref(self), + .callback = callback, + .callback_user_data = user_data, + .driver = driver, + .ifindex = ifindex, + }; + c_list_link_tail(&priv->create_iface_lst_head, &handle->create_iface_lst); + + if (!priv->dbus_connection) { + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: new request interface %d (driver %s). Fail because no D-Bus connection to talk " + "to wpa_supplicant...", + NM_HASH_OBFUSCATE_PTR(handle), + ifindex, + nm_supplicant_driver_to_string(driver)); + nm_utils_error_set(&handle->fail_on_idle_error, + NM_UTILS_ERROR_UNKNOWN, + "No D-Bus connection to talk to wpa_supplicant"); + handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle); + return handle; + } + + if (!priv->name_owner) { + _LOGT( + "create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: new request interface %d (driver %s). %s", + NM_HASH_OBFUSCATE_PTR(handle), + ifindex, + nm_supplicant_driver_to_string(driver), + priv->poke_name_owner_cancellable ? "Waiting for supplicant..." : "Poke supplicant..."); + _poke_name_owner(self); + return handle; + } + + if (priv->get_capabilities_cancellable) { + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: new request interface %d (driver %s). Waiting to fetch capabilities for %s...", + NM_HASH_OBFUSCATE_PTR(handle), + ifindex, + nm_supplicant_driver_to_string(driver), + priv->name_owner->str); + return handle; + } + + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT + "]: new request interface %d (driver %s). create interface on %s...", + NM_HASH_OBFUSCATE_PTR(handle), + ifindex, + nm_supplicant_driver_to_string(driver), + priv->name_owner->str); + + _create_iface_dbus_start(self, handle); + return handle; +} + +static void +_create_iface_proceed_all(NMSupplicantManager *self, GError *error) +{ + NMSupplicantManagerPrivate * priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + NMSupplMgrCreateIfaceHandle *handle; + + nm_assert(error || priv->name_owner); + nm_assert(error || !priv->get_capabilities_cancellable); + + if (c_list_is_empty(&priv->create_iface_lst_head)) + return; + + if (error) { + CList alt_list; + + /* we move the handles we want to proceed to a alternative list. + * That is, because we invoke callbacks to the caller, who might + * create another request right away. We don't want to proceed + * that one. */ + c_list_init(&alt_list); + c_list_splice(&alt_list, &priv->create_iface_lst_head); + + while ((handle = + c_list_last_entry(&alt_list, NMSupplMgrCreateIfaceHandle, create_iface_lst))) { + /* We don't need to keep @self alive. Every handle holds a reference already. */ + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface failed: %s", + NM_HASH_OBFUSCATE_PTR(handle), + error->message); + _create_iface_complete(handle, NULL, error); + } + return; + } + + /* start all the handles. This does not invoke callbacks, so the list of handles + * cannot be modified while we iterate it. */ + c_list_for_each_entry (handle, &priv->create_iface_lst_head, create_iface_lst) { + _LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface on %s...", + NM_HASH_OBFUSCATE_PTR(handle), + priv->name_owner->str); + _create_iface_dbus_start(self, handle); + } +} + +void +nm_supplicant_manager_create_interface_cancel(NMSupplMgrCreateIfaceHandle *handle) +{ + gs_free_error GError *error = NULL; + + if (!handle) + return; + + g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(handle->self)); + g_return_if_fail(handle->callback); + nm_assert(!c_list_is_empty(&handle->create_iface_lst)); + + nm_utils_error_set_cancelled(&error, FALSE, NULL); + _create_iface_complete(handle, NULL, error); +} + +NMSupplicantInterface * +nm_supplicant_manager_create_interface_from_path(NMSupplicantManager *self, const char *object_path) +{ + NMSupplicantManagerPrivate *priv; + NMSupplicantInterface * supp_iface; + nm_auto_ref_string NMRefString *iface_path = NULL; + + g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL); + g_return_val_if_fail(object_path, NULL); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + iface_path = nm_ref_string_new(object_path); + + supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path); + + if (supp_iface) + return g_object_ref(supp_iface); + + supp_iface = nm_supplicant_interface_new(self, iface_path, 0, NM_SUPPLICANT_DRIVER_UNKNOWN); + + _supp_iface_add(self, iface_path, supp_iface); + + return supp_iface; +} + +/*****************************************************************************/ + +static void +_dbus_interface_removed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantManager * self = user_data; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + NMSupplicantInterface * supp_iface; + const char * iface_path_str; + nm_auto_ref_string NMRefString *iface_path = NULL; + + nm_assert(nm_streq(sender_name, priv->name_owner->str)); + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) + return; + + g_variant_get(parameters, "(&o)", &iface_path_str); + + iface_path = nm_ref_string_new(iface_path_str); + + supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path); + if (!supp_iface) + return; + + _supp_iface_remove_one(self, supp_iface, FALSE, "InterfaceRemoved signal from wpa_supplicant"); +} + +/*****************************************************************************/ + +static void +_dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data) +{ + NMSupplicantManager * self; + NMSupplicantManagerPrivate *priv; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->get_capabilities_cancellable); + + /* The supplicant only advertises global capabilities if the following + * commit has been applied: + * + * commit 1634ac0654eba8d458640a115efc0a6cde3bac4d + * Author: Dan Williams + * Date: Sat Sep 29 19:06:30 2012 +0300 + * + * dbus: Add global capabilities property + */ + _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_DEFAULT); + _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_DEFAULT); + _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_DEFAULT); + + /* Support for the following is newer than the capabilities property */ + _caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_FALSE); + + if (res) { + nm_auto_free_variant_iter GVariantIter *res_iter = NULL; + const char * res_key; + GVariant * res_val; + + g_variant_get(res, "(a{sv})", &res_iter); + while (g_variant_iter_loop(res_iter, "{&sv}", &res_key, &res_val)) { + if (nm_streq(res_key, "Capabilities")) { + if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) { + gs_free const char **array = NULL; + const char ** a; + + array = g_variant_get_strv(res_val, NULL); + _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE); + _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_FALSE); + if (array) { + for (a = array; *a; a++) { + if (nm_streq(*a, "ap")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "pmf")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "fils")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "p2p")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "ft")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "sha384")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "mesh")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_TRUE); + continue; + } + if (nm_streq(*a, "suiteb192")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_TRUE); + continue; + } + } + } + } + continue; + } + if (nm_streq(res_key, "EapMethods")) { + if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) { + gs_free const char **array = NULL; + const char ** a; + + array = g_variant_get_strv(res_val, NULL); + if (array) { + for (a = array; *a; a++) { + if (g_ascii_strcasecmp(*a, "FAST") == 0) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE); + break; + } + } + } + } + continue; + } + if (nm_streq(res_key, "WFDIEs")) { + _caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE); + continue; + } + } + } + + _LOGD("supported features:" + " AP%c" + " PMF%c" + " FILS%c" + " P2P%c" + " FT%c" + " SHA384%c" + " MESH%c" + " FAST%c" + " WFD%c" + "", + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_AP), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_PMF), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FILS), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_P2P), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FT), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_SHA384), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_MESH), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FAST), + _caps_to_char(priv, NM_SUPPL_CAP_TYPE_WFD)); + + nm_assert(g_hash_table_size(priv->supp_ifaces) == 0); + nm_assert(c_list_is_empty(&priv->supp_lst_head)); + + _create_iface_proceed_all(self, NULL); +} + +/*****************************************************************************/ + +void +_nm_supplicant_manager_unregister_interface(NMSupplicantManager * self, + NMSupplicantInterface *supp_iface) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface)); + nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head, + &supp_iface->supp_lst)); + + c_list_unlink(&supp_iface->supp_lst); + if (!g_hash_table_remove(priv->supp_ifaces, + nm_supplicant_interface_get_object_path(supp_iface))) + nm_assert_not_reached(); +} + +static void +_supp_iface_add(NMSupplicantManager * self, + NMRefString * iface_path, + NMSupplicantInterface *supp_iface) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + c_list_link_tail(&priv->supp_lst_head, &supp_iface->supp_lst); + if (!g_hash_table_insert(priv->supp_ifaces, iface_path, supp_iface)) + nm_assert_not_reached(); +} + +static void +_supp_iface_remove_one(NMSupplicantManager * self, + NMSupplicantInterface *supp_iface, + gboolean force_remove_from_supplicant, + const char * reason) +{ +#if NM_MORE_ASSERTS + _nm_unused gs_unref_object NMSupplicantInterface *supp_iface_keep_alive = + g_object_ref(supp_iface); +#endif + + nm_assert(NM_IS_SUPPLICANT_MANAGER(self)); + nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface)); + nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head, + &supp_iface->supp_lst)); + + _nm_supplicant_interface_set_state_down(supp_iface, force_remove_from_supplicant, reason); + + nm_assert(c_list_is_empty(&supp_iface->supp_lst)); +} + +static void +_supp_iface_remove_all(NMSupplicantManager *self, + gboolean force_remove_from_supplicant, + const char * reason) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + NMSupplicantInterface * supp_iface; + + while ((supp_iface = c_list_first_entry(&priv->supp_lst_head, NMSupplicantInterface, supp_lst))) + _supp_iface_remove_one(self, supp_iface, force_remove_from_supplicant, reason); +} + +/*****************************************************************************/ + +static gboolean +_available_reset_cb(gpointer user_data) +{ + NMSupplicantManager * self = user_data; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + priv->available_reset_id = 0; + nm_assert(priv->available == NM_TERNARY_FALSE); + priv->available = NM_TERNARY_DEFAULT; + g_signal_emit(self, signals[AVAILABLE_CHANGED], 0); + return G_SOURCE_REMOVE; +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMSupplicantManager *self, const char *name_owner, gboolean first_time) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + NMTernary available; + gboolean available_changed = FALSE; + + nm_assert(!priv->get_name_owner_cancellable); + nm_assert(!name_owner || name_owner[0]); + nm_assert((first_time && !priv->name_owner) + || (!first_time && (!!priv->name_owner) != (!!name_owner))); + + if (first_time) { + _LOGD("wpa_supplicant name owner %s%s%s (%srunning)", + NM_PRINT_FMT_QUOTE_STRING(name_owner), + name_owner ? "" : "not "); + } else { + _LOGD("wpa_supplicant name owner \"%s\" %s (%srunning)", + name_owner ?: priv->name_owner->str, + name_owner ? "disappeared" : "appeared", + name_owner ? "" : "not "); + } + + nm_ref_string_unref(priv->name_owner); + priv->name_owner = nm_ref_string_new(name_owner); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id); + + if (name_owner) { + if (nm_clear_g_source(&priv->poke_name_owner_timeout_id)) + _LOGT("poke service \"%s\" completed with name owner change", NM_WPAS_DBUS_SERVICE); + nm_clear_g_cancellable(&priv->poke_name_owner_cancellable); + } + + nm_clear_g_cancellable(&priv->get_capabilities_cancellable); + + priv->capabilities = NM_SUPPL_CAP_MASK_NONE; + if (priv->name_owner) { + priv->get_capabilities_cancellable = g_cancellable_new(); + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_PATH, + NM_WPAS_DBUS_INTERFACE, + 5000, + priv->get_capabilities_cancellable, + _dbus_get_capabilities_cb, + self); + priv->interface_removed_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_INTERFACE, + "InterfaceRemoved", + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + _dbus_interface_removed_cb, + self, + NULL); + } + + /* if supplicant is running (has a name owner), we may use it. + * If this is the first time, and supplicant is not running, we + * may also use it (and assume that we probably could D-Bus activate + * it). + * + * Otherwise, somebody else stopped supplicant. It's no longer useable to + * us and we block auto starting it. The user has to start the service... + * + * Actually, below we reset the hard block after a short timeout. This + * causes the caller to notify that supplicant may now by around and + * retry to D-Bus activate it. */ + if (priv->name_owner) + available = NM_TERNARY_TRUE; + else if (first_time) + available = NM_TERNARY_DEFAULT; + else + available = NM_TERNARY_FALSE; + + if (priv->available != available) { + priv->available = available; + _LOGD("supplicant is now %savailable", + available == FALSE ? "not " : (available == TRUE ? "" : "maybe ")); + available_changed = TRUE; + + nm_clear_g_source(&priv->available_reset_id); + if (available == NM_TERNARY_FALSE) { + /* reset the availability from a hard "no" to a "maybe" in a bit. */ + priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self); + } + } + + _supp_iface_remove_all(self, TRUE, "name-owner changed"); + + if (!priv->name_owner) { + if (priv->poke_name_owner_timeout_id) { + /* we are still poking for the service to start. Don't cancel + * the pending create requests just yet. */ + } else { + gs_free_error GError *local_error = NULL; + + /* When we loose the name owner, we fail all pending creation requests. */ + nm_utils_error_set(&local_error, NM_UTILS_ERROR_UNKNOWN, "Name owner lost"); + _create_iface_proceed_all(self, local_error); + } + } else { + /* We got a name-owner, but we don't do anything. Instead let + * _dbus_get_capabilities_cb() complete and kick of the create-iface + * handles. + * + * Note that before the first name-owner change, all create-iface + * requests fail right away. So we don't have to handle them here + * (by starting to poke the service). */ + } + + if (available_changed) + g_signal_emit(self, signals[AVAILABLE_CHANGED], 0); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + gs_unref_object NMSupplicantManager *self = g_object_ref(user_data); + NMSupplicantManagerPrivate * priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + const char * name_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + if (priv->get_name_owner_cancellable) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &name_owner); + + name_owner = nm_str_not_empty(name_owner); + + if (nm_streq0(name_owner, nm_ref_string_get_str(priv->name_owner))) + return; + + if (name_owner && priv->name_owner) { + /* odd, we directly switch from one name owner to the next. Can't allow that. + * First clear the name owner before resetting. */ + name_owner_changed(self, NULL, FALSE); + } + name_owner_changed(user_data, name_owner, FALSE); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + NMSupplicantManager * self = user_data; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + if (!name_owner && nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->get_name_owner_cancellable); + + name_owner_changed(self, nm_str_not_empty(name_owner), TRUE); +} + +/*****************************************************************************/ + +static void +nm_supplicant_manager_init(NMSupplicantManager *self) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + nm_assert(priv->capabilities == NM_SUPPL_CAP_MASK_NONE); + nm_assert(priv->available == NM_TERNARY_FALSE); + + priv->supp_ifaces = g_hash_table_new(nm_direct_hash, NULL); + c_list_init(&priv->supp_lst_head); + c_list_init(&priv->create_iface_lst_head); + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!priv->dbus_connection) { + _LOGI("no D-Bus connection to talk to wpa_supplicant"); + return; + } + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + NM_WPAS_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + priv->get_name_owner_cancellable = g_cancellable_new(); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + NM_WPAS_DBUS_SERVICE, + -1, + priv->get_name_owner_cancellable, + get_name_owner_cb, + self); +} + +static void +dispose(GObject *object) +{ + NMSupplicantManager * self = (NMSupplicantManager *) object; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self); + + _supp_iface_remove_all(self, TRUE, "NMSupplicantManager is disposing"); + + nm_assert(c_list_is_empty(&priv->create_iface_lst_head)); + + nm_clear_g_source(&priv->available_reset_id); + + priv->available = NM_TERNARY_FALSE; + nm_clear_pointer(&priv->name_owner, nm_ref_string_unref); + + nm_clear_g_source(&priv->poke_name_owner_timeout_id); + nm_clear_g_cancellable(&priv->poke_name_owner_cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + + nm_clear_g_cancellable(&priv->get_name_owner_cancellable); + nm_clear_g_cancellable(&priv->get_capabilities_cancellable); + + G_OBJECT_CLASS(nm_supplicant_manager_parent_class)->dispose(object); + + g_clear_object(&priv->dbus_connection); + + nm_clear_pointer(&priv->supp_ifaces, g_hash_table_destroy); +} + +static void +nm_supplicant_manager_class_init(NMSupplicantManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + + signals[AVAILABLE_CHANGED] = g_signal_new(NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/core/supplicant/nm-supplicant-manager.h b/src/core/supplicant/nm-supplicant-manager.h new file mode 100644 index 0000000..3000d06 --- /dev/null +++ b/src/core/supplicant/nm-supplicant-manager.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2008 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ +#define __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ + +#include "nm-supplicant-types.h" +#include "devices/nm-device.h" + +#define NM_TYPE_SUPPLICANT_MANAGER (nm_supplicant_manager_get_type()) +#define NM_SUPPLICANT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManager)) +#define NM_SUPPLICANT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass)) +#define NM_IS_SUPPLICANT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SUPPLICANT_MANAGER)) +#define NM_IS_SUPPLICANT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SUPPLICANT_MANAGER)) +#define NM_SUPPLICANT_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass)) + +#define NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED "available-changed" + +typedef struct _NMSupplicantManagerClass NMSupplicantManagerClass; + +GType nm_supplicant_manager_get_type(void); + +NMSupplicantManager *nm_supplicant_manager_get(void); + +NMTernary nm_supplicant_manager_is_available(NMSupplicantManager *self); + +GDBusConnection *nm_supplicant_manager_get_dbus_connection(NMSupplicantManager *self); +NMRefString * nm_supplicant_manager_get_dbus_name_owner(NMSupplicantManager *self); +NMSupplCapMask nm_supplicant_manager_get_global_capabilities(NMSupplicantManager *self); + +void nm_supplicant_manager_set_wfd_ies(NMSupplicantManager *self, GBytes *wfd_ies); + +typedef struct _NMSupplMgrCreateIfaceHandle NMSupplMgrCreateIfaceHandle; + +typedef void (*NMSupplicantManagerCreateInterfaceCb)(NMSupplicantManager * self, + NMSupplMgrCreateIfaceHandle *handle, + NMSupplicantInterface * iface, + GError * error, + gpointer user_data); + +NMSupplMgrCreateIfaceHandle * +nm_supplicant_manager_create_interface(NMSupplicantManager * self, + int ifindex, + NMSupplicantDriver driver, + NMSupplicantManagerCreateInterfaceCb callback, + gpointer user_data); + +void nm_supplicant_manager_create_interface_cancel(NMSupplMgrCreateIfaceHandle *handle); + +NMSupplicantInterface *nm_supplicant_manager_create_interface_from_path(NMSupplicantManager *self, + const char *object_path); + +/*****************************************************************************/ + +void _nm_supplicant_manager_unregister_interface(NMSupplicantManager * self, + NMSupplicantInterface *supp_iface); + +void _nm_supplicant_manager_dbus_call_remove_interface(NMSupplicantManager *self, + const char * name_owner, + const char * iface_path); + +#endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */ diff --git a/src/core/supplicant/nm-supplicant-settings-verify.c b/src/core/supplicant/nm-supplicant-settings-verify.c new file mode 100644 index 0000000..fe4cb1c --- /dev/null +++ b/src/core/supplicant/nm-supplicant-settings-verify.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-settings-verify.h" + +#include +#include + +struct Opt { + const char * key; + const char *const * str_allowed; + const NMSupplOptType type; + const guint32 int_low; /* Inclusive */ + const guint32 int_high; /* Inclusive; max length for strings */ +}; + +typedef gboolean (*validate_func)(const struct Opt *, const char *, const guint32); + +#define OPT_INT(_key, _int_low, _int_high) \ + { \ + .key = _key, .type = NM_SUPPL_OPT_TYPE_INT, .int_high = _int_high, .int_low = _int_low, \ + } +#define OPT_BYTES(_key, _int_high) \ + { \ + .key = _key, .type = NM_SUPPL_OPT_TYPE_BYTES, .int_high = _int_high, \ + } +#define OPT_UTF8(_key, _int_high) \ + { \ + .key = _key, .type = NM_SUPPL_OPT_TYPE_UTF8, .int_high = _int_high, \ + } +#define OPT_KEYWORD(_key, _str_allowed) \ + { \ + .key = _key, .type = NM_SUPPL_OPT_TYPE_KEYWORD, .str_allowed = _str_allowed, \ + } + +static const struct Opt opt_table[] = { + OPT_BYTES("altsubject_match", 0), + OPT_BYTES("altsubject_match2", 0), + OPT_BYTES("anonymous_identity", 0), + OPT_KEYWORD("auth_alg", NM_MAKE_STRV("OPEN", "SHARED", "LEAP", )), + OPT_BYTES("bgscan", 0), + OPT_KEYWORD("bssid", NULL), + OPT_BYTES("ca_cert", 65536), + OPT_BYTES("ca_cert2", 65536), + OPT_BYTES("ca_path", 0), + OPT_BYTES("ca_path2", 0), + OPT_BYTES("client_cert", 65536), + OPT_BYTES("client_cert2", 65536), + OPT_BYTES("domain_match", 0), + OPT_BYTES("domain_match2", 0), + OPT_BYTES("domain_suffix_match", 0), + OPT_BYTES("domain_suffix_match2", 0), + OPT_KEYWORD("eap", + NM_MAKE_STRV("LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM", "PSK", "FAST", "PWD", )), + OPT_INT("eapol_flags", 0, 3), + OPT_BYTES("eappsk", 0), + OPT_INT("engine", 0, 1), + OPT_BYTES("engine_id", 0), + OPT_INT("fragment_size", 1, 2000), + OPT_KEYWORD("freq_list", NULL), + OPT_INT("frequency", 2412, 5825), + OPT_KEYWORD("group", NM_MAKE_STRV("CCMP", "TKIP", "WEP104", "WEP40", "GCMP-256", )), + OPT_BYTES("identity", 0), + OPT_INT("ieee80211w", 0, 2), + OPT_INT("ignore_broadcast_ssid", 0, 2), + OPT_BYTES("key_id", 0), + OPT_KEYWORD("key_mgmt", + NM_MAKE_STRV("WPA-PSK", + "WPA-PSK-SHA256", + "FT-PSK", + "WPA-EAP", + "WPA-EAP-SHA256", + "FT-EAP", + "FT-EAP-SHA384", + "FILS-SHA256", + "FILS-SHA384", + "FT-FILS-SHA256", + "FT-FILS-SHA384", + "IEEE8021X", + "SAE", + "WPA-EAP-SUITE-B-192", + "FT-SAE", + "OWE", + "NONE", )), + OPT_INT("macsec_integ_only", 0, 1), + OPT_INT("macsec_policy", 0, 1), + OPT_INT("macsec_port", 1, 65534), + OPT_BYTES("mka_cak", 65536), + OPT_BYTES("mka_ckn", 65536), + OPT_BYTES("nai", 0), + OPT_BYTES("pac_file", 0), + OPT_KEYWORD("pairwise", NM_MAKE_STRV("CCMP", "TKIP", "GCMP-256", "NONE", )), + OPT_UTF8("password", 0), + OPT_BYTES("pcsc", 0), + OPT_KEYWORD("phase1", + NM_MAKE_STRV("peapver=0", + "peapver=1", + "peaplabel=1", + "peap_outer_success=0", + "include_tls_length=1", + "sim_min_num_chal=3", + "fast_provisioning=0", + "fast_provisioning=1", + "fast_provisioning=2", + "fast_provisioning=3", + "tls_disable_tlsv1_0=0", + "tls_disable_tlsv1_0=1", + "tls_disable_tlsv1_1=0", + "tls_disable_tlsv1_1=1", + "tls_disable_tlsv1_2=0", + "tls_disable_tlsv1_2=1", )), + OPT_KEYWORD("phase2", + NM_MAKE_STRV("auth=PAP", + "auth=CHAP", + "auth=MSCHAP", + "auth=MSCHAPV2", + "auth=GTC", + "auth=OTP", + "auth=MD5", + "auth=TLS", + "autheap=MD5", + "autheap=MSCHAPV2", + "autheap=OTP", + "autheap=GTC", + "autheap=TLS", )), + OPT_BYTES("pin", 0), + OPT_BYTES("private_key", 65536), + OPT_BYTES("private_key2", 65536), + OPT_BYTES("private_key2_passwd", 1024), + OPT_BYTES("private_key_passwd", 1024), + OPT_INT("proactive_key_caching", 0, 1), + OPT_KEYWORD("proto", NM_MAKE_STRV("WPA", "RSN", )), + OPT_BYTES("psk", 0), + OPT_INT("scan_ssid", 0, 1), + OPT_BYTES("ssid", 32), + OPT_BYTES("subject_match", 0), + OPT_BYTES("subject_match2", 0), + OPT_BYTES("wep_key0", 0), + OPT_BYTES("wep_key1", 0), + OPT_BYTES("wep_key2", 0), + OPT_BYTES("wep_key3", 0), + OPT_INT("wep_tx_keyidx", 0, 3), +}; + +static gboolean +validate_type_int(const struct Opt *opt, const char *value, const guint32 len) +{ + gint64 v; + + nm_assert(opt); + nm_assert(value); + + v = _nm_utils_ascii_str_to_int64(value, 10, opt->int_low, opt->int_high, G_MININT64); + return v != G_MININT64 || errno == 0; +} + +static gboolean +validate_type_bytes(const struct Opt *opt, const char *value, const guint32 len) +{ + guint32 check_len; + + nm_assert(opt); + nm_assert(value); + + check_len = opt->int_high ?: 255; + if (len > check_len) + return FALSE; + + return TRUE; +} + +static gboolean +validate_type_utf8(const struct Opt *opt, const char *value, const guint32 len) +{ + guint32 check_len; + + nm_assert(opt); + nm_assert(value); + + check_len = opt->int_high ?: 255; + /* Note that we deliberately don't validate the UTF-8, because + * some "UTF-8" fields, such as 8021x.password, do not actually + * have to be valid UTF-8 */ + if (g_utf8_strlen(value, len) > check_len) + return FALSE; + + return TRUE; +} + +static gboolean +validate_type_keyword(const struct Opt *opt, const char *value, const guint32 len) +{ + gs_free char *value_free = NULL; + + nm_assert(opt); + nm_assert(value); + + /* Allow everything */ + if (!opt->str_allowed) + return TRUE; + + value = nm_strndup_a(300, value, len, &value_free); + + /* validate each space-separated word in 'value' */ + + while (TRUE) { + char *s; + + while (value[0] == ' ') + value++; + + if (value[0] == '\0') + return TRUE; + + s = strchr(value, ' '); + if (s) { + s[0] = '\0'; + s++; + } + + if (nm_utils_strv_find_first((char **) opt->str_allowed, -1, value) < 0) + return FALSE; + + if (!s) + return TRUE; + + value = s; + } +} + +NMSupplOptType +nm_supplicant_settings_verify_setting(const char *key, const char *value, const guint32 len) +{ + static const validate_func validate_table[_NM_SUPPL_OPT_TYPE_NUM - 1] = { + [NM_SUPPL_OPT_TYPE_INT - 1] = validate_type_int, + [NM_SUPPL_OPT_TYPE_BYTES - 1] = validate_type_bytes, + [NM_SUPPL_OPT_TYPE_UTF8 - 1] = validate_type_utf8, + [NM_SUPPL_OPT_TYPE_KEYWORD - 1] = validate_type_keyword, + }; + const struct Opt *opt; + gssize opt_idx; + + g_return_val_if_fail(key, FALSE); + g_return_val_if_fail(value, FALSE); + + if (NM_MORE_ASSERT_ONCE(5)) { + gsize i; + + for (i = 0; i < G_N_ELEMENTS(opt_table); i++) { + opt = &opt_table[i]; + + nm_assert(opt->key); + nm_assert(opt->type > NM_SUPPL_OPT_TYPE_INVALID); + nm_assert(opt->type < _NM_SUPPL_OPT_TYPE_NUM); + if (i > 0) + nm_assert(strcmp(opt[-1].key, opt->key) < 0); + nm_assert(validate_table[opt->type - 1]); + + nm_assert(!opt->str_allowed || (opt->type == NM_SUPPL_OPT_TYPE_KEYWORD)); + nm_assert(!opt->str_allowed || NM_PTRARRAY_LEN(opt->str_allowed) > 0); + + nm_assert(opt->int_low == 0 || opt->type == NM_SUPPL_OPT_TYPE_INT); + + nm_assert(opt->int_high == 0 + || NM_IN_SET(opt->type, + NM_SUPPL_OPT_TYPE_INT, + NM_SUPPL_OPT_TYPE_UTF8, + NM_SUPPL_OPT_TYPE_BYTES)); + + nm_assert(opt->type != NM_SUPPL_OPT_TYPE_INT || opt->int_low < opt->int_high); + } + } + + opt_idx = nm_utils_array_find_binary_search(opt_table, + sizeof(opt_table[0]), + G_N_ELEMENTS(opt_table), + &key, + nm_strcmp_p_with_data, + NULL); + if (opt_idx < 0) { + if (nm_streq(key, "mode")) { + if (len != 1) + return NM_SUPPL_OPT_TYPE_INVALID; + if (!NM_IN_SET(value[0], '1', '2', '5')) + return NM_SUPPL_OPT_TYPE_INVALID; + return NM_SUPPL_OPT_TYPE_INT; + } + return NM_SUPPL_OPT_TYPE_INVALID; + } + + opt = &opt_table[opt_idx]; + if (!((validate_table[opt->type - 1])(opt, value, len))) + return NM_SUPPL_OPT_TYPE_INVALID; + + return opt->type; +} diff --git a/src/core/supplicant/nm-supplicant-settings-verify.h b/src/core/supplicant/nm-supplicant-settings-verify.h new file mode 100644 index 0000000..8ba50b6 --- /dev/null +++ b/src/core/supplicant/nm-supplicant-settings-verify.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2008 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ +#define __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ + +typedef enum { + NM_SUPPL_OPT_TYPE_INVALID = 0, + NM_SUPPL_OPT_TYPE_INT, + NM_SUPPL_OPT_TYPE_BYTES, + NM_SUPPL_OPT_TYPE_UTF8, + NM_SUPPL_OPT_TYPE_KEYWORD, + NM_SUPPL_OPT_TYPE_STRING, + _NM_SUPPL_OPT_TYPE_NUM, +} NMSupplOptType; + +NMSupplOptType +nm_supplicant_settings_verify_setting(const char *key, const char *value, const guint32 len); + +#endif /* __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ */ diff --git a/src/core/supplicant/nm-supplicant-types.h b/src/core/supplicant/nm-supplicant-types.h new file mode 100644 index 0000000..adcf02d --- /dev/null +++ b/src/core/supplicant/nm-supplicant-types.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2008 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__ +#define __NETWORKMANAGER_SUPPLICANT_TYPES_H__ + +#include "c-list/src/c-list.h" + +#define NM_WPAS_DBUS_SERVICE "fi.w1.wpa_supplicant1" +#define NM_WPAS_DBUS_PATH "/fi/w1/wpa_supplicant1" +#define NM_WPAS_DBUS_INTERFACE "fi.w1.wpa_supplicant1" + +#if HAVE_WEXT + #define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211,wext" +#else + #define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211" +#endif + +#define NM_WPAS_DBUS_IFACE_INTERFACE NM_WPAS_DBUS_INTERFACE ".Interface" +#define NM_WPAS_DBUS_IFACE_INTERFACE_WPS NM_WPAS_DBUS_INTERFACE ".Interface.WPS" +#define NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE NM_WPAS_DBUS_INTERFACE ".Interface.P2PDevice" +#define NM_WPAS_DBUS_IFACE_BSS NM_WPAS_DBUS_INTERFACE ".BSS" +#define NM_WPAS_DBUS_IFACE_PEER NM_WPAS_DBUS_INTERFACE ".Peer" +#define NM_WPAS_DBUS_IFACE_GROUP NM_WPAS_DBUS_INTERFACE ".Group" +#define NM_WPAS_DBUS_IFACE_NETWORK NM_WPAS_DBUS_INTERFACE ".Network" +#define NM_WPAS_ERROR_INVALID_IFACE NM_WPAS_DBUS_INTERFACE ".InvalidInterface" +#define NM_WPAS_ERROR_EXISTS_ERROR NM_WPAS_DBUS_INTERFACE ".InterfaceExists" +#define NM_WPAS_ERROR_UNKNOWN_IFACE NM_WPAS_DBUS_INTERFACE ".InterfaceUnknown" + +typedef struct _NMSupplicantManager NMSupplicantManager; +typedef struct _NMSupplicantInterface NMSupplicantInterface; +typedef struct _NMSupplicantConfig NMSupplicantConfig; + +/*****************************************************************************/ + +typedef enum { + NM_SUPPL_CAP_TYPE_AP, + NM_SUPPL_CAP_TYPE_PMF, + NM_SUPPL_CAP_TYPE_FILS, + NM_SUPPL_CAP_TYPE_P2P, + NM_SUPPL_CAP_TYPE_FT, + NM_SUPPL_CAP_TYPE_SHA384, + NM_SUPPL_CAP_TYPE_MESH, + NM_SUPPL_CAP_TYPE_FAST, + NM_SUPPL_CAP_TYPE_WFD, + NM_SUPPL_CAP_TYPE_SUITEB192, + _NM_SUPPL_CAP_TYPE_NUM, +} NMSupplCapType; + +#define NM_SUPPL_CAP_MASK_NO(type) ((NMSupplCapMask)(1llu << ((type) *2u))) +#define NM_SUPPL_CAP_MASK_YES(type) ((NMSupplCapMask)(2llu << ((type) *2u))) +#define NM_SUPPL_CAP_MASK_MASK(type) ((NMSupplCapMask)(3llu << ((type) *2u))) + +typedef enum { + NM_SUPPL_CAP_MASK_NONE = 0, + NM_SUPPL_CAP_MASK_ALL = ((1llu << (_NM_SUPPL_CAP_TYPE_NUM * 2)) - 1), + +/* usually it's bad to use macros to define enum values (because you cannot find them with ctags/cscope + * anymore. In this case, still do it because the alternative is ugly too. */ +#define _NM_SUPPL_CAP_MASK_DEFINE(type) \ + NM_SUPPL_CAP_MASK_T_##type##_NO = (1llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \ + NM_SUPPL_CAP_MASK_T_##type##_YES = (2llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \ + NM_SUPPL_CAP_MASK_T_##type##_MASK = (3llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)) + _NM_SUPPL_CAP_MASK_DEFINE(AP), + _NM_SUPPL_CAP_MASK_DEFINE(FAST), + _NM_SUPPL_CAP_MASK_DEFINE(PMF), + _NM_SUPPL_CAP_MASK_DEFINE(FILS), + _NM_SUPPL_CAP_MASK_DEFINE(P2P), + _NM_SUPPL_CAP_MASK_DEFINE(MESH), + _NM_SUPPL_CAP_MASK_DEFINE(WFD), + _NM_SUPPL_CAP_MASK_DEFINE(FT), + _NM_SUPPL_CAP_MASK_DEFINE(SHA384), +#undef _NM_SUPPL_CAP_MASK_DEFINE +} NMSupplCapMask; + +static inline NMSupplCapMask +NM_SUPPL_CAP_MASK_SET(NMSupplCapMask features, NMSupplCapType type, NMTernary value) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(type)); + nm_assert(type < _NM_SUPPL_CAP_TYPE_NUM); + nm_assert(NM_IN_SET(value, NM_TERNARY_DEFAULT, NM_TERNARY_TRUE, NM_TERNARY_FALSE)); + nm_assert(!(features & ~NM_SUPPL_CAP_MASK_ALL)); + + features &= ~NM_SUPPL_CAP_MASK_MASK(type); + switch (value) { + case NM_TERNARY_FALSE: + features |= NM_SUPPL_CAP_MASK_NO(type); + break; + case NM_TERNARY_TRUE: + features |= NM_SUPPL_CAP_MASK_YES(type); + break; + case NM_TERNARY_DEFAULT: + break; + } + + return features; +} + +static inline NMTernary +NM_SUPPL_CAP_MASK_GET(NMSupplCapMask features, NMSupplCapType type) +{ + int f; + + nm_assert(_NM_INT_NOT_NEGATIVE(type)); + nm_assert(type < _NM_SUPPL_CAP_TYPE_NUM); + nm_assert(!(features & ~NM_SUPPL_CAP_MASK_ALL)); + + f = ((int) (features >> (2 * (int) type))) & 0x3; + + nm_assert(NM_IN_SET(f, 0, 1, 2)); + + return (NMTernary)(f - 1); +} + +/*****************************************************************************/ + +/** + * NMSupplicantError: + * @NM_SUPPLICANT_ERROR_UNKNOWN: unknown or unclassified error + * @NM_SUPPLICANT_ERROR_CONFIG: a failure constructing the + * wpa-supplicant configuration. + */ +typedef enum { + NM_SUPPLICANT_ERROR_UNKNOWN = 0, /*< nick=Unknown >*/ + NM_SUPPLICANT_ERROR_CONFIG = 1, /*< nick=Config >*/ +} NMSupplicantError; + +typedef enum { + NM_SUPPLICANT_DRIVER_UNKNOWN, + NM_SUPPLICANT_DRIVER_WIRELESS, + NM_SUPPLICANT_DRIVER_WIRED, + NM_SUPPLICANT_DRIVER_MACSEC, +} NMSupplicantDriver; + +const char *nm_supplicant_driver_to_string(NMSupplicantDriver driver); + +#define NM_SUPPLICANT_ERROR (nm_supplicant_error_quark()) +GQuark nm_supplicant_error_quark(void); + +typedef struct _NMSupplicantBssInfo { + NMRefString *bss_path; + + NMSupplicantInterface *_self; + CList _bss_lst; + GCancellable * _init_cancellable; + + GBytes *ssid; + + gint64 last_seen_msec; + + NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */ + NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */ + + guint32 frequency; + + guint32 max_rate; + + guint8 signal_percent; + + NMEtherAddr bssid; + + NM80211ApFlags ap_flags : 5; + + NM80211Mode mode : 4; + + bool bssid_valid : 1; + + bool metered : 1; + + bool _bss_dirty : 1; + +} NMSupplicantBssInfo; + +typedef struct _NMSupplicantPeerInfo { + NMRefString *peer_path; + + CList _peer_lst; + NMSupplicantInterface *_self; + GCancellable * _init_cancellable; + + char *device_name; + char *manufacturer; + char *model; + char *model_number; + char *serial; + + const char **groups; + + GBytes *ies; + + gint64 last_seen_msec; + + guint8 address[6 /* ETH_ALEN */]; + + gint8 signal_percent; + + bool address_valid : 1; + + bool _peer_dirty : 1; + +} NMSupplicantPeerInfo; + +#endif /* NM_SUPPLICANT_TYPES_H */ diff --git a/src/core/supplicant/tests/certs/test-ca-cert.pem b/src/core/supplicant/tests/certs/test-ca-cert.pem new file mode 100644 index 0000000..ef1be20 --- /dev/null +++ b/src/core/supplicant/tests/certs/test-ca-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/src/core/supplicant/tests/certs/test-cert.p12 b/src/core/supplicant/tests/certs/test-cert.p12 new file mode 100644 index 0000000..ae4a683 Binary files /dev/null and b/src/core/supplicant/tests/certs/test-cert.p12 differ diff --git a/src/core/supplicant/tests/meson.build b/src/core/supplicant/tests/meson.build new file mode 100644 index 0000000..88832a9 --- /dev/null +++ b/src/core/supplicant/tests/meson.build @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_unit = 'test-supplicant-config' + +exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) + +test( + 'supplicant/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) diff --git a/src/core/supplicant/tests/test-supplicant-config.c b/src/core/supplicant/tests/test-supplicant-config.c new file mode 100644 index 0000000..1dde90b --- /dev/null +++ b/src/core/supplicant/tests/test-supplicant-config.c @@ -0,0 +1,909 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" + +#include "supplicant/nm-supplicant-config.h" +#include "supplicant/nm-supplicant-settings-verify.h" + +#include "nm-test-utils-core.h" + +#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/core/supplicant/tests/certs" + +/*****************************************************************************/ + +static gboolean +validate_opt(const char * detail, + GVariant * config, + const char * key, + NMSupplOptType val_type, + gconstpointer expected) +{ + char * config_key; + GVariant * config_value; + gboolean found = FALSE; + GVariantIter iter; + + g_assert(g_variant_is_of_type(config, G_VARIANT_TYPE_VARDICT)); + + g_variant_iter_init(&iter, config); + while (g_variant_iter_next(&iter, "{&sv}", (gpointer) &config_key, (gpointer) &config_value)) { + if (!strcmp(key, config_key)) { + found = TRUE; + switch (val_type) { + case NM_SUPPL_OPT_TYPE_INT: + { + g_assert(g_variant_is_of_type(config_value, G_VARIANT_TYPE_INT32)); + g_assert_cmpint(g_variant_get_int32(config_value), ==, GPOINTER_TO_INT(expected)); + break; + } + case NM_SUPPL_OPT_TYPE_BYTES: + { + const guint8 *expected_bytes; + gsize expected_len = 0; + const guint8 *config_bytes; + gsize config_len = 0; + + expected_bytes = g_bytes_get_data((GBytes *) expected, &expected_len); + g_assert(g_variant_is_of_type(config_value, G_VARIANT_TYPE_BYTESTRING)); + config_bytes = g_variant_get_fixed_array(config_value, &config_len, 1); + g_assert_cmpmem(config_bytes, config_len, expected_bytes, expected_len); + break; + } + case NM_SUPPL_OPT_TYPE_KEYWORD: + case NM_SUPPL_OPT_TYPE_STRING: + { + const char *expected_str = expected; + const char *config_str; + + g_assert(g_variant_is_of_type(config_value, G_VARIANT_TYPE_STRING)); + config_str = g_variant_get_string(config_value, NULL); + g_assert_cmpstr(config_str, ==, expected_str); + break; + } + default: + g_assert_not_reached(); + break; + } + } + g_variant_unref(config_value); + } + + return found; +} + +static GVariant * +build_supplicant_config(NMConnection * connection, + guint mtu, + guint fixed_freq, + NMSupplCapMask capabilities) +{ + gs_unref_object NMSupplicantConfig *config = NULL; + gs_free_error GError * error = NULL; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x * s_8021x; + gboolean success; + + config = nm_supplicant_config_new(capabilities); + + s_wifi = nm_connection_get_setting_wireless(connection); + g_assert(s_wifi); + success = nm_supplicant_config_add_setting_wireless(config, s_wifi, fixed_freq, &error); + g_assert_no_error(error); + g_assert(success); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + if (s_wsec) { + NMSettingWirelessSecurityPmf pmf = nm_setting_wireless_security_get_pmf(s_wsec); + NMSettingWirelessSecurityFils fils = nm_setting_wireless_security_get_fils(s_wsec); + s_8021x = nm_connection_get_setting_802_1x(connection); + success = + nm_supplicant_config_add_setting_wireless_security(config, + s_wsec, + s_8021x, + nm_connection_get_uuid(connection), + mtu, + pmf, + fils, + &error); + } else { + success = nm_supplicant_config_add_no_security(config, &error); + } + g_assert_no_error(error); + g_assert(success); + + success = nm_supplicant_config_add_bgscan(config, connection, &error); + g_assert_no_error(error); + g_assert(success); + + return nm_supplicant_config_to_variant(config); +} + +static NMConnection * +new_basic_connection(const char *id, GBytes *ssid, const char *bssid_str) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWireless * s_wifi; + NMSettingIPConfig * s_ip4; + gs_free char * uuid = nm_utils_uuid_generate(); + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_BSSID, + bssid_str, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NM_SETTING_WIRELESS_BAND, + "bg", + NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + return connection; +} + +static void +test_wifi_open(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + gboolean success; + GError * error = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + + connection = new_basic_connection("Test Wifi Open", ssid, bssid_str); + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'NONE'"); + config_dict = + build_supplicant_config(connection, + 1500, + 0, + NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-open", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-open", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-open", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + validate_opt("wifi-open", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "NONE"); +} + +static void +test_wifi_wep_key(const char * detail, + gboolean test_bssid, + NMWepKeyType wep_type, + const char * key_data, + const unsigned char *expected, + size_t expected_size) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingWirelessSecurity *s_wsec; + gboolean success; + GError * error = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + gs_unref_bytes GBytes *wep_key_bytes = g_bytes_new(expected, expected_size); + const char * bgscan_data = "simple:30:-70:86400"; + gs_unref_bytes GBytes *bgscan = g_bytes_new(bgscan_data, strlen(bgscan_data)); + + connection = new_basic_connection("Test Wifi WEP Key", ssid, test_bssid ? bssid_str : NULL); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + wep_type, + NULL); + nm_setting_wireless_security_set_wep_key(s_wsec, 0, key_data); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + if (test_bssid) + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'NONE'"); + NMTST_EXPECT_NM_INFO("Config: added 'wep_key0' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'wep_tx_keyidx' value '0'"); + if (!test_bssid) + NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-70:86400'*"); + + config_dict = + build_supplicant_config(connection, + 1500, + 0, + NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt(detail, config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt(detail, config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + if (test_bssid) + validate_opt(detail, config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + else + validate_opt(detail, config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan); + + validate_opt(detail, config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "NONE"); + validate_opt(detail, config_dict, "wep_tx_keyidx", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(0)); + validate_opt(detail, config_dict, "wep_key0", NM_SUPPL_OPT_TYPE_BYTES, wep_key_bytes); +} + +static void +test_wifi_wep(void) +{ + const char * key1 = "12345"; + const unsigned char key1_expected[] = {0x31, 0x32, 0x33, 0x34, 0x35}; + const char * key2 = "ascii test$$$"; + const unsigned char key2_expected[] = + {0x61, 0x73, 0x63, 0x69, 0x69, 0x20, 0x74, 0x65, 0x73, 0x74, 0x24, 0x24, 0x24}; + const char * key3 = "abcdef1234"; + const unsigned char key3_expected[] = {0xab, 0xcd, 0xef, 0x12, 0x34}; + const char * key4 = "96aec785c6392675f87f592972"; + const unsigned char key4_expected[] = + {0x96, 0xae, 0xc7, 0x85, 0xc6, 0x39, 0x26, 0x75, 0xf8, 0x7f, 0x59, 0x29, 0x72}; + const char * key5 = "r34lly l33t w3p p4ssphr4s3 for t3st1ng"; + const unsigned char key5_expected[] = + {0xce, 0x68, 0x8b, 0x35, 0xf6, 0x0a, 0x2b, 0xbf, 0xc9, 0x8f, 0xed, 0x10, 0xda}; + + test_wifi_wep_key("wifi-wep-ascii-40", + TRUE, + NM_WEP_KEY_TYPE_KEY, + key1, + key1_expected, + sizeof(key1_expected)); + test_wifi_wep_key("wifi-wep-ascii-104", + TRUE, + NM_WEP_KEY_TYPE_KEY, + key2, + key2_expected, + sizeof(key2_expected)); + test_wifi_wep_key("wifi-wep-hex-40", + TRUE, + NM_WEP_KEY_TYPE_KEY, + key3, + key3_expected, + sizeof(key3_expected)); + test_wifi_wep_key("wifi-wep-hex-104", + TRUE, + NM_WEP_KEY_TYPE_KEY, + key4, + key4_expected, + sizeof(key4_expected)); + test_wifi_wep_key("wifi-wep-passphrase-104", + TRUE, + NM_WEP_KEY_TYPE_PASSPHRASE, + key5, + key5_expected, + sizeof(key5_expected)); + + test_wifi_wep_key("wifi-wep-old-hex-104", + TRUE, + NM_WEP_KEY_TYPE_UNKNOWN, + key4, + key4_expected, + sizeof(key4_expected)); + + /* Unlocked BSSID to test bgscan */ + test_wifi_wep_key("wifi-wep-hex-40", + FALSE, + NM_WEP_KEY_TYPE_KEY, + key3, + key3_expected, + sizeof(key3_expected)); +} + +static void +test_wifi_wpa_psk(const char * detail, + NMSupplOptType key_type, + const char * key_data, + const unsigned char * expected, + size_t expected_size, + NMSettingWirelessSecurityPmf pmf) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingWirelessSecurity *s_wsec; + gboolean success; + GError * error = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + gs_unref_bytes GBytes *wpa_psk_bytes = g_bytes_new(expected, expected_size); + + connection = new_basic_connection("Test Wifi WPA PSK", ssid, bssid_str); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + key_data, + NM_SETTING_WIRELESS_SECURITY_PMF, + (int) pmf, + NULL); + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'WPA-PSK WPA-PSK-SHA256'"); + NMTST_EXPECT_NM_INFO("Config: added 'psk' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'proto' value 'WPA RSN'"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'TKIP CCMP'"); + switch (pmf) { + case NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE: + NMTST_EXPECT_NM_INFO("Config: added 'ieee80211w' value '0'"); + break; + case NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED: + NMTST_EXPECT_NM_INFO("Config: added 'ieee80211w' value '2'"); + break; + default: + break; + } + config_dict = + build_supplicant_config(connection, + 1500, + 0, + NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES); + + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt(detail, config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt(detail, config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt(detail, config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + validate_opt(detail, + config_dict, + "key_mgmt", + NM_SUPPL_OPT_TYPE_KEYWORD, + "WPA-PSK WPA-PSK-SHA256"); + validate_opt(detail, config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN"); + validate_opt(detail, config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt(detail, config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + if (key_type == NM_SUPPL_OPT_TYPE_BYTES) + validate_opt(detail, config_dict, "psk", key_type, wpa_psk_bytes); + else if (key_type == NM_SUPPL_OPT_TYPE_STRING) + validate_opt(detail, config_dict, "psk", key_type, expected); + else + g_assert_not_reached(); +} + +static void +test_wifi_sae_psk(const char *psk) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingWirelessSecurity *s_wsec; + gboolean success; + GError * error = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + int short_psk = strlen(psk) < 8; + + connection = new_basic_connection("Test Wifi SAE", ssid, bssid_str); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "sae", + NM_SETTING_WIRELESS_SECURITY_PSK, + psk, + NULL); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'SAE'"); + if (short_psk) + NMTST_EXPECT_NM_INFO("Config: added 'sae_password' value *"); + else + NMTST_EXPECT_NM_INFO("Config: added 'psk' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'proto' value 'RSN'"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'TKIP CCMP'"); + config_dict = + build_supplicant_config(connection, + 1500, + 0, + NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES); + + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-sae", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-sae", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-sae", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + validate_opt("wifi-sae", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "SAE"); + validate_opt("wifi-sae", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "RSN"); + validate_opt("wifi-sae", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-sae", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + if (short_psk) + validate_opt("wifi-sae", config_dict, "sae_password", NM_SUPPL_OPT_TYPE_KEYWORD, psk); + else + validate_opt("wifi-sae", config_dict, "psk", NM_SUPPL_OPT_TYPE_KEYWORD, psk); +} + +static void +test_wifi_sae(void) +{ + test_wifi_sae_psk("Moo"); + test_wifi_sae_psk("Hello World!"); +} + +static void +test_wifi_wpa_psk_types(void) +{ + const char * key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893"; + const unsigned char key1_expected[] = {0xd4, 0x72, 0x1e, 0x91, 0x14, 0x61, 0xd3, 0xcd, + 0xef, 0x97, 0x93, 0x85, 0x8e, 0x97, 0x7f, 0xcd, + 0xa0, 0x91, 0x77, 0x92, 0x43, 0xab, 0xb7, 0x31, + 0x6c, 0x2f, 0x11, 0x60, 0x5a, 0x16, 0x08, 0x93}; + const char * key2 = "r34lly l33t wp4 p4ssphr4s3 for t3st1ng"; + + test_wifi_wpa_psk("wifi-wpa-psk-hex", + NM_SUPPL_OPT_TYPE_BYTES, + key1, + key1_expected, + sizeof(key1_expected), + NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL); + test_wifi_wpa_psk("wifi-wep-psk-passphrase", + NM_SUPPL_OPT_TYPE_STRING, + key2, + (gconstpointer) key2, + strlen(key2), + NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED); + test_wifi_wpa_psk("pmf-disabled", + NM_SUPPL_OPT_TYPE_STRING, + key2, + (gconstpointer) key2, + strlen(key2), + NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE); +} + +static NMConnection * +generate_wifi_eap_connection(const char * id, + GBytes * ssid, + const char * bssid_str, + NMSettingWirelessSecurityFils fils) +{ + NMConnection * connection = NULL; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x * s_8021x; + gboolean success; + GError * error = NULL; + + connection = new_basic_connection(id, ssid, bssid_str); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-eap", + NM_SETTING_WIRELESS_SECURITY_FILS, + (int) fils, + NULL); + nm_setting_wireless_security_add_proto(s_wsec, "wpa"); + nm_setting_wireless_security_add_proto(s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + nm_setting_wireless_security_add_group(s_wsec, "ccmp"); + + /* 802-1X setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + nm_setting_802_1x_set_client_cert(s_8021x, + TEST_CERT_DIR "/test-cert.p12", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL); + g_assert(nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_CERT_DIR "/test-ca-cert.pem", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL)); + nm_setting_802_1x_set_private_key(s_8021x, + TEST_CERT_DIR "/test-cert.p12", + NULL, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + return connection; +} + +static NMConnection * +generate_wifi_eap_suite_b_192_connection(const char *id, GBytes *ssid, const char *bssid_str) +{ + NMConnection * connection = NULL; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x * s_8021x; + gboolean success; + GError * error = NULL; + + connection = new_basic_connection(id, ssid, bssid_str); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap-suite-b-192", NULL); + + /* 802-1X setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + nm_setting_802_1x_set_client_cert(s_8021x, + TEST_CERT_DIR "/test-cert.p12", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL); + g_assert(nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_CERT_DIR "/test-ca-cert.pem", + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL)); + nm_setting_802_1x_set_private_key(s_8021x, + TEST_CERT_DIR "/test-cert.p12", + NULL, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + NULL); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + + return connection; +} + +static void +test_wifi_eap_locked_bssid(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + guint32 mtu = 1100; + + connection = generate_wifi_eap_connection("Test Wifi EAP-TLS Locked", + ssid, + bssid_str, + NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'WPA-EAP'"); + NMTST_EXPECT_NM_INFO("Config: added 'proto' value 'WPA RSN'"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'eap' value 'TLS'"); + NMTST_EXPECT_NM_INFO("Config: added 'fragment_size' value '1086'"); + NMTST_EXPECT_NM_INFO("Config: added 'ca_cert' value '*/test-ca-cert.pem'"); + NMTST_EXPECT_NM_INFO("Config: added 'private_key' value '*/test-cert.p12'"); + NMTST_EXPECT_NM_INFO("Config: added 'proactive_key_caching' value '1'"); + config_dict = build_supplicant_config(connection, mtu, 0, NM_SUPPL_CAP_MASK_NONE); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-eap", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + validate_opt("wifi-eap", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA-EAP"); + validate_opt("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS"); + validate_opt("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN"); + validate_opt("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", + config_dict, + "fragment_size", + NM_SUPPL_OPT_TYPE_INT, + GINT_TO_POINTER(mtu - 14)); +} + +static void +test_wifi_eap_unlocked_bssid(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bgscan_data = "simple:30:-65:300"; + gs_unref_bytes GBytes *bgscan = g_bytes_new(bgscan_data, strlen(bgscan_data)); + guint32 mtu = 1100; + + connection = generate_wifi_eap_connection("Test Wifi EAP-TLS Unlocked", + ssid, + NULL, + NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'FILS-SHA256 FILS-SHA384'"); + NMTST_EXPECT_NM_INFO("Config: added 'proto' value 'WPA RSN'"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'eap' value 'TLS'"); + NMTST_EXPECT_NM_INFO("Config: added 'fragment_size' value '1086'"); + NMTST_EXPECT_NM_INFO("Config: added 'ca_cert' value '*/test-ca-cert.pem'"); + NMTST_EXPECT_NM_INFO("Config: added 'private_key' value '*/test-cert.p12'"); + NMTST_EXPECT_NM_INFO("Config: added 'proactive_key_caching' value '1'"); + NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-65:300'"); + config_dict = build_supplicant_config(connection, mtu, 0, NM_SUPPL_CAP_MASK_T_FILS_YES); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-eap", + config_dict, + "key_mgmt", + NM_SUPPL_OPT_TYPE_KEYWORD, + "FILS-SHA256 FILS-SHA384"); + validate_opt("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS"); + validate_opt("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN"); + validate_opt("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", + config_dict, + "fragment_size", + NM_SUPPL_OPT_TYPE_INT, + GINT_TO_POINTER(mtu - 14)); + validate_opt("wifi-eap", config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan); +} + +static void +test_wifi_eap_fils_disabled(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bgscan_data = "simple:30:-65:300"; + gs_unref_bytes GBytes *bgscan = g_bytes_new(bgscan_data, strlen(bgscan_data)); + guint32 mtu = 1100; + + connection = generate_wifi_eap_connection("Test Wifi FILS disabled", + ssid, + NULL, + NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'WPA-EAP WPA-EAP-SHA256'"); + NMTST_EXPECT_NM_INFO("Config: added 'proto' value 'WPA RSN'"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'TKIP CCMP'"); + NMTST_EXPECT_NM_INFO("Config: added 'eap' value 'TLS'"); + NMTST_EXPECT_NM_INFO("Config: added 'fragment_size' value '1086'"); + NMTST_EXPECT_NM_INFO("Config: added 'ca_cert' value '*/test-ca-cert.pem'"); + NMTST_EXPECT_NM_INFO("Config: added 'private_key' value '*/test-cert.p12'"); + NMTST_EXPECT_NM_INFO("Config: added 'proactive_key_caching' value '1'"); + NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-65:300'"); + config_dict = + build_supplicant_config(connection, + mtu, + 0, + NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-eap", + config_dict, + "key_mgmt", + NM_SUPPL_OPT_TYPE_KEYWORD, + "WPA-EAP WPA-EAP-SHA256"); + validate_opt("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS"); + validate_opt("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN"); + validate_opt("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP"); + validate_opt("wifi-eap", + config_dict, + "fragment_size", + NM_SUPPL_OPT_TYPE_INT, + GINT_TO_POINTER(mtu - 14)); + validate_opt("wifi-eap", config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan); +} + +static void +test_wifi_eap_suite_b_generation(void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *config_dict = NULL; + const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44}; + gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data)); + const char * bssid_str = "11:22:33:44:55:66"; + guint32 mtu = 1100; + + connection = generate_wifi_eap_suite_b_192_connection("EAP-TLS Suite B 192", ssid, bssid_str); + + NMTST_EXPECT_NM_INFO("Config: added 'ssid' value 'Test SSID'*"); + NMTST_EXPECT_NM_INFO("Config: added 'scan_ssid' value '1'*"); + NMTST_EXPECT_NM_INFO("Config: added 'bssid' value '11:22:33:44:55:66'*"); + NMTST_EXPECT_NM_INFO("Config: added 'freq_list' value *"); + NMTST_EXPECT_NM_INFO("Config: added 'pairwise' value 'GCMP-256'"); + NMTST_EXPECT_NM_INFO("Config: added 'group' value 'GCMP-256'"); + NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'WPA-EAP-SUITE-B-192'"); + NMTST_EXPECT_NM_INFO("Config: added 'eap' value 'TLS'"); + NMTST_EXPECT_NM_INFO("Config: added 'fragment_size' value '1086'"); + NMTST_EXPECT_NM_INFO("Config: added 'ca_cert' value '*/test-ca-cert.pem'"); + NMTST_EXPECT_NM_INFO("Config: added 'private_key' value '*/test-cert.p12'"); + NMTST_EXPECT_NM_INFO("Config: added 'proactive_key_caching' value '1'"); + config_dict = build_supplicant_config(connection, mtu, 0, NM_SUPPL_CAP_MASK_T_PMF_YES); + g_test_assert_expected_messages(); + g_assert(config_dict); + + validate_opt("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1)); + validate_opt("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid); + validate_opt("wifi-eap", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str); + validate_opt("wifi-eap", + config_dict, + "key_mgmt", + NM_SUPPL_OPT_TYPE_KEYWORD, + "WPA-EAP-SUITE-B-192"); + validate_opt("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS"); + validate_opt("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "GCMP-256"); + validate_opt("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "GCMP-256"); +} + +/*****************************************************************************/ + +static void +test_suppl_cap_mask(void) +{ + NMSupplCapType type; + + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_T_AP_NO, NM_SUPPL_CAP_TYPE_AP), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_T_AP_YES, NM_SUPPL_CAP_TYPE_AP), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_NONE, NM_SUPPL_CAP_TYPE_AP), + ==, + NM_TERNARY_DEFAULT); + + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_T_FILS_NO, NM_SUPPL_CAP_TYPE_FILS), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_T_FILS_YES, NM_SUPPL_CAP_TYPE_FILS), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(NM_SUPPL_CAP_MASK_NONE, NM_SUPPL_CAP_TYPE_FILS), + ==, + NM_TERNARY_DEFAULT); + + for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++) { + NMTernary value; + NMSupplCapMask feature; + NMSupplCapMask feature2; + + feature = nmtst_get_rand_bool() ? 0u : nmtst_get_rand_uint64(); + feature &= NM_SUPPL_CAP_MASK_ALL; + + value = nmtst_rand_select(NM_TERNARY_DEFAULT, NM_TERNARY_FALSE, NM_TERNARY_TRUE); + + feature2 = NM_SUPPL_CAP_MASK_SET(feature, type, value); + + g_assert_cmpint(NM_SUPPL_CAP_MASK_GET(feature2, type), ==, value); + g_assert_cmpint(feature & ~NM_SUPPL_CAP_MASK_MASK(type), + ==, + feature2 & ~NM_SUPPL_CAP_MASK_MASK(type)); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/supplicant-config/wifi-open", test_wifi_open); + g_test_add_func("/supplicant-config/wifi-wep", test_wifi_wep); + g_test_add_func("/supplicant-config/wifi-wpa-psk-types", test_wifi_wpa_psk_types); + g_test_add_func("/supplicant-config/wifi-eap/locked-bssid", test_wifi_eap_locked_bssid); + g_test_add_func("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid); + g_test_add_func("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled); + g_test_add_func("/supplicant-config/wifi-sae", test_wifi_sae); + g_test_add_func("/supplicant-config/test_suppl_cap_mask", test_suppl_cap_mask); + g_test_add_func("/supplicant-config/wifi-eap-suite-b-192", test_wifi_eap_suite_b_generation); + + return g_test_run(); +} diff --git a/src/core/systemd/meson.build b/src/core/systemd/meson.build new file mode 100644 index 0000000..4f3a63e --- /dev/null +++ b/src/core/systemd/meson.build @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +incs = include_directories( + 'sd-adapt-core', + 'src/libsystemd-network', + 'src/libsystemd/sd-event', + 'src/systemd', +) + +libnm_systemd_core = static_library( + 'nm-systemd-core', + sources: files( + 'src/libsystemd-network/arp-util.c', + 'src/libsystemd-network/dhcp-identifier.c', + 'src/libsystemd-network/dhcp-network.c', + 'src/libsystemd-network/dhcp-option.c', + 'src/libsystemd-network/dhcp-packet.c', + 'src/libsystemd-network/dhcp6-network.c', + 'src/libsystemd-network/dhcp6-option.c', + 'src/libsystemd-network/lldp-neighbor.c', + 'src/libsystemd-network/lldp-network.c', + 'src/libsystemd-network/network-internal.c', + 'src/libsystemd-network/sd-dhcp-client.c', + 'src/libsystemd-network/sd-dhcp-lease.c', + 'src/libsystemd-network/sd-dhcp6-client.c', + 'src/libsystemd-network/sd-dhcp6-lease.c', + 'src/libsystemd-network/sd-ipv4acd.c', + 'src/libsystemd-network/sd-ipv4ll.c', + 'src/libsystemd-network/sd-lldp.c', + 'src/libsystemd/sd-event/event-util.c', + 'src/libsystemd/sd-event/sd-event.c', + 'src/libsystemd/sd-id128/id128-util.c', + 'src/libsystemd/sd-id128/sd-id128.c', + 'nm-sd.c', + 'nm-sd-utils-core.c', + 'nm-sd-utils-dhcp.c', + 'sd-adapt-core/nm-sd-adapt-core.c', + ), + include_directories: [ + incs, + src_inc, + ], + dependencies: [ + glib_nm_default_dep, + libnm_core_dep, + libnm_systemd_shared_dep, + ], + c_args: '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD', + link_with: libc_siphash, +) + +libnm_systemd_core_dep = declare_dependency( + include_directories: incs, + dependencies: [ + glib_dep, + libnm_core_dep, + libnm_systemd_shared_dep, + ], + link_with: libnm_systemd_core, +) diff --git a/src/core/systemd/nm-sd-utils-core.c b/src/core/systemd/nm-sd-utils-core.c new file mode 100644 index 0000000..c7d4aad --- /dev/null +++ b/src/core/systemd/nm-sd-utils-core.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-sd-utils-core.h" + +#include "nm-core-internal.h" + +#include "nm-sd-adapt-core.h" + +#include "sd-id128.h" + +/*****************************************************************************/ + +NMUuid * +nm_sd_utils_id128_get_machine(NMUuid *out_uuid) +{ + g_assert(out_uuid); + + G_STATIC_ASSERT_EXPR(sizeof(*out_uuid) == sizeof(sd_id128_t)); + if (sd_id128_get_machine((sd_id128_t *) out_uuid) < 0) + return NULL; + return out_uuid; +} diff --git a/src/core/systemd/nm-sd-utils-core.h b/src/core/systemd/nm-sd-utils-core.h new file mode 100644 index 0000000..ccad002 --- /dev/null +++ b/src/core/systemd/nm-sd-utils-core.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_SD_UTILS_CORE_H__ +#define __NM_SD_UTILS_CORE_H__ + +/*****************************************************************************/ + +struct _NMUuid; + +struct _NMUuid *nm_sd_utils_id128_get_machine(struct _NMUuid *out_uuid); + +/*****************************************************************************/ + +#endif /* __NM_SD_UTILS_CORE_H__ */ diff --git a/src/core/systemd/nm-sd-utils-dhcp.c b/src/core/systemd/nm-sd-utils-dhcp.c new file mode 100644 index 0000000..bafc8fc --- /dev/null +++ b/src/core/systemd/nm-sd-utils-dhcp.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-sd-utils-dhcp.h" + +#include "sd-adapt-core/nm-sd-adapt-core.h" +#include "src/libsystemd-network/dhcp-lease-internal.h" + +int +nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options) +{ + struct sd_dhcp_raw_option *raw_option; + int cnt = 0; + + g_return_val_if_fail(lease, -EINVAL); + g_return_val_if_fail(out_options, -EINVAL); + g_return_val_if_fail(*out_options == NULL, -EINVAL); + + if (lease->private_options == NULL) + return -ENODATA; + + LIST_FOREACH(options, raw_option, lease->private_options) + cnt++; + + *out_options = g_new(nm_sd_dhcp_option, cnt); + cnt = 0; + + LIST_FOREACH(options, raw_option, lease->private_options) + { + (*out_options)[cnt].code = raw_option->tag; + (*out_options)[cnt].data = raw_option->data; + (*out_options)[cnt].data_len = raw_option->length; + cnt++; + } + + return cnt; +} diff --git a/src/core/systemd/nm-sd-utils-dhcp.h b/src/core/systemd/nm-sd-utils-dhcp.h new file mode 100644 index 0000000..5d126d5 --- /dev/null +++ b/src/core/systemd/nm-sd-utils-dhcp.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ +#define __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ + +#include "nm-sd.h" + +typedef struct { + uint8_t code; + uint8_t data_len; + void * data; +} nm_sd_dhcp_option; + +int nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options); + +#endif /* __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ */ diff --git a/src/core/systemd/nm-sd.c b/src/core/systemd/nm-sd.c new file mode 100644 index 0000000..6543321 --- /dev/null +++ b/src/core/systemd/nm-sd.c @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-sd.h" + +#include "sd-event.h" + +/***************************************************************************** + * Integrating sd_event into glib. Taken and adjusted from + * https://www.freedesktop.org/software/systemd/man/sd_event_get_fd.html + *****************************************************************************/ + +typedef struct SDEventSource { + GSource source; + GPollFD pollfd; + sd_event *event; +} SDEventSource; + +static gboolean +event_prepare(GSource *source, int *timeout_) +{ + return sd_event_prepare(((SDEventSource *) source)->event) > 0; +} + +static gboolean +event_check(GSource *source) +{ + return sd_event_wait(((SDEventSource *) source)->event, 0) > 0; +} + +static gboolean +event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + return sd_event_dispatch(((SDEventSource *) source)->event) > 0; +} + +static void +event_finalize(GSource *source) +{ + SDEventSource *s = (SDEventSource *) source; + + sd_event_unref(s->event); +} + +static SDEventSource * +event_create_source(sd_event *event) +{ + static const GSourceFuncs event_funcs = { + .prepare = event_prepare, + .check = event_check, + .dispatch = event_dispatch, + .finalize = event_finalize, + }; + SDEventSource *source; + gboolean is_default_event = FALSE; + int r; + + if (!event) { + is_default_event = TRUE; + r = sd_event_default(&event); + if (r < 0) + g_return_val_if_reached(NULL); + } + + source = (SDEventSource *) g_source_new((GSourceFuncs *) &event_funcs, sizeof(SDEventSource)); + + source->event = is_default_event ? g_steal_pointer(&event) : sd_event_ref(event); + + source->pollfd = (GPollFD){ + .fd = sd_event_get_fd(source->event), + .events = G_IO_IN | G_IO_HUP | G_IO_ERR, + }; + + g_source_add_poll(&source->source, &source->pollfd); + + return source; +} + +static guint +event_attach(sd_event *event, GMainContext *context) +{ + SDEventSource *source; + guint id; + + source = event_create_source(event); + + g_return_val_if_fail(source, 0); + + id = g_source_attach((GSource *) source, context); + g_source_unref((GSource *) source); + + nm_assert(id != 0); + return id; +} + +guint +nm_sd_event_attach_default(void) +{ + return event_attach(NULL, NULL); +} + +/*****************************************************************************/ + +/* ensure that defines in nm-sd.h correspond to the internal defines. */ + +#include "nm-sd-adapt-core.h" +#include "dhcp-lease-internal.h" + +/*****************************************************************************/ diff --git a/src/core/systemd/nm-sd.h b/src/core/systemd/nm-sd.h new file mode 100644 index 0000000..0ea4be2 --- /dev/null +++ b/src/core/systemd/nm-sd.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#ifndef __NM_SD_H__ +#define __NM_SD_H__ + +#include "systemd/src/systemd/sd-dhcp-client.h" +#include "systemd/src/systemd/sd-dhcp6-client.h" +#include "systemd/src/systemd/sd-lldp.h" +#include "systemd/src/systemd/sd-ipv4ll.h" + +/*****************************************************************************/ + +guint nm_sd_event_attach_default(void); + +/***************************************************************************** + * expose internal systemd API + * + * FIXME: don't use any internal systemd API. + *****************************************************************************/ + +struct sd_dhcp_lease; + +int dhcp_lease_save(struct sd_dhcp_lease *lease, const char *lease_file); +int dhcp_lease_load(struct sd_dhcp_lease **ret, const char *lease_file); + +#endif /* __NM_SD_H__ */ diff --git a/src/core/systemd/sd-adapt-core/condition.h b/src/core/systemd/sd-adapt-core/condition.h new file mode 100644 index 0000000..d3a6812 --- /dev/null +++ b/src/core/systemd/sd-adapt-core/condition.h @@ -0,0 +1,5 @@ +#pragma once + +/* dummy header */ + +typedef struct _sd_adapt_Condition Condition; diff --git a/src/core/systemd/sd-adapt-core/conf-parser.h b/src/core/systemd/sd-adapt-core/conf-parser.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/conf-parser.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/device-util.h b/src/core/systemd/sd-adapt-core/device-util.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/device-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/khash.h b/src/core/systemd/sd-adapt-core/khash.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/khash.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/network-util.h b/src/core/systemd/sd-adapt-core/network-util.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/network-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c new file mode 100644 index 0000000..c03d4d9 --- /dev/null +++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-sd-adapt-core.h" + +#include "fd-util.h" + +/*****************************************************************************/ + +int +asynchronous_close(int fd) +{ + safe_close(fd); + return -1; +} + +/*****************************************************************************/ diff --git a/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h new file mode 100644 index 0000000..a733291 --- /dev/null +++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_SD_ADAPT_CORE_H__ +#define __NM_SD_ADAPT_CORE_H__ + +#include "nm-default.h" + +#include +#include +#include + +#include "systemd/sd-adapt-shared/nm-sd-adapt-shared.h" + +#ifndef HAVE_SYS_AUXV_H + #define HAVE_SYS_AUXV_H 0 +#endif + +/***************************************************************************** + * The remainder of the header is only enabled when building the systemd code + * itself. + *****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD + + #include + #include + #include + #include + #include + #ifdef HAVE_SYS_AUXV_H + #include + #endif + #include + #include + #include + + /* Missing in Linux 3.2.0, in Ubuntu 12.04 */ + #ifndef BPF_XOR + #define BPF_XOR 0xa0 + #endif + + #ifndef ETHERTYPE_LLDP + #define ETHERTYPE_LLDP 0x88cc + #endif + + #ifndef HAVE_SECURE_GETENV + #ifdef HAVE___SECURE_GETENV + #define secure_getenv __secure_getenv + #else + #error neither secure_getenv nor __secure_getenv is available + #endif + #endif + +/*****************************************************************************/ + +static inline int +sd_notify(int unset_environment, const char *state) +{ + return 0; +} + + /* Can't include both net/if.h and linux/if.h; so have to define this here */ + #ifndef IF_NAMESIZE + #define IF_NAMESIZE 16 + #endif + + #ifndef IFNAMSIZ + #define IFNAMSIZ IF_NAMESIZE + #endif + + #ifndef MAX_HANDLE_SZ + #define MAX_HANDLE_SZ 128 + #endif + + #include "sd-id128.h" + #include "sparse-endian.h" + #include "async.h" + #include "util.h" + +#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */ + +#endif /* __NM_SD_ADAPT_CORE_H__ */ diff --git a/src/core/systemd/sd-adapt-core/sd-daemon.h b/src/core/systemd/sd-adapt-core/sd-daemon.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/sd-daemon.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/sd-device.h b/src/core/systemd/sd-adapt-core/sd-device.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/sd-device.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/sd-adapt-core/udev-util.h b/src/core/systemd/sd-adapt-core/udev-util.h new file mode 100644 index 0000000..637892c --- /dev/null +++ b/src/core/systemd/sd-adapt-core/udev-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/core/systemd/src/libsystemd-network/arp-util.c b/src/core/systemd/src/libsystemd-network/arp-util.c new file mode 100644 index 0000000..1c777d8 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/arp-util.c @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "arp-util.h" +#include "fd-util.h" +#include "unaligned.h" +#include "util.h" + +int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Hardware Address must be different from our own */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(ð_mac->ether_addr_octet[0])),/* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(ð_mac->ether_addr_octet[4])),/* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter + }; + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +static int arp_send_packet(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ + }; + int r; + + assert(fd >= 0); + assert(pa != 0); + assert(ha); + + memcpy(&arp.arp_sha, ha, ETH_ALEN); + memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + + if (announce) + memcpy(&arp.arp_spa, &pa, sizeof(pa)); + + r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/core/systemd/src/libsystemd-network/arp-util.h b/src/core/systemd/src/libsystemd-network/arp-util.h new file mode 100644 index 0000000..2dac8cf --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/arp-util.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include + +#include "socket-util.h" +#include "sparse-endian.h" + +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); diff --git a/src/core/systemd/src/libsystemd-network/dhcp-identifier.c b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c new file mode 100644 index 0000000..0720e38 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "sd-device.h" +#include "sd-id128.h" + +#include "dhcp-identifier.h" +#include "dhcp6-protocol.h" +#include "network-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "udev-util.h" +#include "virt.h" + +#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) +#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) +#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) { + struct duid d; + + assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); + if (duid_len > MAX_DUID_LEN) + return -EINVAL; + + if (!strict) + /* Strict validation is not requested. We only ensure that the + * DUID is not too long. */ + return 0; + + switch (duid_type) { + case DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} + +#if 0 /* NM_IGNORED */ +int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { + uint16_t time_from_2000y; + + assert(duid); + assert(len); + assert(addr); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (t < USEC_2000) + time_from_2000y = 0; + else + time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff); + + unaligned_write_be16(&duid->type, DUID_TYPE_LLT); + unaligned_write_be16(&duid->llt.htype, arp_type); + unaligned_write_be32(&duid->llt.time, time_from_2000y); + memcpy(duid->llt.haddr, addr, addr_len); + + *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len; + + return 0; +} + +int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { + assert(duid); + assert(len); + assert(addr); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + unaligned_write_be16(&duid->type, DUID_TYPE_LL); + unaligned_write_be16(&duid->ll.htype, arp_type); + memcpy(duid->ll.haddr, addr, addr_len); + + *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len; + + return 0; +} +#endif /* NM_IGNORED */ + +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + uint64_t hash; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine(&machine_id); + if (r < 0) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10); +#else + return r; +#endif + } + + unaligned_write_be16(&duid->type, DUID_TYPE_EN); + unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); + + *len = sizeof(duid->type) + sizeof(duid->en); + + /* a bit of snake-oil perhaps, but no need to expose the machine-id + * directly; duid->en.id might not be aligned, so we need to copy */ + hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); + memcpy(duid->en.id, &hash, sizeof(duid->en.id)); + + return 0; +} + +#if 0 /* NM_IGNORED */ +int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id); + if (r < 0) + return r; + + unaligned_write_be16(&duid->type, DUID_TYPE_UUID); + memcpy(&duid->raw.data, &machine_id, sizeof(machine_id)); + + *len = sizeof(duid->type) + sizeof(machine_id); + + return 0; +} +#endif + +int dhcp_identifier_set_iaid( + int ifindex, + const uint8_t *mac, + size_t mac_len, + bool legacy_unstable_byteorder, + void *_id) { +#if 0 /* NM_IGNORED */ + /* name is a pointer to memory in the sd_device struct, so must + * have the same scope */ + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *name = NULL; + uint64_t id; + uint32_t id32; + + if (detect_container() <= 0) { + /* not in a container, udev will be around */ + char ifindex_str[1 + DECIMAL_STR_MAX(int)]; + int r; + + xsprintf(ifindex_str, "n%d", ifindex); + if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) { + r = sd_device_get_is_initialized(device); + if (r < 0) + return r; + if (r == 0) + /* not yet ready */ + return -EBUSY; + + r = device_is_renaming(device); + if (r < 0) + return r; + if (r > 0) + /* device is under renaming */ + return -EBUSY; + + name = net_get_name_persistent(device); + } + } + + if (name) + id = siphash24(name, strlen(name), HASH_KEY.bytes); + else + /* fall back to MAC address if no predictable name available */ + id = siphash24(mac, mac_len, HASH_KEY.bytes); + + id32 = (id & 0xffffffff) ^ (id >> 32); + + if (legacy_unstable_byteorder) + /* for historical reasons (a bug), the bits were swapped and thus + * the result was endianness dependent. Preserve that behavior. */ + id32 = __bswap_32(id32); + else + /* the fixed behavior returns a stable byte order. Since LE is expected + * to be more common, swap the bytes on LE to give the same as legacy + * behavior. */ + id32 = be32toh(id32); + + unaligned_write_ne32(_id, id32); + return 0; +#else /* NM_IGNORED */ + /* for NetworkManager, we don't use this function and we should never call here. + * This got replaced by nm_utils_create_dhcp_iaid(). */ + g_return_val_if_reached (-EINVAL); +#endif /* NM_IGNORED */ +} diff --git a/src/core/systemd/src/libsystemd-network/dhcp-identifier.h b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h new file mode 100644 index 0000000..e9f2ea7 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-id128.h" + +#include "macro.h" +#include "sparse-endian.h" +#include "time-util.h" +#include "unaligned.h" + +#define SYSTEMD_PEN 43793 + +typedef enum DUIDType { + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -1, +} DUIDType; + +/* RFC 3315 section 9.1: + * A DUID can be no more than 128 octets long (not including the type code). + */ +#define MAX_DUID_LEN 128 + +/* https://tools.ietf.org/html/rfc3315#section-9.1 */ +struct duid { + be16_t type; + union { + struct { + /* DUID_TYPE_LLT */ + be16_t htype; + be32_t time; + uint8_t haddr[0]; + } _packed_ llt; + struct { + /* DUID_TYPE_EN */ + be32_t pen; + uint8_t id[8]; + } _packed_ en; + struct { + /* DUID_TYPE_LL */ + be16_t htype; + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* DUID_TYPE_UUID */ + sd_id128_t uuid; + } _packed_ uuid; + struct { + uint8_t data[MAX_DUID_LEN]; + } _packed_ raw; + }; +} _packed_; + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict); +int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); +int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); +int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len); +int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id); diff --git a/src/core/systemd/src/libsystemd-network/dhcp-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-internal.h new file mode 100644 index 0000000..40e6b1f --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-internal.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include +#include +#include + +#include "sd-dhcp-client.h" + +#include "dhcp-protocol.h" +#include "socket-util.h" + +typedef struct sd_dhcp_option { + unsigned n_ref; + + uint8_t option; + void *data; + size_t length; +} sd_dhcp_option; + +typedef struct DHCPServerData { + struct in_addr *addr; + size_t size; +} DHCPServerData; + +extern const struct hash_ops dhcp_option_hash_ops; + +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, + const uint8_t *mac_addr, size_t mac_addr_len, + const uint8_t *bcast_addr, size_t bcast_addr_len, + uint16_t arp_type, uint16_t port); +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type); +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + const void *packet, size_t len); +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, + const void *packet, size_t len); + +int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload, + uint8_t code, size_t optlen, const void *optval); + +typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, + const void *option, void *userdata); + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + size_t *optoffset); + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source, be32_t destination_addr, + uint16_t destination, uint16_t len, int ip_service_type); + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port); + +/* If we are invoking callbacks of a dhcp-client, ensure unreffing the + * client from the callback doesn't destroy the object we are working + * on */ +#define DHCP_CLIENT_DONT_DESTROY(client) \ + _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client) + +#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__) +#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__) diff --git a/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h new file mode 100644 index 0000000..49392d1 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "sd-dhcp-client.h" + +#include "dhcp-internal.h" +#include "dhcp-protocol.h" +#include "list.h" +#include "util.h" + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + unsigned char dst_prefixlen; + + uint8_t option; +}; + +struct sd_dhcp_raw_option { + LIST_FIELDS(struct sd_dhcp_raw_option, options); + + uint8_t tag; + uint8_t length; + void *data; +}; + +struct sd_dhcp_lease { + unsigned n_ref; + + /* each 0 if unset */ + uint32_t t1; + uint32_t t2; + uint32_t lifetime; + + /* each 0 if unset */ + be32_t address; + be32_t server_address; + be32_t next_server; + + bool have_subnet_mask; + be32_t subnet_mask; + + bool have_broadcast; + be32_t broadcast; + + struct in_addr *router; + size_t router_size; + + DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; + + struct sd_dhcp_route *static_route; + size_t static_route_size, static_route_allocated; + + uint16_t mtu; /* 0 if unset */ + + char *domainname; + char **search_domains; + char *hostname; + char *root_path; + + void *client_id; + size_t client_id_len; + + void *vendor_specific; + size_t vendor_specific_len; + + char *timezone; + + LIST_HEAD(struct sd_dhcp_raw_option, private_options); +}; + +int dhcp_lease_new(sd_dhcp_lease **ret); + +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata); +int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains); +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len); + +int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); + +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len); diff --git a/src/core/systemd/src/libsystemd-network/dhcp-network.c b/src/core/systemd/src/libsystemd-network/dhcp-network.c new file mode 100644 index 0000000..4477f46 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-network.c @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcp-internal.h" +#include "fd-util.h" +#include "socket-util.h" +#include "unaligned.h" + +static int _bind_raw_socket(int ifindex, union sockaddr_union *link, + uint32_t xid, + const uint8_t *bcast_addr, + size_t bcast_addr_len, + const struct ether_addr *eth_mac, + uint16_t arp_type, uint8_t dhcp_hlen, + uint16_t port) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0), /* address length == dhcp_hlen ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally + * compare chaddr for ETH_ALEN bytes. */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12), /* A (the MAC address length) == ETH_ALEN ? */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(ð_mac->ether_addr_octet[0])), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(ð_mac->ether_addr_octet[4])), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + assert(link); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); + if (r < 0) + return r; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + link->ll = (struct sockaddr_ll) { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_hatype = htobe16(arp_type), + .sll_halen = bcast_addr_len, + }; + memcpy(link->ll.sll_addr, bcast_addr, bcast_addr_len); /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ + + r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, + const uint8_t *mac_addr, size_t mac_addr_len, + const uint8_t *bcast_addr, size_t bcast_addr_len, + uint16_t arp_type, uint16_t port) { + static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + /* Default broadcast address for IPoIB */ + static const uint8_t ib_bcast[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } }; + const uint8_t *default_bcast_addr; + size_t expected_bcast_addr_len; + uint8_t dhcp_hlen = 0; + + if (arp_type == ARPHRD_ETHER) { + assert_return(mac_addr_len == ETH_ALEN, -EINVAL); + memcpy(ð_mac, mac_addr, ETH_ALEN); + dhcp_hlen = ETH_ALEN; + + default_bcast_addr = eth_bcast; + expected_bcast_addr_len = ETH_ALEN; + } else if (arp_type == ARPHRD_INFINIBAND) { + default_bcast_addr = ib_bcast; + expected_bcast_addr_len = INFINIBAND_ALEN; + } else + return -EINVAL; + + if (bcast_addr && bcast_addr_len > 0) + assert_return(bcast_addr_len == expected_bcast_addr_len, -EINVAL); + else { + bcast_addr = default_bcast_addr; + bcast_addr_len = expected_bcast_addr_len; + } + + return _bind_raw_socket(ifindex, link, xid, bcast_addr, bcast_addr_len, + ð_mac, arp_type, dhcp_hlen, port); +} + +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { + union sockaddr_union src = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + _cleanup_close_ int s = -1; + int r; + + s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + if (ip_service_type >= 0) + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type); + else + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6); + + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + if (ifindex > 0) { + r = socket_bind_to_ifindex(s, ifindex); + if (r < 0) + return r; + } + + if (address == INADDR_ANY) { + r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + } else { + r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + return r; + } + + r = bind(s, &src.sa, sizeof(src.in)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + const void *packet, size_t len) { + int r; + + assert(link); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll)); + if (r < 0) + return -errno; + + return 0; +} + +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + int r; + + assert(s >= 0); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/dhcp-option.c b/src/core/systemd/src/libsystemd-network/dhcp-option.c new file mode 100644 index 0000000..2c15d85 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-option.c @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "alloc-util.h" +#include "dhcp-internal.h" +#include "dhcp-server-internal.h" +#include "memory-util.h" +#include "strv.h" +#include "utf8.h" + +static int option_append(uint8_t options[], size_t size, size_t *offset, + uint8_t code, size_t optlen, const void *optval) { + assert(options); + assert(offset); + + if (code != SD_DHCP_OPTION_END) + /* always make sure there is space for an END option */ + size--; + + switch (code) { + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_END: + if (*offset + 1 > size) + return -ENOBUFS; + + options[*offset] = code; + *offset += 1; + break; + + case SD_DHCP_OPTION_USER_CLASS: { + size_t total = 0; + char **s; + + STRV_FOREACH(s, (char **) optval) { + size_t len = strlen(*s); + + if (len > 255) + return -ENAMETOOLONG; + + total += 1 + len; + } + + if (*offset + 2 + total > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = total; + *offset += 2; + + STRV_FOREACH(s, (char **) optval) { + size_t len = strlen(*s); + + options[*offset] = len; + + memcpy(&options[*offset + 1], *s, len); + *offset += 1 + len; + } + + break; + } + case SD_DHCP_OPTION_SIP_SERVER: + if (*offset + 3 + optlen > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = optlen + 1; + options[*offset + 2] = 1; + + memcpy_safe(&options[*offset + 3], optval, optlen); + *offset += 3 + optlen; + + break; + case SD_DHCP_OPTION_VENDOR_SPECIFIC: { + OrderedHashmap *s = (OrderedHashmap *) optval; + struct sd_dhcp_option *p; + size_t l = 0; + + ORDERED_HASHMAP_FOREACH(p, s) + l += p->length + 2; + + if (*offset + l + 2 > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = l; + + *offset += 2; + + ORDERED_HASHMAP_FOREACH(p, s) { + options[*offset] = p->option; + options[*offset + 1] = p->length; + memcpy(&options[*offset + 2], p->data, p->length); + *offset += 2 + p->length; + } + + break; + } + default: + if (*offset + 2 + optlen > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = optlen; + + memcpy_safe(&options[*offset + 2], optval, optlen); + *offset += 2 + optlen; + + break; + } + + return 0; +} + +int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, + uint8_t overload, + uint8_t code, size_t optlen, const void *optval) { + const bool use_file = overload & DHCP_OVERLOAD_FILE; + const bool use_sname = overload & DHCP_OVERLOAD_SNAME; + int r; + + assert(message); + assert(offset); + + /* If *offset is in range [0, size), we are writing to ->options, + * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file, + * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname)) + * and use_sname, we are writing to ->sname. + */ + + if (*offset < size) { + /* still space in the options array */ + r = option_append(message->options, size, offset, code, optlen, optval); + if (r >= 0) + return 0; + else if (r == -ENOBUFS && (use_file || use_sname)) { + /* did not fit, but we have more buffers to try + close the options array and move the offset to its end */ + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size; + } else + return r; + } + + if (use_file) { + size_t file_offset = *offset - size; + + if (file_offset < sizeof(message->file)) { + /* still space in the 'file' array */ + r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + file_offset; + return 0; + } else if (r == -ENOBUFS && use_sname) { + /* did not fit, but we have more buffers to try + close the file array and move the offset to its end */ + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size + sizeof(message->file); + } else + return r; + } + } + + if (use_sname) { + size_t sname_offset = *offset - size - use_file*sizeof(message->file); + + if (sname_offset < sizeof(message->sname)) { + /* still space in the 'sname' array */ + r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + use_file*sizeof(message->file) + sname_offset; + return 0; + } else + /* no space, or other error, give up */ + return r; + } + } + + return -ENOBUFS; +} + +static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, + uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, + void *userdata) { + uint8_t code, len; + const uint8_t *option; + size_t offset = 0; + + while (offset < buflen) { + code = options[offset ++]; + + switch (code) { + case SD_DHCP_OPTION_PAD: + continue; + + case SD_DHCP_OPTION_END: + return 0; + } + + if (buflen < offset + 1) + return -ENOBUFS; + + len = options[offset ++]; + + if (buflen < offset + len) + return -EINVAL; + + option = &options[offset]; + + switch (code) { + case SD_DHCP_OPTION_MESSAGE_TYPE: + if (len != 1) + return -EINVAL; + + if (message_type) + *message_type = *option; + + break; + + case SD_DHCP_OPTION_ERROR_MESSAGE: + if (len == 0) + return -EINVAL; + + if (error_message) { + _cleanup_free_ char *string = NULL; + + /* Accept a trailing NUL byte */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = memdup_suffix0((const char *) option, len); + if (!string) + return -ENOMEM; + + if (!ascii_is_valid(string)) + return -EINVAL; + + free_and_replace(*error_message, string); + } + + break; + case SD_DHCP_OPTION_OVERLOAD: + if (len != 1) + return -EINVAL; + + if (overload) + *overload = *option; + + break; + + default: + if (cb) + cb(code, len, option, userdata); + + break; + } + + offset += len; + } + + if (offset < buflen) + return -EINVAL; + + return 0; +} + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; + uint8_t overload = 0; + uint8_t message_type = 0; + int r; + + if (!message) + return -EINVAL; + + if (len < sizeof(DHCPMessage)) + return -EINVAL; + + len -= sizeof(DHCPMessage); + + r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + + if (overload & DHCP_OVERLOAD_FILE) { + r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + } + + if (overload & DHCP_OVERLOAD_SNAME) { + r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + } + + if (message_type == 0) + return -ENOMSG; + + if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) + *_error_message = TAKE_PTR(error_message); + + return message_type; +} + +static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { + if (!i) + return NULL; + + free(i->data); + return mfree(i); +} + +int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) { + assert_return(ret, -EINVAL); + assert_return(length == 0 || data, -EINVAL); + + _cleanup_free_ void *q = memdup(data, length); + if (!q) + return -ENOMEM; + + sd_dhcp_option *p = new(sd_dhcp_option, 1); + if (!p) + return -ENOMEM; + + *p = (sd_dhcp_option) { + .n_ref = 1, + .option = option, + .length = length, + .data = TAKE_PTR(q), + }; + + *ret = TAKE_PTR(p); + return 0; +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + dhcp_option_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + sd_dhcp_option, + sd_dhcp_option_unref); diff --git a/src/core/systemd/src/libsystemd-network/dhcp-packet.c b/src/core/systemd/src/libsystemd-network/dhcp-packet.c new file mode 100644 index 0000000..eeadd56 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-packet.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include + +#include "dhcp-internal.h" +#include "dhcp-protocol.h" + +#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + size_t *optoffset) { + size_t offset = 0; + int r; + + assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); + assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); + + message->op = op; + message->htype = arp_type; + message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0; + message->xid = htobe32(xid); + message->magic = htobe32(DHCP_MAGIC_COOKIE); + + r = dhcp_option_append(message, optlen, &offset, 0, + SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); + if (r < 0) + return r; + + *optoffset = offset; + + return 0; +} + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { + uint64_t *buf_64 = (uint64_t*)buf; + uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); + uint64_t sum = 0; + + /* See RFC1071 */ + + while (buf_64 < end_64) { + sum += *buf_64; + if (sum < *buf_64) + /* wrap around in one's complement */ + sum++; + + buf_64++; + } + + if (len % sizeof(uint64_t)) { + /* If the buffer is not aligned to 64-bit, we need + to zero-pad the last few bytes and add them in */ + uint64_t buf_tail = 0; + + memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); + + sum += buf_tail; + if (sum < buf_tail) + /* wrap around */ + sum++; + } + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source_port, be32_t destination_addr, + uint16_t destination_port, uint16_t len, int ip_service_type) { + packet->ip.version = IPVERSION; + packet->ip.ihl = DHCP_IP_SIZE / 4; + packet->ip.tot_len = htobe16(len); + + if (ip_service_type >= 0) + packet->ip.tos = ip_service_type; + else + packet->ip.tos = IPTOS_CLASS_CS6; + + packet->ip.protocol = IPPROTO_UDP; + packet->ip.saddr = source_addr; + packet->ip.daddr = destination_addr; + + packet->udp.source = htobe16(source_port); + packet->udp.dest = htobe16(destination_port); + + packet->udp.len = htobe16(len - DHCP_IP_SIZE); + + packet->ip.check = packet->udp.len; + packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8); + + packet->ip.ttl = IPDEFTTL; + packet->ip.check = 0; + packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); +} + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { + size_t hdrlen; + + assert(packet); + + /* IP */ + + if (packet->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: not IPv4"); + + if (packet->ip.ihl < 5) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: IPv4 IHL (%u words) invalid", + packet->ip.ihl); + + hdrlen = packet->ip.ihl * 4; + if (hdrlen < 20) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: IPv4 IHL (%zu bytes) " + "smaller than minimum (20 bytes)", + hdrlen); + + if (len < hdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by IP header", + len, hdrlen); + + /* UDP */ + + if (packet->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: not UDP"); + + if (len < hdrlen + be16toh(packet->udp.len)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by UDP header", + len, hdrlen + be16toh(packet->udp.len)); + + if (be16toh(packet->udp.dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: to port %u, which " + "is not the DHCP client port (%u)", + be16toh(packet->udp.dest), port); + + /* checksums - computing these is relatively expensive, so only do it + if all the other checks have passed + */ + + if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: invalid IP checksum"); + + if (checksum && packet->udp.check) { + packet->ip.check = packet->udp.len; + packet->ip.ttl = 0; + + if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, + be16toh(packet->udp.len) + 12)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: invalid UDP checksum"); + } + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/dhcp-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h new file mode 100644 index 0000000..11f4201 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include +#include +#include + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCPMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + be32_t xid; + be16_t secs; + be16_t flags; + be32_t ciaddr; + be32_t yiaddr; + be32_t siaddr; + be32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + be32_t magic; + uint8_t options[0]; +} _packed_; + +typedef struct DHCPMessage DHCPMessage; + +struct DHCPPacket { + struct iphdr ip; + struct udphdr udp; + DHCPMessage dhcp; +} _packed_; + +typedef struct DHCPPacket DHCPPacket; + +#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr)) +#define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE) +#define DHCP_MESSAGE_SIZE (int32_t)(sizeof(DHCPMessage)) +#define DHCP_DEFAULT_MIN_SIZE 576 /* the minimum internet hosts must be able to receive */ +#define DHCP_MIN_OPTIONS_SIZE (DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE) +#define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363) + +enum { + DHCP_PORT_SERVER = 67, + DHCP_PORT_CLIENT = 68, +}; + +enum DHCPState { + DHCP_STATE_INIT = 0, + DHCP_STATE_SELECTING = 1, + DHCP_STATE_INIT_REBOOT = 2, + DHCP_STATE_REBOOTING = 3, + DHCP_STATE_REQUESTING = 4, + DHCP_STATE_BOUND = 5, + DHCP_STATE_RENEWING = 6, + DHCP_STATE_REBINDING = 7, + DHCP_STATE_STOPPED = 8, +}; + +typedef enum DHCPState DHCPState; + +enum { + BOOTREQUEST = 1, + BOOTREPLY = 2, +}; + +enum { + DHCP_DISCOVER = 1, + DHCP_OFFER = 2, + DHCP_REQUEST = 3, + DHCP_DECLINE = 4, + DHCP_ACK = 5, + DHCP_NAK = 6, + DHCP_RELEASE = 7, + DHCP_INFORM = 8, + DHCP_FORCERENEW = 9, +}; + +enum { + DHCP_OVERLOAD_FILE = 1, + DHCP_OVERLOAD_SNAME = 2, +}; + +#define DHCP_MAX_FQDN_LENGTH 255 + +enum { + DHCP_FQDN_FLAG_S = (1 << 0), + DHCP_FQDN_FLAG_O = (1 << 1), + DHCP_FQDN_FLAG_E = (1 << 2), + DHCP_FQDN_FLAG_N = (1 << 3), +}; diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h new file mode 100644 index 0000000..24d8a31 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include +#include + +#include "sd-event.h" + +#include "list.h" +#include "hashmap.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct sd_dhcp6_option { + unsigned n_ref; + + uint32_t enterprise_identifier; + uint16_t option; + void *data; + size_t length; +} sd_dhcp6_option; + +extern const struct hash_ops dhcp6_option_hash_ops; + +/* Common option header */ +typedef struct DHCP6Option { + be16_t code; + be16_t len; + uint8_t data[]; +} _packed_ DHCP6Option; + +/* Address option */ +struct iaaddr { + struct in6_addr address; + be32_t lifetime_preferred; + be32_t lifetime_valid; +} _packed_; + +/* Prefix Delegation Prefix option */ +struct iapdprefix { + be32_t lifetime_preferred; + be32_t lifetime_valid; + uint8_t prefixlen; + struct in6_addr address; +} _packed_; + +typedef struct DHCP6Address DHCP6Address; + +struct DHCP6Address { + LIST_FIELDS(DHCP6Address, addresses); + + union { + struct iaaddr iaaddr; + struct iapdprefix iapdprefix; + }; +}; + +/* Non-temporary Address option */ +struct ia_na { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +/* Prefix Delegation option */ +struct ia_pd { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +/* Temporary Address option */ +struct ia_ta { + be32_t id; +} _packed_; + +struct DHCP6IA { + uint16_t type; + union { + struct ia_na ia_na; + struct ia_pd ia_pd; + struct ia_ta ia_ta; + }; + + LIST_HEAD(DHCP6Address, addresses); +}; + +typedef struct DHCP6IA DHCP6IA; + +#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) +#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__) + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval); +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia); +int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix); +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class); +int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **user_class); +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options); +int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, + size_t *optlen, uint8_t **optvalue); +int dhcp6_option_parse_status(DHCP6Option *option, size_t len); +int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code); +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count, + size_t *allocated); +int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, + char ***str_arr); +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str); + +int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address); +int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, + const void *packet, size_t len); + +const char *dhcp6_message_type_to_string(int s) _const_; +int dhcp6_message_type_from_string(const char *s) _pure_; +const char *dhcp6_message_status_to_string(int s) _const_; +int dhcp6_message_status_from_string(const char *s) _pure_; diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h new file mode 100644 index 0000000..e9e2362 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include + +#include "sd-dhcp6-lease.h" + +#include "dhcp6-internal.h" + +struct sd_dhcp6_lease { + unsigned n_ref; + + uint8_t *serverid; + size_t serverid_len; + uint8_t preference; + bool rapid_commit; + + DHCP6IA ia; + DHCP6IA pd; + + DHCP6Address *addr_iter; + DHCP6Address *prefix_iter; + + struct in6_addr *dns; + size_t dns_count; + size_t dns_allocated; + char **domains; + size_t domains_count; + struct in6_addr *ntp; + size_t ntp_count; + size_t ntp_allocated; + char **ntp_fqdn; + size_t ntp_fqdn_count; + char *fqdn; +}; + +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire); +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia); + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len); +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len); +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference); +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference); +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease); +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit); + +int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid); +int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid); + +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen); +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) ; +int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); + +int dhcp6_lease_new(sd_dhcp6_lease **ret); diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-network.c b/src/core/systemd/src/libsystemd-network/dhcp6-network.c new file mode 100644 index 0000000..b6816de --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp6-network.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcp6-internal.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "socket-util.h" + +int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { + union sockaddr_union src = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), + .in6.sin6_scope_id = ifindex, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + assert(local_address); + + src.in6.sin6_addr = *local_address; + + s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP); + if (s < 0) + return -errno; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true); + if (r < 0) + return r; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = bind(s, &src.sa, sizeof(src.in6)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_SERVER), + }; + int r; + + assert(server_address); + + memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr)); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-option.c b/src/core/systemd/src/libsystemd-network/dhcp6-option.c new file mode 100644 index 0000000..21e2d3f --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp6-option.c @@ -0,0 +1,795 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include + +#include "sd-dhcp6-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp6-internal.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "dns-domain.h" +#include "memory-util.h" +#include "sparse-endian.h" +#include "strv.h" +#include "unaligned.h" + +typedef struct DHCP6StatusOption { + struct DHCP6Option option; + be16_t status; + char msg[]; +} _packed_ DHCP6StatusOption; + +typedef struct DHCP6AddressOption { + struct DHCP6Option option; + struct iaaddr iaaddr; + uint8_t options[]; +} _packed_ DHCP6AddressOption; + +typedef struct DHCP6PDPrefixOption { + struct DHCP6Option option; + struct iapdprefix iapdprefix; + uint8_t options[]; +} _packed_ DHCP6PDPrefixOption; + +#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na)) +#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd)) +#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta)) + +static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, + size_t optlen) { + DHCP6Option *option = (DHCP6Option*) *buf; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + + if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data)) + return -ENOBUFS; + + option->code = htobe16(optcode); + option->len = htobe16(optlen); + + *buf += offsetof(DHCP6Option, data); + *buflen -= offsetof(DHCP6Option, data); + + return 0; +} + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval) { + int r; + + assert_return(optval || optlen == 0, -EINVAL); + + r = option_append_hdr(buf, buflen, code, optlen); + if (r < 0) + return r; + + memcpy_safe(*buf, optval, optlen); + + *buf += optlen; + *buflen -= optlen; + + return 0; +} + +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) { + sd_dhcp6_option *options; + int r; + + assert(buf); + assert(*buf); + assert(buflen); + assert(vendor_options); + + ORDERED_HASHMAP_FOREACH(options, vendor_options) { + _cleanup_free_ uint8_t *p = NULL; + size_t total; + + total = 4 + 2 + 2 + options->length; + + p = malloc(total); + if (!p) + return -ENOMEM; + + unaligned_write_be32(p, options->enterprise_identifier); + unaligned_write_be16(p + 4, options->option); + unaligned_write_be16(p + 6, options->length); + memcpy(p + 8, options->data, options->length); + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p); + if (r < 0) + return r; + } + + return 0; +} + +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { + uint16_t len; + uint8_t *ia_hdr; + size_t iaid_offset, ia_buflen, ia_addrlen = 0; + DHCP6Address *addr; + int r; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + assert_return(ia, -EINVAL); + + switch (ia->type) { + case SD_DHCP6_OPTION_IA_NA: + len = DHCP6_OPTION_IA_NA_LEN; + iaid_offset = offsetof(DHCP6IA, ia_na); + break; + + case SD_DHCP6_OPTION_IA_TA: + len = DHCP6_OPTION_IA_TA_LEN; + iaid_offset = offsetof(DHCP6IA, ia_ta); + break; + + default: + return -EINVAL; + } + + if (*buflen < offsetof(DHCP6Option, data) + len) + return -ENOBUFS; + + ia_hdr = *buf; + ia_buflen = *buflen; + + *buf += offsetof(DHCP6Option, data); + *buflen -= offsetof(DHCP6Option, data); + + memcpy(*buf, (char*) ia + iaid_offset, len); + + *buf += len; + *buflen -= len; + + LIST_FOREACH(addresses, addr, ia->addresses) { + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, + sizeof(addr->iaaddr)); + if (r < 0) + return r; + + memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr)); + + *buf += sizeof(addr->iaaddr); + *buflen -= sizeof(addr->iaaddr); + + ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr); + } + + r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen); + if (r < 0) + return r; + + return 0; +} + +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { + uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX]; + int r; + + assert_return(buf && *buf && buflen && fqdn, -EINVAL); + + buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */ + + /* Store domain name after flags field */ + r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false); + if (r <= 0) + return r; + + /* + * According to RFC 4704, chapter 4.2 only add terminating zero-length + * label in case a FQDN is provided. Since dns_name_to_wire_format + * always adds terminating zero-length label remove if only a hostname + * is provided. + */ + if (dns_name_is_single_label(fqdn)) + r--; + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer); + + return r; +} + +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class) { + _cleanup_free_ uint8_t *p = NULL; + size_t total = 0, offset = 0; + char **s; + + assert_return(buf && *buf && buflen && user_class, -EINVAL); + + STRV_FOREACH(s, user_class) { + size_t len = strlen(*s); + uint8_t *q; + + if (len > 0xffff) + return -ENAMETOOLONG; + q = realloc(p, total + len + 2); + if (!q) + return -ENOMEM; + + p = q; + + unaligned_write_be16(&p[offset], len); + memcpy(&p[offset + 2], *s, len); + + offset += 2 + len; + total += 2 + len; + } + + return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p); +} + +int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **vendor_class) { + _cleanup_free_ uint8_t *p = NULL; + uint32_t enterprise_identifier; + size_t total, offset; + char **s; + + assert(buf); + assert(*buf); + assert(buflen); + assert(vendor_class); + + enterprise_identifier = htobe32(SYSTEMD_PEN); + + p = memdup(&enterprise_identifier, sizeof(enterprise_identifier)); + if (!p) + return -ENOMEM; + + total = sizeof(enterprise_identifier); + offset = total; + + STRV_FOREACH(s, vendor_class) { + size_t len = strlen(*s); + uint8_t *q; + + q = realloc(p, total + len + 2); + if (!q) + return -ENOMEM; + + p = q; + + unaligned_write_be16(&p[offset], len); + memcpy(&p[offset + 2], *s, len); + + offset += 2 + len; + total += 2 + len; + } + + return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p); +} + +int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) { + DHCP6Option *option = (DHCP6Option *)buf; + size_t i = sizeof(*option) + sizeof(pd->ia_pd); + DHCP6PDPrefixOption *prefix_opt; + DHCP6Address *prefix; + + assert_return(buf, -EINVAL); + assert_return(pd, -EINVAL); + assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL); + + if (len < i) + return -ENOBUFS; + + option->code = htobe16(SD_DHCP6_OPTION_IA_PD); + + memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd)); + LIST_FOREACH(addresses, prefix, pd->addresses) { + if (len < i + sizeof(*prefix_opt)) + return -ENOBUFS; + + prefix_opt = (DHCP6PDPrefixOption *)&buf[i]; + prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX); + prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix)); + + memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, sizeof(struct iapdprefix)); + i += sizeof(*prefix_opt); + } + + if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) { + if (len < i + sizeof(*prefix_opt)) + return -ENOBUFS; + + prefix_opt = (DHCP6PDPrefixOption *)&buf[i]; + prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX); + prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix)); + + memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix)); + i += sizeof(*prefix_opt); + } + + option->len = htobe16(i - sizeof(*option)); + + return i; +} + +static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { + DHCP6Option *option = (DHCP6Option*) *buf; + uint16_t len; + + assert_return(buf, -EINVAL); + assert_return(optcode, -EINVAL); + assert_return(optlen, -EINVAL); + + if (*buflen < offsetof(DHCP6Option, data)) + return -ENOMSG; + + len = be16toh(option->len); + + if (len > *buflen) + return -ENOMSG; + + *optcode = be16toh(option->code); + *optlen = len; + + *buf += 4; + *buflen -= 4; + + return 0; +} + +int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, + size_t *optlen, uint8_t **optvalue) { + int r; + + assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); + + r = option_parse_hdr(buf, buflen, optcode, optlen); + if (r < 0) + return r; + + if (*optlen > *buflen) + return -ENOBUFS; + + *optvalue = *buf; + *buflen -= *optlen; + *buf += *optlen; + + return 0; +} + +int dhcp6_option_parse_status(DHCP6Option *option, size_t len) { + DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; + + if (len < sizeof(DHCP6StatusOption) || + be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption)) + return -ENOBUFS; + + return be16toh(statusopt->status); +} + +static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia, + uint32_t *lifetime_valid) { + DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option; + DHCP6Address *addr; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option)) + return -ENOBUFS; + + lt_valid = be32toh(addr_option->iaaddr.lifetime_valid); + lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) { + log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d", + lt_pref, lt_valid); + + return 0; + } + + if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option)); + if (r != 0) + return r < 0 ? r: 0; + } + + addr = new0(DHCP6Address, 1); + if (!addr) + return -ENOMEM; + + LIST_INIT(addresses, addr); + memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); + + LIST_PREPEND(addresses, ia->addresses, addr); + + *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid); + + return 0; +} + +static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia, + uint32_t *lifetime_valid) { + DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option; + DHCP6Address *prefix; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option)) + return -ENOBUFS; + + lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid); + lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) { + log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d", + lt_pref, lt_valid); + + return 0; + } + + if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option)); + if (r != 0) + return r < 0 ? r: 0; + } + + prefix = new0(DHCP6Address, 1); + if (!prefix) + return -ENOMEM; + + LIST_INIT(addresses, prefix); + memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix)); + + LIST_PREPEND(addresses, ia->addresses, prefix); + + *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid); + + return 0; +} + +int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) { + uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX; + uint16_t iatype, optlen; + size_t iaaddr_offset; + int r = 0, status; + size_t i, len; + uint16_t opt; + + assert_return(ia, -EINVAL); + assert_return(!ia->addresses, -EINVAL); + + iatype = be16toh(iaoption->code); + len = be16toh(iaoption->len); + + switch (iatype) { + case SD_DHCP6_OPTION_IA_NA: + + if (len < DHCP6_OPTION_IA_NA_LEN) + return -ENOBUFS; + + iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; + memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na)); + + lt_t1 = be32toh(ia->ia_na.lifetime_t1); + lt_t2 = be32toh(ia->ia_na.lifetime_t2); + + if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { + log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds", + lt_t1, lt_t2); + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + + if (len < sizeof(ia->ia_pd)) + return -ENOBUFS; + + iaaddr_offset = sizeof(ia->ia_pd); + memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd)); + + lt_t1 = be32toh(ia->ia_pd.lifetime_t1); + lt_t2 = be32toh(ia->ia_pd.lifetime_t2); + + if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { + log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds", + lt_t1, lt_t2); + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_TA: + if (len < DHCP6_OPTION_IA_TA_LEN) + return -ENOBUFS; + + iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; + memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta)); + + break; + + default: + return -ENOMSG; + } + + ia->type = iatype; + i = iaaddr_offset; + + while (i < len) { + DHCP6Option *option = (DHCP6Option *)&iaoption->data[i]; + + if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) + return -ENOBUFS; + + opt = be16toh(option->code); + optlen = be16toh(option->len); + + switch (opt) { + case SD_DHCP6_OPTION_IAADDR: + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) { + log_dhcp6_client(client, "IA Address option not in IA NA or TA option"); + return -EINVAL; + } + + r = dhcp6_option_parse_address(option, ia, <_valid); + if (r < 0) + return r; + + if (lt_valid < lt_min) + lt_min = lt_valid; + + break; + + case SD_DHCP6_OPTION_IA_PD_PREFIX: + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) { + log_dhcp6_client(client, "IA PD Prefix option not in IA PD option"); + return -EINVAL; + } + + r = dhcp6_option_parse_pdprefix(option, ia, <_valid); + if (r < 0) + return r; + + if (lt_valid < lt_min) + lt_min = lt_valid; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + + status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data)); + if (status < 0) + return status; + + if (status > 0) { + if (ret_status_code) + *ret_status_code = status; + + log_dhcp6_client(client, "IA status %s", + dhcp6_message_status_to_string(status)); + + return 0; + } + + break; + + default: + log_dhcp6_client(client, "Unknown IA option %d", opt); + break; + } + + i += sizeof(*option) + optlen; + } + + switch(iatype) { + case SD_DHCP6_OPTION_IA_NA: + if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_na.lifetime_t1 = htobe32(lt_t1); + ia->ia_na.lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_pd.lifetime_t1 = htobe32(lt_t1); + ia->ia_pd.lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + break; + + default: + break; + } + + if (ret_status_code) + *ret_status_code = 0; + + return 1; +} + +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count, + size_t *allocated) { + + if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*addrs, *allocated, + count * sizeof(struct in6_addr) + optlen)) + return -ENOMEM; + + memcpy(*addrs + count, optval, optlen); + + count += optlen / sizeof(struct in6_addr); + + return count; +} + +static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const uint8_t *optval = *data; + uint16_t optlen = *len; + bool first = true; + int r; + + if (optlen <= 1) + return -ENODATA; + + for (;;) { + const char *label; + uint8_t c; + + if (optlen == 0) + break; + + c = *optval; + optval++; + optlen--; + + if (c == 0) + /* End label */ + break; + if (c > 63) + return -EBADMSG; + if (c > optlen) + return -EMSGSIZE; + + /* Literal label */ + label = (const char *)optval; + optval += c; + optlen -= c; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (n) { + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + ret[n] = 0; + } + + *out_domain = TAKE_PTR(ret); + *data = optval; + *len = optlen; + + return n; +} + +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) { + _cleanup_free_ char *domain = NULL; + int r; + + r = parse_domain(&optval, &optlen, &domain); + if (r < 0) + return r; + if (r == 0) + return -ENODATA; + if (optlen != 0) + return -EINVAL; + + *str = TAKE_PTR(domain); + return 0; +} + +int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) { + size_t idx = 0; + _cleanup_strv_free_ char **names = NULL; + int r; + + if (optlen <= 1) + return -ENODATA; + if (optval[optlen - 1] != '\0') + return -EINVAL; + + while (optlen > 0) { + _cleanup_free_ char *ret = NULL; + + r = parse_domain(&optval, &optlen, &ret); + if (r < 0) + return r; + if (r == 0) + continue; + + r = strv_extend(&names, ret); + if (r < 0) + return r; + + idx++; + } + + *str_arr = TAKE_PTR(names); + + return idx; +} + +static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) { + if (!i) + return NULL; + + free(i->data); + return mfree(i); +} + +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) { + assert_return(ret, -EINVAL); + assert_return(length == 0 || data, -EINVAL); + + _cleanup_free_ void *q = memdup(data, length); + if (!q) + return -ENOMEM; + + sd_dhcp6_option *p = new(sd_dhcp6_option, 1); + if (!p) + return -ENOMEM; + + *p = (sd_dhcp6_option) { + .n_ref = 1, + .option = option, + .enterprise_identifier = enterprise_identifier, + .length = length, + .data = TAKE_PTR(q), + }; + + *ret = p; + return 0; +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + dhcp6_option_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + sd_dhcp6_option, + sd_dhcp6_option_unref); diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h new file mode 100644 index 0000000..c700363 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include +#include + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCP6Message { + union { + struct { + uint8_t type; + uint8_t _pad[3]; + } _packed_; + be32_t transaction_id; + }; + uint8_t options[]; +} _packed_; + +typedef struct DHCP6Message DHCP6Message; + +#define DHCP6_MIN_OPTIONS_SIZE \ + 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) + +#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } + +enum { + DHCP6_PORT_SERVER = 547, + DHCP6_PORT_CLIENT = 546, +}; + +#define DHCP6_INF_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_INF_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC +#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RC 10 +#define DHCP6_REN_TIMEOUT 10 * USEC_PER_SEC +#define DHCP6_REN_MAX_RT 600 * USEC_PER_SEC +#define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC +#define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC + +enum DHCP6State { + DHCP6_STATE_STOPPED = 0, + DHCP6_STATE_INFORMATION_REQUEST = 1, + DHCP6_STATE_SOLICITATION = 2, + DHCP6_STATE_REQUEST = 3, + DHCP6_STATE_BOUND = 4, + DHCP6_STATE_RENEW = 5, + DHCP6_STATE_REBIND = 6, +}; + +enum { + DHCP6_SOLICIT = 1, + DHCP6_ADVERTISE = 2, + DHCP6_REQUEST = 3, + DHCP6_CONFIRM = 4, + DHCP6_RENEW = 5, + DHCP6_REBIND = 6, + DHCP6_REPLY = 7, + DHCP6_RELEASE = 8, + DHCP6_DECLINE = 9, + DHCP6_RECONFIGURE = 10, + DHCP6_INFORMATION_REQUEST = 11, + DHCP6_RELAY_FORW = 12, + DHCP6_RELAY_REPL = 13, + _DHCP6_MESSAGE_MAX = 14, +}; + +enum { + DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, + DHCP6_NTP_SUBOPTION_MC_ADDR = 2, + DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, +}; + +/* + * RFC 8415, RFC 5007 and RFC 7653 status codes: + * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5 + */ +enum { + DHCP6_STATUS_SUCCESS = 0, + DHCP6_STATUS_UNSPEC_FAIL = 1, + DHCP6_STATUS_NO_ADDRS_AVAIL = 2, + DHCP6_STATUS_NO_BINDING = 3, + DHCP6_STATUS_NOT_ON_LINK = 4, + DHCP6_STATUS_USE_MULTICAST = 5, + DHCP6_STATUS_NO_PREFIX_AVAIL = 6, + DHCP6_STATUS_UNKNOWN_QUERY_TYPE = 7, + DHCP6_STATUS_MALFORMED_QUERY = 8, + DHCP6_STATUS_NOT_CONFIGURED = 9, + DHCP6_STATUS_NOT_ALLOWED = 10, + DHCP6_STATUS_QUERY_TERMINATED = 11, + DHCP6_STATUS_DATA_MISSING = 12, + DHCP6_STATUS_CATCHUP_COMPLETE = 13, + DHCP6_STATUS_NOT_SUPPORTED = 14, + DHCP6_STATUS_TLS_CONNECTION_REFUSED = 15, + DHCP6_STATUS_ADDRESS_IN_USE = 16, + DHCP6_STATUS_CONFIGURATION_CONFLICT = 17, + DHCP6_STATUS_MISSING_BINDING_INFORMATION = 18, + DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19, + DHCP6_STATUS_SERVER_SHUTTING_DOWN = 20, + DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21, + DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22, + _DHCP6_STATUS_MAX = 23, +}; + +enum { + DHCP6_FQDN_FLAG_S = (1 << 0), + DHCP6_FQDN_FLAG_O = (1 << 1), + DHCP6_FQDN_FLAG_N = (1 << 2), +}; diff --git a/src/core/systemd/src/libsystemd-network/lldp-internal.h b/src/core/systemd/src/libsystemd-network/lldp-internal.h new file mode 100644 index 0000000..f23695f --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/lldp-internal.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-event.h" +#include "sd-lldp.h" + +#include "hashmap.h" +#include "log.h" +#include "prioq.h" + +struct sd_lldp { + unsigned n_ref; + + int ifindex; + int fd; + + sd_event *event; + int64_t event_priority; + sd_event_source *io_event_source; + sd_event_source *timer_event_source; + + Prioq *neighbor_by_expiry; + Hashmap *neighbor_by_id; + + uint64_t neighbors_max; + + sd_lldp_callback_t callback; + void *userdata; + + uint16_t capability_mask; + + struct ether_addr filter_address; +}; + +#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) +#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__) + +const char* lldp_event_to_string(sd_lldp_event e) _const_; +sd_lldp_event lldp_event_from_string(const char *s) _pure_; diff --git a/src/core/systemd/src/libsystemd-network/lldp-neighbor.c b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c new file mode 100644 index 0000000..58ff0e0 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c @@ -0,0 +1,794 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include "alloc-util.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "hexdecoct.h" +#include "in-addr-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "memory-util.h" +#include "missing_network.h" +#include "unaligned.h" + +static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) { + siphash24_compress(id->chassis_id, id->chassis_id_size, state); + siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); + siphash24_compress(id->port_id, id->port_id_size, state); + siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); +} + +int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) { + return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size) + ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size); +} + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func, + sd_lldp_neighbor, lldp_neighbor_unlink); + +int lldp_neighbor_prioq_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor *x = a, *y = b; + + return CMP(x->until, y->until); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) { + if (!n) + return NULL; + + assert(n->n_ref > 0 || n->lldp); + n->n_ref++; + + return n; +} + +static void lldp_neighbor_free(sd_lldp_neighbor *n) { + assert(n); + + free(n->id.port_id); + free(n->id.chassis_id); + free(n->port_description); + free(n->system_name); + free(n->system_description); + free(n->mud_url); + free(n->chassis_id_as_string); + free(n->port_id_as_string); + free(n); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) { + + /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from + * the sd_lldp object. */ + + if (!n) + return NULL; + + assert(n->n_ref > 0); + n->n_ref--; + + if (n->n_ref <= 0 && !n->lldp) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) { + + /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */ + + if (!n) + return NULL; + + if (!n->lldp) + return NULL; + + /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is + * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register + * ourselves from the hashtable and sometimes are called after we already are de-registered. */ + + (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n); + + assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0); + + n->lldp = NULL; + + if (n->n_ref <= 0) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) { + sd_lldp_neighbor *n; + + n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size); + if (!n) + return NULL; + + n->raw_size = raw_size; + n->n_ref = 1; + + return n; +} + +static int parse_string(char **s, const void *q, size_t n) { + const char *p = q; + char *k; + + assert(s); + assert(p || n == 0); + + if (*s) { + log_lldp("Found duplicate string, ignoring field."); + return 0; + } + + /* Strip trailing NULs, just to be nice */ + while (n > 0 && p[n-1] == 0) + n--; + + if (n <= 0) /* Ignore empty strings */ + return 0; + + /* Look for inner NULs */ + if (memchr(p, 0, n)) { + log_lldp("Found inner NUL in string, ignoring field."); + return 0; + } + + /* Let's escape weird chars, for security reasons */ + k = cescape_length(p, n); + if (!k) + return -ENOMEM; + + free(*s); + *s = k; + + return 1; +} + +int lldp_neighbor_parse(sd_lldp_neighbor *n) { + struct ether_header h; + const uint8_t *p; + size_t left; + int r; + + assert(n); + + if (n->raw_size < sizeof(struct ether_header)) { + log_lldp("Received truncated packet, ignoring."); + return -EBADMSG; + } + + memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h)); + + if (h.ether_type != htobe16(ETHERTYPE_LLDP)) { + log_lldp("Received packet with wrong type, ignoring."); + return -EBADMSG; + } + + if (h.ether_dhost[0] != 0x01 || + h.ether_dhost[1] != 0x80 || + h.ether_dhost[2] != 0xc2 || + h.ether_dhost[3] != 0x00 || + h.ether_dhost[4] != 0x00 || + !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) { + log_lldp("Received packet with wrong destination address, ignoring."); + return -EBADMSG; + } + + memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr)); + memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr)); + + p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header); + left = n->raw_size - sizeof(struct ether_header); + + for (;;) { + uint8_t type; + uint16_t length; + + if (left < 2) { + log_lldp("TLV lacks header, ignoring."); + return -EBADMSG; + } + + type = p[0] >> 1; + length = p[1] + (((uint16_t) (p[0] & 1)) << 8); + p += 2, left -= 2; + + if (left < length) { + log_lldp("TLV truncated, ignoring datagram."); + return -EBADMSG; + } + + switch (type) { + + case SD_LLDP_TYPE_END: + if (length != 0) { + log_lldp("End marker TLV not zero-sized, ignoring datagram."); + return -EBADMSG; + } + + /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0 + * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */ + + goto end_marker; + + case SD_LLDP_TYPE_CHASSIS_ID: + if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */ + log_lldp("Chassis ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.chassis_id) { + log_lldp("Duplicate chassis ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.chassis_id = memdup(p, length); + if (!n->id.chassis_id) + return -ENOMEM; + + n->id.chassis_id_size = length; + break; + + case SD_LLDP_TYPE_PORT_ID: + if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */ + log_lldp("Port ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.port_id) { + log_lldp("Duplicate port ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.port_id = memdup(p, length); + if (!n->id.port_id) + return -ENOMEM; + + n->id.port_id_size = length; + break; + + case SD_LLDP_TYPE_TTL: + if (length != 2) { + log_lldp("TTL field has wrong size, ignoring datagram."); + return -EBADMSG; + } + + if (n->has_ttl) { + log_lldp("Duplicate TTL field, ignoring datagram."); + return -EBADMSG; + } + + n->ttl = unaligned_read_be16(p); + n->has_ttl = true; + break; + + case SD_LLDP_TYPE_PORT_DESCRIPTION: + r = parse_string(&n->port_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_NAME: + r = parse_string(&n->system_name, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_DESCRIPTION: + r = parse_string(&n->system_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_CAPABILITIES: + if (length != 4) + log_lldp("System capabilities field has wrong size, ignoring."); + else { + n->system_capabilities = unaligned_read_be16(p); + n->enabled_capabilities = unaligned_read_be16(p + 2); + n->has_capabilities = true; + } + + break; + + case SD_LLDP_TYPE_PRIVATE: { + if (length < 4) + log_lldp("Found private TLV that is too short, ignoring."); + else { + /* RFC 8520: MUD URL */ + if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 && + p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) { + r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1, + length - 1 - sizeof(SD_LLDP_OUI_MUD)); + if (r < 0) + return r; + } + } + } + + break; + } + + p += length, left -= length; + } + +end_marker: + if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) { + log_lldp("One or more mandatory TLV missing in datagram. Ignoring."); + return -EBADMSG; + + } + + n->rindex = sizeof(struct ether_header); + + return 0; +} + +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { + assert(n); + + if (n->ttl > 0) { + usec_t base; + + /* Use the packet's timestamp if there is one known */ + base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); + if (base <= 0 || base == USEC_INFINITY) + base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ + + n->until = usec_add(base, n->ttl * USEC_PER_SEC); + } else + n->until = 0; + + if (n->lldp) + prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx); +} + +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + if (a->raw_size != b->raw_size) + return false; + + return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0; +} + +_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->source_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->destination_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = LLDP_NEIGHBOR_RAW(n); + *size = n->raw_size; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.chassis_id_size > 0); + + *type = *(uint8_t*) n->id.chassis_id; + *ret = (uint8_t*) n->id.chassis_id + 1; + *size = n->id.chassis_id_size - 1; + + return 0; +} + +static int format_mac_address(const void *data, size_t sz, char **ret) { + struct ether_addr a; + char *k; + + assert(data || sz <= 0); + + if (sz != 7) + return 0; + + memcpy(&a, (uint8_t*) data + 1, sizeof(a)); + + k = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!k) + return -ENOMEM; + + *ret = ether_addr_to_string(&a, k); + return 1; +} + +static int format_network_address(const void *data, size_t sz, char **ret) { + union in_addr_union a; + int family, r; + + if (sz == 6 && ((uint8_t*) data)[1] == 1) { + memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in)); + family = AF_INET; + } else if (sz == 18 && ((uint8_t*) data)[1] == 2) { + memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6)); + family = AF_INET6; + } else + return 0; + + r = in_addr_to_string(family, &a, ret); + if (r < 0) + return r; + return 1; +} + +_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->chassis_id_as_string) { + *ret = n->chassis_id_as_string; + return 0; + } + + assert(n->id.chassis_id_size > 0); + + switch (*(uint8_t*) n->id.chassis_id) { + + case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.chassis_id, n->id.chassis_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->chassis_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.port_id_size > 0); + + *type = *(uint8_t*) n->id.port_id; + *ret = (uint8_t*) n->id.port_id + 1; + *size = n->id.port_id_size - 1; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->port_id_as_string) { + *ret = n->port_id_as_string; + return 0; + } + + assert(n->id.port_id_size > 0); + + switch (*(uint8_t*) n->id.port_id) { + + case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.port_id, n->id.port_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->port_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { + assert_return(n, -EINVAL); + assert_return(ret_sec, -EINVAL); + + *ret_sec = n->ttl; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_name) + return -ENODATA; + + *ret = n->system_name; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_description) + return -ENODATA; + + *ret = n->system_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->port_description) + return -ENODATA; + + *ret = n->port_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->mud_url) + return -ENODATA; + + *ret = n->mud_url; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->system_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->enabled_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + n = lldp_neighbor_new(raw_size); + if (!n) + return -ENOMEM; + + memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size); + r = lldp_neighbor_parse(n); + if (r < 0) + return r; + + *ret = TAKE_PTR(n); + + return r; +} + +_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { + assert_return(n, -EINVAL); + + assert(n->raw_size >= sizeof(struct ether_header)); + n->rindex = sizeof(struct ether_header); + + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { + size_t length; + + assert_return(n, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) /* Truncated message */ + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + n->rindex += 2 + length; + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + *type = LLDP_NEIGHBOR_TLV_TYPE(n); + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) { + uint8_t k; + int r; + + assert_return(n, -EINVAL); + + r = sd_lldp_neighbor_tlv_get_type(n, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) { + const uint8_t *d; + size_t length; + int r; + + assert_return(n, -EINVAL); + assert_return(oui, -EINVAL); + assert_return(subtype, -EINVAL); + + r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (length < 4) + return -EBADMSG; + + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + d = LLDP_NEIGHBOR_TLV_DATA(n); + memcpy(oui, d, 3); + *subtype = d[3]; + + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) { + uint8_t k[3], st; + int r; + + r = sd_lldp_neighbor_tlv_get_oui(n, k, &st); + if (r == -ENXIO) + return 0; + if (r < 0) + return r; + + return memcmp(k, oui, 3) == 0 && st == subtype; +} + +_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + size_t length; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full TLV, including the TLV header */ + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + *size = length + 2; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { + assert_return(n, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&n->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&n->timestamp, clock); + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/lldp-neighbor.h b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h new file mode 100644 index 0000000..a5718c8 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include + +#include "sd-lldp.h" + +#include "hash-funcs.h" +#include "lldp-internal.h" +#include "time-util.h" + +typedef struct LLDPNeighborID { + /* The spec calls this an "MSAP identifier" */ + void *chassis_id; + size_t chassis_id_size; + + void *port_id; + size_t port_id_size; +} LLDPNeighborID; + +struct sd_lldp_neighbor { + /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */ + sd_lldp *lldp; + unsigned n_ref; + + triple_timestamp timestamp; + + usec_t until; + unsigned prioq_idx; + + struct ether_addr source_address; + struct ether_addr destination_address; + + LLDPNeighborID id; + + /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */ + size_t raw_size; + + /* The current read index for the iterative TLV interface */ + size_t rindex; + + /* And a couple of fields parsed out. */ + bool has_ttl:1; + bool has_capabilities:1; + bool has_port_vlan_id:1; + + uint16_t ttl; + + uint16_t system_capabilities; + uint16_t enabled_capabilities; + + char *port_description; + char *system_name; + char *system_description; + char *mud_url; + + uint16_t port_vlan_id; + + char *chassis_id_as_string; + char *port_id_as_string; +}; + +static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { + return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); +} + +static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; +} + +static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { + uint8_t *p; + + p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + return p[1] + (((size_t) (p[0] & 1)) << 8); +} + +static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; +} + +extern const struct hash_ops lldp_neighbor_hash_ops; +int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y); +int lldp_neighbor_prioq_compare_func(const void *a, const void *b); + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n); +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); +int lldp_neighbor_parse(sd_lldp_neighbor *n); +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); diff --git a/src/core/systemd/src/libsystemd-network/lldp-network.c b/src/core/systemd/src/libsystemd-network/lldp-network.c new file mode 100644 index 0000000..75dce58 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/lldp-network.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include + +#include "fd-util.h" +#include "lldp-network.h" +#include "missing_network.h" +#include "socket-util.h" + +int lldp_network_bind_raw_socket(int ifindex) { + + static const struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ + }; + + static const struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter, + }; + + struct packet_mreq mreq = { + .mr_ifindex = ifindex, + .mr_type = PACKET_MR_MULTICAST, + .mr_alen = ETH_ALEN, + .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 } + }; + + union sockaddr_union saddrll = { + .ll.sll_family = AF_PACKET, + .ll.sll_ifindex = ifindex, + }; + + _cleanup_close_ int fd = -1; + int r; + + assert(ifindex > 0); + + fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, + htobe16(ETHERTYPE_LLDP)); + if (fd < 0) + return -errno; + + r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + mreq.mr_address[ETH_ALEN - 1] = 0x03; + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + mreq.mr_address[ETH_ALEN - 1] = 0x0E; + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + r = bind(fd, &saddrll.sa, sizeof(saddrll.ll)); + if (r < 0) + return -errno; + + return TAKE_FD(fd); +} diff --git a/src/core/systemd/src/libsystemd-network/lldp-network.h b/src/core/systemd/src/libsystemd-network/lldp-network.h new file mode 100644 index 0000000..bc69b32 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/lldp-network.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-event.h" + +int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/core/systemd/src/libsystemd-network/network-internal.c b/src/core/systemd/src/libsystemd-network/network-internal.c new file mode 100644 index 0000000..efa3dcf --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/network-internal.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "extract-word.h" +#include "hexdecoct.h" +#include "log.h" +#include "network-internal.h" +#include "parse-util.h" + +size_t serialize_in_addrs(FILE *f, + const struct in_addr *addresses, + size_t size, + bool *with_leading_space, + bool (*predicate)(const struct in_addr *addr)) { + assert(f); + assert(addresses); + + size_t count = 0; + bool _space = false; + if (!with_leading_space) + with_leading_space = &_space; + + for (size_t i = 0; i < size; i++) { + char sbuf[INET_ADDRSTRLEN]; + + if (predicate && !predicate(&addresses[i])) + continue; + + if (*with_leading_space) + fputc(' ', f); + fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f); + count++; + *with_leading_space = true; + } + + return count; +} + +int deserialize_in_addrs(struct in_addr **ret, const char *string) { + _cleanup_free_ struct in_addr *addresses = NULL; + int size = 0; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET, word, &(addresses[size])); + if (r <= 0) + continue; + + size++; + } + + *ret = size > 0 ? TAKE_PTR(addresses) : NULL; + + return size; +} + +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) { + assert(f); + assert(addresses); + assert(size); + + bool _space = false; + if (!with_leading_space) + with_leading_space = &_space; + + for (size_t i = 0; i < size; i++) { + char buffer[INET6_ADDRSTRLEN]; + + if (*with_leading_space) + fputc(' ', f); + fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); + *with_leading_space = true; + } +} + +int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { + _cleanup_free_ struct in6_addr *addresses = NULL; + int size = 0; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in6_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET6, word, &(addresses[size])); + if (r <= 0) + continue; + + size++; + } + + *ret = TAKE_PTR(addresses); + + return size; +} + +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { + assert(f); + assert(key); + assert(routes); + assert(size); + + fprintf(f, "%s=", key); + + for (size_t i = 0; i < size; i++) { + char sbuf[INET_ADDRSTRLEN]; + struct in_addr dest, gw; + uint8_t length; + + assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); + assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); + + fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof sbuf), length); + fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof sbuf), i < size - 1 ? " ": ""); + } + + fputs("\n", f); +} + +int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) { + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + size_t size = 0, allocated = 0; + + assert(ret); + assert(ret_size); + assert(ret_allocated); + assert(string); + + /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ + for (;;) { + _cleanup_free_ char *word = NULL; + char *tok, *tok_end; + unsigned n; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(routes, allocated, size + 1)) + return -ENOMEM; + + tok = word; + + /* get the subnet */ + tok_end = strchr(tok, '/'); + if (!tok_end) + continue; + *tok_end = '\0'; + + r = inet_aton(tok, &routes[size].dst_addr); + if (r == 0) + continue; + + tok = tok_end + 1; + + /* get the prefixlen */ + tok_end = strchr(tok, ','); + if (!tok_end) + continue; + + *tok_end = '\0'; + + r = safe_atou(tok, &n); + if (r < 0 || n > 32) + continue; + + routes[size].dst_prefixlen = (uint8_t) n; + tok = tok_end + 1; + + /* get the gateway */ + r = inet_aton(tok, &routes[size].gw_addr); + if (r == 0) + continue; + + size++; + } + + *ret_size = size; + *ret_allocated = allocated; + *ret = TAKE_PTR(routes); + + return 0; +} + +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) { + _cleanup_free_ char *hex_buf = NULL; + + assert(f); + assert(key); + assert(data); + + hex_buf = hexmem(data, size); + if (!hex_buf) + return -ENOMEM; + + fprintf(f, "%s=%s\n", key, hex_buf); + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/network-internal.h b/src/core/systemd/src/libsystemd-network/network-internal.h new file mode 100644 index 0000000..e5b853c --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/network-internal.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-dhcp-lease.h" + +size_t serialize_in_addrs(FILE *f, + const struct in_addr *addresses, + size_t size, + bool *with_leading_space, + bool (*predicate)(const struct in_addr *addr)); +int deserialize_in_addrs(struct in_addr **addresses, const char *string); +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, + size_t size, + bool *with_leading_space); +int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); + +/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ +struct sd_dhcp_route; +struct sd_dhcp_lease; + +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size); +int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string); + +/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */ +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); + +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c new file mode 100644 index 0000000..27a3476 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c @@ -0,0 +1,2258 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "sd-dhcp-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp-internal.h" +#include "dhcp-lease-internal.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "event-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "io-util.h" +#include "memory-util.h" +#include "random-util.h" +#include "set.h" +#include "sort-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "web-util.h" + +#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ +#define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) + +#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) +#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) + +#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report + * transient failure. */ + +typedef struct sd_dhcp_client_id { + uint8_t type; + union { + struct { + /* 0: Generic (non-LL) (RFC 2132) */ + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ gen; + struct { + /* 1: Ethernet Link-Layer (RFC 2132) */ + uint8_t haddr[ETH_ALEN]; + } _packed_ eth; + struct { + /* 2 - 254: ARP/Link-Layer (RFC 2132) */ + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* 255: Node-specific (RFC 4361) */ + be32_t iaid; + struct duid duid; + } _packed_ ns; + struct { + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ raw; + }; +} _packed_ sd_dhcp_client_id; + +struct sd_dhcp_client { + unsigned n_ref; + + DHCPState state; + sd_event *event; + int event_priority; + sd_event_source *timeout_resend; + int ifindex; + int fd; + uint16_t port; + union sockaddr_union link; + sd_event_source *receive_message; + bool request_broadcast; + Set *req_opts; + bool anonymize; + be32_t last_addr; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint8_t bcast_addr[MAX_MAC_ADDR_LEN]; + size_t bcast_addr_len; + uint16_t arp_type; + sd_dhcp_client_id client_id; + size_t client_id_len; + char *hostname; + char *vendor_class_identifier; + char *mudurl; + char **user_class; + uint32_t mtu; + uint32_t fallback_lease_lifetime; + uint32_t xid; + usec_t start_time; + usec_t t1_time; + usec_t t2_time; + usec_t expire_time; + uint64_t attempt; + uint64_t max_attempts; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; + usec_t request_sent; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + sd_event_source *timeout_expire; + sd_dhcp_client_callback_t callback; + void *userdata; + sd_dhcp_lease *lease; + usec_t start_delay; + int ip_service_type; +}; + +static const uint8_t default_req_opts[] = { + SD_DHCP_OPTION_SUBNET_MASK, + SD_DHCP_OPTION_ROUTER, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_DOMAIN_NAME, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, +}; + +/* RFC7844 section 3: + MAY contain the Parameter Request List option. + RFC7844 section 3.6: + The client intending to protect its privacy SHOULD only request a + minimal number of options in the PRL and SHOULD also randomly shuffle + the ordering of option codes in the PRL. If this random ordering + cannot be implemented, the client MAY order the option codes in the + PRL by option code number (lowest to highest). +*/ +/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */ +static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVER, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODETYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ +}; + +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +static void client_stop(sd_dhcp_client *client, int error); + +int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) { + const sd_dhcp_client_id *client_id = data; + _cleanup_free_ char *t = NULL; + int r = 0; + + assert_return(data, -EINVAL); + assert_return(len >= 1, -EINVAL); + assert_return(ret, -EINVAL); + + len -= 1; + if (len > MAX_CLIENT_ID_LEN) + return -EINVAL; + + switch (client_id->type) { + case 0: + if (utf8_is_printable((char *) client_id->gen.data, len)) + r = asprintf(&t, "%.*s", (int) len, client_id->gen.data); + else + r = asprintf(&t, "DATA"); + break; + case 1: + if (len != sizeof_field(sd_dhcp_client_id, eth)) + return -EINVAL; + + r = asprintf(&t, "%x:%x:%x:%x:%x:%x", + client_id->eth.haddr[0], + client_id->eth.haddr[1], + client_id->eth.haddr[2], + client_id->eth.haddr[3], + client_id->eth.haddr[4], + client_id->eth.haddr[5]); + break; + case 2 ... 254: + r = asprintf(&t, "ARP/LL"); + break; + case 255: + if (len < 6) + return -EINVAL; + + uint32_t iaid = be32toh(client_id->ns.iaid); + uint16_t duid_type = be16toh(client_id->ns.duid.type); + if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0) + return -EINVAL; + + r = asprintf(&t, "IAID:0x%x/DUID", iaid); + break; + } + + if (r < 0) + return -ENOMEM; + *ret = TAKE_PTR(t); + return 0; +} + +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { + assert_return(client, -EINVAL); + + client->request_broadcast = !!broadcast; + + return 0; +} + +int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + + switch(option) { + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + case SD_DHCP_OPTION_END: + return -EINVAL; + + default: + break; + } + + return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option)); +} + +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_addr) { + + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + + if (last_addr) + client->last_addr = last_addr->s_addr; + else + client->last_addr = INADDR_ANY; + + return 0; +} + +int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) { + + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + assert_return(ifindex > 0, -EINVAL); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + const uint8_t *bcast_addr, + size_t addr_len, + uint16_t arp_type) { + + DHCP_CLIENT_DONT_DESTROY(client); + bool need_restart = false; + int r; + + assert_return(client, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); + assert_return(arp_type > 0, -EINVAL); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0 && + (client->bcast_addr_len > 0) == !!bcast_addr && + (!bcast_addr || memcmp(&client->bcast_addr, bcast_addr, addr_len) == 0)) + return 0; + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Changing MAC address on running DHCP client, restarting"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + client->bcast_addr_len = 0; + + if (bcast_addr) { + memcpy(&client->bcast_addr, bcast_addr, addr_len); + client->bcast_addr_len = addr_len; + } + + if (need_restart && client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len) { + + assert_return(client, -EINVAL); + assert_return(type, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + *type = 0; + *data = NULL; + *data_len = 0; + if (client->client_id_len) { + *type = client->client_id.type; + *data = client->client_id.raw.data; + *data_len = client->client_id_len - sizeof(client->client_id.type); + } + + return 0; +} + +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len) { + + DHCP_CLIENT_DONT_DESTROY(client); + bool need_restart = false; + int r; + + assert_return(client, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL); + G_STATIC_ASSERT_EXPR (_NM_SD_MAX_CLIENT_ID_LEN == MAX_CLIENT_ID_LEN); + + if (client->client_id_len == data_len + sizeof(client->client_id.type) && + client->client_id.type == type && + memcmp(&client->client_id.raw.data, data, data_len) == 0) + return 0; + + /* For hardware types, log debug message about unexpected data length. + * + * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only + * the last 8 bytes of the address are stable and suitable to put into + * the client-id. The caller is advised to account for that. */ + if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) || + (type == ARPHRD_INFINIBAND && data_len != 8)) + log_dhcp_client(client, "Changing client ID to hardware type %u with " + "unexpected address length %zu", + type, data_len); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Changing client ID on running DHCP " + "client, restarting"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + client->client_id.type = type; + memcpy(&client->client_id.raw.data, data, data_len); + client->client_id_len = data_len + sizeof (client->client_id.type); + + if (need_restart && client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +/** + * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +static int dhcp_client_set_iaid_duid_internal( + sd_dhcp_client *client, + bool iaid_append, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len, + usec_t llt_time) { + + DHCP_CLIENT_DONT_DESTROY(client); + int r; + size_t len; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid, -EINVAL); + + if (duid) { + r = dhcp_validate_duid_len(duid_type, duid_len, true); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to validate length of DUID: %m"); + } + + zero(client->client_id); + client->client_id.type = 255; + + if (iaid_append) { + if (iaid_set) + client->client_id.ns.iaid = htobe32(iaid); + else { + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, + client->mac_addr_len, + true, + &client->client_id.ns.iaid); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set IAID: %m"); + } + } + + if (duid) { + client->client_id.ns.duid.type = htobe16(duid_type); + memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); + len = sizeof(client->client_id.ns.duid.type) + duid_len; + } else + switch (duid_type) { + case DUID_TYPE_LLT: + if (client->mac_addr_len == 0) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); + + r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m"); + break; + case DUID_TYPE_EN: + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m"); + break; + case DUID_TYPE_LL: + if (client->mac_addr_len == 0) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); + + r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m"); + break; + case DUID_TYPE_UUID: + r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m"); + break; + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); + } + + client->client_id_len = sizeof(client->client_id.type) + len + + (iaid_append ? sizeof(client->client_id.ns.iaid) : 0); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured %sDUID, restarting.", iaid_append ? "IAID+" : ""); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, duid_type, duid, duid_len, 0); +} + +int sd_dhcp_client_set_iaid_duid_llt( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + usec_t llt_time) { + return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, DUID_TYPE_LLT, NULL, 0, llt_time); +} + +int sd_dhcp_client_set_duid( + sd_dhcp_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp_client_set_iaid_duid_internal(client, false, false, 0, duid_type, duid, duid_len, 0); +} + +int sd_dhcp_client_set_duid_llt( + sd_dhcp_client *client, + usec_t llt_time) { + return dhcp_client_set_iaid_duid_internal(client, false, false, 0, DUID_TYPE_LLT, NULL, 0, llt_time); +} +#endif /* NM_IGNORED */ + +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname) { + + assert_return(client, -EINVAL); + + /* Make sure hostnames qualify as DNS and as Linux hostnames */ + if (hostname && + !(hostname_is_valid(hostname, 0) && dns_name_is_valid(hostname) > 0)) + return -EINVAL; + + return free_and_strdup(&client->hostname, hostname); +} + +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci) { + + assert_return(client, -EINVAL); + + return free_and_strdup(&client->vendor_class_identifier, vci); +} + +int sd_dhcp_client_set_mud_url( + sd_dhcp_client *client, + const char *mudurl) { + + assert_return(client, -EINVAL); + assert_return(mudurl, -EINVAL); + assert_return(strlen(mudurl) <= 255, -EINVAL); + assert_return(http_url_is_valid(mudurl), -EINVAL); + + return free_and_strdup(&client->mudurl, mudurl); +} + +int sd_dhcp_client_set_user_class( + sd_dhcp_client *client, + const char* const* user_class) { + + _cleanup_strv_free_ char **s = NULL; + char **p; + + STRV_FOREACH(p, (char **) user_class) + if (strlen(*p) > 255) + return -ENAMETOOLONG; + + s = strv_copy((char **) user_class); + if (!s) + return -ENOMEM; + + client->user_class = TAKE_PTR(s); + + return 0; +} + +int sd_dhcp_client_set_client_port( + sd_dhcp_client *client, + uint16_t port) { + + assert_return(client, -EINVAL); + + client->port = port; + + return 0; +} + +int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { + assert_return(client, -EINVAL); + assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE); + + client->mtu = mtu; + + return 0; +} + +int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) { + assert_return(client, -EINVAL); + + client->max_attempts = max_attempts; + + return 0; +} + +int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp_option_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v); + if (r < 0) + return r; + + sd_dhcp_option_ref(v); + return 0; +} + +int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops); + if (r < 0) + return -ENOMEM; + + r = ordered_hashmap_put(client->vendor_options, v, v); + if (r < 0) + return r; + + sd_dhcp_option_ref(v); + + return 1; +} + +int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { + assert_return(client, -EINVAL); + + if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) + return -EADDRNOTAVAIL; + + if (ret) + *ret = client->lease; + + return 0; +} + +int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) { + assert_return(client, -EINVAL); + + client->ip_service_type = type; + + return 0; +} + +int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) { + assert_return(client, -EINVAL); + assert_return(fallback_lease_lifetime > 0, -EINVAL); + + client->fallback_lease_lifetime = fallback_lease_lifetime; + + return 0; +} + +static int client_notify(sd_dhcp_client *client, int event) { + assert(client); + + if (client->callback) + return client->callback(client, event, client->userdata); + + return 0; +} + +static int client_initialize(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->receive_message = sd_event_source_unref(client->receive_message); + + client->fd = safe_close(client->fd); + + (void) event_source_disable(client->timeout_resend); + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_expire); + + client->attempt = 0; + + client->state = DHCP_STATE_INIT; + client->xid = 0; + + client->lease = sd_dhcp_lease_unref(client->lease); + + return 0; +} + +static void client_stop(sd_dhcp_client *client, int error) { + assert(client); + + if (error < 0) + log_dhcp_client_errno(client, error, "STOPPED: %m"); + else if (error == SD_DHCP_CLIENT_EVENT_STOP) + log_dhcp_client(client, "STOPPED"); + else + log_dhcp_client(client, "STOPPED: Unknown event"); + + client_notify(client, error); + + client_initialize(client); +} + +/* RFC2131 section 4.1: + * retransmission delays should include -1 to +1 sec of random 'fuzz'. */ +#define RFC2131_RANDOM_FUZZ \ + ((int64_t)(random_u64() % (2 * USEC_PER_SEC)) - (int64_t)USEC_PER_SEC) + +/* RFC2131 section 4.1: + * for retransmission delays, timeout should start at 4s then double + * each attempt with max of 64s, with -1 to +1 sec of random 'fuzz' added. + * This assumes the first call will be using attempt 1. */ +static usec_t client_compute_request_timeout(usec_t now, uint64_t attempt) { + usec_t timeout = (UINT64_C(1) << MIN(attempt + 1, UINT64_C(6))) * USEC_PER_SEC; + + return usec_sub_signed(usec_add(now, timeout), RFC2131_RANDOM_FUZZ); +} + +/* RFC2131 section 4.4.5: + * T1 defaults to (0.5 * duration_of_lease). + * T2 defaults to (0.875 * duration_of_lease). */ +#define T1_DEFAULT(lifetime) ((lifetime) / 2) +#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8) + +/* RFC2131 section 4.4.5: + * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state) + * and one-half of the remaining lease time (in REBINDING state), down to a minimum + * of 60 seconds. + * Note that while the default T1/T2 initial times do have random 'fuzz' applied, + * the RFC sec 4.4.5 does not mention adding any fuzz to retries. */ +static usec_t client_compute_reacquisition_timeout(usec_t now, usec_t expire) { + return now + MAX(usec_sub_unsigned(expire, now) / 2, 60 * USEC_PER_SEC); +} + +static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + return CMP(*a, *b); +} + +static int client_message_init( + sd_dhcp_client *client, + DHCPPacket **ret, + uint8_t type, + size_t *_optlen, + size_t *_optoffset) { + + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optlen, optoffset, size; + be16_t max_size; + usec_t time_now; + uint16_t secs; + int r; + + assert(client); + assert(client->start_time); + assert(ret); + assert(_optlen); + assert(_optoffset); + assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); + + optlen = DHCP_MIN_OPTIONS_SIZE; + size = sizeof(DHCPPacket) + optlen; + + packet = malloc0(size); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, + client->arp_type, optlen, &optoffset); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers + refuse to issue an DHCP lease if 'secs' is set to zero */ + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* seconds between sending first and last DISCOVER + * must always be strictly positive to deal with broken servers */ + secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1; + packet->dhcp.secs = htobe16(secs); + + /* RFC2132 section 4.1 + A client that cannot receive unicast IP datagrams until its protocol + software has been configured with an IP address SHOULD set the + BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + DHCPREQUEST messages that client sends. The BROADCAST bit will + provide a hint to the DHCP server and BOOTP relay agent to broadcast + any messages to the client on the client's subnet. + + Note: some interfaces needs this to be enabled, but some networks + needs this to be disabled as broadcasts are filteretd, so this + needs to be configurable */ + if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) + packet->dhcp.flags = htobe16(0x8000); + + /* RFC2132 section 4.1.1: + The client MUST include its hardware address in the ’chaddr’ field, if + necessary for delivery of DHCP reply messages. Non-Ethernet + interfaces will leave 'chaddr' empty and use the client identifier + instead (eg, RFC 4390 section 2.1). + */ + if (client->arp_type == ARPHRD_ETHER) + memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); + + /* If no client identifier exists, construct an RFC 4361-compliant one */ + if (client->client_id_len == 0) { + size_t duid_len; + + client->client_id.type = 255; + + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, + true, &client->client_id.ns.iaid); + if (r < 0) + return r; + + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len); + if (r < 0) + return r; + + client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len; + } + + /* Some DHCP servers will refuse to issue an DHCP lease if the Client + Identifier option is not set */ + if (client->client_id_len) { + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_CLIENT_IDENTIFIER, + client->client_id_len, + &client->client_id); + if (r < 0) + return r; + } + + /* RFC2131 section 3.5: + in its initial DHCPDISCOVER or DHCPREQUEST message, a + client may provide the server with a list of specific + parameters the client is interested in. If the client + includes a list of parameters in a DHCPDISCOVER message, + it MUST include that list in any subsequent DHCPREQUEST + messages. + */ + + /* RFC7844 section 3: + MAY contain the Parameter Request List option. */ + /* NOTE: in case that there would be an option to do not send + * any PRL at all, the size should be checked before sending */ + if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) { + _cleanup_free_ uint8_t *opts = NULL; + size_t n_opts, i = 0; + void *val; + + n_opts = set_size(client->req_opts); + opts = new(uint8_t, n_opts); + if (!opts) + return -ENOMEM; + + SET_FOREACH(val, client->req_opts) + opts[i++] = PTR_TO_UINT8(val); + assert(i == n_opts); + + /* For anonymizing the request, let's sort the options. */ + typesafe_qsort(opts, n_opts, cmp_uint8); + + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + n_opts, opts); + if (r < 0) + return r; + } + + /* RFC2131 section 3.5: + The client SHOULD include the ’maximum DHCP message size’ option to + let the server know how large the server may make its DHCP messages. + + Note (from ConnMan): Some DHCP servers will send bigger DHCP packets + than the defined default size unless the Maximum Message Size option + is explicitly set + + RFC3442 "Requirements to Avoid Sizing Constraints": + Because a full routing table can be quite large, the standard 576 + octet maximum size for a DHCP message may be too short to contain + some legitimate Classless Static Route options. Because of this, + clients implementing the Classless Static Route option SHOULD send a + Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP + stack is capable of receiving larger IP datagrams. In this case, the + client SHOULD set the value of this option to at least the MTU of the + interface that the client is configuring. The client MAY set the + value of this option higher, up to the size of the largest UDP packet + it is prepared to accept. (Note that the value specified in the + Maximum DHCP Message Size option is the total maximum packet size, + including IP and UDP headers.) + */ + /* RFC7844 section 3: + SHOULD NOT contain any other option. */ + if (!client->anonymize && type != DHCP_RELEASE) { + max_size = htobe16(size); + r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + 2, &max_size); + if (r < 0) + return r; + } + + *_optlen = optlen; + *_optoffset = optoffset; + *ret = TAKE_PTR(packet); + + return 0; +} + +static int client_append_fqdn_option( + DHCPMessage *message, + size_t optlen, + size_t *optoffset, + const char *fqdn) { + + uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; + int r; + + buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ + DHCP_FQDN_FLAG_E; /* Canonical wire format */ + buffer[1] = 0; /* RCODE1 (deprecated) */ + buffer[2] = 0; /* RCODE2 (deprecated) */ + + r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); + if (r > 0) + r = dhcp_option_append(message, optlen, optoffset, 0, + SD_DHCP_OPTION_FQDN, 3 + r, buffer); + + return r; +} + +static int dhcp_client_send_raw( + sd_dhcp_client *client, + DHCPPacket *packet, + size_t len) { + + dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, + INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type); + + return dhcp_network_send_raw_socket(client->fd, &client->link, + packet, len); +} + +static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { + sd_dhcp_option *j; + int r; + + assert(client); + + if (client->hostname) { + /* According to RFC 4702 "clients that send the Client FQDN option in + their messages MUST NOT also send the Host Name option". Just send + one of the two depending on the hostname type. + */ + if (dns_name_is_single_label(client->hostname)) { + /* it is unclear from RFC 2131 if client should send hostname in + DHCPDISCOVER but dhclient does and so we do as well + */ + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + } else + r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset, + client->hostname); + if (r < 0) + return r; + } + + if (client->vendor_class_identifier) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + strlen(client->vendor_class_identifier), + client->vendor_class_identifier); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_MUD_URL, + strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_USER_CLASS, + strv_length(client->user_class), + client->user_class); + if (r < 0) + return r; + } + + ORDERED_HASHMAP_FOREACH(j, client->extra_options) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + j->option, j->length, j->data); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp_option_append( + &packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_VENDOR_SPECIFIC, + ordered_hashmap_size(client->vendor_options), client->vendor_options); + if (r < 0) + return r; + } + + + return 0; +} + +static int client_send_discover(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *discover = NULL; + size_t optoffset, optlen; + int r; + + assert(client); + assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + + r = client_message_init(client, &discover, DHCP_DISCOVER, + &optlen, &optoffset); + if (r < 0) + return r; + + /* the client may suggest values for the network address + and lease time in the DHCPDISCOVER message. The client may include + the ’requested IP address’ option to suggest that a particular IP + address be assigned, and may include the ’IP address lease time’ + option to suggest the lease time it would like. + */ + /* RFC7844 section 3: + SHOULD NOT contain any other option. */ + if (!client->anonymize && client->last_addr != INADDR_ANY) { + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + } + + r = client_append_common_discover_request_options(client, discover, &optoffset, optlen); + if (r < 0) + return r; + + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* We currently ignore: + The client SHOULD wait a random time between one and ten seconds to + desynchronize the use of DHCP at startup. + */ + r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "DISCOVER"); + + return 0; +} + +static int client_send_request(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *request = NULL; + size_t optoffset, optlen; + int r; + + assert(client); + + r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset); + if (r < 0) + return r; + + switch (client->state) { + /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC, + SELECTING should be REQUESTING) + */ + + case DHCP_STATE_REQUESTING: + /* Client inserts the address of the selected server in ’server + identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be + filled in with the yiaddr value from the chosen DHCPOFFER. + */ + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &client->lease->server_address); + if (r < 0) + return r; + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->lease->address); + if (r < 0) + return r; + + break; + + case DHCP_STATE_INIT_REBOOT: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST be filled in with client’s notion of its previously + assigned address. ’ciaddr’ MUST be zero. + */ + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + break; + + case DHCP_STATE_RENEWING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + */ + + case DHCP_STATE_REBINDING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + + This message MUST be broadcast to the 0xffffffff IP broadcast address. + */ + request->dhcp.ciaddr = client->lease->address; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + case DHCP_STATE_STOPPED: + return -EINVAL; + } + + r = client_append_common_discover_request_options(client, request, &optoffset, optlen); + if (r < 0) + return r; + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + if (client->state == DHCP_STATE_RENEWING) + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &request->dhcp, + sizeof(DHCPMessage) + optoffset); + else + r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + switch (client->state) { + + case DHCP_STATE_REQUESTING: + log_dhcp_client(client, "REQUEST (requesting)"); + break; + + case DHCP_STATE_INIT_REBOOT: + log_dhcp_client(client, "REQUEST (init-reboot)"); + break; + + case DHCP_STATE_RENEWING: + log_dhcp_client(client, "REQUEST (renewing)"); + break; + + case DHCP_STATE_REBINDING: + log_dhcp_client(client, "REQUEST (rebinding)"); + break; + + default: + log_dhcp_client(client, "REQUEST (invalid)"); + break; + } + + return 0; +} + +static int client_start(sd_dhcp_client *client); + +static int client_timeout_resend( + sd_event_source *s, + uint64_t usec, + void *userdata) { + + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + usec_t next_timeout; + uint64_t time_now; + int r; + + assert(s); + assert(client); + assert(client->event); + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + switch (client->state) { + + case DHCP_STATE_RENEWING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time); + break; + + case DHCP_STATE_REBINDING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time); + break; + + case DHCP_STATE_REBOOTING: + /* start over as we did not receive a timely ack or nak */ + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start(client); + if (r < 0) + goto error; + + log_dhcp_client(client, "REBOOTED"); + return 0; + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_BOUND: + if (client->attempt >= client->max_attempts) + goto error; + + client->attempt++; + next_timeout = client_compute_request_timeout(time_now, client->attempt); + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + + default: + assert_not_reached("Unhandled choice"); + } + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + next_timeout, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + goto error; + + switch (client->state) { + case DHCP_STATE_INIT: + r = client_send_discover(client); + if (r >= 0) { + client->state = DHCP_STATE_SELECTING; + client->attempt = 0; + } else if (client->attempt >= client->max_attempts) + goto error; + + break; + + case DHCP_STATE_SELECTING: + r = client_send_discover(client); + if (r < 0 && client->attempt >= client->max_attempts) + goto error; + + break; + + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + r = client_send_request(client); + if (r < 0 && client->attempt >= client->max_attempts) + goto error; + + if (client->state == DHCP_STATE_INIT_REBOOT) + client->state = DHCP_STATE_REBOOTING; + + client->request_sent = time_now; + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + + if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS) + client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + + return 0; + +error: + client_stop(client, r); + + /* Errors were dealt with when stopping the client, don't spill + errors into the event loop handler */ + return 0; +} + +static int client_initialize_io_events( + sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { + + int r; + + assert(client); + assert(client->event); + + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, io_callback, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message"); + if (r < 0) + goto error; + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_initialize_time_events(sd_dhcp_client *client) { + uint64_t usec = 0; + int r; + + assert(client); + assert(client->event); + + if (client->start_delay > 0) { + assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0); + usec += client->start_delay; + } + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + usec, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + client_stop(client, r); + + return 0; + +} + +static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { + client_initialize_io_events(client, io_callback); + client_initialize_time_events(client); + + return 0; +} + +static int client_start_delayed(sd_dhcp_client *client) { + int r; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(client->fd < 0, -EBUSY); + assert_return(client->xid == 0, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY); + + client->xid = random_u32(); + + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, + client->mac_addr, client->mac_addr_len, + client->bcast_addr, client->bcast_addr_len, + client->arp_type, client->port); + if (r < 0) { + client_stop(client, r); + return r; + } + client->fd = r; + + if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT)) + client->start_time = now(clock_boottime_or_monotonic()); + + return client_initialize_events(client, client_receive_message_raw); +} + +static int client_start(sd_dhcp_client *client) { + client->start_delay = 0; + return client_start_delayed(client); +} + +static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + + log_dhcp_client(client, "EXPIRED"); + + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); + + /* lease was lost, start over if not freed or stopped in callback */ + if (client->state != DHCP_STATE_STOPPED) { + client_initialize(client); + client_start(client); + } + + return 0; +} + +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + int r; + + assert(client); + + client->receive_message = sd_event_source_unref(client->receive_message); + client->fd = safe_close(client->fd); + + client->state = DHCP_STATE_REBINDING; + client->attempt = 0; + + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, + client->mac_addr, client->mac_addr_len, + client->bcast_addr, client->bcast_addr_len, + client->arp_type, client->port); + if (r < 0) { + client_stop(client, r); + return 0; + } + client->fd = r; + + return client_initialize_events(client, client_receive_message_raw); +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + + if (client->lease) + client->state = DHCP_STATE_RENEWING; + else if (client->state != DHCP_STATE_INIT) + client->state = DHCP_STATE_INIT_REBOOT; + client->attempt = 0; + + return client_initialize_time_events(client); +} + +static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + int r; + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL); + if (r != DHCP_OFFER) { + log_dhcp_client(client, "received message was not an OFFER, ignoring"); + return -ENOMSG; + } + + lease->next_server = offer->siaddr; + lease->address = offer->yiaddr; + + if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) + lease->lifetime = client->fallback_lease_lifetime; + + if (lease->address == 0 || + lease->server_address == 0 || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (!lease->have_subnet_mask) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, + "received lease lacks subnet mask, " + "and a fallback one cannot be generated, ignoring"); + return -ENOMSG; + } + } + + sd_dhcp_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) + return -ENOMSG; + + log_dhcp_client(client, "OFFER"); + + return 0; +} + +static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { + int r; + + r = dhcp_option_parse(force, len, NULL, NULL, NULL); + if (r != DHCP_FORCERENEW) + return -ENOMSG; + + log_dhcp_client(client, "FORCERENEW"); + + return 0; +} + +static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { + if (a->address != b->address) + return false; + + if (a->subnet_mask != b->subnet_mask) + return false; + + if (a->router_size != b->router_size) + return false; + + for (size_t i = 0; i < a->router_size; i++) + if (a->router[i].s_addr != b->router[i].s_addr) + return false; + + return true; +} + +static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char *error_message = NULL; + int r; + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); + if (r == DHCP_NAK) { + log_dhcp_client(client, "NAK: %s", strna(error_message)); + return -EADDRNOTAVAIL; + } + + if (r != DHCP_ACK) { + log_dhcp_client(client, "received message was not an ACK, ignoring"); + return -ENOMSG; + } + + lease->next_server = ack->siaddr; + + lease->address = ack->yiaddr; + + if (lease->address == INADDR_ANY || + lease->server_address == INADDR_ANY || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server " + "address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (lease->subnet_mask == INADDR_ANY) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, + "received lease lacks subnet mask, " + "and a fallback one cannot be generated, ignoring"); + return -ENOMSG; + } + } + + r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + if (client->lease) { + if (lease_equal(client->lease, lease)) + r = SD_DHCP_CLIENT_EVENT_RENEW; + else + r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; + + client->lease = sd_dhcp_lease_unref(client->lease); + } + + client->lease = TAKE_PTR(lease); + + log_dhcp_client(client, "ACK"); + + return r; +} + +static int client_set_lease_timeouts(sd_dhcp_client *client) { + usec_t time_now; + char time_string[FORMAT_TIMESPAN_MAX]; + int r; + + assert(client); + assert(client->event); + assert(client->lease); + assert(client->lease->lifetime); + + /* don't set timers for infinite leases */ + if (client->lease->lifetime == 0xffffffff) { + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_expire); + + return 0; + } + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(client->request_sent <= time_now); + + /* verify that 0 < t2 < lifetime */ + if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime) + client->lease->t2 = T2_DEFAULT(client->lease->lifetime); + /* verify that 0 < t1 < lifetime */ + if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2) + client->lease->t1 = T1_DEFAULT(client->lease->lifetime); + /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check + * could not evalate to false if t1 >= t2; so setting t2 to T2_DEFAULT + * guarantees t1 < t2. */ + if (client->lease->t1 >= client->lease->t2) + client->lease->t2 = T2_DEFAULT(client->lease->lifetime); + + client->expire_time = client->request_sent + client->lease->lifetime * USEC_PER_SEC; + client->t1_time = client->request_sent + client->lease->t1 * USEC_PER_SEC; + client->t2_time = client->request_sent + client->lease->t2 * USEC_PER_SEC; + + /* RFC2131 section 4.4.5: + * Times T1 and T2 SHOULD be chosen with some random "fuzz". + * Since the RFC doesn't specify here the exact 'fuzz' to use, + * we use the range from section 4.1: -1 to +1 sec. */ + client->t1_time = usec_sub_signed(client->t1_time, RFC2131_RANDOM_FUZZ); + client->t2_time = usec_sub_signed(client->t2_time, RFC2131_RANDOM_FUZZ); + + /* after fuzzing, ensure t2 is still >= t1 */ + client->t2_time = MAX(client->t1_time, client->t2_time); + + /* arm lifetime timeout */ + r = event_reset_time(client->event, &client->timeout_expire, + clock_boottime_or_monotonic(), + client->expire_time, 10 * USEC_PER_MSEC, + client_timeout_expire, client, + client->event_priority, "dhcp4-lifetime", true); + if (r < 0) + return r; + + /* don't arm earlier timeouts if this has already expired */ + if (client->expire_time <= time_now) + return 0; + + log_dhcp_client(client, "lease expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC)); + + /* arm T2 timeout */ + r = event_reset_time(client->event, &client->timeout_t2, + clock_boottime_or_monotonic(), + client->t2_time, 10 * USEC_PER_MSEC, + client_timeout_t2, client, + client->event_priority, "dhcp4-t2-timeout", true); + if (r < 0) + return r; + + /* don't arm earlier timeout if this has already expired */ + if (client->t2_time <= time_now) + return 0; + + log_dhcp_client(client, "T2 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC)); + + /* arm T1 timeout */ + r = event_reset_time(client->event, &client->timeout_t1, + clock_boottime_or_monotonic(), + client->t1_time, 10 * USEC_PER_MSEC, + client_timeout_t1, client, + client->event_priority, "dhcp4-t1-timer", true); + if (r < 0) + return r; + + if (client->t1_time > time_now) + log_dhcp_client(client, "T1 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC)); + + return 0; +} + +static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { + DHCP_CLIENT_DONT_DESTROY(client); + char time_string[FORMAT_TIMESPAN_MAX]; + int r = 0, notify_event = 0; + + assert(client); + assert(client->event); + assert(message); + + switch (client->state) { + case DHCP_STATE_SELECTING: + + r = client_handle_offer(client, message, len); + if (r >= 0) { + + client->state = DHCP_STATE_REQUESTING; + client->attempt = 0; + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + + r = client_handle_ack(client, message, len); + if (r >= 0) { + client->start_delay = 0; + (void) event_source_disable(client->timeout_resend); + client->receive_message = + sd_event_source_unref(client->receive_message); + client->fd = safe_close(client->fd); + + if (IN_SET(client->state, DHCP_STATE_REQUESTING, + DHCP_STATE_REBOOTING)) + notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) + notify_event = r; + + client->state = DHCP_STATE_BOUND; + client->attempt = 0; + + client->last_addr = client->lease->address; + + r = client_set_lease_timeouts(client); + if (r < 0) { + log_dhcp_client(client, "could not set lease timeouts"); + goto error; + } + + r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); + if (r < 0) { + log_dhcp_client(client, "could not bind UDP socket"); + goto error; + } + + client->fd = r; + + client_initialize_io_events(client, client_receive_message_udp); + + if (notify_event) { + client_notify(client, notify_event); + if (client->state == DHCP_STATE_STOPPED) + return 0; + } + + } else if (r == -EADDRNOTAVAIL) { + /* got a NAK, let's restart the client */ + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); + + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start_delayed(client); + if (r < 0) + goto error; + + log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, + client->start_delay, USEC_PER_SEC)); + + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); + + return 0; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_BOUND: + r = client_handle_forcerenew(client, message, len); + if (r >= 0) { + r = client_timeout_t1(NULL, 0, client); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return r; +} + +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_dhcp_client *client = userdata; + _cleanup_free_ DHCPMessage *message = NULL; + const uint8_t *expected_chaddr = NULL; + uint8_t expected_hlen = 0; + ssize_t len, buflen; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + /* the link is down. Don't return an error or the I/O event + source will be disconnected and we won't be able to receive + packets again when the link comes back. */ + return 0; + if (buflen < 0) + return buflen; + + message = malloc0(buflen); + if (!message) + return -ENOMEM; + + len = recv(fd, message, buflen, 0); + if (len < 0) { + /* see comment above for why we shouldn't error out on ENETDOWN. */ + if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN)) + return 0; + + return log_dhcp_client_errno(client, errno, + "Could not receive message from UDP socket: %m"); + } + if ((size_t) len < sizeof(DHCPMessage)) { + log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); + return 0; + } + + if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { + log_dhcp_client(client, "Not a DHCP message: ignoring"); + return 0; + } + + if (message->op != BOOTREPLY) { + log_dhcp_client(client, "Not a BOOTREPLY message: ignoring"); + return 0; + } + + if (message->htype != client->arp_type) { + log_dhcp_client(client, "Packet type does not match client type"); + return 0; + } + + if (client->arp_type == ARPHRD_ETHER) { + expected_hlen = ETH_ALEN; + expected_chaddr = &client->mac_addr[0]; + } + + if (message->hlen != expected_hlen) { + log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen); + return 0; + } + + if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) { + log_dhcp_client(client, "Received chaddr does not match expected: ignoring"); + return 0; + } + + if (client->state != DHCP_STATE_BOUND && + be32toh(message->xid) != client->xid) { + /* in BOUND state, we may receive FORCERENEW with xid set by server, + so ignore the xid in this case */ + log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring", + be32toh(message->xid), client->xid); + return 0; + } + + return client_handle_message(client, message, len); +} + +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_dhcp_client *client = userdata; + _cleanup_free_ DHCPPacket *packet = NULL; + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control; + struct iovec iov = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + bool checksum = true; + ssize_t buflen, len; + int r; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + return 0; + if (buflen < 0) + return buflen; + + packet = malloc0(buflen); + if (!packet) + return -ENOMEM; + + iov = IOVEC_MAKE(packet, buflen); + + len = recvmsg_safe(fd, &msg, 0); + if (IN_SET(len, -EAGAIN, -EINTR, -ENETDOWN)) + return 0; + if (len < 0) + return log_dhcp_client_errno(client, len, + "Could not receive message from raw socket: %m"); + + if ((size_t) len < sizeof(DHCPPacket)) + return 0; + + cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata))); + if (cmsg) { + struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg); + checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); + } + + r = dhcp_packet_verify_headers(packet, len, checksum, client->port); + if (r < 0) + return 0; + + len -= DHCP_IP_UDP_SIZE; + + return client_handle_message(client, &packet->dhcp, len); +} + +int sd_dhcp_client_send_renew(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->fd >= 0, -EINVAL); + + if (!client->lease) + return 0; + + client->start_delay = 0; + client->attempt = 1; + client->state = DHCP_STATE_RENEWING; + + return client_initialize_time_events(client); +} + +int sd_dhcp_client_start(sd_dhcp_client *client) { + int r; + + assert_return(client, -EINVAL); + + r = client_initialize(client); + if (r < 0) + return r; + + /* RFC7844 section 3.3: + SHOULD perform a complete four-way handshake, starting with a + DHCPDISCOVER, to obtain a new address lease. If the client can + ascertain that this is exactly the same network to which it was + previously connected, and if the link-layer address did not change, + the client MAY issue a DHCPREQUEST to try to reclaim the current + address. */ + if (client->last_addr && !client->anonymize) + client->state = DHCP_STATE_INIT_REBOOT; + + r = client_start(client); + if (r >= 0) + log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex); + + return r; +} + +int sd_dhcp_client_send_release(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE); + assert_return(client->lease, -EUNATCH); + + _cleanup_free_ DHCPPacket *release = NULL; + size_t optoffset, optlen; + int r; + + r = client_message_init(client, &release, DHCP_RELEASE, &optlen, &optoffset); + if (r < 0) + return r; + + /* Fill up release IP and MAC */ + release->dhcp.ciaddr = client->lease->address; + memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len); + + r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &release->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "RELEASE"); + + return 0; +} + +int sd_dhcp_client_send_decline(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE); + assert_return(client->lease, -EUNATCH); + + _cleanup_free_ DHCPPacket *release = NULL; + size_t optoffset, optlen; + int r; + + r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset); + if (r < 0) + return r; + + release->dhcp.ciaddr = client->lease->address; + memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len); + + r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &release->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "DECLINE"); + + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + + if (client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return r; + } + + return 0; +} + +int sd_dhcp_client_stop(sd_dhcp_client *client) { + if (!client) + return 0; + + DHCP_CLIENT_DONT_DESTROY(client); + + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + client->state = DHCP_STATE_STOPPED; + + return 0; +} + +int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { + int r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp_client_detach_event(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { + assert_return(client, NULL); + + return client->event; +} + +static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { + if (!client) + return NULL; + + log_dhcp_client(client, "FREE"); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + client->timeout_expire = sd_event_source_unref(client->timeout_expire); + + client_initialize(client); + + sd_dhcp_client_detach_event(client); + + sd_dhcp_lease_unref(client->lease); + + set_free(client->req_opts); + free(client->hostname); + free(client->vendor_class_identifier); + free(client->mudurl); + client->user_class = strv_free(client->user_class); + ordered_hashmap_free(client->extra_options); + ordered_hashmap_free(client->vendor_options); + return mfree(client); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free); + +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { + const uint8_t *opts; + size_t n_opts; + int r; + + assert_return(ret, -EINVAL); + + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1); + if (!client) + return -ENOMEM; + + *client = (sd_dhcp_client) { + .n_ref = 1, + .state = DHCP_STATE_INIT, + .ifindex = -1, + .fd = -1, + .mtu = DHCP_DEFAULT_MIN_SIZE, + .port = DHCP_PORT_CLIENT, + .anonymize = !!anonymize, + .max_attempts = (uint64_t) -1, + .ip_service_type = -1, + }; + /* NOTE: this could be moved to a function. */ + if (anonymize) { + n_opts = ELEMENTSOF(default_req_opts_anonymize); + opts = default_req_opts_anonymize; + } else { + n_opts = ELEMENTSOF(default_req_opts); + opts = default_req_opts; + } + + for (size_t i = 0; i < n_opts; i++) { + r = sd_dhcp_client_set_request_option(client, opts[i]); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(client); + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c new file mode 100644 index 0000000..94b0d35 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c @@ -0,0 +1,1401 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include +#include +#include + +#include "sd-dhcp-lease.h" + +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "env-file.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "network-internal.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "unaligned.h" + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->address == 0) + return -ENODATA; + + addr->s_addr = lease->address; + return 0; +} + +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_broadcast) + return -ENODATA; + + addr->s_addr = lease->broadcast; + return 0; +} + +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime) { + assert_return(lease, -EINVAL); + assert_return(lifetime, -EINVAL); + + if (lease->lifetime <= 0) + return -ENODATA; + + *lifetime = lease->lifetime; + return 0; +} + +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1) { + assert_return(lease, -EINVAL); + assert_return(t1, -EINVAL); + + if (lease->t1 <= 0) + return -ENODATA; + + *t1 = lease->t1; + return 0; +} + +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2) { + assert_return(lease, -EINVAL); + assert_return(t2, -EINVAL); + + if (lease->t2 <= 0) + return -ENODATA; + + *t2 = lease->t2; + return 0; +} + +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { + assert_return(lease, -EINVAL); + assert_return(mtu, -EINVAL); + + if (lease->mtu <= 0) + return -ENODATA; + + *mtu = lease->mtu; + return 0; +} + +int sd_dhcp_lease_get_servers( + sd_dhcp_lease *lease, + sd_dhcp_lease_server_type what, + const struct in_addr **addr) { + + assert_return(lease, -EINVAL); + assert_return(what >= 0, -EINVAL); + assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->servers[what].size <= 0) + return -ENODATA; + + *addr = lease->servers[what].addr; + return (int) lease->servers[what].size; +} + +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr); +} +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr); +} +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr); +} +int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr); +} +int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr); +} +int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr); +} + +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { + assert_return(lease, -EINVAL); + assert_return(domainname, -EINVAL); + + if (!lease->domainname) + return -ENODATA; + + *domainname = lease->domainname; + return 0; +} + +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { + assert_return(lease, -EINVAL); + assert_return(hostname, -EINVAL); + + if (!lease->hostname) + return -ENODATA; + + *hostname = lease->hostname; + return 0; +} + +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) { + assert_return(lease, -EINVAL); + assert_return(root_path, -EINVAL); + + if (!lease->root_path) + return -ENODATA; + + *root_path = lease->root_path; + return 0; +} + +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->router_size <= 0) + return -ENODATA; + + *addr = lease->router; + return (int) lease->router_size; +} + +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_subnet_mask) + return -ENODATA; + + addr->s_addr = lease->subnet_mask; + return 0; +} + +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->server_address == 0) + return -ENODATA; + + addr->s_addr = lease->server_address; + return 0; +} + +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->next_server == 0) + return -ENODATA; + + addr->s_addr = lease->next_server; + return 0; +} + +/* + * The returned routes array must be freed by the caller. + * Route objects have the same lifetime of the lease and must not be freed. + */ +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) { + sd_dhcp_route **ret; + unsigned i; + + assert_return(lease, -EINVAL); + assert_return(routes, -EINVAL); + + if (lease->static_route_size <= 0) + return -ENODATA; + + ret = new(sd_dhcp_route *, lease->static_route_size); + if (!ret) + return -ENOMEM; + + for (i = 0; i < lease->static_route_size; i++) + ret[i] = &lease->static_route[i]; + + *routes = ret; + return (int) lease->static_route_size; +} + +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) { + size_t r; + + assert_return(lease, -EINVAL); + assert_return(domains, -EINVAL); + + r = strv_length(lease->search_domains); + if (r > 0) { + *domains = lease->search_domains; + return (int) r; + } + + return -ENODATA; +} + +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { + assert_return(lease, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + if (lease->vendor_specific_len <= 0) + return -ENODATA; + + *data = lease->vendor_specific; + *data_len = lease->vendor_specific_len; + return 0; +} + +static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { + assert(lease); + + while (lease->private_options) { + struct sd_dhcp_raw_option *option = lease->private_options; + + LIST_REMOVE(options, lease->private_options, option); + + free(option->data); + free(option); + } + + free(lease->root_path); + free(lease->router); + free(lease->timezone); + free(lease->hostname); + free(lease->domainname); + + for (sd_dhcp_lease_server_type i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) + free(lease->servers[i].addr); + + free(lease->static_route); + free(lease->client_id); + free(lease->vendor_specific); + strv_free(lease->search_domains); + return mfree(lease); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free); + +static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + *ret = unaligned_read_be32((be32_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { + assert(option); + assert(ret); + + if (len != 2) + return -EINVAL; + + *ret = unaligned_read_be16((be16_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + memcpy(ret, option, 4); + return 0; +} + +static int lease_parse_string(const uint8_t *option, size_t len, char **ret) { + assert(option); + assert(ret); + + if (len <= 0) + *ret = mfree(*ret); + else { + char *string; + + /* + * One trailing NUL byte is OK, we don't mind. See: + * https://github.com/systemd/systemd/issues/1337 + */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = memdup_suffix0((const char *) option, len); + if (!string) + return -ENOMEM; + + free_and_replace(*ret, string); + } + + return 0; +} + +static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *name = NULL, *normalized = NULL; + int r; + + assert(option); + assert(ret); + + r = lease_parse_string(option, len, &name); + if (r < 0) + return r; + if (!name) { + *ret = mfree(*ret); + return 0; + } + + r = dns_name_normalize(name, 0, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + free_and_replace(*ret, normalized); + + return 0; +} + +static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { + assert(option || len == 0); + assert(ret); + assert(n_ret); + + if (len <= 0) { + *ret = mfree(*ret); + *n_ret = 0; + } else { + size_t n_addresses; + struct in_addr *addresses; + + if (len % 4 != 0) + return -EINVAL; + + n_addresses = len / 4; + + addresses = newdup(struct in_addr, option, n_addresses); + if (!addresses) + return -ENOMEM; + + free(*ret); + *ret = addresses; + *n_ret = n_addresses; + } + + return 0; +} + +static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { + assert(option || len == 0); + assert(ret); + assert(n_ret); + + if (len <= 0) + return -EINVAL; + + /* The SIP record is like the other, regular server records, but prefixed with a single "encoding" + * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like + * the other fields */ + + if (option[0] != 1) { /* We only support IP address encoding for now */ + *ret = mfree(*ret); + *n_ret = 0; + return 0; + } + + return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret); +} + +static int lease_parse_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) { + + struct in_addr addr; + + assert(option || len <= 0); + assert(routes); + assert(routes_size); + assert(routes_allocated); + + if (len <= 0) + return 0; + + if (len % 8 != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + (len / 8))) + return -ENOMEM; + + while (len >= 8) { + struct sd_dhcp_route *route = *routes + *routes_size; + int r; + + route->option = SD_DHCP_OPTION_STATIC_ROUTE; + r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen); + if (r < 0) { + log_debug("Failed to determine destination prefix length from class based IP, ignoring"); + continue; + } + + assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0); + route->dst_addr = inet_makeaddr(inet_netof(addr), 0); + option += 4; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + + len -= 8; + (*routes_size)++; + } + + return 0; +} + +/* parses RFC3442 Classless Static Route Option */ +static int lease_parse_classless_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) { + + assert(option || len <= 0); + assert(routes); + assert(routes_size); + assert(routes_allocated); + + if (len <= 0) + return 0; + + /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)* */ + + while (len > 0) { + uint8_t dst_octets; + struct sd_dhcp_route *route; + + if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + 1)) + return -ENOMEM; + + route = *routes + *routes_size; + route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE; + + dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1); + route->dst_prefixlen = *option; + option++; + len--; + + /* can't have more than 4 octets in IPv4 */ + if (dst_octets > 4 || len < dst_octets) + return -EINVAL; + + route->dst_addr.s_addr = 0; + memcpy(&route->dst_addr.s_addr, option, dst_octets); + option += dst_octets; + len -= dst_octets; + + if (len < 4) + return -EINVAL; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + len -= 4; + + (*routes_size)++; + } + + return 0; +} + +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { + sd_dhcp_lease *lease = userdata; + int r; + + assert(lease); + + switch(code) { + + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + r = lease_parse_u32(option, len, &lease->lifetime, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); + + break; + + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + r = lease_parse_be32(option, len, &lease->server_address); + if (r < 0) + log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); + + break; + + case SD_DHCP_OPTION_SUBNET_MASK: + r = lease_parse_be32(option, len, &lease->subnet_mask); + if (r < 0) + log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); + else + lease->have_subnet_mask = true; + break; + + case SD_DHCP_OPTION_BROADCAST: + r = lease_parse_be32(option, len, &lease->broadcast); + if (r < 0) + log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); + else + lease->have_broadcast = true; + break; + + case SD_DHCP_OPTION_ROUTER: + r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size); + if (r < 0) + log_debug_errno(r, "Failed to parse router addresses, ignoring: %m"); + break; + + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size); + if (r < 0) + log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NTP_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_SIP_SERVER: + r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse SIP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_POP3_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size); + if (r < 0) + log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_SMTP_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_LPR_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size); + if (r < 0) + log_debug_errno(r, "Failed to parse LPR server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_STATIC_ROUTE: + r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated); + if (r < 0) + log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_INTERFACE_MTU: + r = lease_parse_u16(option, len, &lease->mtu, 68); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); + if (lease->mtu < DHCP_DEFAULT_MIN_SIZE) { + log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_DEFAULT_MIN_SIZE); + lease->mtu = DHCP_DEFAULT_MIN_SIZE; + } + + break; + + case SD_DHCP_OPTION_DOMAIN_NAME: + r = lease_parse_domain(option, len, &lease->domainname); + if (r < 0) { + log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); + return 0; + } + + break; + + case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST: + r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains); + if (r < 0) + log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m"); + break; + + case SD_DHCP_OPTION_HOST_NAME: + r = lease_parse_domain(option, len, &lease->hostname); + if (r < 0) { + log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); + return 0; + } + + break; + + case SD_DHCP_OPTION_ROOT_PATH: + r = lease_parse_string(option, len, &lease->root_path); + if (r < 0) + log_debug_errno(r, "Failed to parse root path, ignoring: %m"); + break; + + case SD_DHCP_OPTION_RENEWAL_T1_TIME: + r = lease_parse_u32(option, len, &lease->t1, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); + break; + + case SD_DHCP_OPTION_REBINDING_T2_TIME: + r = lease_parse_u32(option, len, &lease->t2, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); + break; + + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + r = lease_parse_classless_routes( + option, len, + &lease->static_route, + &lease->static_route_size, + &lease->static_route_allocated); + if (r < 0) + log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: { + _cleanup_free_ char *tz = NULL; + + r = lease_parse_string(option, len, &tz); + if (r < 0) { + log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); + return 0; + } + + if (!timezone_is_valid(tz, LOG_DEBUG)) { + log_debug_errno(r, "Timezone is not valid, ignoring: %m"); + return 0; + } + + free_and_replace(lease->timezone, tz); + + break; + } + + case SD_DHCP_OPTION_VENDOR_SPECIFIC: + + if (len <= 0) + lease->vendor_specific = mfree(lease->vendor_specific); + else { + void *p; + + p = memdup(option, len); + if (!p) + return -ENOMEM; + + free(lease->vendor_specific); + lease->vendor_specific = p; + } + + lease->vendor_specific_len = len; + break; + + case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: + r = dhcp_lease_insert_private_option(lease, code, option, len); + if (r < 0) + return r; + + break; + + default: + log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); + break; + } + + return 0; +} + +/* Parses compressed domain names. */ +int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) { + _cleanup_strv_free_ char **names = NULL; + size_t pos = 0, cnt = 0; + int r; + + assert(domains); + assert_return(option && len > 0, -ENODATA); + + while (pos < len) { + _cleanup_free_ char *name = NULL; + size_t n = 0, allocated = 0; + size_t jump_barrier = pos, next_chunk = 0; + bool first = true; + + for (;;) { + uint8_t c; + c = option[pos++]; + + if (c == 0) { + /* End of name */ + break; + } else if (c <= 63) { + const char *label; + + /* Literal label */ + label = (const char*) (option + pos); + pos += c; + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + uint8_t d; + uint16_t ptr; + + if (pos >= len) + return -EBADMSG; + + d = option[pos++]; + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (ptr >= jump_barrier) + return -EBADMSG; + jump_barrier = ptr; + + /* Save current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos; + + pos = ptr; + } else + return -EBADMSG; + } + + if (!GREEDY_REALLOC(name, allocated, n + 1)) + return -ENOMEM; + name[n] = 0; + + r = strv_extend(&names, name); + if (r < 0) + return r; + + cnt++; + + if (next_chunk != 0) + pos = next_chunk; + } + + *domains = TAKE_PTR(names); + + return cnt; +} + +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) { + struct sd_dhcp_raw_option *cur, *option; + + assert(lease); + + LIST_FOREACH(options, cur, lease->private_options) { + if (tag < cur->tag) + break; + if (tag == cur->tag) { + log_debug("Ignoring duplicate option, tagged %i.", tag); + return 0; + } + } + + option = new(struct sd_dhcp_raw_option, 1); + if (!option) + return -ENOMEM; + + option->tag = tag; + option->length = len; + option->data = memdup(data, len); + if (!option->data) { + free(option); + return -ENOMEM; + } + + LIST_INSERT_BEFORE(options, lease->private_options, cur, option); + return 0; +} + +int dhcp_lease_new(sd_dhcp_lease **ret) { + sd_dhcp_lease *lease; + + lease = new0(sd_dhcp_lease, 1); + if (!lease) + return -ENOMEM; + + lease->n_ref = 1; + + *ret = lease; + return 0; +} + +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct sd_dhcp_raw_option *option; + struct in_addr address; + const struct in_addr *addresses; + const void *client_id, *data; + size_t client_id_len, data_len; + char sbuf[INET_ADDRSTRLEN]; + const char *string; + uint16_t mtu; + _cleanup_free_ sd_dhcp_route **routes = NULL; + char **search_domains = NULL; + uint32_t t1, t2, lifetime; + int r; + + assert(lease); + assert(lease_file); + + r = fopen_temporary(lease_file, &f, &temp_path); + if (r < 0) + goto fail; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n"); + + r = sd_dhcp_lease_get_address(lease, &address); + if (r >= 0) + fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_netmask(lease, &address); + if (r >= 0) + fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_router(lease, &addresses); + if (r > 0) { + fputs("ROUTER=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_server_identifier(lease, &address); + if (r >= 0) + fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_next_server(lease, &address); + if (r >= 0) + fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_broadcast(lease, &address); + if (r >= 0) + fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_mtu(lease, &mtu); + if (r >= 0) + fprintf(f, "MTU=%" PRIu16 "\n", mtu); + + r = sd_dhcp_lease_get_t1(lease, &t1); + if (r >= 0) + fprintf(f, "T1=%" PRIu32 "\n", t1); + + r = sd_dhcp_lease_get_t2(lease, &t2); + if (r >= 0) + fprintf(f, "T2=%" PRIu32 "\n", t2); + + r = sd_dhcp_lease_get_lifetime(lease, &lifetime); + if (r >= 0) + fprintf(f, "LIFETIME=%" PRIu32 "\n", lifetime); + + r = sd_dhcp_lease_get_dns(lease, &addresses); + if (r > 0) { + fputs("DNS=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_ntp(lease, &addresses); + if (r > 0) { + fputs("NTP=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_sip(lease, &addresses); + if (r > 0) { + fputs("SIP=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_domainname(lease, &string); + if (r >= 0) + fprintf(f, "DOMAINNAME=%s\n", string); + + r = sd_dhcp_lease_get_search_domains(lease, &search_domains); + if (r > 0) { + fputs("DOMAIN_SEARCH_LIST=", f); + fputstrv(f, search_domains, NULL, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_hostname(lease, &string); + if (r >= 0) + fprintf(f, "HOSTNAME=%s\n", string); + + r = sd_dhcp_lease_get_root_path(lease, &string); + if (r >= 0) + fprintf(f, "ROOT_PATH=%s\n", string); + + r = sd_dhcp_lease_get_routes(lease, &routes); + if (r > 0) + serialize_dhcp_routes(f, "ROUTES", routes, r); + + r = sd_dhcp_lease_get_timezone(lease, &string); + if (r >= 0) + fprintf(f, "TIMEZONE=%s\n", string); + + r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); + if (r >= 0) { + _cleanup_free_ char *client_id_hex = NULL; + + client_id_hex = hexmem(client_id, client_id_len); + if (!client_id_hex) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "CLIENTID=%s\n", client_id_hex); + } + + r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); + if (r >= 0) { + _cleanup_free_ char *option_hex = NULL; + + option_hex = hexmem(data, data_len); + if (!option_hex) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); + } + + LIST_FOREACH(options, option, lease->private_options) { + char key[STRLEN("OPTION_000")+1]; + + xsprintf(key, "OPTION_%" PRIu8, option->tag); + r = serialize_dhcp_option(f, key, option->data, option->length); + if (r < 0) + goto fail; + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, lease_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save lease data %s: %m", lease_file); +} + +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char + *address = NULL, + *router = NULL, + *netmask = NULL, + *server_address = NULL, + *next_server = NULL, + *broadcast = NULL, + *dns = NULL, + *ntp = NULL, + *sip = NULL, + *pop3 = NULL, + *smtp = NULL, + *lpr = NULL, + *mtu = NULL, + *routes = NULL, + *domains = NULL, + *client_id_hex = NULL, + *vendor_specific_hex = NULL, + *lifetime = NULL, + *t1 = NULL, + *t2 = NULL, + *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {}; + + int r, i; + + assert(lease_file); + assert(ret); + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + r = parse_env_file(NULL, lease_file, + "ADDRESS", &address, + "ROUTER", &router, + "NETMASK", &netmask, + "SERVER_ADDRESS", &server_address, + "NEXT_SERVER", &next_server, + "BROADCAST", &broadcast, + "DNS", &dns, + "NTP", &ntp, + "SIP", &sip, + "POP3", &pop3, + "SMTP", &smtp, + "LPR", &lpr, + "MTU", &mtu, + "DOMAINNAME", &lease->domainname, + "HOSTNAME", &lease->hostname, + "DOMAIN_SEARCH_LIST", &domains, + "ROOT_PATH", &lease->root_path, + "ROUTES", &routes, + "CLIENTID", &client_id_hex, + "TIMEZONE", &lease->timezone, + "VENDOR_SPECIFIC", &vendor_specific_hex, + "LIFETIME", &lifetime, + "T1", &t1, + "T2", &t2, + "OPTION_224", &options[0], + "OPTION_225", &options[1], + "OPTION_226", &options[2], + "OPTION_227", &options[3], + "OPTION_228", &options[4], + "OPTION_229", &options[5], + "OPTION_230", &options[6], + "OPTION_231", &options[7], + "OPTION_232", &options[8], + "OPTION_233", &options[9], + "OPTION_234", &options[10], + "OPTION_235", &options[11], + "OPTION_236", &options[12], + "OPTION_237", &options[13], + "OPTION_238", &options[14], + "OPTION_239", &options[15], + "OPTION_240", &options[16], + "OPTION_241", &options[17], + "OPTION_242", &options[18], + "OPTION_243", &options[19], + "OPTION_244", &options[20], + "OPTION_245", &options[21], + "OPTION_246", &options[22], + "OPTION_247", &options[23], + "OPTION_248", &options[24], + "OPTION_249", &options[25], + "OPTION_250", &options[26], + "OPTION_251", &options[27], + "OPTION_252", &options[28], + "OPTION_253", &options[29], + "OPTION_254", &options[30]); + if (r < 0) + return r; + + if (address) { + r = inet_pton(AF_INET, address, &lease->address); + if (r <= 0) + log_debug("Failed to parse address %s, ignoring.", address); + } + + if (router) { + r = deserialize_in_addrs(&lease->router, router); + if (r < 0) + log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router); + else + lease->router_size = r; + } + + if (netmask) { + r = inet_pton(AF_INET, netmask, &lease->subnet_mask); + if (r <= 0) + log_debug("Failed to parse netmask %s, ignoring.", netmask); + else + lease->have_subnet_mask = true; + } + + if (server_address) { + r = inet_pton(AF_INET, server_address, &lease->server_address); + if (r <= 0) + log_debug("Failed to parse server address %s, ignoring.", server_address); + } + + if (next_server) { + r = inet_pton(AF_INET, next_server, &lease->next_server); + if (r <= 0) + log_debug("Failed to parse next server %s, ignoring.", next_server); + } + + if (broadcast) { + r = inet_pton(AF_INET, broadcast, &lease->broadcast); + if (r <= 0) + log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); + else + lease->have_broadcast = true; + } + + if (dns) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns); + if (r < 0) + log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns); + else + lease->servers[SD_DHCP_LEASE_DNS].size = r; + } + + if (ntp) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp); + if (r < 0) + log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); + else + lease->servers[SD_DHCP_LEASE_NTP].size = r; + } + + if (sip) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip); + if (r < 0) + log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip); + else + lease->servers[SD_DHCP_LEASE_SIP].size = r; + } + + if (pop3) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3); + if (r < 0) + log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3); + else + lease->servers[SD_DHCP_LEASE_POP3].size = r; + } + + if (smtp) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp); + if (r < 0) + log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp); + else + lease->servers[SD_DHCP_LEASE_SMTP].size = r; + } + + if (lpr) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr); + if (r < 0) + log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr); + else + lease->servers[SD_DHCP_LEASE_LPR].size = r; + } + + if (mtu) { + r = safe_atou16(mtu, &lease->mtu); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu); + } + + if (domains) { + _cleanup_strv_free_ char **a = NULL; + a = strv_split(domains, " "); + if (!a) + return -ENOMEM; + + if (!strv_isempty(a)) + lease->search_domains = TAKE_PTR(a); + } + + if (routes) { + r = deserialize_dhcp_routes( + &lease->static_route, + &lease->static_route_size, + &lease->static_route_allocated, + routes); + if (r < 0) + log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes); + } + + if (lifetime) { + r = safe_atou32(lifetime, &lease->lifetime); + if (r < 0) + log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); + } + + if (t1) { + r = safe_atou32(t1, &lease->t1); + if (r < 0) + log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1); + } + + if (t2) { + r = safe_atou32(t2, &lease->t2); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2); + } + + if (client_id_hex) { + r = unhexmem(client_id_hex, (size_t) -1, &lease->client_id, &lease->client_id_len); + if (r < 0) + log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); + } + + if (vendor_specific_hex) { + r = unhexmem(vendor_specific_hex, (size_t) -1, &lease->vendor_specific, &lease->vendor_specific_len); + if (r < 0) + log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); + } + + for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { + _cleanup_free_ void *data = NULL; + size_t len; + + if (!options[i]) + continue; + + r = unhexmem(options[i], (size_t) -1, &data, &len); + if (r < 0) { + log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); + continue; + } + + r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(lease); + + return 0; +} + +int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { + struct in_addr address, mask; + int r; + + assert(lease); + + if (lease->address == 0) + return -ENODATA; + + address.s_addr = lease->address; + + /* fall back to the default subnet masks based on address class */ + r = in4_addr_default_subnet_mask(&address, &mask); + if (r < 0) + return r; + + lease->subnet_mask = mask.s_addr; + lease->have_subnet_mask = true; + + return 0; +} + +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) { + assert_return(lease, -EINVAL); + assert_return(client_id, -EINVAL); + assert_return(client_id_len, -EINVAL); + + if (!lease->client_id) + return -ENODATA; + + *client_id = lease->client_id; + *client_id_len = lease->client_id_len; + + return 0; +} + +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) { + assert_return(lease, -EINVAL); + assert_return(client_id || client_id_len <= 0, -EINVAL); + + if (client_id_len <= 0) + lease->client_id = mfree(lease->client_id); + else { + void *p; + + p = memdup(client_id, client_id_len); + if (!p) + return -ENOMEM; + + free(lease->client_id); + lease->client_id = p; + lease->client_id_len = client_id_len; + } + + return 0; +} + +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) { + assert_return(lease, -EINVAL); + assert_return(tz, -EINVAL); + + if (!lease->timezone) + return -ENODATA; + + *tz = lease->timezone; + return 0; +} + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { + assert_return(route, -EINVAL); + assert_return(destination, -EINVAL); + + *destination = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { + assert_return(route, -EINVAL); + assert_return(length, -EINVAL); + + *length = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { + assert_return(route, -EINVAL); + assert_return(gateway, -EINVAL); + + *gateway = route->gw_addr; + return 0; +} + +int sd_dhcp_route_get_option(sd_dhcp_route *route) { + assert_return(route, -EINVAL); + + return route->option; +} diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c new file mode 100644 index 0000000..3fafd3c --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c @@ -0,0 +1,1857 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#if 0 /* NM_IGNORED */ +#include +#else /* NM_IGNORED */ +#include +#endif /* NM_IGNORED */ +#include + +#include "sd-dhcp6-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp6-internal.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "dns-domain.h" +#include "event-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "strv.h" +#include "util.h" +#include "web-util.h" + +#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN + +#define IRT_DEFAULT (1 * USEC_PER_DAY) +#define IRT_MINIMUM (600 * USEC_PER_SEC) + +/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */ +enum { + DHCP6_REQUEST_IA_NA = 1, + DHCP6_REQUEST_IA_TA = 2, /* currently not used */ + DHCP6_REQUEST_IA_PD = 4, +}; + +struct sd_dhcp6_client { + unsigned n_ref; + + enum DHCP6State state; + sd_event *event; + int event_priority; + int ifindex; + DHCP6Address hint_pd_prefix; + struct in6_addr local_address; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint16_t arp_type; + DHCP6IA ia_na; + DHCP6IA ia_pd; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + unsigned request; + be32_t transaction_id; + usec_t transaction_start; + struct sd_dhcp6_lease *lease; + int fd; + bool information_request; + bool iaid_set; + be16_t *req_opts; + size_t req_opts_allocated; + size_t req_opts_len; + char *fqdn; + char *mudurl; + char **user_class; + char **vendor_class; + sd_event_source *receive_message; + usec_t retransmit_time; + uint8_t retransmit_count; + sd_event_source *timeout_resend; + sd_event_source *timeout_resend_expire; + sd_dhcp6_client_callback_t callback; + void *userdata; + struct duid duid; + size_t duid_len; + usec_t information_request_time_usec; + usec_t information_refresh_time_usec; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; +}; + +static const uint16_t default_req_opts[] = { + SD_DHCP6_OPTION_DNS_SERVERS, + SD_DHCP6_OPTION_DOMAIN_LIST, + SD_DHCP6_OPTION_NTP_SERVER, + SD_DHCP6_OPTION_SNTP_SERVERS, +}; + +const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = { + [DHCP6_SOLICIT] = "SOLICIT", + [DHCP6_ADVERTISE] = "ADVERTISE", + [DHCP6_REQUEST] = "REQUEST", + [DHCP6_CONFIRM] = "CONFIRM", + [DHCP6_RENEW] = "RENEW", + [DHCP6_REBIND] = "REBIND", + [DHCP6_REPLY] = "REPLY", + [DHCP6_RELEASE] = "RELEASE", + [DHCP6_DECLINE] = "DECLINE", + [DHCP6_RECONFIGURE] = "RECONFIGURE", + [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST", + [DHCP6_RELAY_FORW] = "RELAY-FORW", + [DHCP6_RELAY_REPL] = "RELAY-REPL", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int); + +const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { + [DHCP6_STATUS_SUCCESS] = "Success", + [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", + [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", + [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", + [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", + [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", + [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available", + [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type", + [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query", + [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured", + [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed", + [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated", + [DHCP6_STATUS_DATA_MISSING] = "Data missing", + [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete", + [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported", + [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused", + [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use", + [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict", + [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information", + [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information", + [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down", + [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported", + [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); + +#define DHCP6_CLIENT_DONT_DESTROY(client) \ + _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) + +static int client_start(sd_dhcp6_client *client, enum DHCP6State state); + +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { + + assert_return(client, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address) { + + assert_return(client, -EINVAL); + assert_return(local_address, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->local_address = *local_address; + + return 0; +} + +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, size_t addr_len, + uint16_t arp_type) { + + assert_return(client, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else { + client->arp_type = ARPHRD_NONE; + client->mac_addr_len = 0; + return 0; + } + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0) + return 0; + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + + return 0; +} + +int sd_dhcp6_client_set_prefix_delegation_hint( + sd_dhcp6_client *client, + uint8_t prefixlen, + const struct in6_addr *pd_address) { + + assert_return(client, -EINVAL); + assert_return(pd_address, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->hint_pd_prefix.iapdprefix.address = *pd_address; + client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen; + + return 0; +} + +int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp6_option_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(client->vendor_options, v, v); + if (r < 0) + return r; + + sd_dhcp6_option_ref(v); + + return 1; +} + +static int client_ensure_duid(sd_dhcp6_client *client) { + if (client->duid_len != 0) + return 0; + + return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); +} + +/** + * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +static int dhcp6_client_set_duid_internal( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len, + usec_t llt_time) { + int r; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + if (duid) { + r = dhcp_validate_duid_len(duid_type, duid_len, true); + if (r < 0) { + r = dhcp_validate_duid_len(duid_type, duid_len, false); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to validate length of DUID: %m"); + + log_dhcp6_client(client, "Using DUID of type %u of incorrect length, proceeding.", duid_type); + } + + client->duid.type = htobe16(duid_type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = sizeof(client->duid.type) + duid_len; + } else +#if 0 /* NM_IGNORED */ + switch (duid_type) { + case DUID_TYPE_LLT: + if (client->mac_addr_len == 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); + + r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m"); + break; + case DUID_TYPE_EN: + r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m"); + break; + case DUID_TYPE_LL: + if (client->mac_addr_len == 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); + + r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m"); + break; + case DUID_TYPE_UUID: + r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m"); + break; + default: + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); + } +#else /* NM_IGNORED */ + g_return_val_if_reached (-EINVAL); +#endif /* NM_IGNORED */ + + return 0; +} + +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0); +} + +int sd_dhcp6_client_set_duid_llt( + sd_dhcp6_client *client, + usec_t llt_time) { + return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time); +} + +static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = { + [DUID_TYPE_LLT] = "DUID-LLT", + [DUID_TYPE_EN] = "DUID-EN/Vendor", + [DUID_TYPE_LL] = "DUID-LL", + [DUID_TYPE_UUID] = "UUID", +}; +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType); + +int sd_dhcp6_client_duid_as_string( + sd_dhcp6_client *client, + char **duid) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + const char *v; + int r; + + assert_return(client, -EINVAL); + assert_return(client->duid_len > 0, -ENODATA); + + v = dhcp6_duid_type_to_string(be16toh(client->duid.type)); + if (v) { + s = strdup(v); + if (!s) + return -ENOMEM; + } else { + r = asprintf(&s, "%0x", client->duid.type); + if (r < 0) + return -ENOMEM; + } + + t = hexmem(&client->duid.raw.data, client->duid_len); + if (!t) + return -ENOMEM; + + p = strjoin(s, ":", t); + if (!p) + return -ENOMEM; + + *duid = TAKE_PTR(p); + + return 0; +} + +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ia_na.ia_na.id = htobe32(iaid); + client->ia_pd.ia_pd.id = htobe32(iaid); + client->iaid_set = true; + + return 0; +} + +int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) { + assert_return(client, -EINVAL); + assert_return(iaid, -EINVAL); + + if (!client->iaid_set) + return -ENODATA; + + *iaid = be32toh(client->ia_na.ia_na.id); + + return 0; +} + +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn) { + + assert_return(client, -EINVAL); + + /* Make sure FQDN qualifies as DNS and as Linux hostname */ + if (fqdn && + !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0)) + return -EINVAL; + + return free_and_strdup(&client->fqdn, fqdn); +} + +int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->information_request = enabled; + + return 0; +} + +int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) { + assert_return(client, -EINVAL); + assert_return(enabled, -EINVAL); + + *enabled = client->information_request; + + return 0; +} + +int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) { + size_t t; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + if (option <= 0 || option >= UINT8_MAX) + return -EINVAL; + + for (t = 0; t < client->req_opts_len; t++) + if (client->req_opts[t] == htobe16(option)) + return -EEXIST; + + if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated, + client->req_opts_len + 1)) + return -ENOMEM; + + client->req_opts[client->req_opts_len++] = htobe16(option); + + return 0; +} + +int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) { + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(mudurl, -EINVAL); + assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL); + assert_return(http_url_is_valid(mudurl), -EINVAL); + + return free_and_strdup(&client->mudurl, mudurl); +} + +int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char **user_class) { + _cleanup_strv_free_ char **s = NULL; + char **p; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + assert_return(user_class, -EINVAL); + + STRV_FOREACH(p, user_class) + if (strlen(*p) > UINT16_MAX) + return -ENAMETOOLONG; + + s = strv_copy(user_class); + if (!s) + return -ENOMEM; + + client->user_class = TAKE_PTR(s); + + return 0; +} + +int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char **vendor_class) { + _cleanup_strv_free_ char **s = NULL; + char **p; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(vendor_class, -EINVAL); + + STRV_FOREACH(p, vendor_class) + if (strlen(*p) > UINT8_MAX) + return -ENAMETOOLONG; + + s = strv_copy(vendor_class); + if (!s) + return -ENOMEM; + + client->vendor_class = TAKE_PTR(s); + + return 0; +} + +int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) { + assert_return(client, -EINVAL); + assert_return(delegation, -EINVAL); + + *delegation = FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD); + + return 0; +} + +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) { + assert_return(client, -EINVAL); + + SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation); + + return 0; +} + +int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) { + assert_return(client, -EINVAL); + assert_return(request, -EINVAL); + + *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA); + + return 0; +} + +int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) { + assert_return(client, -EINVAL); + + SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request); + + return 0; +} + +int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) { + assert_return(client, -EINVAL); + + client->transaction_id = transaction_id; + + return 0; +} + +int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { + assert_return(client, -EINVAL); + + if (!client->lease) + return -ENOMSG; + + if (ret) + *ret = client->lease; + + return 0; +} + +int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v); + if (r < 0) + return r; + + sd_dhcp6_option_ref(v); + return 0; +} + +static void client_notify(sd_dhcp6_client *client, int event) { + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); +} + +static int client_reset(sd_dhcp6_client *client) { + assert(client); + + client->lease = sd_dhcp6_lease_unref(client->lease); + + client->receive_message = + sd_event_source_unref(client->receive_message); + + client->transaction_id = 0; + client->transaction_start = 0; + + client->retransmit_time = 0; + client->retransmit_count = 0; + + (void) event_source_disable(client->timeout_resend); + (void) event_source_disable(client->timeout_resend_expire); + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + + client->state = DHCP6_STATE_STOPPED; + + return 0; +} + +static void client_stop(sd_dhcp6_client *client, int error) { + DHCP6_CLIENT_DONT_DESTROY(client); + + assert(client); + + client_notify(client, error); + + client_reset(client); +} + +static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { + _cleanup_free_ DHCP6Message *message = NULL; + struct in6_addr all_servers = + IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; + struct sd_dhcp6_option *j; + size_t len, optlen = 512; + uint8_t *opt; + int r; + usec_t elapsed_usec; + be16_t elapsed_time; + + assert(client); + + len = sizeof(DHCP6Message) + optlen; + + message = malloc0(len); + if (!message) + return -ENOMEM; + + opt = (uint8_t *)(message + 1); + + message->transaction_id = client->transaction_id; + + switch(client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + message->type = DHCP6_INFORMATION_REQUEST; + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + break; + + case DHCP6_STATE_SOLICITATION: + message->type = DHCP6_SOLICIT; + + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + if (r < 0) + return r; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) { + r = dhcp6_option_append_ia(&opt, &optlen, + &client->ia_na); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, + client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { + r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + + break; + + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + + if (client->state == DHCP6_STATE_REQUEST) + message->type = DHCP6_REQUEST; + else + message->type = DHCP6_RENEW; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, + client->lease->serverid_len, + client->lease->serverid); + if (r < 0) + return r; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { + r = dhcp6_option_append_ia(&opt, &optlen, + &client->lease->ia); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { + r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + + break; + + case DHCP6_STATE_REBIND: + message->type = DHCP6_REBIND; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) { + r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { + r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return -EINVAL; + } + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO, + client->req_opts_len * sizeof(be16_t), + client->req_opts); + if (r < 0) + return r; + + assert(client->duid_len); + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID, + client->duid_len, &client->duid); + if (r < 0) + return r; + + elapsed_usec = time_now - client->transaction_start; + if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10) + elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10); + else + elapsed_time = 0xffff; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME, + sizeof(elapsed_time), &elapsed_time); + if (r < 0) + return r; + + ORDERED_HASHMAP_FOREACH(j, client->extra_options) { + r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data); + if (r < 0) + return r; + } + + r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message, + len - optlen); + if (r < 0) + return r; + + log_dhcp6_client(client, "Sent %s", + dhcp6_message_type_to_string(message->type)); + + return 0; +} + +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + + assert(s); + assert(client); + assert(client->lease); + + (void) event_source_disable(client->timeout_t2); + + log_dhcp6_client(client, "Timeout T2"); + + client_start(client, DHCP6_STATE_REBIND); + + return 0; +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + + assert(s); + assert(client); + assert(client->lease); + + (void) event_source_disable(client->timeout_t1); + + log_dhcp6_client(client, "Timeout T1"); + + client_start(client, DHCP6_STATE_RENEW); + + return 0; +} + +static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + DHCP6_CLIENT_DONT_DESTROY(client); + enum DHCP6State state; + + assert(s); + assert(client); + assert(client->event); + + state = client->state; + + client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE); + + /* RFC 3315, section 18.1.4., says that "...the client may choose to + use a Solicit message to locate a new DHCP server..." */ + if (state == DHCP6_STATE_REBIND) + client_start(client, DHCP6_STATE_SOLICITATION); + + return 0; +} + +static usec_t client_timeout_compute_random(usec_t val) { + return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC; +} + +static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) { + int r = 0; + sd_dhcp6_client *client = userdata; + usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; + usec_t max_retransmit_duration = 0; + uint8_t max_retransmit_count = 0; + char time_string[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(client); + assert(client->event); + + (void) event_source_disable(client->timeout_resend); + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + init_retransmit_time = DHCP6_INF_TIMEOUT; + max_retransmit_time = DHCP6_INF_MAX_RT; + + break; + + case DHCP6_STATE_SOLICITATION: + + if (client->retransmit_count && client->lease) { + client_start(client, DHCP6_STATE_REQUEST); + return 0; + } + + init_retransmit_time = DHCP6_SOL_TIMEOUT; + max_retransmit_time = DHCP6_SOL_MAX_RT; + + break; + + case DHCP6_STATE_REQUEST: + init_retransmit_time = DHCP6_REQ_TIMEOUT; + max_retransmit_time = DHCP6_REQ_MAX_RT; + max_retransmit_count = DHCP6_REQ_MAX_RC; + + break; + + case DHCP6_STATE_RENEW: + init_retransmit_time = DHCP6_REN_TIMEOUT; + max_retransmit_time = DHCP6_REN_MAX_RT; + + /* RFC 3315, section 18.1.3. says max retransmit duration will + be the remaining time until T2. Instead of setting MRD, + wait for T2 to trigger with the same end result */ + + break; + + case DHCP6_STATE_REBIND: + init_retransmit_time = DHCP6_REB_TIMEOUT; + max_retransmit_time = DHCP6_REB_MAX_RT; + + if (event_source_is_enabled(client->timeout_resend_expire) <= 0) { + uint32_t expire = 0; + + r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire); + if (r < 0) { + client_stop(client, r); + return 0; + } + max_retransmit_duration = expire * USEC_PER_SEC; + } + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return 0; + } + + if (max_retransmit_count > 0 && + client->retransmit_count >= max_retransmit_count) { + client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); + return 0; + } + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + r = client_send_message(client, time_now); + if (r >= 0) + client->retransmit_count++; + + if (client->retransmit_time == 0) { + client->retransmit_time = + client_timeout_compute_random(init_retransmit_time); + + if (client->state == DHCP6_STATE_SOLICITATION) + client->retransmit_time += init_retransmit_time / 10; + + } else { + if (max_retransmit_time > 0 && + client->retransmit_time > max_retransmit_time / 2) + client->retransmit_time = client_timeout_compute_random(max_retransmit_time); + else + client->retransmit_time += client_timeout_compute_random(client->retransmit_time); + } + + log_dhcp6_client(client, "Next retransmission in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + time_now + client->retransmit_time, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timer", true); + if (r < 0) + goto error; + + if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) { + + log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", + max_retransmit_duration / USEC_PER_SEC); + + r = event_reset_time(client->event, &client->timeout_resend_expire, + clock_boottime_or_monotonic(), + time_now + max_retransmit_duration, USEC_PER_SEC, + client_timeout_resend_expire, client, + client->event_priority, "dhcp6-resend-expire-timer", true); + if (r < 0) + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_ensure_iaid(sd_dhcp6_client *client) { + int r; + uint32_t iaid; + + assert(client); + + if (client->iaid_set) + return 0; + + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid); + if (r < 0) + return r; + + client->ia_na.ia_na.id = iaid; + client->ia_pd.ia_pd.id = iaid; + client->iaid_set = true; + + return 0; +} + +static int client_parse_message( + sd_dhcp6_client *client, + DHCP6Message *message, + size_t len, + sd_dhcp6_lease *lease) { + + uint16_t ia_na_status = 0, ia_pd_status = 0; + uint32_t lt_t1 = ~0, lt_t2 = ~0; + usec_t irt = IRT_DEFAULT; + bool clientid = false; + size_t pos = 0; + int r; + + assert(client); + assert(message); + assert(len >= sizeof(DHCP6Message)); + assert(lease); + + len -= sizeof(DHCP6Message); + + while (pos < len) { + DHCP6Option *option = (DHCP6Option *) &message->options[pos]; + uint16_t optcode, optlen; + be32_t iaid_lease; + int status; + uint8_t *optval; + + if (len < pos + offsetof(DHCP6Option, data)) + return -ENOBUFS; + + optcode = be16toh(option->code); + optlen = be16toh(option->len); + optval = option->data; + + if (len < pos + offsetof(DHCP6Option, data) + optlen) + return -ENOBUFS; + + switch (optcode) { + case SD_DHCP6_OPTION_CLIENTID: + if (clientid) { + log_dhcp6_client(client, "%s contains multiple clientids", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (optlen != client->duid_len || + memcmp(&client->duid, optval, optlen) != 0) { + log_dhcp6_client(client, "%s DUID does not match", + dhcp6_message_type_to_string(message->type)); + + return -EINVAL; + } + clientid = true; + + break; + + case SD_DHCP6_OPTION_SERVERID: + r = dhcp6_lease_get_serverid(lease, NULL, NULL); + if (r >= 0) { + log_dhcp6_client(client, "%s contains multiple serverids", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + r = dhcp6_lease_set_serverid(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_PREFERENCE: + if (optlen != 1) + return -EINVAL; + + r = dhcp6_lease_set_preference(lease, optval[0]); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option)); + if (status < 0) + return status; + + if (status > 0) { + log_dhcp6_client(client, "%s Status %s", + dhcp6_message_type_to_string(message->type), + dhcp6_message_status_to_string(status)); + + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_NA: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA NA option"); + + break; + } + + r = dhcp6_option_parse_ia(option, &lease->ia, &ia_na_status); + if (r < 0 && r != -ENOMSG) + return r; + + if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) { + pos += offsetof(DHCP6Option, data) + optlen; + continue; + } + + r = dhcp6_lease_get_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_na.ia_na.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID for IA NA", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (lease->ia.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1)); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA PD option"); + + break; + } + + r = dhcp6_option_parse_ia(option, &lease->pd, &ia_pd_status); + if (r < 0 && r != -ENOMSG) + return r; + + if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) { + pos += offsetof(DHCP6Option, data) + optlen; + continue; + } + + r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_pd.ia_pd.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID for IA PD", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (lease->pd.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2)); + } + + break; + + case SD_DHCP6_OPTION_RAPID_COMMIT: + r = dhcp6_lease_set_rapid_commit(lease); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DNS_SERVERS: + r = dhcp6_lease_set_dns(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DOMAIN_LIST: + r = dhcp6_lease_set_domains(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_NTP_SERVER: + r = dhcp6_lease_set_ntp(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_SNTP_SERVERS: + r = dhcp6_lease_set_sntp(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_FQDN: + r = dhcp6_lease_set_fqdn(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME: + if (optlen != 4) + return -EINVAL; + + irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC; + break; + } + + pos += offsetof(DHCP6Option, data) + optlen; + } + + if (ia_na_status > 0 && ia_pd_status > 0) { + log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring."); + return -EINVAL; + } + + if (!clientid) { + log_dhcp6_client(client, "%s has incomplete options", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (client->state != DHCP6_STATE_INFORMATION_REQUEST) { + r = dhcp6_lease_get_serverid(lease, NULL, NULL); + if (r < 0) { + log_dhcp6_client(client, "%s has no server id", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + } else { + if (lease->ia.addresses) { + lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1); + lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2); + } + + if (lease->pd.addresses) { + lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1); + lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2); + } + } + + client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM); + + return 0; +} + +static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + bool rapid_commit; + int r; + + assert(client); + assert(reply); + + if (reply->type != DHCP6_REPLY) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return -ENOMEM; + + r = client_parse_message(client, reply, len, lease); + if (r < 0) + return r; + + if (client->state == DHCP6_STATE_SOLICITATION) { + r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit); + if (r < 0) + return r; + + if (!rapid_commit) + return 0; + } + + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + return DHCP6_STATE_BOUND; +} + +static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t pref_advertise = 0, pref_lease = 0; + int r; + + if (advertise->type != DHCP6_ADVERTISE) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return r; + + r = client_parse_message(client, advertise, len, lease); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(lease, &pref_advertise); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(client->lease, &pref_lease); + + if (r < 0 || pref_advertise > pref_lease) { + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + r = 0; + } + + if (pref_advertise == 255 || client->retransmit_count > 1) + r = DHCP6_STATE_REQUEST; + + return r; +} + +static int client_receive_message( + sd_event_source *s, + int fd, uint32_t + revents, + void *userdata) { + + sd_dhcp6_client *client = userdata; + DHCP6_CLIENT_DONT_DESTROY(client); + _cleanup_free_ DHCP6Message *message = NULL; + ssize_t buflen, len; + int r = 0; + + assert(s); + assert(client); + assert(client->event); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + /* the link is down. Don't return an error or the I/O event + source will be disconnected and we won't be able to receive + packets again when the link comes back. */ + return 0; + if (buflen < 0) + return buflen; + + message = malloc(buflen); + if (!message) + return -ENOMEM; + + len = recv(fd, message, buflen, 0); + if (len < 0) { + /* see comment above for why we shouldn't error out on ENETDOWN. */ + if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN)) + return 0; + + return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + + } + if ((size_t) len < sizeof(DHCP6Message)) { + log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); + return 0; + } + + switch(message->type) { + case DHCP6_SOLICIT: + case DHCP6_REQUEST: + case DHCP6_CONFIRM: + case DHCP6_RENEW: + case DHCP6_REBIND: + case DHCP6_RELEASE: + case DHCP6_DECLINE: + case DHCP6_INFORMATION_REQUEST: + case DHCP6_RELAY_FORW: + case DHCP6_RELAY_REPL: + return 0; + + case DHCP6_ADVERTISE: + case DHCP6_REPLY: + case DHCP6_RECONFIGURE: + break; + + default: + log_dhcp6_client(client, "Unknown message type %d", message->type); + return 0; + } + + if (client->transaction_id != (message->transaction_id & + htobe32(0x00ffffff))) + return 0; + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); + + client_start(client, DHCP6_STATE_STOPPED); + + break; + + case DHCP6_STATE_SOLICITATION: + r = client_receive_advertise(client, message, len); + + if (r == DHCP6_STATE_REQUEST) { + client_start(client, r); + + break; + } + + _fallthrough_; /* for Solicitation Rapid Commit option check */ + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + if (r == DHCP6_STATE_BOUND) { + + r = client_start(client, DHCP6_STATE_BOUND); + if (r < 0) { + client_stop(client, r); + return 0; + } + + client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + } + + break; + + case DHCP6_STATE_BOUND: + + break; + + case DHCP6_STATE_STOPPED: + return 0; + } + + log_dhcp6_client(client, "Recv %s", + dhcp6_message_type_to_string(message->type)); + + return 0; +} + +static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1, + uint32_t *lifetime_t2) { + assert_return(client, -EINVAL); + assert_return(client->lease, -EINVAL); + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { + *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1); + *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2); + + return 0; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { + *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1); + *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2); + + return 0; + } + + return -ENOMSG; +} + +static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { + int r; + usec_t timeout, time_now; + char time_string[FORMAT_TIMESPAN_MAX]; + uint32_t lifetime_t1, lifetime_t2; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(client->state != state, -EINVAL); + + (void) event_source_disable(client->timeout_resend_expire); + (void) event_source_disable(client->timeout_resend); + client->retransmit_time = 0; + client->retransmit_count = 0; + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + if (!client->receive_message) { + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, client_receive_message, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, + "dhcp6-receive-message"); + if (r < 0) + goto error; + } + + switch (state) { + case DHCP6_STATE_STOPPED: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + client->state = DHCP6_STATE_STOPPED; + + return 0; + } + + _fallthrough_; + case DHCP6_STATE_SOLICITATION: + client->state = DHCP6_STATE_SOLICITATION; + + break; + + case DHCP6_STATE_INFORMATION_REQUEST: + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + client->state = state; + + break; + + case DHCP6_STATE_BOUND: + + r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2); + if (r < 0) + goto error; + + if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) { + log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x", + lifetime_t1, lifetime_t2); + + return 0; + } + + timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC); + + log_dhcp6_client(client, "T1 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_t1, + clock_boottime_or_monotonic(), + time_now + timeout, 10 * USEC_PER_SEC, + client_timeout_t1, client, + client->event_priority, "dhcp6-t1-timeout", true); + if (r < 0) + goto error; + + timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC); + + log_dhcp6_client(client, "T2 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_t2, + clock_boottime_or_monotonic(), + time_now + timeout, 10 * USEC_PER_SEC, + client_timeout_t2, client, + client->event_priority, "dhcp6-t2-timeout", true); + if (r < 0) + goto error; + + client->state = state; + + return 0; + } + + client->transaction_id = random_u32() & htobe32(0x00ffffff); + client->transaction_start = time_now; + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timeout", true); + if (r < 0) + goto error; + + return 0; + + error: + client_reset(client); + return r; +} + +int sd_dhcp6_client_stop(sd_dhcp6_client *client) { + if (!client) + return 0; + + client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); + + client->fd = safe_close(client->fd); + + return 0; +} + +int sd_dhcp6_client_is_running(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + return client->state != DHCP6_STATE_STOPPED; +} + +int sd_dhcp6_client_start(sd_dhcp6_client *client) { + enum DHCP6State state = DHCP6_STATE_SOLICITATION; + int r; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL); + + if (!IN_SET(client->state, DHCP6_STATE_STOPPED)) + return -EBUSY; + + if (!client->information_request && !client->request) + return -EINVAL; + + r = client_reset(client); + if (r < 0) + return r; + + r = client_ensure_iaid(client); + if (r < 0) + return r; + + r = client_ensure_duid(client); + if (r < 0) + return r; + + if (client->fd < 0) { + r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address); + if (r < 0) { + _cleanup_free_ char *p = NULL; + + (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); + return log_dhcp6_client_errno(client, r, + "Failed to bind to UDP socket at address %s: %m", strna(p)); + } + + client->fd = r; + } + + if (client->information_request) { + usec_t t = now(CLOCK_MONOTONIC); + + if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec)) + return 0; + + client->information_request_time_usec = t; + state = DHCP6_STATE_INFORMATION_REQUEST; + } + + log_dhcp6_client(client, "Started in %s mode", + client->information_request ? "Information request" : "Managed"); + + return client_start(client, state); +} + +int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) { + int r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { + assert_return(client, NULL); + + return client->event; +} + +static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { + assert(client); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire); + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + + client_reset(client); + + client->fd = safe_close(client->fd); + + sd_dhcp6_client_detach_event(client); + + free(client->req_opts); + free(client->fqdn); + free(client->mudurl); + + ordered_hashmap_free(client->extra_options); + strv_free(client->user_class); + strv_free(client->vendor_class); + + return mfree(client); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free); + +int sd_dhcp6_client_new(sd_dhcp6_client **ret) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + _cleanup_free_ be16_t *req_opts = NULL; + size_t t; + + assert_return(ret, -EINVAL); + + req_opts = new(be16_t, ELEMENTSOF(default_req_opts)); + if (!req_opts) + return -ENOMEM; + + for (t = 0; t < ELEMENTSOF(default_req_opts); t++) + req_opts[t] = htobe16(default_req_opts[t]); + + client = new(sd_dhcp6_client, 1); + if (!client) + return -ENOMEM; + + *client = (sd_dhcp6_client) { + .n_ref = 1, + .ia_na.type = SD_DHCP6_OPTION_IA_NA, + .ia_pd.type = SD_DHCP6_OPTION_IA_PD, + .ifindex = -1, + .request = DHCP6_REQUEST_IA_NA, + .fd = -1, + .req_opts_len = ELEMENTSOF(default_req_opts), + .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1, + .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1, + .req_opts = TAKE_PTR(req_opts), + }; + + *ret = TAKE_PTR(client); + + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c new file mode 100644 index 0000000..5792f68 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c @@ -0,0 +1,435 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include + +#include "alloc-util.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "strv.h" +#include "util.h" + +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) { + DHCP6Address *addr; + uint32_t valid = 0, t; + + assert_return(ia, -EINVAL); + assert_return(expire, -EINVAL); + + LIST_FOREACH(addresses, addr, ia->addresses) { + t = be32toh(addr->iaaddr.lifetime_valid); + if (valid < t) + valid = t; + } + + t = be32toh(ia->ia_na.lifetime_t2); + if (t > valid) + return -EINVAL; + + *expire = valid - t; + + return 0; +} + +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) { + DHCP6Address *address; + + if (!ia) + return NULL; + + while (ia->addresses) { + address = ia->addresses; + + LIST_REMOVE(addresses, ia->addresses, address); + + free(address); + } + + return NULL; +} + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len) { + uint8_t *serverid; + + assert_return(lease, -EINVAL); + assert_return(id, -EINVAL); + + serverid = memdup(id, len); + if (!serverid) + return -ENOMEM; + + free_and_replace(lease->serverid, serverid); + lease->serverid_len = len; + + return 0; +} + +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) { + assert_return(lease, -EINVAL); + + if (!lease->serverid) + return -ENOMSG; + + if (id) + *id = lease->serverid; + if (len) + *len = lease->serverid_len; + + return 0; +} + +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) { + assert_return(lease, -EINVAL); + + lease->preference = preference; + + return 0; +} + +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) { + assert_return(preference, -EINVAL); + + if (!lease) + return -EINVAL; + + *preference = lease->preference; + + return 0; +} + +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) { + assert_return(lease, -EINVAL); + + lease->rapid_commit = true; + + return 0; +} + +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) { + assert_return(lease, -EINVAL); + assert_return(rapid_commit, -EINVAL); + + *rapid_commit = lease->rapid_commit; + + return 0; +} + +int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { + assert_return(lease, -EINVAL); + assert_return(iaid, -EINVAL); + + *iaid = lease->ia.ia_na.id; + + return 0; +} + +int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { + assert_return(lease, -EINVAL); + assert_return(iaid, -EINVAL); + + *iaid = lease->pd.ia_pd.id; + + return 0; +} + +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->addr_iter) + return -ENOMSG; + + memcpy(addr, &lease->addr_iter->iaaddr.address, + sizeof(struct in6_addr)); + *lifetime_preferred = + be32toh(lease->addr_iter->iaaddr.lifetime_preferred); + *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); + + lease->addr_iter = lease->addr_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { + if (lease) + lease->addr_iter = lease->ia.addresses; +} + +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(prefix, -EINVAL); + assert_return(prefix_len, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->prefix_iter) + return -ENOMSG; + + memcpy(prefix, &lease->prefix_iter->iapdprefix.address, + sizeof(struct in6_addr)); + *prefix_len = lease->prefix_iter->iapdprefix.prefixlen; + *lifetime_preferred = + be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); + *lifetime_valid = + be32toh(lease->prefix_iter->iapdprefix.lifetime_valid); + + lease->prefix_iter = lease->prefix_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { + if (lease) + lease->prefix_iter = lease->pd.addresses; +} + +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns, + lease->dns_count, + &lease->dns_allocated); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Invalid DNS server option: %m"); + + lease->dns_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->dns_count) { + *addrs = lease->dns; + return lease->dns_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) { + int r; + char **domains; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_domainname_list(optval, optlen, &domains); + if (r < 0) + return 0; + + strv_free_and_replace(lease->domains, domains); + lease->domains_count = r; + + return r; +} + +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) { + assert_return(lease, -EINVAL); + assert_return(domains, -EINVAL); + + if (lease->domains_count) { + *domains = lease->domains; + return lease->domains_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + uint16_t subopt; + size_t sublen; + uint8_t *subval; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + lease->ntp = mfree(lease->ntp); + lease->ntp_count = 0; + lease->ntp_allocated = 0; + + while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen, + &subval)) >= 0) { + int s; + char **servers; + + switch(subopt) { + case DHCP6_NTP_SUBOPTION_SRV_ADDR: + case DHCP6_NTP_SUBOPTION_MC_ADDR: + if (sublen != 16) + return 0; + + s = dhcp6_option_parse_ip6addrs(subval, sublen, + &lease->ntp, + lease->ntp_count, + &lease->ntp_allocated); + if (s < 0) + return s; + + lease->ntp_count = s; + + break; + + case DHCP6_NTP_SUBOPTION_SRV_FQDN: + r = dhcp6_option_parse_domainname_list(subval, sublen, + &servers); + if (r < 0) + return 0; + + strv_free_and_replace(lease->ntp_fqdn, servers); + lease->ntp_fqdn_count = r; + + break; + } + } + + if (r != -ENOMSG) + return r; + + return 0; +} + +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + if (lease->ntp || lease->ntp_fqdn) { + log_dhcp6_client(client, "NTP information already provided"); + + return 0; + } + + log_dhcp6_client(client, "Using deprecated SNTP information"); + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp, + lease->ntp_count, + &lease->ntp_allocated); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Invalid SNTP server option: %m"); + + lease->ntp_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + const struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->ntp_count) { + *addrs = lease->ntp; + return lease->ntp_count; + } + + return -ENOENT; +} + +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) { + assert_return(lease, -EINVAL); + assert_return(ntp_fqdn, -EINVAL); + + if (lease->ntp_fqdn_count) { + *ntp_fqdn = lease->ntp_fqdn; + return lease->ntp_fqdn_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, + size_t optlen) { + int r; + char *fqdn; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (optlen < 2) + return -ENODATA; + + /* Ignore the flags field, it doesn't carry any useful + information for clients. */ + r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn); + if (r < 0) + return r; + + return free_and_replace(lease->fqdn, fqdn); +} + +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) { + assert_return(lease, -EINVAL); + assert_return(fqdn, -EINVAL); + + if (lease->fqdn) { + *fqdn = lease->fqdn; + return 0; + } + + return -ENOENT; +} + +static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { + assert(lease); + + free(lease->serverid); + dhcp6_lease_free_ia(&lease->ia); + dhcp6_lease_free_ia(&lease->pd); + + free(lease->dns); + free(lease->fqdn); + + lease->domains = strv_free(lease->domains); + + free(lease->ntp); + + lease->ntp_fqdn = strv_free(lease->ntp_fqdn); + return mfree(lease); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free); + +int dhcp6_lease_new(sd_dhcp6_lease **ret) { + sd_dhcp6_lease *lease; + + lease = new0(sd_dhcp6_lease, 1); + if (!lease) + return -ENOMEM; + + lease->n_ref = 1; + + LIST_HEAD_INIT(lease->ia.addresses); + + *ret = lease; + return 0; +} diff --git a/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c new file mode 100644 index 0000000..096afbd --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c @@ -0,0 +1,543 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include +#include + +#include "sd-ipv4acd.h" + +#include "alloc-util.h" +#include "arp-util.h" +#include "ether-addr-util.h" +#include "event-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "in-addr-util.h" +#include "log-link.h" +#include "random-util.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" +#include "time-util.h" + +/* Constants from the RFC */ +#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) +#define PROBE_NUM 3U +#define PROBE_MIN_USEC (1U * USEC_PER_SEC) +#define PROBE_MAX_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_NUM 2U +#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) +#define MAX_CONFLICTS 10U +#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) +#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + IPV4ACD_STATE_STARTED, + IPV4ACD_STATE_WAITING_PROBE, + IPV4ACD_STATE_PROBING, + IPV4ACD_STATE_WAITING_ANNOUNCE, + IPV4ACD_STATE_ANNOUNCING, + IPV4ACD_STATE_RUNNING, + _IPV4ACD_STATE_MAX, + _IPV4ACD_STATE_INVALID = -1 +} IPv4ACDState; + +struct sd_ipv4acd { + unsigned n_ref; + + IPv4ACDState state; + int ifindex; + int fd; + + char ifname[IF_NAMESIZE + 1]; + unsigned n_iteration; + unsigned n_conflict; + + sd_event_source *receive_message_event_source; + sd_event_source *timer_event_source; + + usec_t defend_window; + be32_t address; + + /* External */ + struct ether_addr mac_addr; + + sd_event *event; + int event_priority; + sd_ipv4acd_callback_t callback; + void* userdata; +}; + +#define log_ipv4acd_errno(acd, error, fmt, ...) \ + log_interface_full_errno(sd_ipv4acd_get_ifname(acd), LOG_DEBUG, error, "IPV4ACD: " fmt, ##__VA_ARGS__) +#define log_ipv4acd(acd, fmt, ...) \ + log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__) + +static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = { + [IPV4ACD_STATE_INIT] = "init", + [IPV4ACD_STATE_STARTED] = "started", + [IPV4ACD_STATE_WAITING_PROBE] = "waiting-probe", + [IPV4ACD_STATE_PROBING] = "probing", + [IPV4ACD_STATE_WAITING_ANNOUNCE] = "waiting-announce", + [IPV4ACD_STATE_ANNOUNCING] = "announcing", + [IPV4ACD_STATE_RUNNING] = "running", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ipv4acd_state, IPv4ACDState); + +static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) { + assert(acd); + assert(st < _IPV4ACD_STATE_MAX); + + if (st != acd->state) + log_ipv4acd(acd, "%s -> %s", ipv4acd_state_to_string(acd->state), ipv4acd_state_to_string(st)); + + if (st == acd->state && !reset_counter) + acd->n_iteration++; + else { + acd->state = st; + acd->n_iteration = 0; + } +} + +static void ipv4acd_reset(sd_ipv4acd *acd) { + assert(acd); + + (void) event_source_disable(acd->timer_event_source); + acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); + + acd->fd = safe_close(acd->fd); + + ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); +} + +static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) { + assert(acd); + + acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); + + ipv4acd_reset(acd); + sd_ipv4acd_detach_event(acd); + + return mfree(acd); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free); + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; + + assert_return(ret, -EINVAL); + + acd = new(sd_ipv4acd, 1); + if (!acd) + return -ENOMEM; + + *acd = (sd_ipv4acd) { + .n_ref = 1, + .state = IPV4ACD_STATE_INIT, + .ifindex = -1, + .fd = -1, + }; + + *ret = TAKE_PTR(acd); + + return 0; +} + +static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) { + assert(acd); + + if (!acd->callback) + return; + + acd->callback(acd, event, acd->userdata); +} + +int sd_ipv4acd_stop(sd_ipv4acd *acd) { + IPv4ACDState old_state; + + if (!acd) + return 0; + + old_state = acd->state; + + ipv4acd_reset(acd); + + if (old_state == IPV4ACD_STATE_INIT) + return 0; + + log_ipv4acd(acd, "STOPPED"); + + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP); + + return 0; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) { + usec_t next_timeout, time_now; + + assert(acd); + + next_timeout = usec; + + if (random_usec > 0) + next_timeout += (usec_t) random_u64() % random_usec; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + + return event_reset_time(acd->event, &acd->timer_event_source, + clock_boottime_or_monotonic(), + time_now + next_timeout, 0, + ipv4acd_on_timeout, acd, + acd->event_priority, "ipv4acd-timer", true); +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { + assert(acd); + assert(arp); + + /* see the BPF */ + if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) + return true; + + /* the TPA matched instead of the SPA, this is not a conflict */ + return false; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4acd *acd = userdata; + int r = 0; + + assert(acd); + + switch (acd->state) { + + case IPV4ACD_STATE_STARTED: + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); + + if (acd->n_conflict >= MAX_CONFLICTS) { + char ts[FORMAT_TIMESPAN_MAX]; + log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); + + r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } else { + r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); + goto fail; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Probing %s", strna(address)); + } + + if (acd->n_iteration < PROBE_NUM - 2) { + ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); + if (r < 0) + goto fail; + } else { + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (acd->n_iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); + break; + } + + _fallthrough_; + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "ANNOUNCE"); + + ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); + if (r < 0) + goto fail; + + if (acd->n_iteration == 0) { + acd->n_conflict = 0; + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); + } + + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *acd) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + assert(acd); + + acd->n_conflict++; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); + + ipv4acd_reset(acd); + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_ipv4acd *acd = userdata; + struct ether_arp packet; + ssize_t n; + int r; + + assert(s); + assert(acd); + assert(fd >= 0); + + n = recv(fd, &packet, sizeof(struct ether_arp), 0); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); + goto fail; + } + if ((size_t) n != sizeof(struct ether_arp)) { + log_ipv4acd(acd, "Ignoring too short ARP packet."); + return 0; + } + + switch (acd->state) { + + case IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + + if (ipv4acd_arp_conflict(acd, &packet)) { + usec_t ts; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* Defend address */ + if (ts > acd->defend_window) { + acd->defend_window = ts + DEFEND_INTERVAL_USEC; + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "DEFEND"); + + } else + ipv4acd_on_conflict(acd); + } + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* BPF ensures this packet indicates a conflict */ + ipv4acd_on_conflict(acd); + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) { + char ifname[IF_NAMESIZE + 1]; + + assert_return(acd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + if (!format_ifname(ifindex, ifname)) + return -ENODEV; + + strcpy(acd->ifname, ifname); + acd->ifindex = ifindex; + + return 0; +} + +int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd) { + if (!acd) + return -EINVAL; + + return acd->ifindex; +} + +const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + return empty_to_null(acd->ifname); +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { + assert_return(acd, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->mac_addr = *addr; + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + acd->event = sd_event_unref(acd->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) { + int r; + + assert_return(acd, -EINVAL); + assert_return(!acd->event, -EBUSY); + + if (event) + acd->event = sd_event_ref(event); + else { + r = sd_event_default(&acd->event); + if (r < 0) + return r; + } + + acd->event_priority = priority; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) { + assert_return(acd, -EINVAL); + + acd->callback = cb; + acd->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->address = address->s_addr; + + return 0; +} + +int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + + address->s_addr = acd->address; + + return 0; +} + +int sd_ipv4acd_is_running(sd_ipv4acd *acd) { + assert_return(acd, false); + + return acd->state != IPV4ACD_STATE_INIT; +} + +int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) { + int r; + + assert_return(acd, -EINVAL); + assert_return(acd->event, -EINVAL); + assert_return(acd->ifindex > 0, -EINVAL); + assert_return(acd->address != 0, -EINVAL); + assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) + return r; + + CLOSE_AND_REPLACE(acd->fd, r); + acd->defend_window = 0; + + if (reset_conflicts) + acd->n_conflict = 0; + + r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); + + r = ipv4acd_set_next_wakeup(acd, 0, 0); + if (r < 0) + goto fail; + + ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); + return 0; + +fail: + ipv4acd_reset(acd); + return r; +} diff --git a/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c new file mode 100644 index 0000000..c5e7a91 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include +#include + +#include "sd-id128.h" +#include "sd-ipv4acd.h" +#include "sd-ipv4ll.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "log-link.h" +#include "random-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "string-util.h" +#include "util.h" + +#define IPV4LL_NETWORK UINT32_C(0xA9FE0000) +#define IPV4LL_NETMASK UINT32_C(0xFFFF0000) + +#define IPV4LL_DONT_DESTROY(ll) \ + _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) + +struct sd_ipv4ll { + unsigned n_ref; + + sd_ipv4acd *acd; + + be32_t address; /* the address pushed to ACD */ + struct ether_addr mac; + + struct { + le64_t value; + le64_t generation; + } seed; + bool seed_set; + + /* External */ + be32_t claimed_address; + + sd_ipv4ll_callback_t callback; + void* userdata; +}; + +#define log_ipv4ll_errno(ll, error, fmt, ...) \ + log_interface_full_errno(sd_ipv4ll_get_ifname(ll), LOG_DEBUG, error, "IPV4LL: " fmt, ##__VA_ARGS__) +#define log_ipv4ll(ll, fmt, ...) \ + log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__) + +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); + +static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) { + assert(ll); + + sd_ipv4acd_unref(ll->acd); + return mfree(ll); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free); + +int sd_ipv4ll_new(sd_ipv4ll **ret) { + _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL; + int r; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4ll, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = 1; + + r = sd_ipv4acd_new(&ll->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); + if (r < 0) + return r; + + *ret = TAKE_PTR(ll); + + return 0; +} + +int sd_ipv4ll_stop(sd_ipv4ll *ll) { + if (!ll) + return 0; + + return sd_ipv4acd_stop(ll->acd); +} + +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { + assert_return(ll, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + return sd_ipv4acd_set_ifindex(ll->acd, ifindex); +} + +int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) { + if (!ll) + return -EINVAL; + + return sd_ipv4acd_get_ifindex(ll->acd); +} + +const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + return sd_ipv4acd_get_ifname(ll->acd); +} + +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { + int r; + + assert_return(ll, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + r = sd_ipv4acd_set_mac(ll->acd, addr); + if (r < 0) + return r; + + ll->mac = *addr; + return 0; +} + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_detach_event(ll->acd); +} + +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_attach_event(ll->acd, event, priority); +} + +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->callback = cb; + ll->userdata = userdata; + + return 0; +} + +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) { + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + + if (ll->claimed_address == 0) + return -ENOENT; + + address->s_addr = ll->claimed_address; + + return 0; +} + +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) { + assert_return(ll, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + ll->seed.value = htole64(seed); + ll->seed_set = true; + + return 0; +} + +int sd_ipv4ll_is_running(sd_ipv4ll *ll) { + assert_return(ll, false); + + return sd_ipv4acd_is_running(ll->acd); +} + +static bool ipv4ll_address_is_valid(const struct in_addr *address) { + assert(address); + + if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address)) + return false; + + return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U); +} + +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) { + int r; + + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + assert_return(ipv4ll_address_is_valid(address), -EINVAL); + + r = sd_ipv4acd_set_address(ll->acd, address); + if (r < 0) + return r; + + ll->address = address->s_addr; + + return 0; +} + +#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) + +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + _cleanup_free_ char *address = NULL; + be32_t addr; + + assert(ll); + + do { + uint64_t h; + + h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); + + /* Increase the generation counter by one */ + ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); + + addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); + } while (addr == ll->address || + IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); + + (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address); + log_ipv4ll(ll, "Picked new IP address %s.", strna(address)); + + return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); +} + +#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) { + int r; + bool picked_address = false; + + assert_return(ll, -EINVAL); + assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); + + /* If no random seed is set, generate some from the MAC address */ + if (!ll->seed_set) + ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); + + if (reset_generation) + ll->seed.generation = 0; + + if (ll->address == 0) { + r = ipv4ll_pick_address(ll); + if (r < 0) + return r; + + picked_address = true; + } + + r = sd_ipv4acd_start(ll->acd, reset_generation); + if (r < 0) { + + /* We couldn't start? If so, let's forget the picked address again, the user might make a change and + * retry, and we want the new data to take effect when picking an address. */ + if (picked_address) + ll->address = 0; + + return r; + } + + return 1; +} + +int sd_ipv4ll_start(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + if (sd_ipv4ll_is_running(ll)) + return 0; + + return ipv4ll_start_internal(ll, true); +} + +int sd_ipv4ll_restart(sd_ipv4ll *ll) { + ll->address = 0; + + return ipv4ll_start_internal(ll, false); +} + +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); + + if (ll->callback) + ll->callback(ll, event, ll->userdata); +} + +void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + sd_ipv4ll *ll = userdata; + IPV4LL_DONT_DESTROY(ll); + int r; + + assert(acd); + assert(ll); + + switch (event) { + + case SD_IPV4ACD_EVENT_STOP: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); + ll->claimed_address = 0; + break; + + case SD_IPV4ACD_EVENT_BIND: + ll->claimed_address = ll->address; + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND); + break; + + case SD_IPV4ACD_EVENT_CONFLICT: + /* if an address was already bound we must call up to the + user to handle this, otherwise we just try again */ + if (ll->claimed_address != 0) { + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT); + + ll->claimed_address = 0; + } else { + r = sd_ipv4ll_restart(ll); + if (r < 0) + goto error; + } + + break; + + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + return; + +error: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); +} diff --git a/src/core/systemd/src/libsystemd-network/sd-lldp.c b/src/core/systemd/src/libsystemd-network/sd-lldp.c new file mode 100644 index 0000000..b12d8e2 --- /dev/null +++ b/src/core/systemd/src/libsystemd-network/sd-lldp.c @@ -0,0 +1,500 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "sd-lldp.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "event-util.h" +#include "fd-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "lldp-network.h" +#include "memory-util.h" +#include "socket-util.h" +#include "sort-util.h" +#include "string-table.h" + +#define LLDP_DEFAULT_NEIGHBORS_MAX 128U + +static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = { + [SD_LLDP_EVENT_ADDED] = "added", + [SD_LLDP_EVENT_REMOVED] = "removed", + [SD_LLDP_EVENT_UPDATED] = "updated", + [SD_LLDP_EVENT_REFRESHED] = "refreshed", +}; + +DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event); + +static void lldp_flush_neighbors(sd_lldp *lldp) { + assert(lldp); + + hashmap_clear(lldp->neighbor_by_id); +} + +static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) { + assert(lldp); + assert(event >= 0 && event < _SD_LLDP_EVENT_MAX); + + if (!lldp->callback) { + log_lldp("Received '%s' event.", lldp_event_to_string(event)); + return; + } + + log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event)); + lldp->callback(lldp, event, n, lldp->userdata); +} + +static int lldp_make_space(sd_lldp *lldp, size_t extra) { + usec_t t = USEC_INFINITY; + bool changed = false; + + assert(lldp); + + /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries + * are free. */ + + for (;;) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + break; + + sd_lldp_neighbor_ref(n); + + if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra)) + goto remove_one; + + if (t == USEC_INFINITY) + t = now(clock_boottime_or_monotonic()); + + if (n->until > t) + break; + + remove_one: + lldp_neighbor_unlink(n); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); + changed = true; + } + + return changed; +} + +static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + assert(lldp); + assert(n); + + /* Don't keep data with a zero TTL */ + if (n->ttl <= 0) + return false; + + /* Filter out data from the filter address */ + if (!ether_addr_is_null(&lldp->filter_address) && + ether_addr_equal(&lldp->filter_address, &n->source_address)) + return false; + + /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with + * no caps field set. */ + if (n->has_capabilities && + (n->enabled_capabilities & lldp->capability_mask) == 0) + return false; + + /* Keep everything else */ + return true; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor); + +static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL; + bool keep; + int r; + + assert(lldp); + assert(n); + assert(!n->lldp); + + keep = lldp_keep_neighbor(lldp, n); + + /* First retrieve the old entry for this MSAP */ + old = hashmap_get(lldp->neighbor_by_id, &n->id); + if (old) { + sd_lldp_neighbor_ref(old); + + if (!keep) { + lldp_neighbor_unlink(old); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + return 0; + } + + if (lldp_neighbor_equal(n, old)) { + /* Is this equal, then restart the TTL counter, but don't do anything else. */ + old->timestamp = n->timestamp; + lldp_start_timer(lldp, old); + lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); + return 0; + } + + /* Data changed, remove the old entry, and add a new one */ + lldp_neighbor_unlink(old); + + } else if (!keep) + return 0; + + /* Then, make room for at least one new neighbor */ + lldp_make_space(lldp, 1); + + r = hashmap_put(lldp->neighbor_by_id, &n->id, n); + if (r < 0) + goto finish; + + r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx); + if (r < 0) { + assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n); + goto finish; + } + + n->lldp = lldp; + + lldp_start_timer(lldp, n); + lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n); + + return 1; + +finish: + if (old) + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + + return r; +} + +static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) { + int r; + + assert(lldp); + assert(n); + + r = lldp_neighbor_parse(n); + if (r == -EBADMSG) /* Ignore bad messages */ + return 0; + if (r < 0) + return r; + + r = lldp_add_neighbor(lldp, n); + if (r < 0) { + log_lldp_errno(r, "Failed to add datagram. Ignoring."); + return 0; + } + + log_lldp("Successfully processed LLDP datagram."); + return 0; +} + +static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + ssize_t space, length; + sd_lldp *lldp = userdata; + struct timespec ts; + + assert(fd >= 0); + assert(lldp); + + space = next_datagram_size_fd(fd); + if (space < 0) + return log_lldp_errno(space, "Failed to determine datagram size to read: %m"); + + n = lldp_neighbor_new(space); + if (!n) + return -ENOMEM; + + length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); + if (length < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return log_lldp_errno(errno, "Failed to read LLDP datagram: %m"); + } + + if ((size_t) length != n->raw_size) { + log_lldp("Packet size mismatch."); + return -EINVAL; + } + + /* Try to get the timestamp of this packet if it is known */ + if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) + triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); + else + triple_timestamp_get(&n->timestamp); + + return lldp_handle_datagram(lldp, n); +} + +static void lldp_reset(sd_lldp *lldp) { + assert(lldp); + + (void) event_source_disable(lldp->timer_event_source); + lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); + lldp->fd = safe_close(lldp->fd); +} + +_public_ int sd_lldp_start(sd_lldp *lldp) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->event, -EINVAL); + assert_return(lldp->ifindex > 0, -EINVAL); + + if (lldp->fd >= 0) + return 0; + + assert(!lldp->io_event_source); + + lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex); + if (lldp->fd < 0) + return lldp->fd; + + r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); + + log_lldp("Started LLDP client"); + return 1; + +fail: + lldp_reset(lldp); + return r; +} + +_public_ int sd_lldp_stop(sd_lldp *lldp) { + if (!lldp) + return 0; + + if (lldp->fd < 0) + return 0; + + log_lldp("Stopping LLDP client"); + + lldp_reset(lldp); + lldp_flush_neighbors(lldp); + + return 1; +} + +_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + assert_return(!lldp->event, -EBUSY); + + if (event) + lldp->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp->event); + if (r < 0) + return r; + } + + lldp->event_priority = priority; + + return 0; +} + +_public_ int sd_lldp_detach_event(sd_lldp *lldp) { + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->event = sd_event_unref(lldp->event); + return 0; +} + +_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { + assert_return(lldp, NULL); + + return lldp->event; +} + +_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { + assert_return(lldp, -EINVAL); + + lldp->callback = cb; + lldp->userdata = userdata; + + return 0; +} + +_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { + assert_return(lldp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->ifindex = ifindex; + return 0; +} + +static sd_lldp* lldp_free(sd_lldp *lldp) { + assert(lldp); + + lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); + + lldp_reset(lldp); + sd_lldp_detach_event(lldp); + lldp_flush_neighbors(lldp); + + hashmap_free(lldp->neighbor_by_id); + prioq_free(lldp->neighbor_by_expiry); + return mfree(lldp); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free); + +_public_ int sd_lldp_new(sd_lldp **ret) { + _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; + int r; + + assert_return(ret, -EINVAL); + + lldp = new(sd_lldp, 1); + if (!lldp) + return -ENOMEM; + + *lldp = (sd_lldp) { + .n_ref = 1, + .fd = -1, + .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX, + .capability_mask = (uint16_t) -1, + }; + + lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops); + if (!lldp->neighbor_by_id) + return -ENOMEM; + + r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func); + if (r < 0) + return r; + + *ret = TAKE_PTR(lldp); + + return 0; +} + +static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) { + return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id); +} + +static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { + sd_lldp *lldp = userdata; + int r; + + r = lldp_make_space(lldp, 0); + if (r < 0) + return log_lldp_errno(r, "Failed to make space: %m"); + + r = lldp_start_timer(lldp, NULL); + if (r < 0) + return log_lldp_errno(r, "Failed to restart timer: %m"); + + return 0; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) { + sd_lldp_neighbor *n; + + assert(lldp); + + if (neighbor) + lldp_neighbor_start_ttl(neighbor); + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + return event_source_disable(lldp->timer_event_source); + + if (!lldp->event) + return 0; + + return event_reset_time(lldp->event, &lldp->timer_event_source, + clock_boottime_or_monotonic(), + n->until, 0, + on_timer_event, lldp, + lldp->event_priority, "lldp-timer", true); +} + +_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) { + sd_lldp_neighbor **l = NULL, *n; + int k = 0, r; + + assert_return(lldp, -EINVAL); + assert_return(ret, -EINVAL); + + if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */ + *ret = NULL; + return 0; + } + + l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id)); + if (!l) + return -ENOMEM; + + r = lldp_start_timer(lldp, NULL); + if (r < 0) { + free(l); + return r; + } + + HASHMAP_FOREACH(n, lldp->neighbor_by_id) + l[k++] = sd_lldp_neighbor_ref(n); + + assert((size_t) k == hashmap_size(lldp->neighbor_by_id)); + + /* Return things in a stable order */ + typesafe_qsort(l, k, neighbor_compare_func); + *ret = l; + + return k; +} + +_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) { + assert_return(lldp, -EINVAL); + assert_return(m > 0, -EINVAL); + + lldp->neighbors_max = m; + lldp_make_space(lldp, 0); + + return 0; +} + +_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) { + assert_return(lldp, -EINVAL); + assert_return(mask != 0, -EINVAL); + + lldp->capability_mask = mask; + + return 0; +} + +_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) { + assert_return(lldp, -EINVAL); + + /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so + * that our own can be filtered out here. */ + + if (addr) + lldp->filter_address = *addr; + else + zero(lldp->filter_address); + + return 0; +} diff --git a/src/core/systemd/src/libsystemd/sd-event/event-source.h b/src/core/systemd/src/libsystemd/sd-event/event-source.h new file mode 100644 index 0000000..f0d2a1b --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-event/event-source.h @@ -0,0 +1,220 @@ +#pragma once +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" + +#include "fs-util.h" +#include "hashmap.h" +#include "list.h" +#include "prioq.h" +#include "ratelimit.h" + +typedef enum EventSourceType { + SOURCE_IO, + SOURCE_TIME_REALTIME, + SOURCE_TIME_BOOTTIME, + SOURCE_TIME_MONOTONIC, + SOURCE_TIME_REALTIME_ALARM, + SOURCE_TIME_BOOTTIME_ALARM, + SOURCE_SIGNAL, + SOURCE_CHILD, + SOURCE_DEFER, + SOURCE_POST, + SOURCE_EXIT, + SOURCE_WATCHDOG, + SOURCE_INOTIFY, + _SOURCE_EVENT_SOURCE_TYPE_MAX, + _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 +} EventSourceType; + +/* All objects we use in epoll events start with this value, so that + * we know how to dispatch it */ +typedef enum WakeupType { + WAKEUP_NONE, + WAKEUP_EVENT_SOURCE, /* either I/O or pidfd wakeup */ + WAKEUP_CLOCK_DATA, + WAKEUP_SIGNAL_DATA, + WAKEUP_INOTIFY_DATA, + _WAKEUP_TYPE_MAX, + _WAKEUP_TYPE_INVALID = -1, +} WakeupType; + +struct inode_data; + +struct sd_event_source { + WakeupType wakeup; + + unsigned n_ref; + + sd_event *event; + void *userdata; + sd_event_handler_t prepare; + + char *description; + + EventSourceType type:5; + signed int enabled:3; + bool pending:1; + bool dispatching:1; + bool floating:1; + bool exit_on_failure:1; + bool ratelimited:1; + + int64_t priority; + unsigned pending_index; + unsigned prepare_index; + uint64_t pending_iteration; + uint64_t prepare_iteration; + + sd_event_destroy_t destroy_callback; + + LIST_FIELDS(sd_event_source, sources); + + RateLimit rate_limit; + + /* These are primarily fields relevant for time event sources, but since any event source can + * effectively become one when rate-limited, this is part of the common fields. */ + unsigned earliest_index; + unsigned latest_index; + + union { + struct { + sd_event_io_handler_t callback; + int fd; + uint32_t events; + uint32_t revents; + bool registered:1; + bool owned:1; + } io; + struct { + sd_event_time_handler_t callback; + usec_t next, accuracy; + } time; + struct { + sd_event_signal_handler_t callback; + struct signalfd_siginfo siginfo; + int sig; + } signal; + struct { + sd_event_child_handler_t callback; + siginfo_t siginfo; + pid_t pid; + int options; + int pidfd; + bool registered:1; /* whether the pidfd is registered in the epoll */ + bool pidfd_owned:1; /* close pidfd when event source is freed */ + bool process_owned:1; /* kill+reap process when event source is freed */ + bool exited:1; /* true if process exited (i.e. if there's value in SIGKILLing it if we want to get rid of it) */ + bool waited:1; /* true if process was waited for (i.e. if there's value in waitid(P_PID)'ing it if we want to get rid of it) */ + } child; + struct { + sd_event_handler_t callback; + } defer; + struct { + sd_event_handler_t callback; + } post; + struct { + sd_event_handler_t callback; + unsigned prioq_index; + } exit; + struct { + sd_event_inotify_handler_t callback; + uint32_t mask; + struct inode_data *inode_data; + LIST_FIELDS(sd_event_source, by_inode_data); + } inotify; + }; +}; + +struct clock_data { + WakeupType wakeup; + int fd; + + /* For all clocks we maintain two priority queues each, one + * ordered for the earliest times the events may be + * dispatched, and one ordered by the latest times they must + * have been dispatched. The range between the top entries in + * the two prioqs is the time window we can freely schedule + * wakeups in */ + + Prioq *earliest; + Prioq *latest; + usec_t next; + + bool needs_rearm:1; +}; + +struct signal_data { + WakeupType wakeup; + + /* For each priority we maintain one signal fd, so that we + * only have to dequeue a single event per priority at a + * time. */ + + int fd; + int64_t priority; + sigset_t sigset; + sd_event_source *current; +}; + +/* A structure listing all event sources currently watching a specific inode */ +struct inode_data { + /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */ + ino_t ino; + dev_t dev; + + /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can + * rearrange the priority still until then, as we need the original inode to change the priority as we need to + * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the + * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of + * the sd-event object, so that it is efficient to close everything, before entering the next event loop + * iteration. */ + int fd; + + /* The inotify "watch descriptor" */ + int wd; + + /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has + * most recently been set on the watch descriptor. */ + uint32_t combined_mask; + + /* All event sources subscribed to this inode */ + LIST_HEAD(sd_event_source, event_sources); + + /* The inotify object we watch this inode with */ + struct inotify_data *inotify_data; + + /* A linked list of all inode data objects with fds to close (see above) */ + LIST_FIELDS(struct inode_data, to_close); +}; + +/* A structure encapsulating an inotify fd */ +struct inotify_data { + WakeupType wakeup; + + /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at + * a time */ + + int fd; + int64_t priority; + + Hashmap *inodes; /* The inode_data structures keyed by dev+ino */ + Hashmap *wd; /* The inode_data structures keyed by the watch descriptor for each */ + + /* The buffer we read inotify events into */ + union inotify_event_buffer buffer; + size_t buffer_filled; /* fill level of the buffer */ + + /* How many event sources are currently marked pending for this inotify. We won't read new events off the + * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing + * the events locally if they can't be coalesced). */ + unsigned n_pending; + + /* A linked list of all inotify objects with data already read, that still need processing. We keep this list + * to make it efficient to figure out what inotify objects to process data on next. */ + LIST_FIELDS(struct inotify_data, buffered); +}; diff --git a/src/core/systemd/src/libsystemd/sd-event/event-util.c b/src/core/systemd/src/libsystemd/sd-event/event-util.c new file mode 100644 index 0000000..7cc55be --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-event/event-util.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include + +#include "event-source.h" +#include "event-util.h" +#include "log.h" +#include "string-util.h" + +int event_reset_time( + sd_event *e, + sd_event_source **s, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata, + int64_t priority, + const char *description, + bool force_reset) { + + bool created = false; + int enabled, r; + clockid_t c; + + assert(e); + assert(s); + + if (*s) { + if (!force_reset) { + r = sd_event_source_get_enabled(*s, &enabled); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to query whether event source \"%s\" is enabled or not: %m", + strna((*s)->description ?: description)); + + if (enabled != SD_EVENT_OFF) + return 0; + } + + r = sd_event_source_get_time_clock(*s, &c); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to get clock id of event source \"%s\": %m", strna((*s)->description ?: description)); + + if (c != clock) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "sd-event: Current clock id %i of event source \"%s\" is different from specified one %i.", + (int)c, + strna((*s)->description ? : description), + (int)clock); + + r = sd_event_source_set_time(*s, usec); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set time for event source \"%s\": %m", strna((*s)->description ?: description)); + + r = sd_event_source_set_time_accuracy(*s, accuracy); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set accuracy for event source \"%s\": %m", strna((*s)->description ?: description)); + + /* callback function is not updated, as we do not have sd_event_source_set_time_callback(). */ + + (void) sd_event_source_set_userdata(*s, userdata); + + r = sd_event_source_set_enabled(*s, SD_EVENT_ONESHOT); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to enable event source \"%s\": %m", strna((*s)->description ?: description)); + } else { + r = sd_event_add_time(e, s, clock, usec, accuracy, callback, userdata); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to create timer event \"%s\": %m", strna(description)); + + created = true; + } + + r = sd_event_source_set_priority(*s, priority); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set priority for event source \"%s\": %m", strna((*s)->description ?: description)); + + if (description) { + r = sd_event_source_set_description(*s, description); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set description for event source \"%s\": %m", description); + } + + return created; +} + +int event_source_disable(sd_event_source *s) { + if (!s) + return 0; + + return sd_event_source_set_enabled(s, SD_EVENT_OFF); +} + +int event_source_is_enabled(sd_event_source *s) { + if (!s) + return false; + + return sd_event_source_get_enabled(s, NULL); +} diff --git a/src/core/systemd/src/libsystemd/sd-event/event-util.h b/src/core/systemd/src/libsystemd/sd-event/event-util.h new file mode 100644 index 0000000..c8f97bc --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-event/event-util.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-event.h" + +int event_reset_time(sd_event *e, sd_event_source **s, + clockid_t clock, uint64_t usec, uint64_t accuracy, + sd_event_time_handler_t callback, void *userdata, + int64_t priority, const char *description, bool force_reset); +int event_source_disable(sd_event_source *s); +int event_source_is_enabled(sd_event_source *s); diff --git a/src/core/systemd/src/libsystemd/sd-event/sd-event.c b/src/core/systemd/src/libsystemd/sd-event/sd-event.c new file mode 100644 index 0000000..dda4be0 --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-event/sd-event.c @@ -0,0 +1,4329 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "sd-daemon.h" +#include "sd-event.h" +#include "sd-id128.h" + +#include "alloc-util.h" +#include "env-util.h" +#include "event-source.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "list.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_syscall.h" +#include "prioq.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "time-util.h" + +#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) + +static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) { + /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */ + return s && + s->type == SOURCE_CHILD && + s->child.pidfd >= 0 && + s->child.options == WEXITED; +} + +static bool event_source_is_online(sd_event_source *s) { + assert(s); + return s->enabled != SD_EVENT_OFF && !s->ratelimited; +} + +static bool event_source_is_offline(sd_event_source *s) { + assert(s); + return s->enabled == SD_EVENT_OFF || s->ratelimited; +} + +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", + [SOURCE_INOTIFY] = "inotify", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + +#define EVENT_SOURCE_IS_TIME(t) \ + IN_SET((t), \ + SOURCE_TIME_REALTIME, \ + SOURCE_TIME_BOOTTIME, \ + SOURCE_TIME_MONOTONIC, \ + SOURCE_TIME_REALTIME_ALARM, \ + SOURCE_TIME_BOOTTIME_ALARM) + +#define EVENT_SOURCE_CAN_RATE_LIMIT(t) \ + IN_SET((t), \ + SOURCE_IO, \ + SOURCE_TIME_REALTIME, \ + SOURCE_TIME_BOOTTIME, \ + SOURCE_TIME_MONOTONIC, \ + SOURCE_TIME_REALTIME_ALARM, \ + SOURCE_TIME_BOOTTIME_ALARM, \ + SOURCE_SIGNAL, \ + SOURCE_DEFER, \ + SOURCE_INOTIFY) + +struct sd_event { + unsigned n_ref; + + int epoll_fd; + int watchdog_fd; + + Prioq *pending; + Prioq *prepare; + + /* timerfd_create() only supports these five clocks so far. We + * can add support for more clocks when the kernel learns to + * deal with them, too. */ + struct clock_data realtime; + struct clock_data boottime; + struct clock_data monotonic; + struct clock_data realtime_alarm; + struct clock_data boottime_alarm; + + usec_t perturb; + + sd_event_source **signal_sources; /* indexed by signal number */ + Hashmap *signal_data; /* indexed by priority */ + + Hashmap *child_sources; + unsigned n_online_child_sources; + + Set *post_sources; + + Prioq *exit; + + Hashmap *inotify_data; /* indexed by priority */ + + /* A list of inode structures that still have an fd open, that we need to close before the next loop iteration */ + LIST_HEAD(struct inode_data, inode_data_to_close); + + /* A list of inotify objects that already have events buffered which aren't processed yet */ + LIST_HEAD(struct inotify_data, inotify_data_buffered); + + pid_t original_pid; + + uint64_t iteration; + triple_timestamp timestamp; + int state; + + bool exit_requested:1; + bool need_process_child:1; + bool watchdog:1; + bool profile_delays:1; + + int exit_code; + + pid_t tid; + sd_event **default_event_ptr; + + usec_t watchdog_last, watchdog_period; + + unsigned n_sources; + + struct epoll_event *event_queue; + size_t event_queue_allocated; + + LIST_HEAD(sd_event_source, sources); + + usec_t last_run_usec, last_log_usec; + unsigned delays[sizeof(usec_t) * 8]; +}; + +static thread_local sd_event *default_event = NULL; + +static void source_disconnect(sd_event_source *s); +static void event_gc_inode_data(sd_event *e, struct inode_data *d); + +static sd_event *event_resolve(sd_event *e) { + return e == SD_EVENT_DEFAULT ? default_event : e; +} + +static int pending_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + int r; + + assert(x->pending); + assert(y->pending); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Non rate-limited ones first. */ + r = CMP(!!x->ratelimited, !!y->ratelimited); + if (r != 0) + return r; + + /* Lower priority values first */ + r = CMP(x->priority, y->priority); + if (r != 0) + return r; + + /* Older entries first */ + return CMP(x->pending_iteration, y->pending_iteration); +} + +static int prepare_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + int r; + + assert(x->prepare); + assert(y->prepare); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Non rate-limited ones first. */ + r = CMP(!!x->ratelimited, !!y->ratelimited); + if (r != 0) + return r; + + /* Move most recently prepared ones last, so that we can stop + * preparing as soon as we hit one that has already been + * prepared in the current iteration */ + r = CMP(x->prepare_iteration, y->prepare_iteration); + if (r != 0) + return r; + + /* Lower priority values first */ + return CMP(x->priority, y->priority); +} + +static usec_t time_event_source_next(const sd_event_source *s) { + assert(s); + + /* We have two kinds of event sources that have elapsation times associated with them: the actual + * time based ones and the ones for which a ratelimit can be in effect (where we want to be notified + * once the ratelimit time window ends). Let's return the next elapsing time depending on what we are + * looking at here. */ + + if (s->ratelimited) { /* If rate-limited the next elapsation is when the ratelimit time window ends */ + assert(s->rate_limit.begin != 0); + assert(s->rate_limit.interval != 0); + return usec_add(s->rate_limit.begin, s->rate_limit.interval); + } + + /* Otherwise this must be a time event source, if not ratelimited */ + if (EVENT_SOURCE_IS_TIME(s->type)) + return s->time.next; + + return USEC_INFINITY; +} + +static int earliest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + return CMP(time_event_source_next(x), time_event_source_next(y)); +} + +static usec_t time_event_source_latest(const sd_event_source *s) { + assert(s); + + if (s->ratelimited) { /* For ratelimited stuff the earliest and the latest time shall actually be the + * same, as we should avoid adding additional inaccuracy on an inaccuracy time + * window */ + assert(s->rate_limit.begin != 0); + assert(s->rate_limit.interval != 0); + return usec_add(s->rate_limit.begin, s->rate_limit.interval); + } + + /* Must be a time event source, if not ratelimited */ + if (EVENT_SOURCE_IS_TIME(s->type)) + return usec_add(s->time.next, s->time.accuracy); + + return USEC_INFINITY; +} + +static int latest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + return CMP(time_event_source_latest(x), time_event_source_latest(y)); +} + +static int exit_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->type == SOURCE_EXIT); + assert(y->type == SOURCE_EXIT); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + return CMP(x->priority, y->priority); +} + +static void free_clock_data(struct clock_data *d) { + assert(d); + assert(d->wakeup == WAKEUP_CLOCK_DATA); + + safe_close(d->fd); + prioq_free(d->earliest); + prioq_free(d->latest); +} + +static sd_event *event_free(sd_event *e) { + sd_event_source *s; + + assert(e); + + while ((s = e->sources)) { + assert(s->floating); + source_disconnect(s); + sd_event_source_unref(s); + } + + assert(e->n_sources == 0); + + if (e->default_event_ptr) + *(e->default_event_ptr) = NULL; + + safe_close(e->epoll_fd); + safe_close(e->watchdog_fd); + + free_clock_data(&e->realtime); + free_clock_data(&e->boottime); + free_clock_data(&e->monotonic); + free_clock_data(&e->realtime_alarm); + free_clock_data(&e->boottime_alarm); + + prioq_free(e->pending); + prioq_free(e->prepare); + prioq_free(e->exit); + + free(e->signal_sources); + hashmap_free(e->signal_data); + + hashmap_free(e->inotify_data); + + hashmap_free(e->child_sources); + set_free(e->post_sources); + + free(e->event_queue); + + return mfree(e); +} + +_public_ int sd_event_new(sd_event** ret) { + sd_event *e; + int r; + + assert_return(ret, -EINVAL); + + e = new(sd_event, 1); + if (!e) + return -ENOMEM; + + *e = (sd_event) { + .n_ref = 1, + .epoll_fd = -1, + .watchdog_fd = -1, + .realtime.wakeup = WAKEUP_CLOCK_DATA, + .realtime.fd = -1, + .realtime.next = USEC_INFINITY, + .boottime.wakeup = WAKEUP_CLOCK_DATA, + .boottime.fd = -1, + .boottime.next = USEC_INFINITY, + .monotonic.wakeup = WAKEUP_CLOCK_DATA, + .monotonic.fd = -1, + .monotonic.next = USEC_INFINITY, + .realtime_alarm.wakeup = WAKEUP_CLOCK_DATA, + .realtime_alarm.fd = -1, + .realtime_alarm.next = USEC_INFINITY, + .boottime_alarm.wakeup = WAKEUP_CLOCK_DATA, + .boottime_alarm.fd = -1, + .boottime_alarm.next = USEC_INFINITY, + .perturb = USEC_INFINITY, + .original_pid = getpid_cached(), + }; + + r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); + if (r < 0) + goto fail; + + e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (e->epoll_fd < 0) { + r = -errno; + goto fail; + } + + e->epoll_fd = fd_move_above_stdio(e->epoll_fd); + + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + + *ret = e; + return 0; + +fail: + event_free(e); + return r; +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free); + +_public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) { + if (s) + (void) sd_event_source_set_enabled(s, SD_EVENT_OFF); + return sd_event_source_unref(s); +} + +static bool event_pid_changed(sd_event *e) { + assert(e); + + /* We don't support people creating an event loop and keeping + * it around over a fork(). Let's complain. */ + + return e->original_pid != getpid_cached(); +} + +static void source_io_unregister(sd_event_source *s) { + assert(s); + assert(s->type == SOURCE_IO); + + if (event_pid_changed(s->event)) + return; + + if (!s->io.registered) + return; + + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL) < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->io.registered = false; +} + +static int source_io_register( + sd_event_source *s, + int enabled, + uint32_t events) { + + assert(s); + assert(s->type == SOURCE_IO); + assert(enabled != SD_EVENT_OFF); + + struct epoll_event ev = { + .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0), + .data.ptr = s, + }; + + if (epoll_ctl(s->event->epoll_fd, + s->io.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->io.fd, &ev) < 0) + return -errno; + + s->io.registered = true; + + return 0; +} + +static void source_child_pidfd_unregister(sd_event_source *s) { + assert(s); + assert(s->type == SOURCE_CHILD); + + if (event_pid_changed(s->event)) + return; + + if (!s->child.registered) + return; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->child.pidfd, NULL) < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->child.registered = false; +} + +static int source_child_pidfd_register(sd_event_source *s, int enabled) { + assert(s); + assert(s->type == SOURCE_CHILD); + assert(enabled != SD_EVENT_OFF); + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + struct epoll_event ev = { + .events = EPOLLIN | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0), + .data.ptr = s, + }; + + if (epoll_ctl(s->event->epoll_fd, + s->child.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->child.pidfd, &ev) < 0) + return -errno; + } + + s->child.registered = true; + return 0; +} + +static clockid_t event_source_type_to_clock(EventSourceType t) { + + switch (t) { + + case SOURCE_TIME_REALTIME: + return CLOCK_REALTIME; + + case SOURCE_TIME_BOOTTIME: + return CLOCK_BOOTTIME; + + case SOURCE_TIME_MONOTONIC: + return CLOCK_MONOTONIC; + + case SOURCE_TIME_REALTIME_ALARM: + return CLOCK_REALTIME_ALARM; + + case SOURCE_TIME_BOOTTIME_ALARM: + return CLOCK_BOOTTIME_ALARM; + + default: + return (clockid_t) -1; + } +} + +static EventSourceType clock_to_event_source_type(clockid_t clock) { + + switch (clock) { + + case CLOCK_REALTIME: + return SOURCE_TIME_REALTIME; + + case CLOCK_BOOTTIME: + return SOURCE_TIME_BOOTTIME; + + case CLOCK_MONOTONIC: + return SOURCE_TIME_MONOTONIC; + + case CLOCK_REALTIME_ALARM: + return SOURCE_TIME_REALTIME_ALARM; + + case CLOCK_BOOTTIME_ALARM: + return SOURCE_TIME_BOOTTIME_ALARM; + + default: + return _SOURCE_EVENT_SOURCE_TYPE_INVALID; + } +} + +static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { + assert(e); + + switch (t) { + + case SOURCE_TIME_REALTIME: + return &e->realtime; + + case SOURCE_TIME_BOOTTIME: + return &e->boottime; + + case SOURCE_TIME_MONOTONIC: + return &e->monotonic; + + case SOURCE_TIME_REALTIME_ALARM: + return &e->realtime_alarm; + + case SOURCE_TIME_BOOTTIME_ALARM: + return &e->boottime_alarm; + + default: + return NULL; + } +} + +static void event_free_signal_data(sd_event *e, struct signal_data *d) { + assert(e); + + if (!d) + return; + + hashmap_remove(e->signal_data, &d->priority); + safe_close(d->fd); + free(d); +} + +static int event_make_signal_data( + sd_event *e, + int sig, + struct signal_data **ret) { + + struct signal_data *d; + bool added = false; + sigset_t ss_copy; + int64_t priority; + int r; + + assert(e); + + if (event_pid_changed(e)) + return -ECHILD; + + if (e->signal_sources && e->signal_sources[sig]) + priority = e->signal_sources[sig]->priority; + else + priority = SD_EVENT_PRIORITY_NORMAL; + + d = hashmap_get(e->signal_data, &priority); + if (d) { + if (sigismember(&d->sigset, sig) > 0) { + if (ret) + *ret = d; + return 0; + } + } else { + r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops); + if (r < 0) + return r; + + d = new(struct signal_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct signal_data) { + .wakeup = WAKEUP_SIGNAL_DATA, + .fd = -1, + .priority = priority, + }; + + r = hashmap_put(e->signal_data, &d->priority, d); + if (r < 0) { + free(d); + return r; + } + + added = true; + } + + ss_copy = d->sigset; + assert_se(sigaddset(&ss_copy, sig) >= 0); + + r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); + if (r < 0) { + r = -errno; + goto fail; + } + + d->sigset = ss_copy; + + if (d->fd >= 0) { + if (ret) + *ret = d; + return 0; + } + + d->fd = fd_move_above_stdio(r); + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) { + r = -errno; + goto fail; + } + + if (ret) + *ret = d; + + return 0; + +fail: + if (added) + event_free_signal_data(e, d); + + return r; +} + +static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { + assert(e); + assert(d); + + /* Turns off the specified signal in the signal data + * object. If the signal mask of the object becomes empty that + * way removes it. */ + + if (sigismember(&d->sigset, sig) == 0) + return; + + assert_se(sigdelset(&d->sigset, sig) >= 0); + + if (sigisemptyset(&d->sigset)) { + /* If all the mask is all-zero we can get rid of the structure */ + event_free_signal_data(e, d); + return; + } + + assert(d->fd >= 0); + + if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) + log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); +} + +static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { + struct signal_data *d; + static const int64_t zero_priority = 0; + + assert(e); + + /* Rechecks if the specified signal is still something we are interested in. If not, we'll unmask it, + * and possibly drop the signalfd for it. */ + + if (sig == SIGCHLD && + e->n_online_child_sources > 0) + return; + + if (e->signal_sources && + e->signal_sources[sig] && + event_source_is_online(e->signal_sources[sig])) + return; + + /* + * The specified signal might be enabled in three different queues: + * + * 1) the one that belongs to the priority passed (if it is non-NULL) + * 2) the one that belongs to the priority of the event source of the signal (if there is one) + * 3) the 0 priority (to cover the SIGCHLD case) + * + * Hence, let's remove it from all three here. + */ + + if (priority) { + d = hashmap_get(e->signal_data, priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + if (e->signal_sources && e->signal_sources[sig]) { + d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + d = hashmap_get(e->signal_data, &zero_priority); + if (d) + event_unmask_signal_data(e, d, sig); +} + +static void event_source_pp_prioq_reshuffle(sd_event_source *s) { + assert(s); + + /* Reshuffles the pending + prepare prioqs. Called whenever the dispatch order changes, i.e. when + * they are enabled/disabled or marked pending and such. */ + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); +} + +static void event_source_time_prioq_reshuffle(sd_event_source *s) { + struct clock_data *d; + + assert(s); + + /* Called whenever the event source's timer ordering properties changed, i.e. time, accuracy, + * pending, enable state. Makes sure the two prioq's are ordered properly again. */ + + if (s->ratelimited) + d = &s->event->monotonic; + else { + assert(EVENT_SOURCE_IS_TIME(s->type)); + assert_se(d = event_get_clock_data(s->event, s->type)); + } + + prioq_reshuffle(d->earliest, s, &s->earliest_index); + prioq_reshuffle(d->latest, s, &s->latest_index); + d->needs_rearm = true; +} + +static void event_source_time_prioq_remove( + sd_event_source *s, + struct clock_data *d) { + + assert(s); + assert(d); + + prioq_remove(d->earliest, s, &s->earliest_index); + prioq_remove(d->latest, s, &s->latest_index); + s->earliest_index = s->latest_index = PRIOQ_IDX_NULL; + d->needs_rearm = true; +} + +static void source_disconnect(sd_event_source *s) { + sd_event *event; + + assert(s); + + if (!s->event) + return; + + assert(s->event->n_sources > 0); + + switch (s->type) { + + case SOURCE_IO: + if (s->io.fd >= 0) + source_io_unregister(s); + + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + /* Only remove this event source from the time event source here if it is not ratelimited. If + * it is ratelimited, we'll remove it below, separately. Why? Because the clock used might + * differ: ratelimiting always uses CLOCK_MONOTONIC, but timer events might use any clock */ + + if (!s->ratelimited) { + struct clock_data *d; + assert_se(d = event_get_clock_data(s->event, s->type)); + event_source_time_prioq_remove(s, d); + } + + break; + + case SOURCE_SIGNAL: + if (s->signal.sig > 0) { + + if (s->event->signal_sources) + s->event->signal_sources[s->signal.sig] = NULL; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + } + + break; + + case SOURCE_CHILD: + if (s->child.pid > 0) { + if (event_source_is_online(s)) { + assert(s->event->n_online_child_sources > 0); + s->event->n_online_child_sources--; + } + + (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); + } + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + source_child_pidfd_unregister(s); + else + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + + break; + + case SOURCE_DEFER: + /* nothing */ + break; + + case SOURCE_POST: + set_remove(s->event->post_sources, s); + break; + + case SOURCE_EXIT: + prioq_remove(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_INOTIFY: { + struct inode_data *inode_data; + + inode_data = s->inotify.inode_data; + if (inode_data) { + struct inotify_data *inotify_data; + assert_se(inotify_data = inode_data->inotify_data); + + /* Detach this event source from the inode object */ + LIST_REMOVE(inotify.by_inode_data, inode_data->event_sources, s); + s->inotify.inode_data = NULL; + + if (s->pending) { + assert(inotify_data->n_pending > 0); + inotify_data->n_pending--; + } + + /* Note that we don't reduce the inotify mask for the watch descriptor here if the inode is + * continued to being watched. That's because inotify doesn't really have an API for that: we + * can only change watch masks with access to the original inode either by fd or by path. But + * paths aren't stable, and keeping an O_PATH fd open all the time would mean wasting an fd + * continuously and keeping the mount busy which we can't really do. We could reconstruct the + * original inode from /proc/self/fdinfo/$INOTIFY_FD (as all watch descriptors are listed + * there), but given the need for open_by_handle_at() which is privileged and not universally + * available this would be quite an incomplete solution. Hence we go the other way, leave the + * mask set, even if it is not minimized now, and ignore all events we aren't interested in + * anymore after reception. Yes, this sucks, but … Linux … */ + + /* Maybe release the inode data (and its inotify) */ + event_gc_inode_data(s->event, inode_data); + } + + break; + } + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + if (s->pending) + prioq_remove(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_remove(s->event->prepare, s, &s->prepare_index); + + if (s->ratelimited) + event_source_time_prioq_remove(s, &s->event->monotonic); + + event = TAKE_PTR(s->event); + LIST_REMOVE(sources, event->sources, s); + event->n_sources--; + + /* Note that we don't invalidate the type here, since we still need it in order to close the fd or + * pidfd associated with this event source, which we'll do only on source_free(). */ + + if (!s->floating) + sd_event_unref(event); +} + +static void source_free(sd_event_source *s) { + assert(s); + + source_disconnect(s); + + if (s->type == SOURCE_IO && s->io.owned) + s->io.fd = safe_close(s->io.fd); + + if (s->type == SOURCE_CHILD) { + /* Eventually the kernel will do this automatically for us, but for now let's emulate this (unreliably) in userspace. */ + + if (s->child.process_owned) { + + if (!s->child.exited) { + bool sent = false; + + if (s->child.pidfd >= 0) { + if (pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0) < 0) { + if (errno == ESRCH) /* Already dead */ + sent = true; + else if (!ERRNO_IS_NOT_SUPPORTED(errno)) + log_debug_errno(errno, "Failed to kill process " PID_FMT " via pidfd_send_signal(), re-trying via kill(): %m", + s->child.pid); + } else + sent = true; + } + + if (!sent) + if (kill(s->child.pid, SIGKILL) < 0) + if (errno != ESRCH) /* Already dead */ + log_debug_errno(errno, "Failed to kill process " PID_FMT " via kill(), ignoring: %m", + s->child.pid); + } + + if (!s->child.waited) { + siginfo_t si = {}; + + /* Reap the child if we can */ + (void) waitid(P_PID, s->child.pid, &si, WEXITED); + } + } + + if (s->child.pidfd_owned) + s->child.pidfd = safe_close(s->child.pidfd); + } + + if (s->destroy_callback) + s->destroy_callback(s->userdata); + + free(s->description); + free(s); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event_source*, source_free); + +static int source_set_pending(sd_event_source *s, bool b) { + int r; + + assert(s); + assert(s->type != SOURCE_EXIT); + + if (s->pending == b) + return 0; + + s->pending = b; + + if (b) { + s->pending_iteration = s->event->iteration; + + r = prioq_put(s->event->pending, s, &s->pending_index); + if (r < 0) { + s->pending = false; + return r; + } + } else + assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); + + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_reshuffle(s); + + if (s->type == SOURCE_SIGNAL && !b) { + struct signal_data *d; + + d = hashmap_get(s->event->signal_data, &s->priority); + if (d && d->current == s) + d->current = NULL; + } + + if (s->type == SOURCE_INOTIFY) { + + assert(s->inotify.inode_data); + assert(s->inotify.inode_data->inotify_data); + + if (b) + s->inotify.inode_data->inotify_data->n_pending ++; + else { + assert(s->inotify.inode_data->inotify_data->n_pending > 0); + s->inotify.inode_data->inotify_data->n_pending --; + } + } + + return 0; +} + +static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { + sd_event_source *s; + + assert(e); + + s = new(sd_event_source, 1); + if (!s) + return NULL; + + *s = (struct sd_event_source) { + .n_ref = 1, + .event = e, + .floating = floating, + .type = type, + .pending_index = PRIOQ_IDX_NULL, + .prepare_index = PRIOQ_IDX_NULL, + }; + + if (!floating) + sd_event_ref(e); + + LIST_PREPEND(sources, e->sources, s); + e->n_sources++; + + return s; +} + +static int io_exit_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_io( + sd_event *e, + sd_event_source **ret, + int fd, + uint32_t events, + sd_event_io_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(fd >= 0, -EBADF); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = io_exit_callback; + + s = source_new(e, !ret, SOURCE_IO); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->io.fd = fd; + s->io.events = events; + s->io.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static void initialize_perturb(sd_event *e) { + sd_id128_t bootid = {}; + + /* When we sleep for longer, we try to realign the wakeup to + the same time within each minute/second/250ms, so that + events all across the system can be coalesced into a single + CPU wakeup. However, let's take some system-specific + randomness for this value, so that in a network of systems + with synced clocks timer events are distributed a + bit. Here, we calculate a perturbation usec offset from the + boot ID. */ + + if (_likely_(e->perturb != USEC_INFINITY)) + return; + + if (sd_id128_get_boot(&bootid) >= 0) + e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; +} + +static int event_setup_timer_fd( + sd_event *e, + struct clock_data *d, + clockid_t clock) { + + assert(e); + assert(d); + + if (_likely_(d->fd >= 0)) + return 0; + + _cleanup_close_ int fd = -1; + + fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + return -errno; + + fd = fd_move_above_stdio(fd); + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + return -errno; + + d->fd = TAKE_FD(fd); + return 0; +} + +static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +static int setup_clock_data(sd_event *e, struct clock_data *d, clockid_t clock) { + int r; + + assert(d); + + if (d->fd < 0) { + r = event_setup_timer_fd(e, d, clock); + if (r < 0) + return r; + } + + r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); + if (r < 0) + return r; + + return 0; +} + +static int event_source_time_prioq_put( + sd_event_source *s, + struct clock_data *d) { + + int r; + + assert(s); + assert(d); + + r = prioq_put(d->earliest, s, &s->earliest_index); + if (r < 0) + return r; + + r = prioq_put(d->latest, s, &s->latest_index); + if (r < 0) { + assert_se(prioq_remove(d->earliest, s, &s->earliest_index) > 0); + s->earliest_index = PRIOQ_IDX_NULL; + return r; + } + + d->needs_rearm = true; + return 0; +} + +_public_ int sd_event_add_time( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + EventSourceType type; + _cleanup_(source_freep) sd_event_source *s = NULL; + struct clock_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(accuracy != (uint64_t) -1, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */ + return -EOPNOTSUPP; + + type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */ + if (type < 0) + return -EOPNOTSUPP; + + if (!callback) + callback = time_exit_callback; + + assert_se(d = event_get_clock_data(e, type)); + + r = setup_clock_data(e, d, clock); + if (r < 0) + return r; + + s = source_new(e, !ret, type); + if (!s) + return -ENOMEM; + + s->time.next = usec; + s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; + s->time.callback = callback; + s->earliest_index = s->latest_index = PRIOQ_IDX_NULL; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = event_source_time_prioq_put(s, d); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_event_add_time_relative( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + usec_t t; + int r; + + /* Same as sd_event_add_time() but operates relative to the event loop's current point in time, and + * checks for overflow. */ + + r = sd_event_now(e, clock, &t); + if (r < 0) + return r; + + if (usec >= USEC_INFINITY - t) + return -EOVERFLOW; + + return sd_event_add_time(e, ret, clock, t + usec, accuracy, callback, userdata); +} + +static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_signal( + sd_event *e, + sd_event_source **ret, + int sig, + sd_event_signal_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + struct signal_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(SIGNAL_VALID(sig), -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = signal_exit_callback; + + r = signal_is_blocked(sig); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + + if (!e->signal_sources) { + e->signal_sources = new0(sd_event_source*, _NSIG); + if (!e->signal_sources) + return -ENOMEM; + } else if (e->signal_sources[sig]) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_SIGNAL); + if (!s) + return -ENOMEM; + + s->signal.sig = sig; + s->signal.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + e->signal_sources[sig] = s; + + r = event_make_signal_data(e, sig, &d); + if (r < 0) + return r; + + /* Use the signal name as description for the event source by default */ + (void) sd_event_source_set_description(s, signal_to_string(sig)); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +static bool shall_use_pidfd(void) { + /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */ + return getenv_bool_secure("SYSTEMD_PIDFD") != 0; +} + +_public_ int sd_event_add_child( + sd_event *e, + sd_event_source **ret, + pid_t pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(pid > 1, -EINVAL); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = child_exit_callback; + + if (e->n_online_child_sources == 0) { + /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available, + * for compatibility with pre-pidfd and because we don't want the reap the child processes + * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to + * take effect. + * + * (As an optimization we only do this check on the first child event source created.) */ + r = signal_is_blocked(SIGCHLD); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + } + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we + * pin the PID, and make regular waitid() handling race-free. */ + + if (shall_use_pidfd()) { + s->child.pidfd = pidfd_open(s->child.pid, 0); + if (s->child.pidfd < 0) { + /* Propagate errors unless the syscall is not supported or blocked */ + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + } else + s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ + } else + s->child.pidfd = -1; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) + return r; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* We have a pidfd and we only want to watch for exit */ + r = source_child_pidfd_register(s, s->enabled); + if (r < 0) + return r; + + } else { + /* We have no pidfd or we shall wait for some other event than WEXITED */ + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) + return r; + + e->need_process_child = true; + } + + e->n_online_child_sources++; + + if (ret) + *ret = s; + TAKE_PTR(s); + return 0; +} + +_public_ int sd_event_add_child_pidfd( + sd_event *e, + sd_event_source **ret, + int pidfd, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + + _cleanup_(source_freep) sd_event_source *s = NULL; + pid_t pid; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(pidfd >= 0, -EBADF); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = child_exit_callback; + + if (e->n_online_child_sources == 0) { + r = signal_is_blocked(SIGCHLD); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + } + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + r = pidfd_get_pid(pidfd, &pid); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->child.pidfd = pidfd; + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->child.pidfd_owned = false; /* If we got the pidfd passed in we don't own it by default (similar to the IO fd case) */ + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) + return r; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* We only want to watch for WEXITED */ + r = source_child_pidfd_register(s, s->enabled); + if (r < 0) + return r; + } else { + /* We shall wait for some other event than WEXITED */ + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) + return r; + + e->need_process_child = true; + } + + e->n_online_child_sources++; + + if (ret) + *ret = s; + TAKE_PTR(s); + return 0; +} +#endif /* NM_IGNORED */ + +static int generic_exit_callback(sd_event_source *s, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_defer( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = generic_exit_callback; + + s = source_new(e, !ret, SOURCE_DEFER); + if (!s) + return -ENOMEM; + + s->defer.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +_public_ int sd_event_add_post( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = generic_exit_callback; + + s = source_new(e, !ret, SOURCE_POST); + if (!s) + return -ENOMEM; + + s->post.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = set_ensure_put(&e->post_sources, NULL, s); + if (r < 0) + return r; + assert(r > 0); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +_public_ int sd_event_add_exit( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_EXIT); + if (!s) + return -ENOMEM; + + s->exit.callback = callback; + s->userdata = userdata; + s->exit.prioq_index = PRIOQ_IDX_NULL; + s->enabled = SD_EVENT_ONESHOT; + + r = prioq_put(s->event->exit, s, &s->exit.prioq_index); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static void event_free_inotify_data(sd_event *e, struct inotify_data *d) { + assert(e); + + if (!d) + return; + + assert(hashmap_isempty(d->inodes)); + assert(hashmap_isempty(d->wd)); + + if (d->buffer_filled > 0) + LIST_REMOVE(buffered, e->inotify_data_buffered, d); + + hashmap_free(d->inodes); + hashmap_free(d->wd); + + assert_se(hashmap_remove(e->inotify_data, &d->priority) == d); + + if (d->fd >= 0) { + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0) + log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m"); + + safe_close(d->fd); + } + free(d); +} + +static int event_make_inotify_data( + sd_event *e, + int64_t priority, + struct inotify_data **ret) { + + _cleanup_close_ int fd = -1; + struct inotify_data *d; + int r; + + assert(e); + + d = hashmap_get(e->inotify_data, &priority); + if (d) { + if (ret) + *ret = d; + return 0; + } + + fd = inotify_init1(IN_NONBLOCK|O_CLOEXEC); + if (fd < 0) + return -errno; + + fd = fd_move_above_stdio(fd); + + r = hashmap_ensure_allocated(&e->inotify_data, &uint64_hash_ops); + if (r < 0) + return r; + + d = new(struct inotify_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct inotify_data) { + .wakeup = WAKEUP_INOTIFY_DATA, + .fd = TAKE_FD(fd), + .priority = priority, + }; + + r = hashmap_put(e->inotify_data, &d->priority, d); + if (r < 0) { + d->fd = safe_close(d->fd); + free(d); + return r; + } + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) { + r = -errno; + d->fd = safe_close(d->fd); /* let's close this ourselves, as event_free_inotify_data() would otherwise + * remove the fd from the epoll first, which we don't want as we couldn't + * add it in the first place. */ + event_free_inotify_data(e, d); + return r; + } + + if (ret) + *ret = d; + + return 1; +} + +static int inode_data_compare(const struct inode_data *x, const struct inode_data *y) { + int r; + + assert(x); + assert(y); + + r = CMP(x->dev, y->dev); + if (r != 0) + return r; + + return CMP(x->ino, y->ino); +} + +static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) { + assert(d); + + siphash24_compress(&d->dev, sizeof(d->dev), state); + siphash24_compress(&d->ino, sizeof(d->ino), state); +} + +DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare); + +static void event_free_inode_data( + sd_event *e, + struct inode_data *d) { + + assert(e); + + if (!d) + return; + + assert(!d->event_sources); + + if (d->fd >= 0) { + LIST_REMOVE(to_close, e->inode_data_to_close, d); + safe_close(d->fd); + } + + if (d->inotify_data) { + + if (d->wd >= 0) { + if (d->inotify_data->fd >= 0) { + /* So here's a problem. At the time this runs the watch descriptor might already be + * invalidated, because an IN_IGNORED event might be queued right the moment we enter + * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very + * likely case to happen. */ + + if (inotify_rm_watch(d->inotify_data->fd, d->wd) < 0 && errno != EINVAL) + log_debug_errno(errno, "Failed to remove watch descriptor %i from inotify, ignoring: %m", d->wd); + } + + assert_se(hashmap_remove(d->inotify_data->wd, INT_TO_PTR(d->wd)) == d); + } + + assert_se(hashmap_remove(d->inotify_data->inodes, d) == d); + } + + free(d); +} + +static void event_gc_inode_data( + sd_event *e, + struct inode_data *d) { + + struct inotify_data *inotify_data; + + assert(e); + + if (!d) + return; + + if (d->event_sources) + return; + + inotify_data = d->inotify_data; + event_free_inode_data(e, d); + + if (inotify_data && hashmap_isempty(inotify_data->inodes)) + event_free_inotify_data(e, inotify_data); +} + +static int event_make_inode_data( + sd_event *e, + struct inotify_data *inotify_data, + dev_t dev, + ino_t ino, + struct inode_data **ret) { + + struct inode_data *d, key; + int r; + + assert(e); + assert(inotify_data); + + key = (struct inode_data) { + .ino = ino, + .dev = dev, + }; + + d = hashmap_get(inotify_data->inodes, &key); + if (d) { + if (ret) + *ret = d; + + return 0; + } + + r = hashmap_ensure_allocated(&inotify_data->inodes, &inode_data_hash_ops); + if (r < 0) + return r; + + d = new(struct inode_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct inode_data) { + .dev = dev, + .ino = ino, + .wd = -1, + .fd = -1, + .inotify_data = inotify_data, + }; + + r = hashmap_put(inotify_data->inodes, d, d); + if (r < 0) { + free(d); + return r; + } + + if (ret) + *ret = d; + + return 1; +} + +static uint32_t inode_data_determine_mask(struct inode_data *d) { + bool excl_unlink = true; + uint32_t combined = 0; + sd_event_source *s; + + assert(d); + + /* Combines the watch masks of all event sources watching this inode. We generally just OR them together, but + * the IN_EXCL_UNLINK flag is ANDed instead. + * + * Note that we add all sources to the mask here, regardless whether enabled, disabled or oneshot. That's + * because we cannot change the mask anymore after the event source was created once, since the kernel has no + * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and suppress + * events we don't care for client-side. */ + + LIST_FOREACH(inotify.by_inode_data, s, d->event_sources) { + + if ((s->inotify.mask & IN_EXCL_UNLINK) == 0) + excl_unlink = false; + + combined |= s->inotify.mask; + } + + return (combined & ~(IN_ONESHOT|IN_DONT_FOLLOW|IN_ONLYDIR|IN_EXCL_UNLINK)) | (excl_unlink ? IN_EXCL_UNLINK : 0); +} + +static int inode_data_realize_watch(sd_event *e, struct inode_data *d) { + uint32_t combined_mask; + int wd, r; + + assert(d); + assert(d->fd >= 0); + + combined_mask = inode_data_determine_mask(d); + + if (d->wd >= 0 && combined_mask == d->combined_mask) + return 0; + + r = hashmap_ensure_allocated(&d->inotify_data->wd, NULL); + if (r < 0) + return r; + + wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask); + if (wd < 0) + return -errno; + + if (d->wd < 0) { + r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d); + if (r < 0) { + (void) inotify_rm_watch(d->inotify_data->fd, wd); + return r; + } + + d->wd = wd; + + } else if (d->wd != wd) { + + log_debug("Weird, the watch descriptor we already knew for this inode changed?"); + (void) inotify_rm_watch(d->fd, wd); + return -EINVAL; + } + + d->combined_mask = combined_mask; + return 1; +} + +static int inotify_exit_callback(sd_event_source *s, const struct inotify_event *event, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_inotify( + sd_event *e, + sd_event_source **ret, + const char *path, + uint32_t mask, + sd_event_inotify_handler_t callback, + void *userdata) { + + struct inotify_data *inotify_data = NULL; + struct inode_data *inode_data = NULL; + _cleanup_close_ int fd = -1; + _cleanup_(source_freep) sd_event_source *s = NULL; + struct stat st; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(path, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = inotify_exit_callback; + + /* Refuse IN_MASK_ADD since we coalesce watches on the same inode, and hence really don't want to merge + * masks. Or in other words, this whole code exists only to manage IN_MASK_ADD type operations for you, hence + * the user can't use them for us. */ + if (mask & IN_MASK_ADD) + return -EINVAL; + + fd = open(path, O_PATH|O_CLOEXEC| + (mask & IN_ONLYDIR ? O_DIRECTORY : 0)| + (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + s = source_new(e, !ret, SOURCE_INOTIFY); + if (!s) + return -ENOMEM; + + s->enabled = mask & IN_ONESHOT ? SD_EVENT_ONESHOT : SD_EVENT_ON; + s->inotify.mask = mask; + s->inotify.callback = callback; + s->userdata = userdata; + + /* Allocate an inotify object for this priority, and an inode object within it */ + r = event_make_inotify_data(e, SD_EVENT_PRIORITY_NORMAL, &inotify_data); + if (r < 0) + return r; + + r = event_make_inode_data(e, inotify_data, st.st_dev, st.st_ino, &inode_data); + if (r < 0) { + event_free_inotify_data(e, inotify_data); + return r; + } + + /* Keep the O_PATH fd around until the first iteration of the loop, so that we can still change the priority of + * the event source, until then, for which we need the original inode. */ + if (inode_data->fd < 0) { + inode_data->fd = TAKE_FD(fd); + LIST_PREPEND(to_close, e->inode_data_to_close, inode_data); + } + + /* Link our event source to the inode data object */ + LIST_PREPEND(inotify.by_inode_data, inode_data->event_sources, s); + s->inotify.inode_data = inode_data; + + /* Actually realize the watch now */ + r = inode_data_realize_watch(e, inode_data); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s, path); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static sd_event_source* event_source_free(sd_event_source *s) { + if (!s) + return NULL; + + /* Here's a special hack: when we are called from a + * dispatch handler we won't free the event source + * immediately, but we will detach the fd from the + * epoll. This way it is safe for the caller to unref + * the event source and immediately close the fd, but + * we still retain a valid event source object after + * the callback. */ + + if (s->dispatching) { + if (s->type == SOURCE_IO) + source_io_unregister(s); + + source_disconnect(s); + } else + source_free(s); + + return NULL; +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_source_free); + +_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return free_and_strdup(&s->description, description); +} + +_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { + assert_return(s, -EINVAL); + assert_return(description, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (!s->description) + return -ENXIO; + + *description = s->description; + return 0; +} + +_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { + assert_return(s, NULL); + + return s->event; +} + +_public_ int sd_event_source_get_pending(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->pending; +} + +_public_ int sd_event_source_get_io_fd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->io.fd; +} + +_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { + int r; + + assert_return(s, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->io.fd == fd) + return 0; + + if (event_source_is_offline(s)) { + s->io.fd = fd; + s->io.registered = false; + } else { + int saved_fd; + + saved_fd = s->io.fd; + assert(s->io.registered); + + s->io.fd = fd; + s->io.registered = false; + + r = source_io_register(s, s->enabled, s->io.events); + if (r < 0) { + s->io.fd = saved_fd; + s->io.registered = true; + return r; + } + + (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); + } + + return 0; +} + +_public_ int sd_event_source_get_io_fd_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + + return s->io.owned; +} + +_public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + + s->io.owned = own; + return 0; +} + +_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { + assert_return(s, -EINVAL); + assert_return(events, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *events = s->io.events; + return 0; +} + +_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* edge-triggered updates are never skipped, so we can reset edges */ + if (s->io.events == events && !(events & EPOLLET)) + return 0; + + r = source_set_pending(s, false); + if (r < 0) + return r; + + if (event_source_is_online(s)) { + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + } + + s->io.events = events; + + return 0; +} + +_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { + assert_return(s, -EINVAL); + assert_return(revents, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(s->pending, -ENODATA); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *revents = s->io.revents; + return 0; +} + +_public_ int sd_event_source_get_signal(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_SIGNAL, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->signal.sig; +} + +_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *priority = s->priority; + return 0; +} + +_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { + bool rm_inotify = false, rm_inode = false; + struct inotify_data *new_inotify_data = NULL; + struct inode_data *new_inode_data = NULL; + int r; + + assert_return(s, -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->priority == priority) + return 0; + + if (s->type == SOURCE_INOTIFY) { + struct inode_data *old_inode_data; + + assert(s->inotify.inode_data); + old_inode_data = s->inotify.inode_data; + + /* We need the original fd to change the priority. If we don't have it we can't change the priority, + * anymore. Note that we close any fds when entering the next event loop iteration, i.e. for inotify + * events we allow priority changes only until the first following iteration. */ + if (old_inode_data->fd < 0) + return -EOPNOTSUPP; + + r = event_make_inotify_data(s->event, priority, &new_inotify_data); + if (r < 0) + return r; + rm_inotify = r > 0; + + r = event_make_inode_data(s->event, new_inotify_data, old_inode_data->dev, old_inode_data->ino, &new_inode_data); + if (r < 0) + goto fail; + rm_inode = r > 0; + + if (new_inode_data->fd < 0) { + /* Duplicate the fd for the new inode object if we don't have any yet */ + new_inode_data->fd = fcntl(old_inode_data->fd, F_DUPFD_CLOEXEC, 3); + if (new_inode_data->fd < 0) { + r = -errno; + goto fail; + } + + LIST_PREPEND(to_close, s->event->inode_data_to_close, new_inode_data); + } + + /* Move the event source to the new inode data structure */ + LIST_REMOVE(inotify.by_inode_data, old_inode_data->event_sources, s); + LIST_PREPEND(inotify.by_inode_data, new_inode_data->event_sources, s); + s->inotify.inode_data = new_inode_data; + + /* Now create the new watch */ + r = inode_data_realize_watch(s->event, new_inode_data); + if (r < 0) { + /* Move it back */ + LIST_REMOVE(inotify.by_inode_data, new_inode_data->event_sources, s); + LIST_PREPEND(inotify.by_inode_data, old_inode_data->event_sources, s); + s->inotify.inode_data = old_inode_data; + goto fail; + } + + s->priority = priority; + + event_gc_inode_data(s->event, old_inode_data); + + } else if (s->type == SOURCE_SIGNAL && event_source_is_online(s)) { + struct signal_data *old, *d; + + /* Move us from the signalfd belonging to the old + * priority to the signalfd of the new priority */ + + assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); + + s->priority = priority; + + r = event_make_signal_data(s->event, s->signal.sig, &d); + if (r < 0) { + s->priority = old->priority; + return r; + } + + event_unmask_signal_data(s->event, old, s->signal.sig); + } else + s->priority = priority; + + event_source_pp_prioq_reshuffle(s); + + if (s->type == SOURCE_EXIT) + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + + return 0; + +fail: + if (rm_inode) + event_free_inode_data(s->event, new_inode_data); + + if (rm_inotify) + event_free_inotify_data(s->event, new_inotify_data); + + return r; +} + +_public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (ret) + *ret = s->enabled; + + return s->enabled != SD_EVENT_OFF; +} + +static int event_source_offline( + sd_event_source *s, + int enabled, + bool ratelimited) { + + bool was_offline; + int r; + + assert(s); + assert(enabled == SD_EVENT_OFF || ratelimited); + + /* Unset the pending flag when this event source is disabled */ + if (s->enabled != SD_EVENT_OFF && + enabled == SD_EVENT_OFF && + !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + was_offline = event_source_is_offline(s); + s->enabled = enabled; + s->ratelimited = ratelimited; + + switch (s->type) { + + case SOURCE_IO: + source_io_unregister(s); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + event_source_time_prioq_reshuffle(s); + break; + + case SOURCE_SIGNAL: + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + break; + + case SOURCE_CHILD: + if (!was_offline) { + assert(s->event->n_online_child_sources > 0); + s->event->n_online_child_sources--; + } + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + source_child_pidfd_unregister(s); + else + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + break; + + case SOURCE_EXIT: + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + case SOURCE_INOTIFY: + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + return 1; +} + +static int event_source_online( + sd_event_source *s, + int enabled, + bool ratelimited) { + + bool was_online; + int r; + + assert(s); + assert(enabled != SD_EVENT_OFF || !ratelimited); + + /* Unset the pending flag when this event source is enabled */ + if (s->enabled == SD_EVENT_OFF && + enabled != SD_EVENT_OFF && + !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + /* Are we really ready for onlining? */ + if (enabled == SD_EVENT_OFF || ratelimited) { + /* Nope, we are not ready for onlining, then just update the precise state and exit */ + s->enabled = enabled; + s->ratelimited = ratelimited; + return 0; + } + + was_online = event_source_is_online(s); + + switch (s->type) { + case SOURCE_IO: + r = source_io_register(s, enabled, s->io.events); + if (r < 0) + return r; + break; + + case SOURCE_SIGNAL: + r = event_make_signal_data(s->event, s->signal.sig, NULL); + if (r < 0) { + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + return r; + } + + break; + + case SOURCE_CHILD: + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* yes, we have pidfd */ + + r = source_child_pidfd_register(s, enabled); + if (r < 0) + return r; + } else { + /* no pidfd, or something other to watch for than WEXITED */ + + r = event_make_signal_data(s->event, SIGCHLD, NULL); + if (r < 0) { + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + return r; + } + } + + if (!was_online) + s->event->n_online_child_sources++; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + case SOURCE_EXIT: + case SOURCE_DEFER: + case SOURCE_POST: + case SOURCE_INOTIFY: + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->enabled = enabled; + s->ratelimited = ratelimited; + + /* Non-failing operations below */ + switch (s->type) { + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + event_source_time_prioq_reshuffle(s); + break; + + case SOURCE_EXIT: + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + default: + break; + } + + return 1; +} + +_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { + int r; + + assert_return(s, -EINVAL); + assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */ + if (s->event->state == SD_EVENT_FINISHED) + return m == SD_EVENT_OFF ? 0 : -ESTALE; + + if (s->enabled == m) /* No change? */ + return 0; + + if (m == SD_EVENT_OFF) + r = event_source_offline(s, m, s->ratelimited); + else { + if (s->enabled != SD_EVENT_OFF) { + /* Switching from "on" to "oneshot" or back? If that's the case, we can take a shortcut, the + * event source is already enabled after all. */ + s->enabled = m; + return 0; + } + + r = event_source_online(s, m, s->ratelimited); + } + if (r < 0) + return r; + + event_source_pp_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.next; + return 0; +} + +_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { + int r; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + r = source_set_pending(s, false); + if (r < 0) + return r; + + s->time.next = usec; + + event_source_time_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec) { + usec_t t; + int r; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + + r = sd_event_now(s->event, event_source_type_to_clock(s->type), &t); + if (r < 0) + return r; + + if (usec >= USEC_INFINITY - t) + return -EOVERFLOW; + + return sd_event_source_set_time(s, t + usec); +} + +_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.accuracy; + return 0; +} + +_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { + int r; + + assert_return(s, -EINVAL); + assert_return(usec != (uint64_t) -1, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + r = source_set_pending(s, false); + if (r < 0) + return r; + + if (usec == 0) + usec = DEFAULT_ACCURACY_USEC; + + s->time.accuracy = usec; + + event_source_time_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { + assert_return(s, -EINVAL); + assert_return(clock, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *clock = event_source_type_to_clock(s->type); + return 0; +} + +_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { + assert_return(s, -EINVAL); + assert_return(pid, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *pid = s->child.pid; + return 0; +} + +_public_ int sd_event_source_get_child_pidfd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + return s->child.pidfd; +} + +_public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + assert_return(SIGNAL_VALID(sig), -EINVAL); + + /* If we already have seen indication the process exited refuse sending a signal early. This way we + * can be sure we don't accidentally kill the wrong process on PID reuse when pidfds are not + * available. */ + if (s->child.exited) + return -ESRCH; + + if (s->child.pidfd >= 0) { + siginfo_t copy; + + /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the + * structure here */ + if (si) + copy = *si; + + if (pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, 0) < 0) { + /* Let's propagate the error only if the system call is not implemented or prohibited */ + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + } else + return 0; + } + + /* Flags are only supported for pidfd_send_signal(), not for rt_sigqueueinfo(), hence let's refuse + * this here. */ + if (flags != 0) + return -EOPNOTSUPP; + + if (si) { + /* We use rt_sigqueueinfo() only if siginfo_t is specified. */ + siginfo_t copy = *si; + + if (rt_sigqueueinfo(s->child.pid, sig, ©) < 0) + return -errno; + } else if (kill(s->child.pid, sig) < 0) + return -errno; + + return 0; +} + +_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + return s->child.pidfd_owned; +} + +_public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + s->child.pidfd_owned = own; + return 0; +} + +_public_ int sd_event_source_get_child_process_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + return s->child.process_owned; +} + +_public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + s->child.process_owned = own; + return 0; +} + +_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) { + assert_return(s, -EINVAL); + assert_return(mask, -EINVAL); + assert_return(s->type == SOURCE_INOTIFY, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *mask = s->inotify.mask; + return 0; +} + +_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->prepare == callback) + return 0; + + if (callback && s->prepare) { + s->prepare = callback; + return 0; + } + + r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); + if (r < 0) + return r; + + s->prepare = callback; + + if (callback) { + r = prioq_put(s->event->prepare, s, &s->prepare_index); + if (r < 0) + return r; + } else + prioq_remove(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ void* sd_event_source_get_userdata(sd_event_source *s) { + assert_return(s, NULL); + + return s->userdata; +} + +_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { + void *ret; + + assert_return(s, NULL); + + ret = s->userdata; + s->userdata = userdata; + + return ret; +} + +static int event_source_enter_ratelimited(sd_event_source *s) { + int r; + + assert(s); + + /* When an event source becomes ratelimited, we place it in the CLOCK_MONOTONIC priority queue, with + * the end of the rate limit time window, much as if it was a timer event source. */ + + if (s->ratelimited) + return 0; /* Already ratelimited, this is a NOP hence */ + + /* Make sure we can install a CLOCK_MONOTONIC event further down. */ + r = setup_clock_data(s->event, &s->event->monotonic, CLOCK_MONOTONIC); + if (r < 0) + return r; + + /* Timer event sources are already using the earliest/latest queues for the timer scheduling. Let's + * first remove them from the prioq appropriate for their own clock, so that we can use the prioq + * fields of the event source then for adding it to the CLOCK_MONOTONIC prioq instead. */ + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type)); + + /* Now, let's add the event source to the monotonic clock instead */ + r = event_source_time_prioq_put(s, &s->event->monotonic); + if (r < 0) + goto fail; + + /* And let's take the event source officially offline */ + r = event_source_offline(s, s->enabled, /* ratelimited= */ true); + if (r < 0) { + event_source_time_prioq_remove(s, &s->event->monotonic); + goto fail; + } + + event_source_pp_prioq_reshuffle(s); + + log_debug("Event source %p (%s) entered rate limit state.", s, strna(s->description)); + return 0; + +fail: + /* Reinstall time event sources in the priority queue as before. This shouldn't fail, since the queue + * space for it should already be allocated. */ + if (EVENT_SOURCE_IS_TIME(s->type)) + assert_se(event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)) >= 0); + + return r; +} + +static int event_source_leave_ratelimit(sd_event_source *s) { + int r; + + assert(s); + + if (!s->ratelimited) + return 0; + + /* Let's take the event source out of the monotonic prioq first. */ + event_source_time_prioq_remove(s, &s->event->monotonic); + + /* Let's then add the event source to its native clock prioq again — if this is a timer event source */ + if (EVENT_SOURCE_IS_TIME(s->type)) { + r = event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)); + if (r < 0) + goto fail; + } + + /* Let's try to take it online again. */ + r = event_source_online(s, s->enabled, /* ratelimited= */ false); + if (r < 0) { + /* Do something roughly sensible when this failed: undo the two prioq ops above */ + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type)); + + goto fail; + } + + event_source_pp_prioq_reshuffle(s); + ratelimit_reset(&s->rate_limit); + + log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description)); + return 0; + +fail: + /* Do something somewhat reasonable when we cannot move an event sources out of ratelimited mode: + * simply put it back in it, maybe we can then process it more successfully next iteration. */ + assert_se(event_source_time_prioq_put(s, &s->event->monotonic) >= 0); + + return r; +} + +static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { + usec_t c; + assert(e); + assert(a <= b); + + if (a <= 0) + return 0; + if (a >= USEC_INFINITY) + return USEC_INFINITY; + + if (b <= a + 1) + return a; + + initialize_perturb(e); + + /* + Find a good time to wake up again between times a and b. We + have two goals here: + + a) We want to wake up as seldom as possible, hence prefer + later times over earlier times. + + b) But if we have to wake up, then let's make sure to + dispatch as much as possible on the entire system. + + We implement this by waking up everywhere at the same time + within any given minute if we can, synchronised via the + perturbation value determined from the boot ID. If we can't, + then we try to find the same spot in every 10s, then 1s and + then 250ms step. Otherwise, we pick the last possible time + to wake up. + */ + + c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; + if (c >= b) { + if (_unlikely_(c < USEC_PER_MINUTE)) + return b; + + c -= USEC_PER_MINUTE; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC*10)) + return b; + + c -= USEC_PER_SEC*10; + } + + if (c >= a) + return c; + + c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC)) + return b; + + c -= USEC_PER_SEC; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_MSEC*250)) + return b; + + c -= USEC_PER_MSEC*250; + } + + if (c >= a) + return c; + + return b; +} + +static int event_arm_timer( + sd_event *e, + struct clock_data *d) { + + struct itimerspec its = {}; + sd_event_source *a, *b; + usec_t t; + + assert(e); + assert(d); + + if (!d->needs_rearm) + return 0; + else + d->needs_rearm = false; + + a = prioq_peek(d->earliest); + if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) { + + if (d->fd < 0) + return 0; + + if (d->next == USEC_INFINITY) + return 0; + + /* disarm */ + if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + d->next = USEC_INFINITY; + return 0; + } + + b = prioq_peek(d->latest); + assert_se(b && b->enabled != SD_EVENT_OFF); + + t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b)); + if (d->next == t) + return 0; + + assert_se(d->fd >= 0); + + if (t == 0) { + /* We don' want to disarm here, just mean some time looooong ago. */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + } else + timespec_store(&its.it_value, t); + + if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + d->next = t; + return 0; +} + +static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_IO); + + /* If the event source was already pending, we just OR in the + * new revents, otherwise we reset the value. The ORing is + * necessary to handle EPOLLONESHOT events properly where + * readability might happen independently of writability, and + * we need to keep track of both */ + + if (s->pending) + s->io.revents |= revents; + else + s->io.revents = revents; + + return source_set_pending(s, true); +} + +static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { + uint64_t x; + ssize_t ss; + + assert(e); + assert(fd >= 0); + + assert_return(events == EPOLLIN, -EIO); + + ss = read(fd, &x, sizeof(x)); + if (ss < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return -errno; + } + + if (_unlikely_(ss != sizeof(x))) + return -EIO; + + if (next) + *next = USEC_INFINITY; + + return 0; +} + +static int process_timer( + sd_event *e, + usec_t n, + struct clock_data *d) { + + sd_event_source *s; + int r; + + assert(e); + assert(d); + + for (;;) { + s = prioq_peek(d->earliest); + if (!s || time_event_source_next(s) > n) + break; + + if (s->ratelimited) { + /* This is an event sources whose ratelimit window has ended. Let's turn it on + * again. */ + assert(s->ratelimited); + + r = event_source_leave_ratelimit(s); + if (r < 0) + return r; + + continue; + } + + if (s->enabled == SD_EVENT_OFF || s->pending) + break; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + event_source_time_prioq_reshuffle(s); + } + + return 0; +} + +static int process_child(sd_event *e) { + sd_event_source *s; + int r; + + assert(e); + + e->need_process_child = false; + + /* + So, this is ugly. We iteratively invoke waitid() with P_PID + + WNOHANG for each PID we wait for, instead of using + P_ALL. This is because we only want to get child + information of very specific child processes, and not all + of them. We might not have processed the SIGCHLD even of a + previous invocation and we don't want to maintain a + unbounded *per-child* event queue, hence we really don't + want anything flushed out of the kernel's queue that we + don't care about. Since this is O(n) this means that if you + have a lot of processes you probably want to handle SIGCHLD + yourself. + + We do not reap the children here (by using WNOWAIT), this + is only done after the event source is dispatched so that + the callback still sees the process as a zombie. + */ + + HASHMAP_FOREACH(s, e->child_sources) { + assert(s->type == SOURCE_CHILD); + + if (s->pending) + continue; + + if (event_source_is_offline(s)) + continue; + + if (s->child.exited) + continue; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) /* There's a usable pidfd known for this event source? then don't waitid() for it here */ + continue; + + zero(s->child.siginfo); + if (waitid(P_PID, s->child.pid, &s->child.siginfo, + WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0) + return -errno; + + if (s->child.siginfo.si_pid != 0) { + bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + + if (zombie) + s->child.exited = true; + + if (!zombie && (s->child.options & WEXITED)) { + /* If the child isn't dead then let's + * immediately remove the state change + * from the queue, since there's no + * benefit in leaving it queued */ + + assert(s->child.options & (WSTOPPED|WCONTINUED)); + (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + } + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + + return 0; +} + +static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_CHILD); + + if (s->pending) + return 0; + + if (event_source_is_offline(s)) + return 0; + + if (!EVENT_SOURCE_WATCH_PIDFD(s)) + return 0; + + zero(s->child.siginfo); + if (waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) + return -errno; + + if (s->child.siginfo.si_pid == 0) + return 0; + + if (IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED)) + s->child.exited = true; + + return source_set_pending(s, true); +} + +static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) { + bool read_one = false; + int r; + + assert(e); + assert(d); + assert_return(events == EPOLLIN, -EIO); + + /* If there's a signal queued on this priority and SIGCHLD is + on this priority too, then make sure to recheck the + children we watch. This is because we only ever dequeue + the first signal per priority, and if we dequeue one, and + SIGCHLD might be enqueued later we wouldn't know, but we + might have higher priority children we care about hence we + need to check that explicitly. */ + + if (sigismember(&d->sigset, SIGCHLD)) + e->need_process_child = true; + + /* If there's already an event source pending for this + * priority we don't read another */ + if (d->current) + return 0; + + for (;;) { + struct signalfd_siginfo si; + ssize_t n; + sd_event_source *s = NULL; + + n = read(d->fd, &si, sizeof(si)); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return read_one; + + return -errno; + } + + if (_unlikely_(n != sizeof(si))) + return -EIO; + + assert(SIGNAL_VALID(si.ssi_signo)); + + read_one = true; + + if (e->signal_sources) + s = e->signal_sources[si.ssi_signo]; + if (!s) + continue; + if (s->pending) + continue; + + s->signal.siginfo = si; + d->current = s; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + return 1; + } +} + +static int event_inotify_data_read(sd_event *e, struct inotify_data *d, uint32_t revents) { + ssize_t n; + + assert(e); + assert(d); + + assert_return(revents == EPOLLIN, -EIO); + + /* If there's already an event source pending for this priority, don't read another */ + if (d->n_pending > 0) + return 0; + + /* Is the read buffer non-empty? If so, let's not read more */ + if (d->buffer_filled > 0) + return 0; + + n = read(d->fd, &d->buffer, sizeof(d->buffer)); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return -errno; + } + + assert(n > 0); + d->buffer_filled = (size_t) n; + LIST_PREPEND(buffered, e->inotify_data_buffered, d); + + return 1; +} + +static void event_inotify_data_drop(sd_event *e, struct inotify_data *d, size_t sz) { + assert(e); + assert(d); + assert(sz <= d->buffer_filled); + + if (sz == 0) + return; + + /* Move the rest to the buffer to the front, in order to get things properly aligned again */ + memmove(d->buffer.raw, d->buffer.raw + sz, d->buffer_filled - sz); + d->buffer_filled -= sz; + + if (d->buffer_filled == 0) + LIST_REMOVE(buffered, e->inotify_data_buffered, d); +} + +static int event_inotify_data_process(sd_event *e, struct inotify_data *d) { + int r; + + assert(e); + assert(d); + + /* If there's already an event source pending for this priority, don't read another */ + if (d->n_pending > 0) + return 0; + + while (d->buffer_filled > 0) { + size_t sz; + + /* Let's validate that the event structures are complete */ + if (d->buffer_filled < offsetof(struct inotify_event, name)) + return -EIO; + + sz = offsetof(struct inotify_event, name) + d->buffer.ev.len; + if (d->buffer_filled < sz) + return -EIO; + + if (d->buffer.ev.mask & IN_Q_OVERFLOW) { + struct inode_data *inode_data; + + /* The queue overran, let's pass this event to all event sources connected to this inotify + * object */ + + HASHMAP_FOREACH(inode_data, d->inodes) { + sd_event_source *s; + + LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) { + + if (event_source_is_offline(s)) + continue; + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + } else { + struct inode_data *inode_data; + sd_event_source *s; + + /* Find the inode object for this watch descriptor. If IN_IGNORED is set we also remove it from + * our watch descriptor table. */ + if (d->buffer.ev.mask & IN_IGNORED) { + + inode_data = hashmap_remove(d->wd, INT_TO_PTR(d->buffer.ev.wd)); + if (!inode_data) { + event_inotify_data_drop(e, d, sz); + continue; + } + + /* The watch descriptor was removed by the kernel, let's drop it here too */ + inode_data->wd = -1; + } else { + inode_data = hashmap_get(d->wd, INT_TO_PTR(d->buffer.ev.wd)); + if (!inode_data) { + event_inotify_data_drop(e, d, sz); + continue; + } + } + + /* Trigger all event sources that are interested in these events. Also trigger all event + * sources if IN_IGNORED or IN_UNMOUNT is set. */ + LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) { + + if (event_source_is_offline(s)) + continue; + + if ((d->buffer.ev.mask & (IN_IGNORED|IN_UNMOUNT)) == 0 && + (s->inotify.mask & d->buffer.ev.mask & IN_ALL_EVENTS) == 0) + continue; + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + + /* Something pending now? If so, let's finish, otherwise let's read more. */ + if (d->n_pending > 0) + return 1; + } + + return 0; +} + +static int process_inotify(sd_event *e) { + struct inotify_data *d; + int r, done = 0; + + assert(e); + + LIST_FOREACH(buffered, d, e->inotify_data_buffered) { + r = event_inotify_data_process(e, d); + if (r < 0) + return r; + if (r > 0) + done ++; + } + + return done; +} + +static int source_dispatch(sd_event_source *s) { + _cleanup_(sd_event_unrefp) sd_event *saved_event = NULL; + EventSourceType saved_type; + int r = 0; + + assert(s); + assert(s->pending || s->type == SOURCE_EXIT); + + /* Save the event source type, here, so that we still know it after the event callback which might + * invalidate the event. */ + saved_type = s->type; + + /* Similar, store a reference to the event loop object, so that we can still access it after the + * callback might have invalidated/disconnected the event source. */ + saved_event = sd_event_ref(s->event); + + /* Check if we hit the ratelimit for this event source, if so, let's disable it. */ + assert(!s->ratelimited); + if (!ratelimit_below(&s->rate_limit)) { + r = event_source_enter_ratelimited(s); + if (r < 0) + return r; + + return 1; + } + + if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + if (s->type != SOURCE_POST) { + sd_event_source *z; + + /* If we execute a non-post source, let's mark all + * post sources as pending */ + + SET_FOREACH(z, s->event->post_sources) { + if (event_source_is_offline(z)) + continue; + + r = source_set_pending(z, true); + if (r < 0) + return r; + } + } + + if (s->enabled == SD_EVENT_ONESHOT) { + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + } + + s->dispatching = true; + + switch (s->type) { + + case SOURCE_IO: + r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + r = s->time.callback(s, s->time.next, s->userdata); + break; + + case SOURCE_SIGNAL: + r = s->signal.callback(s, &s->signal.siginfo, s->userdata); + break; + + case SOURCE_CHILD: { + bool zombie; + + zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + + r = s->child.callback(s, &s->child.siginfo, s->userdata); + + /* Now, reap the PID for good. */ + if (zombie) { + (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); + s->child.waited = true; + } + + break; + } + + case SOURCE_DEFER: + r = s->defer.callback(s, s->userdata); + break; + + case SOURCE_POST: + r = s->post.callback(s, s->userdata); + break; + + case SOURCE_EXIT: + r = s->exit.callback(s, s->userdata); + break; + + case SOURCE_INOTIFY: { + struct sd_event *e = s->event; + struct inotify_data *d; + size_t sz; + + assert(s->inotify.inode_data); + assert_se(d = s->inotify.inode_data->inotify_data); + + assert(d->buffer_filled >= offsetof(struct inotify_event, name)); + sz = offsetof(struct inotify_event, name) + d->buffer.ev.len; + assert(d->buffer_filled >= sz); + + r = s->inotify.callback(s, &d->buffer.ev, s->userdata); + + /* When no event is pending anymore on this inotify object, then let's drop the event from the + * buffer. */ + if (d->n_pending == 0) + event_inotify_data_drop(e, d, sz); + + break; + } + + case SOURCE_WATCHDOG: + case _SOURCE_EVENT_SOURCE_TYPE_MAX: + case _SOURCE_EVENT_SOURCE_TYPE_INVALID: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->dispatching = false; + + if (r < 0) { + log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m", + strna(s->description), + event_source_type_to_string(saved_type), + s->exit_on_failure ? "exiting" : "disabling"); + + if (s->exit_on_failure) + (void) sd_event_exit(saved_event, r); + } + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + + return 1; +} + +static int event_prepare(sd_event *e) { + int r; + + assert(e); + + for (;;) { + sd_event_source *s; + + s = prioq_peek(e->prepare); + if (!s || s->prepare_iteration == e->iteration || event_source_is_offline(s)) + break; + + s->prepare_iteration = e->iteration; + r = prioq_reshuffle(e->prepare, s, &s->prepare_index); + if (r < 0) + return r; + + assert(s->prepare); + + s->dispatching = true; + r = s->prepare(s, s->userdata); + s->dispatching = false; + + if (r < 0) { + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, %s: %m", + strna(s->description), + event_source_type_to_string(s->type), + s->exit_on_failure ? "exiting" : "disabling"); + + if (s->exit_on_failure) + (void) sd_event_exit(e, r); + } + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + } + + return 0; +} + +static int dispatch_exit(sd_event *e) { + sd_event_source *p; + int r; + + assert(e); + + p = prioq_peek(e->exit); + if (!p || event_source_is_offline(p)) { + e->state = SD_EVENT_FINISHED; + return 0; + } + + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + e->iteration++; + e->state = SD_EVENT_EXITING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + return r; +} + +static sd_event_source* event_next_pending(sd_event *e) { + sd_event_source *p; + + assert(e); + + p = prioq_peek(e->pending); + if (!p) + return NULL; + + if (event_source_is_offline(p)) + return NULL; + + return p; +} + +static int arm_watchdog(sd_event *e) { + struct itimerspec its = {}; + usec_t t; + + assert(e); + assert(e->watchdog_fd >= 0); + + t = sleep_between(e, + e->watchdog_last + (e->watchdog_period / 2), + e->watchdog_last + (e->watchdog_period * 3 / 4)); + + timespec_store(&its.it_value, t); + + /* Make sure we never set the watchdog to 0, which tells the + * kernel to disable it. */ + if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) + its.it_value.tv_nsec = 1; + + if (timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + return 0; +} + +static int process_watchdog(sd_event *e) { + assert(e); + + if (!e->watchdog) + return 0; + + /* Don't notify watchdog too often */ + if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) + return 0; + + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = e->timestamp.monotonic; + + return arm_watchdog(e); +} + +static void event_close_inode_data_fds(sd_event *e) { + struct inode_data *d; + + assert(e); + + /* Close the fds pointing to the inodes to watch now. We need to close them as they might otherwise pin + * filesystems. But we can't close them right-away as we need them as long as the user still wants to make + * adjustments to the even source, such as changing the priority (which requires us to remove and re-add a watch + * for the inode). Hence, let's close them when entering the first iteration after they were added, as a + * compromise. */ + + while ((d = e->inode_data_to_close)) { + assert(d->fd >= 0); + d->fd = safe_close(d->fd); + + LIST_REMOVE(to_close, e->inode_data_to_close, d); + } +} + +_public_ int sd_event_prepare(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + /* Let's check that if we are a default event loop we are executed in the correct thread. We only do + * this check here once, since gettid() is typically not cached, and thus want to minimize + * syscalls */ + assert_return(!e->default_event_ptr || e->tid == gettid(), -EREMOTEIO); + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + if (e->exit_requested) + goto pending; + + e->iteration++; + + e->state = SD_EVENT_PREPARING; + r = event_prepare(e); + e->state = SD_EVENT_INITIAL; + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->monotonic); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime_alarm); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime_alarm); + if (r < 0) + return r; + + event_close_inode_data_fds(e); + + if (event_next_pending(e) || e->need_process_child) + goto pending; + + e->state = SD_EVENT_ARMED; + + return 0; + +pending: + e->state = SD_EVENT_ARMED; + r = sd_event_wait(e, 0); + if (r == 0) + e->state = SD_EVENT_ARMED; + + return r; +} + +_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { + size_t event_queue_max; + int r, m, i; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_ARMED, -EBUSY); + + if (e->exit_requested) { + e->state = SD_EVENT_PENDING; + return 1; + } + + event_queue_max = MAX(e->n_sources, 1u); + if (!GREEDY_REALLOC(e->event_queue, e->event_queue_allocated, event_queue_max)) + return -ENOMEM; + + /* If we still have inotify data buffered, then query the other fds, but don't wait on it */ + if (e->inotify_data_buffered) + timeout = 0; + + m = epoll_wait(e->epoll_fd, e->event_queue, event_queue_max, + timeout == (uint64_t) -1 ? -1 : (int) DIV_ROUND_UP(timeout, USEC_PER_MSEC)); + if (m < 0) { + if (errno == EINTR) { + e->state = SD_EVENT_PENDING; + return 1; + } + + r = -errno; + goto finish; + } + + triple_timestamp_get(&e->timestamp); + + for (i = 0; i < m; i++) { + + if (e->event_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) + r = flush_timer(e, e->watchdog_fd, e->event_queue[i].events, NULL); + else { + WakeupType *t = e->event_queue[i].data.ptr; + + switch (*t) { + + case WAKEUP_EVENT_SOURCE: { + sd_event_source *s = e->event_queue[i].data.ptr; + + assert(s); + + switch (s->type) { + + case SOURCE_IO: + r = process_io(e, s, e->event_queue[i].events); + break; + + case SOURCE_CHILD: + r = process_pidfd(e, s, e->event_queue[i].events); + break; + + default: + assert_not_reached("Unexpected event source type"); + } + + break; + } + + case WAKEUP_CLOCK_DATA: { + struct clock_data *d = e->event_queue[i].data.ptr; + + assert(d); + + r = flush_timer(e, d->fd, e->event_queue[i].events, &d->next); + break; + } + + case WAKEUP_SIGNAL_DATA: + r = process_signal(e, e->event_queue[i].data.ptr, e->event_queue[i].events); + break; + + case WAKEUP_INOTIFY_DATA: + r = event_inotify_data_read(e, e->event_queue[i].data.ptr, e->event_queue[i].events); + break; + + default: + assert_not_reached("Invalid wake-up pointer"); + } + } + if (r < 0) + goto finish; + } + + r = process_watchdog(e); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.boottime, &e->boottime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm); + if (r < 0) + goto finish; + + if (e->need_process_child) { + r = process_child(e); + if (r < 0) + goto finish; + } + + r = process_inotify(e); + if (r < 0) + goto finish; + + if (event_next_pending(e)) { + e->state = SD_EVENT_PENDING; + + return 1; + } + + r = 0; + +finish: + e->state = SD_EVENT_INITIAL; + + return r; +} + +_public_ int sd_event_dispatch(sd_event *e) { + sd_event_source *p; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_PENDING, -EBUSY); + + if (e->exit_requested) + return dispatch_exit(e); + + p = event_next_pending(e); + if (p) { + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + e->state = SD_EVENT_RUNNING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + return r; + } + + e->state = SD_EVENT_INITIAL; + + return 1; +} + +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1], *p; + size_t l, i; + + p = b; + l = sizeof(b); + for (i = 0; i < ELEMENTSOF(e->delays); i++) { + l = strpcpyf(&p, l, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %s", b); +} + +_public_ int sd_event_run(sd_event *e, uint64_t timeout) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->profile_delays && e->last_run_usec != 0) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run_usec); + assert(l < ELEMENTSOF(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log_usec >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log_usec = this_run; + } + } + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + r = sd_event_prepare(e); + if (r == 0) + /* There was nothing? Then wait... */ + r = sd_event_wait(e, timeout); + + if (e->profile_delays) + e->last_run_usec = now(CLOCK_MONOTONIC); + + if (r > 0) { + /* There's something now, then let's dispatch it */ + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} + +_public_ int sd_event_loop(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = NULL; + + while (e->state != SD_EVENT_FINISHED) { + r = sd_event_run(e, (uint64_t) -1); + if (r < 0) + return r; + } + + return e->exit_code; +} + +_public_ int sd_event_get_fd(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->epoll_fd; +} + +_public_ int sd_event_get_state(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->state; +} + +_public_ int sd_event_get_exit_code(sd_event *e, int *code) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(code, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!e->exit_requested) + return -ENODATA; + + *code = e->exit_code; + return 0; +} + +_public_ int sd_event_exit(sd_event *e, int code) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + e->exit_requested = true; + e->exit_code = code; + + return 0; +} + +_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(usec, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock)) + return -EOPNOTSUPP; + + /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here, + * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for + * the purpose of getting the time this doesn't matter. */ + if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported()) + return -EOPNOTSUPP; + + if (!triple_timestamp_is_set(&e->timestamp)) { + /* Implicitly fall back to now() if we never ran before and thus have no cached time. */ + *usec = now(clock); + return 1; + } + + *usec = triple_timestamp_by_clock(&e->timestamp, clock); + return 0; +} + +_public_ int sd_event_default(sd_event **ret) { + sd_event *e = NULL; + int r; + + if (!ret) + return !!default_event; + + if (default_event) { + *ret = sd_event_ref(default_event); + return 0; + } + + r = sd_event_new(&e); + if (r < 0) + return r; + + e->default_event_ptr = &default_event; + e->tid = gettid(); + default_event = e; + + *ret = e; + return 1; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(tid, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->tid != 0) { + *tid = e->tid; + return 0; + } + + return -ENXIO; +} + +_public_ int sd_event_set_watchdog(sd_event *e, int b) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->watchdog == !!b) + return e->watchdog; + + if (b) { + r = sd_watchdog_enabled(false, &e->watchdog_period); + if (r <= 0) + return r; + + /* Issue first ping immediately */ + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = now(CLOCK_MONOTONIC); + + e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->watchdog_fd < 0) + return -errno; + + r = arm_watchdog(e); + if (r < 0) + goto fail; + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG), + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev) < 0) { + r = -errno; + goto fail; + } + + } else { + if (e->watchdog_fd >= 0) { + (void) epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); + e->watchdog_fd = safe_close(e->watchdog_fd); + } + } + + e->watchdog = !!b; + return e->watchdog; + +fail: + e->watchdog_fd = safe_close(e->watchdog_fd); + return r; +} + +_public_ int sd_event_get_watchdog(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->watchdog; +} + +_public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + *ret = e->iteration; + return 0; +} + +_public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) { + assert_return(s, -EINVAL); + + s->destroy_callback = callback; + return 0; +} + +_public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) { + assert_return(s, -EINVAL); + + if (ret) + *ret = s->destroy_callback; + + return !!s->destroy_callback; +} + +_public_ int sd_event_source_get_floating(sd_event_source *s) { + assert_return(s, -EINVAL); + + return s->floating; +} + +_public_ int sd_event_source_set_floating(sd_event_source *s, int b) { + assert_return(s, -EINVAL); + + if (s->floating == !!b) + return 0; + + if (!s->event) /* Already disconnected */ + return -ESTALE; + + s->floating = b; + + if (b) { + sd_event_source_ref(s); + sd_event_unref(s->event); + } else { + sd_event_ref(s->event); + sd_event_source_unref(s); + } + + return 1; +} + +_public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + + return s->exit_on_failure; +} + +_public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + + if (s->exit_on_failure == !!b) + return 0; + + s->exit_on_failure = b; + return 1; +} + +_public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval, unsigned burst) { + int r; + + assert_return(s, -EINVAL); + + /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing + * so is a programming error. */ + assert_return(EVENT_SOURCE_CAN_RATE_LIMIT(s->type), -EDOM); + + /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh, + * non-ratelimited. */ + r = event_source_leave_ratelimit(s); + if (r < 0) + return r; + + s->rate_limit = (RateLimit) { interval, burst }; + return 0; +} + +_public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) { + assert_return(s, -EINVAL); + + /* Querying whether an event source has ratelimiting configured is not a loggable offsense, hence + * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error */ + if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type)) + return -EDOM; + + if (!ratelimit_configured(&s->rate_limit)) + return -ENOEXEC; + + if (ret_interval) + *ret_interval = s->rate_limit.interval; + if (ret_burst) + *ret_burst = s->rate_limit.burst; + + return 0; +} + +_public_ int sd_event_source_is_ratelimited(sd_event_source *s) { + assert_return(s, -EINVAL); + + if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type)) + return false; + + if (!ratelimit_configured(&s->rate_limit)) + return false; + + return s->ratelimited; +} +#endif /* NM_IGNORED */ diff --git a/src/core/systemd/src/libsystemd/sd-id128/id128-util.c b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c new file mode 100644 index 0000000..b61cfdb --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "fd-util.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "io-util.h" +#include "stdio-util.h" +#include "string-util.h" + +#if 0 /* NM_IGNORED */ +char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) { + unsigned n, k = 0; + + assert(s); + + /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */ + + for (n = 0; n < 16; n++) { + + if (IN_SET(n, 4, 6, 8, 10)) + s[k++] = '-'; + + s[k++] = hexchar(id.bytes[n] >> 4); + s[k++] = hexchar(id.bytes[n] & 0xF); + } + + assert(k == 36); + + s[k] = 0; + + return s; +} + +bool id128_is_valid(const char *s) { + size_t i, l; + + assert(s); + + l = strlen(s); + if (l == 32) { + + /* Plain formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (IN_SET(i, 8, 13, 18, 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; +} +#endif /* NM_IGNORED */ + +int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) { + char buffer[36 + 2]; + ssize_t l; + + assert(fd >= 0); + assert(f < _ID128_FORMAT_MAX); + + /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both + * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they + * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you + * accept". */ + + l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */ + if (l < 0) + return (int) l; + if (l == 0) /* empty? */ + return -ENOMEDIUM; + + switch (l) { + + case 13: + case 14: + /* Treat an "uninitialized" id file like an empty one */ + return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL; + + case 33: /* plain UUID with trailing newline */ + if (buffer[32] != '\n') + return -EINVAL; + + _fallthrough_; + case 32: /* plain UUID without trailing newline */ + if (f == ID128_UUID) + return -EINVAL; + + buffer[32] = 0; + break; + + case 37: /* RFC UUID with trailing newline */ + if (buffer[36] != '\n') + return -EINVAL; + + _fallthrough_; + case 36: /* RFC UUID without trailing newline */ + if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT)) + return -EINVAL; + + buffer[36] = 0; + break; + + default: + return -EINVAL; + } + + return sd_id128_from_string(buffer, ret); +} + +int id128_read(const char *p, Id128Format f, sd_id128_t *ret) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + return id128_read_fd(fd, f, ret); +} + +#if 0 /* NM_IGNORED */ +int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) { + char buffer[36 + 2]; + size_t sz; + int r; + + assert(fd >= 0); + assert(f < _ID128_FORMAT_MAX); + + if (f != ID128_UUID) { + sd_id128_to_string(id, buffer); + buffer[32] = '\n'; + sz = 33; + } else { + id128_to_uuid_string(id, buffer); + buffer[36] = '\n'; + sz = 37; + } + + r = loop_write(fd, buffer, sz, false); + if (r < 0) + return r; + + if (do_sync) { + if (fsync(fd) < 0) + return -errno; + + r = fsync_directory_of_file(fd); + if (r < 0) + return r; + } + + return 0; +} + +int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444); + if (fd < 0) + return -errno; + + return id128_write_fd(fd, f, id, do_sync); +} + +void id128_hash_func(const sd_id128_t *p, struct siphash *state) { + siphash24_compress(p, sizeof(sd_id128_t), state); +} + +int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) { + return memcmp(a, b, 16); +} + +sd_id128_t id128_make_v4_uuid(sd_id128_t id) { + /* Stolen from generate_random_uuid() of drivers/char/random.c + * in the kernel sources */ + + /* Set UUID version to 4 --- truly random generation */ + id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40; + + /* Set the UUID variant to DCE */ + id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80; + + return id; +} + +DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func); +#endif /* NM_IGNORED */ diff --git a/src/core/systemd/src/libsystemd/sd-id128/id128-util.h b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h new file mode 100644 index 0000000..6b09bcd --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-id128.h" + +#include "hash-funcs.h" +#include "macro.h" + +#define ID128_UUID_STRING_MAX 37 + +char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]); + +bool id128_is_valid(const char *s) _pure_; + +typedef enum Id128Format { + ID128_ANY, + ID128_PLAIN, /* formatted as 32 hex chars as-is */ + ID128_PLAIN_OR_UNINIT, /* formatted as 32 hex chars as-is; allow special "uninitialized" + * value when reading from file (id128_read() and id128_read_fd()). + * + * This format should be used when reading a machine-id file. */ + ID128_UUID, /* formatted as 36 character uuid string */ + _ID128_FORMAT_MAX, +} Id128Format; + +int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret); +int id128_read(const char *p, Id128Format f, sd_id128_t *ret); + +int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync); +int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync); + +void id128_hash_func(const sd_id128_t *p, struct siphash *state); +int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_; +extern const struct hash_ops id128_hash_ops; + +sd_id128_t id128_make_v4_uuid(sd_id128_t id); diff --git a/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c new file mode 100644 index 0000000..64b8c78 --- /dev/null +++ b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include +#include +#include + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "io-util.h" +#include "khash.h" +#include "macro.h" +#include "missing_syscall.h" +#include "random-util.h" +#include "user-util.h" +#include "util.h" + +#if 0 /* NM_IGNORED */ +_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) { + unsigned n; + + assert_return(s, NULL); + + for (n = 0; n < 16; n++) { + s[n*2] = hexchar(id.bytes[n] >> 4); + s[n*2+1] = hexchar(id.bytes[n] & 0xF); + } + + s[32] = 0; + + return s; +} +#endif /* NM_IGNORED */ + +_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { + unsigned n, i; + sd_id128_t t; + bool is_guid = false; + + assert_return(s, -EINVAL); + + for (n = 0, i = 0; n < 16;) { + int a, b; + + if (s[i] == '-') { + /* Is this a GUID? Then be nice, and skip over + * the dashes */ + + if (i == 8) + is_guid = true; + else if (IN_SET(i, 13, 18, 23)) { + if (!is_guid) + return -EINVAL; + } else + return -EINVAL; + + i++; + continue; + } + + a = unhexchar(s[i++]); + if (a < 0) + return -EINVAL; + + b = unhexchar(s[i++]); + if (b < 0) + return -EINVAL; + + t.bytes[n++] = (a << 4) | b; + } + + if (i != (is_guid ? 36 : 32)) + return -EINVAL; + + if (s[i] != 0) + return -EINVAL; + + if (ret) + *ret = t; + return 0; +} + +_public_ int sd_id128_get_machine(sd_id128_t *ret) { + static thread_local sd_id128_t saved_machine_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_machine_id)) { + r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id); + if (r < 0) + return r; + + if (sd_id128_is_null(saved_machine_id)) + return -ENOMEDIUM; + } + + *ret = saved_machine_id; + return 0; +} + +_public_ int sd_id128_get_boot(sd_id128_t *ret) { + static thread_local sd_id128_t saved_boot_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_boot_id)) { + r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id); + if (r < 0) + return r; + } + + *ret = saved_boot_id; + return 0; +} + +#if 0 /* NM_IGNORED */ +static int get_invocation_from_keyring(sd_id128_t *ret) { + _cleanup_free_ char *description = NULL; + char *d, *p, *g, *u, *e; + unsigned long perms; + key_serial_t key; + size_t sz = 256; + uid_t uid; + gid_t gid; + int r, c; + +#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \ + KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH)) + + assert(ret); + + key = request_key("user", "invocation_id", NULL, 0); + if (key == -1) { + /* Keyring support not available? No invocation key stored? */ + if (IN_SET(errno, ENOSYS, ENOKEY)) + return -ENXIO; + + return -errno; + } + + for (;;) { + description = new(char, sz); + if (!description) + return -ENOMEM; + + c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0); + if (c < 0) + return -errno; + + if ((size_t) c <= sz) + break; + + sz = c; + free(description); + } + + /* The kernel returns a final NUL in the string, verify that. */ + assert(description[c-1] == 0); + + /* Chop off the final description string */ + d = strrchr(description, ';'); + if (!d) + return -EIO; + *d = 0; + + /* Look for the permissions */ + p = strrchr(description, ';'); + if (!p) + return -EIO; + + errno = 0; + perms = strtoul(p + 1, &e, 16); + if (errno > 0) + return -errno; + if (e == p + 1) /* Read at least one character */ + return -EIO; + if (e != d) /* Must reached the end */ + return -EIO; + + if ((perms & ~MAX_PERMS) != 0) + return -EPERM; + + *p = 0; + + /* Look for the group ID */ + g = strrchr(description, ';'); + if (!g) + return -EIO; + r = parse_gid(g + 1, &gid); + if (r < 0) + return r; + if (gid != 0) + return -EPERM; + *g = 0; + + /* Look for the user ID */ + u = strrchr(description, ';'); + if (!u) + return -EIO; + r = parse_uid(u + 1, &uid); + if (r < 0) + return r; + if (uid != 0) + return -EPERM; + + c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0); + if (c < 0) + return -errno; + if (c != sizeof(sd_id128_t)) + return -EIO; + + return 0; +} + +static int get_invocation_from_environment(sd_id128_t *ret) { + const char *e; + + assert(ret); + + e = secure_getenv("INVOCATION_ID"); + if (!e) + return -ENXIO; + + return sd_id128_from_string(e, ret); +} + +_public_ int sd_id128_get_invocation(sd_id128_t *ret) { + static thread_local sd_id128_t saved_invocation_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_invocation_id)) { + /* We first check the environment. The environment variable is primarily relevant for user + * services, and sufficiently safe as long as no privilege boundary is involved. */ + r = get_invocation_from_environment(&saved_invocation_id); + if (r < 0 && r != -ENXIO) + return r; + + /* The kernel keyring is relevant for system services (as for user services we don't store + * the invocation ID in the keyring, as there'd be no trust benefit in that). */ + r = get_invocation_from_keyring(&saved_invocation_id); + if (r < 0) + return r; + } + + *ret = saved_invocation_id; + return 0; +} + +_public_ int sd_id128_randomize(sd_id128_t *ret) { + sd_id128_t t; + int r; + + assert_return(ret, -EINVAL); + + /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be + * fine for UUIDS. */ + r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND); + if (r < 0) + return r; + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones. */ + + *ret = id128_make_v4_uuid(t); + return 0; +} + +static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) { + _cleanup_(khash_unrefp) khash *h = NULL; + sd_id128_t result; + const void *p; + int r; + + assert(ret); + + r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base)); + if (r < 0) + return r; + + r = khash_put(h, &app_id, sizeof(app_id)); + if (r < 0) + return r; + + r = khash_digest_data(h, &p); + if (r < 0) + return r; + + /* We chop off the trailing 16 bytes */ + memcpy(&result, p, MIN(khash_get_size(h), sizeof(result))); + + *ret = id128_make_v4_uuid(result); + return 0; +} + +_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return get_app_specific(id, app_id, ret); +} + +_public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return get_app_specific(id, app_id, ret); +} +#endif /* NM_IGNORED */ diff --git a/src/core/systemd/src/systemd/_sd-common.h b/src/core/systemd/src/systemd/_sd-common.h new file mode 100644 index 0000000..e3de2ae --- /dev/null +++ b/src/core/systemd/src/systemd/_sd-common.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdcommonhfoo +#define foosdcommonhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +/* This is a private header; never even think of including this directly! */ + +#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ <= 1 && !defined(__COVERITY__) +# error "Do not include _sd-common.h directly; it is a private header." +#endif + +typedef void (*_sd_destroy_t)(void *userdata); + +#ifndef _sd_printf_ +# if __GNUC__ >= 4 +# define _sd_printf_(a,b) __attribute__((__format__(printf, a, b))) +# else +# define _sd_printf_(a,b) +# endif +#endif + +#ifndef _sd_sentinel_ +# define _sd_sentinel_ __attribute__((__sentinel__)) +#endif + +#ifndef _sd_packed_ +# define _sd_packed_ __attribute__((__packed__)) +#endif + +#ifndef _sd_pure_ +# define _sd_pure_ __attribute__((__pure__)) +#endif + +/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6 + * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway + * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers + * are probably going to use something newer anyway. */ +#ifndef _sd_deprecated_ +# if __GNUC__ >= 6 +# define _sd_deprecated_ __attribute__((__deprecated__)) +# else +# define _sd_deprecated_ +# endif +#endif + +#ifndef _SD_STRINGIFY +# define _SD_XSTRINGIFY(x) #x +# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) +#endif + +#ifndef _SD_BEGIN_DECLARATIONS +# ifdef __cplusplus +# define _SD_BEGIN_DECLARATIONS \ + extern "C" { \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# else +# define _SD_BEGIN_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_END_DECLARATIONS +# ifdef __cplusplus +# define _SD_END_DECLARATIONS \ + } \ + struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ +# else +# define _SD_END_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_ARRAY_STATIC +# if __STDC_VERSION__ >= 199901L +# define _SD_ARRAY_STATIC static +# else +# define _SD_ARRAY_STATIC +# endif +#endif + +#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ + static __inline__ void func##p(type **p) { \ + if (*p) \ + func(*p); \ + } \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp-client.h b/src/core/systemd/src/systemd/sd-dhcp-client.h new file mode 100644 index 0000000..c35328a --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp-client.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpclienthfoo +#define foosddhcpclienthfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "sd-dhcp-lease.h" +#include "sd-dhcp-option.h" +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP_CLIENT_EVENT_STOP = 0, + SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, + SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, + SD_DHCP_CLIENT_EVENT_EXPIRED = 3, + SD_DHCP_CLIENT_EVENT_RENEW = 4, + SD_DHCP_CLIENT_EVENT_SELECTING = 5, + SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE = 6, /* Sent when we have not received a reply after the first few attempts. + * The client may want to start acquiring link-local addresses. */ +}; + +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_LPR_SERVER = 9, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_ROUTER_DISCOVER = 31, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER = 44, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NODETYPE = 46, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_SCOPE = 47, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_SMTP_SERVER = 69, + SD_DHCP_OPTION_POP3_SERVER = 70, + SD_DHCP_OPTION_USER_CLASS = 77, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_DOMAIN_SEARCH_LIST = 119, + SD_DHCP_OPTION_SIP_SERVER = 120, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_MUD_URL = 161, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + +typedef struct sd_dhcp_client sd_dhcp_client; + +typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata); +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata); + +int sd_dhcp_client_set_request_option( + sd_dhcp_client *client, + uint8_t option); +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_address); +int sd_dhcp_client_set_request_broadcast( + sd_dhcp_client *client, + int broadcast); +int sd_dhcp_client_set_ifindex( + sd_dhcp_client *client, + int interface_index); +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + const uint8_t *bcast_addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len); +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_set_iaid_duid_llt( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint64_t llt_time); +int sd_dhcp_client_set_duid( + sd_dhcp_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_set_duid_llt( + sd_dhcp_client *client, + uint64_t llt_time); +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len); +int sd_dhcp_client_set_mtu( + sd_dhcp_client *client, + uint32_t mtu); +int sd_dhcp_client_set_max_attempts( + sd_dhcp_client *client, + uint64_t attempt); +int sd_dhcp_client_set_client_port( + sd_dhcp_client *client, + uint16_t port); +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname); +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci); +int sd_dhcp_client_set_mud_url( + sd_dhcp_client *client, + const char *mudurl); +int sd_dhcp_client_set_user_class( + sd_dhcp_client *client, + const char* const *user_class); +int sd_dhcp_client_get_lease( + sd_dhcp_client *client, + sd_dhcp_lease **ret); +int sd_dhcp_client_set_service_type( + sd_dhcp_client *client, + int type); +int sd_dhcp_client_set_fallback_lease_lifetime( + sd_dhcp_client *client, + uint32_t fallback_lease_lifetime); + +int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v); +int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v); + +int sd_dhcp_client_stop(sd_dhcp_client *client); +int sd_dhcp_client_start(sd_dhcp_client *client); +int sd_dhcp_client_send_release(sd_dhcp_client *client); +int sd_dhcp_client_send_decline(sd_dhcp_client *client); +int sd_dhcp_client_send_renew(sd_dhcp_client *client); + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); +sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); + +/* NOTE: anonymize parameter is used to initialize PRL memory with different + * options when using RFC7844 Anonymity Profiles */ +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); + +int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret); + +int sd_dhcp_client_attach_event( + sd_dhcp_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp_client_detach_event(sd_dhcp_client *client); +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp-lease.h b/src/core/systemd/src/systemd/sd-dhcp-lease.h new file mode 100644 index 0000000..c255a1f --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp-lease.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpleasehfoo +#define foosddhcpleasehfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_route sd_dhcp_route; + +sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); +sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + +typedef enum sd_dhcp_lease_server_type { + SD_DHCP_LEASE_DNS, + SD_DHCP_LEASE_NTP, + SD_DHCP_LEASE_SIP, + SD_DHCP_LEASE_POP3, + SD_DHCP_LEASE_SMTP, + SD_DHCP_LEASE_LPR, + _SD_DHCP_LEASE_SERVER_TYPE_MAX, + _SD_DHCP_LEASE_SERVER_TYPE_INVALID = -1, +} sd_dhcp_lease_server_type; + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type what, const struct in_addr **addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); +int sd_dhcp_route_get_option(sd_dhcp_route *route); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp-option.h b/src/core/systemd/src/systemd/sd-dhcp-option.h new file mode 100644 index 0000000..71aa479 --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp-option.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpoptionhfoo +#define foosddhcpoptionhfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_option sd_dhcp_option; + +int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret); +sd_dhcp_option *sd_dhcp_option_ref(sd_dhcp_option *ra); +sd_dhcp_option *sd_dhcp_option_unref(sd_dhcp_option *ra); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp6-client.h b/src/core/systemd/src/systemd/sd-dhcp6-client.h new file mode 100644 index 0000000..75ee27d --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp6-client.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6clienthfoo +#define foosddhcp6clienthfoo + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "sd-dhcp6-lease.h" +#include "sd-dhcp6-option.h" +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP6_CLIENT_EVENT_STOP = 0, + SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, + SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, + SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, + SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, +}; + +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + SD_DHCP6_OPTION_IA_PD = 25, /* RFC 3633, prefix delegation */ + SD_DHCP6_OPTION_IA_PD_PREFIX = 26, /* RFC 3633, prefix delegation */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME = 32, /* RFC 8415, sec. 21.23 */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_FQDN = 39, /* RFC 4704 */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + SD_DHCP6_OPTION_MUD_URL = 112, /* RFC 8250 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + +typedef struct sd_dhcp6_client sd_dhcp6_client; + +typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata); +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata); + +int sd_dhcp6_client_set_ifindex( + sd_dhcp6_client *client, + int interface_index); +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address); +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp6_client_set_duid_llt( + sd_dhcp6_client *client, + uint64_t llt_time); +int sd_dhcp6_client_set_iaid( + sd_dhcp6_client *client, + uint32_t iaid); +int sd_dhcp6_client_get_iaid( + sd_dhcp6_client *client, + uint32_t *iaid); +int sd_dhcp6_client_duid_as_string( + sd_dhcp6_client *client, + char **duid); +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn); +int sd_dhcp6_client_set_information_request( + sd_dhcp6_client *client, + int enabled); +int sd_dhcp6_client_get_information_request( + sd_dhcp6_client *client, + int *enabled); +int sd_dhcp6_client_set_request_option( + sd_dhcp6_client *client, + uint16_t option); +int sd_dhcp6_client_set_request_mud_url( + sd_dhcp6_client *client, + const char *mudurl); +int sd_dhcp6_client_set_request_user_class( + sd_dhcp6_client *client, + char** user_class); +int sd_dhcp6_client_set_request_vendor_class( + sd_dhcp6_client *client, + char** vendor_class); +int sd_dhcp6_client_set_prefix_delegation_hint( + sd_dhcp6_client *client, + uint8_t prefixlen, + const struct in6_addr *pd_address); +int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, + int *delegation); +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, + int delegation); +int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, + int *request); +int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, + int request); +int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, + uint32_t transaction_id); +int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, + sd_dhcp6_option *v); + +int sd_dhcp6_client_get_lease( + sd_dhcp6_client *client, + sd_dhcp6_lease **ret); + +int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v); + +int sd_dhcp6_client_stop(sd_dhcp6_client *client); +int sd_dhcp6_client_start(sd_dhcp6_client *client); +int sd_dhcp6_client_is_running(sd_dhcp6_client *client); +int sd_dhcp6_client_attach_event( + sd_dhcp6_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); +int sd_dhcp6_client_new(sd_dhcp6_client **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp6-lease.h b/src/core/systemd/src/systemd/sd-dhcp6-lease.h new file mode 100644 index 0000000..f77b31a --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp6-lease.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6leasehfoo +#define foosddhcp6leasehfoo + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_lease sd_dhcp6_lease; + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, + struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn); + +sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); +sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-dhcp6-option.h b/src/core/systemd/src/systemd/sd-dhcp6-option.h new file mode 100644 index 0000000..ddb2c7c --- /dev/null +++ b/src/core/systemd/src/systemd/sd-dhcp6-option.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6optionhfoo +#define foosddhcp6optionhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_option sd_dhcp6_option; + +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret); +sd_dhcp6_option *sd_dhcp6_option_ref(sd_dhcp6_option *ra); +sd_dhcp6_option *sd_dhcp6_option_unref(sd_dhcp6_option *ra); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_option, sd_dhcp6_option_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-event.h b/src/core/systemd/src/systemd/sd-event.h new file mode 100644 index 0000000..2ae2a0d --- /dev/null +++ b/src/core/systemd/src/systemd/sd-event.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdeventhfoo +#define foosdeventhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +/* + Why is this better than pure epoll? + + - Supports event source prioritization + - Scales better with a large number of time events because it does not require one timerfd each + - Automatically tries to coalesce timer events system-wide + - Handles signals, child PIDs, inotify events + - Supports systemd-style automatic watchdog event generation +*/ + +_SD_BEGIN_DECLARATIONS; + +#define SD_EVENT_DEFAULT ((sd_event *) 1) + +typedef struct sd_event sd_event; +typedef struct sd_event_source sd_event_source; + +enum { + SD_EVENT_OFF = 0, + SD_EVENT_ON = 1, + SD_EVENT_ONESHOT = -1 +}; + +enum { + SD_EVENT_INITIAL, + SD_EVENT_ARMED, + SD_EVENT_PENDING, + SD_EVENT_RUNNING, + SD_EVENT_EXITING, + SD_EVENT_FINISHED, + SD_EVENT_PREPARING +}; + +enum { + /* And everything in-between and outside is good too */ + SD_EVENT_PRIORITY_IMPORTANT = -100, + SD_EVENT_PRIORITY_NORMAL = 0, + SD_EVENT_PRIORITY_IDLE = 100 +}; + +typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); +typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); +typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); +typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); +#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L) +typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); +#else +typedef void* sd_event_child_handler_t; +#endif +typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata); +typedef _sd_destroy_t sd_event_destroy_t; + +int sd_event_default(sd_event **e); + +int sd_event_new(sd_event **e); +sd_event* sd_event_ref(sd_event *e); +sd_event* sd_event_unref(sd_event *e); + +int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); +int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_time_relative(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); +int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_child_pidfd(sd_event *e, sd_event_source **s, int pidfd, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata); +int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); + +int sd_event_prepare(sd_event *e); +int sd_event_wait(sd_event *e, uint64_t usec); +int sd_event_dispatch(sd_event *e); +int sd_event_run(sd_event *e, uint64_t usec); +int sd_event_loop(sd_event *e); +int sd_event_exit(sd_event *e, int code); + +int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); + +int sd_event_get_fd(sd_event *e); +int sd_event_get_state(sd_event *e); +int sd_event_get_tid(sd_event *e, pid_t *tid); +int sd_event_get_exit_code(sd_event *e, int *code); +int sd_event_set_watchdog(sd_event *e, int b); +int sd_event_get_watchdog(sd_event *e); +int sd_event_get_iteration(sd_event *e, uint64_t *ret); + +sd_event_source* sd_event_source_ref(sd_event_source *s); +sd_event_source* sd_event_source_unref(sd_event_source *s); +sd_event_source* sd_event_source_disable_unref(sd_event_source *s); + +sd_event *sd_event_source_get_event(sd_event_source *s); +void* sd_event_source_get_userdata(sd_event_source *s); +void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); + +int sd_event_source_set_description(sd_event_source *s, const char *description); +int sd_event_source_get_description(sd_event_source *s, const char **description); +int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); +int sd_event_source_get_pending(sd_event_source *s); +int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); +int sd_event_source_set_priority(sd_event_source *s, int64_t priority); +int sd_event_source_get_enabled(sd_event_source *s, int *enabled); +int sd_event_source_set_enabled(sd_event_source *s, int enabled); +int sd_event_source_get_io_fd(sd_event_source *s); +int sd_event_source_set_io_fd(sd_event_source *s, int fd); +int sd_event_source_get_io_fd_own(sd_event_source *s); +int sd_event_source_set_io_fd_own(sd_event_source *s, int own); +int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); +int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); +int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); +int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time(sd_event_source *s, uint64_t usec); +int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); +int sd_event_source_get_signal(sd_event_source *s); +int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); +int sd_event_source_get_child_pidfd(sd_event_source *s); +int sd_event_source_get_child_pidfd_own(sd_event_source *s); +int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own); +int sd_event_source_get_child_process_own(sd_event_source *s); +int sd_event_source_set_child_process_own(sd_event_source *s, int own); +#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L) +int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags); +#else +int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags); +#endif +int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); +int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); +int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); +int sd_event_source_get_floating(sd_event_source *s); +int sd_event_source_set_floating(sd_event_source *s, int b); +int sd_event_source_get_exit_on_failure(sd_event_source *s); +int sd_event_source_set_exit_on_failure(sd_event_source *s, int b); +int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst); +int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst); +int sd_event_source_is_ratelimited(sd_event_source *s); + +/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_disable_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-id128.h b/src/core/systemd/src/systemd/sd-id128.h new file mode 100644 index 0000000..02aa318 --- /dev/null +++ b/src/core/systemd/src/systemd/sd-id128.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdid128hfoo +#define foosdid128hfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* 128-bit ID APIs. See sd-id128(3) for more information. */ + +typedef union sd_id128 sd_id128_t; + +union sd_id128 { + uint8_t bytes[16]; + uint64_t qwords[2]; +}; + +#define SD_ID128_STRING_MAX 33 + +char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]); +int sd_id128_from_string(const char *s, sd_id128_t *ret); + +int sd_id128_randomize(sd_id128_t *ret); + +int sd_id128_get_machine(sd_id128_t *ret); +int sd_id128_get_boot(sd_id128_t *ret); +int sd_id128_get_invocation(sd_id128_t *ret); + +int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret); +int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret); + +#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} + +#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + ((const sd_id128_t) SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)) + +/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 + * times. It is hence not a good idea to call this macro with an + * expensive function as parameter or an expression with side + * effects */ + +#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" +#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */ +#define SD_ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" + +#define SD_ID128_CONST_STR(x) \ + ((const char[SD_ID128_STRING_MAX]) { \ + ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ + ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ + ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ + ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ + ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ + ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ + ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ + ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ + ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ + ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ + ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ + ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ + ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ + ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ + ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ + ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ + ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ + ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ + ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ + ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ + ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ + ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ + ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ + ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ + ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ + ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ + ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ + ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ + ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ + ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ + ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ + ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ + 0 }) + +#define SD_ID128_MAKE_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + #a #b #c #d #e #f #g #h #i #j #k #l #m #n #o #p + +_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { + return memcmp(&a, &b, 16) == 0; +} + +_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { + return a.qwords[0] == 0 && a.qwords[1] == 0; +} + +_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { + return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF); +} + +#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) +#define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }}) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-ipv4acd.h b/src/core/systemd/src/systemd/sd-ipv4acd.h new file mode 100644 index 0000000..2809d87 --- /dev/null +++ b/src/core/systemd/src/systemd/sd-ipv4acd.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4ACD_EVENT_STOP = 0, + SD_IPV4ACD_EVENT_BIND = 1, + SD_IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd); +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority); +int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr); +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index); +int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd); +const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd); +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address); +int sd_ipv4acd_is_running(sd_ipv4acd *acd); +int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts); +int sd_ipv4acd_stop(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd); +int sd_ipv4acd_new(sd_ipv4acd **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-ipv4ll.h b/src/core/systemd/src/systemd/sd-ipv4ll.h new file mode 100644 index 0000000..aa4d174 --- /dev/null +++ b/src/core/systemd/src/systemd/sd-ipv4ll.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdipv4llfoo +#define foosdipv4llfoo + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4LL_EVENT_STOP = 0, + SD_IPV4LL_EVENT_BIND = 1, + SD_IPV4LL_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4ll sd_ipv4ll; +typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll); +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index); +int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll); +const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll); +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed); +int sd_ipv4ll_is_running(sd_ipv4ll *ll); +int sd_ipv4ll_restart(sd_ipv4ll *ll); +int sd_ipv4ll_start(sd_ipv4ll *ll); +int sd_ipv4ll_stop(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); +int sd_ipv4ll_new(sd_ipv4ll **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-lldp.h b/src/core/systemd/src/systemd/sd-lldp.h new file mode 100644 index 0000000..f551f6b --- /dev/null +++ b/src/core/systemd/src/systemd/sd-lldp.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdlldphfoo +#define foosdlldphfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* IEEE 802.1AB-2009 Clause 8: TLV Types */ +enum { + SD_LLDP_TYPE_END = 0, + SD_LLDP_TYPE_CHASSIS_ID = 1, + SD_LLDP_TYPE_PORT_ID = 2, + SD_LLDP_TYPE_TTL = 3, + SD_LLDP_TYPE_PORT_DESCRIPTION = 4, + SD_LLDP_TYPE_SYSTEM_NAME = 5, + SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6, + SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7, + SD_LLDP_TYPE_MGMT_ADDRESS = 8, + SD_LLDP_TYPE_PRIVATE = 127, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */ +enum { + SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0, + SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, + SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, + SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, + SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */ +enum { + SD_LLDP_PORT_SUBTYPE_RESERVED = 0, + SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, + SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, + SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, + SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4, + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, + SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, + SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */ +enum { + SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, + SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, + SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, + SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, + SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, +}; + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1) + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \ + ((uint16_t) \ + (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER| \ + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE| \ + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \ + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER| \ + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS| \ + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_TPMR)) + +#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } +#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } + +#define SD_LLDP_OUI_MUD (uint8_t[]) { 0x00, 0x00, 0x5E } +#define SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION 0x01 + +/* IEEE 802.1AB-2009 Annex E */ +enum { + SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1, + SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2, + SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3, + SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4, + SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5, + SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6, + SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, +}; + +/* IEEE 802.1AB-2009 Annex F */ +enum { + SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1, + SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI = 2, + SD_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION = 3, + SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE = 4, +}; + +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_neighbor sd_lldp_neighbor; + +typedef enum sd_lldp_event { + SD_LLDP_EVENT_ADDED, + SD_LLDP_EVENT_REMOVED, + SD_LLDP_EVENT_UPDATED, + SD_LLDP_EVENT_REFRESHED, + _SD_LLDP_EVENT_MAX, + _SD_LLDP_EVENT_INVALID = -1, +} sd_lldp_event; + +typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata); + +int sd_lldp_new(sd_lldp **ret); +sd_lldp* sd_lldp_ref(sd_lldp *lldp); +sd_lldp* sd_lldp_unref(sd_lldp *lldp); + +int sd_lldp_start(sd_lldp *lldp); +int sd_lldp_stop(sd_lldp *lldp); + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); +int sd_lldp_detach_event(sd_lldp *lldp); +sd_event *sd_lldp_get_event(sd_lldp *lldp); + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); +int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex); + +/* Controls how much and what to store in the neighbors database */ +int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); +int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask); +int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address); + +int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors); + +int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); +sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); +sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); + +/* Access to LLDP frame metadata */ +int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); +int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ +int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec); +int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret); + +/* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs + * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */ +int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type); +int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type); +int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype); +int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype); +int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/systemd/src/systemd/sd-ndisc.h b/src/core/systemd/src/systemd/sd-ndisc.h new file mode 100644 index 0000000..c0e3789 --- /dev/null +++ b/src/core/systemd/src/systemd/sd-ndisc.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscfoo +#define foosdndiscfoo + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Neighbor Discovery Options, RFC 4861, Section 4.6 and + * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ +enum { + SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, + SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, + SD_NDISC_OPTION_PREFIX_INFORMATION = 3, + SD_NDISC_OPTION_MTU = 5, + SD_NDISC_OPTION_ROUTE_INFORMATION = 24, + SD_NDISC_OPTION_RDNSS = 25, + SD_NDISC_OPTION_FLAGS_EXTENSION = 26, + SD_NDISC_OPTION_DNSSL = 31, + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, +}; + +/* Route preference, RFC 4191, Section 2.1 */ +enum { + SD_NDISC_PREFERENCE_LOW = 3U, + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, +}; + +typedef struct sd_ndisc sd_ndisc; +typedef struct sd_ndisc_router sd_ndisc_router; + +typedef enum sd_ndisc_event { + SD_NDISC_EVENT_TIMEOUT, + SD_NDISC_EVENT_ROUTER, + _SD_NDISC_EVENT_MAX, + _SD_NDISC_EVENT_INVALID = -1, +} sd_ndisc_event; + +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata); + +int sd_ndisc_new(sd_ndisc **ret); +sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); +sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); + +int sd_ndisc_start(sd_ndisc *nd); +int sd_ndisc_stop(sd_ndisc *nd); + +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); +int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); + +int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret); +int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret); + +int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size); +sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); +sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); + +int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime); +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); + +/* Generic option access */ +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); +int sd_ndisc_router_option_next(sd_ndisc_router *rt); +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ +int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); + +/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ +int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); + +/* Specific option access: SD_NDISC_OPTION_RDNSS */ +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); +int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +/* Specific option access: SD_NDISC_OPTION_DNSSL */ +int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); +int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/core/tests/config/NetworkManager-warn.conf b/src/core/tests/config/NetworkManager-warn.conf new file mode 100644 index 0000000..80df7c5 --- /dev/null +++ b/src/core/tests/config/NetworkManager-warn.conf @@ -0,0 +1,26 @@ +[main] +dhcp=dhclient +plugin=foo,bar,baz +no-auto-default=11:11:11:11:11:11 +rc-managed=unmanaged +dns=none + +[logging] +level=INFO + +[connectivity] +uri=http://example.com +interval=100 +response=Hello +audit=true + +[connection] +ipv4.route-metric=50 +ipv4.addresses=1.2.3.4 +ipv4.dad-timeout=100 + +[connection-wifi] +match-device=type:wifi +wifi.powersave=2 +ipv6.ip6-privacy=1 +wifi.tx-power=99 diff --git a/src/core/tests/config/NetworkManager.conf b/src/core/tests/config/NetworkManager.conf new file mode 100644 index 0000000..a447b6d --- /dev/null +++ b/src/core/tests/config/NetworkManager.conf @@ -0,0 +1,99 @@ +[main] +dhcp=dhclient +plugins=foo,bar,baz +no-auto-default=11:11:11:11:11:11 + +[logging] +level=INFO + +[connectivity] +uri=http://example.com +interval=100 +response=Hello + +[extra-section] +extra-key=some value + + + +[connection] +ipv4.route-metric=50 +ipv6.ip6_privacy=0 +ethernet.mtu=1400 +ipv4.dns-priority=60 + +ord.key00=A-0.0.00 +ord.key01=A-0.0.01 +ord.key02=A-0.0.02 +ord.key03=A-0.0.03 +ord.key04=A-0.0.04 +ord.key05=A-0.0.05 +ord.key06=A-0.0.06 +ord.key07=A-0.0.07 +ord.key08=A-0.0.08 +ord.key09=A-0.0.09 + +[connection.dev51] +match-device=mac:00:00:00:00:00:51 +stop-match=yes +ipv4.route-metric=51 +ethernet.mtu=9000 + +[connection.dev52] +match-device=mac:00:00:00:00:00:52 +ipv4.route-metric=52 + +[connection.public] +match-device=interface-name:wlan1 +# match-wifi is not yet implemented. Just an idea what could be useful. +match-wifi=ssid:*[Ss]tarbucks*|*University* +ipv6.ip6_privacy=2 + + +# the following sections are tested for their order across +# multiple files. +[connection.ord.0.1] +ord.key03=A-0.1.03 +ord.key04=A-0.1.04 +ord.key05=A-0.1.05 +ord.key06=A-0.1.06 +ord.key07=A-0.1.07 +ord.key08=A-0.1.08 +ord.key09=A-0.1.09 +ord.ovw01=A-0.1.ovw01 +[connection.ord.0.2] +ord.key02=A-0.2.02 +ord.key03=A-0.2.03 +ord.key04=A-0.2.04 +ord.key05=A-0.2.05 +ord.key06=A-0.2.06 +ord.key07=A-0.2.07 +ord.key08=A-0.2.08 +ord.key09=A-0.2.09 +[connection.ord.0.3] +ord.key01=A-0.3.01 +ord.key02=A-0.3.02 +ord.key03=A-0.3.03 +ord.key04=A-0.3.04 +ord.key05=A-0.3.05 +ord.key06=A-0.3.06 +ord.key07=A-0.3.07 +ord.key08=A-0.3.08 +ord.key09=A-0.3.09 +ord.ovw01=A-0.3.ovw01 + +[global-dns] +enable=yes +searches=foo.com,bar.org +options=debug,edns0 + +[global-dns-domain-*] +servers=1.1.1.1,bad,1::128 +options=opt1,opt2 + +[global-dns-domain-example.com] +servers=2.2.2.2 + +# Invalid section: 'servers' key is missing +[global-dns-domain-test.com] +options=opt3 diff --git a/src/core/tests/config/NetworkManager.state b/src/core/tests/config/NetworkManager.state new file mode 100644 index 0000000..1b9d604 --- /dev/null +++ b/src/core/tests/config/NetworkManager.state @@ -0,0 +1,4 @@ +[main] +NetworkingEnabled=true +WirelessEnabled=true +WWANEnabled=true diff --git a/src/core/tests/config/bad.conf b/src/core/tests/config/bad.conf new file mode 100644 index 0000000..20d6a67 --- /dev/null +++ b/src/core/tests/config/bad.conf @@ -0,0 +1 @@ +This is not a keyfile. diff --git a/src/core/tests/config/conf.d/00-overrides.conf b/src/core/tests/config/conf.d/00-overrides.conf new file mode 100644 index 0000000..f26ed93 --- /dev/null +++ b/src/core/tests/config/conf.d/00-overrides.conf @@ -0,0 +1,57 @@ +[main] +dhcp=dhcpcd + +no-auto-default=spec1,spec2 +ignore-carrier=\s space1 \s + +[logging] +domains=PLATFORM,DNS,WIFI + +[appendable-test] +non-appendable-key1+=i-will-be-dropped +non-appendable-key2-=i-will-be-dropped + +[order] +a=0 +b=0 +c=0 + + +# the following sections are tested for their order across +# multiple files. +[connection.ord.1.1] +ord.key06=B-1.1.06 +ord.key07=B-1.1.07 +ord.key08=B-1.1.08 +ord.key09=B-1.1.09 +[connection.ord.1.2] +ord.key05=B-1.2.05 +ord.key06=B-1.2.06 +ord.key07=B-1.2.07 +ord.key08=B-1.2.08 +ord.key09=B-1.2.09 +[connection.ord.1.3] +ord.key04=B-1.3.04 +ord.key05=B-1.3.05 +ord.key06=B-1.3.06 +ord.key07=B-1.3.07 +ord.key08=B-1.3.08 +ord.key09=B-1.3.09 + + +[.test-append-stringlist.1] +val1=a,b + +val2-=VAL2 +val2=VAL2 + +val3=VAL3 +val3-=VAL3 + +val4=VAL4 +val4+=VAL4,va,vb,va,vb +val4-=VAL4,va + +val5=VAL5 +val5-=VAL5 +val5+=VAL5 diff --git a/src/core/tests/config/conf.d/10-more.conf b/src/core/tests/config/conf.d/10-more.conf new file mode 100644 index 0000000..eadb7f9 --- /dev/null +++ b/src/core/tests/config/conf.d/10-more.conf @@ -0,0 +1,40 @@ +[main] +extra=hello + +no-auto-default-=spec1 +no-auto-default+=spec3 + +ignore-carrier+=\sspace2\t + +[.test-append-stringlist.0] +new+=something + +[connectivity] +uri=http://example.net + +[order] +a=10 +b=10 + +# the following sections are tested for their order across +# multiple files. +[connection.ord.2.1] +ord.key09=C-2.1.09 +[connection.ord.2.2] +ord.key08=C-2.2.08 +ord.key09=C-2.2.09 +[connection.ord.2.3] +ord.key07=C-2.3.07 +ord.key08=C-2.3.08 +ord.key09=C-2.3.09 + +# you can overwrite individual settings in a file loaded +# previously. But note that this does not bump the priority +# of the section, i.e. [connection.ord.0.1] still has a pretty +# low priority and is shadowed by [connection.ord.2.1]. +[connection.ord.0.1] +ord.ovw01=C-0.1.ovw01 + + +[.test-append-stringlist.1] +val1-=b diff --git a/src/core/tests/config/conf.d/20-config-enable-1.conf b/src/core/tests/config/conf.d/20-config-enable-1.conf new file mode 100644 index 0000000..e6800f5 --- /dev/null +++ b/src/core/tests/config/conf.d/20-config-enable-1.conf @@ -0,0 +1,5 @@ +[.config] +enable=nm-version:1.5.32,env:test-match-env-1 + +[test-group-config-enable-1] +key1=enabled diff --git a/src/core/tests/config/conf.d/90-last.conf b/src/core/tests/config/conf.d/90-last.conf new file mode 100644 index 0000000..7d07878 --- /dev/null +++ b/src/core/tests/config/conf.d/90-last.conf @@ -0,0 +1,8 @@ +[main] +plugins+=one,two + +[order] +a=90 + +[.test-append-stringlist.1] +val1+=c,a diff --git a/src/core/tests/config/global-dns-invalid.conf b/src/core/tests/config/global-dns-invalid.conf new file mode 100644 index 0000000..e490f60 --- /dev/null +++ b/src/core/tests/config/global-dns-invalid.conf @@ -0,0 +1,9 @@ +# Invalid configuration, since there isn't a default domain section + +[global-dns] +searches=foo.com +options=timeout:5 + +[global-dns-domain-test.com] +servers=1.2.3.4 +options=myoption diff --git a/src/core/tests/config/meson.build b/src/core/tests/config/meson.build new file mode 100644 index 0000000..d0d8b1c --- /dev/null +++ b/src/core/tests/config/meson.build @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_config_dir = meson.current_source_dir() + +test_unit = 'test-config' + +sources = files( + 'nm-test-device.c', + 'test-config.c', +) + +exe = executable( + test_unit, + sources, + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) + +test( + 'config/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) diff --git a/src/core/tests/config/nm-test-device.c b/src/core/tests/config/nm-test-device.c new file mode 100644 index 0000000..c6b894d --- /dev/null +++ b/src/core/tests/config/nm-test-device.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-test-device.h" + +#include "devices/nm-device-private.h" +#include "nm-utils.h" + +/*****************************************************************************/ + +struct _NMTestDevice { + NMDevice parent; +}; + +struct _NMTestDeviceClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMTestDevice, nm_test_device, NM_TYPE_DEVICE) + +#define PARENT_CLASS (G_OBJECT_CLASS(g_type_class_peek_parent(nm_test_device_parent_class))) + +/*****************************************************************************/ + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_NON_KERNEL; +} + +/*****************************************************************************/ + +static void +nm_test_device_init(NMTestDevice *self) +{} + +/* We jump over NMDevice's construct/destruct methods, which require NMPlatform + * and NMSettings to be initialized. + */ +static void +constructed(GObject *object) +{ + PARENT_CLASS->constructed(object); +} + +static void +dispose(GObject *object) +{ + PARENT_CLASS->dispose(object); +} + +NMDevice * +nm_test_device_new(const char *hwaddr) +{ + return g_object_new(NM_TYPE_TEST_DEVICE, + NM_DEVICE_IFACE, + "dummy", + NM_DEVICE_PERM_HW_ADDRESS, + hwaddr, + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_ETHERNET, + NULL); +} + +static void +nm_test_device_class_init(NMTestDeviceClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + device_class->get_generic_capabilities = get_generic_capabilities; +} diff --git a/src/core/tests/config/nm-test-device.h b/src/core/tests/config/nm-test-device.h new file mode 100644 index 0000000..638177a --- /dev/null +++ b/src/core/tests/config/nm-test-device.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_TEST_DEVICE_H__ +#define __NETWORKMANAGER_TEST_DEVICE_H__ + +#include "devices/nm-device.h" + +#define NM_TYPE_TEST_DEVICE (nm_test_device_get_type()) +#define NM_TEST_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_TEST_DEVICE, NMTestDevice)) +#define NM_TEST_DEVICE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_TEST_DEVICE, NMTestDeviceClass)) +#define NM_IS_TEST_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_TEST_DEVICE)) +#define NM_IS_TEST_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_TEST_DEVICE)) +#define NM_TEST_DEVICE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_TEST_DEVICE, NMTestDeviceClass)) + +typedef struct _NMTestDevice NMTestDevice; +typedef struct _NMTestDeviceClass NMTestDeviceClass; + +GType nm_test_device_get_type(void); + +NMDevice *nm_test_device_new(const char *hwaddr); + +#endif /* __NETWORKMANAGER_TEST_DEVICE_H__ */ diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c new file mode 100644 index 0000000..526c713 --- /dev/null +++ b/src/core/tests/config/test-config.c @@ -0,0 +1,1398 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-config.h" +#include "nm-test-device.h" +#include "platform/nm-fake-platform.h" +#include "dhcp/nm-dhcp-manager.h" +#include "nm-dbus-manager.h" +#include "nm-connectivity.h" + +#include "nm-test-utils-core.h" + +#define TEST_DIR NM_BUILD_SRCDIR "/src/core/tests/config" +#define BUILD_DIR NM_BUILD_BUILDDIR "/src/core/tests/config" + +/*****************************************************************************/ + +static void +_assert_config_value(const NMConfigData *config_data, + const char * group, + const char * key, + const char * expected_value, + const char * file, + int line) +{ + gs_free char *value = NULL; + + value = nm_config_data_get_value(config_data, group, key, NM_CONFIG_GET_VALUE_NONE); + if (g_strcmp0(value, expected_value)) { + g_error("(%s:%d) invalid value in config-data %s.%s = %s%s%s (instead of %s%s%s)", + file, + line, + group, + key, + NM_PRINT_FMT_QUOTED(value, "\"", value, "\"", "(null)"), + NM_PRINT_FMT_QUOTED(expected_value, "\"", expected_value, "\"", "(null)")); + } +} +#define assert_config_value(config_data, group, key, expected_value) \ + _assert_config_value(config_data, group, key, expected_value, __FILE__, __LINE__) + +#define _config_get_dhcp_client_a(config) \ + ({ \ + gs_free char *_s = NULL; \ + \ + _s = nm_config_data_get_value(nm_config_get_data_orig(config), \ + NM_CONFIG_KEYFILE_GROUP_MAIN, \ + NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, \ + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); \ + _s ? nm_sprintf_bufa(100, "%s", _s) : NULL; \ + }) + +/*****************************************************************************/ + +static NMConfig * +setup_config(GError ** error, + const char * config_file, + const char * intern_config, + const char *const *atomic_section_prefixes, + const char * config_dir, + const char * system_config_dir, + ...) +{ + va_list ap; + GPtrArray * args; + char ** argv, *arg; + int argc; + GOptionContext * context; + gboolean success; + NMConfig * config; + GError * local_error = NULL; + NMConfigCmdLineOptions *cli; + + g_assert(!error || !*error); + + args = g_ptr_array_new(); + g_ptr_array_add(args, "test-config"); + g_ptr_array_add(args, "--config"); + g_ptr_array_add(args, (char *) config_file); + if (intern_config) { + g_ptr_array_add(args, "--intern-config"); + g_ptr_array_add(args, (char *) intern_config); + } + g_ptr_array_add(args, "--config-dir"); + g_ptr_array_add(args, (char *) config_dir); + if (system_config_dir) { + g_ptr_array_add(args, "--system-config-dir"); + g_ptr_array_add(args, (char *) system_config_dir); + } + + va_start(ap, system_config_dir); + while ((arg = va_arg(ap, char *))) + g_ptr_array_add(args, arg); + va_end(ap); + + argv = (char **) args->pdata; + argc = args->len; + + cli = nm_config_cmd_line_options_new(FALSE); + + context = g_option_context_new(NULL); + nm_config_cmd_line_options_add_to_entries(cli, context); + success = g_option_context_parse(context, &argc, &argv, NULL); + g_option_context_free(context); + + if (!success) + g_printerr("Invalid options.\n"); + + g_ptr_array_free(args, TRUE); + + config = nm_config_setup(cli, (char **) atomic_section_prefixes, &local_error); + if (error) { + g_assert(!config); + g_assert(local_error); + g_propagate_error(error, local_error); + } else { + g_assert(config); + g_assert_no_error(local_error); + } + nm_config_cmd_line_options_free(cli); + + if (config) { + NMDhcpManager *dhcp_manager; + gpointer logging_old_state; + + logging_old_state = nmtst_logging_disable(FALSE); + + dhcp_manager = nm_dhcp_manager_get(); + g_test_assert_expected_messages(); + + nmtst_logging_reenable(logging_old_state); + + g_object_set_data_full(G_OBJECT(config), + "nmtst-config-keep-dhcp-manager-alive", + dhcp_manager, + nmtst_dhcp_manager_unget); + } + + return config; +} + +static void +test_config_simple(void) +{ + gs_unref_object NMConfig *config = NULL; + gs_strfreev char ** plugins = NULL; + char * value; + gs_unref_object NMDevice *dev50 = nm_test_device_new("00:00:00:00:00:50"); + gs_unref_object NMDevice *dev51 = nm_test_device_new("00:00:00:00:00:51"); + gs_unref_object NMDevice *dev52 = nm_test_device_new("00:00:00:00:00:52"); + + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", NULL); + + g_assert_cmpstr(nm_config_data_get_config_main_file(nm_config_get_data_orig(config)), + ==, + TEST_DIR "/NetworkManager.conf"); + g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "dhclient"); + g_assert_cmpstr(nm_config_get_log_level(config), ==, "INFO"); + g_assert_cmpint(nm_config_data_get_connectivity_interval(nm_config_get_data_orig(config)), + ==, + 100); + + plugins = nm_config_data_get_plugins(nm_config_get_data_orig(config), FALSE); + g_assert_cmpint(g_strv_length((char **) plugins), ==, 3); + g_assert_cmpstr(plugins[0], ==, "foo"); + g_assert_cmpstr(plugins[1], ==, "bar"); + g_assert_cmpstr(plugins[2], ==, "baz"); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "extra-section", + "extra-key", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "some value"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "extra-section", + "no-key", + NM_CONFIG_GET_VALUE_NONE); + g_assert(!value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "no-section", + "no-key", + NM_CONFIG_GET_VALUE_NONE); + g_assert(!value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "connection", + "ipv6.ip6_privacy", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "0"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "connection.dev51", + "ipv4.route-metric", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "51"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv6.route-metric", + NULL); + g_assert_cmpstr(value, ==, NULL); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.route-metric", + NULL); + g_assert_cmpstr(value, ==, "50"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.route-metric", + dev50); + g_assert_cmpstr(value, ==, "50"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.route-metric", + dev51); + g_assert_cmpstr(value, ==, "51"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.route-metric", + dev52); + g_assert_cmpstr(value, ==, "52"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ethernet.mtu", + dev51); + g_assert_cmpstr(value, ==, "9000"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ethernet.mtu", + dev50); + g_assert_cmpstr(value, ==, "1400"); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.dns-priority", + dev51); + g_assert_cmpstr(value, ==, NULL); + g_free(value); + + value = nm_config_data_get_connection_default(nm_config_get_data_orig(config), + "ipv4.dns-priority", + dev50); + g_assert_cmpstr(value, ==, "60"); + g_free(value); +} + +static void +test_config_non_existent(void) +{ + GError *error = NULL; + + setup_config(&error, TEST_DIR "/no-such-file", "", NULL, "/no/such/dir", "", NULL); + g_assert_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND); + g_clear_error(&error); +} + +static void +test_config_parse_error(void) +{ + GError *error = NULL; + + setup_config(&error, TEST_DIR "/bad.conf", "", NULL, "/no/such/dir", "", NULL); + g_assert_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE); + g_clear_error(&error); +} + +static void +test_config_override(void) +{ + gs_unref_object NMConfig *config = NULL; + gs_strfreev char ** plugins = NULL; + + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + "", + NULL, + "/no/such/dir", + "", + "--plugins", + "alpha,beta,gamma,delta", + "--connectivity-interval", + "12", + NULL); + + g_assert_cmpstr(nm_config_data_get_config_main_file(nm_config_get_data_orig(config)), + ==, + TEST_DIR "/NetworkManager.conf"); + g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "dhclient"); + g_assert_cmpstr(nm_config_get_log_level(config), ==, "INFO"); + g_assert_cmpint(nm_config_data_get_connectivity_interval(nm_config_get_data_orig(config)), + ==, + 12); + + plugins = nm_config_data_get_plugins(nm_config_get_data_orig(config), FALSE); + g_assert_cmpint(g_strv_length((char **) plugins), ==, 4); + g_assert_cmpstr(plugins[0], ==, "alpha"); + g_assert_cmpstr(plugins[1], ==, "beta"); + g_assert_cmpstr(plugins[2], ==, "gamma"); + g_assert_cmpstr(plugins[3], ==, "delta"); +} + +static void +test_config_global_dns(void) +{ + NMConfig * config; + const NMGlobalDnsConfig *dns; + NMGlobalDnsDomain * domain; + const char *const * strv; + + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, "/no/such/dir", "", NULL); + + dns = nm_config_data_get_global_dns_config(nm_config_get_data_orig(config)); + g_assert(dns); + + strv = nm_global_dns_config_get_searches(dns); + g_assert(strv); + g_assert_cmpuint(g_strv_length((char **) strv), ==, 2); + g_assert_cmpstr(strv[0], ==, "foo.com"); + g_assert_cmpstr(strv[1], ==, "bar.org"); + + strv = nm_global_dns_config_get_options(dns); + g_assert(strv); + g_assert_cmpuint(g_strv_length((char **) strv), ==, 2); + g_assert_cmpstr(strv[0], ==, "debug"); + g_assert_cmpstr(strv[1], ==, "edns0"); + + g_assert_cmpuint(nm_global_dns_config_get_num_domains(dns), ==, 2); + + /* Default domain */ + domain = nm_global_dns_config_lookup_domain(dns, "*"); + g_assert(domain); + + strv = nm_global_dns_domain_get_servers(domain); + g_assert(strv); + g_assert_cmpuint(g_strv_length((char **) strv), ==, 2); + g_assert_cmpstr(strv[0], ==, "1.1.1.1"); + g_assert_cmpstr(strv[1], ==, "1::128"); + + strv = nm_global_dns_domain_get_options(domain); + g_assert(strv); + g_assert_cmpuint(g_strv_length((char **) strv), ==, 2); + g_assert_cmpstr(strv[0], ==, "opt1"); + g_assert_cmpstr(strv[1], ==, "opt2"); + + /* 'example.com' domain */ + domain = nm_global_dns_config_lookup_domain(dns, "example.com"); + g_assert(domain); + + strv = nm_global_dns_domain_get_servers(domain); + g_assert(strv); + g_assert_cmpuint(g_strv_length((char **) strv), ==, 1); + g_assert_cmpstr(strv[0], ==, "2.2.2.2"); + + strv = nm_global_dns_domain_get_options(domain); + g_assert(!strv || g_strv_length((char **) strv) == 0); + + /* Non-existent domain 'test.com' */ + domain = nm_global_dns_config_lookup_domain(dns, "test.com"); + g_assert(!domain); + + g_object_unref(config); + + /* Check that a file without a default domain section gives a NULL configuration */ + config = + setup_config(NULL, TEST_DIR "/global-dns-invalid.conf", "", NULL, "/no/such/dir", "", NULL); + dns = nm_config_data_get_global_dns_config(nm_config_get_data_orig(config)); + g_assert(!dns); + g_object_unref(config); +} + +static void +test_config_connectivity_check(void) +{ +#if WITH_CONCHECK + const char * CONFIG_INTERN = BUILD_DIR "/test-connectivity-check-intern.conf"; + NMConfig * config; + NMConnectivity *connectivity; + + g_assert(g_file_set_contents(CONFIG_INTERN, "", 0, NULL)); + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + CONFIG_INTERN, + NULL, + "/no/such/dir", + "", + NULL); + connectivity = nm_connectivity_get(); + + g_assert(nm_connectivity_check_enabled(connectivity)); + + /* disable connectivity checking */ + NMTST_EXPECT_NM_INFO("config: signal: *"); + nm_config_set_connectivity_check_enabled(config, FALSE); + g_test_assert_expected_messages(); + + g_assert(!nm_connectivity_check_enabled(connectivity)); + + /* re-enable connectivity checking */ + NMTST_EXPECT_NM_INFO("config: signal: *"); + nm_config_set_connectivity_check_enabled(config, TRUE); + g_test_assert_expected_messages(); + + g_assert(nm_connectivity_check_enabled(connectivity)); + + g_object_unref(connectivity); + g_object_unref(config); + + g_assert(remove(CONFIG_INTERN) == 0); +#else + g_test_skip("concheck disabled"); +#endif +} + +static void +test_config_no_auto_default(void) +{ + NMConfig *config; + GError * error = NULL; + int fd, nwrote; + char * state_file; + NMDevice *dev1, *dev2, *dev3, *dev4; + + fd = g_file_open_tmp(NULL, &state_file, &error); + g_assert_no_error(error); + + nwrote = write(fd, "22:22:22:22:22:22\n", 18); + g_assert_cmpint(nwrote, ==, 18); + nwrote = write(fd, "44:44:44:44:44:44\n", 18); + g_assert_cmpint(nwrote, ==, 18); + nm_close(fd); + + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + "", + NULL, + "/no/such/dir", + "", + "--no-auto-default", + state_file, + NULL); + + dev1 = nm_test_device_new("11:11:11:11:11:11"); + dev2 = nm_test_device_new("22:22:22:22:22:22"); + dev3 = nm_test_device_new("33:33:33:33:33:33"); + dev4 = nm_test_device_new("44:44:44:44:44:44"); + + g_assert(nm_config_get_no_auto_default_for_device(config, dev1)); + g_assert(nm_config_get_no_auto_default_for_device(config, dev2)); + g_assert(!nm_config_get_no_auto_default_for_device(config, dev3)); + g_assert(nm_config_get_no_auto_default_for_device(config, dev4)); + + NMTST_EXPECT_NM_INFO("config: signal: NO_AUTO_DEFAULT,no-auto-default *"); + nm_config_set_no_auto_default_for_device(config, dev3); + g_test_assert_expected_messages(); + + g_assert(nm_config_get_no_auto_default_for_device(config, dev3)); + + g_object_unref(config); + + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + "", + NULL, + "/no/such/dir", + "", + "--no-auto-default", + state_file, + NULL); + + g_assert(nm_config_get_no_auto_default_for_device(config, dev1)); + g_assert(nm_config_get_no_auto_default_for_device(config, dev2)); + g_assert(nm_config_get_no_auto_default_for_device(config, dev3)); + g_assert(nm_config_get_no_auto_default_for_device(config, dev4)); + + g_object_unref(config); + + g_object_unref(dev1); + g_object_unref(dev2); + g_object_unref(dev3); + g_object_unref(dev4); + + unlink(state_file); + g_free(state_file); +} + +static void +test_config_confdir(void) +{ + gs_unref_object NMConfig *config = NULL; + gs_strfreev char ** plugins = NULL; + char * value; + GSList * specs; + + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL); + + g_assert_cmpstr(nm_config_data_get_config_main_file(nm_config_get_data_orig(config)), + ==, + TEST_DIR "/NetworkManager.conf"); + g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "dhcpcd"); + g_assert_cmpstr(nm_config_get_log_level(config), ==, "INFO"); + g_assert_cmpstr(nm_config_get_log_domains(config), ==, "PLATFORM,DNS,WIFI"); + g_assert_cmpstr(nm_config_data_get_connectivity_uri(nm_config_get_data_orig(config)), + ==, + "http://example.net"); + g_assert_cmpint(nm_config_data_get_connectivity_interval(nm_config_get_data_orig(config)), + ==, + 100); + + plugins = nm_config_data_get_plugins(nm_config_get_data_orig(config), FALSE); + g_assert_cmpint(g_strv_length((char **) plugins), ==, 5); + g_assert_cmpstr(plugins[0], ==, "foo"); + g_assert_cmpstr(plugins[1], ==, "bar"); + g_assert_cmpstr(plugins[2], ==, "baz"); + g_assert_cmpstr(plugins[3], ==, "one"); + g_assert_cmpstr(plugins[4], ==, "two"); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "main", + "extra", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "hello"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "main", + "no-auto-default", + NM_CONFIG_GET_VALUE_TYPE_SPEC); + specs = nm_match_spec_split(value); + g_free(value); + g_assert_cmpint(g_slist_length(specs), ==, 2); + g_assert_cmpstr(g_slist_nth_data(specs, 0), ==, "spec2"); + g_assert_cmpstr(g_slist_nth_data(specs, 1), ==, "spec3"); + g_slist_free_full(specs, g_free); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "main", + "ignore-carrier", + NM_CONFIG_GET_VALUE_TYPE_SPEC); + specs = nm_match_spec_split(value); + g_free(value); + g_assert_cmpint(g_slist_length(specs), ==, 2); + g_assert_cmpstr(g_slist_nth_data(specs, 0), ==, " space1 "); + g_assert_cmpstr(g_slist_nth_data(specs, 1), ==, " space2\t"); + g_slist_free_full(specs, g_free); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".0", + "new", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "something"); /* not ",something" */ + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "order", + "a", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "90"); + g_free(value); + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "order", + "b", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "10"); + g_free(value); + value = nm_config_data_get_value(nm_config_get_data_orig(config), + "order", + "c", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "0"); + g_free(value); + + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key1", + NM_CONFIG_GET_VALUE_RAW)); + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key1+", + NM_CONFIG_GET_VALUE_RAW)); + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key1-", + NM_CONFIG_GET_VALUE_RAW)); + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key2", + NM_CONFIG_GET_VALUE_RAW)); + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key2+", + NM_CONFIG_GET_VALUE_RAW)); + g_assert(!nm_config_data_has_value(nm_config_get_data_orig(config), + "appendable-test", + "non-appendable-key2-", + NM_CONFIG_GET_VALUE_RAW)); + +#define ASSERT_GET_CONN_DEFAULT(xconfig, xname, xvalue) \ + G_STMT_START \ + { \ + gs_free char *_value = \ + nm_config_data_get_connection_default(nm_config_get_data_orig(xconfig), \ + (xname), \ + NULL); \ + g_assert_cmpstr(_value, ==, (xvalue)); \ + } \ + G_STMT_END + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key00"), "A-0.0.00"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key01"), "A-0.3.01"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key02"), "A-0.2.02"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key03"), "A-0.1.03"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key04"), "B-1.3.04"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key05"), "B-1.2.05"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key06"), "B-1.1.06"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key07"), "C-2.3.07"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key08"), "C-2.2.08"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.key09"), "C-2.1.09"); + ASSERT_GET_CONN_DEFAULT(config, NM_CON_DEFAULT("ord.ovw01"), "C-0.1.ovw01"); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".1", + "val1", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "a,c"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".1", + "val2", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "VAL2"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".1", + "val3", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, NULL); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".1", + "val4", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "vb,vb"); + g_free(value); + + value = nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".1", + "val5", + NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr(value, ==, "VAL5"); + g_free(value); + + nm_config_data_log(nm_config_get_data_orig(config), ">>> TEST: ", " ", "/test/file/name", NULL); +} + +static void +test_config_confdir_parse_error(void) +{ + GError *error = NULL; + + /* Using TEST_DIR as the conf dir will pick up bad.conf */ + setup_config(&error, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR, "", NULL); + g_assert_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE); + g_clear_error(&error); +} + +static void +test_config_warnings(void) +{ + gs_unref_object NMConfig *config = NULL; + const char *const * warnings; + + config = setup_config(NULL, + TEST_DIR "/NetworkManager-warn.conf", + "", + NULL, + "/no/such/dir", + "", + NULL); + + warnings = nm_config_get_warnings(config); + +#define check_warning(str, group, key) \ + { \ + gs_free char *expected = NULL; \ + \ + expected = g_strdup_printf("unknown key '%s' in section [%s] of file '" TEST_DIR \ + "/NetworkManager-warn.conf'", \ + key, \ + group); \ + g_assert_cmpstr(str, ==, expected); \ + } + + g_assert(warnings); + g_assert_cmpint(g_strv_length((char **) warnings), ==, 5); + check_warning(warnings[0], "main", "plugin"); + check_warning(warnings[1], "main", "rc-managed"); + check_warning(warnings[2], "connectivity", "audit"); + check_warning(warnings[3], "connection-wifi", "wifi.tx-power"); + check_warning(warnings[4], "connection", "ipv4.addresses"); + +#undef check_warning +} + +/*****************************************************************************/ + +typedef void (*TestSetValuesUserSetFcn)(NMConfig * config, + gboolean is_user, + GKeyFile * keyfile_user, + NMConfigChangeFlags *out_expected_changes); +typedef void (*TestSetValuesCheckStateFcn)(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data); + +typedef struct { + NMConfigChangeFlags changes; + TestSetValuesCheckStateFcn check_state_fcn; +} TestSetValuesConfigChangedData; + +static void +_set_values_config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + TestSetValuesConfigChangedData *config_changed_data) +{ + g_assert(changes != NM_CONFIG_CHANGE_NONE); + g_assert(config_changed_data); + g_assert(config_changed_data->changes == NM_CONFIG_CHANGE_NONE); + + if (changes == NM_CONFIG_CHANGE_CAUSE_SIGHUP) + return; + changes &= ~NM_CONFIG_CHANGE_CAUSE_SIGHUP; + + config_changed_data->changes = changes; + + if (config_changed_data->check_state_fcn) + config_changed_data->check_state_fcn(config, config_data, TRUE, changes, old_data); +} + +static void +_set_values_user(NMConfig * config, + const char * CONFIG_USER, + TestSetValuesUserSetFcn set_fcn, + TestSetValuesCheckStateFcn check_state_fcn) +{ + GKeyFile * keyfile_user; + gboolean success; + gs_free_error GError * error = NULL; + TestSetValuesConfigChangedData config_changed_data = { + .changes = NM_CONFIG_CHANGE_NONE, + .check_state_fcn = check_state_fcn, + }; + NMConfigChangeFlags expected_changes = NM_CONFIG_CHANGE_NONE; + gs_unref_object NMConfigData *config_data_before = NULL; + + keyfile_user = nm_config_create_keyfile(); + + success = g_key_file_load_from_file(keyfile_user, CONFIG_USER, G_KEY_FILE_NONE, &error); + nmtst_assert_success(success, error); + + if (set_fcn) + set_fcn(config, TRUE, keyfile_user, &expected_changes); + + success = g_key_file_save_to_file(keyfile_user, CONFIG_USER, &error); + nmtst_assert_success(success, error); + + g_signal_connect(G_OBJECT(config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_set_values_config_changed_cb), + &config_changed_data); + + config_data_before = g_object_ref(nm_config_get_data(config)); + + if (expected_changes != NM_CONFIG_CHANGE_NONE) + NMTST_EXPECT_NM_INFO("config: signal: *"); + else + NMTST_EXPECT_NM_INFO("config: signal: SIGHUP (no changes from disk)*"); + + nm_config_reload(config, NM_CONFIG_CHANGE_CAUSE_SIGHUP, FALSE); + + g_test_assert_expected_messages(); + + g_assert(expected_changes == config_changed_data.changes); + + if (check_state_fcn) + check_state_fcn(config, + nm_config_get_data(config), + FALSE, + NM_CONFIG_CHANGE_NONE, + config_data_before); + + g_signal_handlers_disconnect_by_func(config, + _set_values_config_changed_cb, + &config_changed_data); + + g_key_file_unref(keyfile_user); +} + +static void +_set_values_intern(NMConfig * config, + TestSetValuesUserSetFcn set_fcn, + TestSetValuesCheckStateFcn check_state_fcn) +{ + GKeyFile * keyfile_intern; + TestSetValuesConfigChangedData config_changed_data = { + .changes = NM_CONFIG_CHANGE_NONE, + .check_state_fcn = check_state_fcn, + }; + NMConfigChangeFlags expected_changes = NM_CONFIG_CHANGE_NONE; + gs_unref_object NMConfigData *config_data_before = NULL; + + config_data_before = g_object_ref(nm_config_get_data(config)); + + keyfile_intern = nm_config_data_clone_keyfile_intern(config_data_before); + + if (set_fcn) + set_fcn(config, FALSE, keyfile_intern, &expected_changes); + + g_signal_connect(G_OBJECT(config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_set_values_config_changed_cb), + &config_changed_data); + + if (expected_changes != NM_CONFIG_CHANGE_NONE) + NMTST_EXPECT_NM_INFO("config: signal: *"); + + nm_config_set_values(config, keyfile_intern, TRUE, FALSE); + + g_test_assert_expected_messages(); + + g_assert(expected_changes == config_changed_data.changes); + + if (check_state_fcn) + check_state_fcn(config, + nm_config_get_data(config), + FALSE, + NM_CONFIG_CHANGE_NONE, + config_data_before); + + g_signal_handlers_disconnect_by_func(config, + _set_values_config_changed_cb, + &config_changed_data); + + g_key_file_unref(keyfile_intern); +} + +static void +_set_values_user_intern_section_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "section1", + "key", + "this-should-be-ignored"); +} + +static void +_set_values_user_intern_section_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + g_assert(changes == NM_CONFIG_CHANGE_NONE); + g_assert( + !nm_config_data_has_group(config_data, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "section1")); +} + +static void +_set_values_user_initial_values_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_remove_group(keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "section1", NULL); + g_key_file_set_string(keyfile, "section1", "key1", "value1"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; +} + +static void +_set_values_user_initial_values_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER)); + assert_config_value(config_data, "section1", "key1", "value1"); +} + +static void +_set_values_intern_internal_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "section1", + "key", + "internal-section"); + *out_expected_changes = NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_internal_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes + == (NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value(config_data, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "section1", + "key", + "internal-section"); +} + +static void +_set_values_user_atomic_section_1_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key1", "user-value1"); + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key2", "user-value2"); + g_key_file_set_string(keyfile, "atomic-prefix-1.section-b", "key1", "user-value1"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); + *out_expected_changes = NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER; +} + +static void +_set_values_user_atomic_section_1_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER)); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key1", "user-value1"); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key2", "user-value2"); + assert_config_value(config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); +} + +static void +_set_values_intern_atomic_section_1_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key1", "intern-value1"); + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key3", "intern-value3"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "intern-value1"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + *out_expected_changes = NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_atomic_section_1_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes + == (NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key1", "intern-value1"); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key2", NULL); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key3", "intern-value3"); + assert_config_value(config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "intern-value1"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert(nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-a")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-b")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "non-atomic-prefix-1.section-a")); +} + +static void +_set_values_user_atomic_section_2_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key1", "user-value1-x"); + g_key_file_set_string(keyfile, "atomic-prefix-1.section-a", "key2", "user-value2"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1-x"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + *out_expected_changes = + NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_user_atomic_section_2_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes + == (NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER + | NM_CONFIG_CHANGE_VALUES_INTERN)); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key1", "user-value1-x"); + assert_config_value(config_data, "atomic-prefix-1.section-a", "key2", "user-value2"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key1", "user-value1-x"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-a")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-b")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "non-atomic-prefix-1.section-a")); +} + +static void +_set_values_intern_atomic_section_2_set(NMConfig * config, + gboolean set_user, + GKeyFile * keyfile, + NMConfigChangeFlags *out_expected_changes) +{ + /* let's hide an atomic section and one key. */ + g_key_file_set_string(keyfile, + "atomic-prefix-1.section-a", + NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, + "any-value"); + g_key_file_set_string(keyfile, + "non-atomic-prefix-1.section-a", + NM_CONFIG_KEYFILE_KEYPREFIX_WAS "nap1-key1", + "any-value"); + g_key_file_set_string(keyfile, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_key_file_set_string(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "with-whitespace", + "key1", + " b c\\, d "); + g_key_file_set_value(keyfile, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "with-whitespace", + "key2", + " b c\\, d "); + *out_expected_changes = NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN; +} + +static void +_set_values_intern_atomic_section_2_check(NMConfig * config, + NMConfigData * config_data, + gboolean is_change_event, + NMConfigChangeFlags changes, + NMConfigData * old_data) +{ + if (is_change_event) + g_assert(changes + == (NM_CONFIG_CHANGE_CAUSE_SET_VALUES | NM_CONFIG_CHANGE_VALUES + | NM_CONFIG_CHANGE_VALUES_INTERN)); + g_assert(!nm_config_data_has_group(config_data, "atomic-prefix-1.section-a")); + assert_config_value(config_data, "atomic-prefix-1.section-b", "key1", "user-value1"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key1", NULL); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key2", "user-value2-x"); + assert_config_value(config_data, "non-atomic-prefix-1.section-a", "nap1-key3", "intern-value3"); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-a")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "atomic-prefix-1.section-b")); + g_assert(!nm_config_data_is_intern_atomic_group(config_data, "non-atomic-prefix-1.section-a")); + assert_config_value(config_data, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "with-whitespace", + "key1", + " b c\\, d "); + assert_config_value(config_data, + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN "with-whitespace", + "key2", + " b c\\, d "); +} + +static void +test_config_set_values(void) +{ + gs_unref_object NMConfig *config = NULL; + const char * CONFIG_USER = BUILD_DIR "/test-set-values-user.conf"; + const char * CONFIG_INTERN = BUILD_DIR "/test-set-values-intern.conf"; + const char * atomic_section_prefixes[] = { + "atomic-prefix-1.", + "atomic-prefix-2.", + NULL, + }; + + g_assert(g_file_set_contents(CONFIG_USER, "", 0, NULL)); + g_assert(g_file_set_contents(CONFIG_INTERN, "", 0, NULL)); + + config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, atomic_section_prefixes, "", "", NULL); + + _set_values_user(config, + CONFIG_USER, + _set_values_user_intern_section_set, + _set_values_user_intern_section_check); + + _set_values_user(config, + CONFIG_USER, + _set_values_user_initial_values_set, + _set_values_user_initial_values_check); + + _set_values_intern(config, _set_values_intern_internal_set, _set_values_intern_internal_check); + + _set_values_user(config, + CONFIG_USER, + _set_values_user_atomic_section_1_set, + _set_values_user_atomic_section_1_check); + + _set_values_intern(config, + _set_values_intern_atomic_section_1_set, + _set_values_intern_atomic_section_1_check); + + _set_values_user(config, + CONFIG_USER, + _set_values_user_atomic_section_2_set, + _set_values_user_atomic_section_2_check); + + _set_values_intern(config, + _set_values_intern_atomic_section_2_set, + _set_values_intern_atomic_section_2_check); + + g_assert(remove(CONFIG_USER) == 0); + g_assert(remove(CONFIG_INTERN) == 0); +} + +/*****************************************************************************/ + +static void +_test_signal_config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + gpointer user_data) +{ + const NMConfigChangeFlags *expected = user_data; + + g_assert(changes); + g_assert_cmpint(changes, ==, *expected); + g_assert(NM_IS_CONFIG(config)); + g_assert(NM_IS_CONFIG_DATA(config_data)); + + g_assert(config_data == old_data); + g_assert(config_data == nm_config_get_data(config)); +} + +static void +_test_signal_config_changed_cb2(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + gpointer user_data) +{ + const NMConfigChangeFlags *expected = user_data; + + g_assert(changes); + g_assert_cmpint(changes, ==, *expected); +} + +static void +test_config_signal(void) +{ + gs_unref_object NMConfig *config = NULL; + NMConfigChangeFlags expected; + gs_unref_object NMConfigData *config_data_orig = NULL; + + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL); + + config_data_orig = g_object_ref(nm_config_get_data_orig(config)); + + g_signal_connect(G_OBJECT(config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_test_signal_config_changed_cb), + &expected); + + expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR1; + NMTST_EXPECT_NM_INFO("config: signal: SIGUSR1"); + nm_config_reload(config, expected, FALSE); + + expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2; + NMTST_EXPECT_NM_INFO("config: signal: SIGUSR2"); + nm_config_reload(config, expected, FALSE); + + expected = NM_CONFIG_CHANGE_CAUSE_SIGHUP; + NMTST_EXPECT_NM_INFO("config: signal: SIGHUP (no changes from disk)*"); + nm_config_reload(config, expected, FALSE); + + /* test with subscribing two signals... + * + * This test exposes glib bug https://bugzilla.redhat.com/show_bug.cgi?id=1260577 + * for which we however have a workaround in 'nm-config.c' */ + g_signal_connect(G_OBJECT(config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_test_signal_config_changed_cb2), + &expected); + expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2; + NMTST_EXPECT_NM_INFO("config: signal: SIGUSR2"); + nm_config_reload(config, NM_CONFIG_CHANGE_CAUSE_SIGUSR2, FALSE); + g_signal_handlers_disconnect_by_func(config, _test_signal_config_changed_cb2, &expected); + + g_signal_handlers_disconnect_by_func(config, _test_signal_config_changed_cb, &expected); + + g_assert(config_data_orig == nm_config_get_data(config)); +} + +/*****************************************************************************/ + +static void +test_config_enable(void) +{ + gs_unref_object NMConfig *config = NULL; + guint match_nm_version = _nm_config_match_nm_version; + char * match_env = g_strdup(_nm_config_match_env); + + nm_clear_g_free(&_nm_config_match_env); + _nm_config_match_env = g_strdup("something-else"); + + _nm_config_match_nm_version = nm_encode_version(1, 3, 4); + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL); + assert_config_value(nm_config_get_data_orig(config), + "test-group-config-enable-1", + "key1", + NULL); + g_clear_object(&config); + + _nm_config_match_nm_version = nm_encode_version(1, 5, 32); + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL); + assert_config_value(nm_config_get_data_orig(config), + "test-group-config-enable-1", + "key1", + "enabled"); + g_clear_object(&config); + + _nm_config_match_nm_version = nm_encode_version(1, 5, 3); + nm_clear_g_free(&_nm_config_match_env); + _nm_config_match_env = g_strdup("test-match-env-1"); + config = + setup_config(NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL); + assert_config_value(nm_config_get_data_orig(config), + "test-group-config-enable-1", + "key1", + "enabled"); + g_clear_object(&config); + + _nm_config_match_nm_version = match_nm_version; + nm_clear_g_free(&_nm_config_match_env); + _nm_config_match_env = match_env; +} + +/*****************************************************************************/ + +static void +test_config_state_file(void) +{ + NMConfig * config; + const NMConfigState *state; + gs_free_error GError *error = NULL; + gboolean ret; + gs_free char * file_data = NULL; + gsize file_size; + const char *const TMP_FILE = BUILD_DIR "/tmp.state"; + + ret = g_file_get_contents(TEST_DIR "/NetworkManager.state", &file_data, &file_size, &error); + nmtst_assert_success(ret, error); + ret = g_file_set_contents(TMP_FILE, file_data, file_size, &error); + nmtst_assert_success(ret, error); + + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + "", + NULL, + TEST_DIR "/conf.d", + "", + "--state-file", + TMP_FILE, + NULL); + g_assert(config); + + state = nm_config_state_get(config); + g_assert(state); + + g_assert_cmpint(state->net_enabled, ==, TRUE); + g_assert_cmpint(state->wifi_enabled, ==, TRUE); + g_assert_cmpint(state->wwan_enabled, ==, TRUE); + + nm_config_state_set(config, + TRUE, + TRUE, + NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, + FALSE, + NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED, + TRUE, + NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED, + FALSE); + + state = nm_config_state_get(config); + g_assert(state); + + g_assert_cmpint(state->net_enabled, ==, FALSE); + g_assert_cmpint(state->wifi_enabled, ==, TRUE); + g_assert_cmpint(state->wwan_enabled, ==, FALSE); + + g_object_unref(config); + + /* Reload configuration */ + config = setup_config(NULL, + TEST_DIR "/NetworkManager.conf", + "", + NULL, + TEST_DIR "/conf.d", + "", + "--state-file", + TMP_FILE, + NULL); + g_assert(config); + + state = nm_config_state_get(config); + g_assert(state); + + g_assert_cmpint(state->net_enabled, ==, FALSE); + g_assert_cmpint(state->wifi_enabled, ==, TRUE); + g_assert_cmpint(state->wwan_enabled, ==, FALSE); + + g_object_unref(config); + unlink(TMP_FILE); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + nm_fake_platform_setup(); + + g_test_add_func("/config/simple", test_config_simple); + g_test_add_func("/config/non-existent", test_config_non_existent); + g_test_add_func("/config/parse-error", test_config_parse_error); + g_test_add_func("/config/no-auto-default", test_config_no_auto_default); + g_test_add_func("/config/confdir", test_config_confdir); + g_test_add_func("/config/confdir-parse-error", test_config_confdir_parse_error); + g_test_add_func("/config/warnings", test_config_warnings); + + g_test_add_func("/config/set-values", test_config_set_values); + g_test_add_func("/config/global-dns", test_config_global_dns); + g_test_add_func("/config/connectivity-check", test_config_connectivity_check); + + g_test_add_func("/config/signal", test_config_signal); + + g_test_add_func("/config/enable", test_config_enable); + + g_test_add_func("/config/state-file", test_config_state_file); + + /* This one has to come last, because it leaves its values in + * nm-config.c's global variables, and there's no way to reset + * those to NULL. + */ + g_test_add_func("/config/override", test_config_override); + + return g_test_run(); +} diff --git a/src/core/tests/meson.build b/src/core/tests/meson.build new file mode 100644 index 0000000..f412539 --- /dev/null +++ b/src/core/tests/meson.build @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +subdir('config') + +test_units = [ + 'test-core', + 'test-core-with-expect', + 'test-dcb', + 'test-ip4-config', + 'test-ip6-config', + 'test-l3cfg', + 'test-utils', + 'test-wired-defname', +] + +foreach test_unit: test_units + exe = executable( + test_unit, + test_unit + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + ) + + test( + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endforeach + +exe = executable( + 'test-systemd', + 'test-systemd.c', + include_directories: [ + top_inc, + src_inc, + ], + dependencies: [ + libnm_systemd_core_dep, + libnm_systemd_shared_dep, + ], + c_args: [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD', + ], +) + +test( + 'test-systemd', + test_script, + args: test_args + [exe.full_path()], +) diff --git a/src/core/tests/test-core-with-expect.c b/src/core/tests/test-core-with-expect.c new file mode 100644 index 0000000..d9750cd --- /dev/null +++ b/src/core/tests/test-core-with-expect.c @@ -0,0 +1,649 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include +#include +#include + +#include "NetworkManagerUtils.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +static void +test_nm_utils_monotonic_timestamp_as_boottime(void) +{ + gint64 timestamp_nsec_per_tick, now, now_boottime, now_boottime_2, now_boottime_3; + struct timespec tp; + clockid_t clockid; + guint i; + + if (clock_gettime(CLOCK_BOOTTIME, &tp) != 0 && errno == EINVAL) + clockid = CLOCK_MONOTONIC; + else + clockid = CLOCK_BOOTTIME; + + for (i = 0; i < 10; i++) { + if (clock_gettime(clockid, &tp) != 0) + g_assert_not_reached(); + now_boottime = (((gint64) tp.tv_sec) * NM_UTILS_NSEC_PER_SEC) + ((gint64) tp.tv_nsec); + + now = nm_utils_get_monotonic_timestamp_nsec(); + + now_boottime_2 = nm_utils_monotonic_timestamp_as_boottime(now, 1); + g_assert_cmpint(now_boottime_2, >=, 0); + g_assert_cmpint(now_boottime_2, >=, now_boottime); + g_assert_cmpint(now_boottime_2 - now_boottime, <=, NM_UTILS_NSEC_PER_SEC / 10); + + g_assert_cmpint(now, ==, nm_utils_monotonic_timestamp_from_boottime(now_boottime_2, 1)); + + for (timestamp_nsec_per_tick = 1; timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC; + timestamp_nsec_per_tick *= 10) { + now_boottime_3 = nm_utils_monotonic_timestamp_as_boottime(now / timestamp_nsec_per_tick, + timestamp_nsec_per_tick); + + g_assert_cmpint(now_boottime_2 / timestamp_nsec_per_tick, ==, now_boottime_3); + g_assert_cmpint(now / timestamp_nsec_per_tick, + ==, + nm_utils_monotonic_timestamp_from_boottime(now_boottime_3, + timestamp_nsec_per_tick)); + } + } +} + +/*****************************************************************************/ + +struct test_nm_utils_kill_child_async_data { + GMainLoop *loop; + pid_t pid; + gboolean called; + gboolean expected_success; + const int *expected_child_status; +}; + +static void +test_nm_utils_kill_child_async_cb(pid_t pid, gboolean success, int child_status, void *user_data) +{ + struct test_nm_utils_kill_child_async_data *data = user_data; + + g_assert(success == !!data->expected_success); + g_assert(pid == data->pid); + if (data->expected_child_status) + g_assert_cmpint(*data->expected_child_status, ==, child_status); + if (!success) + g_assert_cmpint(child_status, ==, -1); + + data->called = TRUE; + + g_assert(data->loop); + g_main_loop_quit(data->loop); +} + +static gboolean +test_nm_utils_kill_child_async_fail_cb(void *user_data) +{ + g_assert_not_reached(); +} + +static void +test_nm_utils_kill_child_async_do(const char *name, + pid_t pid, + int sig, + guint32 wait_before_kill_msec, + gboolean expected_success, + const int * expected_child_status) +{ + gboolean success; + struct test_nm_utils_kill_child_async_data data = {}; + int timeout_id; + + data.pid = pid; + data.expected_success = expected_success; + data.expected_child_status = expected_child_status; + + nm_utils_kill_child_async(pid, + sig, + LOGD_CORE, + name, + wait_before_kill_msec, + test_nm_utils_kill_child_async_cb, + &data); + g_assert(!data.called); + + timeout_id = g_timeout_add_seconds(5, test_nm_utils_kill_child_async_fail_cb, &data); + + data.loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data.loop); + + g_assert(data.called); + success = g_source_remove(timeout_id); + g_assert(success); + + g_main_loop_unref(data.loop); +} + +static void +test_nm_utils_kill_child_sync_do(const char *name, + pid_t pid, + int sig, + guint32 wait_before_kill_msec, + gboolean expected_success, + const int * expected_child_status) +{ + gboolean success; + int child_status = -1; + + success = nm_utils_kill_child_sync(pid, + sig, + LOGD_CORE, + name, + &child_status, + wait_before_kill_msec, + 0); + g_assert(success == !!expected_success); + if (expected_child_status) + g_assert_cmpint(*expected_child_status, ==, child_status); + + g_test_assert_expected_messages(); +} + +static pid_t +test_nm_utils_kill_child_spawn(char **argv, gboolean do_not_reap_child) +{ + GError *error = NULL; + int success; + GPid child_pid; + + success = + g_spawn_async(NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH | (do_not_reap_child ? G_SPAWN_DO_NOT_REAP_CHILD : 0), + NULL, + NULL, + &child_pid, + &error); + g_assert(success && !error); + return child_pid; +} + +static pid_t +do_test_nm_utils_kill_child_create_and_join_pgroup(void) +{ + int err, tmp = 0; + int pipefd[2]; + pid_t pgid; + + err = pipe2(pipefd, O_CLOEXEC); + g_assert(err == 0); + + pgid = fork(); + g_assert(pgid >= 0); + + if (pgid == 0) { + /* child process... */ + nm_close(pipefd[0]); + + err = setpgid(0, 0); + g_assert(err == 0); + + err = write(pipefd[1], &tmp, sizeof(tmp)); + g_assert(err == sizeof(tmp)); + + nm_close(pipefd[1]); + exit(0); + } + + nm_close(pipefd[1]); + + err = read(pipefd[0], &tmp, sizeof(tmp)); + g_assert(err == sizeof(tmp)); + + nm_close(pipefd[0]); + + err = setpgid(0, pgid); + g_assert(err == 0); + + do { + err = waitpid(pgid, &tmp, 0); + } while (err == -1 && errno == EINTR); + g_assert(err == pgid); + g_assert(WIFEXITED(tmp) && WEXITSTATUS(tmp) == 0); + + return pgid; +} + +#define TEST_TOKEN "nm_test_kill_child_process" + +static void +do_test_nm_utils_kill_child(void) +{ + GLogLevelFlags fatal_mask; + char * argv_watchdog[] = { + "bash", + "-c", + "sleep 4; " + "kill -KILL 0; #watchdog for #" TEST_TOKEN, + NULL, + }; + char *argv1[] = { + "bash", + "-c", + "trap \"sleep 0.5; exit 10\" EXIT; " + "sleep 100000; exit $? #" TEST_TOKEN, + NULL, + }; + char *argv2[] = { + "bash", + "-c", + "exit 47; #" TEST_TOKEN, + NULL, + }; + char *argv3[] = { + "bash", + "-c", + "trap \"exit 47\" TERM; while true; do :; done; #" TEST_TOKEN, + NULL, + }; + char *argv4[] = { + "bash", + "-c", + "trap \"while true; do :; done\" TERM; while true; do :; done; #" TEST_TOKEN, + NULL, + }; + pid_t pid1a_1, pid1a_2, pid1a_3, pid2a, pid3a, pid4a; + pid_t pid1s_1, pid1s_2, pid1s_3, pid2s, pid3s, pid4s; + + const int expected_exit_47 = 12032; /* exit with status 47 */ + const int expected_signal_TERM = SIGTERM; + const int expected_signal_KILL = SIGKILL; + + test_nm_utils_kill_child_spawn(argv_watchdog, FALSE); + + pid1s_1 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid1s_2 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid1s_3 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid2s = test_nm_utils_kill_child_spawn(argv2, TRUE); + pid3s = test_nm_utils_kill_child_spawn(argv3, TRUE); + pid4s = test_nm_utils_kill_child_spawn(argv4, TRUE); + + pid1a_1 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid1a_2 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid1a_3 = test_nm_utils_kill_child_spawn(argv1, TRUE); + pid2a = test_nm_utils_kill_child_spawn(argv2, TRUE); + pid3a = test_nm_utils_kill_child_spawn(argv3, TRUE); + pid4a = test_nm_utils_kill_child_spawn(argv4, TRUE); + + /* give processes time to start (and potentially block signals) ... */ + g_usleep(G_USEC_PER_SEC / 10); + + fatal_mask = g_log_set_always_fatal(G_LOG_FATAL_MASK); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-1' (*): waiting up to 3000 milliseconds " + "for process to terminate normally after sending SIGTERM (15)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-1' (*): after sending SIGTERM (15), " + "process * exited by signal 15 (* usec elapsed)"); + test_nm_utils_kill_child_sync_do("test-s-1-1", + pid1s_1, + SIGTERM, + 3000, + TRUE, + &expected_signal_TERM); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-2' (*): waiting for process to terminate " + "after sending SIGKILL (9)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-2' (*): after sending SIGKILL (9), process " + "* exited by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_sync_do("test-s-1-2", + pid1s_2, + SIGKILL, + 1000 / 2, + TRUE, + &expected_signal_KILL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-3' (*): waiting up to 1 milliseconds for " + "process to terminate normally after sending no signal (0)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-3' (*): sending SIGKILL..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-1-3' (*): after sending no signal (0) and " + "SIGKILL, process * exited by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_sync_do("test-s-1-3", pid1s_3, 0, 1, TRUE, &expected_signal_KILL); + + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-s-2' (*): process * already terminated normally with status 47"); + test_nm_utils_kill_child_sync_do("test-s-2", pid2s, SIGTERM, 3000, TRUE, &expected_exit_47); + + /* send invalid signal. */ + NMTST_EXPECT_NM_ERROR("kill child process 'test-s-3-0' (*): failed to send Unexpected signal: " + "Invalid argument (22)"); + test_nm_utils_kill_child_sync_do("test-s-3-0", pid3s, -1, 0, FALSE, NULL); + + /* really kill pid3s */ + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-3-1' (*): waiting up to 3000 milliseconds " + "for process to terminate normally after sending SIGTERM (15)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-3-1' (*): after sending SIGTERM (15), " + "process * exited normally with status 47 (* usec elapsed)"); + test_nm_utils_kill_child_sync_do("test-s-3-1", pid3s, SIGTERM, 3000, TRUE, &expected_exit_47); + + /* pid3s should not be a valid process, hence the call should fail. Note, that there + * is a race here. */ + NMTST_EXPECT_NM_ERROR( + "kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid " + "(No child process*, 10) after sending no signal (0)"); + test_nm_utils_kill_child_sync_do("test-s-3-2", pid3s, 0, 0, FALSE, NULL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): waiting up to 1 milliseconds for " + "process to terminate normally after sending SIGTERM (15)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): sending SIGKILL..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): after sending SIGTERM (15) and " + "SIGKILL, process * exited by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_sync_do("test-s-4", pid4s, SIGTERM, 1, TRUE, &expected_signal_KILL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-1-1' (*): wait for process to terminate " + "after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)..."); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-1-1' (*): terminated by signal 15 (* usec elapsed)"); + test_nm_utils_kill_child_async_do("test-a-1-1", + pid1a_1, + SIGTERM, + 3000, + TRUE, + &expected_signal_TERM); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-1-2' (*): wait for process to terminate " + "after sending SIGKILL (9)..."); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-1-2' (*): terminated by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_async_do("test-a-1-2", + pid1a_2, + SIGKILL, + 1000 / 2, + TRUE, + &expected_signal_KILL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-1-3' (*): wait for process to terminate " + "after sending no signal (0) (send SIGKILL in 1 milliseconds)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-1-3' (*): process not terminated after * " + "usec. Sending SIGKILL signal"); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-1-3' (*): terminated by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_async_do("test-a-1-3", pid1a_3, 0, 1, TRUE, &expected_signal_KILL); + + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-2' (*): process * already terminated normally with status 47"); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-2' (*): invoke callback: terminated normally with status 47"); + test_nm_utils_kill_child_async_do("test-a-2", pid2a, SIGTERM, 3000, TRUE, &expected_exit_47); + + NMTST_EXPECT_NM_ERROR("kill child process 'test-a-3-0' (*): unexpected error sending " + "Unexpected signal: Invalid argument (22)"); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-3-0' (*): invoke callback: killing child failed"); + /* coverity[negative_returns] */ + test_nm_utils_kill_child_async_do("test-a-3-0", pid3a, -1, 1000 / 2, FALSE, NULL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-3-1' (*): wait for process to terminate " + "after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)..."); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-3-1' (*): terminated normally with status 47 (* usec elapsed)"); + test_nm_utils_kill_child_async_do("test-a-3-1", pid3a, SIGTERM, 3000, TRUE, &expected_exit_47); + + /* pid3a should not be a valid process, hence the call should fail. Note, that there + * is a race here. */ + NMTST_EXPECT_NM_ERROR( + "kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid " + "(No child process*, 10) after sending no signal (0)"); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-3-2' (*): invoke callback: killing child failed"); + test_nm_utils_kill_child_async_do("test-a-3-2", pid3a, 0, 0, FALSE, NULL); + + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-4' (*): wait for process to terminate after " + "sending SIGTERM (15) (send SIGKILL in 1 milliseconds)..."); + NMTST_EXPECT_NM_DEBUG("kill child process 'test-a-4' (*): process not terminated after * usec. " + "Sending SIGKILL signal"); + NMTST_EXPECT_NM_DEBUG( + "kill child process 'test-a-4' (*): terminated by signal 9 (* usec elapsed)"); + test_nm_utils_kill_child_async_do("test-a-4", pid4a, SIGTERM, 1, TRUE, &expected_signal_KILL); + + g_log_set_always_fatal(fatal_mask); + + g_test_assert_expected_messages(); +} + +static void +test_nm_utils_kill_child(void) +{ + int err; + int exit_status; + pid_t gpid; + pid_t child_pid; + + /* the tests spawns several processes, we want to clean them up + * by sending a SIGKILL to the process group. + * + * The current process might be a session leader, which prevents it from + * creating a new process group. Hence, first fork and let the child + * create a new process group, run the tests, and kill all pending + * processes. */ + child_pid = fork(); + g_assert(child_pid >= 0); + + if (child_pid == 0) { + gpid = do_test_nm_utils_kill_child_create_and_join_pgroup(); + + do_test_nm_utils_kill_child(); + + err = setpgid(0, 0); + g_assert(err == 0); + + kill(-gpid, SIGKILL); + + exit(0); + }; + + do { + err = waitpid(child_pid, &exit_status, 0); + } while (err == -1 && errno == EINTR); + g_assert(err == child_pid); + if (WIFEXITED(exit_status)) + g_assert_cmpint(WEXITSTATUS(exit_status), ==, 0); + else { + g_assert_cmpint(exit_status, ==, 0); + g_assert_not_reached(); + } +} + +/*****************************************************************************/ + +static void +_remove_at_indexes_init_random_idx(GArray *idx, guint array_len, guint idx_len) +{ + GRand * rand = nmtst_get_rand(); + gs_free char *mask = NULL; + guint i, max_test_idx; + + g_assert(idx); + g_assert(array_len > 0); + g_assert(idx_len >= 1 && idx_len <= array_len); + + mask = g_new0(char, array_len); + + max_test_idx = array_len - 1; + for (i = 0; i < idx_len; i++) { + guint itest; + + /* find a index itest that is not yet taken */ + if (max_test_idx == 0) + itest = 0; + else + itest = g_rand_int_range(rand, 0, max_test_idx); + while (itest < array_len && mask[itest]) + itest++; + g_assert(itest <= max_test_idx); + g_assert(!mask[itest]); + + mask[itest] = TRUE; + if (itest == max_test_idx) { + g_assert(max_test_idx > 0 || i == idx_len - 1); + + if (max_test_idx == 0) + g_assert_cmpint(i, ==, idx_len - 1); + else { + max_test_idx--; + while (max_test_idx > 0 && mask[max_test_idx]) + max_test_idx--; + if (mask[max_test_idx]) + g_assert_cmpint(i, ==, idx_len - 1); + } + } + } + + g_array_set_size(idx, 0); + for (i = 0; i < array_len; i++) { + if (mask[i]) + g_array_append_val(idx, i); + } + g_assert_cmpint(idx->len, ==, idx_len); +} + +static void +test_nm_utils_array_remove_at_indexes(void) +{ + gs_unref_array GArray *idx = NULL, *array = NULL; + gs_unref_hashtable GHashTable *unique = NULL; + guint i_len, i_idx_len, i_rnd, i; + + idx = g_array_new(FALSE, FALSE, sizeof(guint)); + array = g_array_new(FALSE, FALSE, sizeof(gssize)); + unique = g_hash_table_new(nm_direct_hash, NULL); + for (i_len = 1; i_len < 20; i_len++) { + for (i_idx_len = 1; i_idx_len <= i_len; i_idx_len++) { + for (i_rnd = 0; i_rnd < 20; i_rnd++) { + _remove_at_indexes_init_random_idx(idx, i_len, i_idx_len); + g_array_set_size(array, i_len); + for (i = 0; i < i_len; i++) + g_array_index(array, gssize, i) = i; + + nm_utils_array_remove_at_indexes(array, &g_array_index(idx, guint, 0), i_idx_len); + + g_hash_table_remove_all(unique); + /* ensure that all the indexes are still unique */ + for (i = 0; i < array->len; i++) + g_hash_table_add(unique, GUINT_TO_POINTER(g_array_index(array, gssize, i))); + g_assert_cmpint(g_hash_table_size(unique), ==, array->len); + + for (i = 0; i < idx->len; i++) + g_hash_table_add(unique, GUINT_TO_POINTER(g_array_index(idx, guint, i))); + g_assert_cmpint(g_hash_table_size(unique), ==, i_len); + + /* ensure proper sort order in array */ + for (i = 0; i < array->len; i++) { + gssize i1 = g_array_index(array, gssize, i); + + g_assert(i1 >= 0 && i1 < i_len); + if (i > 0) { + gsize i0 = g_array_index(array, gssize, i - 1); + g_assert_cmpint(i0, <, i1); + } + } + } + } + } +} + +/*****************************************************************************/ + +static void +test_nm_ethernet_address_is_valid(void) +{ + g_assert(!nm_ether_addr_is_valid_str(NULL)); + g_assert(!nm_ether_addr_is_valid(NULL)); + + g_assert(!nm_ether_addr_is_valid_str("FF:FF:FF:FF:FF:FF")); + g_assert(!nm_ether_addr_is_valid_str("00:00:00:00:00:00")); + g_assert(!nm_ether_addr_is_valid_str("44:44:44:44:44:44")); + g_assert(!nm_ether_addr_is_valid_str("00:30:b4:00:00:00")); + + g_assert(!nm_ether_addr_is_valid_str("")); + g_assert(!nm_ether_addr_is_valid_str("1")); + g_assert(!nm_ether_addr_is_valid_str("2")); + + g_assert(!nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x00, 0x30, 0xb4, 0x00, 0x00, 0x00))); + g_assert(nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x00, 0x30, 0xb4, 0x00, 0x00, 0x01))); + + /* some Broad cast addresses (with MSB of first octet set). */ + g_assert(!nm_ether_addr_is_valid_str("57:44:44:44:44:44")); + g_assert(nm_ether_addr_is_valid_str("56:44:44:44:44:44")); + g_assert(!nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x03, 0x30, 0xb4, 0x00, 0x00, 0x00))); + g_assert(nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x02, 0x30, 0xb4, 0x00, 0x00, 0x01))); +} + +/*****************************************************************************/ + +static void +test_nm_utils_new_vlan_name(void) +{ + guint i, j; + const char *parent_names[] = { + "a", + "a2", + "a23", + "a23456789", + "a2345678901", + "a23456789012", + "a234567890123", + "a2345678901234", + "a23456789012345", + "a234567890123456", + "a2345678901234567", + }; + + for (i = 0; i < G_N_ELEMENTS(parent_names); i++) { + for (j = 0; j < 10; j++) { + gs_free char *ifname = NULL; + gs_free char *vlan_id_s = NULL; + guint vlan_id; + + /* Create a random VLAN id between 0 and 4094 */ + vlan_id = nmtst_get_rand_uint32() % 4095; + + vlan_id_s = g_strdup_printf(".%d", vlan_id); + + ifname = nm_utils_new_vlan_name(parent_names[i], vlan_id); + g_assert(ifname && ifname[0]); + g_assert_cmpint(strlen(ifname), + ==, + MIN(15, strlen(parent_names[i]) + strlen(vlan_id_s))); + g_assert(g_str_has_suffix(ifname, vlan_id_s)); + g_assert(ifname[strlen(ifname) - strlen(vlan_id_s)] == '.'); + g_assert(strncmp(ifname, parent_names[i], strlen(ifname) - strlen(vlan_id_s)) == 0); + if (!g_str_has_prefix(ifname, parent_names[i])) + g_assert_cmpint(strlen(ifname), ==, 15); + } + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "DEBUG", "DEFAULT"); + + g_test_add_func("/general/nm_utils_monotonic_timestamp_as_boottime", + test_nm_utils_monotonic_timestamp_as_boottime); + g_test_add_func("/general/nm_utils_kill_child", test_nm_utils_kill_child); + g_test_add_func("/general/nm_utils_array_remove_at_indexes", + test_nm_utils_array_remove_at_indexes); + g_test_add_func("/general/nm_ethernet_address_is_valid", test_nm_ethernet_address_is_valid); + g_test_add_func("/general/nm_utils_new_vlan_name", test_nm_utils_new_vlan_name); + + return g_test_run(); +} diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c new file mode 100644 index 0000000..87136dc --- /dev/null +++ b/src/core/tests/test-core.c @@ -0,0 +1,2639 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +/* need math.h for isinf() and INFINITY. No need to link with -lm */ +#include + +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-core-utils.h" +#include "systemd/nm-sd-utils-core.h" + +#include "dns/nm-dns-manager.h" +#include "nm-connectivity.h" + +#include "nm-test-utils-core.h" + +/* Reference implementation for nm_utils_ip6_address_clear_host_address. + * Taken originally from set_address_masked(), src/ndisc/nm-lndp-ndisc.c + **/ +static void +ip6_address_clear_host_address_reference(struct in6_addr *dst, struct in6_addr *src, guint8 plen) +{ + guint nbytes = plen / 8; + guint nbits = plen % 8; + + g_return_if_fail(plen <= 128); + g_assert(src); + g_assert(dst); + + if (plen >= 128) + *dst = *src; + else { + memset(dst, 0, sizeof(*dst)); + memcpy(dst, src, nbytes); + dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); + } +} + +static void +_randomize_in6_addr(struct in6_addr *addr, GRand *r) +{ + int i; + + for (i = 0; i < 4; i++) + ((guint32 *) addr)[i] = g_rand_int(r); +} + +static void +test_nm_utils_ip6_address_clear_host_address(void) +{ + GRand *r = g_rand_new(); + int plen, i; + + g_rand_set_seed(r, 0); + + for (plen = 0; plen <= 128; plen++) { + for (i = 0; i < 50; i++) { + struct in6_addr addr_src, addr_ref; + struct in6_addr addr1, addr2; + + _randomize_in6_addr(&addr_src, r); + _randomize_in6_addr(&addr_ref, r); + _randomize_in6_addr(&addr1, r); + _randomize_in6_addr(&addr2, r); + + addr1 = addr_src; + ip6_address_clear_host_address_reference(&addr_ref, &addr1, plen); + + _randomize_in6_addr(&addr1, r); + _randomize_in6_addr(&addr2, r); + addr1 = addr_src; + nm_utils_ip6_address_clear_host_address(&addr2, &addr1, plen); + g_assert_cmpint(memcmp(&addr1, &addr_src, sizeof(struct in6_addr)), ==, 0); + g_assert_cmpint(memcmp(&addr2, &addr_ref, sizeof(struct in6_addr)), ==, 0); + + /* test for self assignment/inplace update. */ + _randomize_in6_addr(&addr1, r); + addr1 = addr_src; + nm_utils_ip6_address_clear_host_address(&addr1, &addr1, plen); + g_assert_cmpint(memcmp(&addr1, &addr_ref, sizeof(struct in6_addr)), ==, 0); + } + } + + g_rand_free(r); +} + +/*****************************************************************************/ + +static void +test_logging_domains(void) +{ + const char *s; + + s = nm_logging_all_domains_to_string(); + g_assert(s && s[0]); +} + +/*****************************************************************************/ + +static void +test_logging_error(void) +{ + gs_free_error GError *error = NULL; + gboolean success; + + g_assert_cmpint(NM_MANAGER_ERROR, ==, _NM_MANAGER_ERROR); + G_STATIC_ASSERT_EXPR(NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL == _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL); + G_STATIC_ASSERT_EXPR(NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN + == _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN); + + success = nm_logging_setup("bogus", "ALL", NULL, &error); + nmtst_assert_no_success(success, error); + g_assert_cmpint(error->domain, ==, NM_MANAGER_ERROR); + g_assert_cmpint(error->code, ==, NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL); + nm_clear_pointer(&error, g_error_free); + + success = nm_logging_setup("debug", "bogus", NULL, &error); + nmtst_assert_no_success(success, error); + g_assert_cmpint(error->domain, ==, NM_MANAGER_ERROR); + g_assert_cmpint(error->code, ==, NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN); + nm_clear_pointer(&error, g_error_free); +} + +/*****************************************************************************/ + +static void +_test_same_prefix(const char *a1, const char *a2, guint8 plen) +{ + struct in6_addr a = *nmtst_inet6_from_string(a1); + struct in6_addr b = *nmtst_inet6_from_string(a2); + + g_assert(nm_utils_ip6_address_same_prefix(&a, &b, plen)); +} + +static void +test_nm_utils_ip6_address_same_prefix(void) +{ + guint n, i; + const guint N = 100; + union { + guint8 ptr[sizeof(struct in6_addr)]; + struct in6_addr val; + } a, b, addrmask, addrmask_bit; + guint8 plen; + + /* test#1 */ + for (n = 0; n < N; n++) { + gboolean is_same = n < N / 2; + gboolean result; + + nmtst_rand_buf(NULL, a.ptr, sizeof(a)); + nmtst_rand_buf(NULL, b.ptr, sizeof(b)); +again_plen: + plen = nmtst_get_rand_uint32() % 129; + if (!is_same && NM_IN_SET(plen, 0, 128)) + goto again_plen; + + if (plen < 128) { + for (i = 0; (i + 1) * 8 <= plen; i++) + b.ptr[i] = a.ptr[i]; + if (plen % 8) { + guint8 mask; + + g_assert(i < sizeof(a)); + mask = ~((1 << (8 - (plen % 8))) - 1); + b.ptr[i] = (a.ptr[i] & mask) | (b.ptr[i] & ~mask); + if (!is_same) { + mask = (1 << (8 - (plen % 8))); + b.ptr[i] = (b.ptr[i] & ~mask) | ~(b.ptr[i] & mask); + } + } else if (!is_same) { + g_assert(i > 0); + + b.ptr[i - 1] = (b.ptr[i - 1] & ~0x1) | ~(b.ptr[i - 1] & 0x1); + } + } else + b = a; + + result = nm_utils_ip6_address_same_prefix(&a.val, &b.val, plen); + g_assert(result == is_same); + g_assert(NM_IN_SET(result, TRUE, FALSE)); + } + + /* test#2 */ + for (n = 0; n < N; n++) { + nmtst_rand_buf(NULL, a.ptr, sizeof(a)); + nmtst_rand_buf(NULL, b.ptr, sizeof(b)); + plen = nmtst_get_rand_uint32() % 129; + + memset(addrmask.ptr, 0xFF, sizeof(addrmask)); + nm_utils_ip6_address_clear_host_address(&addrmask.val, &addrmask.val, plen); + + for (i = 0; i < sizeof(a); i++) + b.ptr[i] = (a.ptr[i] & addrmask.ptr[i]) | (b.ptr[i] & ~addrmask.ptr[i]); + + g_assert(nm_utils_ip6_address_same_prefix(&a.val, &b.val, plen) == TRUE); + } + + /* test#3 */ + for (n = 0; n < N; n++) { + gboolean reached = FALSE; + + nmtst_rand_buf(NULL, a.ptr, sizeof(a)); + nmtst_rand_buf(NULL, b.ptr, sizeof(b)); + plen = nmtst_get_rand_uint32() % 129; + + if (!plen) + continue; + + memset(addrmask.ptr, 0xFF, sizeof(addrmask)); + nm_utils_ip6_address_clear_host_address(&addrmask.val, &addrmask.val, plen); + + memset(addrmask_bit.ptr, 0xFF, sizeof(addrmask_bit)); + nm_utils_ip6_address_clear_host_address(&addrmask_bit.val, &addrmask_bit.val, plen - 1); + + for (i = 0; i < sizeof(a); i++) + b.ptr[i] = (a.ptr[i] & addrmask.ptr[i]) | (b.ptr[i] & ~addrmask.ptr[i]); + + /* flip the last bit. */ + for (i = 0; i < sizeof(a); i++) { + guint8 mask = addrmask.ptr[i] ^ addrmask_bit.ptr[i]; + if (mask) { + g_assert(!reached); + g_assert(nm_utils_is_power_of_two(mask)); + reached = TRUE; + b.ptr[i] = (b.ptr[i] & ~mask) | ~(b.ptr[i] & mask); + } + } + g_assert(reached); + + g_assert(nm_utils_ip6_address_same_prefix(&a.val, &b.val, plen) == FALSE); + } + + /* test#4 */ + _test_same_prefix("::", "::1", 10); + _test_same_prefix("abcd::", "abcd::1", 10); +} + +/*****************************************************************************/ + +static void +test_nm_utils_log_connection_diff(void) +{ + NMConnection *connection; + NMConnection *connection2; + + /* if logging is disabled (the default), nm_utils_log_connection_diff() returns + * early without doing anything. Hence, in the normal testing, this test does nothing. + * It only gets interesting, when run verbosely with NMTST_DEBUG=debug ... */ + + nm_log(LOGL_DEBUG, LOGD_CORE, NULL, NULL, "START TEST test_nm_utils_log_connection_diff..."); + + connection = nm_simple_connection_new(); + nm_connection_add_setting(connection, nm_setting_connection_new()); + nm_utils_log_connection_diff(connection, NULL, LOGL_DEBUG, LOGD_CORE, "test1", ">>> ", NULL); + + nm_connection_add_setting(connection, nm_setting_wired_new()); + nm_utils_log_connection_diff(connection, NULL, LOGL_DEBUG, LOGD_CORE, "test2", ">>> ", NULL); + + connection2 = nm_simple_connection_new_clone(connection); + nm_utils_log_connection_diff(connection, + connection2, + LOGL_DEBUG, + LOGD_CORE, + "test3", + ">>> ", + NULL); + + g_object_set(nm_connection_get_setting_connection(connection), + NM_SETTING_CONNECTION_ID, + "id", + NM_SETTING_CONNECTION_UUID, + "uuid", + NULL); + g_object_set(nm_connection_get_setting_connection(connection2), + NM_SETTING_CONNECTION_ID, + "id2", + NM_SETTING_CONNECTION_MASTER, + "master2", + NULL); + nm_utils_log_connection_diff(connection, + connection2, + LOGL_DEBUG, + LOGD_CORE, + "test4", + ">>> ", + NULL); + + nm_connection_add_setting(connection, nm_setting_802_1x_new()); + nm_utils_log_connection_diff(connection, + connection2, + LOGL_DEBUG, + LOGD_CORE, + "test5", + ">>> ", + NULL); + + g_object_set(nm_connection_get_setting_802_1x(connection), + NM_SETTING_802_1X_PASSWORD, + "id2", + NM_SETTING_802_1X_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NULL); + nm_utils_log_connection_diff(connection, NULL, LOGL_DEBUG, LOGD_CORE, "test6", ">>> ", NULL); + nm_utils_log_connection_diff(connection, + connection2, + LOGL_DEBUG, + LOGD_CORE, + "test7", + ">>> ", + NULL); + nm_utils_log_connection_diff(connection2, + connection, + LOGL_DEBUG, + LOGD_CORE, + "test8", + ">>> ", + NULL); + + g_clear_object(&connection); + g_clear_object(&connection2); + + connection = + nmtst_create_minimal_connection("id-vpn-1", NULL, NM_SETTING_VPN_SETTING_NAME, NULL); + nm_utils_log_connection_diff(connection, + NULL, + LOGL_DEBUG, + LOGD_CORE, + "test-vpn-1", + ">>> ", + NULL); + + g_clear_object(&connection); +} + +/*****************************************************************************/ + +static void +do_test_sysctl_ip_conf(int addr_family, const char *iface, const char *property) +{ + char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + const char *pp; + + pp = nm_utils_sysctl_ip_conf_path(addr_family, path, iface, property); + g_assert(pp == path); + g_assert(path[0] == '/'); + + g_assert(nm_utils_sysctl_ip_conf_is_path(addr_family, path, iface, property)); + g_assert(nm_utils_sysctl_ip_conf_is_path(addr_family, path, NULL, property)); +} + +static void +test_nm_utils_sysctl_ip_conf_path(void) +{ + do_test_sysctl_ip_conf(AF_INET6, "a", "mtu"); + do_test_sysctl_ip_conf(AF_INET6, "eth0", "mtu"); + do_test_sysctl_ip_conf(AF_INET6, "e23456789012345", "mtu"); +} + +/*****************************************************************************/ + +static NMConnection * +_match_connection_new(void) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4, *s_ip6; + char * uuid; + + connection = nm_simple_connection_new(); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, (NMSetting *) s_con); + uuid = nm_utils_uuid_generate(); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_ID, + "blahblah", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NULL); + g_free(uuid); + + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, (NMSetting *) s_wired); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + + return connection; +} + +static NMConnection * +_match_connection(GSList * connections, + NMConnection *original, + gboolean device_has_carrier, + gint64 default_v4_metric, + gint64 default_v6_metric) +{ + gs_free NMConnection **list = NULL; + guint i, len; + + len = g_slist_length(connections); + g_assert(len < 10); + + list = g_malloc((len + 1) * sizeof(NMConnection *)); + for (i = 0; i < len; i++, connections = connections->next) { + g_assert(connections); + g_assert(connections->data); + list[i] = connections->data; + } + list[i] = NULL; + + return nm_utils_match_connection(list, + original, + FALSE, + device_has_carrier, + default_v4_metric, + default_v6_metric, + NULL, + NULL); +} + +static void +test_connection_match_basic(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip4; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + /* Now change a material property like IPv4 method and ensure matching fails */ + s_ip4 = nm_connection_get_setting_ip4_config(orig); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL, + NULL); + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == NULL); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_ip6_method(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip6; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection is IPv6 method=link-local, and the + * candidate is both method=auto and may-faily=true, that the candidate is + * matched. + */ + s_ip6 = nm_connection_get_setting_ip6_config(orig); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(copy); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_ip6_method_ignore(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip6; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection is IPv6 method=link-local, and the + * candidate is method=ignore, that the candidate is matched. + */ + s_ip6 = nm_connection_get_setting_ip6_config(orig); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(copy); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_ip6_method_ignore_auto(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip6; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection is IPv6 method=auto, and the + * candidate is method=ignore, that the candidate is matched. + */ + s_ip6 = nm_connection_get_setting_ip6_config(orig); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(copy); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_ip4_method(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip4; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection is IPv4 method=disabled, and the + * candidate is both method=auto and may-faily=true, and the device has no + * carrier that the candidate is matched. + */ + s_ip4 = nm_connection_get_setting_ip4_config(orig); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(copy); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + matched = _match_connection(connections, orig, FALSE, 0, 0); + g_assert(matched == copy); + + /* Ensure when carrier=true matching fails */ + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == NULL); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_interface_name(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingConnection *s_con; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection has an interface name and the + * candidate's interface name is NULL, that the candidate is matched. + */ + s_con = nm_connection_get_setting_connection(orig); + g_assert(s_con); + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, "em1", NULL); + + s_con = nm_connection_get_setting_connection(copy); + g_assert(s_con); + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, NULL, NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_wired(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingWired *s_wired; + char * subchan_arr[] = {"0.0.8000", "0.0.8001", "0.0.8002", NULL}; + const char * mac = "52:54:00:ab:db:23"; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + s_wired = nm_connection_get_setting_wired(orig); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), + NM_SETTING_WIRED_PORT, + "tp", /* port is not compared */ + NM_SETTING_WIRED_MAC_ADDRESS, + mac, /* we allow MAC address just in one connection */ + NM_SETTING_WIRED_S390_SUBCHANNELS, + subchan_arr, + NM_SETTING_WIRED_S390_NETTYPE, + "qeth", + NULL); + + s_wired = nm_connection_get_setting_wired(copy); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), + NM_SETTING_WIRED_S390_SUBCHANNELS, + subchan_arr, + NM_SETTING_WIRED_S390_NETTYPE, + "qeth", + NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_wired2(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingWired *s_wired; + const char * mac = "52:54:00:ab:db:23"; + + orig = _match_connection_new(); + s_wired = nm_connection_get_setting_wired(orig); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), + NM_SETTING_WIRED_PORT, + "tp", /* port is not compared */ + NM_SETTING_WIRED_MAC_ADDRESS, + mac, /* we allow MAC address just in one connection */ + NULL); + + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if the generated connection do not have wired setting + * and s390 properties in the existing connection's setting are default, + * the connections match. It can happen if assuming VLAN devices. */ + nm_connection_remove_setting(orig, NM_TYPE_SETTING_WIRED); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_cloned_mac(void) +{ + NMConnection * orig, *exact, *fuzzy, *matched; + GSList * connections = NULL; + NMSettingWired *s_wired; + + orig = _match_connection_new(); + + fuzzy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, fuzzy); + s_wired = nm_connection_get_setting_wired(orig); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "52:54:00:ab:db:23", NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == fuzzy); + + exact = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, exact); + s_wired = nm_connection_get_setting_wired(exact); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "52:54:00:ab:db:23", NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == exact); + + g_object_set(G_OBJECT(s_wired), NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "52:54:00:ab:db:24", NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched == fuzzy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(fuzzy); + g_object_unref(exact); +} + +static void +test_connection_no_match_ip4_addr(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingIPConfig *s_ip4, *s_ip6; + NMIPAddress * nm_addr; + GError * error = NULL; + + orig = _match_connection_new(); + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that if we have two differences, ipv6.method (exception we allow) and + * ipv4.addresses (which is fatal), we don't match the connections. + */ + s_ip6 = nm_connection_get_setting_ip6_config(orig); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NULL); + + s_ip6 = nm_connection_get_setting_ip6_config(copy); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(orig); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.254", + NULL); + nm_addr = nm_ip_address_new(AF_INET, "1.1.1.4", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, nm_addr); + nm_ip_address_unref(nm_addr); + + s_ip4 = nm_connection_get_setting_ip4_config(copy); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "2.2.2.254", + NULL); + nm_addr = nm_ip_address_new(AF_INET, "2.2.2.4", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, nm_addr); + nm_ip_address_unref(nm_addr); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched != copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_no_match_vlan(void) +{ + NMConnection * orig, *copy, *matched; + GSList * connections = NULL; + NMSettingConnection *s_con; + NMSettingVlan * s_vlan_orig, *s_vlan_copy; + char * uuid; + + orig = nm_simple_connection_new(); + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(orig, (NMSetting *) s_con); + uuid = nm_utils_uuid_generate(); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_ID, + "vlan-test", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_VLAN_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NULL); + g_free(uuid); + nm_connection_add_setting(orig, nm_setting_vlan_new()); + + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Check that the connections do not match if VLAN flags differ */ + s_vlan_orig = nm_connection_get_setting_vlan(orig); + g_assert(s_vlan_orig); + g_object_set(G_OBJECT(s_vlan_orig), NM_SETTING_VLAN_FLAGS, NM_VLAN_FLAG_REORDER_HEADERS, NULL); + + s_vlan_copy = nm_connection_get_setting_vlan(copy); + g_assert(s_vlan_copy); + g_object_set(G_OBJECT(s_vlan_copy), NM_SETTING_VLAN_FLAGS, 0, NULL); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched != copy); + + /* Check that the connections do not match if VLAN priorities differ */ + g_object_set(G_OBJECT(s_vlan_orig), NM_SETTING_VLAN_FLAGS, 0, NULL); + nm_setting_vlan_add_priority_str(s_vlan_orig, NM_VLAN_INGRESS_MAP, "1:3"); + + g_object_set(G_OBJECT(s_vlan_copy), NM_SETTING_VLAN_FLAGS, 0, NULL); + nm_setting_vlan_add_priority_str(s_vlan_copy, NM_VLAN_INGRESS_MAP, "4:2"); + + matched = _match_connection(connections, orig, TRUE, 0, 0); + g_assert(matched != copy); + + g_slist_free(connections); + g_object_unref(orig); + g_object_unref(copy); +} + +static void +test_connection_match_ip4_routes1(void) +{ + gs_unref_object NMConnection *orig = NULL, *copy = NULL; + NMConnection * matched; + nm_auto_free_slist GSList *connections = NULL; + NMSettingIPConfig * s_ip4; + + orig = _match_connection_new(); + + s_ip4 = nm_connection_get_setting_ip4_config(orig); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + nmtst_setting_ip_config_add_address(s_ip4, "10.0.0.1", 8); + + /* Clone connection */ + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Set routes on original connection */ + nmtst_setting_ip_config_add_route(s_ip4, "172.25.16.0", 24, "10.0.0.2", -1); + nmtst_setting_ip_config_add_route(s_ip4, "172.25.17.0", 24, "10.0.0.3", 20); + + /* Set single route on cloned connection */ + s_ip4 = nm_connection_get_setting_ip4_config(copy); + g_assert(s_ip4); + nmtst_setting_ip_config_add_route(s_ip4, "172.25.17.0", 24, "10.0.0.3", 20); + + /* Try to match the connections */ + matched = _match_connection(connections, orig, FALSE, 100, 0); + g_assert(matched == NULL); +} + +static void +test_connection_match_ip4_routes2(void) +{ + gs_unref_object NMConnection *orig = NULL, *copy = NULL; + NMConnection * matched; + nm_auto_free_slist GSList *connections = NULL; + NMSettingIPConfig * s_ip4; + + orig = _match_connection_new(); + + s_ip4 = nm_connection_get_setting_ip4_config(orig); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + nmtst_setting_ip_config_add_address(s_ip4, "10.0.0.1", 8); + + /* Clone connection */ + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Set routes on original connection */ + nmtst_setting_ip_config_add_route(s_ip4, "172.25.16.0", 24, "10.0.0.2", -1); + nmtst_setting_ip_config_add_route(s_ip4, "172.25.17.0", 24, "10.0.0.3", 20); + + /* Set routes on cloned connection, changing order and using explicit metrics */ + s_ip4 = nm_connection_get_setting_ip4_config(copy); + g_assert(s_ip4); + nmtst_setting_ip_config_add_route(s_ip4, "172.25.17.0", 24, "10.0.0.3", 20); + nmtst_setting_ip_config_add_route(s_ip4, "172.25.16.0", 24, "10.0.0.2", 100); + + /* Try to match the connections using different default metrics */ + matched = _match_connection(connections, orig, FALSE, 100, 0); + g_assert(matched == copy); + matched = _match_connection(connections, orig, FALSE, 500, 0); + g_assert(matched == NULL); +} + +static void +test_connection_match_ip6_routes(void) +{ + gs_unref_object NMConnection *orig = NULL, *copy = NULL; + NMConnection * matched; + nm_auto_free_slist GSList *connections = NULL; + NMSettingIPConfig * s_ip6; + + orig = _match_connection_new(); + + s_ip6 = nm_connection_get_setting_ip6_config(orig); + g_assert(s_ip6); + g_object_set(G_OBJECT(s_ip6), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NULL); + + nmtst_setting_ip_config_add_address(s_ip6, "fd01::15", 64); + + /* Clone connection */ + copy = nm_simple_connection_new_clone(orig); + connections = g_slist_append(connections, copy); + + /* Set routes on original connection */ + nmtst_setting_ip_config_add_route(s_ip6, "2001:db8:a:b:0:0:0:0", 64, "fd01::16", -1); + + /* Set routes on cloned connection */ + s_ip6 = nm_connection_get_setting_ip6_config(copy); + g_assert(s_ip6); + nmtst_setting_ip_config_add_route(s_ip6, "2001:db8:a:b:0:0:0:0", 64, "fd01::16", 50); + + /* Try to match the connections */ + matched = _match_connection(connections, orig, FALSE, 0, 100); + g_assert(matched == NULL); + matched = _match_connection(connections, orig, FALSE, 0, 50); + g_assert(matched == copy); +} + +#define do_test_wildcard_match_eval(str, ...) \ + nm_wildcard_match_check(str, (const char *const[]){__VA_ARGS__}, NM_NARG(__VA_ARGS__)) + +#define do_test_wildcard_match(str, result, ...) \ + g_assert(do_test_wildcard_match_eval(str, __VA_ARGS__) == result) + +static void +test_wildcard_match(void) +{ + do_test_wildcard_match("foobar", TRUE); + + do_test_wildcard_match("foo", TRUE, "foo", "bar", "baz"); + do_test_wildcard_match("bar", TRUE, "foo", "bar", "baz"); + do_test_wildcard_match("baz", TRUE, "foo", "bar", "baz"); + do_test_wildcard_match("aaa", FALSE, "foo", "bar", "baz"); + do_test_wildcard_match("", FALSE, "foo", "bar", "baz"); + + do_test_wildcard_match("ens1", TRUE, "ens1*"); + do_test_wildcard_match("ens10", TRUE, "ens1*"); + do_test_wildcard_match("ens11", TRUE, "ens1*"); + do_test_wildcard_match("ens12", TRUE, "ens1*"); + do_test_wildcard_match("eth0", FALSE, "ens1*"); + do_test_wildcard_match("ens", FALSE, "ens1*"); + + do_test_wildcard_match("ens1*", TRUE, "ens1\\*"); + do_test_wildcard_match("ens1", FALSE, "ens1\\*"); + do_test_wildcard_match("ens10", FALSE, "ens1\\*"); + + do_test_wildcard_match("abcd", TRUE, "ab??"); + do_test_wildcard_match("ab", FALSE, "ab??"); + + do_test_wildcard_match("ab??", TRUE, "ab\\?\\?"); + do_test_wildcard_match("abcd", FALSE, "ab\\?\\?"); + + do_test_wildcard_match("ens10", TRUE, "ens1*", "!ens11"); + do_test_wildcard_match("ens11", FALSE, "ens1*", "!ens11"); + do_test_wildcard_match("ens12", TRUE, "ens1*", "!ens11"); + + do_test_wildcard_match("a", FALSE, "!a", "!b"); + do_test_wildcard_match("b", FALSE, "!a", "!b"); + do_test_wildcard_match("c", TRUE, "!a", "!b"); + do_test_wildcard_match("!a", TRUE, "!a", "!b"); + + do_test_wildcard_match("!net", TRUE, "\\!net"); + do_test_wildcard_match("net", FALSE, "\\!net"); + do_test_wildcard_match("ens10", FALSE, "\\!net"); + do_test_wildcard_match("\\!net", FALSE, "\\!net"); + + do_test_wildcard_match("eth0", FALSE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("eth1", TRUE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("myeth0", FALSE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("myeth2", TRUE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("veth0", FALSE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("veth1", FALSE, "*eth?", "!veth*", "!*0"); + do_test_wildcard_match("dummy1", FALSE, "*eth?", "!veth*", "!*0"); + + do_test_wildcard_match("a", TRUE, "!!a"); + do_test_wildcard_match("b", TRUE, "!!a"); + do_test_wildcard_match("!a", FALSE, "!!a"); + + do_test_wildcard_match("\\", TRUE, "\\\\\\"); + do_test_wildcard_match("\\\\", FALSE, "\\\\"); + do_test_wildcard_match("", FALSE, "\\\\"); + + do_test_wildcard_match("\\a", TRUE, "\\\\\\a"); + do_test_wildcard_match("b", TRUE, "&!a"); + do_test_wildcard_match("a", FALSE, "&!a"); + do_test_wildcard_match("!a", TRUE, "&\\!a"); + do_test_wildcard_match("!a", TRUE, "|\\!a"); + do_test_wildcard_match("!a", TRUE, "\\!a"); + + do_test_wildcard_match("name", FALSE, "name[123]"); + do_test_wildcard_match("name1", TRUE, "name[123]"); + do_test_wildcard_match("name2", TRUE, "name[123]"); + do_test_wildcard_match("name3", TRUE, "name[123]"); + do_test_wildcard_match("name4", FALSE, "name[123]"); + + if (do_test_wildcard_match_eval("[a]", "\\[a\\]") != TRUE) { +#if defined(__GLIBC__) + do_test_wildcard_match("[a]", TRUE, "\\[a\\]"); + g_assert_not_reached(); +#endif + /* It seems musl's fnmatch() does not like such ranges. */ + g_test_skip("libc does not support ranges with fnmatch()!!"); + } + + do_test_wildcard_match("aa", FALSE, "!a*"); + do_test_wildcard_match("aa", FALSE, "&!a*"); + do_test_wildcard_match("aa", FALSE, "|!a*"); + do_test_wildcard_match("aa", FALSE, "&!a*", "aa"); + do_test_wildcard_match("aa", TRUE, "|!a*", "aa"); +} + +static NMConnection * +_create_connection_autoconnect(const char *id, gboolean autoconnect, int autoconnect_priority) +{ + NMConnection * c; + NMSettingConnection *s_con; + + c = nmtst_create_minimal_connection(id, NULL, NM_SETTING_WIRED_SETTING_NAME, &s_con); + g_object_set(s_con, + NM_SETTING_CONNECTION_AUTOCONNECT, + autoconnect, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + autoconnect_priority, + NULL); + nmtst_connection_normalize(c); + return c; +} + +static int +_cmp_autoconnect_priority_p_with_data(gconstpointer pa, gconstpointer pb, gpointer user_data) +{ + return nm_utils_cmp_connection_by_autoconnect_priority(*((NMConnection **) pa), + *((NMConnection **) pb)); +} + +static void +_test_connection_sort_autoconnect_priority_one(NMConnection **list, gboolean shuffle) +{ + int i, j; + int count = 0; + gs_unref_ptrarray GPtrArray *connections = g_ptr_array_new(); + + while (list[count]) + count++; + g_assert(count > 1); + + /* copy the list of connections over to @connections and shuffle. */ + for (i = 0; i < count; i++) + g_ptr_array_add(connections, list[i]); + if (shuffle) { + for (i = count - 1; i > 0; i--) { + j = g_rand_int(nmtst_get_rand()) % (i + 1); + NM_SWAP(&connections->pdata[i], &connections->pdata[j]); + } + } + + /* sort it... */ + g_ptr_array_sort_with_data(connections, _cmp_autoconnect_priority_p_with_data, NULL); + + for (i = 0; i < count; i++) { + if (list[i] == connections->pdata[i]) + continue; + if (shuffle + && nm_utils_cmp_connection_by_autoconnect_priority(list[i], connections->pdata[i]) == 0) + continue; + g_message( + "After sorting, the order of connections is not as expected!! Offending index: %d", + i); + for (j = 0; j < count; j++) + g_message(" %3d: %p/%-20s - %p/%-20s", + j, + list[j], + nm_connection_get_id(list[j]), + connections->pdata[j], + nm_connection_get_id(connections->pdata[j])); + g_assert_not_reached(); + } +} + +static void +_test_connection_sort_autoconnect_priority_free(NMConnection **list) +{ + while (*list) { + g_object_unref(*list); + *list = NULL; + } +} + +static void +test_connection_sort_autoconnect_priority(void) +{ + NMConnection *c1[] = { + _create_connection_autoconnect("AC/100", TRUE, 100), + _create_connection_autoconnect("AC/100", TRUE, 100), + _create_connection_autoconnect("AC/99", TRUE, 99), + _create_connection_autoconnect("AC/0", TRUE, 0), + _create_connection_autoconnect("AC/0", TRUE, 0), + _create_connection_autoconnect("AC/-1", TRUE, -1), + _create_connection_autoconnect("AC/-3", TRUE, -3), + _create_connection_autoconnect("ac/0", FALSE, 0), + _create_connection_autoconnect("ac/0", FALSE, 0), + _create_connection_autoconnect("ac/1", FALSE, 1), + _create_connection_autoconnect("ac/-1", FALSE, -1), + _create_connection_autoconnect("ac/1", FALSE, 1), + _create_connection_autoconnect("ac/0", FALSE, 0), + NULL, + }; + NMConnection *c2[] = { + _create_connection_autoconnect("AC/100", TRUE, 100), + _create_connection_autoconnect("AC/99", TRUE, 99), + _create_connection_autoconnect("AC/0", TRUE, 0), + _create_connection_autoconnect("AC/-1", TRUE, -1), + _create_connection_autoconnect("AC/-3", TRUE, -3), + _create_connection_autoconnect("ac/0", FALSE, 0), + NULL, + }; + + _test_connection_sort_autoconnect_priority_one(c1, FALSE); + _test_connection_sort_autoconnect_priority_one(c2, FALSE); + _test_connection_sort_autoconnect_priority_one(c1, TRUE); + _test_connection_sort_autoconnect_priority_one(c2, TRUE); + + _test_connection_sort_autoconnect_priority_free(c1); + _test_connection_sort_autoconnect_priority_free(c2); +} + +/*****************************************************************************/ + +#define MATCH_S390 "S390:" +#define MATCH_DRIVER "DRIVER:" + +static NMMatchSpecMatchType +_test_match_spec_device(const GSList *specs, const char *match_str) +{ + if (match_str && g_str_has_prefix(match_str, MATCH_S390)) + return nm_match_spec_device(specs, + NULL, + NULL, + NULL, + NULL, + NULL, + &match_str[NM_STRLEN(MATCH_S390)], + NULL); + if (match_str && g_str_has_prefix(match_str, MATCH_DRIVER)) { + gs_free char *s = g_strdup(&match_str[NM_STRLEN(MATCH_DRIVER)]); + char * t; + + t = strchr(s, '|'); + if (t) { + t[0] = '\0'; + t++; + } + return nm_match_spec_device(specs, NULL, NULL, s, t, NULL, NULL, NULL); + } + return nm_match_spec_device(specs, match_str, NULL, NULL, NULL, NULL, NULL, NULL); +} + +static void +_do_test_match_spec_device(const char * spec_str, + const char *const *matches, + const char *const *no_matches, + const char *const *neg_matches) +{ + GSList * specs, *specs_randperm = NULL, *specs_resplit, *specs_i, *specs_j; + guint i; + gs_free char * specs_joined = NULL; + const char * s; + static const char *no_matches_default[] = {"e", + "em", + "em*", + "em\\", + "em\\*", + "em\\1", + "em\\11", + "em\\2", + "em1", + "em11", + "em2", + "=em*", + NULL}; + + g_assert(spec_str); + + specs = nm_match_spec_split(spec_str); + + /* assert that split(join(specs)) == specs */ + specs_joined = nm_match_spec_join(specs); + specs_resplit = nm_match_spec_split(specs_joined); + specs_i = specs; + specs_j = specs_resplit; + while (specs_i && specs_j && g_strcmp0(specs_i->data, specs_j->data) == 0) { + specs_i = specs_i->next; + specs_j = specs_j->next; + } + g_assert(!specs_i); + g_assert(!specs_j); + g_slist_free_full(specs_resplit, g_free); + + /* also check the matches in the random order. They must yield the same result because + * matches are inclusive -- except "except:" which always wins. */ + specs_randperm = nmtst_rand_perm_gslist(NULL, g_slist_copy(specs)); + + for (i = 0; matches && matches[i]; i++) { + g_assert(_test_match_spec_device(specs, matches[i]) == NM_MATCH_SPEC_MATCH); + g_assert(_test_match_spec_device(specs_randperm, matches[i]) == NM_MATCH_SPEC_MATCH); + } + for (i = 0; neg_matches && neg_matches[i]; i++) { + g_assert(_test_match_spec_device(specs, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH); + g_assert(_test_match_spec_device(specs_randperm, neg_matches[i]) + == NM_MATCH_SPEC_NEG_MATCH); + } + for (i = 0; no_matches && no_matches[i]; i++) { + g_assert(_test_match_spec_device(specs, no_matches[i]) == NM_MATCH_SPEC_NO_MATCH); + g_assert(_test_match_spec_device(specs_randperm, no_matches[i]) == NM_MATCH_SPEC_NO_MATCH); + } + if (!no_matches) { + for (i = 0; (s = no_matches_default[i]); i++) { + if ((matches && g_strv_contains(matches, s)) + || (neg_matches && g_strv_contains(neg_matches, s))) + continue; + g_assert(_test_match_spec_device(specs, s) == NM_MATCH_SPEC_NO_MATCH); + g_assert(_test_match_spec_device(specs_randperm, s) == NM_MATCH_SPEC_NO_MATCH); + } + } + + g_slist_free(specs_randperm); + g_slist_free_full(specs, g_free); +} + +static void +test_match_spec_device(void) +{ + _do_test_match_spec_device("em1", NM_MAKE_STRV("em1"), NULL, NULL); + _do_test_match_spec_device("em1,em2", NM_MAKE_STRV("em1", "em2"), NULL, NULL); + _do_test_match_spec_device("em1,em2,interface-name:em2", + NM_MAKE_STRV("em1", "em2"), + NULL, + NULL); + _do_test_match_spec_device("interface-name:em1", NM_MAKE_STRV("em1"), NULL, NULL); + _do_test_match_spec_device("interface-name:em*", + NM_MAKE_STRV("em", + "em*", + "em\\", + "em\\*", + "em\\1", + "em\\11", + "em\\2", + "em1", + "em11", + "em2", + "em3"), + NULL, + NULL); + _do_test_match_spec_device("interface-name:em\\*", + NM_MAKE_STRV("em\\", "em\\*", "em\\1", "em\\11", "em\\2"), + NULL, + NULL); + _do_test_match_spec_device("interface-name:~em\\*", + NM_MAKE_STRV("em\\", "em\\*", "em\\1", "em\\11", "em\\2"), + NULL, + NULL); + _do_test_match_spec_device("except:*", NULL, NM_MAKE_STRV(NULL), NM_MAKE_STRV("a")); + _do_test_match_spec_device("interface-name:=em*", NM_MAKE_STRV("em*"), NULL, NULL); + _do_test_match_spec_device( + "interface-name:em*,except:interface-name:em1*", + NM_MAKE_STRV("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em2", "em3"), + NULL, + NM_MAKE_STRV("em1", "em11")); + _do_test_match_spec_device("interface-name:em*,except:interface-name:=em*", + NM_MAKE_STRV("em", + "em\\", + "em\\*", + "em\\1", + "em\\11", + "em\\2", + "em1", + "em11", + "em2", + "em3"), + NULL, + NM_MAKE_STRV("em*")); + _do_test_match_spec_device("except:interface-name:em*", + NM_MAKE_STRV("", "eth", "eth1", "e1"), + NM_MAKE_STRV(NULL), + NM_MAKE_STRV("em", + "em\\", + "em\\*", + "em\\1", + "em\\11", + "em\\2", + "em1", + "em11", + "em2", + "em3")); + _do_test_match_spec_device("aa,bb,cc\\,dd,e,,", + NM_MAKE_STRV("aa", "bb", "cc,dd", "e"), + NULL, + NULL); + _do_test_match_spec_device("aa;bb;cc\\;dd;e,;", + NM_MAKE_STRV("aa", "bb", "cc;dd", "e"), + NULL, + NULL); + _do_test_match_spec_device("interface-name:em\\;1,em\\,2,\\,,\\\\,,em\\\\x", + NM_MAKE_STRV("em;1", "em,2", ",", "\\", "em\\x"), + NULL, + NULL); + _do_test_match_spec_device("\\s\\s,\\sinterface-name:a,\\s,", + NM_MAKE_STRV(" ", " ", " interface-name:a"), + NULL, + NULL); + _do_test_match_spec_device(" aa ; bb ; cc\\;dd ;e , ; \t\\t , ", + NM_MAKE_STRV("aa", "bb", "cc;dd", "e", "\t"), + NULL, + NULL); + + _do_test_match_spec_device("s390-subchannels:0.0.1000\\,0.0.1001", + NM_MAKE_STRV(MATCH_S390 "0.0.1000", + MATCH_S390 "0.0.1000,deadbeef", + MATCH_S390 "0.0.1000,0.0.1001", + MATCH_S390 "0.0.1000,0.0.1002"), + NM_MAKE_STRV(MATCH_S390 "0.0.1001"), + NULL); + _do_test_match_spec_device("*,except:s390-subchannels:0.0.1000\\,0.0.1001", + NULL, + NM_MAKE_STRV(NULL), + NM_MAKE_STRV(MATCH_S390 "0.0.1000", + MATCH_S390 "0.0.1000,deadbeef", + MATCH_S390 "0.0.1000,0.0.1001", + MATCH_S390 "0.0.1000,0.0.1002")); + + _do_test_match_spec_device("driver:DRV", + NM_MAKE_STRV(MATCH_DRIVER "DRV", MATCH_DRIVER "DRV|1.6"), + NM_MAKE_STRV(MATCH_DRIVER "DR", MATCH_DRIVER "DR*"), + NULL); + _do_test_match_spec_device( + "driver:DRV//", + NM_MAKE_STRV(MATCH_DRIVER "DRV/"), + NM_MAKE_STRV(MATCH_DRIVER "DRV/|1.6", MATCH_DRIVER "DR", MATCH_DRIVER "DR*"), + NULL); + _do_test_match_spec_device("driver:DRV//*", + NM_MAKE_STRV(MATCH_DRIVER "DRV/", MATCH_DRIVER "DRV/|1.6"), + NM_MAKE_STRV(MATCH_DRIVER "DR", MATCH_DRIVER "DR*"), + NULL); + _do_test_match_spec_device("driver:DRV//1.5*", + NM_MAKE_STRV(MATCH_DRIVER "DRV/|1.5", MATCH_DRIVER "DRV/|1.5.2"), + NM_MAKE_STRV(MATCH_DRIVER "DRV/", + MATCH_DRIVER "DRV/|1.6", + MATCH_DRIVER "DR", + MATCH_DRIVER "DR*"), + NULL); +} + +/*****************************************************************************/ + +static void +_do_test_match_spec_config(const char * file, + int line, + const char * spec_str, + guint version, + guint v_maj, + guint v_min, + guint v_mic, + NMMatchSpecMatchType expected) +{ + GSList * specs; + NMMatchSpecMatchType match_result; + guint c_maj, c_min, c_mic; + + g_assert_cmpint(version, ==, nm_encode_version(v_maj, v_min, v_mic)); + + nm_decode_version(version, &c_maj, &c_min, &c_mic); + g_assert_cmpint(c_maj, ==, c_maj); + g_assert_cmpint(c_min, ==, c_min); + g_assert_cmpint(c_mic, ==, c_mic); + + specs = nm_match_spec_split(spec_str); + + match_result = nm_match_spec_config(specs, version, NULL); + + if (expected != match_result) + g_error("%s:%d: failed comparing \"%s\" with %u.%u.%u. Expected %d, but got %d", + file, + line, + spec_str, + v_maj, + v_min, + v_mic, + (int) expected, + (int) match_result); + + if (g_slist_length(specs) == 1 && !g_str_has_prefix(specs->data, "except:")) { + /* there is only one spec in the list... test that we match except: */ + char * sss = g_strdup_printf("except:%s", (char *) specs->data); + GSList * specs2 = g_slist_append(NULL, sss); + NMMatchSpecMatchType match_result2; + + match_result2 = nm_match_spec_config(specs2, version, NULL); + if (match_result == NM_MATCH_SPEC_NO_MATCH) + g_assert_cmpint(match_result2, ==, NM_MATCH_SPEC_MATCH); + else + g_assert_cmpint(match_result2, ==, NM_MATCH_SPEC_NEG_MATCH); + + g_slist_free_full(specs2, g_free); + } + + g_slist_free_full(specs, g_free); +} +#define do_test_match_spec_config(spec, v_maj, v_min, v_mic, expected) \ + _do_test_match_spec_config(__FILE__, \ + __LINE__, \ + ("" spec), \ + NM_ENCODE_VERSION((v_maj), (v_min), (v_mic)), \ + (v_maj), \ + (v_min), \ + (v_mic), \ + (expected)) + +static void +test_match_spec_config(void) +{ + do_test_match_spec_config("", 1, 2, 3, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version:1.2.3", 1, 2, 2, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version:1.2.3", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version:1.2.3", 1, 2, 4, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("nm-version:1.2", 1, 1, 2, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version:1.2", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version:1.2", 1, 2, 2, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version:1.2", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version:1.2", 1, 2, 4, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version:1.2", 1, 3, 0, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("nm-version-min:1.2.3", 0, 2, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 1, 1, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 2, 2, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 2, 5, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 3, 0, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 3, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2.3", 1, 4, 30, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("nm-version-min:1.2", 0, 2, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 1, 1, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 2, 5, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 3, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 3, 30, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.2", 1, 4, 30, NM_MATCH_SPEC_MATCH); + + do_test_match_spec_config("nm-version-min:1", 0, 2, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 1, 1, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 2, 5, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 3, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 3, 30, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1", 1, 4, 30, NM_MATCH_SPEC_MATCH); + + do_test_match_spec_config("nm-version-max:1.2.3", 0, 2, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 1, 1, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 2, 1, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 2, 2, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 2, 5, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 3, 0, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 3, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2.3", 1, 4, 30, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("nm-version-max:1.2", 0, 2, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 1, 1, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 2, 5, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 3, 0, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 3, 30, NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config("nm-version-max:1.2", 1, 4, 30, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("nm-version-max:1", 0, 2, 30, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 1, 1, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 2, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 2, 3, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 2, 5, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 3, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 3, 30, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 1, 4, 30, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-max:1", 2, 4, 30, NM_MATCH_SPEC_NO_MATCH); + + do_test_match_spec_config("except:nm-version:1.4.8", 1, 6, 0, NM_MATCH_SPEC_MATCH); + do_test_match_spec_config("nm-version-min:1.6,except:nm-version:1.4.8", + 1, + 6, + 0, + NM_MATCH_SPEC_MATCH); + + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 0, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 0, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 15, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 16, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 17, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 2, + 20, + NM_MATCH_SPEC_MATCH); + + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 3, + 0, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 0, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 5, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 6, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 7, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 8, + NM_MATCH_SPEC_NEG_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 4, + 9, + NM_MATCH_SPEC_MATCH); + + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 5, + 0, + NM_MATCH_SPEC_NO_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 6, + 0, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 6, + 5, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 7, + 7, + NM_MATCH_SPEC_MATCH); + do_test_match_spec_config( + "nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", + 1, + 8, + 8, + NM_MATCH_SPEC_MATCH); +} + +/*****************************************************************************/ + +static void +test_nm_utils_strbuf_append(void) +{ +#define BUF_ORIG "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define STR_ORIG "abcdefghijklmnopqrstuvwxyz" + int buf_len; + int rep; + char buf[NM_STRLEN(BUF_ORIG) + 1]; + char str[NM_STRLEN(BUF_ORIG) + 1]; + +#define _strbuf_append(buf, len, format, ...) \ + G_STMT_START \ + { \ + char ** _buf = (buf); \ + gsize * _len = (len); \ + const char * _str_iter; \ + gs_free char *_str = NULL; \ + \ + switch (nmtst_get_rand_uint32() % 4) { \ + case 0: \ + nm_utils_strbuf_append(_buf, _len, (format), __VA_ARGS__); \ + break; \ + case 1: \ + _str = g_strdup_printf((format), __VA_ARGS__); \ + nm_utils_strbuf_append_str(_buf, _len, _str); \ + break; \ + case 2: \ + _str = g_strdup_printf((format), __VA_ARGS__); \ + nm_utils_strbuf_append_bin(_buf, _len, _str, strlen(_str)); \ + break; \ + case 3: \ + _str = g_strdup_printf((format), __VA_ARGS__); \ + if (!_str[0]) \ + nm_utils_strbuf_append_str(_buf, _len, _str); \ + for (_str_iter = _str; _str_iter[0]; _str_iter++) \ + nm_utils_strbuf_append_c(_buf, _len, _str_iter[0]); \ + break; \ + } \ + } \ + G_STMT_END + +#define _strbuf_append_str(buf, len, str) \ + G_STMT_START \ + { \ + char ** _buf = (buf); \ + gsize * _len = (len); \ + const char *_str = (str); \ + \ + switch (nmtst_get_rand_uint32() % 4) { \ + case 0: \ + nm_utils_strbuf_append(_buf, _len, "%s", _str ?: ""); \ + break; \ + case 1: \ + nm_utils_strbuf_append_str(_buf, _len, _str); \ + break; \ + case 2: \ + nm_utils_strbuf_append_bin(_buf, _len, _str, _str ? strlen(_str) : 0); \ + break; \ + case 3: \ + if (!_str || !_str[0]) \ + nm_utils_strbuf_append_str(_buf, _len, _str); \ + for (; _str && _str[0]; _str++) \ + nm_utils_strbuf_append_c(_buf, _len, _str[0]); \ + break; \ + } \ + } \ + G_STMT_END + +#define _strbuf_append_c(buf, len, ch) \ + G_STMT_START \ + { \ + char **_buf = (buf); \ + gsize *_len = (len); \ + char _ch = (ch); \ + \ + switch (nmtst_get_rand_uint32() % 4) { \ + case 0: \ + nm_utils_strbuf_append(_buf, _len, "%c", _ch); \ + break; \ + case 1: \ + nm_utils_strbuf_append_str(_buf, _len, ((char[2]){_ch, 0})); \ + break; \ + case 2: \ + nm_utils_strbuf_append_bin(_buf, _len, &_ch, 1); \ + break; \ + case 3: \ + nm_utils_strbuf_append_c(_buf, _len, _ch); \ + break; \ + } \ + } \ + G_STMT_END + + for (buf_len = 0; buf_len < 10; buf_len++) { + for (rep = 0; rep < 50; rep++) { + const int s_len = nmtst_get_rand_uint32() % (sizeof(str) - 5); + char * t_buf; + gsize t_len; + int test_mode; + + strcpy(str, STR_ORIG); + str[s_len] = '\0'; + + g_assert_cmpint(str[sizeof(str) - 1], ==, '\0'); + g_assert_cmpint(strlen(str), ==, s_len); + + strcpy(buf, BUF_ORIG); + + t_buf = buf; + t_len = buf_len; + + test_mode = nmtst_get_rand_uint32() % 5; + + switch (test_mode) { + case 0: + if (s_len == 1) { + _strbuf_append_c(&t_buf, &t_len, str[0]); + break; + } + /* fall-through */ + case 1: + _strbuf_append_str(&t_buf, &t_len, str); + break; + case 2: + if (s_len == 1) { + _strbuf_append(&t_buf, &t_len, "%c", str[0]); + break; + } + /* fall-through */ + case 3: + _strbuf_append(&t_buf, &t_len, "%s", str); + break; + case 4: + g_snprintf(t_buf, t_len, "%s", str); + if (t_len > 0 && strlen(str) >= buf_len && (nmtst_get_rand_uint32() % 2)) { + /* the string was truncated by g_snprintf(). That means, at the last position in the + * buffer is now NUL. + * Replace the NUL by the actual character, and check that nm_utils_strbuf_seek_end() + * does the right thing: NUL terminate the buffer and seek past the end of the buffer. */ + g_assert_cmpmem(t_buf, t_len - 1, str, t_len - 1); + g_assert(t_buf[t_len - 1] == '\0'); + g_assert(str[t_len - 1] != '\0'); + t_buf[t_len - 1] = str[t_len - 1]; + nm_utils_strbuf_seek_end(&t_buf, &t_len); + g_assert(t_len == 0); + g_assert(t_buf == &buf[buf_len]); + g_assert(t_buf[-1] == '\0'); + } else { + nm_utils_strbuf_seek_end(&t_buf, &t_len); + if (buf_len > 0 && strlen(str) + 1 > buf_len) { + /* the buffer was truncated by g_snprintf() above. + * + * But nm_utils_strbuf_seek_end() does not recognize that and returns + * a remaining length of 1. + * + * Note that other nm_utils_strbuf_append*() functions recognize + * truncation, and properly set the remaining length to zero. + * As the assertions below check for the behavior of nm_utils_strbuf_append*(), + * we assert here that nm_utils_strbuf_seek_end() behaved as expected, and then + * adjust t_buf/t_len according to the "is-truncated" case. */ + g_assert(t_len == 1); + g_assert(t_buf == &buf[buf_len - 1]); + g_assert(t_buf[0] == '\0'); + t_len = 0; + t_buf++; + } + } + break; + } + + /* Assert that the source-buffer is unmodified. */ + g_assert_cmpint(str[s_len], ==, '\0'); + str[s_len] = STR_ORIG[s_len]; + g_assert(!memcmp(str, STR_ORIG, sizeof(str))); + str[s_len] = '\0'; + + g_assert_cmpint(t_len, >=, 0); + g_assert_cmpint(t_len, <=, buf_len); + g_assert(t_buf >= buf); + + /* Assert what was written to the destination buffer. */ + switch (buf_len) { + case 0: + g_assert_cmpint(t_len, ==, 0); + g_assert(t_buf == buf); + g_assert(!memcmp(buf, BUF_ORIG, sizeof(buf))); + break; + case 1: + if (s_len == 0) { + g_assert_cmpint(t_len, ==, 1); + g_assert(t_buf == buf); + g_assert(buf[0] == '\0'); + g_assert(!memcmp(&buf[1], &BUF_ORIG[1], sizeof(buf) - 1)); + } else { + g_assert_cmpint(t_len, ==, 0); + g_assert(t_buf == &buf[1]); + g_assert(buf[0] == '\0'); + g_assert(!memcmp(&buf[1], &BUF_ORIG[1], sizeof(buf) - 1)); + } + break; + default: + if (s_len == 0) { + g_assert_cmpint(t_len, ==, buf_len); + g_assert(t_buf == buf); + g_assert(buf[0] == '\0'); + g_assert(!memcmp(&buf[1], &BUF_ORIG[1], sizeof(buf) - 1)); + } else if (buf_len <= s_len) { + g_assert_cmpint(t_len, ==, 0); + g_assert(t_buf == &buf[buf_len]); + g_assert(!memcmp(buf, STR_ORIG, buf_len - 1)); + g_assert(buf[buf_len - 1] == '\0'); + g_assert(!memcmp(&buf[buf_len], &BUF_ORIG[buf_len], sizeof(buf) - buf_len)); + } else { + g_assert_cmpint(t_len, >, 0); + g_assert_cmpint(buf_len - t_len, ==, s_len); + g_assert_cmpint(strlen(buf), ==, s_len); + g_assert(t_buf == &buf[s_len]); + g_assert(!memcmp(buf, STR_ORIG, s_len)); + g_assert(buf[s_len] == '\0'); + g_assert( + !memcmp(&buf[s_len + 1], &BUF_ORIG[s_len + 1], sizeof(buf) - s_len - 1)); + } + break; + } + } + } +} + +/*****************************************************************************/ + +static void +test_duplicate_decl_specifier(void) +{ + /* We're intentionally assigning values to static arrays v_const + * and v_result without using it afterwards just so that valgrind + * doesn't complain about the leak. */ + NM_PRAGMA_WARNING_DISABLE("-Wunused-but-set-variable") + + /* have some static variables, so that the result is certainly not optimized out. */ + static const int v_const[1] = {1}; + static int v_result[1] = {}; + const int v2 = 3; + + /* Test that we don't get a compiler warning about duplicate const specifier. + * C99 allows that and it can easily happen in macros. */ + +#define TEST_MAX(a, b) \ + ({ \ + const typeof(a) _a = (a); \ + const typeof(b) _b = (b); \ + \ + (_a > _b ? _a : _b); \ + }) + + v_result[0] = TEST_MAX(v_const[0], nmtst_get_rand_uint32() % 5) + v2; + + NM_PRAGMA_WARNING_REENABLE +} + +static void +test_reverse_dns_ip4(void) +{ + guint32 addr; + GPtrArray *domains = g_ptr_array_new_full(8, g_free); + + inet_pton(AF_INET, "7.2.3.0", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 27, domains); + g_assert_cmpuint(domains->len, ==, 32); + g_assert_cmpstr(domains->pdata[0], ==, "0.3.2.7.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[31], ==, "31.3.2.7.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "10.155.16.0", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 22, domains); + g_assert_cmpuint(domains->len, ==, 4); + g_assert_cmpstr(domains->pdata[0], ==, "16.155.10.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "17.155.10.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[2], ==, "18.155.10.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[3], ==, "19.155.10.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "4.5.6.7", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 32, domains); + g_assert_cmpuint(domains->len, ==, 1); + g_assert_cmpstr(domains->pdata[0], ==, "7.6.5.4.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "4.5.6.7", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 8, domains); + g_assert_cmpuint(domains->len, ==, 1); + g_assert_cmpstr(domains->pdata[0], ==, "4.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "4.180.6.7", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 9, domains); + g_assert_cmpuint(domains->len, ==, 128); + g_assert_cmpstr(domains->pdata[0], ==, "128.4.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "129.4.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[127], ==, "255.4.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "172.16.0.0", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 12, domains); + g_assert_cmpuint(domains->len, ==, 16); + g_assert_cmpstr(domains->pdata[0], ==, "16.172.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "17.172.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[14], ==, "30.172.in-addr.arpa"); + g_assert_cmpstr(domains->pdata[15], ==, "31.172.in-addr.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET, "1.2.3.4", &addr); + nm_utils_get_reverse_dns_domains_ip_4(addr, 0, domains); + g_assert_cmpuint(domains->len, ==, 0); + + g_ptr_array_unref(domains); +} + +static void +test_reverse_dns_ip6(void) +{ + struct in6_addr addr; + GPtrArray * domains = g_ptr_array_new_full(8, g_free); + + inet_pton(AF_INET6, "1234::56", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 16, domains); + g_assert_cmpuint(domains->len, ==, 1); + g_assert_cmpstr(domains->pdata[0], ==, "4.3.2.1.ip6.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET6, "1234::56", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 17, domains); + g_assert_cmpuint(domains->len, ==, 8); + g_assert_cmpstr(domains->pdata[0], ==, "0.4.3.2.1.ip6.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "1.4.3.2.1.ip6.arpa"); + g_assert_cmpstr(domains->pdata[7], ==, "7.4.3.2.1.ip6.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET6, "2001:db8::", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 29, domains); + g_assert_cmpuint(domains->len, ==, 8); + g_assert_cmpstr(domains->pdata[0], ==, "8.b.d.0.1.0.0.2.ip6.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "9.b.d.0.1.0.0.2.ip6.arpa"); + g_assert_cmpstr(domains->pdata[7], ==, "f.b.d.0.1.0.0.2.ip6.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET6, "0123:4567:89ab:cdef::", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 63, domains); + g_assert_cmpuint(domains->len, ==, 2); + g_assert_cmpstr(domains->pdata[0], ==, "e.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.ip6.arpa"); + g_assert_cmpstr(domains->pdata[1], ==, "f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.ip6.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET6, "fec0:1234:5678:9ab0::", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 61, domains); + g_assert_cmpuint(domains->len, ==, 8); + g_assert_cmpstr(domains->pdata[0], ==, "0.b.a.9.8.7.6.5.4.3.2.1.0.c.e.f.ip6.arpa"); + g_assert_cmpstr(domains->pdata[7], ==, "7.b.a.9.8.7.6.5.4.3.2.1.0.c.e.f.ip6.arpa"); + + g_ptr_array_set_size(domains, 0); + + inet_pton(AF_INET6, "0123:4567:89ab:cdee::", &addr); + nm_utils_get_reverse_dns_domains_ip_6(&addr, 0, domains); + g_assert_cmpuint(domains->len, ==, 0); + + g_ptr_array_unref(domains); +} + +/*****************************************************************************/ + +static void +do_test_stable_id_parse(const char * stable_id, + NMUtilsStableType expected_stable_type, + const char * expected_generated) +{ + gs_free char * generated = NULL; + NMUtilsStableType stable_type; + + if (expected_stable_type == NM_UTILS_STABLE_TYPE_GENERATED) + g_assert(expected_generated); + else + g_assert(!expected_generated); + + if (expected_stable_type == NM_UTILS_STABLE_TYPE_UUID) + g_assert(!stable_id); + else + g_assert(stable_id); + + stable_type = + nm_utils_stable_id_parse(stable_id, "_DEVICE", "_MAC", "_BOOT", "_CONNECTION", &generated); + + g_assert_cmpint(expected_stable_type, ==, stable_type); + + if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED) { + g_assert_cmpstr(expected_generated, ==, generated); + g_assert(generated); + } else + g_assert(!generated); +} + +static void +test_stable_id_parse(void) +{ +#define _parse_stable_id(stable_id) \ + do_test_stable_id_parse("" stable_id "", NM_UTILS_STABLE_TYPE_STABLE_ID, NULL) +#define _parse_generated(stable_id, expected_generated) \ + do_test_stable_id_parse("" stable_id "", \ + NM_UTILS_STABLE_TYPE_GENERATED, \ + "" expected_generated "") +#define _parse_random(stable_id) \ + do_test_stable_id_parse("" stable_id "", NM_UTILS_STABLE_TYPE_RANDOM, NULL) + do_test_stable_id_parse(NULL, NM_UTILS_STABLE_TYPE_UUID, NULL); + _parse_stable_id(""); + _parse_stable_id("a"); + _parse_stable_id("a$"); + _parse_stable_id("a$x"); + _parse_stable_id(" ${a$x"); + _parse_stable_id("${"); + _parse_stable_id("${="); + _parse_stable_id("${a"); + _parse_stable_id("${a$x"); + _parse_stable_id("a$$"); + _parse_stable_id("a$$x"); + _parse_stable_id("a$${CONNECTION}"); + _parse_stable_id("a$${CONNECTION}x"); + _parse_generated("${CONNECTION}", "${CONNECTION}=11{_CONNECTION}"); + _parse_generated("${${CONNECTION}", "${${CONNECTION}=11{_CONNECTION}"); + _parse_generated("${CONNECTION}x", "${CONNECTION}=11{_CONNECTION}x"); + _parse_generated("x${CONNECTION}", "x${CONNECTION}=11{_CONNECTION}"); + _parse_generated("${BOOT}x", "${BOOT}=5{_BOOT}x"); + _parse_generated("x${BOOT}", "x${BOOT}=5{_BOOT}"); + _parse_generated("x${BOOT}${CONNECTION}", "x${BOOT}=5{_BOOT}${CONNECTION}=11{_CONNECTION}"); + _parse_generated("xX${BOOT}yY${CONNECTION}zZ", + "xX${BOOT}=5{_BOOT}yY${CONNECTION}=11{_CONNECTION}zZ"); + _parse_generated("${MAC}x", "${MAC}=4{_MAC}x"); + _parse_random("${RANDOM}"); + _parse_random(" ${RANDOM}"); + _parse_random("${BOOT}${RANDOM}"); +} + +/*****************************************************************************/ + +static void +test_stable_id_generated_complete(void) +{ +#define ASSERT(str, expected) \ + G_STMT_START \ + { \ + gs_free char *_s = NULL; \ + \ + _s = nm_utils_stable_id_generated_complete((str)); \ + g_assert_cmpstr((expected), ==, _s); \ + } \ + G_STMT_END + + ASSERT("", "2jmj7l5rSw0yVb/vlWAYkK/YBwk"); + ASSERT("a", "hvfkN/qlp/zhXR3cuerq6jd2Z7g"); + ASSERT("password", "W6ph5Mm5Pz8GgiULbPgzG37mj9g"); +#undef ASSERT +} + +/*****************************************************************************/ + +static void +test_nm_utils_exp10(void) +{ +#define FLOAT_CMP(a, b) \ + G_STMT_START \ + { \ + double _a = (a); \ + double _b = (b); \ + \ + if (isinf(_b)) \ + g_assert(isinf(_a)); \ + else if (_b >= 0.0 && _b <= 0.0) \ + g_assert(_a - _b < G_MINFLOAT); \ + else { \ + double _x = (_a) - (_b); \ + g_assert(_b > 0.0); \ + if (_x < 0.0) \ + _x = -_x; \ + g_assert(_x / _b < 1E-10); \ + } \ + } \ + G_STMT_END + + FLOAT_CMP(nm_utils_exp10(G_MININT16), 0.0); + FLOAT_CMP(nm_utils_exp10(-310), 0.0); + FLOAT_CMP(nm_utils_exp10(-309), 0.0); + FLOAT_CMP(nm_utils_exp10(-308), 1e-308); + FLOAT_CMP(nm_utils_exp10(-307), 1e-307); + FLOAT_CMP(nm_utils_exp10(-1), 1e-1); + FLOAT_CMP(nm_utils_exp10(-2), 1e-2); + FLOAT_CMP(nm_utils_exp10(0), 1e0); + FLOAT_CMP(nm_utils_exp10(1), 1e1); + FLOAT_CMP(nm_utils_exp10(2), 1e2); + FLOAT_CMP(nm_utils_exp10(3), 1e3); + FLOAT_CMP(nm_utils_exp10(4), 1e4); + FLOAT_CMP(nm_utils_exp10(5), 1e5); + FLOAT_CMP(nm_utils_exp10(6), 1e6); + FLOAT_CMP(nm_utils_exp10(7), 1e7); + FLOAT_CMP(nm_utils_exp10(122), 1e122); + FLOAT_CMP(nm_utils_exp10(200), 1e200); + FLOAT_CMP(nm_utils_exp10(307), 1e307); + FLOAT_CMP(nm_utils_exp10(308), 1e308); + FLOAT_CMP(nm_utils_exp10(309), INFINITY); + FLOAT_CMP(nm_utils_exp10(310), INFINITY); + FLOAT_CMP(nm_utils_exp10(G_MAXINT16), INFINITY); +} + +/*****************************************************************************/ + +static void +test_utils_file_is_in_path(void) +{ + g_assert(!nm_utils_file_is_in_path("/", "/")); + g_assert(!nm_utils_file_is_in_path("//", "/")); + g_assert(!nm_utils_file_is_in_path("/a/", "/")); + g_assert(nm_utils_file_is_in_path("/a", "/")); + g_assert(nm_utils_file_is_in_path("///a", "/")); + g_assert(nm_utils_file_is_in_path("//b/a", "/b//")); + g_assert(nm_utils_file_is_in_path("//b///a", "/b//")); + g_assert(!nm_utils_file_is_in_path("//b///a/", "/b//")); + g_assert(!nm_utils_file_is_in_path("//b///a/", "/b/a/")); + g_assert(!nm_utils_file_is_in_path("//b///a", "/b/a/")); + g_assert(nm_utils_file_is_in_path("//b///a/.", "/b/a/")); + g_assert(nm_utils_file_is_in_path("//b///a/..", "/b/a/")); +} + +/*****************************************************************************/ + +#define _TEST_RC(searches, nameservers, options, expected) \ + G_STMT_START \ + { \ + const char *const *const _searches = (searches); \ + const char *const *const _nameservers = (nameservers); \ + const char *const *const _options = (options); \ + gs_free char * _content = NULL; \ + \ + _content = nmtst_dns_create_resolv_conf(_searches, _nameservers, _options); \ + g_assert_cmpstr(_content, ==, expected); \ + } \ + G_STMT_END + +static void +test_dns_create_resolv_conf(void) +{ + _TEST_RC(NM_MAKE_STRV("a"), + NULL, + NULL, + "# Generated by NetworkManager\n" + "search a\n" + ""); + + _TEST_RC(NM_MAKE_STRV("a", "b.com"), + NM_MAKE_STRV("192.168.55.1", "192.168.56.1"), + NM_MAKE_STRV("opt1", "opt2"), + "# Generated by NetworkManager\n" + "search a b.com\n" + "nameserver 192.168.55.1\n" + "nameserver 192.168.56.1\n" + "options opt1 opt2\n" + ""); + + _TEST_RC(NM_MAKE_STRV("a2x456789.b2x456789.c2x456789.d2x456789.e2x456789.f2x456789.g2x456789." + "h2x456789.i2x456789.j2x4567890", + "a2y456789.b2y456789.c2y456789.d2y456789.e2y456789.f2y456789.g2y456789." + "h2y456789.i2y456789.j2y4567890", + "a2z456789.b2z456789.c2z456789.d2z456789.e2z456789.f2z456789.g2z456789." + "h2z456789.i2z456789.j2z4567890"), + NULL, + NULL, + "# Generated by NetworkManager\n" + "search " + "a2x456789.b2x456789.c2x456789.d2x456789.e2x456789.f2x456789.g2x456789.h2x456789." + "i2x456789.j2x4567890 " + "a2y456789.b2y456789.c2y456789.d2y456789.e2y456789.f2y456789.g2y456789.h2y456789." + "i2y456789.j2y4567890 " + "a2z456789.b2z456789.c2z456789.d2z456789.e2z456789.f2z456789.g2z456789.h2z456789." + "i2z456789.j2z4567890\n" + ""); +} + +/*****************************************************************************/ + +static void +test_machine_id_read(void) +{ + NMUuid machine_id_sd; + const NMUuid *machine_id; + char machine_id_str[33]; + gpointer logstate; + + logstate = nmtst_logging_disable(FALSE); + /* If you run this test as root, without a valid /etc/machine-id, + * the code will try to get the secret-key. That is a bit ugly, + * but no real problem. */ + machine_id = nm_utils_machine_id_bin(); + nmtst_logging_reenable(logstate); + + g_assert(machine_id); + g_assert(nm_utils_bin2hexstr_full(machine_id, sizeof(NMUuid), '\0', FALSE, machine_id_str) + == machine_id_str); + g_assert(strlen(machine_id_str) == 32); + g_assert_cmpstr(machine_id_str, ==, nm_utils_machine_id_str()); + + /* double check with systemd's implementation... */ + if (!nm_sd_utils_id128_get_machine(&machine_id_sd)) { + /* if systemd failed to read /etc/machine-id, the file likely + * is invalid. Our machine-id is fake, and we have nothing to + * compare against. */ + + /* NOTE: this test will fail, if you don't have /etc/machine-id, + * but a valid "LOCALSTATEDIR/lib/dbus/machine-id" file. + * Just don't do that. */ + g_assert(nm_utils_machine_id_is_fake()); + } else { + g_assert(!nm_utils_machine_id_is_fake()); + g_assert_cmpmem(&machine_id_sd, sizeof(NMUuid), machine_id, 16); + } +} + +/*****************************************************************************/ + +static void +test_nm_utils_dhcp_client_id_systemd_node_specific(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const guint8 HASH_KEY[16] = {0x80, + 0x11, + 0x8c, + 0xc2, + 0xfe, + 0x4a, + 0x03, + 0xee, + 0x3e, + 0xd6, + 0x0c, + 0x6f, + 0x36, + 0x39, + 0x14, + 0x09}; + const guint16 duid_type_en = htons(2); + const guint32 systemd_pen = htonl(43793); + const struct { + NMUuid machine_id; + const char *ifname; + guint64 ifname_hash_1; + guint32 iaid_ifname; + guint64 duid_id; + } d_array[] = { + [0] = + { + .machine_id.uuid = {0xcb, + 0xc2, + 0x2e, + 0x47, + 0x41, + 0x8e, + 0x40, + 0x2a, + 0xa7, + 0xb3, + 0x0d, + 0xea, + 0x92, + 0x83, + 0x94, + 0xef}, + .ifname = "lo", + .ifname_hash_1 = 0x7297085c2b12c911llu, + .iaid_ifname = htobe32(0x5985c14du), + .duid_id = htobe64(0x3d769bb2c14d29e1u), + }, + [1] = + { + .machine_id.uuid = {0x11, + 0x4e, + 0xb4, + 0xda, + 0xd3, + 0x22, + 0x4a, + 0xff, + 0x9f, + 0xc3, + 0x30, + 0x83, + 0x38, + 0xa0, + 0xeb, + 0xb7}, + .ifname = "eth0", + .ifname_hash_1 = 0x9e1cb083b54cd7b6llu, + .iaid_ifname = htobe32(0x2b506735u), + .duid_id = htobe64(0x551572e0f2a2a10fu), + }, + }; + int i; + typeof(d_array[0]) *d = &d_array[TEST_IDX]; + gint64 u64; + gint32 u32; + + /* the test already hard-codes the expected values iaid_ifname and duid_id + * above. Still, redo the steps to derive them from the ifname/machine-id + * and double check. */ + u64 = c_siphash_hash(HASH_KEY, (const guint8 *) d->ifname, strlen(d->ifname)); + g_assert_cmpint(u64, ==, d->ifname_hash_1); + u32 = be32toh((u64 & 0xffffffffu) ^ (u64 >> 32)); + g_assert_cmpint(u32, ==, d->iaid_ifname); + + u64 = htole64(c_siphash_hash(HASH_KEY, (const guint8 *) &d->machine_id, sizeof(d->machine_id))); + g_assert_cmpint(u64, ==, d->duid_id); + + for (i = 0; i < 2; i++) { + const gboolean legacy_unstable_byteorder = (i != 0); + gs_unref_bytes GBytes *client_id = NULL; + const guint8 * cid; + guint32 iaid = d->iaid_ifname; + guint32 tmp; + + tmp = nm_utils_create_dhcp_iaid(legacy_unstable_byteorder, + (const guint8 *) d->ifname, + strlen(d->ifname)); + client_id = + nm_utils_dhcp_client_id_systemd_node_specific_full(tmp, + (const guint8 *) &d->machine_id, + sizeof(d->machine_id)); + + g_assert(client_id); + g_assert_cmpint(g_bytes_get_size(client_id), ==, 19); + cid = g_bytes_get_data(client_id, NULL); + g_assert_cmpint(cid[0], ==, 255); +#if __BYTE_ORDER == __BIG_ENDIAN + if (legacy_unstable_byteorder) { + /* on non-little endian, the legacy behavior is to have the bytes + * swapped. */ + iaid = bswap_32(iaid); + } +#endif + g_assert_cmpmem(&cid[1], 4, &iaid, sizeof(iaid)); + g_assert_cmpmem(&cid[5], 2, &duid_type_en, sizeof(duid_type_en)); + g_assert_cmpmem(&cid[7], 4, &systemd_pen, sizeof(systemd_pen)); + g_assert_cmpmem(&cid[11], 8, &d->duid_id, sizeof(d->duid_id)); + + g_assert_cmpint(iaid, + ==, + htonl(nm_utils_create_dhcp_iaid(legacy_unstable_byteorder, + (const guint8 *) d->ifname, + strlen(d->ifname)))); + } +} + +/*****************************************************************************/ + +static void +_kernel_cmdline_match(gboolean expected_match, + const char *const *proc_cmdline, + const char *const *patterns) +{ + gs_free_error GError *error = NULL; + GError ** p_error = nmtst_get_rand_bool() ? &error : NULL; + gboolean match; + + nm_assert(proc_cmdline); + nm_assert(patterns); + + match = nm_utils_kernel_cmdline_match_check(proc_cmdline, + patterns, + NM_PTRARRAY_LEN(patterns), + p_error); + if (expected_match) + nmtst_assert_success(match, error); + else { + g_assert(!p_error || error); + g_assert(!match); + } +} + +static void +test_kernel_cmdline_match_check(void) +{ + _kernel_cmdline_match(TRUE, NM_MAKE_STRV(""), NM_MAKE_STRV("")); + _kernel_cmdline_match(FALSE, NM_MAKE_STRV(""), NM_MAKE_STRV("a")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a"), NM_MAKE_STRV("a")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a=b"), NM_MAKE_STRV("a")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a=b", "b"), NM_MAKE_STRV("a", "b")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a=b", "b"), NM_MAKE_STRV("&a", "&b")); + _kernel_cmdline_match(FALSE, NM_MAKE_STRV("a=b", "bc"), NM_MAKE_STRV("&a", "&b")); + _kernel_cmdline_match(FALSE, NM_MAKE_STRV("a=b", "b"), NM_MAKE_STRV("&a", "&b", "c")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a=b", "b"), NM_MAKE_STRV("&a", "&b", "b", "c")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a=b", "b", "c=dd"), NM_MAKE_STRV("&a", "&b", "c")); + _kernel_cmdline_match(FALSE, NM_MAKE_STRV("a", "b"), NM_MAKE_STRV("a", "&c")); + _kernel_cmdline_match(TRUE, NM_MAKE_STRV("a", "b"), NM_MAKE_STRV("a", "|\\c")); +} + +/*****************************************************************************/ + +static void +test_connectivity_state_cmp(void) +{ + NMConnectivityState a; + +#define _cmp(a, b, cmp) \ + G_STMT_START \ + { \ + const NMConnectivityState _a = (a); \ + const NMConnectivityState _b = (b); \ + const int _cmp = (cmp); \ + \ + g_assert(NM_IN_SET(_cmp, -1, 0, 1)); \ + g_assert_cmpint(nm_connectivity_state_cmp(_a, _b), ==, _cmp); \ + g_assert_cmpint(nm_connectivity_state_cmp(_b, _a), ==, -_cmp); \ + } \ + G_STMT_END + + for (a = NM_CONNECTIVITY_UNKNOWN; a <= NM_CONNECTIVITY_FULL; a++) + _cmp(a, a, 0); + + _cmp(NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_UNKNOWN, 0); + _cmp(NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_NONE, -1); + _cmp(NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_LIMITED, -1); + _cmp(NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_PORTAL, -1); + _cmp(NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, -1); + + _cmp(NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_UNKNOWN, 1); + _cmp(NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_NONE, 0); + _cmp(NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_LIMITED, -1); + _cmp(NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_PORTAL, -1); + _cmp(NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_FULL, -1); + + _cmp(NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_UNKNOWN, 1); + _cmp(NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_NONE, 1); + _cmp(NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_LIMITED, 0); + _cmp(NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_PORTAL, -1); + _cmp(NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_FULL, -1); + + _cmp(NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_UNKNOWN, 1); + _cmp(NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_NONE, 1); + _cmp(NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_LIMITED, 1); + _cmp(NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_PORTAL, 0); + _cmp(NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_FULL, -1); + + _cmp(NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN, 1); + _cmp(NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_NONE, 1); + _cmp(NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_LIMITED, 1); + _cmp(NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_PORTAL, 1); + _cmp(NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_FULL, 0); + +#undef _cmp +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "ALL"); + + g_test_add_func("/general/test_logging_domains", test_logging_domains); + g_test_add_func("/general/test_logging_error", test_logging_error); + + g_test_add_func("/general/nm_utils_strbuf_append", test_nm_utils_strbuf_append); + + g_test_add_func("/general/nm_utils_ip6_address_clear_host_address", + test_nm_utils_ip6_address_clear_host_address); + g_test_add_func("/general/nm_utils_ip6_address_same_prefix", + test_nm_utils_ip6_address_same_prefix); + g_test_add_func("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff); + + g_test_add_func("/general/nm_utils_sysctl_ip_conf_path", test_nm_utils_sysctl_ip_conf_path); + + g_test_add_func("/general/exp10", test_nm_utils_exp10); + + g_test_add_func("/general/connection-match/basic", test_connection_match_basic); + g_test_add_func("/general/connection-match/ip6-method", test_connection_match_ip6_method); + g_test_add_func("/general/connection-match/ip6-method-ignore", + test_connection_match_ip6_method_ignore); + g_test_add_func("/general/connection-match/ip6-method-ignore-auto", + test_connection_match_ip6_method_ignore_auto); + g_test_add_func("/general/connection-match/ip4-method", test_connection_match_ip4_method); + g_test_add_func("/general/connection-match/con-interface-name", + test_connection_match_interface_name); + g_test_add_func("/general/connection-match/wired", test_connection_match_wired); + g_test_add_func("/general/connection-match/wired2", test_connection_match_wired2); + g_test_add_func("/general/connection-match/cloned_mac", test_connection_match_cloned_mac); + g_test_add_func("/general/connection-match/no-match-ip4-addr", + test_connection_no_match_ip4_addr); + g_test_add_func("/general/connection-match/no-match-vlan", test_connection_no_match_vlan); + g_test_add_func("/general/connection-match/routes/ip4/1", test_connection_match_ip4_routes1); + g_test_add_func("/general/connection-match/routes/ip4/2", test_connection_match_ip4_routes2); + g_test_add_func("/general/connection-match/routes/ip6", test_connection_match_ip6_routes); + + g_test_add_func("/general/wildcard-match", test_wildcard_match); + + g_test_add_func("/general/connection-sort/autoconnect-priority", + test_connection_sort_autoconnect_priority); + + g_test_add_func("/general/match-spec/device", test_match_spec_device); + g_test_add_func("/general/match-spec/config", test_match_spec_config); + g_test_add_func("/general/duplicate_decl_specifier", test_duplicate_decl_specifier); + + g_test_add_func("/general/reverse_dns/ip4", test_reverse_dns_ip4); + g_test_add_func("/general/reverse_dns/ip6", test_reverse_dns_ip6); + + g_test_add_func("/general/stable-id/parse", test_stable_id_parse); + g_test_add_func("/general/stable-id/generated-complete", test_stable_id_generated_complete); + + g_test_add_func("/general/machine-id/read", test_machine_id_read); + + g_test_add_func("/general/test_utils_file_is_in_path", test_utils_file_is_in_path); + + g_test_add_func("/general/test_dns_create_resolv_conf", test_dns_create_resolv_conf); + + g_test_add_data_func("/general/nm_utils_dhcp_client_id_systemd_node_specific/0", + GINT_TO_POINTER(0), + test_nm_utils_dhcp_client_id_systemd_node_specific); + g_test_add_data_func("/general/nm_utils_dhcp_client_id_systemd_node_specific/1", + GINT_TO_POINTER(1), + test_nm_utils_dhcp_client_id_systemd_node_specific); + + g_test_add_func("/core/general/test_connectivity_state_cmp", test_connectivity_state_cmp); + g_test_add_func("/core/general/test_kernel_cmdline_match_check", + test_kernel_cmdline_match_check); + + return g_test_run(); +} diff --git a/src/core/tests/test-dcb.c b/src/core/tests/test-dcb.c new file mode 100644 index 0000000..9ee1fda --- /dev/null +++ b/src/core/tests/test-dcb.c @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dcb.h" + +#include "nm-test-utils-core.h" + +typedef struct { + guint num; + const char *cmds[]; +} DcbExpected; + +static gboolean +test_dcb_func(char **argv, guint which, gpointer user_data, GError **error) +{ + DcbExpected *e = user_data; + char * f; + + g_assert(argv[0] == NULL); + argv[0] = (which == DCBTOOL) ? "dcbtool" : "fcoeadm"; + + f = g_strjoinv(" ", argv); + if (e->cmds[e->num] == NULL) + g_assert_cmpstr(f, ==, NULL); + g_assert_cmpstr(e->cmds[e->num], !=, NULL); + g_assert_cmpstr(f, ==, e->cmds[e->num++]); + g_free(f); + return TRUE; +} + +#define DCB_FLAGS_ALL \ + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + +static void +test_dcb_fcoe(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:1 a:1 w:1", + "dcbtool sc eth0 app:fcoe appcfg:40", + "dcbtool sc eth0 app:iscsi e:0 a:0 w:0", + "dcbtool sc eth0 app:fip e:0 a:0 w:0", + "dcbtool sc eth0 pfc e:0 a:0 w:0", + "dcbtool sc eth0 pg e:0", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FCOE_FLAGS, + DCB_FLAGS_ALL, + NM_SETTING_DCB_APP_FCOE_PRIORITY, + 6, + NULL); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_iscsi(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:0 a:0 w:0", + "dcbtool sc eth0 app:iscsi e:1 a:0 w:1", + "dcbtool sc eth0 app:iscsi appcfg:08", + "dcbtool sc eth0 app:fip e:0 a:0 w:0", + "dcbtool sc eth0 pfc e:0 a:0 w:0", + "dcbtool sc eth0 pg e:0", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_ISCSI_FLAGS, + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_WILLING), + NM_SETTING_DCB_APP_ISCSI_PRIORITY, + 3, + NULL); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_fip(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:0 a:0 w:0", + "dcbtool sc eth0 app:iscsi e:0 a:0 w:0", + "dcbtool sc eth0 app:fip e:1 a:1 w:0", + "dcbtool sc eth0 app:fip appcfg:01", + "dcbtool sc eth0 pfc e:0 a:0 w:0", + "dcbtool sc eth0 pg e:0", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FIP_FLAGS, + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE), + NM_SETTING_DCB_APP_FIP_PRIORITY, + 0, + NULL); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_fip_default_prio(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:0 a:0 w:0", + "dcbtool sc eth0 app:iscsi e:0 a:0 w:0", + "dcbtool sc eth0 app:fip e:1 a:1 w:0", + "dcbtool sc eth0 pfc e:0 a:0 w:0", + "dcbtool sc eth0 pg e:0", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FIP_FLAGS, + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE), + NM_SETTING_DCB_APP_FIP_PRIORITY, + -1, + NULL); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_pfc(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:0 a:0 w:0", + "dcbtool sc eth0 app:iscsi e:0 a:0 w:0", + "dcbtool sc eth0 app:fip e:0 a:0 w:0", + "dcbtool sc eth0 pfc e:1 a:1 w:1", + "dcbtool sc eth0 pfc pfcup:01101100", + "dcbtool sc eth0 pg e:0", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, DCB_FLAGS_ALL, NULL); + + nm_setting_dcb_set_priority_flow_control(s_dcb, 0, FALSE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 1, TRUE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 2, TRUE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 3, FALSE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 4, TRUE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 5, TRUE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 6, FALSE); + nm_setting_dcb_set_priority_flow_control(s_dcb, 7, FALSE); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_priority_groups(void) +{ + static DcbExpected expected = { + 0, + {"dcbtool sc eth0 app:fcoe e:0 a:0 w:0", + "dcbtool sc eth0 app:iscsi e:0 a:0 w:0", + "dcbtool sc eth0 app:fip e:0 a:0 w:0", + "dcbtool sc eth0 pfc e:0 a:0 w:0", + "dcbtool sc eth0 pg e:1 a:1 w:1" + " pgid:765f3210" + " pgpct:10,40,5,10,5,20,7,3" + " uppct:100,50,33,25,20,16,14,12" + " strict:01010101" + " up2tc:01201201", + NULL}, + }; + NMSettingDcb *s_dcb; + GError * error = NULL; + gboolean success; + guint i; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, DCB_FLAGS_ALL, NULL); + + for (i = 0; i < 8; i++) { + /* Make sure at least one 15/f is present in the group IDs */ + nm_setting_dcb_set_priority_group_id(s_dcb, i, (i == 3) ? 15 : 7 - i); + nm_setting_dcb_set_priority_bandwidth(s_dcb, i, 100 / (i + 1)); + nm_setting_dcb_set_priority_strict_bandwidth(s_dcb, i, i % 2); + nm_setting_dcb_set_priority_traffic_class(s_dcb, i, i % 3); + } + + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 0, 10); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 1, 40); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 2, 5); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 3, 10); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 4, 5); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 5, 20); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 6, 7); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 7, 3); + + success = _dcb_setup("eth0", s_dcb, test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); + g_object_unref(s_dcb); +} + +static void +test_dcb_cleanup(void) +{ + static DcbExpected expected = { + 0, + {"fcoeadm -d eth0", + "dcbtool sc eth0 app:fcoe e:0", + "dcbtool sc eth0 app:iscsi e:0", + "dcbtool sc eth0 app:fip e:0", + "dcbtool sc eth0 pfc e:0", + "dcbtool sc eth0 pg e:0", + "dcbtool sc eth0 dcb off", + NULL}, + }; + GError * error = NULL; + gboolean success; + + success = _fcoe_cleanup("eth0", test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + success = _dcb_cleanup("eth0", test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(expected.cmds[expected.num], ==, NULL); +} + +static void +test_fcoe_create(void) +{ + static DcbExpected expected1 = { + 0, + {"fcoeadm -m fabric -c eth0", NULL}, + }; + static DcbExpected expected2 = { + 0, + {"fcoeadm -m vn2vn -c eth0", NULL}, + }; + GError * error = NULL; + gboolean success; + NMSettingDcb *s_dcb; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_FCOE_FLAGS, DCB_FLAGS_ALL, NULL); + + /* Default mode is fabric */ + success = _fcoe_setup("eth0", s_dcb, test_dcb_func, &expected1, &error); + g_assert_no_error(error); + g_assert(success); + + /* Test VN2VN */ + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FCOE_MODE, + NM_SETTING_DCB_FCOE_MODE_VN2VN, + NULL); + success = _fcoe_setup("eth0", s_dcb, test_dcb_func, &expected2, &error); + g_assert_no_error(error); + g_assert(success); + + g_object_unref(s_dcb); +} + +static void +test_fcoe_cleanup(void) +{ + static DcbExpected expected = { + 0, + {"fcoeadm -d eth0", NULL}, + }; + GError * error = NULL; + gboolean success; + + success = _fcoe_cleanup("eth0", test_dcb_func, &expected, &error); + g_assert_no_error(error); + g_assert(success); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/dcb/fcoe", test_dcb_fcoe); + g_test_add_func("/dcb/iscsi", test_dcb_iscsi); + g_test_add_func("/dcb/fip", test_dcb_fip); + g_test_add_func("/dcb/fip-default-priority", test_dcb_fip_default_prio); + g_test_add_func("/dcb/pfc", test_dcb_pfc); + g_test_add_func("/dcb/priority-groups", test_dcb_priority_groups); + g_test_add_func("/dcb/cleanup", test_dcb_cleanup); + g_test_add_func("/fcoe/create", test_fcoe_create); + g_test_add_func("/fcoe/cleanup", test_fcoe_cleanup); + + return g_test_run(); +} diff --git a/src/core/tests/test-ip4-config.c b/src/core/tests/test-ip4-config.c new file mode 100644 index 0000000..008201a --- /dev/null +++ b/src/core/tests/test-ip4-config.c @@ -0,0 +1,380 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2014 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include + +#include "nm-ip4-config.h" +#include "platform/nm-platform.h" + +#include "nm-test-utils-core.h" + +static NMIP4Config * +build_test_config(void) +{ + NMIP4Config * config; + NMPlatformIP4Address addr; + NMPlatformIP4Route route; + + /* Build up the config to subtract */ + config = nmtst_ip4_config_new(1); + + nm_assert(NM_IP_CONFIG_CAST(config)); + + addr = *nmtst_platform_ip4_address("192.168.1.10", "1.2.3.4", 24); + nm_ip4_config_add_address(config, &addr); + + route = *nmtst_platform_ip4_route("10.0.0.0", 8, "192.168.1.1"); + nm_ip4_config_add_route(config, &route, NULL); + + route = *nmtst_platform_ip4_route("172.16.0.0", 16, "192.168.1.1"); + nm_ip4_config_add_route(config, &route, NULL); + + { + const NMPlatformIP4Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_DHCP, + .gateway = nmtst_inet4_from_string("192.168.1.1"), + .table_coerced = 0, + .metric = 100, + }; + + nm_ip4_config_add_route(config, &r, NULL); + } + + nm_ip4_config_add_nameserver(config, nmtst_inet4_from_string("4.2.2.1")); + nm_ip4_config_add_nameserver(config, nmtst_inet4_from_string("4.2.2.2")); + nm_ip4_config_add_domain(config, "foobar.com"); + nm_ip4_config_add_domain(config, "baz.com"); + nm_ip4_config_add_search(config, "blahblah.com"); + nm_ip4_config_add_search(config, "beatbox.com"); + + nm_ip4_config_add_nis_server(config, nmtst_inet4_from_string("1.2.3.9")); + nm_ip4_config_add_nis_server(config, nmtst_inet4_from_string("1.2.3.10")); + + nm_ip4_config_add_wins(config, nmtst_inet4_from_string("4.2.3.9")); + nm_ip4_config_add_wins(config, nmtst_inet4_from_string("4.2.3.10")); + + return config; +} + +static void +test_replace(void) +{ + gs_unref_object NMIP4Config *config1 = NULL; + gs_unref_object NMIP4Config *config2 = NULL; + NMPlatformIP4Address addr; + gboolean relevant_changes; + + config1 = nmtst_ip4_config_new(1); + + addr = *nmtst_platform_ip4_address("172.16.0.1", NULL, 24); + addr.timestamp = 10; + addr.preferred = 3600; + addr.lifetime = 7200; + nm_ip4_config_add_address(config1, &addr); + + addr = *nmtst_platform_ip4_address("172.16.0.2", NULL, 24); + addr.timestamp = 10; + addr.preferred = 3600; + addr.lifetime = 7200; + nm_ip4_config_add_address(config1, &addr); + + config2 = nmtst_ip4_config_new(1); + + addr = *nmtst_platform_ip4_address("192.168.1.1", NULL, 24); + addr.timestamp = 40; + addr.preferred = 60; + addr.lifetime = 120; + nm_ip4_config_add_address(config2, &addr); + + addr = *nmtst_platform_ip4_address("172.16.0.2", NULL, 24); + addr.timestamp = 40; + addr.preferred = 60; + addr.lifetime = 120; + nm_ip4_config_add_address(config2, &addr); + + g_assert(nm_ip4_config_replace(config2, config1, &relevant_changes)); + g_assert(relevant_changes); + g_assert(nm_ip4_config_equal(config1, config2)); +} + +static void +test_subtract(void) +{ + NMIP4Config * src, *dst; + NMPlatformIP4Address addr; + NMPlatformIP4Route route; + const NMPlatformIP4Address *test_addr; + const NMPlatformIP4Route * test_route; + const char * expected_addr = "192.168.1.12"; + guint32 expected_addr_plen = 24; + const char * expected_route_dest = "8.0.0.0"; + guint32 expected_route_plen = 8; + const char * expected_route_next_hop = "192.168.1.1"; + guint32 expected_ns1 = nmtst_inet4_from_string("8.8.8.8"); + guint32 expected_ns2 = nmtst_inet4_from_string("8.8.8.9"); + const char * expected_domain = "wonderfalls.com"; + const char * expected_search = "somewhere.com"; + guint32 expected_nis = nmtst_inet4_from_string("1.2.3.13"); + guint32 expected_wins = nmtst_inet4_from_string("2.3.4.5"); + guint32 expected_mtu = 1492; + + src = build_test_config(); + + /* add a couple more things to the test config */ + dst = build_test_config(); + addr = *nmtst_platform_ip4_address(expected_addr, NULL, expected_addr_plen); + nm_ip4_config_add_address(dst, &addr); + + route = *nmtst_platform_ip4_route(expected_route_dest, + expected_route_plen, + expected_route_next_hop); + nm_ip4_config_add_route(dst, &route, NULL); + + nm_ip4_config_add_nameserver(dst, expected_ns1); + nm_ip4_config_add_nameserver(dst, expected_ns2); + nm_ip4_config_add_domain(dst, expected_domain); + nm_ip4_config_add_search(dst, expected_search); + + nm_ip4_config_add_nis_server(dst, expected_nis); + nm_ip4_config_add_wins(dst, expected_wins); + + nm_ip4_config_set_mtu(dst, expected_mtu, NM_IP_CONFIG_SOURCE_UNKNOWN); + + nm_ip4_config_subtract(dst, src, 0); + + /* ensure what's left is what we expect */ + g_assert_cmpuint(nm_ip4_config_get_num_addresses(dst), ==, 1); + test_addr = _nmtst_ip4_config_get_address(dst, 0); + g_assert(test_addr != NULL); + g_assert_cmpuint(test_addr->address, ==, nmtst_inet4_from_string(expected_addr)); + g_assert_cmpuint(test_addr->peer_address, ==, test_addr->address); + g_assert_cmpuint(test_addr->plen, ==, expected_addr_plen); + + g_assert(!nm_ip4_config_best_default_route_get(dst)); + g_assert_cmpuint(nmtst_ip4_config_get_gateway(dst), ==, 0); + + g_assert_cmpuint(nm_ip4_config_get_num_routes(dst), ==, 1); + test_route = _nmtst_ip4_config_get_route(dst, 0); + g_assert(test_route != NULL); + g_assert_cmpuint(test_route->network, ==, nmtst_inet4_from_string(expected_route_dest)); + g_assert_cmpuint(test_route->plen, ==, expected_route_plen); + g_assert_cmpuint(test_route->gateway, ==, nmtst_inet4_from_string(expected_route_next_hop)); + + g_assert_cmpuint(nm_ip4_config_get_num_nameservers(dst), ==, 2); + g_assert_cmpuint(nm_ip4_config_get_nameserver(dst, 0), ==, expected_ns1); + g_assert_cmpuint(nm_ip4_config_get_nameserver(dst, 1), ==, expected_ns2); + + g_assert_cmpuint(nm_ip4_config_get_num_domains(dst), ==, 1); + g_assert_cmpstr(nm_ip4_config_get_domain(dst, 0), ==, expected_domain); + g_assert_cmpuint(nm_ip4_config_get_num_searches(dst), ==, 1); + g_assert_cmpstr(nm_ip4_config_get_search(dst, 0), ==, expected_search); + + g_assert_cmpuint(nm_ip4_config_get_num_nis_servers(dst), ==, 1); + g_assert_cmpuint(nm_ip4_config_get_nis_server(dst, 0), ==, expected_nis); + + g_assert_cmpuint(nm_ip4_config_get_num_wins(dst), ==, 1); + g_assert_cmpuint(nm_ip4_config_get_wins(dst, 0), ==, expected_wins); + + g_assert_cmpuint(nm_ip4_config_get_mtu(dst), ==, expected_mtu); + + g_object_unref(src); + g_object_unref(dst); +} + +static void +test_compare_with_source(void) +{ + NMIP4Config * a, *b; + NMPlatformIP4Address addr; + NMPlatformIP4Route route; + + a = nmtst_ip4_config_new(1); + b = nmtst_ip4_config_new(2); + + /* Address */ + addr = *nmtst_platform_ip4_address("1.2.3.4", NULL, 24); + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip4_config_add_address(a, &addr); + + addr.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip4_config_add_address(b, &addr); + + /* Route */ + route = *nmtst_platform_ip4_route("10.0.0.0", 8, "192.168.1.1"); + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip4_config_add_route(a, &route, NULL); + + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip4_config_add_route(b, &route, NULL); + + /* Assert that the configs are basically the same, eg that the source is ignored */ + g_assert(nm_ip4_config_equal(a, b)); + + g_object_unref(a); + g_object_unref(b); +} + +static void +test_add_address_with_source(void) +{ + NMIP4Config * a; + NMPlatformIP4Address addr; + const NMPlatformIP4Address *test_addr; + + a = nmtst_ip4_config_new(1); + + /* Test that a higher priority source is not overwritten */ + addr = *nmtst_platform_ip4_address("1.2.3.4", NULL, 24); + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip4_config_add_address(a, &addr); + + test_addr = _nmtst_ip4_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + addr.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip4_config_add_address(a, &addr); + + test_addr = _nmtst_ip4_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + /* Test that a lower priority address source is overwritten */ + _nmtst_ip4_config_del_address(a, 0); + addr.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + nm_ip4_config_add_address(a, &addr); + + test_addr = _nmtst_ip4_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_KERNEL); + + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip4_config_add_address(a, &addr); + + test_addr = _nmtst_ip4_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + g_object_unref(a); +} + +static void +test_add_route_with_source(void) +{ + gs_unref_object NMIP4Config *a = NULL; + NMPlatformIP4Route route; + const NMPlatformIP4Route * test_route; + + a = nmtst_ip4_config_new(1); + + /* Test that a higher priority source is not overwritten */ + route = *nmtst_platform_ip4_route("1.2.3.0", 24, "1.2.3.1"); + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip4_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip4_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip4_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER); + + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip4_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip4_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip4_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER); + + _nmtst_ip4_config_del_route(a, 0); + g_assert_cmpint(nm_ip4_config_get_num_routes(a), ==, 0); + + /* Test that a lower priority address source is overwritten */ + route.rt_source = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL; + nm_ip4_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip4_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip4_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_RTPROT_KERNEL); + + route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL; + nm_ip4_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip4_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip4_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_KERNEL); +} + +static void +test_merge_subtract_mtu(void) +{ + NMIP4Config *cfg1, *cfg2, *cfg3; + guint32 expected_mtu2 = 1492; + guint32 expected_mtu3 = 666; + + cfg1 = build_test_config(); + cfg2 = build_test_config(); + cfg3 = build_test_config(); + + /* add MSS, MTU to configs to test them */ + nm_ip4_config_set_mtu(cfg2, expected_mtu2, NM_IP_CONFIG_SOURCE_UNKNOWN); + nm_ip4_config_set_mtu(cfg3, expected_mtu3, NM_IP_CONFIG_SOURCE_UNKNOWN); + + nm_ip4_config_merge(cfg1, cfg2, NM_IP_CONFIG_MERGE_DEFAULT, 0); + /* ensure MSS and MTU are in cfg1 */ + g_assert_cmpuint(nm_ip4_config_get_mtu(cfg1), ==, expected_mtu2); + + nm_ip4_config_merge(cfg1, cfg3, NM_IP_CONFIG_MERGE_DEFAULT, 0); + /* ensure again the MSS and MTU in cfg1 got overridden */ + g_assert_cmpuint(nm_ip4_config_get_mtu(cfg1), ==, expected_mtu3); + + nm_ip4_config_subtract(cfg1, cfg3, 0); + /* ensure MSS and MTU are zero in cfg1 */ + g_assert_cmpuint(nm_ip4_config_get_mtu(cfg1), ==, 0); + + g_object_unref(cfg1); + g_object_unref(cfg2); + g_object_unref(cfg3); +} + +static void +test_strip_search_trailing_dot(void) +{ + NMIP4Config *config; + + config = nmtst_ip4_config_new(1); + + nm_ip4_config_add_search(config, "."); + nm_ip4_config_add_search(config, "foo"); + nm_ip4_config_add_search(config, "bar."); + nm_ip4_config_add_search(config, "baz.com"); + nm_ip4_config_add_search(config, "baz.com."); + nm_ip4_config_add_search(config, "foobar.."); + nm_ip4_config_add_search(config, ".foobar"); + nm_ip4_config_add_search(config, "~."); + + g_assert_cmpuint(nm_ip4_config_get_num_searches(config), ==, 4); + g_assert_cmpstr(nm_ip4_config_get_search(config, 0), ==, "foo"); + g_assert_cmpstr(nm_ip4_config_get_search(config, 1), ==, "bar"); + g_assert_cmpstr(nm_ip4_config_get_search(config, 2), ==, "baz.com"); + g_assert_cmpstr(nm_ip4_config_get_search(config, 3), ==, "~"); + + g_object_unref(config); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT"); + + g_test_add_func("/ip4-config/replace", test_replace); + g_test_add_func("/ip4-config/subtract", test_subtract); + g_test_add_func("/ip4-config/compare-with-source", test_compare_with_source); + g_test_add_func("/ip4-config/add-address-with-source", test_add_address_with_source); + g_test_add_func("/ip4-config/add-route-with-source", test_add_route_with_source); + g_test_add_func("/ip4-config/merge-subtract-mtu", test_merge_subtract_mtu); + g_test_add_func("/ip4-config/strip-search-trailing-dot", test_strip_search_trailing_dot); + + return g_test_run(); +} diff --git a/src/core/tests/test-ip6-config.c b/src/core/tests/test-ip6-config.c new file mode 100644 index 0000000..7ba7973 --- /dev/null +++ b/src/core/tests/test-ip6-config.c @@ -0,0 +1,538 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-ip6-config.h" + +#include "platform/nm-platform.h" +#include "nm-test-utils-core.h" + +static NMIP6Config * +build_test_config(void) +{ + NMIP6Config *config; + + /* Build up the config to subtract */ + config = nmtst_ip6_config_new(1); + + nm_ip6_config_add_address(config, + nmtst_platform_ip6_address("abcd:1234:4321::cdde", "1:2:3:4::5", 64)); + nm_ip6_config_add_route( + config, + nmtst_platform_ip6_route("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL), + NULL); + nm_ip6_config_add_route(config, + nmtst_platform_ip6_route("2001::", 16, "2001:abba::2234", NULL), + NULL); + + nm_ip6_config_add_route(config, + nmtst_platform_ip6_route("::", 0, "3001:abba::3234", NULL), + NULL); + + nm_ip6_config_add_nameserver(config, nmtst_inet6_from_string("1:2:3:4::1")); + nm_ip6_config_add_nameserver(config, nmtst_inet6_from_string("1:2:3:4::2")); + nm_ip6_config_add_domain(config, "foobar.com"); + nm_ip6_config_add_domain(config, "baz.com"); + nm_ip6_config_add_search(config, "blahblah.com"); + nm_ip6_config_add_search(config, "beatbox.com"); + + return config; +} + +static void +test_subtract(void) +{ + NMIP6Config * src, *dst; + const NMPlatformIP6Address *test_addr; + const NMPlatformIP6Route * test_route; + const char * expected_addr = "1122:3344:5566::7788"; + guint32 expected_addr_plen = 96; + const char * expected_route_dest = "9991:8800::"; + guint32 expected_route_plen = 24; + const char * expected_route_next_hop = "1119:2228:3337:4446::5555"; + struct in6_addr expected_ns1; + struct in6_addr expected_ns2; + const char * expected_domain = "wonderfalls.com"; + const char * expected_search = "somewhere.com"; + struct in6_addr tmp; + + src = build_test_config(); + + /* add a couple more things to the test config */ + dst = build_test_config(); + nm_ip6_config_add_address(dst, + nmtst_platform_ip6_address(expected_addr, NULL, expected_addr_plen)); + nm_ip6_config_add_route(dst, + nmtst_platform_ip6_route(expected_route_dest, + expected_route_plen, + expected_route_next_hop, + NULL), + NULL); + + expected_ns1 = *nmtst_inet6_from_string("2222:3333:4444::5555"); + nm_ip6_config_add_nameserver(dst, &expected_ns1); + expected_ns2 = *nmtst_inet6_from_string("2222:3333:4444::5556"); + nm_ip6_config_add_nameserver(dst, &expected_ns2); + + nm_ip6_config_add_domain(dst, expected_domain); + nm_ip6_config_add_search(dst, expected_search); + + nm_ip6_config_subtract(dst, src, 0); + + /* ensure what's left is what we expect */ + g_assert_cmpuint(nm_ip6_config_get_num_addresses(dst), ==, 1); + test_addr = _nmtst_ip6_config_get_address(dst, 0); + g_assert(test_addr != NULL); + tmp = *nmtst_inet6_from_string(expected_addr); + g_assert(memcmp(&test_addr->address, &tmp, sizeof(tmp)) == 0); + g_assert(memcmp(&test_addr->peer_address, &in6addr_any, sizeof(tmp)) == 0); + g_assert_cmpuint(test_addr->plen, ==, expected_addr_plen); + + g_assert(nm_ip6_config_best_default_route_get(dst) == NULL); + + g_assert_cmpuint(nm_ip6_config_get_num_routes(dst), ==, 1); + test_route = _nmtst_ip6_config_get_route(dst, 0); + g_assert(test_route != NULL); + + tmp = *nmtst_inet6_from_string(expected_route_dest); + g_assert(memcmp(&test_route->network, &tmp, sizeof(tmp)) == 0); + g_assert_cmpuint(test_route->plen, ==, expected_route_plen); + tmp = *nmtst_inet6_from_string(expected_route_next_hop); + g_assert(memcmp(&test_route->gateway, &tmp, sizeof(tmp)) == 0); + + g_assert_cmpuint(nm_ip6_config_get_num_nameservers(dst), ==, 2); + g_assert(memcmp(nm_ip6_config_get_nameserver(dst, 0), &expected_ns1, sizeof(expected_ns1)) + == 0); + g_assert(memcmp(nm_ip6_config_get_nameserver(dst, 1), &expected_ns2, sizeof(expected_ns2)) + == 0); + + g_assert_cmpuint(nm_ip6_config_get_num_domains(dst), ==, 1); + g_assert_cmpstr(nm_ip6_config_get_domain(dst, 0), ==, expected_domain); + g_assert_cmpuint(nm_ip6_config_get_num_searches(dst), ==, 1); + g_assert_cmpstr(nm_ip6_config_get_search(dst, 0), ==, expected_search); + + g_object_unref(src); + g_object_unref(dst); +} + +static void +test_compare_with_source(void) +{ + NMIP6Config * a, *b; + NMPlatformIP6Address addr; + NMPlatformIP6Route route; + + a = nmtst_ip6_config_new(1); + b = nmtst_ip6_config_new(2); + + /* Address */ + addr = *nmtst_platform_ip6_address("1122:3344:5566::7788", NULL, 64); + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_address(a, &addr); + + addr.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip6_config_add_address(b, &addr); + + /* Route */ + route = *nmtst_platform_ip6_route("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL); + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_route(a, &route, NULL); + + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip6_config_add_route(b, &route, NULL); + + /* Assert that the configs are basically the same, eg that the source is ignored */ + g_assert(nm_ip6_config_equal(a, b)); + + g_object_unref(a); + g_object_unref(b); +} + +static void +test_add_address_with_source(void) +{ + NMIP6Config * a; + NMPlatformIP6Address addr; + const NMPlatformIP6Address *test_addr; + + a = nmtst_ip6_config_new(1); + + /* Test that a higher priority source is not overwritten */ + addr = *nmtst_platform_ip6_address("1122:3344:5566::7788", NULL, 64); + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_address(a, &addr); + + test_addr = _nmtst_ip6_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + addr.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip6_config_add_address(a, &addr); + + test_addr = _nmtst_ip6_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + /* Test that a lower priority address source is overwritten */ + _nmtst_ip6_config_del_address(a, 0); + addr.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + nm_ip6_config_add_address(a, &addr); + + test_addr = _nmtst_ip6_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_KERNEL); + + addr.addr_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_address(a, &addr); + + test_addr = _nmtst_ip6_config_get_address(a, 0); + g_assert_cmpint(test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER); + + g_object_unref(a); +} + +static void +test_add_route_with_source(void) +{ + gs_unref_object NMIP6Config *a = NULL; + NMPlatformIP6Route route; + const NMPlatformIP6Route * test_route; + + a = nmtst_ip6_config_new(1); + + /* Test that a higher priority source is not overwritten */ + route = *nmtst_platform_ip6_route("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL); + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip6_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip6_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER); + + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip6_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip6_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip6_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER); + + _nmtst_ip6_config_del_route(a, 0); + g_assert_cmpint(nm_ip6_config_get_num_routes(a), ==, 0); + + /* Test that a lower priority address source is overwritten */ + route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL; + nm_ip6_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip6_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip6_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_KERNEL); + + route.rt_source = NM_IP_CONFIG_SOURCE_USER; + nm_ip6_config_add_route(a, &route, NULL); + + g_assert_cmpint(nm_ip6_config_get_num_routes(a), ==, 1); + test_route = _nmtst_ip6_config_get_route(a, 0); + g_assert_cmpint(test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER); +} + +static void +test_nm_ip6_config_addresses_sort_check(NMIP6Config * config, + NMSettingIP6ConfigPrivacy use_tempaddr, + int repeat) +{ + int addr_count = nm_ip6_config_get_num_addresses(config); + int i, irepeat; + NMIP6Config *copy, *copy2; + int * idx = g_new(int, addr_count); + + nm_ip6_config_set_privacy(config, use_tempaddr); + copy = nm_ip6_config_clone(config); + g_assert(copy); + copy2 = nm_ip6_config_clone(config); + g_assert(copy2); + + /* initialize the array of indices, and keep shuffling them for every @repeat iteration. */ + for (i = 0; i < addr_count; i++) + idx[i] = i; + + for (irepeat = 0; irepeat < repeat; irepeat++) { + /* randomly shuffle the addresses. */ + nm_ip6_config_reset_addresses(copy); + for (i = 0; i < addr_count; i++) { + int j = g_rand_int_range(nmtst_get_rand(), i, addr_count); + + NM_SWAP(&idx[i], &idx[j]); + nm_ip6_config_add_address(copy, _nmtst_ip6_config_get_address(config, idx[i])); + } + + /* reorder them again */ + _nmtst_ip6_config_addresses_sort(copy); + + /* check equality using nm_ip6_config_equal() */ + if (!nm_ip6_config_equal(copy, config)) { + g_message("%s", "SORTING yields unexpected output:"); + for (i = 0; i < addr_count; i++) { + g_message( + " >> [%d] = %s", + i, + nm_platform_ip6_address_to_string(_nmtst_ip6_config_get_address(config, i), + NULL, + 0)); + g_message(" << [%d] = %s", + i, + nm_platform_ip6_address_to_string(_nmtst_ip6_config_get_address(copy, i), + NULL, + 0)); + } + g_assert_not_reached(); + } + + /* also check equality using nm_ip6_config_replace() */ + g_assert(nm_ip6_config_replace(copy2, copy, NULL) == FALSE); + } + + g_free(idx); + g_object_unref(copy); + g_object_unref(copy2); +} + +static void +test_nm_ip6_config_addresses_sort(void) +{ + NMIP6Config *config = build_test_config(); + +#define ADDR_ADD(...) \ + nm_ip6_config_add_address(config, nmtst_platform_ip6_address_full(__VA_ARGS__)) + + nm_ip6_config_reset_addresses(config); + ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::6", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_NDISC, + 0, + 0, + 0, + IFA_F_MANAGETEMPADDR); + ADDR_ADD("2607:f0d0:1002:51::3", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("2607:f0d0:1002:51::8", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("2607:f0d0:1002:51::0", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_KERNEL, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("fec0::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::2", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TENTATIVE); + test_nm_ip6_config_addresses_sort_check(config, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, 8); + test_nm_ip6_config_addresses_sort_check(config, NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED, 8); + test_nm_ip6_config_addresses_sort_check(config, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, + 8); + + nm_ip6_config_reset_addresses(config); + ADDR_ADD("2607:f0d0:1002:51::3", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::8", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("2607:f0d0:1002:51::0", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_KERNEL, + 0, + 0, + 0, + IFA_F_TEMPORARY); + ADDR_ADD("2607:f0d0:1002:51::6", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_NDISC, + 0, + 0, + 0, + IFA_F_MANAGETEMPADDR); + ADDR_ADD("fec0::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); + ADDR_ADD("::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); + ADDR_ADD("2607:f0d0:1002:51::2", + NULL, + 64, + 0, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + 0, + IFA_F_TENTATIVE); + test_nm_ip6_config_addresses_sort_check(config, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + 8); + +#undef ADDR_ADD + g_object_unref(config); +} + +static void +test_strip_search_trailing_dot(void) +{ + NMIP6Config *config; + + config = nmtst_ip6_config_new(1); + + nm_ip6_config_add_search(config, "."); + nm_ip6_config_add_search(config, "foo"); + nm_ip6_config_add_search(config, "bar."); + nm_ip6_config_add_search(config, "baz.com"); + nm_ip6_config_add_search(config, "baz.com."); + nm_ip6_config_add_search(config, "foobar.."); + nm_ip6_config_add_search(config, ".foobar"); + nm_ip6_config_add_search(config, "~."); + + g_assert_cmpuint(nm_ip6_config_get_num_searches(config), ==, 4); + g_assert_cmpstr(nm_ip6_config_get_search(config, 0), ==, "foo"); + g_assert_cmpstr(nm_ip6_config_get_search(config, 1), ==, "bar"); + g_assert_cmpstr(nm_ip6_config_get_search(config, 2), ==, "baz.com"); + g_assert_cmpstr(nm_ip6_config_get_search(config, 3), ==, "~"); + + g_object_unref(config); +} + +/*****************************************************************************/ + +static void +test_replace(gconstpointer user_data) +{ + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new(); + const int TEST_IDX = GPOINTER_TO_INT(user_data); + const int IFINDEX = 1; + gs_unref_object NMIP6Config *src_conf = NULL; + gs_unref_object NMIP6Config *dst_conf = NULL; + NMPlatformIP6Address * addr; + NMPlatformIP6Address addrs[5] = {}; + guint addrs_n = 0; + guint i; + + dst_conf = nm_ip6_config_new(multi_idx, IFINDEX); + src_conf = nm_ip6_config_new(multi_idx, IFINDEX); + + switch (TEST_IDX) { + case 1: + addr = &addrs[addrs_n++]; + addr->ifindex = IFINDEX; + addr->address = *nmtst_inet6_from_string("fe80::78ec:7a6d:602d:20f2"); + addr->plen = 64; + addr->n_ifa_flags = IFA_F_PERMANENT; + addr->addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + break; + case 2: + addr = &addrs[addrs_n++]; + addr->ifindex = IFINDEX; + addr->address = *nmtst_inet6_from_string("fe80::78ec:7a6d:602d:20f2"); + addr->plen = 64; + addr->n_ifa_flags = IFA_F_PERMANENT; + addr->addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + + addr = &addrs[addrs_n++]; + addr->ifindex = IFINDEX; + addr->address = *nmtst_inet6_from_string("1::1"); + addr->plen = 64; + addr->addr_source = NM_IP_CONFIG_SOURCE_USER; + + nm_ip6_config_add_address(dst_conf, addr); + break; + default: + g_assert_not_reached(); + } + + g_assert(addrs_n < G_N_ELEMENTS(addrs)); + + for (i = 0; i < addrs_n; i++) + nm_ip6_config_add_address(src_conf, &addrs[i]); + + nm_ip6_config_replace(dst_conf, src_conf, NULL); + + for (i = 0; i < addrs_n; i++) { + const NMPlatformIP6Address *a = _nmtst_ip6_config_get_address(dst_conf, i); + const NMPlatformIP6Address *b = _nmtst_ip6_config_get_address(src_conf, i); + + g_assert(nm_platform_ip6_address_cmp(&addrs[i], a) == 0); + g_assert(nm_platform_ip6_address_cmp(&addrs[i], b) == 0); + } + g_assert(addrs_n == nm_ip6_config_get_num_addresses(dst_conf)); + g_assert(addrs_n == nm_ip6_config_get_num_addresses(src_conf)); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "ALL"); + + g_test_add_func("/ip6-config/subtract", test_subtract); + g_test_add_func("/ip6-config/compare-with-source", test_compare_with_source); + g_test_add_func("/ip6-config/add-address-with-source", test_add_address_with_source); + g_test_add_func("/ip6-config/add-route-with-source", test_add_route_with_source); + g_test_add_func("/ip6-config/test_nm_ip6_config_addresses_sort", + test_nm_ip6_config_addresses_sort); + g_test_add_func("/ip6-config/strip-search-trailing-dot", test_strip_search_trailing_dot); + g_test_add_data_func("/ip6-config/replace/1", GINT_TO_POINTER(1), test_replace); + g_test_add_data_func("/ip6-config/replace/2", GINT_TO_POINTER(2), test_replace); + + return g_test_run(); +} diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c new file mode 100644 index 0000000..92e596a --- /dev/null +++ b/src/core/tests/test-l3cfg.c @@ -0,0 +1,795 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-default.h" + +#include "nm-l3cfg.h" +#include "nm-l3-ipv4ll.h" +#include "nm-netns.h" +#include "platform/nm-platform.h" + +#include "platform/tests/test-common.h" + +/*****************************************************************************/ + +static NML3Cfg * +_netns_access_l3cfg(NMNetns *netns, int ifindex) +{ + NML3Cfg *l3cfg; + + g_assert(NM_IS_NETNS(netns)); + g_assert(ifindex > 0); + + g_assert(!nm_netns_get_l3cfg(netns, ifindex)); + + l3cfg = nm_netns_access_l3cfg(netns, ifindex); + g_assert(NM_IS_L3CFG(l3cfg)); + return l3cfg; +} + +/*****************************************************************************/ + +typedef struct { + int test_idx; + NMPlatform * platform; + NMNetns * netns; + NMDedupMultiIndex *multiidx; + const char * ifname0; + const char * ifname1; + NMPLinkAddress hwaddr0; + NMPLinkAddress hwaddr1; + int ifindex0; + int ifindex1; +} TestFixture1; + +static const TestFixture1 * +_test_fixture_1_setup(TestFixture1 *f, int test_idx) +{ + const NMPlatformLink *l0; + const NMPlatformLink *l1; + const NMEtherAddr addr0 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x00); + const NMEtherAddr addr1 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x11); + + g_assert_cmpint(test_idx, >, 0); + g_assert_cmpint(f->test_idx, ==, 0); + + f->test_idx = test_idx; + + f->ifname0 = "nm-test-veth0"; + f->ifname1 = "nm-test-veth1"; + + f->platform = g_object_ref(NM_PLATFORM_GET); + f->multiidx = nm_dedup_multi_index_ref(nm_platform_get_multi_idx(f->platform)); + f->netns = nm_netns_new(f->platform); + + nmtstp_link_veth_add(f->platform, -1, f->ifname0, f->ifname1); + + l0 = nmtstp_link_get_typed(f->platform, -1, f->ifname0, NM_LINK_TYPE_VETH); + l1 = nmtstp_link_get_typed(f->platform, -1, f->ifname1, NM_LINK_TYPE_VETH); + + f->ifindex0 = l0->ifindex; + f->ifindex1 = l1->ifindex; + + g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex0, &addr0, sizeof(addr0)), + ==, + 0); + g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex1, &addr1, sizeof(addr1)), + ==, + 0); + + l0 = nmtstp_link_get_typed(f->platform, f->ifindex0, f->ifname0, NM_LINK_TYPE_VETH); + l1 = nmtstp_link_get_typed(f->platform, f->ifindex1, f->ifname1, NM_LINK_TYPE_VETH); + + f->hwaddr0 = l0->l_address; + f->hwaddr1 = l1->l_address; + + g_assert(nm_platform_link_set_up(f->platform, f->ifindex0, NULL)); + g_assert(nm_platform_link_set_up(f->platform, f->ifindex1, NULL)); + + return f; +} + +static void +_test_fixture_1_teardown(TestFixture1 *f) +{ + g_assert(f); + + if (f->test_idx == 0) + return; + + _LOGD("test teatdown"); + + nmtstp_link_delete(f->platform, -1, f->ifindex0, f->ifname0, TRUE); + g_assert(!nm_platform_link_get(f->platform, f->ifindex0)); + g_assert(!nm_platform_link_get(f->platform, f->ifindex1)); + g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname0)); + g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname1)); + + g_object_unref(f->netns); + g_object_unref(f->platform); + nm_dedup_multi_index_unref(f->multiidx); + + *f = (TestFixture1){ + .test_idx = 0, + }; +} + +/*****************************************************************************/ + +typedef enum { + TEST_L3CFG_NOTIFY_TYPE_NONE, + TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL, + TEST_L3CFG_NOTIFY_TYPE_COMMIT_1, + TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1, +} TestL3cfgNotifyType; + +typedef struct { + const TestFixture1 *f; + + bool has_addr4_101 : 1; + bool add_addr4_101 : 1; + + guint32 acd_timeout_msec_a; + NML3AcdDefendType acd_defend_type_a; + + TestL3cfgNotifyType notify_type; + guint post_commit_event_count; + guint general_event_count; + guint general_event_flags; + union { + struct { + int cb_count; + bool expected_probe_result : 1; + bool acd_event_ready_45 : 1; + bool acd_event_ready_101 : 1; + } wait_for_acd_ready_1; + } notify_result; +} TestL3cfgData; + +static void +_test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notify_type) +{ + g_assert(tdata); + + tdata->notify_type = notify_type; + tdata->post_commit_event_count = 0; + tdata->general_event_count = 0; + tdata->general_event_flags = 0; + memset(&tdata->notify_result, 0, sizeof(tdata->notify_result)); +} + +static void +_test_l3cfg_signal_notify(NML3Cfg * l3cfg, + const NML3ConfigNotifyData *notify_data, + TestL3cfgData * tdata) +{ + guint i; + + g_assert(NM_IS_L3CFG(l3cfg)); + g_assert(tdata); + g_assert(notify_data); + g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type)); + g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM); + + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) + g_assert(notify_data->platform_change_on_idle.obj_type_flags != 0u); + else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) { + g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj)); + g_assert(notify_data->platform_change.change_type != 0); + } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { + g_assert_cmpint(notify_data->acd_event.info.n_track_infos, >=, 1); + g_assert(notify_data->acd_event.info.track_infos); + for (i = 0; i < notify_data->acd_event.info.n_track_infos; i++) { + const NML3AcdAddrTrackInfo *ti = ¬ify_data->acd_event.info.track_infos[i]; + + nm_assert(NMP_OBJECT_GET_TYPE(ti->obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); + nm_assert(NMP_OBJECT_CAST_IP4_ADDRESS(ti->obj)->address + == notify_data->acd_event.info.addr); + nm_assert(NM_IS_L3_CONFIG_DATA(ti->l3cd)); + nm_assert(ti->tag); + } + } + + switch (tdata->notify_type) { + case TEST_L3CFG_NOTIFY_TYPE_NONE: + g_assert_not_reached(); + break; + case TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL: + if (NM_IN_SET(notify_data->notify_type, + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)) + return; + g_assert_not_reached(); + return; + case TEST_L3CFG_NOTIFY_TYPE_COMMIT_1: + g_assert_cmpint(tdata->post_commit_event_count, ==, 0); + switch (notify_data->notify_type) { + case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT: + tdata->post_commit_event_count++; + return; + case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: + switch (tdata->f->test_idx) { + case 2: + case 3: + nmtst_assert_ip4_address(notify_data->acd_event.info.addr, "192.168.133.45"); + if (tdata->f->test_idx == 2) + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); + else + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); + g_assert(tdata->general_event_count == 0); + tdata->general_event_count++; + return; + case 4: + if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) { + g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x1u)); + tdata->general_event_flags |= 0x1u; + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); + tdata->general_event_count++; + } else if (notify_data->acd_event.info.addr + == nmtst_inet4_from_string("192.168.133.101")) { + g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x4u)); + tdata->general_event_flags |= 0x4u; + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); + tdata->general_event_count++; + } else + g_assert_not_reached(); + return; + default: + g_assert_not_reached(); + return; + } + case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: + return; + default: + g_assert_not_reached(); + return; + } + case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1: + { + int num_acd_completed_events = + 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0); + + if (NM_IN_SET(notify_data->notify_type, + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, + NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)) + return; + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { + if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) { + g_assert(NM_IN_SET(notify_data->acd_event.info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)); + tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 = TRUE; + } else if (notify_data->acd_event.info.addr + == nmtst_inet4_from_string("192.168.133.101")) { + if (tdata->has_addr4_101) { + g_assert( + NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_USED)); + } else { + g_assert(NM_IN_SET(notify_data->acd_event.info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)); + tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 = TRUE; + } + } else + g_assert_not_reached(); + + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, + <, + num_acd_completed_events); + tdata->notify_result.wait_for_acd_ready_1.cb_count++; + return; + } + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) { + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, >, 0); + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, + <, + num_acd_completed_events); + tdata->notify_result.wait_for_acd_ready_1.cb_count++; + nmtstp_platform_ip_addresses_assert( + tdata->f->platform, + tdata->f->ifindex0, + TRUE, + TRUE, + TRUE, + tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 ? "192.168.133.45" + : NULL, + tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 ? "192.168.133.101" + : NULL, + "1:2:3:4::45"); + return; + } + g_assert_not_reached(); + return; + } + } + + g_assert_not_reached(); +} + +static void +test_l3cfg(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const guint32 ACD_TIMEOUT_BASE_MSEC = 1000; + nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; + const TestFixture1 * f; + NML3CfgCommitTypeHandle * commit_type_1; + NML3CfgCommitTypeHandle * commit_type_2; + gs_unref_object NML3Cfg *l3cfg0 = NULL; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL; + TestL3cfgData tdata_stack = { + .f = NULL, + }; + TestL3cfgData *const tdata = &tdata_stack; + + _LOGD("test start (/l3cfg/%d)", TEST_IDX); + + if (nmtst_test_quick()) { + gs_free char *msg = + g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-l3cfg"); + + g_test_skip(msg); + return; + } + + f = _test_fixture_1_setup(&test_fixture, TEST_IDX); + + tdata->f = f; + tdata->has_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool()); + tdata->add_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool()); + + tdata->acd_timeout_msec_a = NM_IN_SET(f->test_idx, 3, 4) ? ACD_TIMEOUT_BASE_MSEC : 0u; + tdata->acd_defend_type_a = NM_IN_SET(f->test_idx, 4) + ? nmtst_rand_select(NM_L3_ACD_DEFEND_TYPE_NEVER, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_L3_ACD_DEFEND_TYPE_ALWAYS) + : NM_L3_ACD_DEFEND_TYPE_NEVER; + + if (tdata->has_addr4_101) { + nmtstp_ip4_address_add(f->platform, + -1, + f->ifindex1, + nmtst_inet4_from_string("192.168.133.101"), + 24, + nmtst_inet4_from_string("192.168.133.101"), + 100000, + 0, + 0, + NULL); + } + + l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0); + + g_signal_connect(l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3cfg_signal_notify), tdata); + + commit_type_1 = nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL); + + if (!nmtst_get_rand_one_case_in(4)) { + commit_type_2 = + nm_l3cfg_commit_type_register(l3cfg0, + nmtst_rand_select(NM_L3_CFG_COMMIT_TYPE_NONE, + NM_L3_CFG_COMMIT_TYPE_ASSUME, + NM_L3_CFG_COMMIT_TYPE_UPDATE), + NULL); + } else + commit_type_2 = NULL; + + switch (f->test_idx) { + case 1: + break; + case 2: + case 3: + case 4: + { + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + + l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0); + + nm_l3_config_data_add_address_4( + l3cd, + NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.45"), + .peer_address = nmtst_inet4_from_string("192.168.133.45"), + .plen = 24, )); + + if (tdata->add_addr4_101) { + nm_l3_config_data_add_address_4( + l3cd, + NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.101"), + .peer_address = + nmtst_inet4_from_string("192.168.133.101"), + .plen = 24, )); + } + + nm_l3_config_data_add_address_6( + l3cd, + NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"), + .plen = 64, )); + + if (nmtst_get_rand_one_case_in(2)) + nm_l3_config_data_seal(l3cd); + l3cd_a = g_steal_pointer(&l3cd); + break; + } + } + + nm_l3_config_data_log(l3cd_a, "l3cd_a", "platform-test: l3cd_a: ", LOGL_DEBUG, LOGD_PLATFORM); + + if (l3cd_a) { + nm_l3cfg_add_config(l3cfg0, + GINT_TO_POINTER('a'), + nmtst_get_rand_bool(), + l3cd_a, + 'a', + 0, + 0, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + 0, + 0, + tdata->acd_defend_type_a, + tdata->acd_timeout_msec_a, + NM_L3_CONFIG_MERGE_FLAGS_NONE); + } + + nm_l3_config_data_log(nm_l3cfg_get_combined_l3cd(l3cfg0, FALSE), + "test", + "platform-test: l3cfg0: ", + LOGL_DEBUG, + LOGD_PLATFORM); + + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1); + nm_l3cfg_commit(l3cfg0, NM_L3_CFG_COMMIT_TYPE_REAPPLY); + g_assert_cmpint(tdata->post_commit_event_count, ==, 1); + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); + + nmtstp_platform_ip_addresses_assert(tdata->f->platform, + tdata->f->ifindex0, + TRUE, + TRUE, + TRUE, + NM_IN_SET(f->test_idx, 2) ? "192.168.133.45" : NULL, + NM_IN_SET(f->test_idx, 2, 3, 4) ? "1:2:3:4::45" : NULL); + + if (NM_IN_SET(f->test_idx, 1, 2)) { + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL); + _LOGT("poll 1 start"); + nmtst_main_context_iterate_until(NULL, + nmtst_get_rand_uint32() % (ACD_TIMEOUT_BASE_MSEC * 5u), + FALSE); + _LOGT("poll 1 end"); + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); + } + + if (NM_IN_SET(f->test_idx, 3, 4)) { + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1); + tdata->notify_result.wait_for_acd_ready_1.expected_probe_result = TRUE; + _LOGT("poll 2 start"); + nmtst_main_context_iterate_until( + NULL, + ACD_TIMEOUT_BASE_MSEC * 3u / 2u + + (nmtst_get_rand_uint32() % (2u * ACD_TIMEOUT_BASE_MSEC)), + FALSE); + _LOGT("poll 2 end"); + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, + ==, + 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0)); + _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); + } + + g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3cfg_signal_notify), tdata); + + nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_1); + nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_2); + + if (nmtst_get_rand_one_case_in(3)) + _test_fixture_1_teardown(&test_fixture); + + nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'), FALSE); + + if (nmtst_get_rand_one_case_in(3)) + _test_fixture_1_teardown(&test_fixture); + + _LOGD("test end (/l3cfg/%d)", TEST_IDX); +} + +/*****************************************************************************/ + +#define L3IPV4LL_ACD_TIMEOUT_MSEC 1500u + +typedef struct { + const TestFixture1 * f; + NML3CfgCommitTypeHandle *l3cfg_commit_type_1; + guint acd_timeout_msec; + NML3IPv4LL * l3ipv4ll; + bool has_addr4_101; + gint8 ready_seen; + gint8 addr_commit; + in_addr_t addr_commit_addr; + bool add_conflict_checked : 1; + bool add_conflict_done; +} TestL3IPv4LLData; + +static gconstpointer +TEST_L3_IPV4LL_TAG(const TestL3IPv4LLData *tdata, guint offset) +{ + return (&(((const char *) tdata)[offset])); +} + +static void +_test_l3_ipv4ll_maybe_add_addr_4(const TestL3IPv4LLData *tdata, + int ifindex, + guint one_case_in_num, + bool * has_addr, + const char * addr) +{ + if (has_addr) { + if (*has_addr || !nmtst_get_rand_one_case_in(one_case_in_num)) + return; + *has_addr = TRUE; + } + + if (ifindex == 0) + ifindex = tdata->f->ifindex0; + + g_assert_cmpint(ifindex, >, 0); + + _LOGT("add test address: %s on ifindex=%d", addr, ifindex); + + nmtstp_ip4_address_add(tdata->f->platform, + -1, + ifindex, + nmtst_inet4_from_string(addr), + 24, + nmtst_inet4_from_string(addr), + 100000, + 0, + 0, + NULL); +} + +static void +_test_l3_ipv4ll_signal_notify(NML3Cfg * l3cfg, + const NML3ConfigNotifyData *notify_data, + TestL3IPv4LLData * tdata) +{ + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + g_assert(NM_IS_L3CFG(l3cfg)); + g_assert(tdata); + g_assert(notify_data); + g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type)); + g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM); + + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT) { + g_assert(tdata->l3ipv4ll == notify_data->ipv4ll_event.ipv4ll); + g_assert(NM_IN_SET(tdata->ready_seen, 0, 1)); + g_assert(NM_IN_SET(tdata->addr_commit, 0, 1)); + + if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) == NM_L3_IPV4LL_STATE_READY) { + g_assert_cmpint(tdata->ready_seen, ==, 0); + g_assert_cmpint(tdata->addr_commit, ==, 0); + tdata->ready_seen++; + + if (tdata->f->test_idx == 2 && nmtst_get_rand_bool()) { + tdata->addr_commit++; + tdata->addr_commit_addr = nm_l3_ipv4ll_get_addr(tdata->l3ipv4ll); + g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr)); + _LOGT("add address %s that passed ACD", + _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr)); + if (!nm_l3cfg_add_config(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), + TEST_L3_IPV4LL_TAG(tdata, 1), + nmtst_get_rand_bool(), + nm_l3_ipv4ll_get_l3cd(tdata->l3ipv4ll), + NM_L3CFG_CONFIG_PRIORITY_IPV4LL, + 0, + 0, + 104, + 105, + 0, + 0, + NM_L3_ACD_DEFEND_TYPE_ONCE, + nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u, + NM_L3_CONFIG_MERGE_FLAGS_NONE)) + g_assert_not_reached(); + nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll)); + + tdata->l3cfg_commit_type_1 = + nm_l3cfg_commit_type_register(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), + NM_L3_CFG_COMMIT_TYPE_UPDATE, + tdata->l3cfg_commit_type_1); + } + } else if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) != NM_L3_IPV4LL_STATE_DEFENDING + && tdata->ready_seen > 0) { + g_assert_cmpint(tdata->ready_seen, ==, 1); + tdata->ready_seen--; + if (tdata->addr_commit > 0) { + g_assert_cmpint(tdata->addr_commit, ==, 1); + tdata->addr_commit--; + g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr)); + _LOGT("remove address %s that previously passed ACD", + _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr)); + if (!nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), + TEST_L3_IPV4LL_TAG(tdata, 1), + FALSE)) + g_assert_not_reached(); + nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll)); + nm_l3cfg_commit_type_unregister(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), + g_steal_pointer(&tdata->l3cfg_commit_type_1)); + } + } + return; + } +} + +static void +test_l3_ipv4ll(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; + const TestFixture1 * f; + gs_unref_object NML3Cfg *l3cfg0 = NULL; + TestL3IPv4LLData tdata_stack = { + .f = NULL, + }; + TestL3IPv4LLData *const tdata = &tdata_stack; + NMTstpAcdDefender * acd_defender_1 = NULL; + NMTstpAcdDefender * acd_defender_2 = NULL; + nm_auto_unref_l3ipv4ll NML3IPv4LL * l3ipv4ll = NULL; + gint64 start_time_msec; + gint64 total_poll_time_msec; + nm_auto_remove_l3ipv4ll_registration NML3IPv4LLRegistration *l3ipv4ll_reg = NULL; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD("test start (/l3-ipv4ll/%d)", TEST_IDX); + + if (nmtst_test_quick()) { + gs_free char *msg = + g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-l3-ipv4ll"); + + g_test_skip(msg); + return; + } + + f = _test_fixture_1_setup(&test_fixture, TEST_IDX); + + tdata->f = f; + + if (tdata->f->test_idx == 1) + tdata->acd_timeout_msec = 0; + else + tdata->acd_timeout_msec = L3IPV4LL_ACD_TIMEOUT_MSEC; + + _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101"); + + l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0); + + g_signal_connect(l3cfg0, + NM_L3CFG_SIGNAL_NOTIFY, + G_CALLBACK(_test_l3_ipv4ll_signal_notify), + tdata); + + l3ipv4ll = nm_l3_ipv4ll_new(l3cfg0); + + tdata->l3ipv4ll = l3ipv4ll; + + g_assert_cmpint(nm_l3_ipv4ll_get_ifindex(l3ipv4ll), ==, f->ifindex0); + g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED); + g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u); + + if (tdata->f->test_idx == 1) { + if (nmtst_get_rand_one_case_in(2)) + l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec); + } else + l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec); + + g_assert(tdata->acd_timeout_msec == 0 || l3ipv4ll_reg); + g_assert(!l3ipv4ll_reg || l3ipv4ll == nm_l3_ipv4ll_register_get_instance(l3ipv4ll_reg)); + + if (tdata->acd_timeout_msec == 0) { + g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED); + g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u); + } else { + g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_PROBING); + if (f->test_idx == 1) { + g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), + ==, + nmtst_inet4_from_string("169.254.30.158")); + } else { + g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), + ==, + nmtst_inet4_from_string("169.254.17.45")); + } + g_assert(nm_l3_ipv4ll_get_l3cd(l3ipv4ll)); + } + + _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101"); + + if (tdata->f->test_idx == 2 && nmtst_get_rand_one_case_in(3)) { + in_addr_t a = nm_l3_ipv4ll_get_addr(l3ipv4ll); + + g_assert(nm_utils_ip4_address_is_link_local(a)); + _test_l3_ipv4ll_maybe_add_addr_4(tdata, + tdata->f->ifindex1, + 2, + &tdata->add_conflict_done, + _nm_utils_inet4_ntop(a, sbuf_addr)); + g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr)); + acd_defender_2 = + nmtstp_acd_defender_new(tdata->f->ifindex1, a, &tdata->f->hwaddr1.ether_addr); + } + + start_time_msec = nm_utils_get_monotonic_timestamp_msec(); + total_poll_time_msec = + (L3IPV4LL_ACD_TIMEOUT_MSEC * 3 / 2) + (nmtst_get_rand_uint32() % L3IPV4LL_ACD_TIMEOUT_MSEC); + _LOGT("poll 1 start (wait %" G_GINT64_FORMAT " msec)", total_poll_time_msec); + while (TRUE) { + gint64 next_timeout_msec; + + next_timeout_msec = + start_time_msec + total_poll_time_msec - nm_utils_get_monotonic_timestamp_msec(); + if (next_timeout_msec <= 0) + break; + + next_timeout_msec = NM_MIN(next_timeout_msec, nmtst_get_rand_uint32() % 1000u); + nmtst_main_context_iterate_until(NULL, next_timeout_msec, FALSE); + _LOGT("poll 1 intermezzo"); + + _test_l3_ipv4ll_maybe_add_addr_4(tdata, + 0, + 1 + total_poll_time_msec / 1000, + &tdata->has_addr4_101, + "192.168.133.101"); + + if (tdata->addr_commit == 1 && !tdata->add_conflict_checked) { + tdata->add_conflict_checked = TRUE; + _test_l3_ipv4ll_maybe_add_addr_4( + tdata, + tdata->f->ifindex1, + 2, + &tdata->add_conflict_done, + _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr)); + if (tdata->add_conflict_done) + total_poll_time_msec += L3IPV4LL_ACD_TIMEOUT_MSEC / 2; + g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr)); + acd_defender_2 = nmtstp_acd_defender_new(tdata->f->ifindex1, + tdata->addr_commit_addr, + &tdata->f->hwaddr1.ether_addr); + } + } + _LOGT("poll 1 end"); + + if (tdata->addr_commit || nmtst_get_rand_bool()) { + nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll), + TEST_L3_IPV4LL_TAG(tdata, 1), + FALSE); + } + + nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_1)); + nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_2)); + + nm_l3cfg_commit_type_unregister(l3cfg0, g_steal_pointer(&tdata->l3cfg_commit_type_1)); + + g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3_ipv4ll_signal_notify), tdata); +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, "ERR", "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + g_test_add_data_func("/l3cfg/1", GINT_TO_POINTER(1), test_l3cfg); + g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg); + g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg); + g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg); + g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll); + g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll); +} diff --git a/src/core/tests/test-secret-agent.py b/src/core/tests/test-secret-agent.py new file mode 100755 index 0000000..46f82b3 --- /dev/null +++ b/src/core/tests/test-secret-agent.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +from gi.repository import GLib +import sys +import dbus +import dbus.service +import dbus.mainloop.glib + +IFACE_SECRET_AGENT = "org.freedesktop.NetworkManager.SecretAgent" +IFACE_AGENT_MANAGER = "org.freedesktop.NetworkManager.AgentManager" + + +class NotAuthorizedException(dbus.DBusException): + _dbus_error_name = IFACE_SECRET_AGENT + ".NotAuthorized" + + +class Agent(dbus.service.Object): + def __init__(self, bus, object_path): + self.agents = {} + self.bus = bus + dbus.service.Object.__init__(self, bus, object_path) + + @dbus.service.method( + IFACE_SECRET_AGENT, + in_signature="a{sa{sv}}osasb", + out_signature="a{sa{sv}}", + sender_keyword="sender", + ) + def GetSecrets( + self, + connection_hash, + connection_path, + setting_name, + hints, + request_new, + sender=None, + ): + if not sender: + raise NotAuthorizedException("Internal error: couldn't get sender") + uid = self.bus.get_unix_user(sender) + if uid != 0: + raise NotAuthorizedException("UID %d not authorized" % uid) + + print( + "Secrets requested path '%s' setting '%s' hints '%s' new %d" + % (connection_path, setting_name, str(hints), request_new) + ) + + # return some random GSM secrets + s_gsm = dbus.Dictionary({"password": "asdfadfasdfaf"}) + con = dbus.Dictionary({"gsm": s_gsm}) + return con + + +def register(proxy): + proxy.Register("test.agent.id", dbus_interface=IFACE_AGENT_MANAGER) + print("Registered!") + return False + + +def unregister(proxy, loop): + proxy.Unregister(dbus_interface=IFACE_AGENT_MANAGER) + loop.quit() + return False + + +def main(): + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + obj = Agent(bus, "/org/freedesktop/NetworkManager/SecretAgent") + proxy = bus.get_object( + "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/AgentManager" + ) + + mainloop = GLib.MainLoop() + + GLib.idle_add(register, proxy) + print("Running test secret agent") + + try: + mainloop.run() + except KeyboardInterrupt: + pass + + print("Unregistering...") + unregister(proxy, mainloop) + + +if __name__ == "__main__": + main() diff --git a/src/core/tests/test-systemd.c b/src/core/tests/test-systemd.c new file mode 100644 index 0000000..aa87660 --- /dev/null +++ b/src/core/tests/test-systemd.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "systemd/nm-sd.h" +#include "systemd/nm-sd-utils-shared.h" + +#include "nm-test-utils-core.h" + +/***************************************************************************** + * Stub implementations of libNetworkManagerBase symbols + *****************************************************************************/ + +gboolean +nm_utils_get_testing_initialized(void) +{ + return TRUE; +} + +void +_nm_utils_set_testing(NMUtilsTestFlags flags) +{ + g_assert_not_reached(); +} + +gint32 +nm_utils_get_monotonic_timestamp_sec(void) +{ + return 1; +} + +NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL]; + +gboolean +_nm_log_enabled_impl(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain) +{ + return FALSE; +} + +void +_nm_log_impl(const char *file, + guint line, + const char *func, + gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain, + int error, + const char *ifname, + const char *con_uuid, + const char *fmt, + ...) +{} + +gboolean +nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error) +{ + return TRUE; +} + +const char * +nm_strerror_native(int errsv) +{ + return g_strerror(errsv); +} + +/*****************************************************************************/ + +static void +test_dhcp_create(void) +{ + sd_dhcp_client *client4 = NULL; + int r; + + r = sd_dhcp_client_new(&client4, FALSE); + g_assert(r == 0); + g_assert(client4); + + if (/* never true */ client4 == (gpointer) &r) { + /* we don't want to call this, but ensure that the linker + * includes all these symbols. */ + sd_dhcp_client_start(client4); + } + + sd_dhcp_client_unref(client4); +} + +/*****************************************************************************/ + +static void +test_lldp_create(void) +{ + sd_lldp *lldp = NULL; + int r; + + r = sd_lldp_new(&lldp); + g_assert(r == 0); + g_assert(lldp); + + sd_lldp_unref(lldp); +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop * mainloop; + sd_event_source *event_source; +} TestSdEventData; + +static int +_test_sd_event_timeout_cb(sd_event_source *s, uint64_t usec, void *userdata) +{ + TestSdEventData *user_data = userdata; + + g_assert(user_data); + g_assert(user_data->mainloop); + g_assert(user_data->event_source); + + user_data->event_source = sd_event_source_unref(user_data->event_source); + g_main_loop_quit(user_data->mainloop); + return 0; +} + +static void +test_sd_event(void) +{ + int repeat; + + for (repeat = 0; repeat < 2; repeat++) { + guint sd_id = 0; + int r; + int i, n; + sd_event * other_events[3] = {NULL}, *event = NULL; + TestSdEventData user_data = {0}; + + g_assert_cmpint(sd_event_default(NULL), ==, 0); + + for (i = 0, n = (nmtst_get_rand_uint32() % (G_N_ELEMENTS(other_events) + 1)); i < n; i++) { + r = sd_event_default(&other_events[i]); + g_assert(r >= 0 && other_events[i]); + } + + sd_id = nm_sd_event_attach_default(); + + r = sd_event_default(&event); + g_assert(r >= 0 && event); + + r = sd_event_add_time(event, + &user_data.event_source, + CLOCK_MONOTONIC, + 1, + 0, + _test_sd_event_timeout_cb, + &user_data); + g_assert(r >= 0 && user_data.event_source); + + user_data.mainloop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(user_data.mainloop); + g_main_loop_unref(user_data.mainloop); + + g_assert(!user_data.event_source); + + event = sd_event_unref(event); + for (i = 0, n = (nmtst_get_rand_uint32() % (G_N_ELEMENTS(other_events) + 1)); i < n; i++) + other_events[i] = sd_event_unref(other_events[i]); + nm_clear_g_source(&sd_id); + for (i = 0, n = G_N_ELEMENTS(other_events); i < n; i++) + other_events[i] = sd_event_unref(other_events[i]); + + g_assert_cmpint(sd_event_default(NULL), ==, 0); + } +} + +/*****************************************************************************/ + +static void +test_path_equal(void) +{ +#define _path_equal_check1(path, kill_dots, expected) \ + G_STMT_START \ + { \ + const gboolean _kill_dots = (kill_dots); \ + const char * _path0 = (path); \ + const char * _expected = (expected); \ + gs_free char * _path = g_strdup(_path0); \ + const char * _path_result; \ + \ + if (!_kill_dots && !nm_sd_utils_path_equal(_path0, _expected)) \ + g_error("Paths \"%s\" and \"%s\" don't compare equal", _path0, _expected); \ + \ + _path_result = nm_sd_utils_path_simplify(_path, _kill_dots); \ + g_assert(_path_result == _path); \ + g_assert_cmpstr(_path, ==, _expected); \ + } \ + G_STMT_END + +#define _path_equal_check(path, expected_no_kill_dots, expected_kill_dots) \ + G_STMT_START \ + { \ + _path_equal_check1(path, FALSE, expected_no_kill_dots); \ + _path_equal_check1(path, TRUE, expected_kill_dots ?: expected_no_kill_dots); \ + } \ + G_STMT_END + + _path_equal_check("", "", NULL); + _path_equal_check(".", ".", NULL); + _path_equal_check("..", "..", NULL); + _path_equal_check("/..", "/..", NULL); + _path_equal_check("//..", "/..", NULL); + _path_equal_check("/.", "/.", "/"); + _path_equal_check("./", ".", "."); + _path_equal_check("./.", "./.", "."); + _path_equal_check(".///.", "./.", "."); + _path_equal_check(".///./", "./.", "."); + _path_equal_check(".////", ".", "."); + _path_equal_check("//..//foo/", "/../foo", NULL); + _path_equal_check("///foo//./bar/.", "/foo/./bar/.", "/foo/bar"); + _path_equal_check(".//./foo//./bar/.", "././foo/./bar/.", "foo/bar"); +} + +/*****************************************************************************/ + +static void +_test_unbase64char(char ch, gboolean maybe_invalid) +{ + int r; + + r = nm_sd_utils_unbase64char(ch, FALSE); + + if (ch == '=') { + g_assert(!maybe_invalid); + g_assert_cmpint(r, <, 0); + g_assert_cmpint(nm_sd_utils_unbase64char(ch, TRUE), ==, G_MAXINT); + } else { + g_assert_cmpint(r, ==, nm_sd_utils_unbase64char(ch, TRUE)); + if (r >= 0) + g_assert_cmpint(r, <=, 255); + if (!maybe_invalid) + g_assert_cmpint(r, >=, 0); + } +} + +static void +_test_unbase64mem_mem(const char *base64, const guint8 *expected_arr, gsize expected_len) +{ + gs_free char *expected_base64 = NULL; + int r; + gs_free guint8 *exp2_arr = NULL; + gs_free guint8 *exp3_arr = NULL; + gsize exp2_len; + gsize exp3_len; + gsize i; + + expected_base64 = g_base64_encode(expected_arr, expected_len); + + for (i = 0; expected_base64[i]; i++) + _test_unbase64char(expected_base64[i], FALSE); + + r = nm_sd_utils_unbase64mem(expected_base64, + strlen(expected_base64), + TRUE, + &exp2_arr, + &exp2_len); + g_assert_cmpint(r, ==, 0); + g_assert_cmpmem(expected_arr, expected_len, exp2_arr, exp2_len); + + if (!nm_streq(base64, expected_base64)) { + r = nm_sd_utils_unbase64mem(base64, strlen(base64), TRUE, &exp3_arr, &exp3_len); + g_assert_cmpint(r, ==, 0); + g_assert_cmpmem(expected_arr, expected_len, exp3_arr, exp3_len); + } +} + +#define _test_unbase64mem(base64, expected_str) \ + _test_unbase64mem_mem(base64, (const guint8 *) "" expected_str "", NM_STRLEN(expected_str)) + +static void +_test_unbase64mem_inval(const char *base64) +{ + gs_free guint8 *exp_arr = NULL; + gsize exp_len = 0; + int r; + + r = nm_sd_utils_unbase64mem(base64, strlen(base64), TRUE, &exp_arr, &exp_len); + g_assert_cmpint(r, <, 0); + g_assert(!exp_arr); + g_assert(exp_len == 0); +} + +static void +test_nm_sd_utils_unbase64mem(void) +{ + gs_free char *rnd_base64 = NULL; + guint8 rnd_buf[30]; + guint i, rnd_len; + + _test_unbase64mem("", ""); + _test_unbase64mem(" ", ""); + _test_unbase64mem(" Y Q == ", "a"); + _test_unbase64mem(" Y WJjZGV mZ 2g = ", "abcdefgh"); + _test_unbase64mem_inval(" Y %WJjZGV mZ 2g = "); + _test_unbase64mem_inval(" Y %WJjZGV mZ 2g = a"); + _test_unbase64mem("YQ==", "a"); + _test_unbase64mem_inval("YQ==a"); + + rnd_len = nmtst_get_rand_uint32() % sizeof(rnd_buf); + for (i = 0; i < rnd_len; i++) + rnd_buf[i] = nmtst_get_rand_uint32() % 256; + rnd_base64 = g_base64_encode(rnd_buf, rnd_len); + _test_unbase64mem_mem(rnd_base64, rnd_buf, rnd_len); + + _test_unbase64char('=', FALSE); + for (i = 0; i < 10; i++) { + char ch = nmtst_get_rand_uint32() % 256; + + if (ch != '=') + _test_unbase64char(ch, TRUE); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "ALL"); + + g_test_add_func("/systemd/dhcp/create", test_dhcp_create); + g_test_add_func("/systemd/lldp/create", test_lldp_create); + g_test_add_func("/systemd/sd-event", test_sd_event); + g_test_add_func("/systemd/test_path_equal", test_path_equal); + g_test_add_func("/systemd/test_nm_sd_utils_unbase64mem", test_nm_sd_utils_unbase64mem); + + return g_test_run(); +} diff --git a/src/core/tests/test-utils.c b/src/core/tests/test-utils.c new file mode 100644 index 0000000..7750109 --- /dev/null +++ b/src/core/tests/test-utils.c @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include +#include + +#include "nm-test-utils-core.h" + +static void +test_stable_privacy(void) +{ + struct in6_addr addr1; + + inet_pton(AF_INET6, "1234::", &addr1); + nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID, + &addr1, + "eth666", + "6b138152-9f3e-4b97-aaf7-e6e553f2a24e", + 0, + (guint8 *) "key", + 3, + NULL); + nmtst_assert_ip6_address(&addr1, "1234::4ceb:14cd:3d54:793f"); + + /* We get an address without the UUID. */ + inet_pton(AF_INET6, "1::", &addr1); + nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID, + &addr1, + "eth666", + "", + 384, + (guint8 *) "key", + 3, + NULL); + nmtst_assert_ip6_address(&addr1, "1::11aa:2530:9144:dafa"); + + /* We get a different address in a different network. */ + inet_pton(AF_INET6, "2::", &addr1); + nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID, + &addr1, + "eth666", + "", + 384, + (guint8 *) "key", + 3, + NULL); + nmtst_assert_ip6_address(&addr1, "2::338e:8d:c11:8726"); + + inet_pton(AF_INET6, "1234::", &addr1); + nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_STABLE_ID, + &addr1, + "eth666", + "6b138152-9f3e-4b97-aaf7-e6e553f2a24e", + 0, + (guint8 *) "key", + 3, + NULL); + nmtst_assert_ip6_address(&addr1, "1234::ad4c:ae44:3d30:af1e"); + + inet_pton(AF_INET6, "1234::", &addr1); + nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_STABLE_ID, + &addr1, + "eth666", + "stable-id-1", + 0, + (guint8 *) "key", + 3, + NULL); + nmtst_assert_ip6_address(&addr1, "1234::4944:67b0:7a6c:1cf"); +} + +/*****************************************************************************/ + +static void +_do_test_hw_addr(NMUtilsStableType stable_type, + const char * stable_id, + const guint8 * secret_key, + gsize key_len, + const char * ifname, + const char * current_mac_address, + const char * generate_mac_address_mask, + const char *const *expected) +{ + gs_free char * generated = NULL; + const char *const *e; + gboolean found = FALSE; + + for (e = expected; *e; e++) { + g_assert(*e); + g_assert(nm_utils_hwaddr_valid(*e, ETH_ALEN)); + } + + generated = nm_utils_hw_addr_gen_stable_eth_impl(stable_type, + stable_id, + secret_key, + key_len, + ifname, + current_mac_address, + generate_mac_address_mask); + + g_assert(generated); + g_assert(nm_utils_hwaddr_valid(generated, ETH_ALEN)); + for (e = expected; *e; e++) { + if (!nm_utils_hwaddr_matches(generated, -1, *e, -1)) + continue; + g_assert(!found); + found = TRUE; + g_assert_cmpstr(generated, ==, *e); + } + g_assert(found); +} +#define do_test_hw_addr(stable_type, \ + stable_id, \ + secret_key, \ + ifname, \ + current_mac_address, \ + generate_mac_address_mask, \ + ...) \ + _do_test_hw_addr((stable_type), \ + (stable_id), \ + (const guint8 *) "" secret_key "", \ + NM_STRLEN(secret_key), \ + (ifname), \ + "" current_mac_address "", \ + generate_mac_address_mask, \ + NM_MAKE_STRV(__VA_ARGS__)) + +static void +test_hw_addr_gen_stable_eth(void) +{ + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + NULL, + "06:0D:CD:0C:9E:2C"); + do_test_hw_addr(NM_UTILS_STABLE_TYPE_STABLE_ID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + NULL, + "C6:AE:A9:9A:76:09"); + + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "FF:FF:FF:00:00:00", + "00:23:45:0C:9E:2C"); + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "03:23:45:67:89:ab", + "FF:FF:FF:00:00:00", + "02:23:45:0C:9E:2C"); + + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "00:00:00:00:00:00", + "06:0D:CD:0C:9E:2C"); + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "02:00:00:00:00:00", + "04:0D:CD:0C:9E:2C"); + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "02:00:00:00:00:00", + "04:0D:CD:0C:9E:2C"); + + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "02:00:00:00:00:00 00:00:00:00:00:00", + "04:0D:CD:0C:9E:2C"); + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "02:00:00:00:00:00 02:00:00:00:00:00", + "06:0D:CD:0C:9E:2C"); + + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "00:00:00:00:00:00 E9:60:CE:F5:ED:2F", + "06:0D:CD:0C:9E:2C"); + + do_test_hw_addr(NM_UTILS_STABLE_TYPE_UUID, + "stable-1", + "key1", + "eth0", + "01:23:45:67:89:ab", + "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00", + "06:0D:CD:0C:9E:2C", + "04:0D:CD:0C:9E:2C"); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_with_logging(&argc, &argv, NULL, "ALL"); + + g_test_add_func("/utils/stable_privacy", test_stable_privacy); + g_test_add_func("/utils/hw_addr_gen_stable_eth", test_hw_addr_gen_stable_eth); + + return g_test_run(); +} diff --git a/src/core/tests/test-wired-defname.c b/src/core/tests/test-wired-defname.c new file mode 100644 index 0000000..7fb8cba --- /dev/null +++ b/src/core/tests/test-wired-defname.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-simple-connection.h" +#include "nm-setting-connection.h" +#include "devices/nm-device-ethernet-utils.h" + +#include "nm-test-utils-core.h" + +static NMConnection * +_new_connection(const char *id) +{ + NMConnection *a; + NMSetting * setting; + + a = nm_simple_connection_new(); + setting = nm_setting_connection_new(); + g_object_set(setting, NM_SETTING_CONNECTION_ID, id, NULL); + nm_connection_add_setting(a, setting); + return a; +} + +/*****************************************************************************/ + +static char * +_get_default_wired_name(GSList *list) +{ + gs_unref_hashtable GHashTable *existing_ids = NULL; + + if (list) { + existing_ids = g_hash_table_new(nm_str_hash, g_str_equal); + for (; list; list = list->next) + g_hash_table_add(existing_ids, (char *) nm_connection_get_id(list->data)); + } + return nm_device_ethernet_utils_get_default_wired_name(existing_ids); +} + +/*****************************************************************************/ + +static void +test_defname_no_connections(void) +{ + gs_free char *name = NULL; + + name = _get_default_wired_name(NULL); + g_assert_cmpstr(name, ==, "Wired connection 1"); +} + +/*****************************************************************************/ + +static void +test_defname_no_conflict(void) +{ + GSList * list = NULL; + gs_free char *name = NULL; + + list = g_slist_append(list, _new_connection("asdfasdfasdfadf")); + list = g_slist_append(list, _new_connection("work wifi")); + list = g_slist_append(list, _new_connection("random gsm connection")); + + name = _get_default_wired_name(list); + g_assert_cmpstr(name, ==, "Wired connection 1"); + + g_slist_free_full(list, g_object_unref); +} + +/*****************************************************************************/ + +static void +test_defname_conflict(void) +{ + GSList * list = NULL; + gs_free char *name = NULL; + + list = g_slist_append(list, _new_connection("asdfasdfasdfadf")); + list = g_slist_append(list, _new_connection("Wired connection 1")); + list = g_slist_append(list, _new_connection("random gsm connection")); + + name = _get_default_wired_name(list); + g_assert_cmpstr(name, ==, "Wired connection 2"); + + g_slist_free_full(list, g_object_unref); +} + +/*****************************************************************************/ + +static void +test_defname_multiple_conflicts(void) +{ + GSList * list = NULL; + gs_free char *name = NULL; + + list = g_slist_append(list, _new_connection("random gsm connection")); + list = g_slist_append(list, _new_connection("home wifi")); + list = g_slist_append(list, _new_connection("Wired connection 1")); + list = g_slist_append(list, _new_connection("Wired connection 2")); + list = g_slist_append(list, _new_connection("Wired connection 3")); + list = g_slist_append(list, _new_connection("work wifi")); + list = g_slist_append(list, _new_connection("a vpn")); + + name = _get_default_wired_name(list); + g_assert_cmpstr(name, ==, "Wired connection 4"); + + g_slist_free_full(list, g_object_unref); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/defname/no_connections", test_defname_no_connections); + g_test_add_func("/defname/no_conflict", test_defname_no_conflict); + g_test_add_func("/defname/conflict", test_defname_conflict); + g_test_add_func("/defname/multiple_conflicts", test_defname_multiple_conflicts); + + return g_test_run(); +} diff --git a/src/core/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c new file mode 100644 index 0000000..b651349 --- /dev/null +++ b/src/core/vpn/nm-vpn-connection.c @@ -0,0 +1,2990 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-vpn-connection.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-proxy-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "platform/nm-platform.h" +#include "nm-active-connection.h" +#include "NetworkManagerUtils.h" +#include "settings/nm-settings-connection.h" +#include "nm-dispatcher.h" +#include "nm-netns.h" +#include "settings/nm-agent-manager.h" +#include "nm-core-internal.h" +#include "nm-pacrunner-manager.h" +#include "nm-firewall-manager.h" +#include "nm-config.h" +#include "nm-vpn-plugin-info.h" +#include "nm-vpn-manager.h" +#include "dns/nm-dns-manager.h" + +typedef enum { + /* Only system secrets */ + SECRETS_REQ_SYSTEM = 0, + /* All existing secrets including agent secrets */ + SECRETS_REQ_EXISTING = 1, + /* New secrets required; ask an agent */ + SECRETS_REQ_NEW = 2, + /* Plugin requests secrets interactively */ + SECRETS_REQ_INTERACTIVE = 3, + /* Placeholder for bounds checking */ + SECRETS_REQ_LAST +} SecretsReq; + +/* Internal VPN states, private to NMVpnConnection */ +typedef enum { + STATE_UNKNOWN = 0, + STATE_WAITING, + STATE_PREPARE, + STATE_NEED_AUTH, + STATE_CONNECT, + STATE_IP_CONFIG_GET, + STATE_PRE_UP, + STATE_ACTIVATED, + STATE_DEACTIVATING, + STATE_DISCONNECTED, + STATE_FAILED, +} VpnState; + +enum { + INTERNAL_STATE_CHANGED, + INTERNAL_RETRY_AFTER_FAILURE, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMVpnConnection, PROP_VPN_STATE, PROP_BANNER, +#define PROP_IP4_CONFIG 2000 +#define PROP_IP6_CONFIG 2001 +#define PROP_MASTER 2002 +); + +typedef struct { + gboolean service_can_persist; + gboolean connection_can_persist; + + NMSettingsConnectionCallId *secrets_id; + SecretsReq secrets_idx; + char * username; + + VpnState vpn_state; + NMDispatcherCallId * dispatcher_id; + NMActiveConnectionStateReason failure_reason; + + NMVpnServiceState service_state; + guint start_timeout; + gboolean service_running; + NMVpnPluginInfo * plugin_info; + char * bus_name; + + NMFirewallManagerCallId *fw_call; + + NMNetns *netns; + + GPtrArray *ip4_dev_route_blacklist; + + GDBusProxy * proxy; + GCancellable * cancellable; + GVariant * connect_hash; + guint connect_timeout; + NMProxyConfig * proxy_config; + NMPacrunnerConfId *pacrunner_conf_id; + gboolean has_ip4; + NMIP4Config * ip4_config; + guint32 ip4_internal_gw; + guint32 ip4_external_gw; + gboolean has_ip6; + NMIP6Config * ip6_config; + + /* These config instances are passed on to NMDevice and modified by NMDevice. + * This pointer is only useful for nm_device_replace_vpn4_config() to clear the + * previous configuration. Consider these instances to be owned by NMDevice. */ + NMIP4Config *last_device_ip4_config; + NMIP6Config *last_device_ip6_config; + + struct in6_addr *ip6_internal_gw; + struct in6_addr *ip6_external_gw; + char * ip_iface; + int ip_ifindex; + char * banner; + guint32 mtu; +} NMVpnConnectionPrivate; + +struct _NMVpnConnection { + NMActiveConnection parent; + NMVpnConnectionPrivate _priv; +}; + +struct _NMVpnConnectionClass { + NMActiveConnectionClass parent; +}; + +G_DEFINE_TYPE(NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION) + +#define NM_VPN_CONNECTION_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMVpnConnection, NM_IS_VPN_CONNECTION, NMActiveConnection) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_vpn_connection; +static const GDBusSignalInfo signal_info_vpn_state_changed; + +static NMSettingsConnection *_get_settings_connection(NMVpnConnection *self, + gboolean allow_missing); + +static void get_secrets(NMVpnConnection *self, SecretsReq secrets_idx, const char *const *hints); + +static guint32 get_route_table(NMVpnConnection *self, int addr_family, gboolean fallback_main); + +static void plugin_interactive_secrets_required(NMVpnConnection * self, + const char * message, + const char *const *secrets); + +static void _set_vpn_state(NMVpnConnection * self, + VpnState vpn_state, + NMActiveConnectionStateReason reason, + gboolean quitting); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_VPN +#define _NMLOG_PREFIX_NAME "vpn-connection" + +#define __NMLOG_prefix_buf_len 128 + +static const char * +__LOG_create_prefix(char *buf, NMVpnConnection *self, NMSettingsConnection *con) +{ + NMVpnConnectionPrivate *priv; + const char * id; + + if (!self) + return _NMLOG_PREFIX_NAME; + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + id = con ? nm_settings_connection_get_id(con) : NULL; + + g_snprintf(buf, + __NMLOG_prefix_buf_len, + "%s[" + "%p" /*self*/ + "%s%s" /*con-uuid*/ + "%s%s%s%s" /*con-id*/ + ",%d" /*ifindex*/ + "%s%s%s" /*iface*/ + "]", + _NMLOG_PREFIX_NAME, + self, + con ? "," : "--", + con ? (nm_settings_connection_get_uuid(con) ?: "??") : "", + con ? "," : "", + NM_PRINT_FMT_QUOTED(id, "\"", id, "\"", con ? "??" : ""), + priv->ip_ifindex, + NM_PRINT_FMT_QUOTED(priv->ip_iface, ":(", priv->ip_iface, ")", "")); + + return buf; +} + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + NMSettingsConnection *_con = (self) ? _get_settings_connection(self, TRUE) : NULL; \ + \ + if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ + char __prefix[__NMLOG_prefix_buf_len]; \ + \ + _nm_log(_level, \ + _NMLOG_DOMAIN, \ + 0, \ + (self) ? NM_VPN_CONNECTION_GET_PRIVATE(self)->ip_iface : NULL, \ + (_con) ? nm_settings_connection_get_uuid(_con) : NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __LOG_create_prefix(__prefix, (self), _con) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +cancel_get_secrets(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->secrets_id) { + nm_settings_connection_cancel_secrets(_get_settings_connection(self, FALSE), + priv->secrets_id); + g_warn_if_fail(!priv->secrets_id); + priv->secrets_id = NULL; + } +} + +static NMVpnConnectionState +_state_to_nm_vpn_state(VpnState state) +{ + switch (state) { + case STATE_WAITING: + case STATE_PREPARE: + return NM_VPN_CONNECTION_STATE_PREPARE; + case STATE_NEED_AUTH: + return NM_VPN_CONNECTION_STATE_NEED_AUTH; + case STATE_CONNECT: + return NM_VPN_CONNECTION_STATE_CONNECT; + case STATE_IP_CONFIG_GET: + case STATE_PRE_UP: + return NM_VPN_CONNECTION_STATE_IP_CONFIG_GET; + case STATE_ACTIVATED: + return NM_VPN_CONNECTION_STATE_ACTIVATED; + case STATE_DEACTIVATING: + { + /* Map DEACTIVATING to ACTIVATED to preserve external API behavior, + * since our API has no DEACTIVATING state of its own. Since this can + * take some time, and the VPN isn't actually disconnected until it + * hits the DISCONNECTED state, to clients it should still appear + * connected. + */ + return NM_VPN_CONNECTION_STATE_ACTIVATED; + } + case STATE_DISCONNECTED: + return NM_VPN_CONNECTION_STATE_DISCONNECTED; + case STATE_FAILED: + return NM_VPN_CONNECTION_STATE_FAILED; + default: + return NM_VPN_CONNECTION_STATE_UNKNOWN; + } +} + +static NMActiveConnectionState +_state_to_ac_state(VpnState vpn_state) +{ + /* Set the NMActiveConnection state based on VPN state */ + switch (vpn_state) { + case STATE_WAITING: + case STATE_PREPARE: + case STATE_NEED_AUTH: + case STATE_CONNECT: + case STATE_IP_CONFIG_GET: + case STATE_PRE_UP: + return NM_ACTIVE_CONNECTION_STATE_ACTIVATING; + case STATE_ACTIVATED: + return NM_ACTIVE_CONNECTION_STATE_ACTIVATED; + case STATE_DEACTIVATING: + return NM_ACTIVE_CONNECTION_STATE_DEACTIVATING; + case STATE_DISCONNECTED: + case STATE_FAILED: + return NM_ACTIVE_CONNECTION_STATE_DEACTIVATED; + default: + break; + } + return NM_ACTIVE_CONNECTION_STATE_UNKNOWN; +} + +static NMSettingsConnection * +_get_settings_connection(NMVpnConnection *self, gboolean allow_missing) +{ + NMSettingsConnection *con; + + /* Currently, we operate on the assumption, that the settings-connection + * never changes after it is set (though initially, it might be unset). + * Later we might want to change that, but then we need fixes here too. */ + + con = _nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(self)); + if (!con && !allow_missing) + g_return_val_if_reached(NULL); + return con; +} + +static NMConnection * +_get_applied_connection(NMVpnConnection *connection) +{ + NMConnection *con; + + con = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(connection)); + g_return_val_if_fail(con, NULL); + return con; +} + +static void +disconnect_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + GVariant *variant; + + variant = g_dbus_proxy_call_finish(proxy, result, NULL); + if (variant) + g_variant_unref(variant); + g_object_unref(user_data); +} + +static void +fw_call_cleanup(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->fw_call) { + nm_firewall_manager_cancel_call(priv->fw_call); + g_warn_if_fail(!priv->fw_call); + priv->fw_call = NULL; + } +} + +static void +remove_parent_device_config(NMVpnConnection *connection, NMDevice *device) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(connection); + + if (priv->last_device_ip4_config) { + nm_device_replace_vpn4_config(device, priv->last_device_ip4_config, NULL); + g_clear_object(&priv->last_device_ip4_config); + } + + if (priv->last_device_ip6_config) { + nm_device_replace_vpn6_config(device, priv->last_device_ip6_config, NULL); + g_clear_object(&priv->last_device_ip6_config); + } +} + +static void +vpn_cleanup(NMVpnConnection *self, NMDevice *parent_dev) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->ip_ifindex) { + NMPlatform *platform = nm_netns_get_platform(priv->netns); + + nm_platform_link_set_down(platform, priv->ip_ifindex); + nm_platform_ip_route_flush(platform, AF_UNSPEC, priv->ip_ifindex); + nm_platform_ip_address_flush(platform, AF_UNSPEC, priv->ip_ifindex); + } + + remove_parent_device_config(self, parent_dev); + + /* Remove zone from firewall */ + if (priv->ip_iface) { + nm_firewall_manager_remove_from_zone(nm_firewall_manager_get(), + priv->ip_iface, + NULL, + NULL, + NULL); + } + /* Cancel pending firewall call */ + fw_call_cleanup(self); + + g_free(priv->banner); + priv->banner = NULL; + + g_free(priv->ip_iface); + priv->ip_iface = NULL; + priv->ip_ifindex = 0; + + g_free(priv->bus_name); + priv->bus_name = NULL; + + /* Clear out connection secrets to ensure that the settings service + * gets asked for them next time the connection is activated. + */ + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(self)); +} + +static void +dispatcher_pre_down_done(NMDispatcherCallId *call_id, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + nm_assert(call_id); + nm_assert(priv->dispatcher_id == call_id); + + priv->dispatcher_id = NULL; + _set_vpn_state(self, + STATE_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED, + FALSE); +} + +static void +dispatcher_pre_up_done(NMDispatcherCallId *call_id, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + nm_assert(call_id); + nm_assert(priv->dispatcher_id == call_id); + + priv->dispatcher_id = NULL; + _set_vpn_state(self, STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); +} + +static void +dispatcher_cleanup(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->dispatcher_id) + nm_dispatcher_call_cancel(g_steal_pointer(&priv->dispatcher_id)); +} + +static void +_set_vpn_state(NMVpnConnection * self, + VpnState vpn_state, + NMActiveConnectionStateReason reason, + gboolean quitting) +{ + NMVpnConnectionPrivate *priv; + VpnState old_vpn_state; + NMVpnConnectionState new_external_state, old_external_state; + NMDevice * parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); + NMConnection *applied; + + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (vpn_state == priv->vpn_state) + return; + + old_vpn_state = priv->vpn_state; + priv->vpn_state = vpn_state; + + /* The device gets destroyed by active connection when it enters + * the deactivated state, so we need to ref it for usage below. + */ + if (parent_dev) + g_object_ref(parent_dev); + + /* Update active connection base class state */ + nm_active_connection_set_state(NM_ACTIVE_CONNECTION(self), + _state_to_ac_state(vpn_state), + reason); + + /* Clear any in-progress secrets request */ + cancel_get_secrets(self); + + dispatcher_cleanup(self); + + /* The connection gets destroyed by the VPN manager when it enters the + * disconnected/failed state, but we need to keep it around for a bit + * to send out signals and handle the dispatcher. So ref it. + */ + g_object_ref(self); + + old_external_state = _state_to_nm_vpn_state(old_vpn_state); + new_external_state = _state_to_nm_vpn_state(priv->vpn_state); + if (new_external_state != old_external_state) { + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_vpn_connection, + &signal_info_vpn_state_changed, + "(uu)", + (guint32) new_external_state, + (guint32) reason); + g_signal_emit(self, + signals[INTERNAL_STATE_CHANGED], + 0, + new_external_state, + old_external_state, + reason); + _notify(self, PROP_VPN_STATE); + } + + switch (vpn_state) { + case STATE_NEED_AUTH: + /* Do nothing; not part of 'default' because we don't want to touch + * priv->secrets_req as NEED_AUTH is re-entered during interactive + * secrets. + */ + break; + case STATE_PRE_UP: + if (!nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_PRE_UP, + _get_settings_connection(self, FALSE), + _get_applied_connection(self), + parent_dev, + priv->ip_iface, + priv->proxy_config, + priv->ip4_config, + priv->ip6_config, + dispatcher_pre_up_done, + self, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + dispatcher_pre_up_done(0, self); + } + break; + case STATE_ACTIVATED: + applied = _get_applied_connection(self); + + /* Secrets no longer needed now that we're connected */ + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(self)); + + /* Let dispatcher scripts know we're up and running */ + nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_UP, + _get_settings_connection(self, FALSE), + applied, + parent_dev, + priv->ip_iface, + priv->proxy_config, + priv->ip4_config, + priv->ip6_config, + NULL, + NULL, + NULL); + + if (priv->proxy_config) { + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + priv->pacrunner_conf_id = nm_pacrunner_manager_add(nm_pacrunner_manager_get(), + priv->proxy_config, + priv->ip_iface, + priv->ip4_config, + priv->ip6_config); + } + break; + case STATE_DEACTIVATING: + applied = _get_applied_connection(self); + if (quitting) { + nm_dispatcher_call_vpn_sync(NM_DISPATCHER_ACTION_VPN_PRE_DOWN, + _get_settings_connection(self, FALSE), + applied, + parent_dev, + priv->ip_iface, + priv->proxy_config, + priv->ip4_config, + priv->ip6_config); + } else { + if (!nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_PRE_DOWN, + _get_settings_connection(self, FALSE), + applied, + parent_dev, + priv->ip_iface, + priv->proxy_config, + priv->ip4_config, + priv->ip6_config, + dispatcher_pre_down_done, + self, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + dispatcher_pre_down_done(0, self); + } + } + + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + break; + case STATE_FAILED: + case STATE_DISCONNECTED: + if (old_vpn_state >= STATE_ACTIVATED && old_vpn_state <= STATE_DEACTIVATING) { + /* Let dispatcher scripts know we're about to go down */ + if (quitting) { + nm_dispatcher_call_vpn_sync(NM_DISPATCHER_ACTION_VPN_DOWN, + _get_settings_connection(self, FALSE), + _get_applied_connection(self), + parent_dev, + priv->ip_iface, + NULL, + NULL, + NULL); + } else { + nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_DOWN, + _get_settings_connection(self, FALSE), + _get_applied_connection(self), + parent_dev, + priv->ip_iface, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + } + } + + /* Tear down and clean up the connection */ + if (priv->proxy) { + g_dbus_proxy_call(priv->proxy, + "Disconnect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) disconnect_cb, + g_object_ref(self)); + } + + vpn_cleanup(self, parent_dev); + /* fall-through */ + default: + priv->secrets_idx = SECRETS_REQ_SYSTEM; + break; + } + + g_object_unref(self); + if (parent_dev) + g_object_unref(parent_dev); +} + +static gboolean +_service_and_connection_can_persist(NMVpnConnection *self) +{ + return NM_VPN_CONNECTION_GET_PRIVATE(self)->connection_can_persist + && NM_VPN_CONNECTION_GET_PRIVATE(self)->service_can_persist; +} + +static gboolean +_connection_only_can_persist(NMVpnConnection *self) +{ + return NM_VPN_CONNECTION_GET_PRIVATE(self)->connection_can_persist + && !NM_VPN_CONNECTION_GET_PRIVATE(self)->service_can_persist; +} + +static void +device_state_changed(NMActiveConnection *active, + NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state) +{ + if (_service_and_connection_can_persist(NM_VPN_CONNECTION(active))) { + if (new_state <= NM_DEVICE_STATE_DISCONNECTED || new_state == NM_DEVICE_STATE_FAILED) { + nm_active_connection_set_device(active, NULL); + } + return; + } + + if (new_state <= NM_DEVICE_STATE_DISCONNECTED) { + _set_vpn_state(NM_VPN_CONNECTION(active), + STATE_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + FALSE); + } else if (new_state == NM_DEVICE_STATE_FAILED) { + _set_vpn_state(NM_VPN_CONNECTION(active), + STATE_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + FALSE); + } + + /* FIXME: map device DEACTIVATING state to VPN DEACTIVATING state and + * block device deactivation on VPN deactivation. + */ +} + +static void +add_ip4_vpn_gateway_route(NMIP4Config *config, + NMDevice * parent_device, + in_addr_t vpn_gw, + NMPlatform * platform) +{ + guint32 parent_gw = 0; + gboolean has_parent_gw = FALSE; + NMPlatformIP4Route route; + int ifindex; + guint32 route_metric; + nm_auto_nmpobj const NMPObject *route_resolved = NULL; + + g_return_if_fail(NM_IS_IP4_CONFIG(config)); + g_return_if_fail(NM_IS_DEVICE(parent_device)); + g_return_if_fail(vpn_gw != 0); + + ifindex = nm_ip4_config_get_ifindex(config); + + nm_assert(ifindex > 0); + nm_assert(ifindex == nm_device_get_ip_ifindex(parent_device)); + + /* Ask kernel how to reach @vpn_gw. We can only inject the route in + * @parent_device, so whatever we resolve, it can only be on @ifindex. */ + if (nm_platform_ip_route_get(platform, + AF_INET, + &vpn_gw, + ifindex, + (NMPObject **) &route_resolved) + >= 0) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(route_resolved); + + if (r->ifindex == ifindex) { + const NMPObject *obj; + + /* `ip route get` always resolves the route, even if the destination is unreachable. + * In which case, it pretends the destination is directly reachable. + * + * So, only accept direct routes if @vpn_gw is a private network + * or if the parent device also has a direct default route */ + if (nm_platform_route_table_is_main(r->table_coerced)) { + if (r->gateway) { + parent_gw = r->gateway; + has_parent_gw = TRUE; + } else if (nm_utils_ip_is_site_local(AF_INET, &vpn_gw)) { + has_parent_gw = TRUE; + } else if ((obj = nm_device_get_best_default_route(parent_device, AF_INET)) + && !NMP_OBJECT_CAST_IP4_ROUTE(obj)->gateway) { + has_parent_gw = TRUE; + } + } + } + } + + if (!has_parent_gw) + return; + + route_metric = nm_device_get_route_metric(parent_device, AF_INET); + + memset(&route, 0, sizeof(route)); + route.ifindex = ifindex; + route.network = vpn_gw; + route.plen = 32; + route.gateway = parent_gw; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + route.metric = route_metric; + nm_ip4_config_add_route(config, &route, NULL); + + if (parent_gw) { + /* Ensure there's a route to the parent device's gateway through the + * parent device, since if the VPN claims the default route and the VPN + * routes include a subnet that matches the parent device's subnet, + * the parent device's gateway would get routed through the VPN and fail. + */ + memset(&route, 0, sizeof(route)); + route.network = parent_gw; + route.plen = 32; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + route.metric = route_metric; + nm_ip4_config_add_route(config, &route, NULL); + } +} + +static void +add_ip6_vpn_gateway_route(NMIP6Config * config, + NMDevice * parent_device, + const struct in6_addr *vpn_gw, + NMPlatform * platform) +{ + const struct in6_addr *parent_gw = NULL; + gboolean has_parent_gw = FALSE; + NMPlatformIP6Route route; + int ifindex; + guint32 route_metric; + nm_auto_nmpobj const NMPObject *route_resolved = NULL; + + g_return_if_fail(NM_IS_IP6_CONFIG(config)); + g_return_if_fail(NM_IS_DEVICE(parent_device)); + g_return_if_fail(vpn_gw != NULL); + + ifindex = nm_ip6_config_get_ifindex(config); + + nm_assert(ifindex > 0); + nm_assert(ifindex == nm_device_get_ip_ifindex(parent_device)); + + /* Ask kernel how to reach @vpn_gw. We can only inject the route in + * @parent_device, so whatever we resolve, it can only be on @ifindex. */ + if (nm_platform_ip_route_get(platform, + AF_INET6, + vpn_gw, + ifindex, + (NMPObject **) &route_resolved) + >= 0) { + const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE(route_resolved); + + if (r->ifindex == ifindex) { + const NMPObject *obj; + + /* `ip route get` always resolves the route, even if the destination is unreachable. + * In which case, it pretends the destination is directly reachable. + * + * So, only accept direct routes if @vpn_gw is a private network + * or if the parent device also has a direct default route */ + if (nm_platform_route_table_is_main(r->table_coerced)) { + if (!IN6_IS_ADDR_UNSPECIFIED(&r->gateway)) { + parent_gw = &r->gateway; + has_parent_gw = TRUE; + } else if (nm_utils_ip_is_site_local(AF_INET6, &vpn_gw)) { + has_parent_gw = TRUE; + } else if ((obj = nm_device_get_best_default_route(parent_device, AF_INET6)) + && IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->gateway)) { + has_parent_gw = TRUE; + } + } + } + } + + if (!has_parent_gw) + return; + + route_metric = nm_device_get_route_metric(parent_device, AF_INET6); + + memset(&route, 0, sizeof(route)); + route.ifindex = ifindex; + route.network = *vpn_gw; + route.plen = 128; + if (parent_gw) + route.gateway = *parent_gw; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + route.metric = route_metric; + nm_ip6_config_add_route(config, &route, NULL); + + /* Ensure there's a route to the parent device's gateway through the + * parent device, since if the VPN claims the default route and the VPN + * routes include a subnet that matches the parent device's subnet, + * the parent device's gateway would get routed through the VPN and fail. + */ + if (parent_gw && !IN6_IS_ADDR_UNSPECIFIED(parent_gw)) { + memset(&route, 0, sizeof(route)); + route.network = *parent_gw; + route.plen = 128; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + route.metric = route_metric; + nm_ip6_config_add_route(config, &route, NULL); + } +} + +NMVpnConnection * +nm_vpn_connection_new(NMSettingsConnection * settings_connection, + NMDevice * parent_device, + const char * specific_object, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + NMAuthSubject * subject) +{ + g_return_val_if_fail(!settings_connection || NM_IS_SETTINGS_CONNECTION(settings_connection), + NULL); + g_return_val_if_fail(NM_IS_DEVICE(parent_device), NULL); + g_return_val_if_fail(specific_object, NULL); + + return g_object_new(NM_TYPE_VPN_CONNECTION, + NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, + settings_connection, + NM_ACTIVE_CONNECTION_INT_DEVICE, + parent_device, + NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, + specific_object, + NM_ACTIVE_CONNECTION_INT_SUBJECT, + subject, + NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, + activation_reason, + NM_ACTIVE_CONNECTION_VPN, + TRUE, + NM_ACTIVE_CONNECTION_STATE_FLAGS, + (guint) initial_state_flags, + NULL); +} + +const char * +nm_vpn_connection_get_service(NMVpnConnection *self) +{ + NMSettingVpn *s_vpn; + + s_vpn = nm_connection_get_setting_vpn(_get_applied_connection(self)); + return nm_setting_vpn_get_service_type(s_vpn); +} + +static NM_UTILS_LOOKUP_STR_DEFINE( + _vpn_plugin_failure_to_string, + NMVpnPluginFailure, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED, "login-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED, "connect-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG, "bad-ip-config"), ); + +#define vpn_plugin_failure_to_string_a(failure) \ + NM_UTILS_LOOKUP_STR_A(_vpn_plugin_failure_to_string, failure) + +static void +plugin_failed(NMVpnConnection *self, guint reason) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + _LOGW("VPN plugin: failed: %s (%d)", vpn_plugin_failure_to_string_a(reason), reason); + + switch (reason) { + case NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED: + priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED; + break; + case NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG: + priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID; + break; + default: + priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN; + break; + } +} + +static NM_UTILS_LOOKUP_STR_DEFINE( + _vpn_service_state_to_string, + NMVpnServiceState, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_INIT, "init"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_SHUTDOWN, "shutdown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STARTING, "starting"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STARTED, "started"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STOPPING, "stopping"), + NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STOPPED, "stopped"), ); + +#define vpn_service_state_to_string_a(state) \ + NM_UTILS_LOOKUP_STR_A(_vpn_service_state_to_string, state) + +static NM_UTILS_LOOKUP_STR_DEFINE(_vpn_state_to_string, + VpnState, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(STATE_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_WAITING, "waiting"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_PREPARE, "prepare"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_NEED_AUTH, "need-auth"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_CONNECT, "connect"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_IP_CONFIG_GET, "ip-config-get"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_PRE_UP, "pre-up"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_ACTIVATED, "activated"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_DEACTIVATING, "deactivating"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_DISCONNECTED, "disconnected"), + NM_UTILS_LOOKUP_STR_ITEM(STATE_FAILED, "failed"), ); + +#define vpn_state_to_string_a(state) NM_UTILS_LOOKUP_STR_A(_vpn_state_to_string, state) + +static void +plugin_state_changed(NMVpnConnection *self, NMVpnServiceState new_service_state) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMVpnServiceState old_service_state = priv->service_state; + + _LOGI("VPN plugin: state changed: %s (%d)", + vpn_service_state_to_string_a(new_service_state), + new_service_state); + priv->service_state = new_service_state; + + if (new_service_state == NM_VPN_SERVICE_STATE_STOPPED) { + /* Clear connection secrets to ensure secrets get requested each time the + * connection is activated. + */ + nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(self)); + + if ((priv->vpn_state >= STATE_WAITING) && (priv->vpn_state <= STATE_ACTIVATED)) { + VpnState old_state = priv->vpn_state; + + _set_vpn_state(self, STATE_FAILED, priv->failure_reason, FALSE); + + /* Reset the failure reason */ + priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN; + + /* If the connection failed, the service cannot persist, but the + * connection can persist, ask listeners to re-activate the connection. + */ + if (old_state == STATE_ACTIVATED && priv->vpn_state == STATE_FAILED + && _connection_only_can_persist(self)) + g_signal_emit(self, signals[INTERNAL_RETRY_AFTER_FAILURE], 0); + } + } else if (new_service_state == NM_VPN_SERVICE_STATE_STARTING + && old_service_state == NM_VPN_SERVICE_STATE_STARTED) { + /* The VPN service got disconnected and is attempting to reconnect */ + _set_vpn_state(self, + STATE_CONNECT, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, + FALSE); + } +} + +static void +print_vpn_config(NMVpnConnection *self) +{ + NMVpnConnectionPrivate * priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + const NMPlatformIP4Address *address4; + const NMPlatformIP6Address *address6; + char * dns_domain = NULL; + guint32 num, i; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + NMDedupMultiIter ipconf_iter; + + if (priv->ip4_external_gw) { + _LOGI("Data: VPN Gateway: %s", _nm_utils_inet4_ntop(priv->ip4_external_gw, b1)); + } else if (priv->ip6_external_gw) { + _LOGI("Data: VPN Gateway: %s", _nm_utils_inet6_ntop(priv->ip6_external_gw, b1)); + } + + _LOGI("Data: Tunnel Device: %s%s%s", NM_PRINT_FMT_QUOTE_STRING(priv->ip_iface)); + + if (priv->ip4_config) { + const NMPlatformIP4Route *route; + + _LOGI("Data: IPv4 configuration:"); + + address4 = nm_ip4_config_get_first_address(priv->ip4_config); + nm_assert(address4); + + if (priv->ip4_internal_gw) + _LOGI("Data: Internal Gateway: %s", _nm_utils_inet4_ntop(priv->ip4_internal_gw, b1)); + _LOGI("Data: Internal Address: %s", + address4 ? _nm_utils_inet4_ntop(address4->address, b1) : "??"); + _LOGI("Data: Internal Prefix: %d", address4 ? (int) address4->plen : -1); + _LOGI("Data: Internal Point-to-Point Address: %s", + _nm_utils_inet4_ntop(address4->peer_address, b1)); + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route) { + _LOGI("Data: Static Route: %s/%d Next Hop: %s", + _nm_utils_inet4_ntop(route->network, b1), + route->plen, + _nm_utils_inet4_ntop(route->gateway, b2)); + } + + num = nm_ip4_config_get_num_nameservers(priv->ip4_config); + for (i = 0; i < num; i++) { + _LOGI("Data: Internal DNS: %s", + _nm_utils_inet4_ntop(nm_ip4_config_get_nameserver(priv->ip4_config, i), b1)); + } + + if (nm_ip4_config_get_num_domains(priv->ip4_config) > 0) + dns_domain = (char *) nm_ip4_config_get_domain(priv->ip4_config, 0); + + _LOGI("Data: DNS Domain: '%s'", dns_domain ?: "(none)"); + } else + _LOGI("Data: No IPv4 configuration"); + + if (priv->ip6_config) { + const NMPlatformIP6Route *route; + + _LOGI("Data: IPv6 configuration:"); + + address6 = nm_ip6_config_get_first_address(priv->ip6_config); + nm_assert(address6); + + if (priv->ip6_internal_gw) + _LOGI("Data: Internal Gateway: %s", _nm_utils_inet6_ntop(priv->ip6_internal_gw, b1)); + _LOGI("Data: Internal Address: %s", _nm_utils_inet6_ntop(&address6->address, b1)); + _LOGI("Data: Internal Prefix: %d", address6->plen); + _LOGI("Data: Internal Point-to-Point Address: %s", + _nm_utils_inet6_ntop(&address6->peer_address, b1)); + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route) { + _LOGI("Data: Static Route: %s/%d Next Hop: %s", + _nm_utils_inet6_ntop(&route->network, b1), + route->plen, + _nm_utils_inet6_ntop(&route->gateway, b2)); + } + + num = nm_ip6_config_get_num_nameservers(priv->ip6_config); + for (i = 0; i < num; i++) { + _LOGI("Data: Internal DNS: %s", + _nm_utils_inet6_ntop(nm_ip6_config_get_nameserver(priv->ip6_config, i), b1)); + } + + if (nm_ip6_config_get_num_domains(priv->ip6_config) > 0) + dns_domain = (char *) nm_ip6_config_get_domain(priv->ip6_config, 0); + + _LOGI("Data: DNS Domain: '%s'", dns_domain ?: "(none)"); + } else + _LOGI("Data: No IPv6 configuration"); + + if (priv->banner && strlen(priv->banner)) { + _LOGI("Data: Login Banner:"); + _LOGI("Data: -----------------------------------------"); + _LOGI("Data: %s", priv->banner); + _LOGI("Data: -----------------------------------------"); + } +} + +static void +apply_parent_device_config(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMDevice * parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); + int ifindex; + NMIP4Config *vpn4_parent_config = NULL; + NMIP6Config *vpn6_parent_config = NULL; + + ifindex = nm_device_get_ip_ifindex(parent_dev); + if (ifindex > 0) { + /* If the VPN didn't return a network interface, it is a route-based + * VPN (like kernel IPSec) and all IP addressing and routing should + * be done on the parent interface instead. + */ + if (priv->ip4_config) { + vpn4_parent_config = nm_ip4_config_new(nm_netns_get_multi_idx(priv->netns), ifindex); + if (priv->ip_ifindex <= 0) + nm_ip4_config_merge(vpn4_parent_config, + priv->ip4_config, + NM_IP_CONFIG_MERGE_NO_DNS, + 0); + } + if (priv->ip6_config) { + vpn6_parent_config = nm_ip6_config_new(nm_netns_get_multi_idx(priv->netns), ifindex); + if (priv->ip_ifindex <= 0) + nm_ip6_config_merge(vpn6_parent_config, + priv->ip6_config, + NM_IP_CONFIG_MERGE_NO_DNS, + 0); + } + } + + /* Add any explicit route to the VPN gateway through the parent device */ + if (vpn4_parent_config && priv->ip4_external_gw) { + add_ip4_vpn_gateway_route(vpn4_parent_config, + parent_dev, + priv->ip4_external_gw, + nm_netns_get_platform(priv->netns)); + } + if (vpn6_parent_config && priv->ip6_external_gw) { + add_ip6_vpn_gateway_route(vpn6_parent_config, + parent_dev, + priv->ip6_external_gw, + nm_netns_get_platform(priv->netns)); + } + + nm_device_replace_vpn4_config(parent_dev, priv->last_device_ip4_config, vpn4_parent_config); + g_clear_object(&priv->last_device_ip4_config); + priv->last_device_ip4_config = vpn4_parent_config; + + nm_device_replace_vpn6_config(parent_dev, priv->last_device_ip6_config, vpn6_parent_config); + g_clear_object(&priv->last_device_ip6_config); + priv->last_device_ip6_config = vpn6_parent_config; +} + +static gboolean +nm_vpn_connection_apply_config(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + apply_parent_device_config(self); + + if (priv->ip_ifindex > 0) { + nm_platform_link_set_up(nm_netns_get_platform(priv->netns), priv->ip_ifindex, NULL); + + if (priv->ip4_config) { + nm_assert(priv->ip_ifindex == nm_ip4_config_get_ifindex(priv->ip4_config)); + if (!nm_ip4_config_commit(priv->ip4_config, + nm_netns_get_platform(priv->netns), + get_route_table(self, AF_INET, FALSE) + ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL + : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN)) + return FALSE; + nm_platform_ip4_dev_route_blacklist_set(nm_netns_get_platform(priv->netns), + priv->ip_ifindex, + priv->ip4_dev_route_blacklist); + } + + if (priv->ip6_config) { + nm_assert(priv->ip_ifindex == nm_ip6_config_get_ifindex(priv->ip6_config)); + if (!nm_ip6_config_commit(priv->ip6_config, + nm_netns_get_platform(priv->netns), + get_route_table(self, AF_INET6, FALSE) + ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL + : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, + NULL)) + return FALSE; + } + + if (priv->mtu + && priv->mtu + != nm_platform_link_get_mtu(nm_netns_get_platform(priv->netns), + priv->ip_ifindex)) + nm_platform_link_set_mtu(nm_netns_get_platform(priv->netns), + priv->ip_ifindex, + priv->mtu); + } + + _LOGI("VPN connection: (IP Config Get) complete"); + if (priv->vpn_state < STATE_PRE_UP) + _set_vpn_state(self, STATE_PRE_UP, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + return TRUE; +} + +static void +_cleanup_failed_config(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + nm_dbus_object_clear_and_unexport(&priv->ip4_config); + nm_dbus_object_clear_and_unexport(&priv->ip6_config); + + _LOGW("VPN connection: did not receive valid IP config information"); + _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, FALSE); +} + +static void +fw_change_zone_cb(NMFirewallManager * firewall_manager, + NMFirewallManagerCallId *call_id, + GError * error, + gpointer user_data) +{ + NMVpnConnection * self = user_data; + NMVpnConnectionPrivate *priv; + + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + g_return_if_fail(priv->fw_call == call_id); + + priv->fw_call = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (error) { + // FIXME: fail the activation? + } + + if (!nm_vpn_connection_apply_config(self)) + _cleanup_failed_config(self); +} + +static void +nm_vpn_connection_config_maybe_complete(NMVpnConnection *self, gboolean success) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMConnection * base_con; + NMSettingConnection * s_con; + const char * zone; + + if (priv->vpn_state < STATE_IP_CONFIG_GET || priv->vpn_state > STATE_ACTIVATED) + return; + + if (success) { + if ((priv->has_ip4 && !priv->ip4_config) || (priv->has_ip6 && !priv->ip6_config)) { + /* Need to wait for other config */ + return; + } + } + + nm_clear_g_source(&priv->connect_timeout); + + if (success) { + print_vpn_config(self); + + /* Add the tunnel interface to the specified firewall zone */ + if (priv->ip_iface) { + base_con = _get_applied_connection(self); + s_con = nm_connection_get_setting_connection(base_con); + zone = nm_setting_connection_get_zone(s_con); + + _LOGD("setting firewall zone %s%s%s for '%s'", + NM_PRINT_FMT_QUOTED(zone, "'", zone, "'", "(default)"), + priv->ip_iface); + fw_call_cleanup(self); + priv->fw_call = nm_firewall_manager_add_or_change_zone(nm_firewall_manager_get(), + priv->ip_iface, + zone, + FALSE, + fw_change_zone_cb, + self); + return; + } else if (nm_vpn_connection_apply_config(self)) + return; + } + + _cleanup_failed_config(self); +} + +static gboolean +ip6_addr_from_variant(GVariant *v, struct in6_addr *addr) +{ + const guint8 *bytes; + gsize len; + + g_return_val_if_fail(v, FALSE); + g_return_val_if_fail(addr, FALSE); + + if (g_variant_is_of_type(v, G_VARIANT_TYPE("ay"))) { + bytes = g_variant_get_fixed_array(v, &len, sizeof(guint8)); + if (len == sizeof(struct in6_addr) && !IN6_IS_ADDR_UNSPECIFIED(bytes)) { + memcpy(addr, bytes, len); + return TRUE; + } + } + return FALSE; +} + +static struct in6_addr * +ip6_addr_dup_from_variant(GVariant *v) +{ + struct in6_addr *addr; + + addr = g_malloc0(sizeof(*addr)); + if (ip6_addr_from_variant(v, addr)) + return addr; + g_free(addr); + return NULL; +} + +static gboolean +process_generic_config(NMVpnConnection *self, GVariant *dict) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + const char * str; + GVariant * v; + guint32 u32; + gboolean b; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CAN_PERSIST, "b", &b) && b) { + /* Defaults to FALSE, so only let service indicate TRUE */ + priv->service_can_persist = TRUE; + } + + nm_clear_g_free(&priv->ip_iface); + priv->ip_ifindex = 0; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_TUNDEV, "&s", &str)) { + /* Backwards compat with NM-openswan */ + if (g_strcmp0(str, "_none_") != 0) + priv->ip_iface = g_strdup(str); + } + + if (priv->ip_iface) { + /* Grab the interface index for address/routing operations */ + priv->ip_ifindex = + nm_platform_link_get_ifindex(nm_netns_get_platform(priv->netns), priv->ip_iface); + if (priv->ip_ifindex <= 0) { + nm_platform_process_events(nm_netns_get_platform(priv->netns)); + priv->ip_ifindex = + nm_platform_link_get_ifindex(nm_netns_get_platform(priv->netns), priv->ip_iface); + } + if (priv->ip_ifindex <= 0) { + _LOGE("failed to look up VPN interface index for \"%s\"", priv->ip_iface); + nm_clear_g_free(&priv->ip_iface); + priv->ip_ifindex = 0; + nm_vpn_connection_config_maybe_complete(self, FALSE); + return FALSE; + } + } + + nm_clear_g_free(&priv->banner); + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_BANNER, "&s", &str)) { + priv->banner = g_strdup(str); + _notify(self, PROP_BANNER); + } + + /* Proxy Config */ + g_clear_object(&priv->proxy_config); + priv->proxy_config = nm_proxy_config_new(); + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_PROXY_PAC, "&s", &str)) { + nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_AUTO); + nm_proxy_config_set_pac_url(priv->proxy_config, str); + } else + nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_NONE); + + /* User overrides if any from the NMConnection's Proxy settings */ + nm_proxy_config_merge_setting(priv->proxy_config, + nm_connection_get_setting_proxy(_get_applied_connection(self))); + + /* External world-visible address of the VPN server */ + priv->ip4_external_gw = 0; + nm_clear_g_free(&priv->ip6_external_gw); + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, "u", &u32)) { + priv->ip4_external_gw = u32; + } else if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, "@ay", &v)) { + priv->ip6_external_gw = ip6_addr_dup_from_variant(v); + g_variant_unref(v); + + if (!priv->ip6_external_gw) { + _LOGE("Invalid IPv6 VPN gateway address received"); + nm_vpn_connection_config_maybe_complete(self, FALSE); + return FALSE; + } + } + + priv->mtu = 0; + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_MTU, "u", &u32)) + priv->mtu = u32; + + return TRUE; +} + +static void +nm_vpn_connection_config_get(NMVpnConnection *self, GVariant *dict) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + gboolean b; + + g_return_if_fail(dict && g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)); + + _LOGI("VPN connection: (IP Config Get) reply received."); + + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state(self, STATE_IP_CONFIG_GET, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + + if (!process_generic_config(self, dict)) + return; + + /* Note whether to expect IPv4 and IPv6 configs */ + priv->has_ip4 = FALSE; + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_HAS_IP4, "b", &b)) + priv->has_ip4 = b; + nm_dbus_object_clear_and_unexport(&priv->ip4_config); + + priv->has_ip6 = FALSE; + if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_HAS_IP6, "b", &b)) + priv->has_ip6 = b; + nm_dbus_object_clear_and_unexport(&priv->ip6_config); + + nm_vpn_connection_config_maybe_complete(self, TRUE); +} + +guint32 +nm_vpn_connection_get_ip4_route_metric(NMVpnConnection *self) +{ + gint64 route_metric; + NMConnection *applied; + + applied = _get_applied_connection(self); + route_metric = + nm_setting_ip_config_get_route_metric(nm_connection_get_setting_ip4_config(applied)); + + return (route_metric >= 0) ? route_metric : NM_VPN_ROUTE_METRIC_DEFAULT; +} + +guint32 +nm_vpn_connection_get_ip6_route_metric(NMVpnConnection *self) +{ + gint64 route_metric; + NMConnection *applied; + + applied = _get_applied_connection(self); + route_metric = + nm_setting_ip_config_get_route_metric(nm_connection_get_setting_ip6_config(applied)); + + return (route_metric >= 0) ? route_metric : NM_VPN_ROUTE_METRIC_DEFAULT; +} + +static guint32 +get_route_table(NMVpnConnection *self, int addr_family, gboolean fallback_main) +{ + NMConnection * connection; + NMSettingIPConfig *s_ip; + guint32 route_table = 0; + + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + connection = _get_applied_connection(self); + if (connection) { + s_ip = nm_connection_get_setting_ip_config(connection, addr_family); + if (s_ip) + route_table = nm_setting_ip_config_get_route_table(s_ip); + } + + return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0); +} + +static gboolean +_is_device_vrf(NMVpnConnection *self) +{ + NMDevice *parent; + NMDevice *master; + + parent = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); + if (!parent) + return FALSE; + + master = nm_device_get_master(parent); + return master && nm_device_get_link_type(master) == NM_LINK_TYPE_VRF; +} + +static void +nm_vpn_connection_ip4_config_get(NMVpnConnection *self, GVariant *dict) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMPlatformIP4Address address; + guint32 u32, route_metric; + NMSettingIPConfig * s_ip; + NMSettingConnection * s_con; + guint32 route_table; + NMIP4Config * config; + GVariantIter * iter; + const char * str; + GVariant * v; + gboolean b; + int ip_ifindex; + guint32 mss = 0; + gboolean never_default = FALSE; + + g_return_if_fail(dict && g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)); + + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state(self, STATE_IP_CONFIG_GET, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + + if (priv->vpn_state > STATE_ACTIVATED) { + _LOGI("VPN connection: (IP4 Config Get) ignoring, the connection is no longer active"); + return; + } + + if (priv->has_ip4) { + _LOGI("VPN connection: (IP4 Config Get) reply received"); + + if (g_variant_n_children(dict) == 0) { + priv->has_ip4 = FALSE; + nm_vpn_connection_config_maybe_complete(self, TRUE); + return; + } + } else { + _LOGI("VPN connection: (IP4 Config Get) reply received from old-style plugin"); + + /* In the old API, the generic and IPv4 configuration items + * were mixed together. + */ + if (!process_generic_config(self, dict)) + return; + + priv->has_ip4 = TRUE; + priv->has_ip6 = FALSE; + } + + ip_ifindex = nm_vpn_connection_get_ip_ifindex(self, TRUE); + if (ip_ifindex <= 0) + g_return_if_reached(); + + config = nm_ip4_config_new(nm_netns_get_multi_idx(priv->netns), ip_ifindex); + nm_ip4_config_set_dns_priority(config, NM_DNS_PRIORITY_DEFAULT_VPN); + + memset(&address, 0, sizeof(address)); + address.plen = 24; + + /* Internal address of the VPN subnet's gateway */ + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, "u", &u32)) + priv->ip4_internal_gw = u32; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, "u", &u32)) + address.address = u32; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_PTP, "u", &u32)) + address.peer_address = u32; + else + address.peer_address = address.address; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, "u", &u32)) + address.plen = u32; + + if (address.address && address.plen && address.plen <= 32) { + address.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip4_config_add_address(config, &address); + } else { + _LOGW("invalid IP4 config received!"); + g_object_unref(config); + nm_vpn_connection_config_maybe_complete(self, FALSE); + return; + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_DNS, "au", &iter)) { + while (g_variant_iter_next(iter, "u", &u32)) + nm_ip4_config_add_nameserver(config, u32); + g_variant_iter_free(iter); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_NBNS, "au", &iter)) { + while (g_variant_iter_next(iter, "u", &u32)) + nm_ip4_config_add_wins(config, u32); + g_variant_iter_free(iter); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_MSS, "u", &u32)) + mss = u32; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, "&s", &str)) + nm_ip4_config_add_domain(config, str); + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS, "as", &iter)) { + while (g_variant_iter_next(iter, "&s", &str)) + nm_ip4_config_add_domain(config, str); + g_variant_iter_free(iter); + } + + route_table = get_route_table(self, AF_INET, TRUE); + route_metric = nm_vpn_connection_get_ip4_route_metric(self); + s_ip = nm_connection_get_setting_ip4_config(_get_applied_connection(self)); + s_con = nm_connection_get_setting_connection(_get_applied_connection(self)); + + if (nm_setting_ip_config_get_ignore_auto_routes(s_ip)) { + /* ignore VPN routes */ + } else if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_PRESERVE_ROUTES, "b", &b) && b) { + if (priv->ip4_config) { + NMDedupMultiIter ipconf_iter; + const NMPlatformIP4Route *route; + + nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route) + nm_ip4_config_add_route(config, route, NULL); + } + } else if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, "aau", &iter)) { + while (g_variant_iter_next(iter, "@au", &v)) { + NMPlatformIP4Route route = { + 0, + }; + guint32 plen; + + switch (g_variant_n_children(v)) { + case 5: + g_variant_get_child(v, 4, "u", &route.pref_src); + /* fall-through */ + case 4: + g_variant_get_child(v, 0, "u", &route.network); + g_variant_get_child(v, 1, "u", &plen); + g_variant_get_child(v, 2, "u", &route.gateway); + /* 4th item is unused route metric */ + route.table_coerced = nm_platform_route_table_coerce(route_table); + route.metric = route_metric; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + + if (plen > 32 || plen == 0) + break; + route.plen = plen; + route.network = nm_utils_ip4_address_clear_host_address(route.network, plen); + + if (priv->ip4_external_gw && route.network == priv->ip4_external_gw + && route.plen == 32) { + /* Ignore host routes to the VPN gateway since NM adds one itself + * below. Since NM knows more about the routing situation than + * the VPN server, we want to use the NM created route instead of + * whatever the server provides. + */ + break; + } + + nm_ip4_config_add_route(config, &route, NULL); + break; + default: + break; + } + g_variant_unref(v); + } + g_variant_iter_free(iter); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, "b", &b)) + never_default = b; + + /* Merge in user overrides from the NMConnection's IPv4 setting */ + nm_ip4_config_merge_setting(config, + s_ip, + nm_setting_connection_get_mdns(s_con), + nm_setting_connection_get_llmnr(s_con), + route_table, + route_metric); + + if (!never_default && !nm_setting_ip_config_get_never_default(s_ip)) { + const NMPlatformIP4Route r = { + .ifindex = ip_ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_VPN, + .gateway = priv->ip4_internal_gw, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .mss = mss, + }; + + nm_ip4_config_add_route(config, &r, NULL); + } + + nm_clear_pointer(&priv->ip4_dev_route_blacklist, g_ptr_array_unref); + + nm_ip4_config_add_dependent_routes(config, + route_table, + nm_vpn_connection_get_ip4_route_metric(self), + _is_device_vrf(self), + &priv->ip4_dev_route_blacklist); + + if (priv->ip4_config) { + nm_ip4_config_replace(priv->ip4_config, config, NULL); + g_object_unref(config); + } else { + priv->ip4_config = config; + nm_dbus_object_export(NM_DBUS_OBJECT(config)); + g_object_notify((GObject *) self, NM_ACTIVE_CONNECTION_IP4_CONFIG); + } + + nm_vpn_connection_config_maybe_complete(self, TRUE); +} + +static void +nm_vpn_connection_ip6_config_get(NMVpnConnection *self, GVariant *dict) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMPlatformIP6Address address; + guint32 u32, route_metric; + NMSettingIPConfig * s_ip; + guint32 route_table; + NMIP6Config * config; + GVariantIter * iter; + const char * str; + GVariant * v; + gboolean b; + int ip_ifindex; + guint32 mss = 0; + gboolean never_default = FALSE; + + g_return_if_fail(dict && g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)); + + _LOGI("VPN connection: (IP6 Config Get) reply received"); + + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state(self, STATE_IP_CONFIG_GET, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + + if (priv->vpn_state > STATE_ACTIVATED) { + _LOGI("VPN connection: (IP6 Config Get) ignoring, the connection is no longer active"); + return; + } + + if (g_variant_n_children(dict) == 0) { + priv->has_ip6 = FALSE; + nm_vpn_connection_config_maybe_complete(self, TRUE); + return; + } + + ip_ifindex = nm_vpn_connection_get_ip_ifindex(self, TRUE); + if (ip_ifindex <= 0) + g_return_if_reached(); + + config = nm_ip6_config_new(nm_netns_get_multi_idx(priv->netns), ip_ifindex); + nm_ip6_config_set_dns_priority(config, NM_DNS_PRIORITY_DEFAULT_VPN); + + memset(&address, 0, sizeof(address)); + address.plen = 128; + + /* Internal address of the VPN subnet's gateway */ + nm_clear_g_free(&priv->ip6_internal_gw); + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, "@ay", &v)) { + priv->ip6_internal_gw = ip6_addr_dup_from_variant(v); + g_variant_unref(v); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, "@ay", &v)) { + ip6_addr_from_variant(v, &address.address); + g_variant_unref(v); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_PTP, "@ay", &v)) { + ip6_addr_from_variant(v, &address.peer_address); + g_variant_unref(v); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_PREFIX, "u", &u32)) + address.plen = u32; + + if (!IN6_IS_ADDR_UNSPECIFIED(&address.address) && address.plen && address.plen <= 128) { + address.addr_source = NM_IP_CONFIG_SOURCE_VPN; + nm_ip6_config_add_address(config, &address); + } else { + _LOGW("invalid IP6 config received!"); + g_object_unref(config); + nm_vpn_connection_config_maybe_complete(self, FALSE); + return; + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_DNS, "aay", &iter)) { + while (g_variant_iter_next(iter, "@ay", &v)) { + struct in6_addr dns; + + if (ip6_addr_from_variant(v, &dns)) + nm_ip6_config_add_nameserver(config, &dns); + g_variant_unref(v); + } + g_variant_iter_free(iter); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_MSS, "u", &u32)) + mss = u32; + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, "&s", &str)) + nm_ip6_config_add_domain(config, str); + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_DOMAINS, "as", &iter)) { + while (g_variant_iter_next(iter, "&s", &str)) + nm_ip6_config_add_domain(config, str); + g_variant_iter_free(iter); + } + + route_table = get_route_table(self, AF_INET6, TRUE); + route_metric = nm_vpn_connection_get_ip6_route_metric(self); + s_ip = nm_connection_get_setting_ip6_config(_get_applied_connection(self)); + + if (nm_setting_ip_config_get_ignore_auto_routes(s_ip)) { + /* Ignore VPN routes */ + } else if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_PRESERVE_ROUTES, "b", &b) && b) { + if (priv->ip6_config) { + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Route *route; + + nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route) + nm_ip6_config_add_route(config, route, NULL); + } + } else if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, "a(ayuayu)", &iter)) { + GVariant *dest, *next_hop; + guint32 prefix, metric; + + while (g_variant_iter_next(iter, "(@ayu@ayu)", &dest, &prefix, &next_hop, &metric)) { + NMPlatformIP6Route route; + + memset(&route, 0, sizeof(route)); + + if (!ip6_addr_from_variant(dest, &route.network)) + goto next; + + if (prefix > 128 || prefix == 0) + goto next; + + route.plen = prefix; + ip6_addr_from_variant(next_hop, &route.gateway); + route.table_coerced = nm_platform_route_table_coerce(route_table); + route.metric = route_metric; + route.rt_source = NM_IP_CONFIG_SOURCE_VPN; + + nm_utils_ip6_address_clear_host_address(&route.network, &route.network, route.plen); + + if (priv->ip6_external_gw && IN6_ARE_ADDR_EQUAL(&route.network, priv->ip6_external_gw) + && route.plen == 128) { + /* Ignore host routes to the VPN gateway since NM adds one itself. + * Since NM knows more about the routing situation than the VPN + * server, we want to use the NM created route instead of whatever + * the server provides. + */ + goto next; + } + + nm_ip6_config_add_route(config, &route, NULL); + +next: + g_variant_unref(dest); + g_variant_unref(next_hop); + } + g_variant_iter_free(iter); + } + + if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, "b", &b)) + never_default = b; + + /* Merge in user overrides from the NMConnection's IPv6 setting */ + nm_ip6_config_merge_setting(config, s_ip, route_table, route_metric); + + if (!never_default && !nm_setting_ip_config_get_never_default(s_ip)) { + const NMPlatformIP6Route r = { + .ifindex = ip_ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_VPN, + .gateway = *(priv->ip6_internal_gw ?: &in6addr_any), + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .mss = mss, + }; + + nm_ip6_config_add_route(config, &r, NULL); + } + + nm_ip6_config_add_dependent_routes(config, route_table, route_metric, _is_device_vrf(self)); + + if (priv->ip6_config) { + nm_ip6_config_replace(priv->ip6_config, config, NULL); + g_object_unref(config); + } else { + priv->ip6_config = config; + nm_dbus_object_export(NM_DBUS_OBJECT(config)); + g_object_notify((GObject *) self, NM_ACTIVE_CONNECTION_IP6_CONFIG); + } + + nm_vpn_connection_config_maybe_complete(self, TRUE); +} + +static gboolean +connect_timeout_cb(gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + priv->connect_timeout = 0; + + /* Cancel activation if it's taken too long */ + if (priv->vpn_state == STATE_CONNECT || priv->vpn_state == STATE_IP_CONFIG_GET) { + _LOGW("VPN connection: connect timeout exceeded."); + _set_vpn_state(self, + STATE_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, + FALSE); + } + + return FALSE; +} + +static void +connect_success(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMSettingVpn * s_vpn; + guint32 timeout; + + s_vpn = nm_connection_get_setting_vpn(_get_applied_connection(self)); + g_assert(s_vpn); + + /* Timeout waiting for IP config signal from VPN service + * It is a configured value or 60 seconds */ + timeout = nm_setting_vpn_get_timeout(s_vpn); + if (timeout == 0) { + timeout = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("vpn.timeout"), + NULL, + 1, + G_MAXUINT32, + 60); + } + priv->connect_timeout = g_timeout_add_seconds(timeout, connect_timeout_cb, self); + + nm_clear_pointer(&priv->connect_hash, g_variant_unref); +} + +static void +connect_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMVpnConnection *self; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish(proxy, result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_VPN_CONNECTION(user_data); + + if (error) { + g_dbus_error_strip_remote_error(error); + _LOGW("VPN connection: failed to connect: '%s'", error->message); + _set_vpn_state(self, + STATE_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + FALSE); + } else + connect_success(self); +} + +static void +connect_interactive_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMVpnConnection * self; + NMVpnConnectionPrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish(proxy, result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_VPN_CONNECTION(user_data); + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + _LOGI("VPN connection: (ConnectInteractive) reply received"); + + if (g_error_matches(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED)) { + _LOGD("VPN connection: falling back to non-interactive connect"); + + /* Fall back to Connect() */ + g_dbus_proxy_call(priv->proxy, + "Connect", + g_variant_new("(@a{sa{sv}})", priv->connect_hash), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) connect_cb, + self); + } else if (error) { + g_dbus_error_strip_remote_error(error); + _LOGW("VPN connection: failed to connect interactively: '%s'", error->message); + _set_vpn_state(self, + STATE_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + FALSE); + } else + connect_success(self); +} + +/* Add a username to a hashed connection */ +static GVariant * +_hash_with_username(NMConnection *connection, const char *username) +{ + gs_unref_object NMConnection *dup = NULL; + NMSettingVpn * s_vpn; + + /* Shortcut if we weren't given a username or if there already was one in + * the VPN setting; don't bother duplicating the connection and everything. + */ + s_vpn = nm_connection_get_setting_vpn(connection); + g_assert(s_vpn); + if (username == NULL || nm_setting_vpn_get_user_name(s_vpn)) + return nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + + dup = nm_simple_connection_new_clone(connection); + g_assert(dup); + s_vpn = nm_connection_get_setting_vpn(dup); + g_assert(s_vpn); + g_object_set(s_vpn, NM_SETTING_VPN_USER_NAME, username, NULL); + return nm_connection_to_dbus(dup, NM_CONNECTION_SERIALIZE_ALL); +} + +static void +really_activate(NMVpnConnection *self, const char *username) +{ + NMVpnConnectionPrivate *priv; + GVariantBuilder details; + + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + g_return_if_fail(priv->vpn_state == STATE_NEED_AUTH); + + nm_clear_pointer(&priv->connect_hash, g_variant_unref); + priv->connect_hash = _hash_with_username(_get_applied_connection(self), username); + g_variant_ref_sink(priv->connect_hash); + + /* If at least one agent doesn't support VPN hints, then we can't use + * ConnectInteractive(), because that agent won't be able to pass hints + * from the VPN plugin's interactive secrets requests to the VPN authentication + * dialog and we won't get the secrets we need. In this case fall back to + * the old Connect() call. + */ + if (nm_agent_manager_all_agents_have_capability( + nm_agent_manager_get(), + nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(self)), + NM_SECRET_AGENT_CAPABILITY_VPN_HINTS)) { + _LOGD("Allowing interactive secrets as all agents have that capability"); + + g_variant_builder_init(&details, G_VARIANT_TYPE_VARDICT); + g_dbus_proxy_call(priv->proxy, + "ConnectInteractive", + g_variant_new("(@a{sa{sv}}a{sv})", priv->connect_hash, &details), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) connect_interactive_cb, + self); + } else { + _LOGD("Calling old Connect function as not all agents support interactive secrets"); + g_dbus_proxy_call(priv->proxy, + "Connect", + g_variant_new("(@a{sa{sv}})", priv->connect_hash), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) connect_cb, + self); + } + + _set_vpn_state(self, STATE_CONNECT, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); +} + +static void +failure_cb(GDBusProxy *proxy, guint32 reason, gpointer user_data) +{ + NMVpnConnection *self = NM_VPN_CONNECTION(user_data); + + plugin_failed(self, reason); +} + +static void +state_changed_cb(GDBusProxy *proxy, guint32 new_service_state, gpointer user_data) +{ + NMVpnConnection *self = NM_VPN_CONNECTION(user_data); + + plugin_state_changed(self, new_service_state); +} + +static void +secrets_required_cb(GDBusProxy * proxy, + const char * message, + const char *const *secrets, + gpointer user_data) +{ + NMVpnConnection *self = NM_VPN_CONNECTION(user_data); + + plugin_interactive_secrets_required(self, message, secrets); +} + +static void +config_cb(GDBusProxy *proxy, GVariant *dict, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + /* Only list to this signals during and after connection */ + if (priv->vpn_state >= STATE_NEED_AUTH) + nm_vpn_connection_config_get(self, dict); +} + +static void +ip4_config_cb(GDBusProxy *proxy, GVariant *dict, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + /* Only list to this signals during and after connection */ + if (priv->vpn_state >= STATE_NEED_AUTH) + nm_vpn_connection_ip4_config_get(self, dict); +} + +static void +ip6_config_cb(GDBusProxy *proxy, GVariant *dict, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + /* Only list to this signals during and after connection */ + if (priv->vpn_state >= STATE_NEED_AUTH) + nm_vpn_connection_ip6_config_get(self, dict); +} + +static void +_name_owner_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + char * owner; + + owner = g_dbus_proxy_get_name_owner(G_DBUS_PROXY(object)); + + if (owner && !priv->service_running) { + /* service appeared */ + priv->service_running = TRUE; + _LOGI("Saw the service appear; activating connection"); + + /* No need to wait for the timeout any longer */ + nm_clear_g_source(&priv->start_timeout); + + /* Expect success because the VPN service has already appeared */ + _nm_dbus_signal_connect(priv->proxy, + "Failure", + G_VARIANT_TYPE("(u)"), + G_CALLBACK(failure_cb), + self); + _nm_dbus_signal_connect(priv->proxy, + "StateChanged", + G_VARIANT_TYPE("(u)"), + G_CALLBACK(state_changed_cb), + self); + _nm_dbus_signal_connect(priv->proxy, + "SecretsRequired", + G_VARIANT_TYPE("(sas)"), + G_CALLBACK(secrets_required_cb), + self); + _nm_dbus_signal_connect(priv->proxy, + "Config", + G_VARIANT_TYPE("(a{sv})"), + G_CALLBACK(config_cb), + self); + _nm_dbus_signal_connect(priv->proxy, + "Ip4Config", + G_VARIANT_TYPE("(a{sv})"), + G_CALLBACK(ip4_config_cb), + self); + _nm_dbus_signal_connect(priv->proxy, + "Ip6Config", + G_VARIANT_TYPE("(a{sv})"), + G_CALLBACK(ip6_config_cb), + self); + + _set_vpn_state(self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + + /* Kick off the secrets requests; first we get existing system secrets + * and ask the plugin if these are sufficient, next we get all existing + * secrets from system and from user agents and ask the plugin again, + * and last we ask the user for new secrets if required. + */ + get_secrets(self, SECRETS_REQ_SYSTEM, NULL); + } else if (!owner && priv->service_running) { + /* service went away */ + priv->service_running = FALSE; + _LOGI("VPN service disappeared"); + nm_vpn_connection_disconnect(self, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED, + FALSE); + } + + g_free(owner); +} + +static gboolean +_daemon_exec_timeout(gpointer data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(data); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + _LOGW("Timed out waiting for the service to start"); + priv->start_timeout = 0; + nm_vpn_connection_disconnect(self, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, + FALSE); + return G_SOURCE_REMOVE; +} + +static int +_get_log_level(void) +{ + NMLogLevel level; + + /* curiously enough, nm-logging also uses syslog. But it + * maps NMLogLevel differently to the syslog levels then we + * do here. + * + * The reason is, that LOG_NOTICE is already something worth + * highlighting in the journal, but we have 3 levels that are + * lower then LOG_NOTICE (LOGL_TRACE, LOGL_DEBUG, LOGL_INFO), + * On the other hand, syslog only defines LOG_DEBUG and LOG_INFO. + * Thus, we must map them differently. + * + * Inside the VPN plugin, you might want to treat LOG_NOTICE as + * as low severity, not worthy to be highlighted (like NM does). */ + + level = nm_logging_get_level(LOGD_VPN_PLUGIN); + if (level != _LOGL_OFF) { + if (level <= LOGL_TRACE) + return LOG_DEBUG; + if (level <= LOGL_DEBUG) + return LOG_INFO; + if (level <= LOGL_INFO) + return LOG_NOTICE; + if (level <= LOGL_WARN) + return LOG_WARNING; + if (level <= LOGL_ERR) + return LOG_ERR; + } + + return LOG_EMERG; +} + +static gboolean +nm_vpn_service_daemon_exec(NMVpnConnection *self, GError **error) +{ + NMVpnConnectionPrivate *priv; + GPid pid; + char * vpn_argv[4]; + gboolean success = FALSE; + GError * spawn_error = NULL; + guint i, j, n_environ; + gs_free char ** envp = NULL; + char env_log_level[NM_STRLEN("NM_VPN_LOG_LEVEL=") + 100]; + char env_log_syslog[NM_STRLEN("NM_VPN_LOG_SYSLOG=") + 10]; + const int N_ENVIRON_EXTRA = 3; + char ** p_environ; + + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), FALSE); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + i = 0; + vpn_argv[i++] = (char *) nm_vpn_plugin_info_get_program(priv->plugin_info); + g_return_val_if_fail(vpn_argv[0], FALSE); + if (nm_vpn_plugin_info_supports_multiple(priv->plugin_info)) { + vpn_argv[i++] = "--bus-name"; + vpn_argv[i++] = priv->bus_name; + } + vpn_argv[i++] = NULL; + + /* we include and "config.h" defines _GNU_SOURCE for us. So, we have @environ. */ + p_environ = environ; + n_environ = p_environ ? g_strv_length(p_environ) : 0; + envp = g_new(char *, n_environ + N_ENVIRON_EXTRA); + for (i = 0, j = 0; j < n_environ; j++) { + if (g_str_has_prefix(p_environ[j], "NM_VPN_LOG_LEVEL=") + || g_str_has_prefix(p_environ[j], "NM_VPN_LOG_SYSLOG=")) + continue; + envp[i++] = p_environ[j]; + } + + /* NM_VPN_LOG_LEVEL: the syslog logging level for the plugin. */ + envp[i++] = nm_sprintf_buf(env_log_level, "NM_VPN_LOG_LEVEL=%d", _get_log_level()); + + /* NM_VPN_LOG_SYSLOG: whether to log to stdout or syslog. If NetworkManager itself runs in + * foreground, we also want the plugin to log to stdout. + * If the plugin runs in background, the plugin should prefer logging to syslog. Otherwise + * logging messages will be lost (unless using journald, in which case it wouldn't matter). */ + envp[i++] = nm_sprintf_buf(env_log_syslog, + "NM_VPN_LOG_SYSLOG=%c", + nm_logging_syslog_enabled() ? '1' : '0'); + + envp[i++] = NULL; + nm_assert(i <= n_environ + N_ENVIRON_EXTRA); + + success = g_spawn_async(NULL, vpn_argv, envp, 0, nm_utils_setpgid, NULL, &pid, &spawn_error); + + if (success) { + _LOGI("Started the VPN service, PID %ld", (long int) pid); + priv->start_timeout = g_timeout_add_seconds(5, _daemon_exec_timeout, self); + } else { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "%s", + spawn_error ? spawn_error->message : "unknown g_spawn_async() error"); + + if (spawn_error) + g_error_free(spawn_error); + } + + return success; +} + +static void +on_proxy_acquired(GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMVpnConnection * self; + NMVpnConnectionPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_VPN_CONNECTION(user_data); + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (error) { + _LOGE("failed to acquire dbus proxy for VPN service: %s", error->message); + _set_vpn_state(self, + STATE_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + FALSE); + return; + } + + priv->proxy = proxy; + + g_signal_connect(priv->proxy, "notify::g-name-owner", G_CALLBACK(_name_owner_changed), self); + _name_owner_changed(G_OBJECT(priv->proxy), NULL, self); + + if (priv->service_running) + return; + + if (!nm_vpn_service_daemon_exec(self, &error)) { + _LOGW("Could not launch the VPN service. error: %s.", error->message); + + nm_vpn_connection_disconnect(self, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + FALSE); + } +} + +void +nm_vpn_connection_activate(NMVpnConnection *self, NMVpnPluginInfo *plugin_info) +{ + NMVpnConnectionPrivate *priv; + NMSettingVpn * s_vpn; + const char * service; + + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + g_return_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + g_return_if_fail(!priv->plugin_info); + + s_vpn = nm_connection_get_setting_vpn(_get_applied_connection(self)); + g_return_if_fail(s_vpn); + + service = nm_vpn_plugin_info_get_service(plugin_info); + nm_assert(service); + + if (nm_vpn_plugin_info_supports_multiple(plugin_info)) { + const char *path; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(self)); + if (path) + path = strrchr(path, '/'); + g_return_if_fail(path); + + priv->bus_name = g_strdup_printf("%s.Connection_%s", service, &path[1]); + } else + priv->bus_name = g_strdup(service); + + priv->connection_can_persist = nm_setting_vpn_get_persistent(s_vpn); + priv->plugin_info = g_object_ref(plugin_info); + priv->cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + priv->bus_name, + NM_VPN_DBUS_PLUGIN_PATH, + NM_VPN_DBUS_PLUGIN_INTERFACE, + priv->cancellable, + (GAsyncReadyCallback) on_proxy_acquired, + self); + + _set_vpn_state(self, STATE_PREPARE, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); +} + +NMVpnConnectionState +nm_vpn_connection_get_vpn_state(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NM_VPN_CONNECTION_STATE_UNKNOWN); + + return _state_to_nm_vpn_state(NM_VPN_CONNECTION_GET_PRIVATE(self)->vpn_state); +} + +const char * +nm_vpn_connection_get_banner(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->banner; +} + +NMProxyConfig * +nm_vpn_connection_get_proxy_config(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->proxy_config; +} + +NMIP4Config * +nm_vpn_connection_get_ip4_config(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->ip4_config; +} + +NMIP6Config * +nm_vpn_connection_get_ip6_config(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->ip6_config; +} + +static int +_get_ip_iface_for_device(NMVpnConnection *self, const char **out_iface) +{ + NMDevice * parent_dev; + int ifindex; + const char *iface; + + nm_assert(NM_IS_VPN_CONNECTION(self)); + + /* the ifindex and the ifname in this case should come together. + * They either must be both set, or none. */ + + parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); + if (!parent_dev) + goto none; + ifindex = nm_device_get_ip_ifindex(parent_dev); + if (ifindex <= 0) + goto none; + iface = nm_device_get_ip_iface(parent_dev); + if (!iface) + goto none; + + NM_SET_OUT(out_iface, iface); + return ifindex; +none: + NM_SET_OUT(out_iface, NULL); + return 0; +} + +const char * +nm_vpn_connection_get_ip_iface(NMVpnConnection *self, gboolean fallback_device) +{ + NMVpnConnectionPrivate *priv; + const char * iface; + + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->ip_iface || !fallback_device) + return priv->ip_iface; + + _get_ip_iface_for_device(self, &iface); + return iface; +} + +int +nm_vpn_connection_get_ip_ifindex(NMVpnConnection *self, gboolean fallback_device) +{ + NMVpnConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->ip_ifindex > 0) + return priv->ip_ifindex; + if (!fallback_device) + return 0; + + return _get_ip_iface_for_device(self, NULL); +} + +guint32 +nm_vpn_connection_get_ip4_internal_gateway(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->ip4_internal_gw; +} + +struct in6_addr * +nm_vpn_connection_get_ip6_internal_gateway(NMVpnConnection *self) +{ + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); + + return NM_VPN_CONNECTION_GET_PRIVATE(self)->ip6_internal_gw; +} + +void +nm_vpn_connection_disconnect(NMVpnConnection * self, + NMActiveConnectionStateReason reason, + gboolean quitting) +{ + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + + _set_vpn_state(self, STATE_DISCONNECTED, reason, quitting); +} + +gboolean +nm_vpn_connection_deactivate(NMVpnConnection * self, + NMActiveConnectionStateReason reason, + gboolean quitting) +{ + NMVpnConnectionPrivate *priv; + gboolean success = FALSE; + + g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), FALSE); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + if (priv->vpn_state > STATE_UNKNOWN && priv->vpn_state <= STATE_DEACTIVATING) { + _set_vpn_state(self, STATE_DEACTIVATING, reason, quitting); + success = TRUE; + } + return success; +} + +/*****************************************************************************/ + +static void +plugin_need_secrets_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMVpnConnection * self; + NMVpnConnectionPrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + const char * setting_name; + + reply = _nm_dbus_proxy_call_finish(proxy, result, G_VARIANT_TYPE("(s)"), &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_VPN_CONNECTION(user_data); + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (error) { + g_dbus_error_strip_remote_error(error); + _LOGE("plugin NeedSecrets request #%d failed: %s", priv->secrets_idx + 1, error->message); + _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); + return; + } + + g_variant_get(reply, "(&s)", &setting_name); + if (!strlen(setting_name)) { + _LOGD("service indicated no additional secrets required"); + + /* No secrets required; we can start the VPN */ + really_activate(self, priv->username); + return; + } + + /* More secrets required */ + if (priv->secrets_idx == SECRETS_REQ_NEW) { + _LOGE("final secrets request failed to provide sufficient secrets"); + _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); + } else { + _LOGD("service indicated additional secrets required"); + get_secrets(self, priv->secrets_idx + 1, NULL); + } +} + +static void +plugin_new_secrets_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMVpnConnection *self; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish(proxy, result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_VPN_CONNECTION(user_data); + + if (error) { + g_dbus_error_strip_remote_error(error); + _LOGE("sending new secrets to the plugin failed: %s", error->message); + _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); + } else + _set_vpn_state(self, STATE_CONNECT, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); +} + +static void +get_secrets_cb(NMSettingsConnection * connection, + NMSettingsConnectionCallId *call_id, + const char * agent_username, + const char * setting_name, + GError * error, + gpointer user_data) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(user_data); + NMVpnConnectionPrivate *priv; + GVariant * dict; + + g_return_if_fail(NM_IS_VPN_CONNECTION(self)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + g_return_if_fail(connection && connection == _get_settings_connection(self, FALSE)); + g_return_if_fail(call_id == priv->secrets_id); + + priv->secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (error && priv->secrets_idx >= SECRETS_REQ_NEW) { + _LOGE("Failed to request VPN secrets #%d: %s", priv->secrets_idx + 1, error->message); + _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); + return; + } + + /* Cache the username for later */ + if (agent_username) { + g_free(priv->username); + priv->username = g_strdup(agent_username); + } + + dict = _hash_with_username(_get_applied_connection(self), priv->username); + + if (priv->secrets_idx == SECRETS_REQ_INTERACTIVE) { + _LOGD("sending secrets to the plugin"); + + /* Send the secrets back to the plugin */ + g_dbus_proxy_call(priv->proxy, + "NewSecrets", + g_variant_new("(@a{sa{sv}})", dict), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) plugin_new_secrets_cb, + self); + } else { + _LOGD("asking service if additional secrets are required"); + + /* Ask the VPN service if more secrets are required */ + g_dbus_proxy_call(priv->proxy, + "NeedSecrets", + g_variant_new("(@a{sa{sv}})", dict), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->cancellable, + (GAsyncReadyCallback) plugin_need_secrets_cb, + self); + } +} + +static void +get_secrets(NMVpnConnection *self, SecretsReq secrets_idx, const char *const *hints) +{ + NMVpnConnectionPrivate * priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE; + + g_return_if_fail(secrets_idx < SECRETS_REQ_LAST); + priv->secrets_idx = secrets_idx; + + cancel_get_secrets(self); + + _LOGD("requesting VPN secrets pass #%d", priv->secrets_idx + 1); + + switch (priv->secrets_idx) { + case SECRETS_REQ_SYSTEM: + flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM; + break; + case SECRETS_REQ_EXISTING: + flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE; + break; + case SECRETS_REQ_NEW: + case SECRETS_REQ_INTERACTIVE: + flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + break; + default: + g_assert_not_reached(); + } + + if (nm_active_connection_get_user_requested(NM_ACTIVE_CONNECTION(self))) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED; + + priv->secrets_id = nm_settings_connection_get_secrets( + _get_settings_connection(self, FALSE), + _get_applied_connection(self), + nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(self)), + NM_SETTING_VPN_SETTING_NAME, + flags, + hints, + get_secrets_cb, + self); + g_return_if_fail(priv->secrets_id); +} + +static void +plugin_interactive_secrets_required(NMVpnConnection * self, + const char * message, + const char *const *secrets) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + const gsize secrets_len = NM_PTRARRAY_LEN(secrets); + gsize i; + gs_free const char ** hints = NULL; + gs_free char * message_hint = NULL; + + if (!NM_IN_SET(priv->vpn_state, STATE_CONNECT, STATE_NEED_AUTH)) { + _LOGD("VPN plugin: requested secrets; state %s (%d); ignore request in current state", + vpn_state_to_string_a(priv->vpn_state), + priv->vpn_state); + return; + } + + _LOGI("VPN plugin: requested secrets; state %s (%d)", + vpn_state_to_string_a(priv->vpn_state), + priv->vpn_state); + + priv->secrets_idx = SECRETS_REQ_INTERACTIVE; + _set_vpn_state(self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); + + /* Copy hints and add message to the end */ + hints = g_new(const char *, secrets_len + 2); + for (i = 0; i < secrets_len; i++) + hints[i] = secrets[i]; + if (message) { + message_hint = g_strdup_printf("x-vpn-message:%s", message); + hints[i++] = message_hint; + } + hints[i] = NULL; + nm_assert(i < secrets_len + 2); + + get_secrets(self, SECRETS_REQ_INTERACTIVE, hints); +} + +/*****************************************************************************/ + +static void +device_changed(NMActiveConnection *active, NMDevice *new_device, NMDevice *old_device) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(active); + + if (!_service_and_connection_can_persist(NM_VPN_CONNECTION(active))) + return; + if (priv->vpn_state < STATE_CONNECT || priv->vpn_state > STATE_ACTIVATED) + return; + + /* Route-based VPNs must update their routing and send a new IP config + * since all their routes need to be adjusted for new_device. + */ + if (priv->ip_ifindex <= 0) + return; + + /* Device changed underneath the VPN connection. Let the plugin figure + * out that connectivity is down and start its reconnect attempt if it + * needs to. + */ + if (old_device) + remove_parent_device_config(NM_VPN_CONNECTION(active), old_device); + + if (new_device) + apply_parent_device_config(NM_VPN_CONNECTION(active)); +} + +/*****************************************************************************/ + +static void +nm_vpn_connection_init(NMVpnConnection *self) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + priv->vpn_state = STATE_WAITING; + priv->secrets_idx = SECRETS_REQ_SYSTEM; + priv->netns = g_object_ref(nm_netns_get()); +} + +static void +dispose(GObject *object) +{ + NMVpnConnection * self = NM_VPN_CONNECTION(object); + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); + + if (priv->proxy) + g_signal_handlers_disconnect_by_data(priv->proxy, self); + + nm_clear_g_source(&priv->start_timeout); + + nm_clear_pointer(&priv->connect_hash, g_variant_unref); + + nm_clear_pointer(&priv->ip4_dev_route_blacklist, g_ptr_array_unref); + + nm_clear_g_source(&priv->connect_timeout); + + dispatcher_cleanup(self); + + cancel_get_secrets(self); + + nm_clear_g_cancellable(&priv->cancellable); + + g_clear_object(&priv->proxy_config); + nm_dbus_object_clear_and_unexport(&priv->ip4_config); + nm_dbus_object_clear_and_unexport(&priv->ip6_config); + g_clear_object(&priv->proxy); + g_clear_object(&priv->plugin_info); + + fw_call_cleanup(self); + + nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); + + G_OBJECT_CLASS(nm_vpn_connection_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(object); + + g_free(priv->banner); + g_free(priv->ip_iface); + g_free(priv->username); + g_free(priv->ip6_internal_gw); + g_free(priv->ip6_external_gw); + + G_OBJECT_CLASS(nm_vpn_connection_parent_class)->finalize(object); + + g_clear_object(&priv->netns); +} + +static gboolean +ip_config_valid(VpnState state) +{ + return (state == STATE_PRE_UP || state == STATE_ACTIVATED); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(object); + NMDevice * parent_dev; + + switch (prop_id) { + case PROP_VPN_STATE: + g_value_set_uint(value, _state_to_nm_vpn_state(priv->vpn_state)); + break; + case PROP_BANNER: + g_value_set_string(value, priv->banner ?: ""); + break; + case PROP_IP4_CONFIG: + nm_dbus_utils_g_value_set_object_path(value, + ip_config_valid(priv->vpn_state) ? priv->ip4_config + : NULL); + break; + case PROP_IP6_CONFIG: + nm_dbus_utils_g_value_set_object_path(value, + ip_config_valid(priv->vpn_state) ? priv->ip6_config + : NULL); + break; + case PROP_MASTER: + parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(object)); + nm_dbus_utils_g_value_set_object_path(value, parent_dev); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static const GDBusSignalInfo signal_info_vpn_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "VpnStateChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("state", "u"), + NM_DEFINE_GDBUS_ARG_INFO("reason", "u"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_vpn_connection = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_VPN_CONNECTION, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_vpn_state_changed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("VpnState", + "u", + NM_VPN_CONNECTION_VPN_STATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Banner", + "s", + NM_VPN_CONNECTION_BANNER), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_vpn_connection_class_init(NMVpnConnectionClass *connection_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(connection_class); + NMActiveConnectionClass *active_class = NM_ACTIVE_CONNECTION_CLASS(connection_class); + NMDBusObjectClass * dbus_object_class = NM_DBUS_OBJECT_CLASS(connection_class); + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_vpn_connection); + + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + active_class->device_state_changed = device_state_changed; + active_class->device_changed = device_changed; + + obj_properties[PROP_VPN_STATE] = g_param_spec_uint(NM_VPN_CONNECTION_VPN_STATE, + "", + "", + NM_VPN_CONNECTION_STATE_UNKNOWN, + NM_VPN_CONNECTION_STATE_DISCONNECTED, + NM_VPN_CONNECTION_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BANNER] = g_param_spec_string(NM_VPN_CONNECTION_BANNER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + g_object_class_override_property(object_class, PROP_MASTER, NM_ACTIVE_CONNECTION_MASTER); + g_object_class_override_property(object_class, + PROP_IP4_CONFIG, + NM_ACTIVE_CONNECTION_IP4_CONFIG); + g_object_class_override_property(object_class, + PROP_IP6_CONFIG, + NM_ACTIVE_CONNECTION_IP6_CONFIG); + + signals[INTERNAL_STATE_CHANGED] = g_signal_new(NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); + + signals[INTERNAL_RETRY_AFTER_FAILURE] = + g_signal_new(NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/core/vpn/nm-vpn-connection.h b/src/core/vpn/nm-vpn-connection.h new file mode 100644 index 0000000..66ec7ee --- /dev/null +++ b/src/core/vpn/nm-vpn-connection.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2011 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_VPN_CONNECTION_H__ +#define __NM_VPN_CONNECTION_H__ + +#include "nm-vpn-dbus-interface.h" +#include "devices/nm-device.h" +#include "nm-libnm-core-intern/nm-auth-subject.h" +#include "nm-active-connection.h" +#include "nm-vpn-plugin-info.h" + +#define NM_TYPE_VPN_CONNECTION (nm_vpn_connection_get_type()) +#define NM_VPN_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VPN_CONNECTION, NMVpnConnection)) +#define NM_VPN_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_VPN_CONNECTION, NMVpnConnectionClass)) +#define NM_IS_VPN_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_VPN_CONNECTION)) +#define NM_IS_VPN_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_VPN_CONNECTION)) +#define NM_VPN_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_VPN_CONNECTION, NMVpnConnectionClass)) + +/* Properties */ +#define NM_VPN_CONNECTION_VPN_STATE "vpn-state" +#define NM_VPN_CONNECTION_BANNER "banner" + +/* Signals */ +#define NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED "internal-state-changed" +#define NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE "internal-retry-after-failure" + +typedef struct _NMVpnConnectionClass NMVpnConnectionClass; + +GType nm_vpn_connection_get_type(void); + +NMVpnConnection *nm_vpn_connection_new(NMSettingsConnection * settings_connection, + NMDevice * parent_device, + const char * specific_object, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + NMAuthSubject * subject); + +void nm_vpn_connection_activate(NMVpnConnection *self, NMVpnPluginInfo *plugin_info); +NMVpnConnectionState nm_vpn_connection_get_vpn_state(NMVpnConnection *self); +const char * nm_vpn_connection_get_banner(NMVpnConnection *self); +const char * nm_vpn_connection_get_service(NMVpnConnection *self); + +gboolean nm_vpn_connection_deactivate(NMVpnConnection * self, + NMActiveConnectionStateReason reason, + gboolean quitting); +void nm_vpn_connection_disconnect(NMVpnConnection * self, + NMActiveConnectionStateReason reason, + gboolean quitting); + +NMProxyConfig *nm_vpn_connection_get_proxy_config(NMVpnConnection *self); + +NMIP4Config * nm_vpn_connection_get_ip4_config(NMVpnConnection *self); +NMIP6Config * nm_vpn_connection_get_ip6_config(NMVpnConnection *self); +const char * nm_vpn_connection_get_ip_iface(NMVpnConnection *self, gboolean fallback_device); +int nm_vpn_connection_get_ip_ifindex(NMVpnConnection *self, gboolean fallback_device); +guint32 nm_vpn_connection_get_ip4_internal_gateway(NMVpnConnection *self); +struct in6_addr *nm_vpn_connection_get_ip6_internal_gateway(NMVpnConnection *self); + +guint32 nm_vpn_connection_get_ip4_route_metric(NMVpnConnection *self); +guint32 nm_vpn_connection_get_ip6_route_metric(NMVpnConnection *self); + +#endif /* __NM_VPN_CONNECTION_H__ */ diff --git a/src/core/vpn/nm-vpn-manager.c b/src/core/vpn/nm-vpn-manager.c new file mode 100644 index 0000000..65f9e56 --- /dev/null +++ b/src/core/vpn/nm-vpn-manager.c @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2012 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-vpn-manager.h" + +#include "nm-vpn-plugin-info.h" +#include "nm-vpn-connection.h" +#include "nm-setting-vpn.h" +#include "nm-vpn-dbus-interface.h" +#include "nm-core-internal.h" + +typedef struct { + GSList * plugins; + GFileMonitor *monitor_etc; + GFileMonitor *monitor_lib; + gulong monitor_id_etc; + gulong monitor_id_lib; + + /* This is only used for services that don't support multiple + * connections, to guard access to them. */ + GHashTable *active_services; +} NMVpnManagerPrivate; + +struct _NMVpnManager { + GObject parent; + NMVpnManagerPrivate _priv; +}; + +struct _NMVpnManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMVpnManager, nm_vpn_manager, G_TYPE_OBJECT) + +#define NM_VPN_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMVpnManager, NM_IS_VPN_MANAGER) + +/*****************************************************************************/ + +static void +vpn_state_changed(NMVpnConnection *vpn, GParamSpec *pspec, NMVpnManager *manager) +{ + NMVpnManagerPrivate * priv = NM_VPN_MANAGER_GET_PRIVATE(manager); + NMActiveConnectionState state = nm_active_connection_get_state(NM_ACTIVE_CONNECTION(vpn)); + const char * service_name = nm_vpn_connection_get_service(vpn); + + if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + g_hash_table_remove(priv->active_services, service_name); + g_signal_handlers_disconnect_by_func(vpn, vpn_state_changed, manager); + g_object_unref(manager); + } +} + +gboolean +nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn, GError **error) +{ + NMVpnManagerPrivate *priv; + NMVpnPluginInfo * plugin_info; + const char * service_name; + NMDevice * device; + + g_return_val_if_fail(NM_IS_VPN_MANAGER(manager), FALSE); + g_return_val_if_fail(NM_IS_VPN_CONNECTION(vpn), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_VPN_MANAGER_GET_PRIVATE(manager); + device = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(vpn)); + g_assert(device); + if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED + && nm_device_get_state(device) != NM_DEVICE_STATE_SECONDARIES) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The base device for the VPN connection was not active."); + return FALSE; + } + + service_name = nm_vpn_connection_get_service(vpn); + + plugin_info = nm_vpn_plugin_info_list_find_by_service(priv->plugins, service_name); + if (!plugin_info) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE, + "The VPN service '%s' was not installed.", + service_name); + return FALSE; + } + + if (!nm_vpn_plugin_info_supports_multiple(plugin_info) + && g_hash_table_contains(priv->active_services, service_name)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE, + "The '%s' plugin only supports a single active connection.", + nm_vpn_plugin_info_get_name(plugin_info)); + return FALSE; + } + + nm_vpn_connection_activate(vpn, plugin_info); + + if (!nm_vpn_plugin_info_supports_multiple(plugin_info)) { + /* Block activations of the connections of the same service type. */ + g_hash_table_add(priv->active_services, g_strdup(service_name)); + g_signal_connect(vpn, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(vpn_state_changed), + g_object_ref(manager)); + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +try_add_plugin(NMVpnManager *self, NMVpnPluginInfo *plugin_info) +{ + NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE(self); + const char * program; + + program = nm_vpn_plugin_info_get_program(plugin_info); + if (!program || !*program) + return; + + /* Make sure we don't add dupes. + * We don't really allow reload of the same file. What we do allow is however to + * delete a file and re-add it. */ + if (nm_vpn_plugin_info_list_find_by_filename(priv->plugins, + nm_vpn_plugin_info_get_filename(plugin_info))) + return; + if (!nm_vpn_plugin_info_list_add(&priv->plugins, plugin_info, NULL)) + return; +} + +static void +vpn_dir_changed(GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMVpnManager * self = NM_VPN_MANAGER(user_data); + NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE(self); + NMVpnPluginInfo * plugin_info; + gs_free char * path = NULL; + GError * error = NULL; + + path = g_file_get_path(file); + if (!nm_vpn_plugin_info_validate_filename(path)) + return; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + plugin_info = nm_vpn_plugin_info_list_find_by_filename(priv->plugins, path); + if (!plugin_info) + break; + + nm_log_dbg(LOGD_VPN, "vpn: service file %s deleted", path); + nm_vpn_plugin_info_list_remove(&priv->plugins, plugin_info); + break; + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + plugin_info = nm_vpn_plugin_info_list_find_by_filename(priv->plugins, path); + if (plugin_info) { + /* we don't support reloading an existing plugin. You can only remove the file + * and re-add it. By reloading we want to support the use case of installing + * a VPN plugin after NM started. No need to burden ourself with a complete + * reload. */ + break; + } + + if (!_nm_vpn_plugin_info_check_file(path, TRUE, TRUE, 0, NULL, NULL, &error)) { + nm_log_dbg(LOGD_VPN, "vpn: ignore changed service file %s (%s)", path, error->message); + g_clear_error(&error); + break; + } + plugin_info = nm_vpn_plugin_info_new_from_file(path, &error); + if (!plugin_info) { + nm_log_dbg(LOGD_VPN, + "vpn: ignore changed service file %s due to invalid content (%s)", + path, + error->message); + g_clear_error(&error); + break; + } + + nm_log_dbg(LOGD_VPN, "vpn: service file %s created or modified", path); + try_add_plugin(self, plugin_info); + g_object_unref(plugin_info); + break; + default: + nm_log_dbg(LOGD_VPN, "vpn: service file %s change event %d", path, event_type); + break; + } +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMVpnManager, nm_vpn_manager_get, NM_TYPE_VPN_MANAGER); + +static void +nm_vpn_manager_init(NMVpnManager *self) +{ + NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE(self); + GFile * file; + GSList * infos, *info; + const char * conf_dir_etc = _nm_vpn_plugin_info_get_default_dir_etc(); + const char * conf_dir_lib = _nm_vpn_plugin_info_get_default_dir_lib(); + + /* Watch the VPN directory for changes */ + file = g_file_new_for_path(conf_dir_lib); + priv->monitor_lib = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + if (priv->monitor_lib) { + priv->monitor_id_lib = + g_signal_connect(priv->monitor_lib, "changed", G_CALLBACK(vpn_dir_changed), self); + } + + file = g_file_new_for_path(conf_dir_etc); + priv->monitor_etc = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + if (priv->monitor_etc) { + priv->monitor_id_etc = + g_signal_connect(priv->monitor_etc, "changed", G_CALLBACK(vpn_dir_changed), self); + } + + /* first read conf_dir_lib. The name files are not really user configuration, but + * plugin configuration. Hence we expect ~newer~ plugins to install their files + * in /usr/lib/NetworkManager. We want to prefer those files. + * In case of no-conflict, the order doesn't matter. */ + infos = _nm_vpn_plugin_info_list_load_dir(conf_dir_lib, TRUE, 0, NULL, NULL); + for (info = infos; info; info = info->next) + try_add_plugin(self, info->data); + g_slist_free_full(infos, g_object_unref); + + infos = _nm_vpn_plugin_info_list_load_dir(conf_dir_etc, TRUE, 0, NULL, NULL); + for (info = infos; info; info = info->next) + try_add_plugin(self, info->data); + g_slist_free_full(infos, g_object_unref); + + priv->active_services = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); +} + +static void +dispose(GObject *object) +{ + NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE(object); + + if (priv->monitor_etc) { + if (priv->monitor_id_etc) + g_signal_handler_disconnect(priv->monitor_etc, priv->monitor_id_etc); + g_file_monitor_cancel(priv->monitor_etc); + g_clear_object(&priv->monitor_etc); + } + + if (priv->monitor_lib) { + if (priv->monitor_id_lib) + g_signal_handler_disconnect(priv->monitor_lib, priv->monitor_id_lib); + g_file_monitor_cancel(priv->monitor_lib); + g_clear_object(&priv->monitor_lib); + } + + while (priv->plugins) + nm_vpn_plugin_info_list_remove(&priv->plugins, priv->plugins->data); + + g_hash_table_unref(priv->active_services); + + G_OBJECT_CLASS(nm_vpn_manager_parent_class)->dispose(object); +} + +static void +nm_vpn_manager_class_init(NMVpnManagerClass *manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(manager_class); + + object_class->dispose = dispose; +} diff --git a/src/core/vpn/nm-vpn-manager.h b/src/core/vpn/nm-vpn-manager.h new file mode 100644 index 0000000..41ecb8d --- /dev/null +++ b/src/core/vpn/nm-vpn-manager.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2011 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_VPN_MANAGER_H__ +#define __NM_VPN_MANAGER_H__ + +#include "nm-vpn-connection.h" + +#define NM_TYPE_VPN_MANAGER (nm_vpn_manager_get_type()) +#define NM_VPN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VPN_MANAGER, NMVpnManager)) +#define NM_VPN_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_VPN_MANAGER, NMVpnManagerClass)) +#define NM_IS_VPN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_VPN_MANAGER)) +#define NM_IS_VPN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_VPN_MANAGER)) +#define NM_VPN_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_VPN_MANAGER, NMVpnManagerClass)) + +typedef struct _NMVpnManager NMVpnManager; +typedef struct _NMVpnManagerClass NMVpnManagerClass; + +GType nm_vpn_manager_get_type(void); + +NMVpnManager *nm_vpn_manager_get(void); + +gboolean +nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn, GError **error); + +#endif /* __NM_VPN_MANAGER_H__ */ diff --git a/tools/check-config-options.sh b/tools/check-config-options.sh index d9d4431..4cbd485 100755 --- a/tools/check-config-options.sh +++ b/tools/check-config-options.sh @@ -5,20 +5,20 @@ ret=0 get_supported_options() { - awk '/START OPTION LIST/{flag=1;next}/END OPTION LIST/{flag=0}flag' "$srcdir/src/nm-config.c" | + awk '/START OPTION LIST/{flag=1;next}/END OPTION LIST/{flag=0}flag' "$srcdir/src/core/nm-config.c" | grep -o 'NM_CONFIG_KEYFILE_KEY_\w*' } get_missing_options() { - grep -v '/\* check-config-options skip \*/' "$srcdir/src/nm-config.h" | + grep -v '/\* check-config-options skip \*/' "$srcdir/src/core/nm-config.h" | grep -o 'NM_CONFIG_KEYFILE_KEY_\w*' | grep -v -Fx -f <(get_supported_options) } get_src_con_defaults() { - sed -n 's/\/dev/null); do + (for f in $(find ./src/core/settings/plugins/*/${libs} \ + ./src/core/devices/*/${libs} \ + ./src/core/ppp/${libs} -name '*.so' 2>/dev/null); do call_nm "$f" | sed -n 's/^\([U]\) \(\(nm_\|nmp_\|_nm\|NM\|_NM\|nmtst_\|c_siphash_\|c_list_\).*\)$/\2/p' done) | @@ -82,7 +82,7 @@ do_rebuild() { } do_update() { - do_generate > ./src/NetworkManager.ver + do_generate > ./src/core/NetworkManager.ver } SYMBOLS_MISSING="$(get_symbols_missing | pretty)" @@ -110,7 +110,7 @@ else libs=.libs/ fi -test -f ./src/${libs}libNetworkManager.a || die "must be called from NetworkManager top build dir after building the tree" +test -f ./src/core/${libs}libNetworkManager.a || die "must be called from NetworkManager top build dir after building the tree" case "$1" in rebuild) @@ -125,8 +125,8 @@ case "$1" in if test -z "${NM_BUILD_NO_CREATE_EXPORTS+x}"; then do_update else - if test -f "./src/NetworkManager.ver"; then - touch ./src/NetworkManager.ver + if test -f "./src/core/NetworkManager.ver"; then + touch ./src/core/NetworkManager.ver fi fi ;; diff --git a/tools/run-nm-test.sh b/tools/run-nm-test.sh index 9a5015f..6d835a8 100755 --- a/tools/run-nm-test.sh +++ b/tools/run-nm-test.sh @@ -23,6 +23,43 @@ _is_true() { esac } +usage() { + echo "$0 [\$OPTIONS] [--] \$TEST [\$TEST_OPTIONS]" + echo "" + echo " Runs the unit test with setting up dbus-session (as necessary)," + echo " optionally build the test first, and run valgrind" + echo "" + echo " --help|-h: help" + echo " --launch-dbus: the test runner by default automatically launches a D-Bus session" + echo " depending on a hard-coded list of tests that require it. This flag overwrites" + echo " the automatism to always launch a D-Bus session" + echo " --no-launch-dbus|-D: prevent launching a D-Bus session" + echo " --no-libtool: when running with valgrind, the script tries automatically to" + echo " use libtool as necessary. This disables libtool usage" + echo " --make-first|-m: before running the test, make it (only works with autotools build)" + echo " --valgrind|-v: run under valgrind" + echo " --no-valgrind|-V: disable running under valgrind (overrides NMTST_USE_VALGRIND=1)" + echo " -d: set NMTST_DEBUG=d" + echo " --test|-t \$TEST: set the test that should be run" + echo "" + echo " With \"--test\" and \"--\" you can select the test and which arguments are" + echo " passed to the test. You can omit these, in which case the first unknown parameter" + echo " is the test and all other unknown parameters are passed to the test. For example" + echo " $0 -m --test src/core/tests/test-core -- -p /general/match-spec/device" + echo " can also be called as" + echo " $0 src/core/tests/test-core -p /general/match-spec/device -m" + echo "" + echo " The following environment variables are honored:" + echo " NMTST_USE_VALGRIND=0|1: enable/disable valgrind" + echo " NMTST_LIBTOOL=: libtool path (or disable)" + echo " NMTST_LAUNCH_DBUS=0|1: whether to lounch a D-Bus session" + echo " NMTST_SET_DEBUG=0|1: saet NMTST_DEBUG=d" + echo "" + echo " This script is also called by the build system as test wrapper. In that case" + echo " different, internal command line syntax is used. In that case, environment variables" + echo " are still honored, so \`NMTST_USE_VALGRIND=1 make check\` works as expected" +} + SCRIPT_PATH="${SCRIPT_PATH:-$(readlink -f "$(dirname "$0")")}" VALGRIND_ERROR=37 @@ -108,6 +145,10 @@ else unset TEST while test $# -gt 0; do case "$1" in + --help|-h) + usage + exit 0 + ;; "--launch-dbus") NMTST_LAUNCH_DBUS=1 shift @@ -143,6 +184,10 @@ else ;; "--") shift + if test -z "${TEST+x}"; then + TEST="$1"; + shift + fi TEST_ARGV+=("$@") break ;; diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 6d85a02..fe5e4d4 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -1424,6 +1424,7 @@ class ActiveConnection(ExportedObj): self._activation_id = None self._deactivation_id = None + self.activation_state_change_delay_ms = 50 s_con = con_inst.con_hash[NM.SETTING_CONNECTION_SETTING_NAME] @@ -1495,7 +1496,9 @@ class ActiveConnection(ExportedObj): def _activation_step1(self): assert self._activation_id is not None - self._activation_id = GLib.timeout_add(50, self._activation_step2) + self._activation_id = GLib.timeout_add( + self.activation_state_change_delay_ms, self._activation_step2 + ) self.device.set_active_connection(self) self.device.set_state(NM.DeviceState.PREPARE, NM.DeviceStateReason.NONE) self._set_state( @@ -1521,7 +1524,9 @@ class ActiveConnection(ExportedObj): def start_activation(self): assert self._activation_id is None - self._activation_id = GLib.timeout_add(50, self._activation_step1) + self._activation_id = GLib.timeout_add( + self.activation_state_change_delay_ms, self._activation_step1 + ) def start_deactivation(self): assert self._deactivation_id is None @@ -1964,6 +1969,16 @@ class NetworkManager(ExportedObj): def SetActiveConnectionFailure(self, connection_id, failure): gl.force_activation_failure[connection_id] = failure + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature="ou", out_signature="") + def SetActiveConnectionStateChangedDelay(self, devpath, delay_ms): + for ac in reversed(self.active_connections): + if ac.device.path == devpath: + ac.activation_state_change_delay_ms = delay_ms + return + raise BusErr.UnknownDeviceException( + "Device with iface '%s' not found" % devpath + ) + @dbus.service.method( dbus_interface=IFACE_TEST, in_signature="ouu", out_signature="" )